SDK Usage Guide

June 16, 2026 · View on GitHub

In-depth guide to @factory/droid-sdk, the TypeScript SDK for the Factory Droid CLI. For a quick overview and the public API reference, see the README. This guide is the depth document: it walks through each capability with a runnable example.

This guide covers exec mode (run(), createSession()), which spawns a droid subprocess per session. For WebSocket-based daemon mode with concurrent sessions, see the Daemon Usage Guide.

Table of Contents

Convention used in this guide: most examples create a session with apiKey and cwd, run a turn, and then close(). The two creation options are repeated for copy-paste convenience; in your own code you typically create one session and reuse it across turns. For brevity, examples call close() directly after the turn; in production, wrap turns in try/finally (as shown in the README) so the subprocess is always closed even if a turn throws.


Getting Started

npm install @factory/droid-sdk

Requires Node.js 18+ and the droid CLI on your PATH.

import { run } from '@factory/droid-sdk';

const result = await run('What files are in this directory?', {
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});
console.log(result.text);

apiKey is required by the type signature, but its runtime value may be undefined: the ! only satisfies TypeScript. When the value is undefined, the droid CLI falls back to its stored login credentials, so apiKey: process.env.FACTORY_API_KEY! works on any machine where droid is logged in. The remaining examples use the same pattern.


Core Usage

One-shot Run

Send a prompt, get a result, done. The session is created and closed automatically.

import { run } from '@factory/droid-sdk';

const result = await run('What is 2 + 2?', {
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});
console.log(result.text);

run() accepts every MessageOptions field except includePartialMessages (one-shot runs aggregate the whole turn, so there is nothing to stream). See Structured Output for an example that uses outputFormat.

The returned DroidResult carries more than text. See Error Handling for how to discriminate success from error results (isError, subtype, errors, structuredOutputError).

Structured Output

Force the response to match a JSON schema. The validated object is available on result.structuredOutput (typed unknown, so cast it or validate it before use).

import { OutputFormatType, run } from '@factory/droid-sdk';

const result = await run('Pick a number between 1 and 42.', {
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
  outputFormat: {
    type: OutputFormatType.JsonSchema,
    schema: {
      type: 'object',
      properties: { number: { type: 'number' } },
      required: ['number'],
    },
  },
});

console.log((result.structuredOutput as { number: number }).number);

If the model's output cannot be coerced to the schema, the result becomes an error result with subtype: 'error_structured_output' and a populated structuredOutputError (see Error Handling). Always check result.isError before reading structuredOutput.


Sessions

Multi-turn Session

Create a session once, then call stream() multiple times. Context is preserved across turns.

import { createSession, DroidMessageType } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});

for await (const msg of session.stream('Remember the word "mango".')) {
  // consume first turn
}

for await (const msg of session.stream('What word did I say?')) {
  if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
}

await session.close();

Resume Session

Reconnect to a previously created session by its ID. The options argument is optional; when omitted, the CLI uses its stored credentials.

import { resumeSession, DroidMessageType } from '@factory/droid-sdk';

const session = await resumeSession('existing-session-id');

for await (const msg of session.stream('Continue where we left off.')) {
  if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
}

await session.close();

resumeSession() always runs in the working directory persisted with the session; it intentionally does not accept a cwd option. To run in a different directory, create a new session or fork the existing one. See ResumeSessionOptions for the full option set.

Fork Session

Create a copy of the current session with all context preserved. Useful for branching a conversation.

import {
  createSession,
  DroidMessageType,
  resumeSession,
} from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});
for await (const msg of session.stream('Remember: the password is "banana".')) {
}

const { newSessionId } = await session.forkSession();
const fork = await resumeSession(newSessionId);

for await (const msg of fork.stream('What is the password?')) {
  if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
}

await fork.close();
await session.close();

Compact Session

Summarize and remove old messages to free up context window space.

import { createSession, resumeSession } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});

// ... after many turns ...
const result = await session.compactSession({
  // customInstructions: 'Keep the API design decisions verbatim.',
});
console.log(
  `New session: ${result.newSessionId}, removed: ${result.removedCount} messages`
);

