@keenocean/open-agent-sdk
v0.2.2
Published
Open-source Agent SDK. Runs the full agent loop in-process — no local CLI required. Deploy anywhere: cloud, serverless, Docker, CI/CD.
Maintainers
Readme
Open Agent SDK (TypeScript)
Open-source Agent SDK that runs the full agent loop in-process — no subprocess or CLI required. Supports both Anthropic and OpenAI-compatible APIs. Deploy anywhere: cloud, serverless, Docker, CI/CD.
Get started
npm install @keenocean/open-agent-sdkSet your API key:
export CODEANY_API_KEY=your-api-keyOpenAI-compatible models
Works with OpenAI, DeepSeek, Qwen, Mistral, or any OpenAI-compatible endpoint:
export CODEANY_API_TYPE=openai-completions
export CODEANY_API_KEY=sk-...
export CODEANY_BASE_URL=https://api.openai.com/v1
export CODEANY_MODEL=gpt-4oThird-party Anthropic-compatible providers
export CODEANY_BASE_URL=https://openrouter.ai/api
export CODEANY_API_KEY=sk-or-...
export CODEANY_MODEL=anthropic/claude-sonnet-4Quick start
One-shot query (streaming)
import { query } from "@keenocean/open-agent-sdk";
for await (const message of query({
prompt: "Read package.json and tell me the project name.",
options: {
allowedTools: ["Read", "Glob"],
},
})) {
if (message.type === "assistant") {
for (const block of message.message.content) {
if ("text" in block) console.log(block.text);
}
}
}Token streaming
Set includePartialMessages: true to receive provider-level deltas before the
final assistant message:
import { query } from "@keenocean/open-agent-sdk";
for await (const message of query({
prompt: "Write a short haiku.",
options: { includePartialMessages: true },
})) {
if (message.type === "partial_message" && message.partial.type === "text") {
process.stdout.write(message.partial.text ?? "");
}
}partial_message events stream text/tool-call fragments. A final assistant
event is still emitted with the complete normalized response for conversation
history, tool execution, and compatibility with existing consumers.
Simple blocking prompt
import { createAgent } from "@keenocean/open-agent-sdk";
const agent = createAgent({ model: "claude-sonnet-4-6" });
const result = await agent.prompt("What files are in this project?");
console.log(result.text);
console.log(
`Turns: ${result.num_turns}, Tokens: ${result.usage.input_tokens + result.usage.output_tokens}`,
);OpenAI / GPT models
import { createAgent } from "@keenocean/open-agent-sdk";
const agent = createAgent({
apiType: "openai-completions",
model: "gpt-4o",
apiKey: "sk-...",
baseURL: "https://api.openai.com/v1",
});
const result = await agent.prompt("What files are in this project?");
console.log(result.text);The apiType is auto-detected from model name — models containing gpt-, o1, o3, deepseek, qwen, mistral, etc. automatically use openai-completions.
Multi-turn conversation
import { createAgent } from "@keenocean/open-agent-sdk";
const agent = createAgent({
maxTurns: 5,
tools: ["Read", "Write"],
permissionMode: "acceptEdits",
sandbox: {
enabled: true,
filesystem: {
allowRead: [".", "/tmp"],
allowWrite: [".", "/tmp"],
},
},
});
const r1 = await agent.prompt(
'Create a file /tmp/hello.txt with "Hello World"',
);
console.log(r1.text);
const r2 = await agent.prompt("Read back the file you just created");
console.log(r2.text);
console.log(`Session messages: ${agent.getMessages().length}`);Custom tools (Zod schema)
import { z } from "zod";
import { query, tool, createSdkMcpServer } from "@keenocean/open-agent-sdk";
const getWeather = tool(
"get_weather",
"Get the temperature for a city",
{ city: z.string().describe("City name") },
async ({ city }) => ({
content: [{ type: "text", text: `${city}: 22°C, sunny` }],
}),
);
const server = createSdkMcpServer({ name: "weather", tools: [getWeather] });
for await (const msg of query({
prompt: "What is the weather in Tokyo?",
options: {
mcpServers: { weather: server },
allowedTools: ["mcp__weather__*"],
sandbox: { enabled: false },
canUseTool: async (tool) =>
tool.name.startsWith("mcp__weather__")
? { behavior: "allow" }
: { behavior: "deny", message: "Only weather MCP tools are allowed" },
},
})) {
if (msg.type === "result")
console.log(`Done: $${msg.total_cost_usd?.toFixed(4)}`);
}Custom tools (low-level)
import {
createAgent,
getSafeBaseTools,
defineTool,
} from "@keenocean/open-agent-sdk";
const calculator = defineTool({
name: "Calculator",
description: "Evaluate a math expression",
inputSchema: {
type: "object",
properties: { expression: { type: "string" } },
required: ["expression"],
},
isReadOnly: true,
sandboxAware: true,
async call(input) {
if (!/^[\d+\-*/().\s%]+$/.test(input.expression)) {
return { data: "Error: only arithmetic expressions are supported", is_error: true };
}
const result = Function(`'use strict'; return (${input.expression})`)();
return `${input.expression} = ${result}`;
},
});
const agent = createAgent({ tools: [...getSafeBaseTools(), calculator] });
const r = await agent.prompt("Calculate 2**10 * 3");
console.log(r.text);Security defaults
The SDK is safe-by-default at the tool boundary:
createAgent()exposes onlyRead,Glob, andGrepunless you passtools.- The default agent enables the SDK app-level sandbox and limits file reads to
cwd. - Custom tools must set
sandboxAware: trueto run while SDK sandbox guards are enabled. - The default
permissionModeisdefault, which allows read-only tools and blocks writes, shell commands, subagents, schedulers, non-read-only MCP tools, and other side-effecting tools. maxToolCallsdefaults to50per query. The cap counts model-requestedtool_useblocks, including calls blocked by safety checks.- To opt into every built-in tool, pass
tools: { type: "preset", preset: "default" }and still provide a host approval policy withcanUseToolor an explicitpermissionMode. - Per-query
toolsoverrides cannot expand beyond the constructor tool pool unlessallowQueryToolExpansion: trueis set on the agent. allowedToolsanddisallowedToolssupport exact names, wildcard names such asmcp__filesystem__*, and permission-style entries such asBash(git:*)for tool-pool matching.
The SDK sandbox adds application-level guards for filesystem and network access,
but it is not a trusted OS sandbox. The TypeScript SDK does not bundle an OS-level
sandbox runtime. The default agent enables this app-level sandbox; pass
sandbox: { enabled: false } only when your host has its own boundary. Set
sandbox.failIfUnavailable: true when your host requires a trusted sandbox and
wants the query to fail closed if one is unavailable. By default, sandboxed reads
are limited to cwd, and sandboxed writes are limited to cwd plus the OS temp
directory unless you set filesystem.allowRead or filesystem.allowWrite.
Only mark custom tools as sandboxAware when they do not touch protected host
resources or when they enforce their own filesystem/network boundary.
const agent = createAgent({
sandbox: {
enabled: true,
failIfUnavailable: true,
},
});With SDK sandbox mode enabled, Bash and MCP tools are blocked because the SDK
cannot confine external processes. Running shell commands requires all of:
dangerouslyDisableSandbox: true in the tool input,
sandbox.allowUnsandboxedCommands: true, and explicit host approval.
External MCP transports are not connected while SDK sandbox mode is enabled.
Low-level connectMCPServer() tool wrappers also require an explicit sandbox
context and only execute when sandbox.enabled is false.
Direct calls to exported SDK tool definitions are a low-level API: built-in
tools require an explicit sandbox context, and external-execution tools also
require a direct toolCallBudget plus host approval where applicable.
For complex tasks, raise maxToolCalls deliberately and pair it with
maxBudgetUsd. Subagents inherit the same tool-call budget instead of receiving
a fresh counter.
const agent = createAgent({
maxTurns: 30,
maxToolCalls: 200,
maxBudgetUsd: 2,
});Skills
Skills are reusable prompt templates that extend agent capabilities. Five bundled skills are included: simplify, commit, review, debug, test.
import {
createAgent,
registerSkill,
getAllSkills,
} from "@keenocean/open-agent-sdk";
// Register a custom skill
registerSkill({
name: "explain",
description: "Explain a concept in simple terms",
userInvocable: true,
async getPrompt(args) {
return [
{
type: "text",
text: `Explain in simple terms: ${args || "Ask what to explain."}`,
},
];
},
});
console.log(`${getAllSkills().length} skills registered`);
// The model can invoke skills via the Skill tool when explicitly enabled
const agent = createAgent({
tools: ["Skill"],
canUseTool: async (tool) =>
tool.name === "Skill"
? { behavior: "allow" }
: { behavior: "deny", message: "Only Skill is allowed" },
});
const result = await agent.prompt('Use the "explain" skill to explain git rebase');
console.log(result.text);Hooks (lifecycle events)
import { createAgent, createHookRegistry } from "@keenocean/open-agent-sdk";
const hooks = createHookRegistry({
PreToolUse: [
{
handler: async (input) => {
console.log(`About to use: ${input.toolName}`);
// Return { block: true } to prevent tool execution
},
},
],
PostToolUse: [
{
handler: async (input) => {
console.log(`Tool ${input.toolName} completed`);
},
},
],
});20 lifecycle events: PreToolUse, PostToolUse, PostToolUseFailure, SessionStart, SessionEnd, Stop, SubagentStart, SubagentStop, UserPromptSubmit, PermissionRequest, PermissionDenied, TaskCreated, TaskCompleted, ConfigChange, CwdChanged, FileChanged, Notification, PreCompact, PostCompact, TeammateIdle.
MCP server integration
import { createAgent } from "@keenocean/open-agent-sdk";
const agent = createAgent({
sandbox: { enabled: false },
allowedTools: ["mcp__filesystem__*"],
mcpServers: {
filesystem: {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
},
},
canUseTool: async (tool) =>
tool.name.startsWith("mcp__filesystem__")
? { behavior: "allow" }
: { behavior: "deny", message: "Demo only allows filesystem MCP tools" },
});
const result = await agent.prompt("List files in /tmp");
console.log(result.text);
await agent.close();Subagents
import { query } from "@keenocean/open-agent-sdk";
for await (const msg of query({
prompt: "Use the code-reviewer agent to review src/index.ts",
options: {
agents: {
"code-reviewer": {
description: "Expert code reviewer",
prompt: "Analyze code quality. Focus on security and performance.",
tools: ["Read", "Glob", "Grep"],
},
},
},
})) {
if (msg.type === "result") console.log("Done");
}Permissions
import { query } from "@keenocean/open-agent-sdk";
// Read-only agent — can only analyze, not modify
for await (const msg of query({
prompt: "Review the code in src/ for best practices.",
options: {
allowedTools: ["Read", "Glob", "Grep"],
},
})) {
// ...
}Web UI
A built-in web chat interface is included for testing:
npx tsx examples/web/server.ts
# Open http://localhost:8081API reference
Top-level functions
| Function | Description |
| ------------------------------------- | -------------------------------------------------------------- |
| query({ prompt, options }) | One-shot streaming query, returns AsyncGenerator<SDKMessage> |
| createAgent(options) | Create a reusable agent with session persistence |
| tool(name, desc, schema, handler) | Create a tool with Zod schema validation |
| createSdkMcpServer({ name, tools }) | Bundle tools into an in-process MCP server |
| defineTool(config) | Low-level tool definition helper |
| getSafeBaseTools() | Get the safe default built-in tools: Read, Glob, Grep |
| getAllBaseTools() | Get all 35+ built-in tools |
| registerSkill(definition) | Register a custom skill |
| getAllSkills() | Get all registered skills |
| createProvider(apiType, opts) | Create an LLM provider directly |
| createHookRegistry(config) | Create a hook registry for lifecycle events |
| listSessions() | List persisted sessions |
| forkSession(id) | Fork a session for branching |
Agent methods
| Method | Description |
| ------------------------------- | ----------------------------------------------------- |
| agent.query(prompt) | Streaming query, returns AsyncGenerator<SDKMessage> |
| agent.prompt(text) | Blocking query, returns Promise<QueryResult> |
| agent.getMessages() | Get conversation history |
| agent.clear() | Reset session |
| agent.interrupt() | Abort current query |
| agent.setModel(model) | Change model mid-session |
| agent.setPermissionMode(mode) | Change permission mode |
| agent.getApiType() | Get current API type |
| agent.close() | Close MCP connections, persist session |
Options
| Option | Type | Default | Description |
| -------------------- | --------------------------------------- | ---------------------- | -------------------------------------------------------------------- |
| apiType | string | auto-detected | 'anthropic-messages' or 'openai-completions' |
| model | string | claude-sonnet-4-6 | LLM model ID |
| apiKey | string | CODEANY_API_KEY | API key |
| baseURL | string | — | Custom API endpoint |
| cwd | string | process.cwd() | Working directory |
| systemPrompt | string | — | System prompt override |
| appendSystemPrompt | string | — | Append to default system prompt |
| tools | ToolDefinition[] / string[] / preset | Read, Glob, Grep | Available tools; use { type: 'preset', preset: 'default' } for all built-ins |
| allowQueryToolExpansion | boolean | false | Allow per-query tools overrides to expand beyond the constructor tool pool |
| allowedTools | string[] | — | Tool allow-list |
| disallowedTools | string[] | — | Tool deny-list |
| permissionMode | string | default | default / acceptEdits / dontAsk / bypassPermissions / plan |
| canUseTool | function | — | Custom permission callback |
| maxTurns | number | 10 | Max agentic turns |
| maxBudgetUsd | number | — | Spending cap |
| maxToolCalls | number | 50 | Hard cap on model-requested tool calls; use Infinity to opt out intentionally |
| thinking | ThinkingConfig | { type: 'adaptive' } | Extended thinking |
| effort | string | high | Reasoning effort: low / medium / high / max |
| mcpServers | Record<string, McpServerConfig> | — | MCP server connections |
| agents | Record<string, AgentDefinition> | — | Subagent definitions |
| hooks | Record<string, HookCallbackMatcher[]> | — | Lifecycle hooks |
| resume | string | — | Resume session by ID |
| continue | boolean | false | Continue most recent session |
| persistSession | boolean | true | Persist session to disk |
| sessionId | string | auto | Explicit session ID |
| outputFormat | { type: 'json_schema', schema } | — | Structured output |
| sandbox | SandboxSettings | — | App-level filesystem/network guard; not a trusted OS sandbox |
| settingSources | SettingSource[] | — | Load AGENT.md, project settings |
| env | Record<string, string> | — | Environment variables |
| abortController | AbortController | — | Cancellation controller |
Environment variables
| Variable | Description |
| -------------------- | -------------------------------------------------------- |
| CODEANY_API_KEY | API key (required) |
| CODEANY_API_TYPE | anthropic-messages (default) or openai-completions |
| CODEANY_MODEL | Default model override |
| CODEANY_BASE_URL | Custom API endpoint |
| CODEANY_AUTH_TOKEN | Alternative auth token |
Built-in tools
Only Read, Glob, and Grep are exposed by default. All other built-ins
require an explicit tools option and host approval.
| Tool | Description | | ------------------------------------------ | -------------------------------------------- | | Bash | Execute shell commands | | Read | Read files with line numbers | | Write | Create / overwrite files | | Edit | Precise string replacement in files | | Glob | Find files by pattern | | Grep | Search file contents with regex | | WebFetch | Fetch and parse web content | | WebSearch | Search the web | | NotebookEdit | Edit Jupyter notebook cells | | Agent | Spawn subagents for parallel work | | Skill | Invoke registered skills | | TaskCreate/List/Update/Get/Stop/Output | Task management system | | TeamCreate/Delete | Multi-agent team coordination | | SendMessage | Inter-agent messaging | | EnterWorktree/ExitWorktree | Git worktree isolation | | EnterPlanMode/ExitPlanMode | Structured planning workflow | | AskUserQuestion | Ask the user for input | | ToolSearch | Discover lazy-loaded tools | | ListMcpResources/ReadMcpResource | MCP resource access | | CronCreate/Delete/List | Scheduled task management | | RemoteTrigger | Remote agent triggers | | LSP | Language Server Protocol (code intelligence) | | Config | Dynamic configuration | | TodoWrite | Session todo list |
Bundled skills
| Skill | Description |
| ------------ | -------------------------------------------------------------- |
| simplify | Review changed code for reuse, quality, and efficiency |
| commit | Create a git commit with a well-crafted message |
| review | Review code changes for correctness, security, and performance |
| debug | Systematic debugging using structured investigation |
| test | Run tests and analyze failures |
Register custom skills with registerSkill().
Architecture
┌──────────────────────────────────────────────────────┐
│ Your Application │
│ │
│ import { createAgent } from '@keenocean/open-agent-sdk' │
└────────────────────────┬─────────────────────────────┘
│
┌──────────▼──────────┐
│ Agent │ Session state, tool pool,
│ query() / prompt() │ MCP connections, hooks
└──────────┬──────────┘
│
┌──────────▼──────────┐
│ QueryEngine │ Agentic loop:
│ submitMessage() │ API call → tools → repeat
└──────────┬──────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ Provider │ │ 35 Tools │ │ MCP │
│ Anthropic │ │ Bash,Read │ │ Servers │
│ OpenAI │ │ Edit,... │ │ stdio/SSE/ │
│ DeepSeek │ │ + Skills │ │ HTTP/SDK │
└───────────┘ └───────────┘ └───────────┘Key internals:
| Component | Description | | --------------------- | ------------------------------------------------------------------ | | Provider layer | Abstracts Anthropic / OpenAI API differences | | QueryEngine | Core agentic loop with auto-compact, retry, tool orchestration | | Skill system | Reusable prompt templates with 5 bundled skills | | Hook system | 20 lifecycle events integrated into the engine | | Auto-compact | Summarizes conversation when context window fills up | | Micro-compact | Truncates oversized tool results | | Retry | Exponential backoff for rate limits and transient errors | | Token estimation | Rough token counting with pricing for Claude, GPT, DeepSeek models | | File cache | LRU cache (100 entries, 25 MB) for file reads | | Session storage | Persist / resume / fork sessions on disk | | Context injection | Git status + AGENT.md automatically injected into system prompt |
Examples
| # | File | Description |
| --- | ------------------------------------- | -------------------------------------- |
| 01 | examples/01-simple-query.ts | Streaming query with event handling |
| 02 | examples/02-multi-tool.ts | Multi-tool orchestration (Glob + Bash) |
| 03 | examples/03-multi-turn.ts | Multi-turn session persistence |
| 04 | examples/04-prompt-api.ts | Blocking prompt() API |
| 05 | examples/05-custom-system-prompt.ts | Custom system prompt |
| 06 | examples/06-mcp-server.ts | MCP server integration |
| 07 | examples/07-custom-tools.ts | Custom tools with defineTool() |
| 08 | examples/08-official-api-compat.ts | query() API pattern |
| 09 | examples/09-subagents.ts | Subagent delegation |
| 10 | examples/10-permissions.ts | Read-only agent with tool restrictions |
| 11 | examples/11-custom-mcp-tools.ts | tool() + createSdkMcpServer() |
| 12 | examples/12-skills.ts | Skill system usage |
| 13 | examples/13-hooks.ts | Lifecycle hooks |
| 14 | examples/14-openai-compat.ts | OpenAI / DeepSeek models |
| web | examples/web/ | Web chat UI for testing |
Run any example:
npx tsx examples/01-simple-query.tsStart the web UI:
npx tsx examples/web/server.tsStar History
License
MIT
