A session-first TypeScript Agent SDK for both local Node.js processes and Node.js servers. It provides one API for multi-turn conversations, streaming tool execution, MCP, subagents, Skills, permissions, hooks, sandbox policies, structured output, and observability.
- Node.js 22.14.0 or later
- An ESM project or ESM-capable build tool
The package is ESM-only and does not support CommonJS require().
npm install @blade-ai/agent-sdkCreate agent.mjs:
import { createAgent } from '@blade-ai/agent-sdk';
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) throw new Error('OPENAI_API_KEY is required');
const agent = await createAgent({
model: 'gpt-4o-mini',
apiKey,
filesystem: {
roots: [process.cwd()],
cwd: process.cwd(),
},
});
const response = await agent.send(
'Read package.json and summarize this project in three bullets',
);
for await (const chunk of response.textStream()) {
process.stdout.write(chunk);
}
await agent.close();Run it:
OPENAI_API_KEY=your-key node agent.mjscreateAgent() defaults to OpenAI. Set provider and baseUrl for another
provider. A filesystem option automatically selects the local runtime
profile; without it, the server profile is used. Select either behavior
explicitly with profile: 'local' | 'server'.
Common options stay at the top level. Infrastructure and policy options live
under advanced:
const agent = await createAgent({
model: 'gpt-4o-mini',
apiKey,
temperature: 0.2,
systemPrompt: 'Be concise.',
advanced: {
permission: 'accept-edits',
tokenBudget: { maxTotalTokens: 100_000 },
skills: [
{
name: 'review',
description: 'Review code for correctness and risk',
content: 'Report findings by severity with file and line references.',
allowedTools: ['Read', 'Glob', 'Grep'],
},
],
},
});Each send() returns one AgentResponse. Use text() for the complete text,
textStream() for text deltas, on(type, listener) for selected events, or
stream() for all typed events. Every view shares one underlying execution and
can replay events already observed by another view.
Generate a local Agent without PostgreSQL or Docker:
npm exec --yes --package=@blade-ai/agent-sdk@latest -- \
create-blade-agent my-agent --preset local --verifyGenerate a Browser + AgentServer application:
npm exec --yes --package=@blade-ai/agent-sdk@latest -- \
create-blade-agent my-agent --preset web --verifyGenerate the complete PostgreSQL, Worker, Docker, approval, and recovery topology:
npm exec --yes --package=@blade-ai/agent-sdk@latest -- \
create-blade-agent my-agent --preset production --verifyThe longest installed smoke has a five-minute budget. Omit --verify to skip
it, or use --skip-install to write files only.
Framework and runtime integrations can use createSession() directly:
import {
createSession as createNodeSession,
createServerSession,
} from '@blade-ai/agent-sdk/advanced';Both profiles share the Session API and protocol. They differ in where the process runs and what it may touch:
local |
server |
|
|---|---|---|
| Local file and Shell tools | Available once a filesystem capability is configured |
Not registered implicitly; explicit data Skills get only the Skill loader |
storagePath |
Backed by local JSONL persistence | Throws ConfigError: server Sessions need sessionRepository and sessionEventStore |
| Default persistence | Local JSONL when storagePath is set |
In memory unless you inject a repository |
| Context and Skill discovery | Enabled | Disabled (localDiscovery is off) |
Use the local profile for a local agent or CLI. Use the server profile when the runtime is a shared multi-tenant service and storage is injected explicitly.
- Agent facade:
createAgent()with required, common, andadvancedoption layers - Low-level Session lifecycle:
createSession(),resumeSession(),forkSession(), andprompt() - Steerable requests: durable
now,next, andlaterinputs with cancellation and pending-input inspection - Durable recovery: lease-fenced execution ownership, controlled worker handoff, safe Request/Turn rollover, explicit model/tool reconciliation, and reconnectable cursors
- Execution plane:
AgentWorker, the injectableSessionRunnercontract,SdkSessionRunner, andExecutionHostSessionRunner - Streaming: 17 typed events for turns, content, reasoning, tools, usage, steering, results, and errors
- Providers: OpenAI, Anthropic, Azure OpenAI, Gemini, DeepSeek, and OpenAI-compatible APIs
- Tools: async-function and AsyncGenerator authoring, TypeBox schemas, capability-grouped built-ins, MCP tools, typed progress/effects, and the
blade-tool-*package convention - Extensibility: onion-style model/tool middleware and declarative plugins that bundle middleware, hooks, and tools
- Collaboration: foreground and background subagents, task tools, and project Skills
- Safety: bounded model, tool, and inline-hook execution, permission modes, policy callbacks, path checks, and optional OS sandbox integration
- Runtime: optional workspace context, structured output, crash-safe local transcripts, context compaction, token budgets, and traces
send() returns an InputSubmission. While a request is active, choose when the new input should apply:
const current = await session.send('Analyze the repository');
for await (const event of session.stream()) {
if (event.type === 'tool_use' && event.name === 'Bash') {
await session.send('Stop editing and only report findings', {
priority: 'now',
expectedRequestId: current.requestId,
});
}
}now: interrupt the current cancellable step and steer immediatelynext: apply at the next model or tool safe pointlater: queue input for the next request
Use getPendingInputs() and cancelInput() to manage accepted inputs.
Use a TypeBox schema with a regular async function for the common path. The return value is converted to a successful internal tool result:
import { defineTool } from '@blade-ai/agent-sdk';
import Type from 'typebox';
const weather = defineTool({
name: 'GetWeather',
description: 'Get the weather for a city',
parameters: Type.Object({ city: Type.String() }),
async execute({ city }) {
return { weather: `${city}: clear, 25 C` };
},
});defineTool.execute always returns JSON data. Throw an error to report failure;
the SDK does not infer result semantics from returned object fields.
New Agent integrations use one permission field:
const agent = await createAgent({
model: 'gpt-4o-mini',
apiKey,
advanced: {
permission: async (request) =>
request.kind === 'readonly' ? 'allow' : 'ask',
hooks: {
PreToolUse: [
async (event) => {
console.log(event.toolName, event.toolInput);
return { action: 'continue' };
},
],
},
},
});advanced.hooks contains in-process TypeScript callbacks for the eight Session
hook events. The low-level SessionOptions.permissionMode and
permissionHandler APIs remain available for runtime integrations.
import { createAgent, defineTool } from '@blade-ai/agent-sdk';
import { AgentClient } from '@blade-ai/agent-sdk/browser';
import {
createSession,
createServerSession,
DockerExecutionHost,
type SessionRunner,
} from '@blade-ai/agent-sdk/advanced';
import {
AgentServer,
AgentWorker,
} from '@blade-ai/agent-sdk/server/infra';
import { PostgresRuntimeStore } from '@blade-ai/agent-sdk/server/postgres';- Root:
createAgent,defineTool, middleware, model contracts, constants, and public types /browser: browser-safeAgentClient, protocol contracts, and event parsers/protocol: wire protocol schemas and parsers/server/infra:AgentServer,AgentWorker, and Runtime Store contracts/advanced: low-level local/server Sessions,SessionRunner, execution hosts, and Node adapters
The former /node, /server, /core, /model, /session, /middleware,
and /tools compatibility aliases have been removed. The optional PostgreSQL
adapter lives at /server/postgres, so importing /server/infra does not
require pg. Server and Worker telemetry are explicit interfaces; applications
may connect them to any observability backend.
Importing a server-only entry in a browser resolves to a stub that throws a clear runtime error.
Run the complete browser-to-worker production topology locally with one command:
pnpm example:productionThis starts PostgreSQL, AgentServer, AgentWorker running a real SDK
Session, and an isolated Docker repository workspace with a browser approval
step. See Runnable golden paths.
PostgreSQL, non-bundled provider adapters, and native Node enhancements are opt-in peers:
pnpm add pg # PostgresRuntimeStore from /server/postgres
pnpm add @ai-sdk/anthropic # provider: anthropic
pnpm add fs-native-extensions # cross-process Node JSONL locksSessions are ephemeral unless a read-side SessionRepository and write-side
SessionEventStore are configured. A local Agent converts
advanced.storagePath into one local JSONL storage
implementation:
import { createAgent } from '@blade-ai/agent-sdk';
const agent = await createAgent({
model,
apiKey,
profile: 'local',
filesystem: {
roots: [process.cwd()],
cwd: process.cwd(),
},
advanced: {
storagePath: '/var/lib/my-agent',
},
});The low-level createServerSession() never interprets storagePath as local
persistence. Server applications must inject
sessionRepository plus sessionEventStore, or configure one shared
runtimeStore. See
Server Runtime for the HTTP/SSE server,
browser client, multi-tenant storage, idempotency, approvals, and telemetry.
For multi-instance storage, see Runtime Store.
For worker coordination and crash recovery, see
Worker Runtime.
For container isolation, resource limits, checkpoints, and ephemeral
credentials, see Execution Host.
The ownership and boundary rules for public contracts are documented in
Type Architecture.
The workspace is optional. Sessions and explicitly configured agents work without one, but local filesystem tools and project-level discovery require a filesystem-capable workspace.
- English documentation
- Middleware and plugins
- Server Runtime
- Runtime Store
- Worker Runtime
- Execution Host
- Durable Event Store
- 中文文档
- Migrating to your own repository
- Runnable golden paths
- Runtime benchmarks
- English changelog
- 中文更新日志
pnpm install
pnpm run lint
pnpm run type-check
pnpm run test
pnpm run build
pnpm run docs:buildThe released version comes from the Git tag, never from commit types. Every
releasable change still adds a bilingual JSON fragment under .changes/:
{
"type": "feature",
"en": "Add a user-facing capability.",
"zh-CN": "新增一项用户可见能力。"
}Use a unique kebab-case filename. Allowed types are breaking, feature,
fix, performance, refactor, and docs; they select the changelog section,
not the version.
To publish, tag the commit on main and push that tag:
git tag v7.4.2
git push origin v7.4.2The release workflow then, from the tagged tree:
- checks out the tag it names and stamps that version into
package.jsonbefore anything is built, so the bundle and the tarball manifest carry the released version rather than the previous one; - validates fragments, lints, type-checks, builds, and tests the package and documentation;
- publishes exactly
v7.4.2to npm with provenance — no other number can be produced — after checking that the built output really contains that version; - records the version, both changelogs, and the consumed fragments in one
chore(release): 7.4.2commit onmain; - creates the GitHub Release with the bilingual notes.
Pushing to main no longer releases anything. Validate fragments with
pnpm run changelog:check, and preview a release by creating the tag locally
first and running pnpm run release:dry --tag v7.4.2.
See CONTRIBUTING.md for contribution guidance.