await session.close();

// Compaction produces a NEW session id. The original `session` object still
// points at the pre-compaction session id, so to continue with the compacted
// history, resume the new id:
const compacted = await resumeSession(result.newSessionId);
// ... use `compacted` ...
await compacted.close();

compactSession() optionally takes customInstructions to steer what the summary preserves.

List Sessions

Discover saved sessions on disk. Filters to the current project by default (cwd defaults to process.cwd(), fetchOutsideCWD defaults to false). No droid process is spawned.

import { listSessions } from '@factory/droid-sdk';

const sessions = await listSessions({ numSessions: 10 });

for (const s of sessions) {
  console.log(`${s.id}: ${s.sessionTitle ?? '(untitled)'}`);
}

ListSessionsOptions: cwd (scope to a directory; ignored when fetchOutsideCWD is true), fetchOutsideCWD (return sessions from every working directory), numSessions (cap on results), sessionsDir (override the sessions root, default ~/.factory/sessions/).

Rename Session

Give the current session a human-readable title.

import { createSession } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});

await session.renameSession({ title: 'Refactor auth module' });

await session.close();

Streaming

Full Message Streaming

By default, stream() yields complete messages: assistant text, user messages, tool calls, tool results, hooks, errors, and the final result.

import { createSession, DroidMessageType } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});

for await (const msg of session.stream(
  'List files in the current directory.'
)) {
  switch (msg.type) {
    case DroidMessageType.Assistant:
      console.log(msg.text);
      break;
    case DroidMessageType.ToolCall:
      console.log(`[Tool] ${msg.toolUse.name}`);
      break;
    case DroidMessageType.ToolResult:
      console.log(`[Result] ${msg.isError ? 'Error' : 'OK'}`);
      break;
    case DroidMessageType.Result:
      console.log(`Done in ${msg.durationMs}ms`);
      break;
  }
}

await session.close();

The default stream also yields user messages (DroidMessageType.User); the example above simply does not handle them. See the README's DroidMessage Types tables for the full type list.

Partial Message Streaming

Enable includePartialMessages to also get token-by-token deltas, thinking blocks, tool progress, token-usage updates, and more.

import { createSession, DroidMessageType } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});

for await (const msg of session.stream('Explain recursion.', {
  includePartialMessages: true,
})) {
  if (msg.type === DroidMessageType.AssistantTextDelta) {
    process.stdout.write(msg.text);
  }
}

await session.close();

Interrupt or Cancel Running Work

Use session.interrupt() to stop the current turn server-side, or pass an AbortSignal to cancel from the client.

import { createSession, DroidMessageType } from '@factory/droid-sdk';

// Interrupt after receiving some output
const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});
for await (const msg of session.stream('Write a long essay.')) {
  if (msg.type === DroidMessageType.Assistant) {
    // Interrupt once, after the first chunk of output, then stop reading.
    await session.interrupt();
    break;
  }
}
await session.close();

// Or cancel with AbortSignal
const session2 = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});
const controller = new AbortController();
setTimeout(() => controller.abort(), 2000);

try {
  for await (const msg of session2.stream('Write a long essay.', {
    abortSignal: controller.signal,
  })) {
  }
} catch {
  console.log('Aborted');
}
await session2.close();

When you abort via AbortSignal, the stream throws a plain Error carrying the signal's reason (not one of the typed SDK errors). See Error Handling.


Controlling the Droid

Autonomy Levels

Control what Droid can do without asking for permission. Set at session creation or change mid-session.

import { createSession, AutonomyLevel } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
  autonomyLevel: AutonomyLevel.High, // Off | Low | Medium | High
});

// Change mid-session
await session.updateSettings({ autonomyLevel: AutonomyLevel.Low });
await session.close();

Enabled/Disabled Tools

Restrict which exec tools Droid can use. Tool IDs are the CLI's internal IDs ('read-cli', 'execute-cli', 'grep_tool_cli', 'ls-cli', 'glob-search-cli', ...), not the model-facing names like 'Read'. Use session.listTools() to discover the exact IDs and their current allow/deny state.

import { createSession } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
  disabledToolIds: ['execute-cli'],
});

