TypeScript SDK for the Factory Droid CLI. Provides a high-level API for interacting with Droid as a subprocess, with streaming message support, multi-turn sessions, 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 stream the response:
import { query } from '@factory/droid-sdk';
const stream = query({
prompt: 'What files are in the current directory?',
cwd: '/my/project',
});
for await (const msg of stream) {
if (msg.type === 'assistant_text_delta') {
process.stdout.write(msg.text);
}
if (msg.type === 'turn_complete') {
console.log('\nDone!');
}
}Use createSession() for persistent conversations with multiple turns:
import { createSession } from '@factory/droid-sdk';
const session = await createSession({ cwd: '/my/project' });
// Streaming turn
for await (const msg of session.stream('List all TypeScript files')) {
if (msg.type === 'assistant_text_delta') {
process.stdout.write(msg.text);
}
}
// Non-streaming turn
const result = await session.send('Summarize the project');
console.log(result.text);
await session.close();Resume an existing session by ID:
import { resumeSession } from '@factory/droid-sdk';
const session = await resumeSession('session-id-here');
const result = await session.send('Continue where we left off');
console.log(result.text);
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.
Inspect the raw initialization metadata from query(), createSession(), and resumeSession():
import { createSession, query, resumeSession } from '@factory/droid-sdk';
const stream = query({
prompt: 'Reply with "ready" and nothing else.',
cwd: '/my/project',
});
console.log(stream.sessionId); // null before initialization
console.log(stream.initResult); // null before initialization
const initialized = await stream.initialized;
console.log(initialized.sessionId);
console.log(initialized.settings.modelId);
stream.abort();
const session = await createSession({ cwd: '/my/project' });
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 {
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',
});
const plan = await session.send('Draft a plan for adding integration tests');
console.log(plan.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 { createSession, resumeSession } from '@factory/droid-sdk';
const session = await createSession({ cwd: '/my/project' });
await session.send('Remember this phrase: mango sunrise');
const { newSessionId } = await session.forkSession();
const fork = await resumeSession(newSessionId, { cwd: '/my/project' });
const result = await fork.send('What phrase did I ask you to remember?');
console.log(result.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 { query, ToolConfirmationOutcome } from '@factory/droid-sdk';
const stream = query({
prompt: 'Create a hello.txt file',
cwd: '/my/project',
permissionHandler(params) {
console.log('Tool permission requested:', params);
return ToolConfirmationOutcome.ProceedOnce;
},
});
for await (const msg of stream) {
if (msg.type === 'assistant_text_delta') {
process.stdout.write(msg.text);
}
}| Function | Description |
|---|---|
query(options) |
One-shot prompt → async generator of DroidMessage events |
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[]> |
Returns an async generator that yields DroidMessage events. The returned DroidQuery object also exposes:
interrupt()— gracefully interrupt the agent's current turnabort()— forcefully kill the subprocesssessionId— the session ID (available after initialization)initResult— cachedinitialize_sessionresult, ornullbefore initializationinitialized— promise that resolves with theinitialize_sessionresult
query(options) also accepts an abortSignal for external cancellation.
Returned by createSession() and resumeSession(). Key methods:
stream(text, options?)— send a message, returns async generator ofDroidMessagesend(text, options?)— send a message, returns aggregatedDroidResultinterrupt()— 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 IDaddMcpServer(params)/removeMcpServer(params)— manage MCP serverslistTools(params?)— inspect the exec tool catalog and current allow/deny staterenameSession(params)— rename the current sessionsessionId— the session IDinitResult— cachedinitialize_sessionorload_sessionresult
Returned by session.send():
text— concatenated assistant response textmessages— allDroidMessageobjects from the turntokenUsage— final token usage, ornull
All messages have a discriminated type field:
| 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 |
error |
Error event from the process |
QueryOptions and CreateSessionOptions accept:
prompt— the user prompt (query only)cwd— working directory for the sessionmodelId— LLM model identifierautonomyLevel—AutonomyLevelenum valueinteractionMode—DroidInteractionModeenum valuereasoningEffort—ReasoningEffortenum valuespecModeModelId— override model used in spec modespecModeReasoningEffort— override reasoning level used in spec modeenabledToolIds— explicit exec tool allowlistdisabledToolIds— explicit exec tool denylistpermissionHandler— callback for tool confirmationsaskUserHandler— callback for interactive questionsabortSignal— standardAbortSignalfor cancellationexecPath— path todroidexecutable (default:"droid")transport— provide a custom transport instead of spawning a process
Low-level JSON-RPC client for advanced use. Provides typed methods for the underlying protocol operations, including listTools() and renameSession(). Most users should prefer query() 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:
simple-query.ts— one-shot query with streaming outputmulti-turn-session.ts— multi-turn session lifecycleinit-metadata.ts— read initialization and load metadata from query/session APIspermission-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 availabilityfork-session.ts— fork a session and continue from the new session IDlist-sessions.ts— discover droid sessions saved on disk
Apache 2.0