diff --git a/bun.lock b/bun.lock
index f61606a..3d3c497 100644
--- a/bun.lock
+++ b/bun.lock
@@ -7,6 +7,8 @@
"devDependencies": {
"@biomejs/biome": "^2.3.10",
"@types/bun": "latest",
+ "typedoc": "^0.27.0",
+ "typedoc-plugin-markdown": "^4.4.0",
"typescript": "^5.7.0",
"zod": "^4.2.1",
},
@@ -37,16 +39,56 @@
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.10", "", { "os": "win32", "cpu": "x64" }, "sha512-pHEFgq7dUEsKnqG9mx9bXihxGI49X+ar+UBrEIj3Wqj3UCZp1rNgV+OoyjFgcXsjCWpuEAF4VJdkZr3TrWdCbQ=="],
+ "@gerrit0/mini-shiki": ["@gerrit0/mini-shiki@1.27.2", "", { "dependencies": { "@shikijs/engine-oniguruma": "^1.27.2", "@shikijs/types": "^1.27.2", "@shikijs/vscode-textmate": "^10.0.1" } }, "sha512-GeWyHz8ao2gBiUW4OJnQDxXQnFgZQwwQk05t/CVVgNBN7/rK8XZ7xY6YhLVv9tH3VppWWmr9DCl3MwemB/i+Og=="],
+
+ "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1" } }, "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA=="],
+
+ "@shikijs/types": ["@shikijs/types@1.29.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="],
+
+ "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
+
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
+ "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
+
"@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
+ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
+
+ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
+
+ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
+ "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
+ "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
+
+ "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="],
+
+ "lunr": ["lunr@2.3.9", "", {}, "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="],
+
+ "markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="],
+
+ "mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="],
+
+ "minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+
+ "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="],
+
+ "typedoc": ["typedoc@0.27.9", "", { "dependencies": { "@gerrit0/mini-shiki": "^1.24.0", "lunr": "^2.3.9", "markdown-it": "^14.1.0", "minimatch": "^9.0.5", "yaml": "^2.6.1" }, "peerDependencies": { "typescript": "5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x || 5.7.x || 5.8.x" }, "bin": { "typedoc": "bin/typedoc" } }, "sha512-/z585740YHURLl9DN2jCWe6OW7zKYm6VoQ93H0sxZ1cwHQEQrUn5BJrEnkWhfzUdyO+BLGjnKUZ9iz9hKloFDw=="],
+
+ "typedoc-plugin-markdown": ["typedoc-plugin-markdown@4.9.0", "", { "peerDependencies": { "typedoc": "0.28.x" } }, "sha512-9Uu4WR9L7ZBgAl60N/h+jqmPxxvnC9nQAlnnO/OujtG2ubjnKTVUFY1XDhcMY+pCqlX3N2HsQM2QTYZIU9tJuw=="],
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
+ "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+ "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
+
"zod": ["zod@4.2.1", "", {}, "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw=="],
}
}
diff --git a/docs/api-reference/classes/droid.mdx b/docs/api-reference/classes/droid.mdx
new file mode 100644
index 0000000..d567925
--- /dev/null
+++ b/docs/api-reference/classes/droid.mdx
@@ -0,0 +1,205 @@
+---
+title: Droid
+description: Main entry point for the Droid SDK
+---
+
+# Droid
+
+The `Droid` class is the main entry point for the SDK. It provides methods for executing prompts and managing conversation threads.
+
+## Import
+
+```typescript
+import { Droid, MODELS } from '@activade/droid-sdk';
+```
+
+## Constructor
+
+```typescript
+new Droid(config?: DroidConfig)
+```
+
+Creates a new Droid instance with the specified configuration.
+
+### Parameters
+
+
+ Configuration options for the Droid instance.
+
+
+
+ Working directory for CLI operations.
+
+
+ AI model to use for generation.
+
+
+ Level of autonomous decision-making: `'default'`, `'low'`, `'medium'`, or `'high'`.
+
+
+ Reasoning intensity: `'off'`, `'none'`, `'low'`, `'medium'`, or `'high'`.
+
+
+ Path to the Droid CLI binary.
+
+
+ Maximum execution time in milliseconds.
+
+
+
+
+### Example
+
+```typescript
+const droid = new Droid({
+ model: MODELS.CLAUDE_SONNET,
+ autonomyLevel: 'high',
+ reasoningEffort: 'medium',
+ cwd: '/path/to/project',
+ timeout: 300000
+});
+```
+
+## Properties
+
+### config
+
+```typescript
+get config(): Readonly
+```
+
+Returns the current configuration as a readonly object.
+
+```typescript
+const droid = new Droid({ model: MODELS.CLAUDE_SONNET });
+console.log(droid.config.model); // 'claude-sonnet-4-5-20250929'
+```
+
+## Methods
+
+### startThread()
+
+```typescript
+startThread(options?: ThreadOptions): Thread
+```
+
+Creates a new conversation thread for multi-turn interactions.
+
+
+ Thread-specific configuration that overrides instance defaults.
+
+
+**Returns:** A new `Thread` instance.
+
+```typescript
+const thread = droid.startThread({
+ autonomyLevel: 'high',
+ enabledTools: ['file_read', 'file_write']
+});
+
+await thread.run('Create a REST API');
+await thread.run('Add authentication');
+
+console.log('Session ID:', thread.id);
+```
+
+### resumeThread()
+
+```typescript
+resumeThread(sessionId: string, options?: ThreadOptions): Thread
+```
+
+Resumes a previously created conversation thread.
+
+
+ The unique session identifier from a previous thread.
+
+
+ Thread-specific configuration overrides.
+
+
+**Returns:** A `Thread` instance connected to the existing session.
+
+```typescript
+const thread = droid.resumeThread('session_abc123...');
+await thread.run('What did we work on last time?');
+```
+
+### exec()
+
+```typescript
+exec(prompt: string, options?: ExecOptions): Promise
+```
+
+Executes a single prompt without maintaining conversation state.
+
+
+ The natural language prompt to execute.
+
+
+ Execution options including model, output schema, etc.
+
+
+**Returns:** A promise resolving to the execution result.
+
+**Throws:**
+- `ExecutionError` - If the CLI execution fails
+- `TimeoutError` - If the operation exceeds the configured timeout
+- `CliNotFoundError` - If the Droid CLI is not installed
+
+```typescript
+const result = await droid.exec('Generate a UUID');
+console.log(result.finalResponse);
+```
+
+### listTools()
+
+```typescript
+listTools(model?: string): Promise
+```
+
+Lists all available tools for the configured or specified model.
+
+
+ Model ID to query tools for (defaults to instance model).
+
+
+**Returns:** A promise resolving to an array of tool names.
+
+```typescript
+const tools = await droid.listTools();
+console.log('Available tools:', tools);
+// ['file_read', 'file_write', 'shell_exec', ...]
+```
+
+## Full Example
+
+```typescript
+import { Droid, MODELS } from '@activade/droid-sdk';
+import { z } from 'zod';
+
+const droid = new Droid({
+ model: MODELS.CLAUDE_SONNET,
+ autonomyLevel: 'high',
+ cwd: './my-project'
+});
+
+// One-shot execution
+const result = await droid.exec('Create a hello world function');
+console.log(result.finalResponse);
+
+// Multi-turn conversation
+const thread = droid.startThread();
+await thread.run('Create a React component');
+await thread.run('Add unit tests for it');
+
+// Structured output
+const schema = z.object({
+ name: z.string(),
+ version: z.string()
+});
+
+const pkgResult = await droid.exec('Get package info as JSON');
+const pkg = pkgResult.parse(schema);
+console.log(pkg.name, pkg.version);
+```
diff --git a/docs/api-reference/classes/thread.mdx b/docs/api-reference/classes/thread.mdx
new file mode 100644
index 0000000..cfcd096
--- /dev/null
+++ b/docs/api-reference/classes/thread.mdx
@@ -0,0 +1,226 @@
+---
+title: Thread
+description: Multi-turn conversation management
+---
+
+# Thread
+
+The `Thread` class manages multi-turn conversations with the Droid AI. It maintains context across prompts and supports both synchronous and streaming execution.
+
+## Import
+
+```typescript
+import { Droid } from '@activade/droid-sdk';
+
+// Threads are created via Droid methods
+const droid = new Droid();
+const thread = droid.startThread();
+```
+
+## Properties
+
+### id
+
+```typescript
+get id(): string | undefined
+```
+
+The unique session identifier for this thread. Returns `undefined` until the first prompt is executed.
+
+```typescript
+const thread = droid.startThread();
+console.log(thread.id); // undefined
+
+await thread.run('Hello');
+console.log(thread.id); // 'session_abc123...'
+```
+
+### cwd
+
+```typescript
+get cwd(): string
+```
+
+The working directory for this thread's CLI operations.
+
+```typescript
+const thread = droid.startThread({ cwd: '/projects/my-app' });
+console.log(thread.cwd); // '/projects/my-app'
+```
+
+## Methods
+
+### run()
+
+```typescript
+run(prompt: string, options?: RunOptions): Promise
+```
+
+Executes a prompt and waits for the complete response.
+
+
+ The natural language prompt to execute.
+
+
+ Run-specific configuration.
+
+
+
+ JSON Schema for structured output.
+
+
+ Path to a file containing the prompt.
+
+
+ File attachments to include with the prompt.
+
+
+ Override the model for this run.
+
+
+ Override the autonomy level for this run.
+
+
+
+
+**Returns:** A promise resolving to the execution result.
+
+```typescript
+const result = await thread.run('Create a function to sort an array');
+console.log(result.finalResponse);
+
+// Access tool calls
+for (const call of result.toolCalls) {
+ console.log(`Used tool: ${call.toolName}`);
+}
+```
+
+### runStreamed()
+
+```typescript
+runStreamed(prompt: string, options?: RunOptions): Promise
+```
+
+Executes a prompt with real-time streaming of events.
+
+
+ The natural language prompt to execute.
+
+
+ Run-specific configuration.
+
+
+**Returns:** A `StreamedTurn` containing an async event iterator and result promise.
+
+```typescript
+const { events, result } = await thread.runStreamed('Build a REST API');
+
+for await (const event of events) {
+ switch (event.type) {
+ case 'message':
+ console.log(`[${event.role}] ${event.text}`);
+ break;
+ case 'tool_call':
+ console.log(`Calling: ${event.toolName}`);
+ break;
+ case 'tool_result':
+ console.log(`Result: ${event.isError ? 'ERROR' : 'OK'}`);
+ break;
+ case 'completion':
+ console.log(`Completed in ${event.durationMs}ms`);
+ break;
+ }
+}
+
+const finalResult = await result;
+```
+
+## StreamedTurn
+
+The return type of `runStreamed()`:
+
+```typescript
+interface StreamedTurn {
+ events: AsyncIterable;
+ result: Promise;
+}
+```
+
+### Event Types
+
+| Type | Description | Properties |
+|------|-------------|------------|
+| `system` | System init | `cwd`, `session_id`, `tools`, `model` |
+| `message` | Message | `role`, `text`, `id`, `timestamp` |
+| `tool_call` | Tool call | `toolName`, `parameters`, `id` |
+| `tool_result` | Tool result | `toolName`, `value`, `isError` |
+| `completion` | Complete | `finalText`, `durationMs`, `numTurns` |
+| `turn.failed` | Failed | `error.message`, `error.code` |
+
+### Type Guards
+
+Use type guards for type-safe event handling:
+
+```typescript
+import {
+ isToolCallEvent,
+ isToolResultEvent,
+ isMessageEvent
+} from '@activade/droid-sdk';
+
+for await (const event of events) {
+ if (isToolCallEvent(event)) {
+ // TypeScript knows event is ToolCallEvent
+ console.log(event.toolName, event.parameters);
+ }
+}
+```
+
+## File Attachments
+
+Attach files to provide context:
+
+```typescript
+const result = await thread.run('Describe this image', {
+ attachments: [
+ { path: './screenshot.png', type: 'image' }
+ ]
+});
+
+const result = await thread.run('Review these files', {
+ attachments: [
+ { path: './src/main.ts', type: 'text', description: 'Main entry' },
+ { path: './data.json', type: 'data' }
+ ]
+});
+```
+
+## Full Example
+
+```typescript
+import { Droid, MODELS, isToolCallEvent } from '@activade/droid-sdk';
+
+const droid = new Droid({ model: MODELS.CLAUDE_SONNET });
+const thread = droid.startThread();
+
+// First turn
+await thread.run('Create a TypeScript function to validate emails');
+
+// Follow-up with context
+await thread.run('Add support for custom domain restrictions');
+
+// Streaming execution
+const { events, result } = await thread.runStreamed('Write tests');
+
+for await (const event of events) {
+ if (isToolCallEvent(event)) {
+ console.log(`Tool: ${event.toolName}`);
+ }
+}
+
+const finalResult = await result;
+console.log('Tests created:', finalResult.finalResponse);
+
+// Save session for later
+console.log('Session ID:', thread.id);
+```
diff --git a/docs/api-reference/classes/turn-result.mdx b/docs/api-reference/classes/turn-result.mdx
new file mode 100644
index 0000000..f4c8d66
--- /dev/null
+++ b/docs/api-reference/classes/turn-result.mdx
@@ -0,0 +1,267 @@
+---
+title: TurnResult
+description: Execution result wrapper with parsing and accessors
+---
+
+# TurnResult
+
+The `TurnResult` class encapsulates all data from a single AI interaction, including the final response, tool calls, messages, and execution metadata.
+
+## Import
+
+```typescript
+import { TurnResult } from '@activade/droid-sdk';
+
+// TurnResult is returned from Thread.run() and Droid.exec()
+const result = await thread.run('Generate code');
+```
+
+## Properties
+
+### finalResponse
+
+```typescript
+readonly finalResponse: string
+```
+
+The final text response from the AI. For structured output, this contains JSON that can be parsed using `parse()` or `tryParse()`.
+
+### items
+
+```typescript
+readonly items: AnyTurnItem[]
+```
+
+All items from this execution turn (messages, tool calls, tool results) in chronological order.
+
+### sessionId
+
+```typescript
+readonly sessionId: string
+```
+
+The unique session identifier. Use with `Droid.resumeThread()` to continue the conversation.
+
+### durationMs
+
+```typescript
+readonly durationMs: number
+```
+
+Total execution time in milliseconds.
+
+### numTurns
+
+```typescript
+readonly numTurns: number
+```
+
+Number of conversation turns in this execution.
+
+### isError
+
+```typescript
+readonly isError: boolean
+```
+
+Whether the execution resulted in an error.
+
+## Methods
+
+### parse()
+
+```typescript
+parse(schema: { parse: (data: unknown) => T }): T
+```
+
+Parses the final response as JSON and validates against a schema.
+
+
+ A schema with a `parse` method (e.g., Zod schema).
+
+
+**Returns:** The parsed and validated data.
+
+**Throws:**
+- `ParseError` - If the response is not valid JSON
+- Schema error - If validation fails
+
+```typescript
+import { z } from 'zod';
+
+const UserSchema = z.object({
+ id: z.number(),
+ name: z.string(),
+ email: z.string().email()
+});
+
+const result = await thread.run('Generate a user object as JSON');
+const user = result.parse(UserSchema);
+// TypeScript knows: user.id is number, user.name is string, etc.
+```
+
+### tryParse()
+
+```typescript
+tryParse(schema: { safeParse: (data: unknown) => { success: boolean; data?: T } }): T | null
+```
+
+Attempts to parse the final response, returning `null` on failure.
+
+```typescript
+const config = result.tryParse(ConfigSchema);
+
+if (config) {
+ console.log('Config:', config);
+} else {
+ console.log('No valid config in response');
+}
+```
+
+### toolCalls
+
+```typescript
+get toolCalls(): ToolCallItem[]
+```
+
+All tool calls made during this execution.
+
+```typescript
+for (const call of result.toolCalls) {
+ console.log(`Tool: ${call.toolName}`);
+ console.log(`Params: ${JSON.stringify(call.parameters)}`);
+}
+```
+
+### toolResults
+
+```typescript
+get toolResults(): ToolResultItem[]
+```
+
+All tool results from this execution.
+
+```typescript
+for (const toolResult of result.toolResults) {
+ if (toolResult.isError) {
+ console.error(`${toolResult.toolName} failed: ${toolResult.value}`);
+ } else {
+ console.log(`${toolResult.toolName} succeeded`);
+ }
+}
+```
+
+### messages
+
+```typescript
+get messages(): MessageItem[]
+```
+
+All messages (user and assistant) from this execution.
+
+### assistantMessages
+
+```typescript
+get assistantMessages(): MessageItem[]
+```
+
+Assistant messages only, excluding user prompts.
+
+```typescript
+for (const msg of result.assistantMessages) {
+ console.log(`[${new Date(msg.timestamp).toISOString()}] ${msg.text}`);
+}
+```
+
+### toJSON()
+
+```typescript
+toJSON(): TurnResultData
+```
+
+Converts the result to a plain JSON-serializable object.
+
+```typescript
+// Save to file
+fs.writeFileSync('result.json', JSON.stringify(result.toJSON(), null, 2));
+```
+
+## Item Types
+
+### MessageItem
+
+```typescript
+interface MessageItem {
+ type: 'message';
+ role: 'user' | 'assistant';
+ id: string;
+ text: string;
+ timestamp: number;
+}
+```
+
+### ToolCallItem
+
+```typescript
+interface ToolCallItem {
+ type: 'tool_call';
+ id: string;
+ messageId: string;
+ toolId: string;
+ toolName: string;
+ parameters: Record;
+ timestamp: number;
+}
+```
+
+### ToolResultItem
+
+```typescript
+interface ToolResultItem {
+ type: 'tool_result';
+ id: string;
+ messageId: string;
+ toolId: string;
+ toolName: string;
+ isError: boolean;
+ value: string;
+ timestamp: number;
+}
+```
+
+## Full Example
+
+```typescript
+import { Droid } from '@activade/droid-sdk';
+import { z } from 'zod';
+
+const droid = new Droid();
+const thread = droid.startThread();
+
+const result = await thread.run('Create a config file and return the config as JSON');
+
+// Access metadata
+console.log(`Session: ${result.sessionId}`);
+console.log(`Duration: ${result.durationMs}ms`);
+console.log(`Turns: ${result.numTurns}`);
+console.log(`Error: ${result.isError}`);
+
+// Access tool activity
+console.log(`Tool calls: ${result.toolCalls.length}`);
+for (const call of result.toolCalls) {
+ const resultItem = result.toolResults.find(r => r.toolId === call.toolId);
+ console.log(` ${call.toolName}: ${resultItem?.isError ? 'FAILED' : 'OK'}`);
+}
+
+// Parse structured output
+const ConfigSchema = z.object({
+ port: z.number(),
+ host: z.string(),
+ debug: z.boolean().optional()
+});
+
+const config = result.tryParse(ConfigSchema);
+if (config) {
+ console.log(`Server: ${config.host}:${config.port}`);
+}
+```
diff --git a/docs/api-reference/cli/installer.mdx b/docs/api-reference/cli/installer.mdx
new file mode 100644
index 0000000..83bee90
--- /dev/null
+++ b/docs/api-reference/cli/installer.mdx
@@ -0,0 +1,141 @@
+---
+title: CLI Installer
+description: Functions for installing and managing the Droid CLI
+---
+
+# CLI Installer
+
+Functions for detecting and installing the Droid CLI binary.
+
+## Import
+
+```typescript
+import {
+ isDroidCliInstalled,
+ getDroidCliPath,
+ ensureDroidCli
+} from '@activade/droid-sdk/cli';
+```
+
+## isDroidCliInstalled()
+
+Check if the CLI is installed:
+
+```typescript
+async function isDroidCliInstalled(): Promise
+```
+
+### Example
+
+```typescript
+const installed = await isDroidCliInstalled();
+if (!installed) {
+ console.log('CLI not found, installing...');
+ await ensureDroidCli();
+}
+```
+
+## getDroidCliPath()
+
+Get the path to an installed CLI:
+
+```typescript
+async function getDroidCliPath(): Promise
+```
+
+### Example
+
+```typescript
+const path = await getDroidCliPath();
+if (path) {
+ console.log('CLI found at:', path);
+} else {
+ console.log('CLI not installed');
+}
+```
+
+## ensureDroidCli()
+
+Install the CLI if not present:
+
+```typescript
+async function ensureDroidCli(options?: InstallOptions): Promise
+```
+
+### Parameters
+
+```typescript
+interface InstallOptions {
+ /** Progress callback */
+ onProgress?: (progress: InstallProgress) => void;
+
+ /** Force reinstall even if already installed */
+ force?: boolean;
+}
+
+interface InstallProgress {
+ /** Current phase of installation */
+ phase: 'checking' | 'downloading' | 'installing' | 'complete';
+
+ /** Human-readable progress message */
+ message: string;
+
+ /** Progress percentage (0-100), if available */
+ percent?: number;
+}
+```
+
+### Example
+
+```typescript
+const cliPath = await ensureDroidCli({
+ onProgress: (progress) => {
+ console.log(`[${progress.phase}] ${progress.message}`);
+ if (progress.percent !== undefined) {
+ console.log(`Progress: ${progress.percent}%`);
+ }
+ }
+});
+
+console.log('CLI ready at:', cliPath);
+```
+
+### Force Reinstall
+
+```typescript
+await ensureDroidCli({ force: true });
+```
+
+## Full Example
+
+```typescript
+import { Droid } from '@activade/droid-sdk';
+import { ensureDroidCli, isDroidCliInstalled } from '@activade/droid-sdk/cli';
+
+async function setup() {
+ // Check and install if needed
+ if (!(await isDroidCliInstalled())) {
+ console.log('Installing Droid CLI...');
+ await ensureDroidCli({
+ onProgress: (p) => console.log(` ${p.message}`)
+ });
+ }
+
+ // Now safe to create Droid instance
+ const droid = new Droid();
+ return droid;
+}
+```
+
+## CI/CD Usage
+
+```typescript
+// In CI, install quietly
+await ensureDroidCli({
+ onProgress: (p) => {
+ if (p.phase === 'complete') {
+ console.log('CLI installed successfully');
+ }
+ }
+});
+```
diff --git a/docs/api-reference/cli/process.mdx b/docs/api-reference/cli/process.mdx
new file mode 100644
index 0000000..30c70c5
--- /dev/null
+++ b/docs/api-reference/cli/process.mdx
@@ -0,0 +1,188 @@
+---
+title: CLI Process
+description: Low-level CLI process management functions
+---
+
+# CLI Process
+
+Low-level functions for spawning and managing CLI processes. These are primarily used internally but are exported for advanced use cases.
+
+## Import
+
+```typescript
+import {
+ spawnDroid,
+ spawnDroidStreaming,
+ execDroidJson,
+ listDroidTools,
+ findDroidPath
+} from '@activade/droid-sdk/cli';
+```
+
+## findDroidPath()
+
+Locate the CLI binary:
+
+```typescript
+async function findDroidPath(customPath?: string): Promise
+```
+
+### Example
+
+```typescript
+try {
+ const path = await findDroidPath();
+ console.log('Found CLI at:', path);
+} catch (error) {
+ if (error instanceof CliNotFoundError) {
+ console.log('Searched:', error.searchedPaths);
+ }
+}
+```
+
+## spawnDroid()
+
+Spawn a CLI process and wait for completion:
+
+```typescript
+async function spawnDroid(options: SpawnOptions): Promise
+```
+
+### SpawnOptions
+
+```typescript
+interface SpawnOptions {
+ prompt: string;
+ cwd?: string;
+ droidPath?: string;
+ model?: string;
+ outputFormat?: OutputFormat;
+ timeout?: number;
+ autonomyLevel?: AutonomyLevel;
+ reasoningEffort?: ReasoningEffort;
+ outputSchema?: JsonSchema;
+ sessionId?: string;
+ enabledTools?: string[];
+ promptFile?: string;
+ attachments?: FileAttachment[];
+}
+```
+
+### DroidProcessResult
+
+```typescript
+interface DroidProcessResult {
+ stdout: string;
+ stderr: string;
+ exitCode: number | null;
+}
+```
+
+### Example
+
+```typescript
+const result = await spawnDroid({
+ prompt: 'Hello',
+ cwd: '/my/project',
+ outputFormat: 'json'
+});
+
+console.log('Output:', result.stdout);
+console.log('Exit code:', result.exitCode);
+```
+
+## spawnDroidStreaming()
+
+Spawn a CLI process with streaming output:
+
+```typescript
+async function spawnDroidStreaming(
+ options: SpawnOptions
+): Promise
+```
+
+### StreamingDroidProcess
+
+```typescript
+interface StreamingDroidProcess {
+ stdout: ReadableStream;
+ stderr: ReadableStream;
+ exitCode: Promise;
+}
+```
+
+### Example
+
+```typescript
+const process = await spawnDroidStreaming({
+ prompt: 'Build feature',
+ outputFormat: 'stream-json'
+});
+
+const reader = process.stdout.getReader();
+while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+ console.log('Chunk:', new TextDecoder().decode(value));
+}
+
+const exitCode = await process.exitCode;
+```
+
+## execDroidJson()
+
+Execute and parse JSON response:
+
+```typescript
+async function execDroidJson(options: SpawnOptions): Promise
+```
+
+### Example
+
+```typescript
+const result = await execDroidJson({
+ prompt: 'Generate data',
+ cwd: '/project'
+});
+
+console.log('Session:', result.session_id);
+console.log('Response:', result.result);
+```
+
+## listDroidTools()
+
+List available tools:
+
+```typescript
+async function listDroidTools(
+ droidPath?: string,
+ model?: string
+): Promise
+```
+
+### Example
+
+```typescript
+const tools = await listDroidTools();
+console.log('Available tools:', tools);
+// ['Read', 'Write', 'Bash', 'Glob', 'Grep', ...]
+```
+
+## Advanced Usage
+
+```typescript
+import {
+ spawnDroidStreaming,
+ parseJsonLines
+} from '@activade/droid-sdk/cli';
+
+// Custom streaming with event parsing
+const process = await spawnDroidStreaming({
+ prompt: 'Complex task',
+ outputFormat: 'stream-json'
+});
+
+for await (const event of parseJsonLines(process.stdout)) {
+ console.log('Event:', event);
+}
+```
diff --git a/docs/api-reference/errors/cli-not-found-error.mdx b/docs/api-reference/errors/cli-not-found-error.mdx
new file mode 100644
index 0000000..cd0f412
--- /dev/null
+++ b/docs/api-reference/errors/cli-not-found-error.mdx
@@ -0,0 +1,74 @@
+---
+title: CliNotFoundError
+description: Thrown when the Droid CLI is not installed
+---
+
+# CliNotFoundError
+
+Thrown when the Droid CLI binary cannot be found on the system.
+
+## Import
+
+```typescript
+import { CliNotFoundError, ensureDroidCli } from '@activade/droid-sdk';
+```
+
+## Properties
+
+### searchedPaths
+
+```typescript
+readonly searchedPaths: string[]
+```
+
+List of paths that were searched for the CLI.
+
+## Usage
+
+```typescript
+try {
+ await droid.exec('Hello');
+} catch (error) {
+ if (error instanceof CliNotFoundError) {
+ console.log('CLI not found in:', error.searchedPaths);
+
+ // Auto-install the CLI
+ await ensureDroidCli();
+
+ // Retry the operation
+ await droid.exec('Hello');
+ }
+}
+```
+
+## Common Causes
+
+1. **CLI not installed** - Run the installation script
+2. **PATH not configured** - Add CLI location to PATH
+3. **Custom path not set** - Configure `droidPath` in Droid options
+
+## Solutions
+
+### Install the CLI
+
+```bash
+curl -fsSL https://app.factory.ai/cli | sh
+```
+
+### Use Auto-installer
+
+```typescript
+import { ensureDroidCli } from '@activade/droid-sdk/cli';
+
+await ensureDroidCli({
+ onProgress: (p) => console.log(`[${p.phase}] ${p.message}`)
+});
+```
+
+### Specify Custom Path
+
+```typescript
+const droid = new Droid({
+ droidPath: '/custom/path/to/droid'
+});
+```
diff --git a/docs/api-reference/errors/droid-error.mdx b/docs/api-reference/errors/droid-error.mdx
new file mode 100644
index 0000000..283e768
--- /dev/null
+++ b/docs/api-reference/errors/droid-error.mdx
@@ -0,0 +1,76 @@
+---
+title: DroidError
+description: Base error class for all SDK errors
+---
+
+# DroidError
+
+The base class for all Droid SDK errors. Extend this for custom error handling.
+
+## Import
+
+```typescript
+import { DroidError } from '@activade/droid-sdk';
+```
+
+## Usage
+
+```typescript
+try {
+ await droid.exec('Do something');
+} catch (error) {
+ if (error instanceof DroidError) {
+ // Handle any SDK error
+ console.error('SDK error:', error.message);
+ }
+}
+```
+
+## Properties
+
+### message
+
+```typescript
+readonly message: string
+```
+
+Human-readable error description.
+
+### name
+
+```typescript
+readonly name: string
+```
+
+Error class name (`'DroidError'` or subclass name).
+
+## Error Hierarchy
+
+```
+DroidError
+├── CliNotFoundError
+├── ExecutionError
+├── ParseError
+├── TimeoutError
+└── StreamError
+```
+
+## Catching All SDK Errors
+
+```typescript
+import { DroidError } from '@activade/droid-sdk';
+
+async function safeDroidOperation() {
+ try {
+ return await droid.exec('Generate code');
+ } catch (error) {
+ if (error instanceof DroidError) {
+ // Log and handle SDK errors
+ console.error(`Droid SDK error: ${error.name} - ${error.message}`);
+ return null;
+ }
+ // Re-throw unknown errors
+ throw error;
+ }
+}
+```
diff --git a/docs/api-reference/errors/execution-error.mdx b/docs/api-reference/errors/execution-error.mdx
new file mode 100644
index 0000000..b6622e6
--- /dev/null
+++ b/docs/api-reference/errors/execution-error.mdx
@@ -0,0 +1,85 @@
+---
+title: ExecutionError
+description: Thrown when CLI execution fails
+---
+
+# ExecutionError
+
+Thrown when the Droid CLI process exits with an error.
+
+## Import
+
+```typescript
+import { ExecutionError } from '@activade/droid-sdk';
+```
+
+## Properties
+
+### exitCode
+
+```typescript
+readonly exitCode: number | null
+```
+
+The CLI process exit code, or `null` if unavailable.
+
+### stderr
+
+```typescript
+readonly stderr: string
+```
+
+Standard error output from the CLI.
+
+### stdout
+
+```typescript
+readonly stdout: string
+```
+
+Standard output from the CLI (may contain partial results).
+
+## Usage
+
+```typescript
+try {
+ await droid.exec('Risky operation');
+} catch (error) {
+ if (error instanceof ExecutionError) {
+ console.error('Execution failed:');
+ console.error('Exit code:', error.exitCode);
+ console.error('Error output:', error.stderr);
+ console.error('Standard output:', error.stdout);
+ }
+}
+```
+
+## Common Causes
+
+1. **Invalid prompt** - Malformed or empty prompt
+2. **Permission denied** - Insufficient permissions for file operations
+3. **Tool failure** - A tool returned an error
+4. **API error** - Backend API returned an error
+
+## Debugging
+
+```typescript
+try {
+ await droid.exec(prompt);
+} catch (error) {
+ if (error instanceof ExecutionError) {
+ // Log full error details
+ console.error({
+ message: error.message,
+ exitCode: error.exitCode,
+ stderr: error.stderr,
+ stdout: error.stdout
+ });
+
+ // Check for specific error patterns
+ if (error.stderr.includes('permission denied')) {
+ console.log('Try running with higher autonomy level');
+ }
+ }
+}
+```
diff --git a/docs/api-reference/errors/parse-error.mdx b/docs/api-reference/errors/parse-error.mdx
new file mode 100644
index 0000000..2ee3677
--- /dev/null
+++ b/docs/api-reference/errors/parse-error.mdx
@@ -0,0 +1,94 @@
+---
+title: ParseError
+description: Thrown when JSON parsing fails
+---
+
+# ParseError
+
+Thrown when attempting to parse a non-JSON response.
+
+## Import
+
+```typescript
+import { ParseError } from '@activade/droid-sdk';
+```
+
+## Properties
+
+### rawText
+
+```typescript
+readonly rawText: string
+```
+
+The original text that failed to parse.
+
+## Usage
+
+```typescript
+import { z } from 'zod';
+
+const Schema = z.object({ name: z.string() });
+
+try {
+ const data = result.parse(Schema);
+} catch (error) {
+ if (error instanceof ParseError) {
+ console.error('Not valid JSON');
+ console.error('Raw response:', error.rawText);
+ }
+}
+```
+
+## Distinguishing Parse Errors
+
+```typescript
+import { ParseError } from '@activade/droid-sdk';
+import { z } from 'zod';
+
+try {
+ const data = result.parse(MySchema);
+} catch (error) {
+ if (error instanceof ParseError) {
+ // Response was not valid JSON
+ console.error('JSON syntax error:', error.message);
+ console.error('Response was:', error.rawText);
+ } else if (error instanceof z.ZodError) {
+ // JSON was valid but didn't match schema
+ console.error('Validation errors:', error.issues);
+ }
+}
+```
+
+## Using tryParse Instead
+
+For optional JSON parsing, use `tryParse()` to avoid exceptions:
+
+```typescript
+const data = result.tryParse(MySchema);
+
+if (data) {
+ console.log('Parsed:', data);
+} else {
+ console.log('Response was not valid JSON');
+ console.log('Raw response:', result.finalResponse);
+}
+```
+
+## Prompting for Better JSON
+
+If you frequently get ParseError, improve your prompts:
+
+```typescript
+// Better prompt for JSON output
+const result = await droid.exec(`
+ Analyze the file and return ONLY a JSON object with this structure:
+ {
+ "lines": number,
+ "functions": number,
+ "complexity": "low" | "medium" | "high"
+ }
+
+ Return ONLY the JSON, no other text.
+`);
+```
diff --git a/docs/api-reference/errors/stream-error.mdx b/docs/api-reference/errors/stream-error.mdx
new file mode 100644
index 0000000..bcaeaed
--- /dev/null
+++ b/docs/api-reference/errors/stream-error.mdx
@@ -0,0 +1,96 @@
+---
+title: StreamError
+description: Thrown when stream processing fails
+---
+
+# StreamError
+
+Thrown when reading or processing a streaming response fails.
+
+## Import
+
+```typescript
+import { StreamError } from '@activade/droid-sdk';
+```
+
+## Properties
+
+### cause
+
+```typescript
+readonly cause?: Error
+```
+
+The underlying error that caused the stream failure, if available.
+
+## Usage
+
+```typescript
+try {
+ const { events, result } = await thread.runStreamed('Build feature');
+
+ for await (const event of events) {
+ // Process events
+ }
+
+ await result;
+} catch (error) {
+ if (error instanceof StreamError) {
+ console.error('Stream failed:', error.message);
+ if (error.cause) {
+ console.error('Caused by:', error.cause);
+ }
+ }
+}
+```
+
+## Common Causes
+
+1. **Connection interrupted** - Network issues during streaming
+2. **Process terminated** - CLI process was killed
+3. **Invalid stream data** - Malformed JSON in stream
+4. **Resource exhaustion** - Out of memory or file descriptors
+
+## Handling Stream Errors
+
+```typescript
+async function resilientStream(thread: Thread, prompt: string) {
+ const maxRetries = 3;
+
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
+ try {
+ const { events, result } = await thread.runStreamed(prompt);
+
+ for await (const event of events) {
+ if (event.type === 'turn.failed') {
+ throw new Error(event.error.message);
+ }
+ // Process event
+ }
+
+ return await result;
+ } catch (error) {
+ if (error instanceof StreamError && attempt < maxRetries - 1) {
+ console.log(`Stream error, retrying (${attempt + 1}/${maxRetries})`);
+ await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));
+ continue;
+ }
+ throw error;
+ }
+ }
+}
+```
+
+## Inline Error Events
+
+Note that some errors come as events rather than thrown exceptions:
+
+```typescript
+for await (const event of events) {
+ if (event.type === 'turn.failed') {
+ // Error during execution (not a StreamError)
+ console.error('Turn failed:', event.error);
+ break;
+ }
+}
+```
diff --git a/docs/api-reference/errors/timeout-error.mdx b/docs/api-reference/errors/timeout-error.mdx
new file mode 100644
index 0000000..96a1763
--- /dev/null
+++ b/docs/api-reference/errors/timeout-error.mdx
@@ -0,0 +1,90 @@
+---
+title: TimeoutError
+description: Thrown when operations exceed the timeout
+---
+
+# TimeoutError
+
+Thrown when an operation takes longer than the configured timeout.
+
+## Import
+
+```typescript
+import { TimeoutError } from '@activade/droid-sdk';
+```
+
+## Properties
+
+### timeoutMs
+
+```typescript
+readonly timeoutMs: number
+```
+
+The timeout value in milliseconds that was exceeded.
+
+## Usage
+
+```typescript
+try {
+ await droid.exec('Complex long-running task');
+} catch (error) {
+ if (error instanceof TimeoutError) {
+ console.error(`Timed out after ${error.timeoutMs}ms`);
+ }
+}
+```
+
+## Preventing Timeouts
+
+### Increase Timeout
+
+```typescript
+const droid = new Droid({
+ timeout: 600000 // 10 minutes
+});
+```
+
+### Per-execution Timeout
+
+```typescript
+// Use a longer timeout for specific operations
+const result = await droid.exec('Build entire project', {
+ timeout: 900000 // 15 minutes
+});
+```
+
+## Handling Timeouts Gracefully
+
+```typescript
+async function executeWithFallback(prompt: string) {
+ const droid = new Droid({ timeout: 60000 });
+
+ try {
+ return await droid.exec(prompt);
+ } catch (error) {
+ if (error instanceof TimeoutError) {
+ console.log('Operation timed out, trying with streaming...');
+
+ // Fall back to streaming for progress visibility
+ const thread = droid.startThread();
+ const { events, result } = await thread.runStreamed(prompt);
+
+ for await (const event of events) {
+ console.log('Progress:', event.type);
+ }
+
+ return await result;
+ }
+ throw error;
+ }
+}
+```
+
+## Default Timeout
+
+The default timeout is 600000ms (10 minutes). For complex operations, consider:
+
+1. Breaking into smaller tasks
+2. Using streaming for progress visibility
+3. Increasing the timeout value
diff --git a/docs/api-reference/models/model-info.mdx b/docs/api-reference/models/model-info.mdx
new file mode 100644
index 0000000..b5dd14e
--- /dev/null
+++ b/docs/api-reference/models/model-info.mdx
@@ -0,0 +1,144 @@
+---
+title: Model Info
+description: Model metadata and utilities
+---
+
+# Model Info
+
+Utilities for working with model metadata.
+
+## Import
+
+```typescript
+import {
+ getModelInfo,
+ isValidModel,
+ MODEL_INFO
+} from '@activade/droid-sdk';
+```
+
+## MODEL_INFO
+
+Registry of model metadata:
+
+```typescript
+const MODEL_INFO: Record = {
+ 'claude-opus-4-5-20251101': {
+ id: 'claude-opus-4-5-20251101',
+ name: 'Claude Opus 4.5',
+ provider: 'anthropic',
+ contextWindow: 200000,
+ maxOutput: 16384
+ },
+ 'claude-sonnet-4-5-20250929': {
+ id: 'claude-sonnet-4-5-20250929',
+ name: 'Claude Sonnet 4.5',
+ provider: 'anthropic',
+ contextWindow: 200000,
+ maxOutput: 16384
+ },
+ // ... more models
+};
+```
+
+## ModelInfo Interface
+
+```typescript
+interface ModelInfo {
+ /** Model identifier */
+ id: ModelId;
+
+ /** Human-readable model name */
+ name: string;
+
+ /** Model provider */
+ provider: 'anthropic' | 'openai' | 'google' | 'open-source';
+
+ /** Context window size in tokens */
+ contextWindow: number;
+
+ /** Maximum output tokens */
+ maxOutput: number;
+}
+```
+
+## getModelInfo()
+
+Get metadata for a model:
+
+```typescript
+function getModelInfo(modelId: string): ModelInfo | undefined
+```
+
+### Example
+
+```typescript
+const info = getModelInfo('claude-sonnet-4-5-20250929');
+
+if (info) {
+ console.log(`${info.name} by ${info.provider}`);
+ console.log(`Context: ${info.contextWindow} tokens`);
+ console.log(`Max output: ${info.maxOutput} tokens`);
+}
+```
+
+## isValidModel()
+
+Check if a model ID is valid:
+
+```typescript
+function isValidModel(modelId: string): modelId is ModelId
+```
+
+### Example
+
+```typescript
+const modelId = 'claude-sonnet-4-5-20250929';
+
+if (isValidModel(modelId)) {
+ // TypeScript knows modelId is ModelId
+ const droid = new Droid({ model: modelId });
+}
+```
+
+## Practical Examples
+
+### Model Comparison
+
+```typescript
+import { MODEL_INFO, MODELS } from '@activade/droid-sdk';
+
+// Compare context windows
+const sonnet = MODEL_INFO[MODELS.CLAUDE_SONNET];
+const haiku = MODEL_INFO[MODELS.CLAUDE_HAIKU];
+
+console.log(`Sonnet context: ${sonnet.contextWindow}`);
+console.log(`Haiku context: ${haiku.contextWindow}`);
+```
+
+### Selecting by Provider
+
+```typescript
+import { MODEL_INFO, ModelId } from '@activade/droid-sdk';
+
+function getAnthropicModels(): ModelId[] {
+ return Object.values(MODEL_INFO)
+ .filter(m => m.provider === 'anthropic')
+ .map(m => m.id);
+}
+```
+
+### Validation
+
+```typescript
+function createDroid(modelId: string) {
+ if (!isValidModel(modelId)) {
+ throw new Error(`Unknown model: ${modelId}`);
+ }
+
+ const info = getModelInfo(modelId)!;
+ console.log(`Using ${info.name}`);
+
+ return new Droid({ model: modelId });
+}
+```
diff --git a/docs/api-reference/models/overview.mdx b/docs/api-reference/models/overview.mdx
new file mode 100644
index 0000000..f30b4cb
--- /dev/null
+++ b/docs/api-reference/models/overview.mdx
@@ -0,0 +1,90 @@
+---
+title: Models Overview
+description: Available AI models and configuration
+---
+
+# Models
+
+The SDK supports multiple AI models from different providers.
+
+## Available Models
+
+```typescript
+import { MODELS } from '@activade/droid-sdk';
+```
+
+### Anthropic Claude
+
+| Constant | Model ID | Description |
+|----------|----------|-------------|
+| `MODELS.CLAUDE_OPUS` | `claude-opus-4-5-20251101` | Most capable, best for complex tasks |
+| `MODELS.CLAUDE_SONNET` | `claude-sonnet-4-5-20250929` | Balanced performance and speed |
+| `MODELS.CLAUDE_HAIKU` | `claude-haiku-4-5-20251001` | Fastest, best for simple tasks |
+
+### OpenAI GPT
+
+| Constant | Model ID | Description |
+|----------|----------|-------------|
+| `MODELS.GPT_5_1` | `gpt-5.1` | GPT-5.1 base model |
+| `MODELS.GPT_5_1_CODEX` | `gpt-5.1-codex` | Optimized for code |
+| `MODELS.GPT_5_2` | `gpt-5.2` | Latest GPT-5 variant |
+
+### Google Gemini
+
+| Constant | Model ID | Description |
+|----------|----------|-------------|
+| `MODELS.GEMINI_3_PRO` | `gemini-3-pro-preview` | Gemini 3 Pro preview |
+| `MODELS.GEMINI_3_FLASH` | `gemini-3-flash-preview` | Fast Gemini variant |
+
+### Open Source
+
+| Constant | Model ID | Description |
+|----------|----------|-------------|
+| `MODELS.DROID_CORE` | `glm-4.6` | Open source GLM model |
+
+## Usage
+
+```typescript
+import { Droid, MODELS } from '@activade/droid-sdk';
+
+// Using a constant
+const droid = new Droid({ model: MODELS.CLAUDE_SONNET });
+
+// Using a string
+const droid = new Droid({ model: 'claude-sonnet-4-5-20250929' });
+
+// Override per-thread
+const thread = droid.startThread({ model: MODELS.CLAUDE_OPUS });
+
+// Override per-run
+const result = await thread.run('Task', { model: MODELS.CLAUDE_HAIKU });
+```
+
+## Model Selection
+
+Choose based on your needs:
+
+| Use Case | Recommended Model |
+|----------|------------------|
+| Complex analysis | `CLAUDE_OPUS` |
+| General development | `CLAUDE_SONNET` |
+| Quick tasks | `CLAUDE_HAIKU` |
+| Code generation | `GPT_5_1_CODEX` |
+| Fast iteration | `GEMINI_3_FLASH` |
+
+## ModelId Type
+
+```typescript
+type ModelId =
+ | 'claude-opus-4-5-20251101'
+ | 'claude-sonnet-4-5-20250929'
+ | 'claude-haiku-4-5-20251001'
+ | 'gpt-5.1'
+ | 'gpt-5.1-codex'
+ | 'gpt-5.2'
+ | 'gemini-3-pro-preview'
+ | 'gemini-3-flash-preview'
+ | 'glm-4.6';
+```
+
+The `model` configuration accepts either a `ModelId` or any string for custom/new models.
diff --git a/docs/api-reference/overview.mdx b/docs/api-reference/overview.mdx
new file mode 100644
index 0000000..50c3b82
--- /dev/null
+++ b/docs/api-reference/overview.mdx
@@ -0,0 +1,126 @@
+---
+title: API Overview
+description: Complete reference for the Droid SDK API
+---
+
+# API Reference
+
+This section provides comprehensive documentation for all classes, types, and functions in the Droid SDK.
+
+## Core Classes
+
+
+
+ Main entry point for the SDK
+
+
+ Multi-turn conversation management
+
+
+ Execution result wrapper
+
+
+
+## Quick Reference
+
+### Creating a Droid Instance
+
+```typescript
+import { Droid, MODELS } from '@activade/droid-sdk';
+
+const droid = new Droid({
+ model: MODELS.CLAUDE_SONNET,
+ autonomyLevel: 'high',
+ reasoningEffort: 'medium',
+ cwd: '/path/to/project',
+ timeout: 600000
+});
+```
+
+### Configuration Options
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `model` | `ModelId \| string` | - | AI model to use |
+| `autonomyLevel` | `'default' \| 'low' \| 'medium' \| 'high'` | `'default'` | Autonomous decision level |
+| `reasoningEffort` | `'off' \| 'none' \| 'low' \| 'medium' \| 'high'` | - | Reasoning intensity |
+| `cwd` | `string` | `process.cwd()` | Working directory |
+| `timeout` | `number` | `600000` | Timeout in milliseconds |
+| `droidPath` | `string` | `'droid'` | Path to CLI binary |
+
+### Available Models
+
+```typescript
+import { MODELS } from '@activade/droid-sdk';
+
+// Anthropic
+MODELS.CLAUDE_OPUS // claude-opus-4-5-20251101
+MODELS.CLAUDE_SONNET // claude-sonnet-4-5-20250929
+MODELS.CLAUDE_HAIKU // claude-haiku-4-5-20251001
+
+// OpenAI
+MODELS.GPT_5_1 // gpt-5.1
+MODELS.GPT_5_1_CODEX // gpt-5.1-codex
+MODELS.GPT_5_2 // gpt-5.2
+
+// Google
+MODELS.GEMINI_3_PRO // gemini-3-pro-preview
+MODELS.GEMINI_3_FLASH // gemini-3-flash-preview
+
+// Open Source
+MODELS.DROID_CORE // glm-4.6
+```
+
+### Error Classes
+
+| Error | Description |
+|-------|-------------|
+| `DroidError` | Base class for all SDK errors |
+| `CliNotFoundError` | CLI binary not found |
+| `ExecutionError` | CLI execution failed |
+| `ParseError` | JSON parsing failed |
+| `TimeoutError` | Operation timed out |
+| `StreamError` | Stream reading failed |
+
+### Event Types
+
+| Event Type | Description |
+|------------|-------------|
+| `system` | System initialization |
+| `message` | User or assistant message |
+| `tool_call` | Tool invocation |
+| `tool_result` | Tool result |
+| `completion` | Turn completed |
+| `turn.failed` | Turn failed |
+
+## Import Patterns
+
+```typescript
+// Main SDK
+import { Droid, Thread, TurnResult, MODELS } from '@activade/droid-sdk';
+
+// Error classes
+import {
+ DroidError,
+ CliNotFoundError,
+ ExecutionError,
+ ParseError,
+ TimeoutError,
+ StreamError
+} from '@activade/droid-sdk';
+
+// Event types and guards
+import {
+ isToolCallEvent,
+ isToolResultEvent,
+ isMessageEvent,
+ type StreamEvent,
+ type ToolCallEvent
+} from '@activade/droid-sdk';
+
+// CLI utilities (separate entry point)
+import { ensureDroidCli, isDroidCliInstalled } from '@activade/droid-sdk/cli';
+
+// Model utilities
+import { getModelInfo, isValidModel, MODEL_INFO } from '@activade/droid-sdk';
+```
diff --git a/docs/api-reference/types/config.mdx b/docs/api-reference/types/config.mdx
new file mode 100644
index 0000000..01ed417
--- /dev/null
+++ b/docs/api-reference/types/config.mdx
@@ -0,0 +1,86 @@
+---
+title: Configuration Types
+description: SDK configuration interfaces and defaults
+---
+
+# Configuration Types
+
+Types for configuring Droid instances.
+
+## DroidConfig
+
+Main configuration interface for the Droid class.
+
+```typescript
+interface DroidConfig {
+ /** Working directory for CLI operations */
+ cwd?: string;
+
+ /** AI model to use */
+ model?: ModelId | string;
+
+ /** Level of autonomous decision-making */
+ autonomyLevel?: AutonomyLevel;
+
+ /** Reasoning intensity for the AI */
+ reasoningEffort?: ReasoningEffort;
+
+ /** Path to the Droid CLI binary */
+ droidPath?: string;
+
+ /** Maximum execution time in milliseconds */
+ timeout?: number;
+}
+```
+
+## Usage
+
+```typescript
+import { Droid, MODELS } from '@activade/droid-sdk';
+
+const config: DroidConfig = {
+ model: MODELS.CLAUDE_SONNET,
+ autonomyLevel: 'high',
+ reasoningEffort: 'medium',
+ cwd: '/path/to/project',
+ timeout: 300000,
+ droidPath: '/custom/bin/droid'
+};
+
+const droid = new Droid(config);
+```
+
+## Defaults
+
+| Property | Default Value |
+|----------|--------------|
+| `cwd` | `process.cwd()` |
+| `model` | (none) |
+| `autonomyLevel` | `'default'` |
+| `reasoningEffort` | (none) |
+| `droidPath` | `'droid'` |
+| `timeout` | `600000` (10 min) |
+
+## Constants
+
+```typescript
+/** Default execution timeout in milliseconds (10 minutes) */
+const DEFAULT_TIMEOUT = 600000;
+
+/** Default CLI binary name */
+const DEFAULT_DROID_PATH = 'droid';
+```
+
+## Config Immutability
+
+The config is frozen after construction:
+
+```typescript
+const droid = new Droid({ model: MODELS.CLAUDE_SONNET });
+
+// Read config
+console.log(droid.config.model); // 'claude-sonnet-4-5-20250929'
+
+// Config is readonly
+droid.config.model = 'other'; // TypeScript error
+```
diff --git a/docs/api-reference/types/events.mdx b/docs/api-reference/types/events.mdx
new file mode 100644
index 0000000..51f057b
--- /dev/null
+++ b/docs/api-reference/types/events.mdx
@@ -0,0 +1,179 @@
+---
+title: Event Types
+description: Streaming event types and type guards
+---
+
+# Event Types
+
+Types for streaming events emitted during execution.
+
+## StreamEvent
+
+Union type of all possible events:
+
+```typescript
+type StreamEvent =
+ | SystemInitEvent
+ | MessageEvent
+ | ToolCallEvent
+ | ToolResultEvent
+ | TurnCompletedEvent
+ | TurnFailedEvent;
+```
+
+## SystemInitEvent
+
+Emitted at the start of execution:
+
+```typescript
+interface SystemInitEvent {
+ type: 'system';
+ cwd: string;
+ session_id: string;
+ tools: string[];
+ model: string;
+}
+```
+
+## MessageEvent
+
+User or assistant messages:
+
+```typescript
+interface MessageEvent {
+ type: 'message';
+ role: 'user' | 'assistant';
+ id: string;
+ text: string;
+ timestamp: number;
+}
+```
+
+## ToolCallEvent
+
+Tool invocations:
+
+```typescript
+interface ToolCallEvent {
+ type: 'tool_call';
+ id: string;
+ messageId: string;
+ toolId: string;
+ toolName: string;
+ parameters: Record;
+ timestamp: number;
+}
+```
+
+## ToolResultEvent
+
+Tool execution results:
+
+```typescript
+interface ToolResultEvent {
+ type: 'tool_result';
+ id: string;
+ messageId: string;
+ toolId: string;
+ toolName: string;
+ isError: boolean;
+ value: string;
+ timestamp: number;
+}
+```
+
+## TurnCompletedEvent
+
+Successful completion:
+
+```typescript
+interface TurnCompletedEvent {
+ type: 'completion';
+ finalText: string;
+ durationMs: number;
+ numTurns: number;
+}
+```
+
+## TurnFailedEvent
+
+Execution failure:
+
+```typescript
+interface TurnFailedEvent {
+ type: 'turn.failed';
+ error: {
+ message: string;
+ code?: string;
+ };
+}
+```
+
+## Type Guards
+
+Import type guards for type-safe event handling:
+
+```typescript
+import {
+ isSystemInitEvent,
+ isMessageEvent,
+ isToolCallEvent,
+ isToolResultEvent,
+ isTurnCompletedEvent,
+ isTurnFailedEvent
+} from '@activade/droid-sdk';
+
+for await (const event of events) {
+ if (isToolCallEvent(event)) {
+ // TypeScript knows: event is ToolCallEvent
+ console.log(event.toolName, event.parameters);
+ }
+
+ if (isMessageEvent(event)) {
+ // TypeScript knows: event is MessageEvent
+ console.log(`[${event.role}] ${event.text}`);
+ }
+}
+```
+
+## Type Guard Functions
+
+```typescript
+function isSystemInitEvent(e: StreamEvent): e is SystemInitEvent;
+function isMessageEvent(e: StreamEvent): e is MessageEvent;
+function isToolCallEvent(e: StreamEvent): e is ToolCallEvent;
+function isToolResultEvent(e: StreamEvent): e is ToolResultEvent;
+function isTurnCompletedEvent(e: StreamEvent): e is TurnCompletedEvent;
+function isTurnFailedEvent(e: StreamEvent): e is TurnFailedEvent;
+```
+
+## Complete Example
+
+```typescript
+import {
+ isMessageEvent,
+ isToolCallEvent,
+ isToolResultEvent,
+ isTurnFailedEvent
+} from '@activade/droid-sdk';
+
+const { events, result } = await thread.runStreamed('Build feature');
+
+for await (const event of events) {
+ if (isMessageEvent(event) && event.role === 'assistant') {
+ console.log('AI:', event.text);
+ }
+
+ if (isToolCallEvent(event)) {
+ console.log(`Running: ${event.toolName}`);
+ }
+
+ if (isToolResultEvent(event)) {
+ console.log(` ${event.isError ? '✗' : '✓'} ${event.toolName}`);
+ }
+
+ if (isTurnFailedEvent(event)) {
+ console.error('Failed:', event.error.message);
+ }
+}
+```
diff --git a/docs/api-reference/types/options.mdx b/docs/api-reference/types/options.mdx
new file mode 100644
index 0000000..f9853d3
--- /dev/null
+++ b/docs/api-reference/types/options.mdx
@@ -0,0 +1,150 @@
+---
+title: Option Types
+description: Execution and thread option types
+---
+
+# Option Types
+
+Types for configuring thread and execution options.
+
+## AutonomyLevel
+
+Controls how independently the AI operates:
+
+```typescript
+type AutonomyLevel = 'default' | 'low' | 'medium' | 'high';
+```
+
+| Level | Behavior |
+|-------|----------|
+| `default` | Standard confirmation prompts |
+| `low` | Conservative, confirms most operations |
+| `medium` | Balanced autonomy |
+| `high` | Maximum autonomy, minimal confirmations |
+
+## ReasoningEffort
+
+Controls reasoning intensity:
+
+```typescript
+type ReasoningEffort = 'off' | 'none' | 'low' | 'medium' | 'high';
+```
+
+## OutputFormat
+
+Output format for CLI operations:
+
+```typescript
+type OutputFormat = 'text' | 'json' | 'stream-json';
+```
+
+## ThreadOptions
+
+Options for creating threads:
+
+```typescript
+interface ThreadOptions {
+ /** Override working directory */
+ cwd?: string;
+
+ /** Override autonomy level */
+ autonomyLevel?: AutonomyLevel;
+
+ /** Restrict available tools */
+ enabledTools?: string[];
+
+ /** Override model */
+ model?: string;
+}
+```
+
+## RunOptions
+
+Options for run/runStreamed:
+
+```typescript
+interface RunOptions {
+ /** JSON Schema for structured output */
+ outputSchema?: JsonSchema;
+
+ /** Path to a prompt file */
+ promptFile?: string;
+
+ /** File attachments */
+ attachments?: FileAttachment[];
+
+ /** Override model for this run */
+ model?: string;
+
+ /** Override autonomy level */
+ autonomyLevel?: AutonomyLevel;
+}
+```
+
+## ExecOptions
+
+Options for one-shot execution:
+
+```typescript
+interface ExecOptions extends RunOptions {
+ /** Override working directory */
+ cwd?: string;
+
+ /** Override timeout */
+ timeout?: number;
+}
+```
+
+## FileAttachment
+
+File attachment for prompts:
+
+```typescript
+interface FileAttachment {
+ /** Path to the file */
+ path: string;
+
+ /** Type of file */
+ type: 'image' | 'text' | 'data';
+
+ /** Optional description */
+ description?: string;
+}
+```
+
+## JsonSchema
+
+JSON Schema for structured output:
+
+```typescript
+interface JsonSchema {
+ type?: string;
+ properties?: Record;
+ items?: JsonSchema;
+ required?: string[];
+ enum?: unknown[];
+ description?: string;
+ [key: string]: unknown;
+}
+```
+
+## Usage Example
+
+```typescript
+const thread = droid.startThread({
+ autonomyLevel: 'high',
+ enabledTools: ['Read', 'Write']
+});
+
+const result = await thread.run('Analyze code', {
+ attachments: [
+ { path: './src/main.ts', type: 'text' }
+ ],
+ outputSchema: {
+ type: 'object',
+ properties: {
+ issues: { type: 'array' }
+ }
+ }
+});
+```
diff --git a/docs/api-reference/types/turn-items.mdx b/docs/api-reference/types/turn-items.mdx
new file mode 100644
index 0000000..0e76c71
--- /dev/null
+++ b/docs/api-reference/types/turn-items.mdx
@@ -0,0 +1,161 @@
+---
+title: Turn Item Types
+description: Types for turn results and items
+---
+
+# Turn Item Types
+
+Types representing items within a turn result.
+
+## AnyTurnItem
+
+Union of all item types:
+
+```typescript
+type AnyTurnItem = MessageItem | ToolCallItem | ToolResultItem;
+```
+
+## MessageItem
+
+Represents a message in the conversation:
+
+```typescript
+interface MessageItem {
+ type: 'message';
+ role: 'user' | 'assistant';
+ id: string;
+ text: string;
+ timestamp: number;
+}
+```
+
+## ToolCallItem
+
+Represents a tool invocation:
+
+```typescript
+interface ToolCallItem {
+ type: 'tool_call';
+ id: string;
+ messageId: string;
+ toolId: string;
+ toolName: string;
+ parameters: Record;
+ timestamp: number;
+}
+```
+
+## ToolResultItem
+
+Represents a tool execution result:
+
+```typescript
+interface ToolResultItem {
+ type: 'tool_result';
+ id: string;
+ messageId: string;
+ toolId: string;
+ toolName: string;
+ isError: boolean;
+ value: string;
+ timestamp: number;
+}
+```
+
+## TurnResultData
+
+Serializable turn result data:
+
+```typescript
+interface TurnResultData {
+ sessionId: string;
+ finalResponse: string;
+ items: AnyTurnItem[];
+ durationMs: number;
+ numTurns: number;
+ isError: boolean;
+}
+```
+
+## JsonResult
+
+Response from JSON-mode execution:
+
+```typescript
+interface JsonResult {
+ session_id: string;
+ is_error: boolean;
+ duration_ms: number;
+ num_turns: number;
+ result: string;
+}
+```
+
+## Usage Examples
+
+### Accessing Items
+
+```typescript
+const result = await thread.run('Create a file');
+
+// All items in order
+for (const item of result.items) {
+ console.log(item.type, item.timestamp);
+}
+
+// Filter by type
+const messages = result.messages;
+const toolCalls = result.toolCalls;
+const toolResults = result.toolResults;
+```
+
+### Correlating Tool Calls and Results
+
+```typescript
+for (const call of result.toolCalls) {
+ const callResult = result.toolResults.find(
+ r => r.toolId === call.toolId
+ );
+
+ console.log(`${call.toolName}:`, callResult?.isError ? 'FAILED' : 'OK');
+}
+```
+
+### Serializing Results
+
+```typescript
+const result = await thread.run('Build feature');
+
+// Convert to plain object
+const data = result.toJSON();
+
+// Save to file
+fs.writeFileSync('result.json', JSON.stringify(data, null, 2));
+
+// Structure matches TurnResultData
+console.log(data.sessionId);
+console.log(data.items.length);
+```
+
+### Type Narrowing
+
+```typescript
+for (const item of result.items) {
+ switch (item.type) {
+ case 'message':
+ // item is MessageItem
+ console.log(`[${item.role}] ${item.text}`);
+ break;
+
+ case 'tool_call':
+ // item is ToolCallItem
+ console.log(`Call: ${item.toolName}`);
+ break;
+
+ case 'tool_result':
+ // item is ToolResultItem
+ console.log(`Result: ${item.isError ? 'Error' : 'Success'}`);
+ break;
+ }
+}
+```
diff --git a/docs/concepts/droid.mdx b/docs/concepts/droid.mdx
new file mode 100644
index 0000000..a5e71c5
--- /dev/null
+++ b/docs/concepts/droid.mdx
@@ -0,0 +1,86 @@
+---
+title: The Droid Class
+description: Understanding the main SDK entry point
+---
+
+# The Droid Class
+
+The `Droid` class is your gateway to the Factory Droid CLI. It manages configuration, creates conversation threads, and executes AI-powered tasks.
+
+## Core Responsibilities
+
+The Droid class handles:
+
+- **Configuration Management** - Model selection, timeouts, autonomy levels
+- **Thread Creation** - Starting new or resuming existing conversations
+- **One-shot Execution** - Running single prompts without conversation state
+- **Tool Discovery** - Listing available AI tools
+
+## Creating an Instance
+
+```typescript
+import { Droid, MODELS } from '@activade/droid-sdk';
+
+const droid = new Droid({
+ model: MODELS.CLAUDE_SONNET,
+ autonomyLevel: 'high',
+ cwd: './my-project',
+ timeout: 300000
+});
+```
+
+## Configuration Options
+
+| Option | Type | Default | Description |
+|--------|------|---------|-------------|
+| `model` | `ModelId \| string` | - | AI model to use |
+| `autonomyLevel` | `AutonomyLevel` | `'default'` | Level of autonomous decision-making |
+| `reasoningEffort` | `ReasoningEffort` | - | Reasoning intensity |
+| `cwd` | `string` | `process.cwd()` | Working directory for CLI operations |
+| `timeout` | `number` | `600000` | Maximum execution time in ms |
+| `droidPath` | `string` | `'droid'` | Path to CLI binary |
+
+## Autonomy Levels
+
+The autonomy level controls how independently the AI makes decisions:
+
+- **`default`** - Standard behavior, asks for confirmation on risky operations
+- **`low`** - More conservative, confirms most file operations
+- **`medium`** - Balanced autonomy for routine tasks
+- **`high`** - Maximum autonomy, executes most operations without confirmation
+
+```typescript
+// High autonomy for automated workflows
+const automatedDroid = new Droid({ autonomyLevel: 'high' });
+
+// Low autonomy for careful review
+const reviewDroid = new Droid({ autonomyLevel: 'low' });
+```
+
+## Execution Patterns
+
+### One-shot Execution
+
+Use `exec()` for stateless, single-prompt operations:
+
+```typescript
+const result = await droid.exec('Generate a random UUID');
+console.log(result.finalResponse);
+```
+
+### Threaded Conversations
+
+Use threads for multi-turn conversations with context:
+
+```typescript
+const thread = droid.startThread();
+await thread.run('Create a REST API');
+await thread.run('Add authentication'); // Has context from previous turn
+```
+
+## Best Practices
+
+1. **Reuse Droid instances** - Create once, use for multiple operations
+2. **Match autonomy to use case** - Higher for automation, lower for interactive
+3. **Set appropriate timeouts** - Longer for complex tasks
+4. **Use structured output** - For programmatic response handling
diff --git a/docs/concepts/streaming.mdx b/docs/concepts/streaming.mdx
new file mode 100644
index 0000000..4a60123
--- /dev/null
+++ b/docs/concepts/streaming.mdx
@@ -0,0 +1,210 @@
+---
+title: Streaming Events
+description: Real-time event handling during AI execution
+---
+
+# Streaming Events
+
+Streaming provides real-time visibility into AI execution, allowing you to display progress, handle tool calls, and respond to events as they occur.
+
+## Basic Streaming
+
+```typescript
+const { events, result } = await thread.runStreamed('Create a web server');
+
+for await (const event of events) {
+ console.log(event.type, event);
+}
+
+const finalResult = await result;
+```
+
+## Event Types
+
+### System Event
+
+Emitted at the start with session metadata:
+
+```typescript
+{
+ type: 'system',
+ cwd: '/path/to/project',
+ session_id: 'session_abc123',
+ tools: ['Read', 'Write', 'Bash', ...],
+ model: 'claude-sonnet-4-5-20250929'
+}
+```
+
+### Message Event
+
+User or assistant text messages:
+
+```typescript
+{
+ type: 'message',
+ role: 'assistant',
+ id: 'msg_123',
+ text: 'I will create the server...',
+ timestamp: 1704067200000
+}
+```
+
+### Tool Call Event
+
+When the AI invokes a tool:
+
+```typescript
+{
+ type: 'tool_call',
+ id: 'tc_456',
+ messageId: 'msg_123',
+ toolId: 'tool_789',
+ toolName: 'Write',
+ parameters: {
+ path: 'server.ts',
+ content: '...'
+ },
+ timestamp: 1704067201000
+}
+```
+
+### Tool Result Event
+
+Tool execution results:
+
+```typescript
+{
+ type: 'tool_result',
+ id: 'tr_012',
+ messageId: 'msg_123',
+ toolId: 'tool_789',
+ toolName: 'Write',
+ isError: false,
+ value: 'File written successfully',
+ timestamp: 1704067202000
+}
+```
+
+### Completion Event
+
+Execution completed successfully:
+
+```typescript
+{
+ type: 'completion',
+ finalText: 'Server created at server.ts',
+ durationMs: 5432,
+ numTurns: 1
+}
+```
+
+### Turn Failed Event
+
+Execution failed:
+
+```typescript
+{
+ type: 'turn.failed',
+ error: {
+ message: 'Command timed out',
+ code: 'TIMEOUT'
+ }
+}
+```
+
+## Type Guards
+
+Use type guards for type-safe event handling:
+
+```typescript
+import {
+ isMessageEvent,
+ isToolCallEvent,
+ isToolResultEvent,
+ isTurnCompletedEvent,
+ isTurnFailedEvent
+} from '@activade/droid-sdk';
+
+for await (const event of events) {
+ if (isMessageEvent(event)) {
+ // TypeScript knows: event.role, event.text
+ console.log(`[${event.role}] ${event.text}`);
+ }
+
+ if (isToolCallEvent(event)) {
+ // TypeScript knows: event.toolName, event.parameters
+ console.log(`Calling ${event.toolName}`);
+ }
+
+ if (isToolResultEvent(event)) {
+ // TypeScript knows: event.isError, event.value
+ if (event.isError) {
+ console.error(`Tool failed: ${event.value}`);
+ }
+ }
+}
+```
+
+## Building a Progress UI
+
+```typescript
+const { events, result } = await thread.runStreamed(prompt);
+
+for await (const event of events) {
+ switch (event.type) {
+ case 'message':
+ if (event.role === 'assistant') {
+ updateUI({ message: event.text });
+ }
+ break;
+
+ case 'tool_call':
+ updateUI({
+ status: 'working',
+ tool: event.toolName,
+ action: `Running ${event.toolName}...`
+ });
+ break;
+
+ case 'tool_result':
+ updateUI({
+ status: event.isError ? 'error' : 'success',
+ result: event.value
+ });
+ break;
+
+ case 'completion':
+ updateUI({
+ status: 'complete',
+ duration: `${event.durationMs}ms`
+ });
+ break;
+
+ case 'turn.failed':
+ updateUI({
+ status: 'failed',
+ error: event.error.message
+ });
+ break;
+ }
+}
+```
+
+## StreamedTurn Structure
+
+```typescript
+interface StreamedTurn {
+ events: AsyncIterable;
+ result: Promise;
+}
+```
+
+- **events** - Async iterator of events as they occur
+- **result** - Promise that resolves to the final TurnResult
+
+## Best Practices
+
+1. **Always await result** - Even if you only need events
+2. **Handle all event types** - Especially `turn.failed`
+3. **Use type guards** - For type-safe event handling
+4. **Stream for long tasks** - Better UX than waiting
diff --git a/docs/concepts/structured-output.mdx b/docs/concepts/structured-output.mdx
new file mode 100644
index 0000000..a5090e9
--- /dev/null
+++ b/docs/concepts/structured-output.mdx
@@ -0,0 +1,181 @@
+---
+title: Structured Output
+description: Parse AI responses into typed data structures
+---
+
+# Structured Output
+
+The SDK supports parsing AI responses into strongly-typed data structures using Zod schemas.
+
+## Basic Usage
+
+```typescript
+import { Droid } from '@activade/droid-sdk';
+import { z } from 'zod';
+
+const droid = new Droid();
+
+const UserSchema = z.object({
+ id: z.number(),
+ name: z.string(),
+ email: z.string().email()
+});
+
+const result = await droid.exec('Generate a user object as JSON');
+const user = result.parse(UserSchema);
+
+// TypeScript knows the type:
+console.log(user.id); // number
+console.log(user.name); // string
+console.log(user.email); // string
+```
+
+## Parse Methods
+
+### parse() - Strict Parsing
+
+Throws on invalid data:
+
+```typescript
+const ConfigSchema = z.object({
+ port: z.number(),
+ host: z.string()
+});
+
+try {
+ const config = result.parse(ConfigSchema);
+ console.log(`Server: ${config.host}:${config.port}`);
+} catch (error) {
+ if (error instanceof ParseError) {
+ console.error('Invalid JSON:', error.message);
+ }
+ // Schema validation errors are thrown as-is
+}
+```
+
+### tryParse() - Safe Parsing
+
+Returns `null` on failure:
+
+```typescript
+const config = result.tryParse(ConfigSchema);
+
+if (config) {
+ console.log('Config:', config);
+} else {
+ console.log('Response was not valid config JSON');
+}
+```
+
+## Complex Schemas
+
+### Nested Objects
+
+```typescript
+const ProjectSchema = z.object({
+ name: z.string(),
+ version: z.string(),
+ dependencies: z.record(z.string()),
+ scripts: z.object({
+ build: z.string().optional(),
+ test: z.string().optional()
+ })
+});
+
+const result = await droid.exec('Analyze package.json and return as JSON');
+const project = result.parse(ProjectSchema);
+```
+
+### Arrays
+
+```typescript
+const TodoSchema = z.object({
+ id: z.number(),
+ title: z.string(),
+ completed: z.boolean()
+});
+
+const TodoListSchema = z.array(TodoSchema);
+
+const result = await droid.exec('Generate 5 todo items as JSON array');
+const todos = result.parse(TodoListSchema);
+```
+
+### Unions and Optionals
+
+```typescript
+const ResponseSchema = z.object({
+ status: z.enum(['success', 'error']),
+ data: z.union([
+ z.object({ users: z.array(z.string()) }),
+ z.object({ error: z.string() })
+ ]).optional()
+});
+```
+
+## JSON Schema Output
+
+For direct schema specification in prompts:
+
+```typescript
+const result = await thread.run('Generate user data', {
+ outputSchema: {
+ type: 'object',
+ properties: {
+ id: { type: 'number' },
+ name: { type: 'string' }
+ },
+ required: ['id', 'name']
+ }
+});
+```
+
+## Error Handling
+
+```typescript
+import { ParseError } from '@activade/droid-sdk';
+import { z } from 'zod';
+
+try {
+ const data = result.parse(MySchema);
+} catch (error) {
+ if (error instanceof ParseError) {
+ // Response was not valid JSON
+ console.error('JSON parse failed:', error.message);
+ console.error('Raw response:', error.rawText);
+ } else if (error instanceof z.ZodError) {
+ // JSON was valid but didn't match schema
+ console.error('Validation failed:', error.issues);
+ }
+}
+```
+
+## Prompting for JSON
+
+For best results, be explicit about JSON format:
+
+```typescript
+// Good - explicit JSON request
+const result = await droid.exec(
+ 'Analyze the codebase and return a JSON object with: ' +
+ '{ files: number, lines: number, languages: string[] }'
+);
+
+// Better - with example
+const result = await droid.exec(`
+ Analyze the codebase and return JSON like:
+ {
+ "files": 42,
+ "lines": 1234,
+ "languages": ["TypeScript", "JavaScript"]
+ }
+`);
+```
+
+## Best Practices
+
+1. **Use tryParse for optional data** - When JSON might not be present
+2. **Use parse for required data** - When you need guaranteed structure
+3. **Define precise schemas** - Be specific about types and constraints
+4. **Handle both error types** - ParseError and ZodError
+5. **Prompt clearly** - Ask explicitly for JSON with structure hints
diff --git a/docs/concepts/threads.mdx b/docs/concepts/threads.mdx
new file mode 100644
index 0000000..1f266ef
--- /dev/null
+++ b/docs/concepts/threads.mdx
@@ -0,0 +1,123 @@
+---
+title: Conversation Threads
+description: Managing multi-turn AI conversations
+---
+
+# Conversation Threads
+
+Threads maintain context across multiple prompts, enabling natural multi-turn conversations with the AI.
+
+## Creating Threads
+
+Threads are created from a Droid instance:
+
+```typescript
+import { Droid } from '@activade/droid-sdk';
+
+const droid = new Droid();
+const thread = droid.startThread();
+```
+
+## Thread Lifecycle
+
+```
+┌─────────────┐ ┌─────────────┐ ┌─────────────┐
+│ Create │────▶│ Execute │────▶│ Resume │
+│ (new) │ │ (run/ │ │ (later) │
+│ │ │ stream) │ │ │
+└─────────────┘ └─────────────┘ └─────────────┘
+ │ │ │
+ │ ▼ │
+ │ ┌─────────────┐ │
+ │ │ Session │◀────────────┘
+ └──────────▶│ ID │
+ └─────────────┘
+```
+
+## Multi-turn Conversations
+
+Each prompt in a thread builds on previous context:
+
+```typescript
+const thread = droid.startThread();
+
+// Turn 1: Create initial code
+await thread.run('Create a UserService class with CRUD methods');
+
+// Turn 2: AI remembers the UserService
+await thread.run('Add validation to the create method');
+
+// Turn 3: AI has full context
+await thread.run('Write unit tests for the validation');
+```
+
+## Session Persistence
+
+Threads can be resumed across process restarts:
+
+```typescript
+// First session
+const thread = droid.startThread();
+await thread.run('Start building a payment system');
+const sessionId = thread.id;
+// Save sessionId to database/file
+
+// Later session
+const resumedThread = droid.resumeThread(sessionId);
+await resumedThread.run('Continue with refund handling');
+```
+
+## Thread Options
+
+Override Droid configuration per-thread:
+
+```typescript
+const thread = droid.startThread({
+ autonomyLevel: 'high', // Override autonomy
+ enabledTools: ['Read', 'Write', 'Bash'], // Limit tools
+ cwd: '/specific/project' // Different working directory
+});
+```
+
+## Execution Methods
+
+### Synchronous Execution
+
+Wait for the complete response:
+
+```typescript
+const result = await thread.run('Create a config file');
+console.log(result.finalResponse);
+```
+
+### Streaming Execution
+
+Process events in real-time:
+
+```typescript
+const { events, result } = await thread.runStreamed('Build a complex feature');
+
+for await (const event of events) {
+ if (event.type === 'tool_call') {
+ console.log(`Using: ${event.toolName}`);
+ }
+}
+
+const finalResult = await result;
+```
+
+## Thread vs Exec
+
+| Feature | Thread | Exec |
+|---------|--------|------|
+| Context | Maintained | None |
+| Session ID | Yes | Per-call |
+| Resume | Yes | No |
+| Use case | Multi-turn | One-shot |
+
+## Best Practices
+
+1. **Use threads for related tasks** - Keep context across related operations
+2. **Save session IDs** - Enable resumption for long-running projects
+3. **Use exec for isolated tasks** - When context isn't needed
+4. **Stream for long operations** - Better UX with progress updates
diff --git a/docs/guides/ci-cd.mdx b/docs/guides/ci-cd.mdx
new file mode 100644
index 0000000..2ce515c
--- /dev/null
+++ b/docs/guides/ci-cd.mdx
@@ -0,0 +1,223 @@
+---
+title: CI/CD Integration
+description: Using the SDK in automated pipelines
+---
+
+# CI/CD Integration
+
+The Droid SDK is designed for integration into CI/CD pipelines, enabling AI-powered automation in your development workflow.
+
+## Setup
+
+### Installing in CI
+
+```yaml
+# GitHub Actions
+- name: Install Droid CLI
+ run: curl -fsSL https://app.factory.ai/cli | sh
+
+- name: Install dependencies
+ run: npm install
+```
+
+Or use the SDK's auto-installer:
+
+```typescript
+import { ensureDroidCli } from '@activade/droid-sdk/cli';
+
+// Automatically install if not present
+await ensureDroidCli({
+ onProgress: (p) => console.log(`[${p.phase}] ${p.message}`)
+});
+```
+
+### Environment Configuration
+
+```yaml
+env:
+ # Set any required API keys
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+```
+
+## Common Use Cases
+
+### Automated Code Review
+
+```typescript
+import { Droid, MODELS } from '@activade/droid-sdk';
+import { execSync } from 'child_process';
+
+async function reviewPR() {
+ const droid = new Droid({
+ model: MODELS.CLAUDE_SONNET,
+ autonomyLevel: 'low' // Read-only operations
+ });
+
+ // Get changed files
+ const diff = execSync('git diff origin/main').toString();
+
+ const result = await droid.exec(`
+ Review this code diff for:
+ - Bugs and potential issues
+ - Security vulnerabilities
+ - Code style violations
+
+ Diff:
+ ${diff}
+ `);
+
+ return result.finalResponse;
+}
+```
+
+### Automated Documentation
+
+```typescript
+async function generateDocs() {
+ const droid = new Droid({ autonomyLevel: 'high' });
+ const thread = droid.startThread();
+
+ await thread.run('Analyze the src/ directory and update README.md');
+ await thread.run('Generate API documentation for public exports');
+
+ // Commit changes
+ execSync('git add README.md docs/');
+ execSync('git commit -m "docs: auto-update documentation"');
+}
+```
+
+### Test Generation
+
+```typescript
+async function generateTests(changedFiles: string[]) {
+ const droid = new Droid({ autonomyLevel: 'high' });
+
+ for (const file of changedFiles) {
+ if (!file.endsWith('.test.ts')) {
+ await droid.exec(`Generate unit tests for ${file}`);
+ }
+ }
+}
+```
+
+## GitHub Actions Example
+
+```yaml
+name: AI Code Review
+
+on:
+ pull_request:
+ branches: [main]
+
+jobs:
+ review:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Install Droid CLI
+ run: curl -fsSL https://app.factory.ai/cli | sh
+
+ - name: Install dependencies
+ run: npm install
+
+ - name: Run AI Review
+ run: npx tsx scripts/ai-review.ts
+ env:
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+
+ - name: Comment on PR
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const fs = require('fs');
+ const review = fs.readFileSync('review.md', 'utf8');
+ github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ body: review
+ });
+```
+
+## Best Practices
+
+### 1. Use Appropriate Autonomy
+
+```typescript
+// CI environments should typically use lower autonomy
+const droid = new Droid({
+ autonomyLevel: 'low', // Safer for automated runs
+ timeout: 300000 // 5 minute timeout
+});
+```
+
+### 2. Handle Errors Gracefully
+
+```typescript
+async function ciTask() {
+ try {
+ const result = await droid.exec('Analyze codebase');
+ return { success: true, output: result.finalResponse };
+ } catch (error) {
+ console.error('AI task failed:', error);
+ // Don't fail the whole pipeline for AI errors
+ return { success: false, error: error.message };
+ }
+}
+```
+
+### 3. Set Timeouts
+
+```typescript
+const droid = new Droid({
+ timeout: 180000 // 3 minutes - reasonable for CI
+});
+```
+
+### 4. Cache CLI Installation
+
+```yaml
+- name: Cache Droid CLI
+ uses: actions/cache@v4
+ with:
+ path: ~/.local/bin/droid
+ key: droid-cli-${{ runner.os }}
+```
+
+### 5. Use Structured Output
+
+```typescript
+import { z } from 'zod';
+
+const ReviewSchema = z.object({
+ issues: z.array(z.object({
+ severity: z.enum(['error', 'warning', 'info']),
+ file: z.string(),
+ line: z.number().optional(),
+ message: z.string()
+ })),
+ summary: z.string()
+});
+
+const result = await droid.exec('Review code and return JSON');
+const review = result.tryParse(ReviewSchema);
+
+if (review && review.issues.some(i => i.severity === 'error')) {
+ process.exit(1); // Fail pipeline on errors
+}
+```
+
+## Security Considerations
+
+1. **Store API keys as secrets** - Never commit credentials
+2. **Use read-only mode** - Lower autonomy prevents unwanted changes
+3. **Validate outputs** - Don't blindly trust AI-generated code
+4. **Limit scope** - Restrict working directory and available tools
+5. **Review before merge** - AI suggestions should be reviewed
diff --git a/docs/guides/error-handling.mdx b/docs/guides/error-handling.mdx
new file mode 100644
index 0000000..c753577
--- /dev/null
+++ b/docs/guides/error-handling.mdx
@@ -0,0 +1,245 @@
+---
+title: Error Handling
+description: Gracefully handling SDK errors
+---
+
+# Error Handling
+
+The Droid SDK provides specific error classes for different failure scenarios, enabling precise error handling in your applications.
+
+## Error Hierarchy
+
+```
+DroidError (base)
+├── CliNotFoundError
+├── ExecutionError
+├── ParseError
+├── TimeoutError
+└── StreamError
+```
+
+## Error Types
+
+### CliNotFoundError
+
+Thrown when the Droid CLI is not installed:
+
+```typescript
+import { CliNotFoundError, ensureDroidCli } from '@activade/droid-sdk';
+
+try {
+ const result = await droid.exec('Hello');
+} catch (error) {
+ if (error instanceof CliNotFoundError) {
+ console.log('CLI not found, installing...');
+ await ensureDroidCli();
+ }
+}
+```
+
+### ExecutionError
+
+Thrown when CLI execution fails:
+
+```typescript
+import { ExecutionError } from '@activade/droid-sdk';
+
+try {
+ const result = await droid.exec('Perform risky operation');
+} catch (error) {
+ if (error instanceof ExecutionError) {
+ console.error('Execution failed:', error.message);
+ console.error('Exit code:', error.exitCode);
+ console.error('Stderr:', error.stderr);
+ }
+}
+```
+
+### ParseError
+
+Thrown when JSON parsing fails:
+
+```typescript
+import { ParseError } from '@activade/droid-sdk';
+
+try {
+ const data = result.parse(MySchema);
+} catch (error) {
+ if (error instanceof ParseError) {
+ console.error('Not valid JSON:', error.message);
+ console.error('Raw text:', error.rawText);
+ }
+}
+```
+
+### TimeoutError
+
+Thrown when operations exceed the timeout:
+
+```typescript
+import { TimeoutError } from '@activade/droid-sdk';
+
+try {
+ const result = await droid.exec('Complex operation');
+} catch (error) {
+ if (error instanceof TimeoutError) {
+ console.error(`Operation timed out after ${error.timeoutMs}ms`);
+ }
+}
+```
+
+### StreamError
+
+Thrown when stream processing fails:
+
+```typescript
+import { StreamError } from '@activade/droid-sdk';
+
+try {
+ const { events } = await thread.runStreamed('Build feature');
+ for await (const event of events) {
+ // Process events
+ }
+} catch (error) {
+ if (error instanceof StreamError) {
+ console.error('Stream error:', error.message);
+ }
+}
+```
+
+## Comprehensive Error Handling
+
+```typescript
+import {
+ Droid,
+ DroidError,
+ CliNotFoundError,
+ ExecutionError,
+ TimeoutError,
+ ParseError,
+ StreamError,
+ ensureDroidCli
+} from '@activade/droid-sdk';
+
+async function safeExecute(prompt: string) {
+ const droid = new Droid({ timeout: 60000 });
+
+ try {
+ return await droid.exec(prompt);
+ } catch (error) {
+ if (error instanceof CliNotFoundError) {
+ console.log('Installing CLI...');
+ await ensureDroidCli();
+ // Retry
+ return await droid.exec(prompt);
+ }
+
+ if (error instanceof TimeoutError) {
+ console.error('Operation timed out. Try with higher timeout.');
+ throw error;
+ }
+
+ if (error instanceof ExecutionError) {
+ console.error('Execution failed:', error.message);
+ throw error;
+ }
+
+ if (error instanceof DroidError) {
+ // Catch-all for other SDK errors
+ console.error('SDK error:', error.message);
+ throw error;
+ }
+
+ // Unknown error
+ throw error;
+ }
+}
+```
+
+## Retry Patterns
+
+### Simple Retry
+
+```typescript
+async function withRetry(
+ fn: () => Promise,
+ maxRetries = 3
+): Promise {
+ let lastError: Error | undefined;
+
+ for (let i = 0; i < maxRetries; i++) {
+ try {
+ return await fn();
+ } catch (error) {
+ lastError = error as Error;
+
+ // Don't retry certain errors
+ if (error instanceof CliNotFoundError) throw error;
+ if (error instanceof ParseError) throw error;
+
+ // Wait before retry
+ await new Promise(r => setTimeout(r, 1000 * (i + 1)));
+ }
+ }
+
+ throw lastError;
+}
+
+// Usage
+const result = await withRetry(() => droid.exec('Generate code'));
+```
+
+### Exponential Backoff
+
+```typescript
+async function withExponentialBackoff(
+ fn: () => Promise,
+ options = { maxRetries: 3, baseDelay: 1000 }
+): Promise {
+ for (let i = 0; i < options.maxRetries; i++) {
+ try {
+ return await fn();
+ } catch (error) {
+ if (i === options.maxRetries - 1) throw error;
+
+ const delay = options.baseDelay * Math.pow(2, i);
+ await new Promise(r => setTimeout(r, delay));
+ }
+ }
+ throw new Error('Unreachable');
+}
+```
+
+## Streaming Error Handling
+
+```typescript
+async function safeStream(thread: Thread, prompt: string) {
+ try {
+ const { events, result } = await thread.runStreamed(prompt);
+
+ for await (const event of events) {
+ if (event.type === 'turn.failed') {
+ console.error('Turn failed:', event.error.message);
+ // Handle inline error
+ break;
+ }
+ // Process other events
+ }
+
+ return await result;
+ } catch (error) {
+ if (error instanceof StreamError) {
+ console.error('Stream interrupted:', error.message);
+ }
+ throw error;
+ }
+}
+```
+
+## Best Practices
+
+1. **Catch specific errors** - Handle each error type appropriately
+2. **Use the error hierarchy** - Catch `DroidError` as fallback
+3. **Implement retries** - For transient failures
+4. **Log error details** - Use error properties for debugging
+5. **Handle stream errors inline** - Check for `turn.failed` events
diff --git a/docs/guides/file-attachments.mdx b/docs/guides/file-attachments.mdx
new file mode 100644
index 0000000..9bf0a4b
--- /dev/null
+++ b/docs/guides/file-attachments.mdx
@@ -0,0 +1,178 @@
+---
+title: File Attachments
+description: Providing context through file attachments
+---
+
+# File Attachments
+
+File attachments allow you to provide additional context to the AI by including files with your prompts.
+
+## Attachment Types
+
+The SDK supports several attachment types:
+
+| Type | Description | Use Cases |
+|------|-------------|-----------|
+| `image` | Image files | Screenshots, diagrams, UI mockups |
+| `text` | Text files | Source code, configs, logs |
+| `data` | Data files | JSON, CSV, structured data |
+
+## Basic Usage
+
+```typescript
+const result = await thread.run('Describe this image', {
+ attachments: [
+ { path: './screenshot.png', type: 'image' }
+ ]
+});
+```
+
+## Image Attachments
+
+Attach images for visual analysis:
+
+```typescript
+// Single image
+const result = await thread.run('What does this UI show?', {
+ attachments: [
+ { path: './mockup.png', type: 'image' }
+ ]
+});
+
+// Multiple images
+const result = await thread.run('Compare these two designs', {
+ attachments: [
+ { path: './design-v1.png', type: 'image' },
+ { path: './design-v2.png', type: 'image' }
+ ]
+});
+```
+
+## Text Attachments
+
+Attach text files for code review or analysis:
+
+```typescript
+// Code review
+const result = await thread.run('Review this code for security issues', {
+ attachments: [
+ { path: './src/auth.ts', type: 'text', description: 'Authentication module' }
+ ]
+});
+
+// Multiple files with context
+const result = await thread.run('How do these modules interact?', {
+ attachments: [
+ { path: './src/user.ts', type: 'text', description: 'User model' },
+ { path: './src/auth.ts', type: 'text', description: 'Auth service' },
+ { path: './src/api.ts', type: 'text', description: 'API routes' }
+ ]
+});
+```
+
+## Data Attachments
+
+Attach structured data files:
+
+```typescript
+// JSON data
+const result = await thread.run('Analyze this API response', {
+ attachments: [
+ { path: './response.json', type: 'data' }
+ ]
+});
+
+// Configuration analysis
+const result = await thread.run('Review our build configuration', {
+ attachments: [
+ { path: './tsconfig.json', type: 'data' },
+ { path: './package.json', type: 'data' }
+ ]
+});
+```
+
+## File Attachment Interface
+
+```typescript
+interface FileAttachment {
+ /** Path to the file */
+ path: string;
+
+ /** Type of file content */
+ type: 'image' | 'text' | 'data';
+
+ /** Optional description for context */
+ description?: string;
+}
+```
+
+## Practical Examples
+
+### Screenshot Analysis
+
+```typescript
+async function analyzeScreenshot(screenshotPath: string) {
+ const droid = new Droid();
+ const thread = droid.startThread();
+
+ const result = await thread.run(
+ 'Analyze this screenshot and identify any UI issues or improvements',
+ {
+ attachments: [
+ { path: screenshotPath, type: 'image' }
+ ]
+ }
+ );
+
+ return result.finalResponse;
+}
+```
+
+### Code Review Workflow
+
+```typescript
+async function reviewChanges(files: string[]) {
+ const droid = new Droid();
+ const thread = droid.startThread();
+
+ const attachments = files.map(file => ({
+ path: file,
+ type: 'text' as const,
+ description: `File: ${file}`
+ }));
+
+ const result = await thread.run(
+ 'Review these files for bugs, security issues, and code quality',
+ { attachments }
+ );
+
+ return result.finalResponse;
+}
+```
+
+### Error Log Analysis
+
+```typescript
+async function analyzeErrors(logPath: string) {
+ const droid = new Droid();
+
+ const result = await droid.exec(
+ 'Analyze this log file and summarize the errors',
+ {
+ attachments: [
+ { path: logPath, type: 'text', description: 'Application error log' }
+ ]
+ }
+ );
+
+ return result.finalResponse;
+}
+```
+
+## Best Practices
+
+1. **Use descriptions** - Help the AI understand file purpose
+2. **Limit file size** - Large files may be truncated
+3. **Choose correct type** - Affects how content is processed
+4. **Group related files** - Attach files that work together
+5. **Be specific in prompts** - Reference attachments in your prompt
diff --git a/docs/guides/multi-turn.mdx b/docs/guides/multi-turn.mdx
new file mode 100644
index 0000000..66004ab
--- /dev/null
+++ b/docs/guides/multi-turn.mdx
@@ -0,0 +1,173 @@
+---
+title: Multi-turn Conversations
+description: Building complex interactions across multiple prompts
+---
+
+# Multi-turn Conversations
+
+Multi-turn conversations allow you to build complex features incrementally, with the AI maintaining context from previous interactions.
+
+## Basic Multi-turn Flow
+
+```typescript
+import { Droid, MODELS } from '@activade/droid-sdk';
+
+const droid = new Droid({ model: MODELS.CLAUDE_SONNET });
+const thread = droid.startThread();
+
+// Turn 1: Set up the project
+await thread.run('Create a new Express.js REST API project structure');
+
+// Turn 2: Add specific functionality (AI remembers the project)
+await thread.run('Add a /users endpoint with CRUD operations');
+
+// Turn 3: Continue building (AI has full context)
+await thread.run('Add JWT authentication middleware');
+
+// Turn 4: Finish up
+await thread.run('Add unit tests for the authentication');
+```
+
+## Saving and Resuming Sessions
+
+Sessions persist beyond process restarts:
+
+```typescript
+// Initial session
+async function startProject() {
+ const droid = new Droid();
+ const thread = droid.startThread();
+
+ await thread.run('Initialize a React TypeScript project');
+ await thread.run('Create a basic component structure');
+
+ // Save the session ID
+ return thread.id;
+}
+
+// Resume later
+async function continueProject(sessionId: string) {
+ const droid = new Droid();
+ const thread = droid.resumeThread(sessionId);
+
+ // AI remembers the React project
+ await thread.run('Add routing with React Router');
+ await thread.run('Create a navigation component');
+}
+```
+
+## Building Features Incrementally
+
+### Step-by-step Feature Development
+
+```typescript
+const thread = droid.startThread();
+
+// Step 1: Design
+const design = await thread.run(
+ 'Design a user authentication system. List the components needed.'
+);
+console.log('Design:', design.finalResponse);
+
+// Step 2: Data models
+await thread.run('Create the User and Session data models');
+
+// Step 3: Core logic
+await thread.run('Implement the authentication service');
+
+// Step 4: API layer
+await thread.run('Create login and logout API endpoints');
+
+// Step 5: Middleware
+await thread.run('Add authentication middleware');
+
+// Step 6: Tests
+await thread.run('Write tests for the authentication flow');
+```
+
+### Iterative Refinement
+
+```typescript
+const thread = droid.startThread();
+
+// Initial implementation
+await thread.run('Create a function to validate email addresses');
+
+// Refine based on requirements
+await thread.run('Add support for custom TLDs');
+
+// Handle edge cases
+await thread.run('Handle emails with + signs and dots');
+
+// Optimize
+await thread.run('Optimize for performance with large lists');
+```
+
+## Managing Long Sessions
+
+### Checking Session State
+
+```typescript
+const thread = droid.startThread();
+await thread.run('Start working on feature X');
+
+// Thread ID is available after first run
+console.log('Session:', thread.id);
+
+// Check what's been done
+const status = await thread.run('Summarize what we have done so far');
+console.log(status.finalResponse);
+```
+
+### Session Storage Patterns
+
+```typescript
+// Store in a database
+interface ProjectSession {
+ projectId: string;
+ sessionId: string;
+ createdAt: Date;
+ lastActivity: Date;
+}
+
+async function saveSession(projectId: string, sessionId: string) {
+ await db.sessions.upsert({
+ projectId,
+ sessionId,
+ lastActivity: new Date()
+ });
+}
+
+async function getSession(projectId: string): Promise {
+ const session = await db.sessions.findOne({ projectId });
+ return session?.sessionId ?? null;
+}
+```
+
+## Parallel Thread Execution
+
+Run multiple threads concurrently:
+
+```typescript
+const droid = new Droid();
+
+// Create multiple threads
+const featureThread = droid.startThread();
+const testThread = droid.startThread();
+const docsThread = droid.startThread();
+
+// Run in parallel
+const [feature, tests, docs] = await Promise.all([
+ featureThread.run('Implement user profile feature'),
+ testThread.run('Write test suite for API endpoints'),
+ docsThread.run('Generate API documentation')
+]);
+```
+
+## Best Practices
+
+1. **Be specific** - Clear prompts lead to better context retention
+2. **Reference previous work** - "Now add tests for the function we just created"
+3. **Save session IDs** - For long-running projects
+4. **Use summaries** - Ask for summaries to verify context
+5. **Start fresh when needed** - New threads for unrelated work
diff --git a/docs/installation.mdx b/docs/installation.mdx
new file mode 100644
index 0000000..cbd93f3
--- /dev/null
+++ b/docs/installation.mdx
@@ -0,0 +1,165 @@
+---
+title: Installation
+description: Install the Droid SDK and CLI in your project
+---
+
+# Installation
+
+## SDK Installation
+
+Install the Droid SDK using your preferred package manager:
+
+
+
+ ```bash
+ npm install @activade/droid-sdk
+ ```
+
+
+ ```bash
+ bun add @activade/droid-sdk
+ ```
+
+
+ ```bash
+ yarn add @activade/droid-sdk
+ ```
+
+
+ ```bash
+ pnpm add @activade/droid-sdk
+ ```
+
+
+
+## CLI Installation
+
+The Droid SDK requires the Factory Droid CLI to be installed. You have two options:
+
+### Option 1: Manual Installation
+
+Install the CLI globally:
+
+```bash
+curl -fsSL https://app.factory.ai/cli | sh
+```
+
+### Option 2: Automatic Installation
+
+Use the SDK's built-in installer:
+
+```typescript
+import { ensureDroidCli } from '@activade/droid-sdk/cli';
+
+// Install CLI if not already present
+const cliPath = await ensureDroidCli({
+ onProgress: (progress) => {
+ console.log(`[${progress.phase}] ${progress.message}`);
+ }
+});
+
+console.log('CLI installed at:', cliPath);
+```
+
+## Optional Dependencies
+
+### Zod (Recommended)
+
+For structured output validation, install Zod:
+
+```bash
+npm install zod
+```
+
+Then use it with the SDK:
+
+```typescript
+import { z } from 'zod';
+
+const schema = z.object({
+ name: z.string(),
+ version: z.string()
+});
+
+const result = await droid.exec('Get package info as JSON');
+const data = result.parse(schema);
+```
+
+## TypeScript Configuration
+
+The SDK is written in TypeScript and includes type definitions. Ensure your `tsconfig.json` includes:
+
+```json
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strict": true
+ }
+}
+```
+
+## Verification
+
+Verify your installation:
+
+```typescript
+import { Droid, MODELS } from '@activade/droid-sdk';
+
+const droid = new Droid({ model: MODELS.CLAUDE_SONNET });
+
+// List available tools
+const tools = await droid.listTools();
+console.log('Available tools:', tools);
+
+// Test execution
+const result = await droid.exec('Say hello');
+console.log(result.finalResponse);
+```
+
+## Environment Variables
+
+The SDK respects these environment variables:
+
+| Variable | Description |
+|----------|-------------|
+| `PATH` | System PATH for finding the CLI |
+| `HOME` | Home directory for CLI installation |
+
+## Troubleshooting
+
+### CLI Not Found
+
+If you get a `CliNotFoundError`:
+
+```typescript
+import { CliNotFoundError, ensureDroidCli } from '@activade/droid-sdk';
+
+try {
+ await droid.exec('Hello');
+} catch (error) {
+ if (error instanceof CliNotFoundError) {
+ console.log('Installing CLI...');
+ await ensureDroidCli();
+ }
+}
+```
+
+### Permission Issues
+
+On Unix systems, ensure the CLI is executable:
+
+```bash
+chmod +x ~/.local/bin/droid
+```
+
+### Timeout Errors
+
+Increase the timeout for long operations:
+
+```typescript
+const droid = new Droid({
+ timeout: 600000 // 10 minutes
+});
+```
diff --git a/docs/introduction.mdx b/docs/introduction.mdx
new file mode 100644
index 0000000..ffb36ba
--- /dev/null
+++ b/docs/introduction.mdx
@@ -0,0 +1,82 @@
+---
+title: Introduction
+description: TypeScript SDK for Factory Droid CLI - AI-powered code generation and automation
+---
+
+# Droid SDK
+
+The Droid SDK provides a programmatic TypeScript/JavaScript interface to the Factory Droid CLI, enabling AI-powered code generation and task automation in your applications.
+
+## What is Droid?
+
+Droid is Factory's AI development agent that can:
+
+- Generate and modify code based on natural language prompts
+- Execute multi-turn conversations with context retention
+- Use tools like file operations, shell commands, and web requests
+- Support multiple AI models (Claude, GPT, Gemini, and more)
+
+## Why Use the SDK?
+
+The SDK wraps the Droid CLI to enable:
+
+
+
+ Integrate AI code generation into CI/CD pipelines and automation workflows
+
+
+ Maintain context across multiple prompts with persistent sessions
+
+
+ Observe tool calls and intermediate results as they happen
+
+
+ Parse AI responses with Zod schemas for type-safe data
+
+
+
+## Quick Example
+
+```typescript
+import { Droid, MODELS } from '@activade/droid-sdk';
+
+const droid = new Droid({
+ model: MODELS.CLAUDE_SONNET,
+ autonomyLevel: 'high'
+});
+
+// One-shot execution
+const result = await droid.exec('Create a hello world function');
+console.log(result.finalResponse);
+
+// Multi-turn conversation
+const thread = droid.startThread();
+await thread.run('Create a React component');
+await thread.run('Add unit tests for it');
+```
+
+## Features
+
+- **Thread-based conversations**: Maintain context across multiple prompts
+- **Session persistence**: Resume conversations across process restarts
+- **Real-time streaming**: Observe tool calls as they happen
+- **Structured output**: Validate responses with Zod schemas
+- **Automatic CLI installation**: Install the CLI programmatically if needed
+- **Multiple model support**: Claude, GPT, Gemini, and more
+
+## Next Steps
+
+
+
+ Get up and running in minutes
+
+
+ Detailed installation instructions
+
+
+ Complete API documentation
+
+
+ Learn through practical examples
+
+
diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx
new file mode 100644
index 0000000..7ab0fbc
--- /dev/null
+++ b/docs/quickstart.mdx
@@ -0,0 +1,154 @@
+---
+title: Quick Start
+description: Get started with the Droid SDK in under 5 minutes
+---
+
+# Quick Start
+
+This guide will get you up and running with the Droid SDK in just a few minutes.
+
+## Prerequisites
+
+- Node.js 18+ or Bun 1.0+
+- Factory Droid CLI installed (or let the SDK install it for you)
+
+## Installation
+
+
+
+ ```bash
+ npm install @activade/droid-sdk
+ ```
+
+
+ ```bash
+ bun add @activade/droid-sdk
+ ```
+
+
+ ```bash
+ yarn add @activade/droid-sdk
+ ```
+
+
+ ```bash
+ pnpm add @activade/droid-sdk
+ ```
+
+
+
+## Basic Usage
+
+### 1. Create a Droid Instance
+
+```typescript
+import { Droid, MODELS } from '@activade/droid-sdk';
+
+const droid = new Droid({
+ model: MODELS.CLAUDE_SONNET,
+ cwd: process.cwd()
+});
+```
+
+### 2. Execute a Prompt
+
+```typescript
+// One-shot execution
+const result = await droid.exec('Create a TypeScript function that validates email addresses');
+
+console.log(result.finalResponse);
+console.log(`Completed in ${result.durationMs}ms`);
+```
+
+### 3. Multi-turn Conversation
+
+```typescript
+// Start a conversation thread
+const thread = droid.startThread();
+
+// First prompt - establish context
+await thread.run('Create a REST API for a todo list');
+
+// Follow-up - the AI remembers the previous context
+await thread.run('Add authentication using JWT');
+
+// Another follow-up
+await thread.run('Write tests for the authentication');
+
+// Save session for later
+console.log('Session ID:', thread.id);
+```
+
+### 4. Resume a Conversation
+
+```typescript
+// Resume a previous conversation
+const thread = droid.resumeThread('session_abc123...');
+
+await thread.run('What did we work on last time?');
+```
+
+## Streaming Example
+
+For real-time progress updates:
+
+```typescript
+const thread = droid.startThread();
+const { events, result } = await thread.runStreamed('Build a React component');
+
+// Process events as they arrive
+for await (const event of events) {
+ switch (event.type) {
+ case 'tool_call':
+ console.log(`Calling: ${event.toolName}`);
+ break;
+ case 'tool_result':
+ console.log(`Result: ${event.isError ? 'ERROR' : 'OK'}`);
+ break;
+ case 'message':
+ console.log(`[${event.role}] ${event.text.slice(0, 100)}...`);
+ break;
+ }
+}
+
+// Get final result
+const finalResult = await result;
+console.log('Done:', finalResult.finalResponse);
+```
+
+## Structured Output
+
+Parse AI responses with Zod schemas:
+
+```typescript
+import { z } from 'zod';
+
+const TaskSchema = z.object({
+ title: z.string(),
+ priority: z.enum(['low', 'medium', 'high']),
+ completed: z.boolean()
+});
+
+const result = await thread.run('Create a task object as JSON');
+const task = result.parse(TaskSchema);
+
+console.log(task.title); // TypeScript knows this is a string
+console.log(task.priority); // TypeScript knows this is 'low' | 'medium' | 'high'
+```
+
+## Next Steps
+
+
+
+ Learn about Droid, Threads, and TurnResults
+
+
+ Master real-time event handling
+
+
+ Handle errors gracefully
+
+
+ Complete API documentation
+
+
diff --git a/mint.json b/mint.json
new file mode 100644
index 0000000..79fb54d
--- /dev/null
+++ b/mint.json
@@ -0,0 +1,125 @@
+{
+ "$schema": "https://mintlify.com/schema.json",
+ "name": "Droid SDK",
+ "logo": {
+ "dark": "/logo/dark.svg",
+ "light": "/logo/light.svg"
+ },
+ "favicon": "/favicon.svg",
+ "colors": {
+ "primary": "#6366F1",
+ "light": "#818CF8",
+ "dark": "#4F46E5",
+ "anchors": {
+ "from": "#6366F1",
+ "to": "#8B5CF6"
+ }
+ },
+ "topbarLinks": [
+ {
+ "name": "GitHub",
+ "url": "https://github.com/activadee/droid-sdk"
+ }
+ ],
+ "topbarCtaButton": {
+ "name": "Get Started",
+ "url": "/quickstart"
+ },
+ "tabs": [
+ {
+ "name": "API Reference",
+ "url": "api-reference"
+ }
+ ],
+ "anchors": [
+ {
+ "name": "GitHub",
+ "icon": "github",
+ "url": "https://github.com/activadee/droid-sdk"
+ },
+ {
+ "name": "NPM",
+ "icon": "npm",
+ "url": "https://www.npmjs.com/package/@activade/droid-sdk"
+ }
+ ],
+ "navigation": [
+ {
+ "group": "Getting Started",
+ "pages": [
+ "introduction",
+ "quickstart",
+ "installation"
+ ]
+ },
+ {
+ "group": "Core Concepts",
+ "pages": [
+ "concepts/droid",
+ "concepts/threads",
+ "concepts/streaming",
+ "concepts/structured-output"
+ ]
+ },
+ {
+ "group": "Guides",
+ "pages": [
+ "guides/multi-turn",
+ "guides/file-attachments",
+ "guides/error-handling",
+ "guides/ci-cd"
+ ]
+ },
+ {
+ "group": "API Reference",
+ "pages": [
+ "api-reference/overview",
+ {
+ "group": "Classes",
+ "pages": [
+ "api-reference/classes/droid",
+ "api-reference/classes/thread",
+ "api-reference/classes/turn-result"
+ ]
+ },
+ {
+ "group": "Errors",
+ "pages": [
+ "api-reference/errors/droid-error",
+ "api-reference/errors/cli-not-found-error",
+ "api-reference/errors/execution-error",
+ "api-reference/errors/parse-error",
+ "api-reference/errors/timeout-error",
+ "api-reference/errors/stream-error"
+ ]
+ },
+ {
+ "group": "Types",
+ "pages": [
+ "api-reference/types/config",
+ "api-reference/types/options",
+ "api-reference/types/events",
+ "api-reference/types/turn-items"
+ ]
+ },
+ {
+ "group": "CLI",
+ "pages": [
+ "api-reference/cli/installer",
+ "api-reference/cli/process"
+ ]
+ },
+ {
+ "group": "Models",
+ "pages": [
+ "api-reference/models/overview",
+ "api-reference/models/model-info"
+ ]
+ }
+ ]
+ }
+ ],
+ "footerSocials": {
+ "github": "https://github.com/activadee/droid-sdk"
+ }
+}
diff --git a/package.json b/package.json
index 02fb32d..3348d21 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,8 @@
"lint:fix": "bunx biome check --write .",
"format": "bunx biome format --write .",
"clean": "rm -rf dist",
- "prepublishOnly": "bun run clean && bun run lint && bun run test && bun run build"
+ "prepublishOnly": "bun run clean && bun run lint && bun run test && bun run build",
+ "docs:dev": "bunx mintlify dev --port 3333"
},
"keywords": [
"factory",
diff --git a/src/cli/installer.ts b/src/cli/installer.ts
index 91291a9..6594d9b 100644
--- a/src/cli/installer.ts
+++ b/src/cli/installer.ts
@@ -5,26 +5,88 @@ import { DroidError } from '../errors';
import { findDroidPath } from './process';
import { streamToString, waitForExit } from './utils';
+/**
+ * Options for installing the Droid CLI.
+ *
+ * @category CLI
+ */
export interface InstallOptions {
+ /**
+ * Directory to install the CLI binary.
+ * @default ~/.droid-sdk/bin
+ */
installDir?: string;
+
+ /**
+ * Force reinstallation even if CLI is already present.
+ * @default false
+ */
force?: boolean;
+
+ /**
+ * Specific version to install.
+ * If not specified, installs the latest version.
+ */
version?: string;
+
+ /**
+ * Callback for installation progress updates.
+ */
onProgress?: (progress: InstallProgress) => void;
}
+/**
+ * Installation progress update.
+ *
+ * @category CLI
+ */
export interface InstallProgress {
+ /**
+ * Current phase of the installation process.
+ */
phase: 'checking' | 'downloading' | 'installing' | 'verifying' | 'complete';
+
+ /**
+ * Percentage complete (0-100), if available.
+ */
percent?: number;
+
+ /**
+ * Human-readable status message.
+ */
message?: string;
}
const INSTALL_SCRIPT_URL = 'https://app.factory.ai/cli';
+/**
+ * Gets the default installation directory.
+ *
+ * @returns Path to the default install directory
+ *
+ * @internal
+ */
function getDefaultInstallDir(): string {
const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
return join(home, '.droid-sdk', 'bin');
}
+/**
+ * Checks if the Droid CLI is installed and accessible.
+ *
+ * @returns True if the CLI is found, false otherwise
+ *
+ * @example
+ * ```typescript
+ * if (await isDroidCliInstalled()) {
+ * console.log('Droid CLI is ready');
+ * } else {
+ * console.log('Please install the Droid CLI');
+ * }
+ * ```
+ *
+ * @category CLI
+ */
export async function isDroidCliInstalled(): Promise {
try {
await findDroidPath();
@@ -34,6 +96,21 @@ export async function isDroidCliInstalled(): Promise {
}
}
+/**
+ * Gets the path to the Droid CLI if installed.
+ *
+ * @returns Path to the CLI binary, or null if not installed
+ *
+ * @example
+ * ```typescript
+ * const path = await getDroidCliPath();
+ * if (path) {
+ * console.log('CLI located at:', path);
+ * }
+ * ```
+ *
+ * @category CLI
+ */
export async function getDroidCliPath(): Promise {
try {
return await findDroidPath();
@@ -42,6 +119,44 @@ export async function getDroidCliPath(): Promise {
}
}
+/**
+ * Ensures the Droid CLI is installed, installing it if necessary.
+ *
+ * This is the recommended way to guarantee CLI availability before
+ * using the SDK. It handles:
+ * - Checking for existing installations
+ * - Downloading and installing if needed
+ * - Platform-specific installation (Unix/Windows)
+ * - Progress reporting
+ *
+ * @param options - Installation options
+ * @returns Path to the installed CLI binary
+ *
+ * @throws {DroidError} If installation fails
+ *
+ * @example
+ * ```typescript
+ * import { ensureDroidCli } from '@activade/droid-sdk/cli';
+ *
+ * // Simple usage
+ * const cliPath = await ensureDroidCli();
+ *
+ * // With progress reporting
+ * const cliPath = await ensureDroidCli({
+ * onProgress: (progress) => {
+ * console.log(`[${progress.phase}] ${progress.message}`);
+ * if (progress.percent) {
+ * console.log(`Progress: ${progress.percent}%`);
+ * }
+ * }
+ * });
+ *
+ * // Force reinstall
+ * const cliPath = await ensureDroidCli({ force: true });
+ * ```
+ *
+ * @category CLI
+ */
export async function ensureDroidCli(options: InstallOptions = {}): Promise {
const { force = false, onProgress } = options;
@@ -69,6 +184,15 @@ export async function ensureDroidCli(options: InstallOptions = {}): Promise {
const { onProgress } = options;
@@ -115,6 +239,15 @@ async function installUnix(installDir: string, options: InstallOptions): Promise
return droidPath;
}
+/**
+ * Installs the Droid CLI on Windows.
+ *
+ * @param _installDir - Directory to install the binary (may be ignored by Windows installer)
+ * @param options - Installation options
+ * @returns Path to the installed binary
+ *
+ * @internal
+ */
async function installWindows(_installDir: string, options: InstallOptions): Promise {
const { onProgress } = options;
diff --git a/src/cli/process.ts b/src/cli/process.ts
index c4158f1..b3ff41a 100644
--- a/src/cli/process.ts
+++ b/src/cli/process.ts
@@ -13,32 +13,88 @@ import type {
import { parseJsonLines } from './stream-parser';
import { nodeStreamToWebStream, streamToString, waitForExit } from './utils';
+/**
+ * Options for spawning a Droid CLI process.
+ *
+ * Used internally to configure CLI invocations for execution.
+ *
+ * @category CLI
+ */
export interface SpawnOptions {
+ /** The prompt to execute */
prompt?: string;
+ /** Path to a file containing the prompt */
promptFile?: string;
+ /** Session ID to resume a conversation */
sessionId?: string;
+ /** Working directory for the CLI process */
cwd?: string;
+ /** Path to the Droid CLI binary */
droidPath?: string;
+ /** Timeout in milliseconds */
timeout?: number;
+ /** Output format for CLI response */
outputFormat?: OutputFormat;
+ /** Thread-level options */
threadOptions?: ThreadOptions;
+ /** Run-level options */
runOptions?: RunOptions;
+ /** File attachments to include */
attachments?: FileAttachment[];
}
+/**
+ * Result from a synchronous Droid CLI execution.
+ *
+ * @category CLI
+ */
export interface DroidProcessResult {
+ /** Standard output from the process */
stdout: string;
+ /** Standard error from the process */
stderr: string;
+ /** Process exit code */
exitCode: number;
}
+/**
+ * A streaming Droid CLI process.
+ *
+ * Provides access to real-time events as well as process control methods.
+ *
+ * @category CLI
+ */
export interface StreamingDroidProcess {
+ /** Async iterable of stream events */
events: AsyncIterable;
+ /** The underlying Node.js child process */
process: ChildProcess;
+ /** Wait for the process to exit and return the exit code */
waitForExit: () => Promise;
+ /** Kill the process */
kill: () => void;
}
+/**
+ * Builds a prompt string with file attachments.
+ *
+ * Prepends file references using the `@path` syntax before the main prompt.
+ *
+ * @param prompt - The main prompt text
+ * @param attachments - File attachments to include
+ * @returns Combined prompt string, or undefined if both are empty
+ *
+ * @example
+ * ```typescript
+ * const prompt = buildPromptWithAttachments(
+ * 'Analyze this code',
+ * [{ path: './src/main.ts', type: 'text' }]
+ * );
+ * // Returns: "@./src/main.ts\n\nAnalyze this code"
+ * ```
+ *
+ * @category CLI
+ */
export function buildPromptWithAttachments(
prompt: string | undefined,
attachments: FileAttachment[] | undefined,
@@ -68,6 +124,14 @@ export function buildPromptWithAttachments(
return `${attachmentBlock}\n\n${prompt}`;
}
+/**
+ * Builds CLI arguments from spawn options.
+ *
+ * @param options - The spawn options to convert
+ * @returns Array of CLI arguments
+ *
+ * @internal
+ */
function buildArgs(options: SpawnOptions): string[] {
const args: string[] = ['exec'];
@@ -133,6 +197,30 @@ function buildArgs(options: SpawnOptions): string[] {
return args;
}
+/**
+ * Finds the Droid CLI binary path.
+ *
+ * Searches in the following order:
+ * 1. Preferred path (if provided)
+ * 2. System PATH directories
+ * 3. Common installation locations (~/.local/bin, ~/.droid-sdk/bin, etc.)
+ *
+ * @param preferredPath - Optional preferred path to check first
+ * @returns The path to the Droid CLI binary
+ *
+ * @throws {CliNotFoundError} If the CLI cannot be found in any location
+ *
+ * @example
+ * ```typescript
+ * // Find CLI in default locations
+ * const path = await findDroidPath();
+ *
+ * // Check specific path first
+ * const path = await findDroidPath('/custom/path/droid');
+ * ```
+ *
+ * @category CLI
+ */
export async function findDroidPath(preferredPath?: string): Promise {
const searchPaths: string[] = [];
@@ -170,6 +258,35 @@ export async function findDroidPath(preferredPath?: string): Promise {
throw new CliNotFoundError(searchPaths);
}
+/**
+ * Spawns a Droid CLI process and waits for completion.
+ *
+ * Executes the CLI synchronously and returns the complete output.
+ * For real-time streaming, use {@link spawnDroidStreaming} instead.
+ *
+ * @param options - Spawn configuration options
+ * @returns Process result with stdout, stderr, and exit code
+ *
+ * @throws {CliNotFoundError} If the CLI cannot be found
+ * @throws {TimeoutError} If the operation exceeds the timeout
+ *
+ * @example
+ * ```typescript
+ * const result = await spawnDroid({
+ * prompt: 'Generate hello world',
+ * outputFormat: 'json',
+ * timeout: 60000
+ * });
+ *
+ * if (result.exitCode === 0) {
+ * console.log(result.stdout);
+ * } else {
+ * console.error(result.stderr);
+ * }
+ * ```
+ *
+ * @category CLI
+ */
export async function spawnDroid(options: SpawnOptions): Promise {
const droidPath = await findDroidPath(options.droidPath);
const args = buildArgs(options);
@@ -207,6 +324,34 @@ export async function spawnDroid(options: SpawnOptions): Promise {
const droidPath = await findDroidPath(options.droidPath);
const args = buildArgs({ ...options, outputFormat: 'stream-json' });
@@ -227,6 +372,32 @@ export async function spawnDroidStreaming(options: SpawnOptions): Promise {
const result = await spawnDroid({ ...options, outputFormat: 'json' });
@@ -249,6 +420,26 @@ export async function execDroidJson(options: SpawnOptions): Promise
}
}
+/**
+ * Lists available tools for a given model.
+ *
+ * Queries the CLI for the list of tools available to the AI during execution.
+ *
+ * @param droidPath - Optional path to the CLI binary
+ * @param model - Optional model to query tools for
+ * @returns Array of tool names, or empty array on failure
+ *
+ * @example
+ * ```typescript
+ * const tools = await listDroidTools();
+ * console.log('Available tools:', tools);
+ * // ['file_read', 'file_write', 'shell_exec', ...]
+ *
+ * const gptTools = await listDroidTools(undefined, 'gpt-5.1');
+ * ```
+ *
+ * @category CLI
+ */
export async function listDroidTools(droidPath?: string, model?: string): Promise {
const args = ['exec', '--list-tools', '-o', 'json'];
if (model) {
diff --git a/src/cli/stream-parser.ts b/src/cli/stream-parser.ts
index 9dbef28..f2a9bf7 100644
--- a/src/cli/stream-parser.ts
+++ b/src/cli/stream-parser.ts
@@ -1,6 +1,28 @@
import { ParseError, StreamError } from '../errors';
import type { StreamEvent } from '../types';
+/**
+ * Parses a stream of newline-delimited JSON into stream events.
+ *
+ * This async generator reads from a byte stream, buffers partial lines,
+ * and yields parsed {@link StreamEvent} objects as they become available.
+ *
+ * @param stream - A readable stream of bytes (from CLI stdout)
+ * @yields Parsed stream events in order
+ *
+ * @throws {StreamError} If reading from the stream fails
+ * @throws {ParseError} If a line cannot be parsed as JSON
+ *
+ * @example
+ * ```typescript
+ * const stream = process.stdout; // Assume Web ReadableStream
+ * for await (const event of parseJsonLines(stream)) {
+ * console.log(event.type, event);
+ * }
+ * ```
+ *
+ * @category CLI
+ */
export async function* parseJsonLines(
stream: ReadableStream,
): AsyncGenerator {
@@ -40,6 +62,16 @@ export async function* parseJsonLines(
}
}
+/**
+ * Parses a single JSON line into a stream event.
+ *
+ * @param line - A single line of JSON text
+ * @returns Parsed stream event
+ *
+ * @throws {ParseError} If the line is not valid JSON
+ *
+ * @internal
+ */
function parseJsonLine(line: string): StreamEvent {
try {
return JSON.parse(line) as StreamEvent;
@@ -52,12 +84,42 @@ function parseJsonLine(line: string): StreamEvent {
}
}
+/**
+ * Collects summary information from an array of stream events.
+ *
+ * Extracts the session ID, final response, timing, and error status
+ * from a collection of events. Useful for aggregating streaming results.
+ *
+ * @param events - Array of stream events to process
+ * @returns Collected summary with session info, final text, and status
+ *
+ * @example
+ * ```typescript
+ * const events: StreamEvent[] = [];
+ * for await (const event of stream) {
+ * events.push(event);
+ * }
+ *
+ * const summary = collectStreamEvents(events);
+ * console.log(`Session: ${summary.sessionId}`);
+ * console.log(`Duration: ${summary.durationMs}ms`);
+ * console.log(`Result: ${summary.finalText}`);
+ * ```
+ *
+ * @category CLI
+ */
export function collectStreamEvents(events: StreamEvent[]): {
+ /** Session ID from the events */
sessionId: string | undefined;
+ /** Final response text */
finalText: string | undefined;
+ /** Total execution duration in milliseconds */
durationMs: number;
+ /** Number of conversation turns */
numTurns: number;
+ /** Whether an error occurred */
isError: boolean;
+ /** Error message if isError is true */
errorMessage?: string;
} {
let sessionId: string | undefined;
diff --git a/src/cli/utils.ts b/src/cli/utils.ts
index 7e45efe..3f27df4 100644
--- a/src/cli/utils.ts
+++ b/src/cli/utils.ts
@@ -2,7 +2,22 @@ import type { ChildProcess } from 'node:child_process';
import type { Readable } from 'node:stream';
/**
- * Convert a Node.js Readable stream to a string.
+ * Converts a Node.js Readable stream to a string.
+ *
+ * Collects all chunks from the stream and concatenates them into
+ * a UTF-8 string.
+ *
+ * @param stream - A Node.js Readable stream, or null
+ * @returns Promise resolving to the complete stream content as a string
+ *
+ * @example
+ * ```typescript
+ * const proc = spawn('echo', ['Hello']);
+ * const output = await streamToString(proc.stdout);
+ * console.log(output); // 'Hello\n'
+ * ```
+ *
+ * @category CLI
*/
export function streamToString(stream: Readable | null): Promise {
if (!stream) return Promise.resolve('');
@@ -15,8 +30,25 @@ export function streamToString(stream: Readable | null): Promise {
}
/**
- * Wait for a child process to exit and return the exit code.
- * Handles the race condition where process may exit before listener is registered.
+ * Waits for a child process to exit and returns the exit code.
+ *
+ * Handles the race condition where a process may exit before the
+ * listener is registered by checking exitCode after attaching the
+ * close handler.
+ *
+ * @param proc - The child process to wait for
+ * @returns Promise resolving to the exit code (0 if null)
+ *
+ * @example
+ * ```typescript
+ * const proc = spawn('npm', ['test']);
+ * const exitCode = await waitForExit(proc);
+ * if (exitCode !== 0) {
+ * console.error('Tests failed');
+ * }
+ * ```
+ *
+ * @category CLI
*/
export function waitForExit(proc: ChildProcess): Promise {
return new Promise((resolve) => {
@@ -39,7 +71,28 @@ export function waitForExit(proc: ChildProcess): Promise {
}
/**
- * Convert a Node.js Readable stream to a Web ReadableStream.
+ * Converts a Node.js Readable stream to a Web ReadableStream.
+ *
+ * Creates a WHATWG-compatible ReadableStream that wraps a Node.js
+ * Readable stream, enabling use with web APIs.
+ *
+ * @param nodeStream - A Node.js Readable stream
+ * @returns A Web ReadableStream of Uint8Array chunks
+ *
+ * @example
+ * ```typescript
+ * const proc = spawn('cat', ['file.txt']);
+ * const webStream = nodeStreamToWebStream(proc.stdout!);
+ *
+ * const reader = webStream.getReader();
+ * while (true) {
+ * const { done, value } = await reader.read();
+ * if (done) break;
+ * console.log(new TextDecoder().decode(value));
+ * }
+ * ```
+ *
+ * @category CLI
*/
export function nodeStreamToWebStream(nodeStream: Readable): ReadableStream {
return new ReadableStream({
diff --git a/src/droid.ts b/src/droid.ts
index 09c1733..384e93f 100644
--- a/src/droid.ts
+++ b/src/droid.ts
@@ -4,9 +4,68 @@ import { buildTurnResultFromJson, type TurnResult } from './turn';
import type { DroidConfig, ExecOptions, ThreadOptions } from './types';
import { DEFAULT_DROID_PATH, DEFAULT_TIMEOUT } from './types';
+/**
+ * Main entry point for the Droid SDK.
+ *
+ * The `Droid` class provides a high-level interface to interact with the Factory Droid CLI,
+ * enabling AI-powered code generation and task automation. It supports both stateless
+ * one-shot execution and stateful multi-turn conversations through threads.
+ *
+ * @example
+ * ```typescript
+ * import { Droid, MODELS } from '@activade/droid-sdk';
+ *
+ * // Create a new Droid instance with configuration
+ * const droid = new Droid({
+ * model: MODELS.CLAUDE_SONNET,
+ * autonomyLevel: 'high',
+ * cwd: './my-project'
+ * });
+ *
+ * // One-shot execution
+ * const result = await droid.exec('Create a hello world function');
+ * console.log(result.finalResponse);
+ *
+ * // Multi-turn conversation
+ * const thread = droid.startThread();
+ * await thread.run('Create a React component');
+ * await thread.run('Add unit tests for it');
+ * ```
+ *
+ * @see {@link Thread} for multi-turn conversation management
+ * @see {@link TurnResult} for response handling
+ *
+ * @category Core
+ */
export class Droid {
private readonly _config: DroidConfig;
+ /**
+ * Creates a new Droid instance with the specified configuration.
+ *
+ * @param config - Configuration options for the Droid instance
+ * @param config.cwd - Working directory for CLI operations (defaults to process.cwd())
+ * @param config.model - AI model to use for generation (e.g., 'claude-sonnet-4-5-20250929')
+ * @param config.autonomyLevel - Level of autonomous decision-making ('low', 'medium', 'high')
+ * @param config.reasoningEffort - Reasoning intensity for complex tasks ('off', 'low', 'medium', 'high')
+ * @param config.droidPath - Custom path to the Droid CLI binary
+ * @param config.timeout - Maximum execution time in milliseconds (default: 600000)
+ *
+ * @example
+ * ```typescript
+ * // Basic usage with defaults
+ * const droid = new Droid();
+ *
+ * // With full configuration
+ * const droid = new Droid({
+ * model: 'claude-opus-4-5-20251101',
+ * autonomyLevel: 'high',
+ * reasoningEffort: 'medium',
+ * cwd: '/path/to/project',
+ * timeout: 300000
+ * });
+ * ```
+ */
constructor(config: DroidConfig = {}) {
this._config = {
cwd: config.cwd ?? process.cwd(),
@@ -18,10 +77,53 @@ export class Droid {
};
}
+ /**
+ * Returns the current configuration as a readonly object.
+ *
+ * @returns The current Droid configuration
+ *
+ * @example
+ * ```typescript
+ * const droid = new Droid({ model: 'claude-sonnet-4-5-20250929' });
+ * console.log(droid.config.model); // 'claude-sonnet-4-5-20250929'
+ * ```
+ */
get config(): Readonly {
return this._config;
}
+ /**
+ * Creates a new conversation thread for multi-turn interactions.
+ *
+ * Threads maintain context across multiple prompts, allowing for iterative
+ * development and refinement of AI responses. Each thread has a unique
+ * session ID that persists the conversation state.
+ *
+ * @param options - Optional thread-specific configuration that overrides instance defaults
+ * @returns A new Thread instance ready for interaction
+ *
+ * @example
+ * ```typescript
+ * const droid = new Droid({ model: MODELS.CLAUDE_SONNET });
+ *
+ * // Start a new thread
+ * const thread = droid.startThread({
+ * autonomyLevel: 'high',
+ * enabledTools: ['file_read', 'file_write']
+ * });
+ *
+ * // Use the thread for multi-turn conversation
+ * await thread.run('Create a REST API');
+ * await thread.run('Add authentication');
+ * await thread.run('Write tests');
+ *
+ * // Access the session ID for later resumption
+ * console.log('Session ID:', thread.id);
+ * ```
+ *
+ * @see {@link Thread.run} for executing prompts
+ * @see {@link resumeThread} for continuing a previous conversation
+ */
startThread(options: ThreadOptions = {}): Thread {
const mergedOptions: ThreadOptions = {
model: this._config.model,
@@ -33,6 +135,36 @@ export class Droid {
return new Thread(this._config, mergedOptions);
}
+ /**
+ * Resumes a previously created conversation thread using its session ID.
+ *
+ * This allows continuing a conversation across process restarts or
+ * different execution contexts. The session state is persisted by the
+ * Droid CLI and can be resumed at any time.
+ *
+ * @param sessionId - The unique session identifier from a previous thread
+ * @param options - Optional thread configuration overrides
+ * @returns A Thread instance connected to the existing session
+ *
+ * @example
+ * ```typescript
+ * // Save the session ID from a previous thread
+ * const previousSessionId = 'session_abc123...';
+ *
+ * // Resume the conversation later
+ * const droid = new Droid();
+ * const thread = droid.resumeThread(previousSessionId);
+ *
+ * // Continue where you left off
+ * const result = await thread.run('What did we work on last time?');
+ * console.log(result.finalResponse);
+ * ```
+ *
+ * @throws {ExecutionError} If the session ID is invalid or expired
+ *
+ * @see {@link startThread} for creating new threads
+ * @see {@link Thread.id} for accessing session IDs
+ */
resumeThread(sessionId: string, options: ThreadOptions = {}): Thread {
const mergedOptions: ThreadOptions = {
model: this._config.model,
@@ -44,6 +176,45 @@ export class Droid {
return new Thread(this._config, mergedOptions, sessionId);
}
+ /**
+ * Executes a single prompt without maintaining conversation state.
+ *
+ * This is ideal for one-off tasks that don't require context from
+ * previous interactions. For multi-turn conversations, use
+ * {@link startThread} instead.
+ *
+ * @param prompt - The natural language prompt to execute
+ * @param options - Execution options including model, output schema, etc.
+ * @returns A promise resolving to the execution result
+ *
+ * @example
+ * ```typescript
+ * const droid = new Droid({ model: MODELS.CLAUDE_SONNET });
+ *
+ * // Simple execution
+ * const result = await droid.exec('Generate a UUID');
+ * console.log(result.finalResponse);
+ *
+ * // With structured output using Zod schema
+ * import { z } from 'zod';
+ *
+ * const schema = z.object({
+ * name: z.string(),
+ * version: z.string()
+ * });
+ *
+ * const result = await droid.exec('Get package info as JSON', {
+ * outputSchema: { type: 'object', properties: { name: {}, version: {} } }
+ * });
+ * const data = result.parse(schema);
+ * ```
+ *
+ * @throws {ExecutionError} If the CLI execution fails
+ * @throws {TimeoutError} If the operation exceeds the configured timeout
+ * @throws {CliNotFoundError} If the Droid CLI is not installed
+ *
+ * @see {@link TurnResult} for parsing and accessing response data
+ */
async exec(prompt: string, options: ExecOptions = {}): Promise {
const mergedOptions: ExecOptions = {
model: this._config.model,
@@ -65,6 +236,28 @@ export class Droid {
return buildTurnResultFromJson(jsonResult);
}
+ /**
+ * Lists all available tools for the configured or specified model.
+ *
+ * Tools represent capabilities that the AI can use during execution,
+ * such as file operations, shell commands, or web requests.
+ *
+ * @param model - Optional model ID to query tools for (defaults to instance model)
+ * @returns A promise resolving to an array of tool names
+ *
+ * @example
+ * ```typescript
+ * const droid = new Droid({ model: MODELS.CLAUDE_SONNET });
+ *
+ * // List tools for the configured model
+ * const tools = await droid.listTools();
+ * console.log('Available tools:', tools);
+ * // ['file_read', 'file_write', 'shell_exec', ...]
+ *
+ * // List tools for a specific model
+ * const gptTools = await droid.listTools('gpt-5.1');
+ * ```
+ */
async listTools(model?: string): Promise {
return listDroidTools(this._config.droidPath, model ?? this._config.model);
}
diff --git a/src/errors.ts b/src/errors.ts
index f65a9b1..8cdf280 100644
--- a/src/errors.ts
+++ b/src/errors.ts
@@ -1,4 +1,36 @@
+/**
+ * Base error class for all Droid SDK errors.
+ *
+ * All SDK-specific errors extend this class, making it easy to catch
+ * and handle Droid-related errors separately from other exceptions.
+ *
+ * @example
+ * ```typescript
+ * import { Droid, DroidError, CliNotFoundError } from '@activade/droid-sdk';
+ *
+ * try {
+ * const droid = new Droid();
+ * await droid.exec('Generate code');
+ * } catch (error) {
+ * if (error instanceof CliNotFoundError) {
+ * console.log('Please install the Droid CLI first');
+ * } else if (error instanceof DroidError) {
+ * console.log('Droid error:', error.message);
+ * } else {
+ * throw error; // Re-throw non-Droid errors
+ * }
+ * }
+ * ```
+ *
+ * @category Errors
+ */
export class DroidError extends Error {
+ /**
+ * Creates a new DroidError instance.
+ *
+ * @param message - Human-readable error description
+ * @param options - Optional error options including cause
+ */
constructor(message: string, options?: { cause?: Error }) {
super(message, options);
this.name = 'DroidError';
@@ -6,9 +38,43 @@ export class DroidError extends Error {
}
}
+/**
+ * Thrown when the Droid CLI binary cannot be found.
+ *
+ * This error indicates that the Droid CLI is not installed or not
+ * in the system PATH. The {@link searchedPaths} property contains
+ * all locations that were checked.
+ *
+ * @example
+ * ```typescript
+ * import { Droid, CliNotFoundError } from '@activade/droid-sdk';
+ * import { ensureDroidCli } from '@activade/droid-sdk/cli';
+ *
+ * try {
+ * const droid = new Droid();
+ * await droid.exec('Hello');
+ * } catch (error) {
+ * if (error instanceof CliNotFoundError) {
+ * console.log('Searched paths:', error.searchedPaths);
+ * // Auto-install the CLI
+ * await ensureDroidCli();
+ * }
+ * }
+ * ```
+ *
+ * @category Errors
+ */
export class CliNotFoundError extends DroidError {
+ /**
+ * List of filesystem paths that were searched for the CLI binary.
+ */
readonly searchedPaths: string[];
+ /**
+ * Creates a new CliNotFoundError instance.
+ *
+ * @param searchedPaths - Array of paths that were checked for the CLI
+ */
constructor(searchedPaths: string[]) {
super(
`Droid CLI not found. Searched: ${searchedPaths.join(', ')}. Install with: curl -fsSL https://app.factory.ai/cli | sh`,
@@ -18,11 +84,55 @@ export class CliNotFoundError extends DroidError {
}
}
+/**
+ * Thrown when the Droid CLI process fails to execute.
+ *
+ * This error provides details about the failure including the exit code,
+ * stderr output, and optionally the session ID if one was established.
+ *
+ * @example
+ * ```typescript
+ * import { Droid, ExecutionError } from '@activade/droid-sdk';
+ *
+ * try {
+ * await droid.exec('Invalid command');
+ * } catch (error) {
+ * if (error instanceof ExecutionError) {
+ * console.log('Exit code:', error.exitCode);
+ * console.log('Stderr:', error.stderr);
+ * if (error.sessionId) {
+ * console.log('Session:', error.sessionId);
+ * }
+ * }
+ * }
+ * ```
+ *
+ * @category Errors
+ */
export class ExecutionError extends DroidError {
+ /**
+ * The process exit code (non-zero indicates failure).
+ */
readonly exitCode: number;
+
+ /**
+ * Standard error output from the CLI process.
+ */
readonly stderr: string;
+
+ /**
+ * The session ID if one was established before the error occurred.
+ */
readonly sessionId?: string;
+ /**
+ * Creates a new ExecutionError instance.
+ *
+ * @param message - Human-readable error description
+ * @param exitCode - The process exit code
+ * @param stderr - Standard error output
+ * @param sessionId - Optional session ID from the execution
+ */
constructor(message: string, exitCode: number, stderr: string, sessionId?: string) {
super(message);
this.name = 'ExecutionError';
@@ -32,9 +142,45 @@ export class ExecutionError extends DroidError {
}
}
+/**
+ * Thrown when parsing JSON or structured output fails.
+ *
+ * This error includes the raw content that failed to parse, useful
+ * for debugging malformed responses.
+ *
+ * @example
+ * ```typescript
+ * import { Droid, ParseError } from '@activade/droid-sdk';
+ * import { z } from 'zod';
+ *
+ * const schema = z.object({ value: z.number() });
+ *
+ * try {
+ * const result = await droid.exec('Generate JSON');
+ * result.parse(schema);
+ * } catch (error) {
+ * if (error instanceof ParseError) {
+ * console.log('Failed to parse:', error.raw.slice(0, 100));
+ * console.log('Cause:', error.cause);
+ * }
+ * }
+ * ```
+ *
+ * @category Errors
+ */
export class ParseError extends DroidError {
+ /**
+ * The raw content that failed to parse.
+ */
readonly raw: string;
+ /**
+ * Creates a new ParseError instance.
+ *
+ * @param message - Human-readable error description
+ * @param raw - The content that failed to parse
+ * @param cause - Optional underlying error that caused the parse failure
+ */
constructor(message: string, raw: string, cause?: Error) {
super(message, { cause });
this.name = 'ParseError';
@@ -42,9 +188,39 @@ export class ParseError extends DroidError {
}
}
+/**
+ * Thrown when an operation exceeds the configured timeout.
+ *
+ * The {@link timeoutMs} property indicates the timeout value that was exceeded.
+ *
+ * @example
+ * ```typescript
+ * import { Droid, TimeoutError } from '@activade/droid-sdk';
+ *
+ * const droid = new Droid({ timeout: 30000 }); // 30 seconds
+ *
+ * try {
+ * await droid.exec('Long running task');
+ * } catch (error) {
+ * if (error instanceof TimeoutError) {
+ * console.log(`Operation timed out after ${error.timeoutMs}ms`);
+ * }
+ * }
+ * ```
+ *
+ * @category Errors
+ */
export class TimeoutError extends DroidError {
+ /**
+ * The timeout duration in milliseconds that was exceeded.
+ */
readonly timeoutMs: number;
+ /**
+ * Creates a new TimeoutError instance.
+ *
+ * @param timeoutMs - The timeout value in milliseconds
+ */
constructor(timeoutMs: number) {
super(`Operation timed out after ${timeoutMs}ms`);
this.name = 'TimeoutError';
@@ -52,7 +228,38 @@ export class TimeoutError extends DroidError {
}
}
+/**
+ * Thrown when reading from a stream fails.
+ *
+ * This typically occurs during streaming execution when the event
+ * stream is interrupted or corrupted.
+ *
+ * @example
+ * ```typescript
+ * import { StreamError } from '@activade/droid-sdk';
+ *
+ * try {
+ * const { events } = await thread.runStreamed('Generate code');
+ * for await (const event of events) {
+ * console.log(event);
+ * }
+ * } catch (error) {
+ * if (error instanceof StreamError) {
+ * console.log('Stream failed:', error.message);
+ * console.log('Cause:', error.cause);
+ * }
+ * }
+ * ```
+ *
+ * @category Errors
+ */
export class StreamError extends DroidError {
+ /**
+ * Creates a new StreamError instance.
+ *
+ * @param message - Human-readable error description
+ * @param cause - Optional underlying error that caused the stream failure
+ */
constructor(message: string, cause?: Error) {
super(message, { cause });
this.name = 'StreamError';
diff --git a/src/events.ts b/src/events.ts
index d6e663e..0655383 100644
--- a/src/events.ts
+++ b/src/events.ts
@@ -1,11 +1,47 @@
import type { TurnResult } from './turn';
import type { StreamEvent } from './types';
+/**
+ * A streaming turn result with real-time events.
+ *
+ * Returned by {@link Thread.runStreamed} to provide access to events
+ * as they occur, along with a promise for the final result.
+ *
+ * @example
+ * ```typescript
+ * const { events, result } = await thread.runStreamed('Generate code');
+ *
+ * // Process events as they arrive
+ * for await (const event of events) {
+ * console.log(event.type);
+ * }
+ *
+ * // Get the final result after all events
+ * const finalResult = await result;
+ * console.log(finalResult.finalResponse);
+ * ```
+ *
+ * @category Events
+ */
export interface StreamedTurn {
+ /**
+ * Async iterable of stream events.
+ *
+ * Yields events in real-time as they are emitted by the CLI.
+ * Must be fully consumed before accessing the result promise.
+ */
events: AsyncIterable;
+
+ /**
+ * Promise that resolves to the final turn result.
+ *
+ * Resolves after all events have been emitted and the CLI
+ * process has completed.
+ */
result: Promise;
}
+// Re-export event types for convenient access
export type {
MessageEvent,
StreamEvent,
@@ -16,6 +52,7 @@ export type {
TurnFailedEvent,
} from './types';
+// Re-export type guards for event handling
export {
isMessageEvent,
isSystemInitEvent,
diff --git a/src/models.ts b/src/models.ts
index 7741b81..535f555 100644
--- a/src/models.ts
+++ b/src/models.ts
@@ -1,13 +1,55 @@
export { MODELS, type ModelId } from './types/options';
+/**
+ * Metadata about a supported AI model.
+ *
+ * Contains information about the model's capabilities, provider,
+ * and default settings.
+ *
+ * @example
+ * ```typescript
+ * const info = getModelInfo('claude-sonnet-4-5-20250929');
+ * if (info) {
+ * console.log(`${info.name} by ${info.provider}`);
+ * console.log(`Supports reasoning: ${info.supportsReasoning}`);
+ * }
+ * ```
+ *
+ * @category Models
+ */
export interface ModelInfo {
+ /** Unique model identifier */
id: string;
+ /** Human-readable model name */
name: string;
+ /** Model provider */
provider: 'anthropic' | 'openai' | 'google' | 'opensource';
+ /** Whether the model supports reasoning/thinking mode */
supportsReasoning: boolean;
+ /** Default reasoning effort level for this model */
defaultReasoningEffort?: string;
}
+/**
+ * Registry of all supported models with their metadata.
+ *
+ * Use {@link getModelInfo} to look up a specific model, or
+ * iterate this object to list all available models.
+ *
+ * @example
+ * ```typescript
+ * // List all models
+ * for (const [id, info] of Object.entries(MODEL_INFO)) {
+ * console.log(`${info.name} (${id})`);
+ * }
+ *
+ * // Get models by provider
+ * const claudeModels = Object.values(MODEL_INFO)
+ * .filter(m => m.provider === 'anthropic');
+ * ```
+ *
+ * @category Models
+ */
export const MODEL_INFO: Record = {
'claude-opus-4-5-20251101': {
id: 'claude-opus-4-5-20251101',
@@ -80,10 +122,47 @@ export const MODEL_INFO: Record = {
},
};
+/**
+ * Retrieves metadata for a specific model.
+ *
+ * @param modelId - The model identifier to look up
+ * @returns Model metadata, or undefined if not found
+ *
+ * @example
+ * ```typescript
+ * const info = getModelInfo(MODELS.CLAUDE_SONNET);
+ * if (info) {
+ * console.log(`Using ${info.name}`);
+ * if (info.supportsReasoning) {
+ * console.log(`Default reasoning: ${info.defaultReasoningEffort}`);
+ * }
+ * }
+ * ```
+ *
+ * @category Models
+ */
export function getModelInfo(modelId: string): ModelInfo | undefined {
return MODEL_INFO[modelId];
}
+/**
+ * Checks if a model identifier is valid.
+ *
+ * A model is valid if it exists in {@link MODEL_INFO} or is a custom
+ * model (prefixed with "custom:").
+ *
+ * @param modelId - The model identifier to validate
+ * @returns True if the model is valid
+ *
+ * @example
+ * ```typescript
+ * isValidModel('claude-sonnet-4-5-20250929'); // true
+ * isValidModel('custom:my-fine-tuned-model'); // true
+ * isValidModel('invalid-model'); // false
+ * ```
+ *
+ * @category Models
+ */
export function isValidModel(modelId: string): boolean {
return modelId in MODEL_INFO || modelId.startsWith('custom:');
}
diff --git a/src/schemas/zod-adapter.ts b/src/schemas/zod-adapter.ts
index a6c6fb2..227d0c8 100644
--- a/src/schemas/zod-adapter.ts
+++ b/src/schemas/zod-adapter.ts
@@ -1,5 +1,13 @@
import type { JsonSchema } from '../types';
+/**
+ * Internal type representing a Zod-like schema structure.
+ *
+ * This type allows the SDK to work with Zod schemas without requiring
+ * Zod as a direct dependency.
+ *
+ * @internal
+ */
type ZodTypeLike = {
_def?: {
typeName?: string;
@@ -13,6 +21,46 @@ type ZodTypeLike = {
shape?: Record;
};
+/**
+ * Converts a Zod schema to a JSON Schema object.
+ *
+ * This enables the SDK to accept Zod schemas for structured output
+ * validation and convert them to JSON Schema format for the CLI.
+ *
+ * Supported Zod types:
+ * - Primitives: string, number, boolean, null, undefined
+ * - Literals and enums
+ * - Arrays and objects
+ * - Optional, nullable, and default
+ * - Union, intersection, record, tuple
+ *
+ * @param schema - A Zod schema or Zod-like object
+ * @returns Equivalent JSON Schema object
+ *
+ * @example
+ * ```typescript
+ * import { z } from 'zod';
+ *
+ * const userSchema = z.object({
+ * name: z.string().min(1),
+ * email: z.string().email(),
+ * age: z.number().optional()
+ * });
+ *
+ * const jsonSchema = zodToJsonSchema(userSchema);
+ * // {
+ * // type: 'object',
+ * // properties: {
+ * // name: { type: 'string', minLength: 1 },
+ * // email: { type: 'string', format: 'email' },
+ * // age: { type: 'number' }
+ * // },
+ * // required: ['name', 'email']
+ * // }
+ * ```
+ *
+ * @category Schema
+ */
export function zodToJsonSchema(schema: ZodTypeLike): JsonSchema {
const def = schema._def;
if (!def) {
@@ -94,6 +142,14 @@ export function zodToJsonSchema(schema: ZodTypeLike): JsonSchema {
}
}
+/**
+ * Builds a JSON Schema for a Zod string type.
+ *
+ * @param def - The Zod definition object
+ * @returns JSON Schema for a string type
+ *
+ * @internal
+ */
function buildStringSchema(def: ZodTypeLike['_def']): JsonSchema {
const schema: JsonSchema = { type: 'string' };
@@ -127,6 +183,14 @@ function buildStringSchema(def: ZodTypeLike['_def']): JsonSchema {
return schema;
}
+/**
+ * Builds a JSON Schema for a Zod number type.
+ *
+ * @param def - The Zod definition object
+ * @returns JSON Schema for a number type
+ *
+ * @internal
+ */
function buildNumberSchema(def: ZodTypeLike['_def']): JsonSchema {
const schema: JsonSchema = { type: 'number' };
@@ -150,6 +214,15 @@ function buildNumberSchema(def: ZodTypeLike['_def']): JsonSchema {
return schema;
}
+/**
+ * Builds a JSON Schema for a Zod object type.
+ *
+ * @param schema - The Zod schema object
+ * @param def - The Zod definition object
+ * @returns JSON Schema for an object type
+ *
+ * @internal
+ */
function buildObjectSchema(schema: ZodTypeLike, def: ZodTypeLike['_def']): JsonSchema {
const shape = def?.shape?.() ?? schema.shape ?? {};
const properties: Record = {};
@@ -174,6 +247,25 @@ function buildObjectSchema(schema: ZodTypeLike, def: ZodTypeLike['_def']): JsonS
};
}
+/**
+ * Type guard to check if a value is a Zod schema.
+ *
+ * @param value - Value to check
+ * @returns True if the value appears to be a Zod schema
+ *
+ * @example
+ * ```typescript
+ * import { z } from 'zod';
+ *
+ * const schema = z.object({ name: z.string() });
+ *
+ * if (isZodSchema(schema)) {
+ * const jsonSchema = zodToJsonSchema(schema);
+ * }
+ * ```
+ *
+ * @category Schema
+ */
export function isZodSchema(value: unknown): value is ZodTypeLike {
return (
typeof value === 'object' &&
diff --git a/src/thread.ts b/src/thread.ts
index 678ee17..2a63f4e 100644
--- a/src/thread.ts
+++ b/src/thread.ts
@@ -4,12 +4,77 @@ import type { StreamedTurn } from './events';
import { buildTurnResultFromEvents, buildTurnResultFromJson, type TurnResult } from './turn';
import type { DroidConfig, RunOptions, StreamEvent, ThreadOptions } from './types';
+/**
+ * Represents a conversation thread with the Droid AI.
+ *
+ * A Thread maintains conversational context across multiple interactions,
+ * allowing for iterative development and refinement of AI responses. Each
+ * thread is identified by a unique session ID that persists the conversation
+ * state across process restarts.
+ *
+ * Threads support both synchronous execution via {@link run} and real-time
+ * streaming via {@link runStreamed} for observing tool calls and intermediate
+ * results as they happen.
+ *
+ * @example
+ * ```typescript
+ * import { Droid, MODELS } from '@activade/droid-sdk';
+ *
+ * const droid = new Droid({ model: MODELS.CLAUDE_SONNET });
+ * const thread = droid.startThread();
+ *
+ * // First interaction - establish context
+ * const result1 = await thread.run('Create a TypeScript function to validate emails');
+ *
+ * // Follow-up - the AI remembers the previous context
+ * const result2 = await thread.run('Add support for custom domain restrictions');
+ *
+ * // Access session ID for later resumption
+ * console.log('Session ID:', thread.id);
+ * ```
+ *
+ * @example
+ * ```typescript
+ * // Streaming example - observe tool calls in real-time
+ * const { events, result } = await thread.runStreamed('Build a REST API');
+ *
+ * for await (const event of events) {
+ * if (event.type === 'tool_call') {
+ * console.log(`Calling tool: ${event.toolName}`);
+ * } else if (event.type === 'tool_result') {
+ * console.log(`Tool result: ${event.value.slice(0, 100)}...`);
+ * }
+ * }
+ *
+ * const finalResult = await result;
+ * console.log('Final response:', finalResult.finalResponse);
+ * ```
+ *
+ * @see {@link Droid.startThread} for creating new threads
+ * @see {@link Droid.resumeThread} for resuming existing threads
+ * @see {@link TurnResult} for handling execution results
+ *
+ * @category Core
+ */
export class Thread {
private _sessionId: string | undefined;
private readonly _cwd: string;
private readonly _config: DroidConfig;
private readonly _threadOptions: ThreadOptions;
+ /**
+ * Creates a new Thread instance.
+ *
+ * @param config - The Droid configuration inherited from the parent Droid instance
+ * @param threadOptions - Thread-specific options that override config defaults
+ * @param sessionId - Optional session ID to resume an existing conversation
+ *
+ * @remarks
+ * This constructor is typically not called directly. Use {@link Droid.startThread}
+ * or {@link Droid.resumeThread} instead for proper initialization.
+ *
+ * @internal
+ */
constructor(config: DroidConfig, threadOptions: ThreadOptions = {}, sessionId?: string) {
this._config = config;
this._threadOptions = threadOptions;
@@ -17,14 +82,104 @@ export class Thread {
this._cwd = threadOptions.cwd ?? config.cwd ?? process.cwd();
}
+ /**
+ * The unique session identifier for this thread.
+ *
+ * The session ID is assigned after the first prompt is executed and
+ * remains constant for the lifetime of the thread. Use this ID with
+ * {@link Droid.resumeThread} to continue the conversation later.
+ *
+ * @returns The session ID, or undefined if no prompts have been executed yet
+ *
+ * @example
+ * ```typescript
+ * const thread = droid.startThread();
+ * console.log(thread.id); // undefined
+ *
+ * await thread.run('Hello');
+ * console.log(thread.id); // 'session_abc123...'
+ *
+ * // Save for later resumption
+ * localStorage.setItem('sessionId', thread.id);
+ * ```
+ */
get id(): string | undefined {
return this._sessionId;
}
+ /**
+ * The working directory for this thread's CLI operations.
+ *
+ * All file operations and commands executed by the AI will be relative
+ * to this directory.
+ *
+ * @returns The absolute path to the working directory
+ *
+ * @example
+ * ```typescript
+ * const thread = droid.startThread({ cwd: '/projects/my-app' });
+ * console.log(thread.cwd); // '/projects/my-app'
+ * ```
+ */
get cwd(): string {
return this._cwd;
}
+ /**
+ * Executes a prompt with real-time streaming of events.
+ *
+ * This method provides access to intermediate events (tool calls, tool results,
+ * messages) as they occur, enabling real-time UI updates and progress monitoring.
+ * The final result is available via the returned promise.
+ *
+ * @param prompt - The natural language prompt to execute
+ * @param options - Optional run-specific configuration
+ * @returns A StreamedTurn containing an async event iterator and result promise
+ *
+ * @example
+ * ```typescript
+ * // Basic streaming with event handling
+ * const { events, result } = await thread.runStreamed('Create a React component');
+ *
+ * for await (const event of events) {
+ * switch (event.type) {
+ * case 'message':
+ * console.log(`[${event.role}] ${event.text}`);
+ * break;
+ * case 'tool_call':
+ * console.log(`Calling: ${event.toolName}(${JSON.stringify(event.parameters)})`);
+ * break;
+ * case 'tool_result':
+ * console.log(`Result: ${event.isError ? 'ERROR' : 'OK'}`);
+ * break;
+ * case 'completion':
+ * console.log(`Completed in ${event.durationMs}ms`);
+ * break;
+ * }
+ * }
+ *
+ * const finalResult = await result;
+ * ```
+ *
+ * @example
+ * ```typescript
+ * // Use with type guards for type-safe event handling
+ * import { isToolCallEvent, isToolResultEvent } from '@activade/droid-sdk';
+ *
+ * for await (const event of events) {
+ * if (isToolCallEvent(event)) {
+ * // TypeScript knows event is ToolCallEvent
+ * console.log(event.toolName, event.parameters);
+ * }
+ * }
+ * ```
+ *
+ * @throws {ExecutionError} If the Droid process exits with a non-zero code
+ * @throws {StreamError} If there's an error reading the event stream
+ *
+ * @see {@link run} for simpler synchronous execution
+ * @see {@link StreamedTurn} for the return type structure
+ */
async runStreamed(prompt: string, options: RunOptions = {}): Promise {
const spawnOptions = this.buildSpawnOptions(prompt, options);
const streamingProcess = await spawnDroidStreaming(spawnOptions);
@@ -77,6 +232,61 @@ export class Thread {
};
}
+ /**
+ * Executes a prompt and waits for the complete response.
+ *
+ * This method waits for the AI to finish processing before returning,
+ * providing the complete result including all tool calls and the final
+ * response. For real-time updates, use {@link runStreamed} instead.
+ *
+ * @param prompt - The natural language prompt to execute
+ * @param options - Optional run-specific configuration
+ * @returns A promise resolving to the execution result
+ *
+ * @example
+ * ```typescript
+ * // Simple execution
+ * const result = await thread.run('Create a function to sort an array');
+ * console.log(result.finalResponse);
+ *
+ * // Access tool calls made during execution
+ * for (const call of result.toolCalls) {
+ * console.log(`Used tool: ${call.toolName}`);
+ * }
+ * ```
+ *
+ * @example
+ * ```typescript
+ * // With file attachments
+ * const result = await thread.run('Describe this image', {
+ * attachments: [
+ * { path: './screenshot.png', type: 'image' }
+ * ]
+ * });
+ * ```
+ *
+ * @example
+ * ```typescript
+ * // With structured output validation
+ * import { z } from 'zod';
+ *
+ * const TaskSchema = z.object({
+ * title: z.string(),
+ * priority: z.enum(['low', 'medium', 'high']),
+ * completed: z.boolean()
+ * });
+ *
+ * const result = await thread.run('Create a task object as JSON');
+ * const task = result.parse(TaskSchema);
+ * console.log(task.title, task.priority);
+ * ```
+ *
+ * @throws {ExecutionError} If the CLI execution fails
+ * @throws {TimeoutError} If the operation exceeds the configured timeout
+ *
+ * @see {@link runStreamed} for real-time event streaming
+ * @see {@link TurnResult} for the return type and parsing methods
+ */
async run(prompt: string, options: RunOptions = {}): Promise {
const spawnOptions = this.buildSpawnOptions(prompt, options);
@@ -89,6 +299,15 @@ export class Thread {
return buildTurnResultFromJson(jsonResult);
}
+ /**
+ * Builds the spawn options for CLI execution.
+ *
+ * @param prompt - The prompt to execute
+ * @param options - Run-specific options
+ * @returns Configured spawn options for the CLI process
+ *
+ * @internal
+ */
private buildSpawnOptions(prompt: string, options: RunOptions): SpawnOptions {
const mergedOptions: ThreadOptions = {
...this._threadOptions,
diff --git a/src/turn.ts b/src/turn.ts
index c9a3992..6d6d469 100644
--- a/src/turn.ts
+++ b/src/turn.ts
@@ -8,14 +8,99 @@ import type {
TurnResultData,
} from './types';
+/**
+ * Represents the result of a Droid execution turn.
+ *
+ * TurnResult encapsulates all data from a single AI interaction, including
+ * the final response, tool calls made, messages exchanged, and execution
+ * metadata. It provides convenient accessors for filtering results and
+ * methods for parsing structured output.
+ *
+ * @example
+ * ```typescript
+ * const result = await thread.run('Generate a config object');
+ *
+ * // Access the final text response
+ * console.log(result.finalResponse);
+ *
+ * // Check execution metadata
+ * console.log(`Completed in ${result.durationMs}ms over ${result.numTurns} turns`);
+ *
+ * // Access tool calls and results
+ * for (const call of result.toolCalls) {
+ * console.log(`Called: ${call.toolName}`);
+ * }
+ *
+ * // Parse structured JSON output
+ * import { z } from 'zod';
+ * const Config = z.object({ port: z.number(), host: z.string() });
+ * const config = result.parse(Config);
+ * ```
+ *
+ * @see {@link Thread.run} for creating TurnResult instances
+ * @see {@link Thread.runStreamed} for streaming execution
+ *
+ * @category Core
+ */
export class TurnResult {
+ /**
+ * The final text response from the AI.
+ *
+ * This contains the complete response after all tool calls have been
+ * processed. For structured output, this will be JSON that can be
+ * parsed using {@link parse} or {@link tryParse}.
+ */
readonly finalResponse: string;
+
+ /**
+ * All items from this execution turn.
+ *
+ * Items include messages (user and assistant), tool calls, and tool
+ * results in chronological order. Use the typed accessors like
+ * {@link toolCalls}, {@link toolResults}, and {@link messages} for
+ * filtered access.
+ */
readonly items: AnyTurnItem[];
+
+ /**
+ * The unique session identifier for this conversation.
+ *
+ * This ID can be used with {@link Droid.resumeThread} to continue
+ * the conversation later.
+ */
readonly sessionId: string;
+
+ /**
+ * Total execution time in milliseconds.
+ *
+ * Measures the time from prompt submission to final response,
+ * including all tool calls.
+ */
readonly durationMs: number;
+
+ /**
+ * Number of conversation turns in this execution.
+ *
+ * A turn typically represents one prompt-response cycle, though
+ * complex operations may involve multiple internal turns.
+ */
readonly numTurns: number;
+
+ /**
+ * Indicates whether the execution resulted in an error.
+ *
+ * When true, {@link finalResponse} may contain error details
+ * rather than the expected output.
+ */
readonly isError: boolean;
+ /**
+ * Creates a new TurnResult instance.
+ *
+ * @param data - The raw turn result data from the CLI
+ *
+ * @internal
+ */
constructor(data: TurnResultData) {
this.finalResponse = data.finalResponse;
this.items = data.items;
@@ -25,6 +110,36 @@ export class TurnResult {
this.isError = data.isError;
}
+ /**
+ * Parses the final response as JSON and validates against a schema.
+ *
+ * This method is designed to work with Zod schemas but supports any
+ * object with a `parse` method that throws on invalid input.
+ *
+ * @typeParam T - The expected output type
+ * @param schema - A schema with a parse method (e.g., Zod schema)
+ * @returns The parsed and validated data
+ *
+ * @example
+ * ```typescript
+ * import { z } from 'zod';
+ *
+ * const UserSchema = z.object({
+ * id: z.number(),
+ * name: z.string(),
+ * email: z.string().email()
+ * });
+ *
+ * const result = await thread.run('Generate a user object as JSON');
+ * const user = result.parse(UserSchema);
+ * // TypeScript knows: user.id is number, user.name is string, etc.
+ * ```
+ *
+ * @throws {ParseError} If the response is not valid JSON
+ * @throws {Error} If the schema validation fails (error from schema.parse)
+ *
+ * @see {@link tryParse} for non-throwing validation
+ */
parse(schema: { parse: (data: unknown) => T }): T {
try {
const data = JSON.parse(this.finalResponse);
@@ -37,6 +152,38 @@ export class TurnResult {
}
}
+ /**
+ * Attempts to parse the final response, returning null on failure.
+ *
+ * This is a safe alternative to {@link parse} that returns null instead
+ * of throwing when parsing or validation fails. Ideal for optional
+ * structured output.
+ *
+ * @typeParam T - The expected output type
+ * @param schema - A schema with a safeParse method (e.g., Zod schema)
+ * @returns The parsed data, or null if parsing/validation fails
+ *
+ * @example
+ * ```typescript
+ * import { z } from 'zod';
+ *
+ * const ConfigSchema = z.object({
+ * debug: z.boolean(),
+ * logLevel: z.enum(['info', 'warn', 'error'])
+ * });
+ *
+ * const result = await thread.run('Generate config if needed');
+ * const config = result.tryParse(ConfigSchema);
+ *
+ * if (config) {
+ * console.log('Config:', config);
+ * } else {
+ * console.log('No valid config in response');
+ * }
+ * ```
+ *
+ * @see {@link parse} for throwing validation
+ */
tryParse(schema: { safeParse: (data: unknown) => { success: boolean; data?: T } }): T | null {
try {
const data = JSON.parse(this.finalResponse);
@@ -47,22 +194,103 @@ export class TurnResult {
}
}
+ /**
+ * All tool calls made during this execution.
+ *
+ * Tool calls represent AI requests to use external capabilities
+ * like file operations, shell commands, or web requests.
+ *
+ * @returns Array of tool call items in chronological order
+ *
+ * @example
+ * ```typescript
+ * const result = await thread.run('Create and test a function');
+ *
+ * for (const call of result.toolCalls) {
+ * console.log(`Tool: ${call.toolName}`);
+ * console.log(`Params: ${JSON.stringify(call.parameters)}`);
+ * }
+ * ```
+ */
get toolCalls(): ToolCallItem[] {
return this.items.filter((item): item is ToolCallItem => item.type === 'tool_call');
}
+ /**
+ * All tool results from this execution.
+ *
+ * Tool results contain the output (or error) from each tool call.
+ * Use {@link ToolResultItem.isError} to check for failures.
+ *
+ * @returns Array of tool result items in chronological order
+ *
+ * @example
+ * ```typescript
+ * const result = await thread.run('Read and process files');
+ *
+ * for (const toolResult of result.toolResults) {
+ * if (toolResult.isError) {
+ * console.error(`${toolResult.toolName} failed: ${toolResult.value}`);
+ * } else {
+ * console.log(`${toolResult.toolName} succeeded`);
+ * }
+ * }
+ * ```
+ */
get toolResults(): ToolResultItem[] {
return this.items.filter((item): item is ToolResultItem => item.type === 'tool_result');
}
+ /**
+ * All messages from this execution.
+ *
+ * Messages include both user prompts and assistant responses.
+ * Use {@link assistantMessages} for AI responses only.
+ *
+ * @returns Array of message items in chronological order
+ */
get messages(): MessageItem[] {
return this.items.filter((item): item is MessageItem => item.type === 'message');
}
+ /**
+ * Assistant messages only from this execution.
+ *
+ * Filters to only AI responses, excluding user prompts.
+ *
+ * @returns Array of assistant message items in chronological order
+ *
+ * @example
+ * ```typescript
+ * const result = await thread.run('Explain your reasoning');
+ *
+ * for (const msg of result.assistantMessages) {
+ * console.log(`[${new Date(msg.timestamp).toISOString()}] ${msg.text}`);
+ * }
+ * ```
+ */
get assistantMessages(): MessageItem[] {
return this.messages.filter((m) => m.role === 'assistant');
}
+ /**
+ * Converts the result to a plain JSON-serializable object.
+ *
+ * Useful for logging, caching, or transmitting results.
+ *
+ * @returns A plain object representation of the turn result
+ *
+ * @example
+ * ```typescript
+ * const result = await thread.run('Generate code');
+ *
+ * // Save to file
+ * fs.writeFileSync('result.json', JSON.stringify(result.toJSON(), null, 2));
+ *
+ * // Or use with JSON.stringify directly
+ * console.log(JSON.stringify(result));
+ * ```
+ */
toJSON(): TurnResultData {
return {
finalResponse: this.finalResponse,
@@ -75,6 +303,17 @@ export class TurnResult {
}
}
+/**
+ * Builds a TurnResult from an array of stream events.
+ *
+ * Processes events from a streaming execution to extract the final
+ * response, session ID, and all interaction items.
+ *
+ * @param events - Array of stream events from execution
+ * @returns A constructed TurnResult instance
+ *
+ * @internal
+ */
export function buildTurnResultFromEvents(events: StreamEvent[]): TurnResult {
const items: AnyTurnItem[] = [];
let sessionId = '';
@@ -147,6 +386,17 @@ export function buildTurnResultFromEvents(events: StreamEvent[]): TurnResult {
});
}
+/**
+ * Builds a TurnResult from a JSON CLI response.
+ *
+ * Converts the raw JSON output format from the Droid CLI into a
+ * TurnResult instance.
+ *
+ * @param json - The JSON response from the CLI
+ * @returns A constructed TurnResult instance
+ *
+ * @internal
+ */
export function buildTurnResultFromJson(json: {
result: string;
session_id: string;
diff --git a/src/types/config.ts b/src/types/config.ts
index 8a8be53..32fe66f 100644
--- a/src/types/config.ts
+++ b/src/types/config.ts
@@ -1,13 +1,124 @@
import type { AutonomyLevel, ModelId, ReasoningEffort } from './options';
+/**
+ * Configuration options for the Droid SDK.
+ *
+ * This interface defines the global settings that apply to all operations
+ * performed by a Droid instance. Individual operations can override these
+ * settings using their respective options.
+ *
+ * @example
+ * ```typescript
+ * import { Droid, MODELS } from '@activade/droid-sdk';
+ *
+ * const config: DroidConfig = {
+ * model: MODELS.CLAUDE_SONNET,
+ * autonomyLevel: 'high',
+ * reasoningEffort: 'medium',
+ * cwd: '/path/to/project',
+ * timeout: 300000 // 5 minutes
+ * };
+ *
+ * const droid = new Droid(config);
+ * ```
+ *
+ * @see {@link Droid} for usage context
+ *
+ * @category Configuration
+ */
export interface DroidConfig {
+ /**
+ * Working directory for CLI operations.
+ *
+ * All file paths and commands will be relative to this directory.
+ * Defaults to `process.cwd()` if not specified.
+ *
+ * @default process.cwd()
+ */
cwd?: string;
+
+ /**
+ * The AI model to use for generation.
+ *
+ * Can be a predefined model ID from {@link MODELS} or a custom model string.
+ * Different models have different capabilities, speeds, and costs.
+ *
+ * @example
+ * ```typescript
+ * // Using predefined model constant
+ * { model: MODELS.CLAUDE_SONNET }
+ *
+ * // Using model ID string
+ * { model: 'claude-opus-4-5-20251101' }
+ *
+ * // Custom model
+ * { model: 'custom:my-fine-tuned-model' }
+ * ```
+ */
model?: ModelId | string;
+
+ /**
+ * Level of autonomous decision-making.
+ *
+ * Controls how independently the AI operates:
+ * - `'default'`: Use model's default behavior
+ * - `'low'`: Require confirmation for most actions
+ * - `'medium'`: Allow some autonomous actions
+ * - `'high'`: Maximize autonomous operation
+ *
+ * @default 'default'
+ */
autonomyLevel?: AutonomyLevel;
+
+ /**
+ * Reasoning intensity for complex tasks.
+ *
+ * Controls the depth of reasoning the AI uses:
+ * - `'off'` or `'none'`: Minimal reasoning
+ * - `'low'`: Light reasoning
+ * - `'medium'`: Balanced reasoning
+ * - `'high'`: Deep, thorough reasoning
+ *
+ * Higher values may improve quality but increase latency and cost.
+ */
reasoningEffort?: ReasoningEffort;
+
+ /**
+ * Path to the Droid CLI binary.
+ *
+ * Allows specifying a custom CLI location. If not provided,
+ * the SDK will search standard locations and the system PATH.
+ *
+ * @default 'droid'
+ */
droidPath?: string;
+
+ /**
+ * Maximum execution time in milliseconds.
+ *
+ * Operations that exceed this timeout will be terminated and
+ * throw a {@link TimeoutError}.
+ *
+ * @default 600000 (10 minutes)
+ */
timeout?: number;
}
+/**
+ * Default timeout for Droid operations in milliseconds.
+ *
+ * Set to 10 minutes (600,000ms) to accommodate complex, long-running tasks.
+ *
+ * @category Configuration
+ */
export const DEFAULT_TIMEOUT = 600_000;
+
+/**
+ * Default path/command to invoke the Droid CLI.
+ *
+ * Uses 'droid' which relies on the binary being in the system PATH.
+ * Can be overridden via {@link DroidConfig.droidPath}.
+ *
+ * @category Configuration
+ */
export const DEFAULT_DROID_PATH = 'droid';
diff --git a/src/types/events.ts b/src/types/events.ts
index 7e134d7..5565cef 100644
--- a/src/types/events.ts
+++ b/src/types/events.ts
@@ -1,63 +1,182 @@
+/**
+ * System initialization event.
+ *
+ * Emitted at the start of a streaming execution to indicate the
+ * session configuration and available tools.
+ *
+ * @category Events
+ */
export interface SystemInitEvent {
+ /** Event type identifier */
type: 'system';
+ /** Event subtype */
subtype: 'init';
+ /** Working directory for this session */
cwd: string;
+ /** Unique session identifier */
session_id: string;
+ /** List of available tool names */
tools: string[];
+ /** AI model being used */
model: string;
}
+/**
+ * Message event for user or assistant messages.
+ *
+ * Emitted when a message is sent or received during execution.
+ * Check the `role` property to distinguish between user prompts
+ * and AI responses.
+ *
+ * @category Events
+ */
export interface MessageEvent {
+ /** Event type identifier */
type: 'message';
+ /** Message author role */
role: 'user' | 'assistant';
+ /** Unique message identifier */
id: string;
+ /** Message text content */
text: string;
+ /** Unix timestamp in milliseconds */
timestamp: number;
+ /** Session identifier */
session_id: string;
}
+/**
+ * Tool call event.
+ *
+ * Emitted when the AI invokes a tool during execution.
+ * Contains the tool name and parameters being passed.
+ *
+ * @example
+ * ```typescript
+ * if (isToolCallEvent(event)) {
+ * console.log(`Calling ${event.toolName} with:`, event.parameters);
+ * }
+ * ```
+ *
+ * @category Events
+ */
export interface ToolCallEvent {
+ /** Event type identifier */
type: 'tool_call';
+ /** Unique identifier for this tool call */
id: string;
+ /** ID of the message containing this tool call */
messageId: string;
+ /** Internal tool identifier */
toolId: string;
+ /** Human-readable tool name */
toolName: string;
+ /** Parameters passed to the tool */
parameters: Record;
+ /** Unix timestamp in milliseconds */
timestamp: number;
+ /** Session identifier */
session_id: string;
}
+/**
+ * Tool result event.
+ *
+ * Emitted after a tool call completes with the result or error.
+ *
+ * @example
+ * ```typescript
+ * if (isToolResultEvent(event)) {
+ * if (event.isError) {
+ * console.error(`${event.toolName} failed:`, event.value);
+ * } else {
+ * console.log(`${event.toolName} succeeded:`, event.value.slice(0, 100));
+ * }
+ * }
+ * ```
+ *
+ * @category Events
+ */
export interface ToolResultEvent {
+ /** Event type identifier */
type: 'tool_result';
+ /** Unique identifier for this result */
id: string;
+ /** ID of the message containing the original tool call */
messageId: string;
+ /** Internal tool identifier */
toolId: string;
+ /** Human-readable tool name */
toolName: string;
+ /** Whether the tool execution resulted in an error */
isError: boolean;
+ /** Tool output value or error message */
value: string;
+ /** Unix timestamp in milliseconds */
timestamp: number;
+ /** Session identifier */
session_id: string;
}
+/**
+ * Turn completed event.
+ *
+ * Emitted when execution completes successfully with the final response
+ * and execution statistics.
+ *
+ * @category Events
+ */
export interface TurnCompletedEvent {
+ /** Event type identifier */
type: 'completion';
+ /** Final text response from the AI */
finalText: string;
+ /** Number of conversation turns */
numTurns: number;
+ /** Total execution duration in milliseconds */
durationMs: number;
+ /** Session identifier */
session_id: string;
+ /** Unix timestamp in milliseconds */
timestamp: number;
}
+/**
+ * Turn failed event.
+ *
+ * Emitted when execution fails with error details.
+ *
+ * @category Events
+ */
export interface TurnFailedEvent {
+ /** Event type identifier */
type: 'turn.failed';
+ /** Error information */
error: {
+ /** Human-readable error message */
message: string;
+ /** Error code if available */
code?: string;
};
+ /** Session identifier */
session_id: string;
+ /** Unix timestamp in milliseconds */
timestamp: number;
}
+/**
+ * Union of all stream event types.
+ *
+ * Use the type guard functions to narrow the type:
+ * - {@link isSystemInitEvent}
+ * - {@link isMessageEvent}
+ * - {@link isToolCallEvent}
+ * - {@link isToolResultEvent}
+ * - {@link isTurnCompletedEvent}
+ * - {@link isTurnFailedEvent}
+ *
+ * @category Events
+ */
export type StreamEvent =
| SystemInitEvent
| MessageEvent
@@ -66,26 +185,121 @@ export type StreamEvent =
| TurnCompletedEvent
| TurnFailedEvent;
+/**
+ * Type guard for system initialization events.
+ *
+ * @param event - The event to check
+ * @returns True if the event is a SystemInitEvent
+ *
+ * @example
+ * ```typescript
+ * for await (const event of events) {
+ * if (isSystemInitEvent(event)) {
+ * console.log('Session started:', event.session_id);
+ * console.log('Available tools:', event.tools);
+ * }
+ * }
+ * ```
+ *
+ * @category Events
+ */
export function isSystemInitEvent(event: StreamEvent): event is SystemInitEvent {
return event.type === 'system' && (event as SystemInitEvent).subtype === 'init';
}
+/**
+ * Type guard for message events.
+ *
+ * @param event - The event to check
+ * @returns True if the event is a MessageEvent
+ *
+ * @example
+ * ```typescript
+ * if (isMessageEvent(event)) {
+ * console.log(`[${event.role}] ${event.text}`);
+ * }
+ * ```
+ *
+ * @category Events
+ */
export function isMessageEvent(event: StreamEvent): event is MessageEvent {
return event.type === 'message';
}
+/**
+ * Type guard for tool call events.
+ *
+ * @param event - The event to check
+ * @returns True if the event is a ToolCallEvent
+ *
+ * @example
+ * ```typescript
+ * if (isToolCallEvent(event)) {
+ * console.log(`Calling ${event.toolName}...`);
+ * }
+ * ```
+ *
+ * @category Events
+ */
export function isToolCallEvent(event: StreamEvent): event is ToolCallEvent {
return event.type === 'tool_call';
}
+/**
+ * Type guard for tool result events.
+ *
+ * @param event - The event to check
+ * @returns True if the event is a ToolResultEvent
+ *
+ * @example
+ * ```typescript
+ * if (isToolResultEvent(event)) {
+ * if (event.isError) {
+ * console.error('Tool error:', event.value);
+ * }
+ * }
+ * ```
+ *
+ * @category Events
+ */
export function isToolResultEvent(event: StreamEvent): event is ToolResultEvent {
return event.type === 'tool_result';
}
+/**
+ * Type guard for turn completed events.
+ *
+ * @param event - The event to check
+ * @returns True if the event is a TurnCompletedEvent
+ *
+ * @example
+ * ```typescript
+ * if (isTurnCompletedEvent(event)) {
+ * console.log(`Completed in ${event.durationMs}ms`);
+ * }
+ * ```
+ *
+ * @category Events
+ */
export function isTurnCompletedEvent(event: StreamEvent): event is TurnCompletedEvent {
return event.type === 'completion';
}
+/**
+ * Type guard for turn failed events.
+ *
+ * @param event - The event to check
+ * @returns True if the event is a TurnFailedEvent
+ *
+ * @example
+ * ```typescript
+ * if (isTurnFailedEvent(event)) {
+ * console.error('Execution failed:', event.error.message);
+ * }
+ * ```
+ *
+ * @category Events
+ */
export function isTurnFailedEvent(event: StreamEvent): event is TurnFailedEvent {
return event.type === 'turn.failed';
}
diff --git a/src/types/options.ts b/src/types/options.ts
index b04e60f..f6bfc50 100644
--- a/src/types/options.ts
+++ b/src/types/options.ts
@@ -1,21 +1,62 @@
+/**
+ * Autonomy level for AI decision-making.
+ *
+ * Controls how independently the AI operates during execution:
+ * - `'default'`: Use the model's default behavior
+ * - `'low'`: Require confirmation for most actions
+ * - `'medium'`: Allow some autonomous actions
+ * - `'high'`: Maximize autonomous operation
+ *
+ * @category Configuration
+ */
export type AutonomyLevel = 'default' | 'low' | 'medium' | 'high';
+/**
+ * Reasoning intensity level.
+ *
+ * Controls the depth of reasoning the AI uses during execution:
+ * - `'off'` or `'none'`: Minimal reasoning (fastest)
+ * - `'low'`: Light reasoning
+ * - `'medium'`: Balanced reasoning
+ * - `'high'`: Deep, thorough reasoning (highest quality)
+ *
+ * Higher values may improve quality but increase latency and cost.
+ *
+ * @category Configuration
+ */
export type ReasoningEffort = 'off' | 'none' | 'low' | 'medium' | 'high';
+/**
+ * Output format for CLI responses.
+ *
+ * - `'text'`: Plain text output (default)
+ * - `'json'`: Single JSON object response
+ * - `'stream-json'`: Newline-delimited JSON events
+ * - `'stream-jsonrpc'`: JSON-RPC formatted events
+ *
+ * @category Configuration
+ */
export type OutputFormat = 'text' | 'json' | 'stream-json' | 'stream-jsonrpc';
/**
* Supported file attachment types.
- * - 'image': Image files (png, jpg, gif, webp, etc.) for vision-capable models
- * - 'text': Text files, code, markdown, etc.
- * - 'data': JSON, CSV, or other data files
- * - 'other': Any other file type
+ *
+ * Used with {@link FileAttachment} to indicate the type of attached file:
+ * - `'image'`: Image files (png, jpg, gif, webp, etc.) for vision-capable models
+ * - `'text'`: Text files, code, markdown, etc.
+ * - `'data'`: JSON, CSV, or other data files
+ * - `'other'`: Any other file type
+ *
+ * @category Types
*/
export type FileAttachmentType = 'image' | 'text' | 'data' | 'other';
/**
* A file attachment to include with the prompt.
- * The file will be referenced using the @path syntax that the droid CLI understands.
+ *
+ * Files are referenced using the `@path` syntax that the Droid CLI understands.
+ * Attachments allow you to provide context files, images for vision models,
+ * or data files for processing.
*
* @example
* ```typescript
@@ -23,72 +64,292 @@ export type FileAttachmentType = 'image' | 'text' | 'data' | 'other';
* const result = await thread.run('Describe this screenshot', {
* attachments: [{ path: './screenshot.png', type: 'image' }]
* });
+ * ```
*
- * // Attach multiple files
+ * @example
+ * ```typescript
+ * // Attach multiple files with descriptions
* const result = await thread.run('Review these files', {
* attachments: [
- * { path: './src/main.ts', type: 'text' },
- * { path: './data.json', type: 'data' }
+ * { path: './src/main.ts', type: 'text', description: 'Main entry point' },
+ * { path: './data.json', type: 'data', description: 'Sample data' }
* ]
* });
* ```
+ *
+ * @category Types
*/
export interface FileAttachment {
- /** Path to the file (relative to cwd or absolute) */
+ /**
+ * Path to the file (relative to cwd or absolute).
+ */
path: string;
- /** Type of the file for context */
+
+ /**
+ * Type of the file for context.
+ * Helps the AI understand how to process the attachment.
+ */
type: FileAttachmentType;
- /** Optional description to include with the file reference */
+
+ /**
+ * Optional description to include with the file reference.
+ * Provides additional context about the file's purpose.
+ */
description?: string;
}
+/**
+ * Predefined model identifiers for supported AI models.
+ *
+ * Use these constants to specify the model in configuration:
+ *
+ * @example
+ * ```typescript
+ * import { Droid, MODELS } from '@activade/droid-sdk';
+ *
+ * // Claude models
+ * const droid = new Droid({ model: MODELS.CLAUDE_OPUS });
+ * const droid = new Droid({ model: MODELS.CLAUDE_SONNET });
+ * const droid = new Droid({ model: MODELS.CLAUDE_HAIKU });
+ *
+ * // OpenAI models
+ * const droid = new Droid({ model: MODELS.GPT_5_1_CODEX });
+ *
+ * // Google models
+ * const droid = new Droid({ model: MODELS.GEMINI_3_PRO });
+ * ```
+ *
+ * @category Configuration
+ */
export const MODELS = {
+ /** Claude Opus 4.5 - Most capable Claude model */
CLAUDE_OPUS: 'claude-opus-4-5-20251101',
+ /** Claude Sonnet 4.5 - Balanced performance and speed */
CLAUDE_SONNET: 'claude-sonnet-4-5-20250929',
+ /** Claude Haiku 4.5 - Fast and efficient */
CLAUDE_HAIKU: 'claude-haiku-4-5-20251001',
+ /** GPT-5.1 - OpenAI's base GPT-5.1 model */
GPT_5_1: 'gpt-5.1',
+ /** GPT-5.1 Codex - Optimized for code */
GPT_5_1_CODEX: 'gpt-5.1-codex',
+ /** GPT-5.1 Codex Max - Extended context and capabilities */
GPT_5_1_CODEX_MAX: 'gpt-5.1-codex-max',
+ /** GPT-5.2 - Latest GPT model */
GPT_5_2: 'gpt-5.2',
+ /** Gemini 3 Pro - Google's most capable model */
GEMINI_3_PRO: 'gemini-3-pro-preview',
+ /** Gemini 3 Flash - Fast Gemini variant */
GEMINI_3_FLASH: 'gemini-3-flash-preview',
+ /** Droid Core - Factory's open-source GLM-based model */
DROID_CORE: 'glm-4.6',
} as const;
+/**
+ * Union type of all predefined model IDs.
+ *
+ * @category Types
+ */
export type ModelId = (typeof MODELS)[keyof typeof MODELS];
+/**
+ * Thread-level execution options.
+ *
+ * These options configure a thread's behavior for all prompts executed
+ * within it. They can be specified when creating a thread via
+ * {@link Droid.startThread} or {@link Droid.resumeThread}.
+ *
+ * @example
+ * ```typescript
+ * const thread = droid.startThread({
+ * model: MODELS.CLAUDE_SONNET,
+ * autonomyLevel: 'high',
+ * cwd: '/path/to/project',
+ * enabledTools: ['file_read', 'file_write', 'shell_exec'],
+ * disabledTools: ['web_search']
+ * });
+ * ```
+ *
+ * @category Configuration
+ */
export interface ThreadOptions {
+ /**
+ * Working directory for this thread.
+ * Overrides the Droid instance's cwd setting.
+ */
cwd?: string;
+
+ /**
+ * AI model to use for this thread.
+ * Overrides the Droid instance's model setting.
+ */
model?: ModelId | string;
+
+ /**
+ * Autonomy level for this thread.
+ * @see {@link AutonomyLevel}
+ */
autonomyLevel?: AutonomyLevel;
+
+ /**
+ * Reasoning effort for this thread.
+ * @see {@link ReasoningEffort}
+ */
reasoningEffort?: ReasoningEffort;
+
+ /**
+ * Whether to use specification mode.
+ * When true, generates a spec before implementing.
+ */
useSpec?: boolean;
+
+ /**
+ * Model to use for specification generation.
+ * Only applies when `useSpec` is true.
+ */
specModel?: string;
+
+ /**
+ * Reasoning effort for specification generation.
+ * Only applies when `useSpec` is true.
+ */
specReasoningEffort?: ReasoningEffort;
+
+ /**
+ * Whitelist of tools the AI is allowed to use.
+ * If specified, only these tools will be available.
+ *
+ * @example
+ * ```typescript
+ * { enabledTools: ['file_read', 'file_write'] }
+ * ```
+ */
enabledTools?: string[];
+
+ /**
+ * Blacklist of tools the AI is not allowed to use.
+ * These tools will be excluded even if in enabledTools.
+ *
+ * @example
+ * ```typescript
+ * { disabledTools: ['shell_exec', 'web_request'] }
+ * ```
+ */
disabledTools?: string[];
+
+ /**
+ * Skip permission checks (unsafe).
+ *
+ * @warning This bypasses safety checks and should only be used
+ * in trusted environments where you fully control the prompts.
+ */
skipPermissionsUnsafe?: boolean;
}
+/**
+ * JSON Schema definition for structured output.
+ *
+ * Used with {@link RunOptions.outputSchema} to define the expected
+ * structure of AI responses. Follows the JSON Schema specification.
+ *
+ * @example
+ * ```typescript
+ * const schema: JsonSchema = {
+ * type: 'object',
+ * properties: {
+ * name: { type: 'string' },
+ * age: { type: 'number' },
+ * email: { type: 'string', format: 'email' }
+ * },
+ * required: ['name', 'email']
+ * };
+ * ```
+ *
+ * @category Types
+ */
export interface JsonSchema {
+ /** The type of the value */
type?: string;
+ /** Object properties (for type: 'object') */
properties?: Record;
+ /** Required property names */
required?: string[];
+ /** Array item schema (for type: 'array') */
items?: JsonSchema;
+ /** Allowed values (for enums) */
enum?: unknown[];
+ /** Whether additional properties are allowed */
additionalProperties?: boolean | JsonSchema;
+ /** Additional schema properties */
[key: string]: unknown;
}
+/**
+ * Run-level execution options.
+ *
+ * These options configure a single prompt execution within a thread.
+ * They extend {@link ThreadOptions} and add prompt-specific settings.
+ *
+ * @example
+ * ```typescript
+ * const result = await thread.run('Generate a user object', {
+ * outputSchema: {
+ * type: 'object',
+ * properties: {
+ * name: { type: 'string' },
+ * email: { type: 'string' }
+ * }
+ * },
+ * attachments: [
+ * { path: './template.json', type: 'data' }
+ * ]
+ * });
+ * ```
+ *
+ * @category Configuration
+ */
export interface RunOptions extends Partial {
+ /**
+ * JSON Schema for structured output.
+ * When provided, the AI will format its response to match this schema.
+ */
outputSchema?: JsonSchema;
+
+ /**
+ * Path to a file containing the prompt.
+ * Alternative to passing the prompt as a string.
+ */
promptFile?: string;
+
+ /**
+ * File attachments to include with the prompt.
+ * @see {@link FileAttachment}
+ */
attachments?: FileAttachment[];
}
+/**
+ * Options for one-shot execution via {@link Droid.exec}.
+ *
+ * Extends {@link RunOptions} with the ability to specify a session ID
+ * for continuing a previous conversation.
+ *
+ * @example
+ * ```typescript
+ * // One-shot with session continuation
+ * const result = await droid.exec('Continue our previous work', {
+ * sessionId: 'session_abc123...'
+ * });
+ * ```
+ *
+ * @category Configuration
+ */
export interface ExecOptions extends RunOptions {
+ /**
+ * Session ID to continue a previous conversation.
+ * If provided, the execution will have access to previous context.
+ */
sessionId?: string;
}
diff --git a/src/types/turn.ts b/src/types/turn.ts
index 94409f5..3b36117 100644
--- a/src/types/turn.ts
+++ b/src/types/turn.ts
@@ -1,53 +1,186 @@
+/**
+ * Base interface for all turn items.
+ *
+ * Turn items represent discrete events that occurred during an
+ * execution turn, such as messages, tool calls, and tool results.
+ *
+ * @category Types
+ */
export interface TurnItem {
+ /** Discriminator for the item type */
type: 'message' | 'tool_call' | 'tool_result';
}
+/**
+ * A message item from an execution turn.
+ *
+ * Represents a message exchanged during the conversation, either
+ * from the user (prompt) or assistant (AI response).
+ *
+ * @example
+ * ```typescript
+ * for (const msg of result.messages) {
+ * console.log(`[${msg.role}] ${msg.text}`);
+ * }
+ * ```
+ *
+ * @category Types
+ */
export interface MessageItem extends TurnItem {
+ /** Item type discriminator */
type: 'message';
+ /** Message author: 'user' for prompts, 'assistant' for AI responses */
role: 'user' | 'assistant';
+ /** Unique message identifier */
id: string;
+ /** Message text content */
text: string;
+ /** Unix timestamp in milliseconds when the message was created */
timestamp: number;
}
+/**
+ * A tool call item from an execution turn.
+ *
+ * Represents an AI request to execute a tool with specific parameters.
+ * Each tool call will have a corresponding {@link ToolResultItem}.
+ *
+ * @example
+ * ```typescript
+ * for (const call of result.toolCalls) {
+ * console.log(`Tool: ${call.toolName}`);
+ * console.log(`Params: ${JSON.stringify(call.parameters, null, 2)}`);
+ * }
+ * ```
+ *
+ * @category Types
+ */
export interface ToolCallItem extends TurnItem {
+ /** Item type discriminator */
type: 'tool_call';
+ /** Unique identifier for this tool call */
id: string;
+ /** ID of the message that contains this tool call */
messageId: string;
+ /** Internal tool identifier */
toolId: string;
+ /** Human-readable tool name (e.g., 'file_read', 'shell_exec') */
toolName: string;
+ /** Parameters passed to the tool */
parameters: Record;
+ /** Unix timestamp in milliseconds */
timestamp: number;
}
+/**
+ * A tool result item from an execution turn.
+ *
+ * Represents the output from a tool execution, which may be a
+ * successful result or an error.
+ *
+ * @example
+ * ```typescript
+ * for (const result of result.toolResults) {
+ * if (result.isError) {
+ * console.error(`${result.toolName} failed: ${result.value}`);
+ * } else {
+ * console.log(`${result.toolName}: ${result.value.slice(0, 100)}...`);
+ * }
+ * }
+ * ```
+ *
+ * @category Types
+ */
export interface ToolResultItem extends TurnItem {
+ /** Item type discriminator */
type: 'tool_result';
+ /** Unique identifier for this result */
id: string;
+ /** ID of the message containing the original tool call */
messageId: string;
+ /** Internal tool identifier */
toolId: string;
+ /** Human-readable tool name */
toolName: string;
+ /** Whether the tool execution resulted in an error */
isError: boolean;
+ /** Tool output value or error message */
value: string;
+ /** Unix timestamp in milliseconds */
timestamp: number;
}
+/**
+ * Union type of all turn item types.
+ *
+ * Use the `type` property to narrow the type:
+ *
+ * @example
+ * ```typescript
+ * for (const item of result.items) {
+ * switch (item.type) {
+ * case 'message':
+ * console.log(`Message: ${item.text}`);
+ * break;
+ * case 'tool_call':
+ * console.log(`Tool call: ${item.toolName}`);
+ * break;
+ * case 'tool_result':
+ * console.log(`Tool result: ${item.value}`);
+ * break;
+ * }
+ * }
+ * ```
+ *
+ * @category Types
+ */
export type AnyTurnItem = MessageItem | ToolCallItem | ToolResultItem;
+/**
+ * Data structure for TurnResult construction.
+ *
+ * Contains all the data needed to construct a {@link TurnResult} instance.
+ * This is typically created internally from CLI output.
+ *
+ * @category Types
+ */
export interface TurnResultData {
+ /** The final text response from the AI */
finalResponse: string;
+ /** All items from this execution turn */
items: AnyTurnItem[];
+ /** Unique session identifier */
sessionId: string;
+ /** Total execution duration in milliseconds */
durationMs: number;
+ /** Number of conversation turns */
numTurns: number;
+ /** Whether the execution resulted in an error */
isError: boolean;
}
+/**
+ * JSON response structure from the Droid CLI.
+ *
+ * This represents the raw JSON output format when using the `-o json`
+ * output format with the CLI.
+ *
+ * @internal
+ * @category Types
+ */
export interface JsonResult {
+ /** Response type */
type: 'result';
+ /** Result subtype indicating success or error */
subtype: 'success' | 'error';
+ /** Whether the execution resulted in an error */
is_error: boolean;
+ /** Execution duration in milliseconds */
duration_ms: number;
+ /** Number of conversation turns */
num_turns: number;
+ /** Final response text */
result: string;
+ /** Session identifier */
session_id: string;
}