// Change mid-session
await session.updateSettings({
  disabledToolIds: ['read-cli', 'execute-cli'],
});
await session.close();

disabledToolIds turns the listed tools off. enabledToolIds re-enables tools that are off by default; it is applied on top of the default tool set, not as an exclusive allowlist. Disabling a tool is the reliable way to restrict capability.

Tool discovery with listTools()

session.listTools() returns the exec tool catalog with each tool's id, llmId, displayName, category, defaultAllowed, and currentlyAllowed. Use it to find IDs and confirm the effect of your overrides.

import { createSession } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
  disabledToolIds: ['execute-cli'],
});

const { tools } = await session.listTools();
for (const t of tools) {
  console.log(`${t.id} (${t.llmId}): allowed=${t.currentlyAllowed}`);
}

await session.close();

Permission Handler

Programmatically approve or reject tool calls instead of prompting a human. The handler receives full tool details including file paths and commands.

By default (no handler set), every confirmation request is cancelled. At higher autonomy levels most tool calls are auto-approved and never reach a handler; to route confirmations through your handler, set a stricter autonomyLevel (such as AutonomyLevel.Off) or rely on the tools that always require confirmation (such as MCP tools, see below).

import {
  run,
  ToolConfirmationOutcome,
  ToolConfirmationType,
} from '@factory/droid-sdk';

await run('Create hello.txt with "Hello, World!"', {
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
  permissionHandler(params) {
    const safe = params.toolUses.every(
      (item) => item.details.type === ToolConfirmationType.Create
    );
    return safe
      ? ToolConfirmationOutcome.ProceedOnce
      : ToolConfirmationOutcome.Cancel;
  },
});

A handler may return a ToolConfirmationOutcome enum value, a plain string outcome, or an object of the form { selectedOption, updatedContent? }. Returning ToolConfirmationOutcome.Cancel rejects the call.

Ask-User Handler

Programmatically answer questions that Droid asks the user during execution.

import { createSession, DroidMessageType } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
  askUserHandler(params) {
    return {
      cancelled: false,
      answers: params.questions.map((q) => ({
        index: q.index,
        question: q.question,
        answer: q.options[0] ?? 'yes',
      })),
    };
  },
});

for await (const msg of session.stream('Help me set up this project.')) {
  if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
}

await session.close();

Spec Mode

Start Droid in read-only planning mode. It researches and produces a plan, then requests to exit spec mode for implementation.

import {
  createSession,
  DroidInteractionMode,
  ToolConfirmationOutcome,
  ToolConfirmationType,
} from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
  interactionMode: DroidInteractionMode.Spec,
  permissionHandler(params) {
    const exitsSpec = params.toolUses.some(
      (t) => t.details.type === ToolConfirmationType.ExitSpecMode
    );
    return exitsSpec
      ? ToolConfirmationOutcome.ProceedOnce
      : ToolConfirmationOutcome.Cancel;
  },
});

for await (const msg of session.stream('Plan a refactor of src/utils.ts')) {
}
await session.close();

You can also switch an existing session into spec mode with session.enterSpecMode({ specModeModelId?, specModeReasoningEffort? }). When approving the exit-spec-mode confirmation, returning ToolConfirmationOutcome.ProceedOnce continues implementation in the same session, while ToolConfirmationOutcome.ProceedNewSessionHigh hands off to a fresh session.

Model and Reasoning Effort

Choose which model to use and how much reasoning effort to apply. Configurable at creation or mid-session.

import { createSession, ReasoningEffort } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
  modelId: 'claude-sonnet-4-20250514',
  reasoningEffort: ReasoningEffort.High,
});

// Change mid-session
await session.updateSettings({
  reasoningEffort: ReasoningEffort.Low,
});

await session.close();

For valid model identifiers, see the ModelID enum in the @factory/droid-sdk/protocol subpath export. The full ReasoningEffort enum is listed in the Configuration Reference.

Multimodal Input

Send images or documents alongside your prompt. Images must be base64-encoded; mediaType must be one of image/jpeg, image/png, image/gif, or image/webp.

