TypeScript SDK for the Factory Droid CLI. Provides a high-level API for interacting with Droid as a subprocess, with one-shot prompts, streaming messages, multi-turn sessions, structured output, SDK-backed MCP tools, spec mode, tool controls, initialization metadata, session forking, session discovery, and tool permission handling.
- Node.js 18+
- The
droidCLI installed and available on your PATH
npm install @factory/droid-sdkSend a one-shot prompt and get the aggregated result:
import { run } from '@factory/droid-sdk';
const result = await run('What files are in the current directory?', {
cwd: '/my/project',
});
console.log(result.text);Create a session when you want to stream one or more turns:
import { DroidMessageType, createSession } from '@factory/droid-sdk';
const session = await createSession({ cwd: '/my/project' });
try {
for await (const msg of session.stream(
'What files are in the current directory?'
)) {
if (msg.type === DroidMessageType.AssistantTextDelta) {
process.stdout.write(msg.text);
}
if (msg.type === DroidMessageType.TurnComplete) {
console.log('\nDone!');
}
}
} finally {
await session.close();
}Request a JSON object that matches a JSON Schema:
import { OutputFormatType, run } from '@factory/droid-sdk';
const result = await run('Pick a favorite number between 1 and 42.', {
cwd: '/my/project',
outputFormat: {
type: OutputFormatType.JsonSchema,
schema: {
type: 'object',
properties: {
favoriteNumber: {
type: 'number',
minimum: 1,
maximum: 42,
},
},
required: ['favoriteNumber'],
},
},
});
console.log(result.structuredOutput?.favoriteNumber);Structured output is available on run() and session.stream(prompt, options) through the outputFormat message option. run() parses the final object into result.structuredOutput; streaming callers can read and parse the final assistant message themselves.
Use createSession() for persistent conversations with multiple turns:
import { createSession, DroidMessageType } from '@factory/droid-sdk';
const session = await createSession({ cwd: '/my/project' });
console.log(session.sessionId);
// Streaming turn
for await (const msg of session.stream('List all TypeScript files')) {
if (msg.type === DroidMessageType.AssistantTextDelta) {
process.stdout.write(msg.text);
}
}
// Later turns use the same streaming API
for await (const msg of session.stream('Summarize the project')) {
if (msg.type === DroidMessageType.AssistantTextDelta) {
process.stdout.write(msg.text);
}
}
await session.close();Use session.sessionId to persist the session ID, then resume it later:
import { resumeSession } from '@factory/droid-sdk';
const session = await resumeSession(savedSessionId, {
cwd: '/my/project',
});
for await (const msg of session.stream('Continue where we left off')) {
// Handle streamed DroidMessage events.
}
await session.close();The returned DroidSession also exposes session.initResult, which contains the raw initialize_session or load_session result returned by the JSON-RPC server.
Expose in-process TypeScript tools to Droid through MCP:
import { z } from 'zod';
import {
DroidMessageType,
ToolConfirmationOutcome,
createSession,
createSdkMcpServer,
tool,
} from '@factory/droid-sdk';
const sdkTools = createSdkMcpServer({
name: 'sdk-tools',
tools: [
tool(
'favorite_number',
'Returns a favorite number for a person',
{ name: z.string() },
({ name }) => `${name}'s favorite number is 42.`
),
],
});
const session = await createSession({
cwd: '/my/project',
mcpServers: [sdkTools],
permissionHandler: () => ToolConfirmationOutcome.ProceedOnce,
});
for await (const msg of session.stream(
'Use the favorite_number tool for Ada and tell me the answer.'
)) {
if (msg.type === DroidMessageType.AssistantTextDelta) {
process.stdout.write(msg.text);
}
}
await session.close();createSdkMcpServer() is managed by the SDK session lifecycle. Use tool() with a Zod object shape for typed tool input validation.
Inspect the raw initialization metadata from createSession() and resumeSession():
import { createSession, resumeSession } from '@factory/droid-sdk';
const session = await createSession({ cwd: '/my/project' });
console.log(session.sessionId);
console.log(session.initResult.settings.modelId);
const resumed = await resumeSession(session.sessionId, { cwd: '/my/project' });
console.log(resumed.initResult.cwd);
await resumed.close();
await session.close();Start a session directly in spec mode, or enter spec mode later on an existing session:
import {
DroidMessageType,
createSession,
DroidInteractionMode,
ReasoningEffort,
} from '@factory/droid-sdk';
const session = await createSession({
cwd: '/my/project',
interactionMode: DroidInteractionMode.Spec,
specModeReasoningEffort: ReasoningEffort.High,
specModeModelId: 'claude-sonnet-4-20250514',
});
for await (const msg of session.stream(
'Draft a plan for adding integration tests'
)) {
if (msg.type === DroidMessageType.AssistantTextDelta) {
process.stdout.write(msg.text);
}
}
await session.enterSpecMode({
specModeReasoningEffort: ReasoningEffort.High,
});
await session.close();When handling spec-mode approval, you can approve implementation in the same session with ToolConfirmationOutcome.ProceedOnce, or hand off to a fresh session with ToolConfirmationOutcome.ProceedNewSessionHigh.
Control which exec tools are available at session start, inspect the current tool catalog, and update tool overrides later:
import { createSession } from '@factory/droid-sdk';
const session = await createSession({
cwd: '/my/project',
enabledToolIds: ['Read'],
disabledToolIds: ['Execute'],
});
const { tools } = await session.listTools();
console.log(
tools.map((tool) => ({
id: tool.llmId,
allowed: tool.currentlyAllowed,
}))
);
await session.updateSettings({
disabledToolIds: ['Read', 'Execute'],
});
await session.close();Fork the current server-side session and continue from the new session ID:
import {
DroidMessageType,
createSession,
resumeSession,
} from '@factory/droid-sdk';
const session = await createSession({ cwd: '/my/project' });
for await (const _msg of session.stream(
'Remember this phrase: mango sunrise'
)) {
// Consume the turn.
}
const { newSessionId } = await session.forkSession();
const fork = await resumeSession(newSessionId, { cwd: '/my/project' });
for await (const msg of fork.stream('What phrase did I ask you to remember?')) {
if (msg.type === DroidMessageType.AssistantTextDelta) {
process.stdout.write(msg.text);
}
}
await fork.close();
await session.close();Discover droid sessions saved on disk (mirrors the CLI's /sessions command). Reads ~/.factory/sessions/ directly — no droid process is spawned, so this works even when no session is running:
import { listSessions } from '@factory/droid-sdk';
// Sessions for the current project (cwd defaults to process.cwd())
const current = await listSessions();
// 10 most recent sessions in the current project
const recent = await listSessions({ numSessions: 10 });
// Every session on disk, most recent first
const all = await listSessions({ fetchOutsideCWD: true });
// 10 most recent sessions across all projects
const recentAcrossProjects = await listSessions({
fetchOutsideCWD: true,
numSessions: 10,
});
// Sessions for a specific other project
const other = await listSessions({ cwd: '/Users/me/other-repo' });
for (const s of current) {
console.log(`[${s.id}] ${s.title} (${s.messageCount} msgs)`);
}Each SessionMetadata record includes id, title, sessionTitle, owner, messageCount, modifiedTime, createdTime, isFavorite, cwd, decompSessionType, and decompMissionId. Archived sessions (those with an archivedAt in their settings file) are excluded automatically. Results are sorted by modifiedTime descending.
ListSessionsOptions:
cwd— working directory to scope the listing to (defaultprocess.cwd()). Ignored whenfetchOutsideCWDistrue.fetchOutsideCWD— return sessions from every working directory on disk (defaultfalse)numSessions— cap on total sessions returnedsessionsDir— override the sessions root (default~/.factory/sessions/)
Handle tool confirmation requests with a custom permission handler:
import {
DroidMessageType,
createSession,
ToolConfirmationOutcome,
} from '@factory/droid-sdk';
const session = await createSession({
cwd: '/my/project',
permissionHandler(params) {
console.log('Tool permission requested:', params);
return ToolConfirmationOutcome.ProceedOnce;
},
});
try {
for await (const msg of session.stream('Create a hello.txt file')) {
if (msg.type === DroidMessageType.AssistantTextDelta) {
process.stdout.write(msg.text);
}
}
} finally {
await session.close();
}| Function | Description |
|---|---|
run(prompt, options?) |
One-shot prompt → aggregated DroidResult |
createSession(options?) |
Create a new multi-turn session → DroidSession |
resumeSession(id, options?) |
Resume an existing session → DroidSession |
listSessions(options?) |
List droid sessions saved on disk → Promise<SessionMetadata[]> |
createSdkMcpServer(options) |
Create an SDK-managed MCP server for in-process tools |
tool(...) |
Define a typed SDK-backed MCP tool |
Returned by createSession() and resumeSession(). Key methods:
stream(prompt, options?)— streamDroidMessageevents for one turninterrupt()— interrupt the current turnclose()— close the session and release resourcesupdateSettings(params)— update model, autonomy level, etc.enterSpecMode(params?)— switch the current session into spec modeforkSession()— create a forked server-side session and return its new session IDcompactSession(params?)— compact session history and return the new session IDgetContextStats()— read current context window utilizationaddMcpServer(params)/removeMcpServer(params)/toggleMcpServer(params)— manage MCP serverslistMcpServers()/listMcpTools()/authenticateMcpServer(params)— inspect and authenticate MCP serverslistTools(params?)— inspect the exec tool catalog and current allow/deny staterenameSession(params)— rename the current sessiongetRewindInfo(params)/executeRewind(params)— inspect and execute file rewind operationssessionId— the session IDinitResult— cachedinitialize_sessionorload_sessionresult
Returned by run():
sessionId— session that produced the resulttext— concatenated assistant response textmessages— allDroidMessageobjects from the turntokenUsage— final token usage, ornulldurationMs— wall-clock time spent consuming the turnturnCount— number of completed turns observed while consuming the streamerror— first Droid error event from the turn, ornullstructuredOutput— parsed structured JSON object, ornullsuccess—truewhen no Droid error event was emitted
All messages have a discriminated type field:
import { DroidMessageType } from '@factory/droid-sdk';
if (msg.type === DroidMessageType.AssistantTextDelta) {
process.stdout.write(msg.text);
}| Type | Description |
|---|---|
assistant_text_delta |
Streaming text token from the assistant |
thinking_text_delta |
Streaming reasoning/thinking token |
tool_use |
Tool invocation by the assistant |
tool_result |
Result from a tool execution |
tool_progress |
Progress update during tool execution |
working_state_changed |
Agent working state transition |
token_usage_update |
Updated token usage counters |
create_message |
Full assistant message created |
turn_complete |
Sentinel: agent turn finished |
session_title_updated |
Session title changed |
mcp_status_changed |
MCP server status changed |
mission_state_changed |
Mission state changed |
mission_features_changed |
Mission features changed |
mission_progress_entry |
Mission progress log changed |
mission_heartbeat |
Mission heartbeat |
mission_worker_started |
Mission worker started |
mission_worker_completed |
Mission worker completed |
mcp_auth_required |
MCP authentication required |
mcp_auth_completed |
MCP authentication completed |
permission_resolved |
Tool permission request resolved |
settings_updated |
Session settings changed |
error |
Error event from the process |
Session creation options used by run() and createSession() include:
cwd— working directory for the sessionexecPath— path todroidexecutable (default:"droid")execArgs— extra CLI arguments for the spawned droid processenv— environment variables for the spawned processtransport— provide a custom transport instead of spawning a processmodelId— LLM model identifierautonomyLevel—AutonomyLevelenum valueinteractionMode—DroidInteractionModeenum valuereasoningEffort—ReasoningEffortenum valuespecModeModelId— override model used in spec modespecModeReasoningEffort— override reasoning level used in spec modemcpServers— initial MCP server configurations, including SDK-backed MCP servers fromcreateSdkMcpServer()enabledToolIds— explicit exec tool allowlistdisabledToolIds— explicit exec tool denylistpermissionHandler— callback for tool confirmationsaskUserHandler— callback for interactive questionsabortSignal— standardAbortSignalfor cancellation
resumeSession() accepts the process, transport, handler, cwd, mcpServers, and abortSignal options needed to reconnect to an existing session, but does not accept new-session-only options such as modelId or interactionMode.
Message APIs (run() and session.stream()) also accept:
images— base64 image attachmentsfiles— document/file attachmentsoutputFormat— structured output request, currentlyOutputFormatType.JsonSchemaabortSignal— standardAbortSignalfor turn cancellation
Low-level JSON-RPC client for advanced use. Provides typed methods for the underlying protocol operations, including listTools(), renameSession(), getRewindInfo(), and executeRewind(). Most users should prefer run() and createSession().
| Error | Description |
|---|---|
ConnectionError |
Failed to connect to the droid process |
ProtocolError |
JSON-RPC protocol error |
SessionError |
Base session error |
SessionNotFoundError |
Session ID not found |
TimeoutError |
Request timed out |
ProcessExitError |
Droid subprocess exited unexpectedly |
See the examples/ directory for runnable examples:
session-stream.ts— session streaming outputrun.ts— one-shot prompt with aggregated resultmulti-turn-session.ts— multi-turn session lifecycleabort-session-stream.ts— cancel an in-flight streaming session turn withAbortSignalinterrupt-session.ts— interrupt a running session turninit-metadata.ts— read initialization and load metadata from session APIsresult-metadata.ts— inspectDroidResultmetadata fromrun()structured-output.ts— request and parse structured outputdroid-dev-structured-output.ts— structured output smoke test with configurable Droid executablepermission-handler.ts— custom permission handlingspec-mode-same-session.ts— approve a spec and continue in the same sessionspec-mode-new-session.ts— approve a spec and hand off implementation to a new sessiontool-controls.ts— configure allow/deny lists and inspect tool availabilitysdk-mcp-tool.ts— expose SDK-defined tools to Droid through MCPfork-session.ts— fork a session and continue from the new session IDlist-sessions.ts— discover droid sessions saved on disktest-compact.ts— compact session historytest-rewind.ts— use low-level rewind APIs
Apache 2.0