import { readFileSync } from 'node:fs';
import { createSession, DroidMessageType } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});

for await (const msg of session.stream('Describe this image.', {
  images: [
    {
      type: 'base64',
      data: readFileSync('screenshot.png').toString('base64'),
      mediaType: 'image/png',
    },
  ],
})) {
  if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
}

await session.close();

Documents are passed through the files option (DocumentSource[]) the same way:

import { readFileSync } from 'node:fs';
import { createSession, DroidMessageType } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});

for await (const msg of session.stream('Summarize this document.', {
  files: [
    {
      type: 'base64',
      data: readFileSync('report.pdf').toString('base64'),
      mediaType: 'application/pdf',
    },
  ],
})) {
  if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
}

await session.close();

MCP

SDK-backed MCP Tools

Define custom tools that Droid can call during a session. Tools are served via a local MCP server that the SDK starts and stops automatically with the session lifecycle.

import {
  createSession,
  createSdkMcpServer,
  DroidMessageType,
  tool,
  ToolConfirmationOutcome,
  ToolConfirmationType,
} from '@factory/droid-sdk';
import { z } from 'zod';

const server = createSdkMcpServer({
  name: 'my-tools',
  tools: [
    tool(
      'lookup',
      'Look up a user by name',
      { name: z.string() },
      ({ name }) => {
        return `${name} is user #42.`;
      }
    ),
  ],
});

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
  mcpServers: [server],
  permissionHandler(params) {
    const allMcp = params.toolUses.every(
      (item) => item.details.type === ToolConfirmationType.McpTool
    );
    return allMcp
      ? ToolConfirmationOutcome.ProceedOnce
      : ToolConfirmationOutcome.Cancel;
  },
});

for await (const msg of session.stream('Look up Alice.')) {
  if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
}

await session.close();

MCP tool calls request confirmation even at the default autonomy level, so the session must supply a permissionHandler (or set a high autonomy level) to approve them. The handler above gates approval on ToolConfirmationType.McpTool so it only auto-approves your tools and cancels everything else.

tool() has two forms: tool(name, description, zodShape, handler) for typed input validation (the input is parsed against the Zod object shape), and tool(name, description, handler) with no schema for tools that take no structured input. A tool handler may return a string or a full MCP CallToolResult.

MCP Server Management

Add, remove, toggle, and list external MCP servers from within an active session.

Side effect warning: addMcpServer() persists the server into your user-level droid settings, so it remains configured in future sessions until removed. removeMcpServer() and toggleMcpServer() likewise operate on user-level settings and require settingsLevel: SettingsLevel.User. The example below adds a server and then removes it in the same script so it leaves no trace.

import {
  createSession,
  McpServerType,
  SettingsLevel,
} from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});

await session.addMcpServer({
  name: 'my-server',
  type: McpServerType.Http,
  url: 'https://mcp.example.com/mcp',
});

const { servers, summary } = await session.listMcpServers();
// `summary` is an McpStatusSummary: { total, connected, connecting, failed, disabled? }.
// There is no `summary.status` field; per-server status lives on servers[i].status.
console.log(`Connected: ${summary.connected}/${summary.total}`);
for (const s of servers) {
  console.log(`${s.name}: ${s.status}`);
}

// Toggle a server on/off (user-level settings)...
await session.toggleMcpServer({
  serverName: 'my-server',
  enabled: false,
  settingsLevel: SettingsLevel.User,
});

// ...and remove it to undo the persistent change made by addMcpServer().
await session.removeMcpServer({
  serverName: 'my-server',
  settingsLevel: SettingsLevel.User,
});

await session.close();

Inspect tools exposed by MCP servers with session.listMcpTools(), and authenticate OAuth-style servers with session.authenticateMcpServer(params).


Observability

Hook Execution Monitoring

Observe file hooks (pre/post tool execution hooks) as they run during a session.

Hooks must be configured in your droid settings files. The SDK only observes hook executions reported by the CLI; it does not register hooks. If no hooks are configured for the events your session triggers, the stream contains no hook messages and the loop below prints nothing.

import { createSession, DroidMessageType } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});

for await (const msg of session.stream('Create a new file.')) {
  if (msg.type === DroidMessageType.Hook) {
    if (msg.status === 'started') {
      console.log(`[Hook] ${msg.command}`);
    } else {
      console.log(`[Hook ${msg.status}] exit=${msg.exitCode}`);
    }
  }
}

await session.close();

Token Usage Tracking

Monitor token consumption in real time via stream events (requires includePartialMessages: true), or read the final totals from the result.

import { createSession, DroidMessageType } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});

for await (const msg of session.stream('Summarize this project.', {
  includePartialMessages: true,
})) {
  if (msg.type === DroidMessageType.TokenUsageUpdate) {
    console.log(`Tokens — in: ${msg.inputTokens}, out: ${msg.outputTokens}`);
  }
  if (msg.type === DroidMessageType.Result && msg.tokenUsage) {
    console.log(
      `Final — in: ${msg.tokenUsage.inputTokens}, out: ${msg.tokenUsage.outputTokens}`
    );
  }
}

await session.close();

Context Stats

Query current context window usage to understand how much capacity remains.

import { createSession } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});
for await (const msg of session.stream('Hello')) {
}

const stats = await session.getContextStats();
console.log(
  `Used: ${stats.used}, Remaining: ${stats.remaining}, Limit: ${stats.limit}`
);

await session.close();

List Skills

List all skills available in the current session.

import { createSession } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});
const { skills } = await session.listSkills();

for (const skill of skills) {
  console.log(`${skill.name} (${skill.location}): ${skill.description ?? ''}`);
}

await session.close();

Raw Notification Subscription

Subscribe to raw protocol notifications for custom event handling beyond the stream API. The callback receives the raw JSON-RPC envelope; the payload is at params.notification. An optional filter restricts which notification types are delivered.

import { createSession, SessionNotificationType } from '@factory/droid-sdk';

const session = await createSession({
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});

const unsubscribe = session.onNotification(
  (notification) => {
    console.log('Notification:', notification);
  },
  { type: SessionNotificationType.ERROR }
);

// ... use the session ...

unsubscribe();
await session.close();

Error Handling

There are two distinct failure surfaces, and most failures are the first kind:

  1. Error results — the turn completes but reports a problem. run() returns (and stream() yields) a DroidResult with isError: true. This is the most common failure mode and does not throw.
  2. Thrown exceptions — connection, protocol, or session-level failures throw typed SDK errors you catch with try/catch.

Handling error results

Always check result.isError before trusting text or structuredOutput:

import { run } from '@factory/droid-sdk';

const result = await run('Do something that might fail.', {
  apiKey: process.env.FACTORY_API_KEY!,
  cwd: process.cwd(),
});

if (result.isError) {
  // subtype is 'error_during_execution' | 'error_structured_output'
  console.error(`Failed (${result.subtype}):`, result.errors);
  if (result.structuredOutputError) {
    console.error('Structured output error:', result.structuredOutputError);
  }
} else {
  console.log(result.text);
}

Result discrimination fields: success / isError (booleans), subtype ('success' | 'error_during_execution' | 'error_structured_output'), errors (error descriptions, error results only), error (the first error event, or null), and structuredOutputError (structured-output parse/validation failure, if any). The same result message arrives as the final DroidMessageType.Result event when streaming.

Handling thrown errors

The SDK throws typed errors with structured context. Catch specific classes to handle different failure modes.

import {
  resumeSession,
  ConnectionError,
  TimeoutError,
  SessionNotFoundError,
  ProtocolError,
} from '@factory/droid-sdk';

try {
  const session = await resumeSession('nonexistent-id');
} catch (error) {
  if (error instanceof SessionNotFoundError) {
    console.log(`Session not found: ${error.sessionId}`);
  } else if (error instanceof TimeoutError) {
    console.log('Request timed out');
  } else if (error instanceof ConnectionError) {
    console.log(`Connection failed: ${error.message}`);
  } else if (error instanceof ProtocolError) {
    console.log(`Protocol error ${error.code}: ${error.message}`);
  }
}

See the Error Types reference for all classes. Note that aborting a stream via AbortSignal throws a plain Error (carrying signal.reason), not one of these typed errors.


Low-level APIs

Most users should use run() and DroidSession. For direct RPC access, the SDK also exports ProcessTransport, ProtocolEngine, and DroidClient.

DroidClient exposes additional methods not on DroidSession: killWorkerSession(), cancelMcpAuth(), clearMcpAuth(), submitMcpAuthCode(), listMcpRegistry(), toggleMcpTool(), and submitBugReport().

The package also re-exports its full Zod schema surface from the package root for runtime validation of protocol payloads. Low-level JSON-RPC protocol types, enums (including ModelID), and schemas are additionally available from the @factory/droid-sdk/protocol subpath export and the protocol namespace export. Factory REST API helpers (for managing remote computers, machine templates, and remote sessions) are also exported from the package root.


Configuration Reference

CreateSessionOptions

FieldTypeDescription
apiKeystringRequired by the type. Runtime value may be undefined; the CLI then uses stored credentials
cwdstringWorking directory for the session (default: ".")
machineIdstringMachine identifier for initialization (default: "default")
modelIdstringLLM model identifier
autonomyLevelAutonomyLevelOff | Low | Medium | High
interactionModeDroidInteractionModeAuto | Spec | AGI
reasoningEffortReasoningEffortNone | Dynamic | Off | Minimal | Low | Medium | High | ExtraHigh ('xhigh') | Max
specModeModelIdstringOverride model for spec mode
specModeReasoningEffortReasoningEffortOverride reasoning effort for spec mode
mcpServersDroidMcpServerConfig[]Initial MCP server configs, including SDK-backed servers from createSdkMcpServer()
enabledToolIdsstring[]Tool IDs to re-enable on top of the default set (not an exclusive allowlist)
disabledToolIdsstring[]Tool IDs to disable
tagsSessionTag[]Session tags for categorization (the SDK always appends its own SDK tag)
sessionSourceSessionSourceAttribution metadata (e.g., integration origin)
permissionHandlerPermissionHandlerTool confirmation callback (default behavior with no handler: cancel everything)
askUserHandlerAskUserHandlerStructured user-input callback
execPathstringPath to droid executable (default: "droid")
execArgsstring[]Extra CLI arguments for the subprocess
envRecord<string, string>Environment variables for the subprocess
transportDroidClientTransportCustom transport (skips subprocess spawn)
abortSignalAbortSignalCancellation signal

ResumeSessionOptions

Accepted by resumeSession(sessionId, options?). The options argument is optional. resumeSession() accepts only the subset of options needed to reconnect; new-session-only options such as modelId or interactionMode are not accepted, and cwd is intentionally omitted (the persisted session cwd is authoritative).

FieldTypeDescription
apiKeystringOptional on resume. When omitted, the CLI uses stored credentials
mcpServersDroidMcpServerConfig[]MCP servers to attach to the resumed session
permissionHandlerPermissionHandlerTool confirmation callback
askUserHandlerAskUserHandlerStructured user-input callback
execPathstringPath to droid executable (default: "droid")
execArgsstring[]Extra CLI arguments for the subprocess
envRecord<string, string>Environment variables for the subprocess
transportDroidClientTransportCustom transport (skips subprocess spawn)
abortSignalAbortSignalCancellation signal

MessageOptions

Accepted by session.stream() and run() (run() accepts all of these except includePartialMessages):

FieldTypeDescription
imagesBase64ImageSource[]Base64-encoded image attachments
filesDocumentSource[]Document/file attachments
outputFormatOutputFormatStructured output JSON schema request
includePartialMessagesbooleanstream() only: yield token-level deltas and progress events
abortSignalAbortSignalCancellation signal for this turn

Error Types

ErrorDescription
ConnectionErrorFailed to connect to the droid subprocess
ProtocolErrorJSON-RPC or protocol-level failure
SessionErrorBase session error
SessionNotFoundErrorRequested session does not exist
TimeoutErrorRPC timed out
ProcessExitErrorSubprocess exited unexpectedly

Aborting a turn via AbortSignal throws a plain Error (carrying signal.reason), not one of the typed SDK errors above.