@factory/droid-sdk 0.1.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +400 -76
- package/dist/index.cjs +2154 -1458
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8065 -4332
- package/dist/index.d.ts +8065 -4332
- package/dist/index.js +2078 -1456
- package/dist/index.js.map +1 -1
- package/docs/sdk-usage-guide.md +678 -0
- package/examples/.gitkeep +1 -0
- package/examples/abort-session-stream.ts +31 -0
- package/examples/droid-dev-structured-output.ts +43 -0
- package/examples/fork-session.ts +61 -0
- package/examples/hook-execution.ts +62 -0
- package/examples/init-metadata.ts +19 -0
- package/examples/interrupt-session.ts +32 -0
- package/examples/list-sessions.ts +19 -0
- package/examples/multi-turn-session.ts +35 -0
- package/examples/permission-handler.ts +40 -0
- package/examples/readme-structured-output.ts +25 -0
- package/examples/result-metadata.ts +62 -0
- package/examples/run.ts +38 -0
- package/examples/sdk-mcp-tool.ts +42 -0
- package/examples/session-stream.ts +54 -0
- package/examples/spec-mode-new-session.ts +60 -0
- package/examples/spec-mode-same-session.ts +71 -0
- package/examples/structured-output.ts +59 -0
- package/examples/test-compact.ts +62 -0
- package/examples/test-rewind.ts +87 -0
- package/examples/tool-controls.ts +36 -0
- package/package.json +26 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @factory/droid-sdk
|
|
2
2
|
|
|
3
|
-
TypeScript SDK for the [Factory](https://factory.ai) Droid CLI. Provides a high-level API for interacting with Droid as a subprocess, with streaming
|
|
3
|
+
TypeScript SDK for the [Factory](https://factory.ai) Droid CLI. Provides a high-level API for interacting with Droid as a subprocess, with one-shot prompts, streaming messages, multi-turn sessions, structured output, SDK-backed MCP tools, spec mode, tool controls, initialization metadata, session forking, session discovery, and tool permission handling.
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
@@ -15,80 +15,338 @@ npm install @factory/droid-sdk
|
|
|
15
15
|
|
|
16
16
|
## Quick Start
|
|
17
17
|
|
|
18
|
-
Send a one-shot prompt and
|
|
18
|
+
Send a one-shot prompt and get the aggregated result:
|
|
19
19
|
|
|
20
20
|
```ts
|
|
21
|
-
import {
|
|
21
|
+
import { run } from '@factory/droid-sdk';
|
|
22
22
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
cwd: "/my/project",
|
|
23
|
+
const result = await run('What files are in the current directory?', {
|
|
24
|
+
cwd: '/my/project',
|
|
26
25
|
});
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
console.log(result.text);
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Create a session when you want to stream one or more turns:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { DroidMessageType, createSession } from '@factory/droid-sdk';
|
|
34
|
+
|
|
35
|
+
const session = await createSession({ cwd: '/my/project' });
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
for await (const msg of session.stream(
|
|
39
|
+
'What files are in the current directory?'
|
|
40
|
+
)) {
|
|
41
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
42
|
+
process.stdout.write(msg.text);
|
|
43
|
+
}
|
|
44
|
+
if (msg.type === DroidMessageType.Result) {
|
|
45
|
+
console.log('\nDone!');
|
|
46
|
+
}
|
|
34
47
|
}
|
|
48
|
+
} finally {
|
|
49
|
+
await session.close();
|
|
35
50
|
}
|
|
36
51
|
```
|
|
37
52
|
|
|
53
|
+
## Structured Output
|
|
54
|
+
|
|
55
|
+
Request a JSON object that matches a JSON Schema:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { OutputFormatType, run } from '@factory/droid-sdk';
|
|
59
|
+
|
|
60
|
+
const result = await run('Pick a favorite number between 1 and 42.', {
|
|
61
|
+
cwd: '/my/project',
|
|
62
|
+
outputFormat: {
|
|
63
|
+
type: OutputFormatType.JsonSchema,
|
|
64
|
+
schema: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
favoriteNumber: {
|
|
68
|
+
type: 'number',
|
|
69
|
+
minimum: 1,
|
|
70
|
+
maximum: 42,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
required: ['favoriteNumber'],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
console.log(result.structuredOutput?.favoriteNumber);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Structured output is available on `run()` and `session.stream(prompt, options)` through the `outputFormat` message option. `run()` parses the final object into `result.structuredOutput`; streaming callers can read and parse the final assistant message themselves.
|
|
82
|
+
|
|
38
83
|
## Multi-Turn Sessions
|
|
39
84
|
|
|
40
85
|
Use `createSession()` for persistent conversations with multiple turns:
|
|
41
86
|
|
|
42
87
|
```ts
|
|
43
|
-
import { createSession } from
|
|
88
|
+
import { createSession, DroidMessageType } from '@factory/droid-sdk';
|
|
44
89
|
|
|
45
|
-
const session = await createSession({ cwd:
|
|
90
|
+
const session = await createSession({ cwd: '/my/project' });
|
|
91
|
+
console.log(session.sessionId);
|
|
46
92
|
|
|
47
93
|
// Streaming turn
|
|
48
|
-
for await (const msg of session.stream(
|
|
49
|
-
if (msg.type ===
|
|
94
|
+
for await (const msg of session.stream('List all TypeScript files')) {
|
|
95
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
50
96
|
process.stdout.write(msg.text);
|
|
51
97
|
}
|
|
52
98
|
}
|
|
53
99
|
|
|
54
|
-
//
|
|
55
|
-
const
|
|
56
|
-
|
|
100
|
+
// Later turns use the same streaming API
|
|
101
|
+
for await (const msg of session.stream('Summarize the project')) {
|
|
102
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
103
|
+
process.stdout.write(msg.text);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
57
106
|
|
|
58
107
|
await session.close();
|
|
59
108
|
```
|
|
60
109
|
|
|
61
|
-
|
|
110
|
+
Use `session.sessionId` to persist the session ID, then resume it later:
|
|
62
111
|
|
|
63
112
|
```ts
|
|
64
|
-
import { resumeSession } from
|
|
113
|
+
import { resumeSession } from '@factory/droid-sdk';
|
|
65
114
|
|
|
66
|
-
const session = await resumeSession(
|
|
67
|
-
const
|
|
68
|
-
|
|
115
|
+
const session = await resumeSession(savedSessionId);
|
|
116
|
+
for await (const msg of session.stream('Continue where we left off')) {
|
|
117
|
+
// Handle streamed DroidMessage events.
|
|
118
|
+
}
|
|
119
|
+
await session.close();
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The returned `DroidSession` also exposes `session.initResult`, which contains the raw `initialize_session` or `load_session` result returned by the JSON-RPC server.
|
|
123
|
+
|
|
124
|
+
## SDK-backed MCP Tools
|
|
125
|
+
|
|
126
|
+
Expose in-process TypeScript tools to Droid through MCP:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
import { z } from 'zod';
|
|
130
|
+
|
|
131
|
+
import {
|
|
132
|
+
DroidMessageType,
|
|
133
|
+
ToolConfirmationOutcome,
|
|
134
|
+
createSession,
|
|
135
|
+
createSdkMcpServer,
|
|
136
|
+
tool,
|
|
137
|
+
} from '@factory/droid-sdk';
|
|
138
|
+
|
|
139
|
+
const sdkTools = createSdkMcpServer({
|
|
140
|
+
name: 'sdk-tools',
|
|
141
|
+
tools: [
|
|
142
|
+
tool(
|
|
143
|
+
'favorite_number',
|
|
144
|
+
'Returns a favorite number for a person',
|
|
145
|
+
{ name: z.string() },
|
|
146
|
+
({ name }) => `${name}'s favorite number is 42.`
|
|
147
|
+
),
|
|
148
|
+
],
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const session = await createSession({
|
|
152
|
+
cwd: '/my/project',
|
|
153
|
+
mcpServers: [sdkTools],
|
|
154
|
+
permissionHandler: () => ToolConfirmationOutcome.ProceedOnce,
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
for await (const msg of session.stream(
|
|
158
|
+
'Use the favorite_number tool for Ada and tell me the answer.'
|
|
159
|
+
)) {
|
|
160
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
161
|
+
process.stdout.write(msg.text);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
await session.close();
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`createSdkMcpServer()` is managed by the SDK session lifecycle. Use `tool()` with a Zod object shape for typed tool input validation.
|
|
169
|
+
|
|
170
|
+
## Initialization Metadata
|
|
171
|
+
|
|
172
|
+
Inspect the raw initialization metadata from `createSession()` and `resumeSession()`:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { createSession, resumeSession } from '@factory/droid-sdk';
|
|
176
|
+
|
|
177
|
+
const session = await createSession({ cwd: '/my/project' });
|
|
178
|
+
console.log(session.sessionId);
|
|
179
|
+
console.log(session.initResult.settings.modelId);
|
|
180
|
+
|
|
181
|
+
const resumed = await resumeSession(session.sessionId);
|
|
182
|
+
console.log(resumed.initResult.cwd);
|
|
183
|
+
|
|
184
|
+
await resumed.close();
|
|
185
|
+
await session.close();
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Spec Mode
|
|
189
|
+
|
|
190
|
+
Start a session directly in spec mode, or enter spec mode later on an existing session:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import {
|
|
194
|
+
DroidMessageType,
|
|
195
|
+
createSession,
|
|
196
|
+
DroidInteractionMode,
|
|
197
|
+
ReasoningEffort,
|
|
198
|
+
} from '@factory/droid-sdk';
|
|
199
|
+
|
|
200
|
+
const session = await createSession({
|
|
201
|
+
cwd: '/my/project',
|
|
202
|
+
interactionMode: DroidInteractionMode.Spec,
|
|
203
|
+
specModeReasoningEffort: ReasoningEffort.High,
|
|
204
|
+
specModeModelId: 'claude-sonnet-4-20250514',
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
for await (const msg of session.stream(
|
|
208
|
+
'Draft a plan for adding integration tests'
|
|
209
|
+
)) {
|
|
210
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
211
|
+
process.stdout.write(msg.text);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
await session.enterSpecMode({
|
|
216
|
+
specModeReasoningEffort: ReasoningEffort.High,
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
await session.close();
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
When handling spec-mode approval, you can approve implementation in the same session with `ToolConfirmationOutcome.ProceedOnce`, or hand off to a fresh session with `ToolConfirmationOutcome.ProceedNewSessionHigh`.
|
|
223
|
+
|
|
224
|
+
## Tool Controls
|
|
225
|
+
|
|
226
|
+
Control which exec tools are available at session start, inspect the current tool catalog, and update tool overrides later:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import { createSession } from '@factory/droid-sdk';
|
|
230
|
+
|
|
231
|
+
const session = await createSession({
|
|
232
|
+
cwd: '/my/project',
|
|
233
|
+
enabledToolIds: ['Read'],
|
|
234
|
+
disabledToolIds: ['Execute'],
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const { tools } = await session.listTools();
|
|
238
|
+
console.log(
|
|
239
|
+
tools.map((tool) => ({
|
|
240
|
+
id: tool.llmId,
|
|
241
|
+
allowed: tool.currentlyAllowed,
|
|
242
|
+
}))
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
await session.updateSettings({
|
|
246
|
+
disabledToolIds: ['Read', 'Execute'],
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
await session.close();
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Forking Sessions
|
|
253
|
+
|
|
254
|
+
Fork the current server-side session and continue from the new session ID:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
import {
|
|
258
|
+
DroidMessageType,
|
|
259
|
+
createSession,
|
|
260
|
+
resumeSession,
|
|
261
|
+
} from '@factory/droid-sdk';
|
|
262
|
+
|
|
263
|
+
const session = await createSession({ cwd: '/my/project' });
|
|
264
|
+
|
|
265
|
+
for await (const _msg of session.stream(
|
|
266
|
+
'Remember this phrase: mango sunrise'
|
|
267
|
+
)) {
|
|
268
|
+
// Consume the turn.
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const { newSessionId } = await session.forkSession();
|
|
272
|
+
const fork = await resumeSession(newSessionId);
|
|
273
|
+
|
|
274
|
+
for await (const msg of fork.stream('What phrase did I ask you to remember?')) {
|
|
275
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
276
|
+
process.stdout.write(msg.text);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
await fork.close();
|
|
69
281
|
await session.close();
|
|
70
282
|
```
|
|
71
283
|
|
|
284
|
+
## Listing Sessions
|
|
285
|
+
|
|
286
|
+
Discover droid sessions saved on disk (mirrors the CLI's `/sessions` command). Reads `~/.factory/sessions/` directly — no droid process is spawned, so this works even when no session is running:
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
import { listSessions } from '@factory/droid-sdk';
|
|
290
|
+
|
|
291
|
+
// Sessions for the current project (cwd defaults to process.cwd())
|
|
292
|
+
const current = await listSessions();
|
|
293
|
+
|
|
294
|
+
// 10 most recent sessions in the current project
|
|
295
|
+
const recent = await listSessions({ numSessions: 10 });
|
|
296
|
+
|
|
297
|
+
// Every session on disk, most recent first
|
|
298
|
+
const all = await listSessions({ fetchOutsideCWD: true });
|
|
299
|
+
|
|
300
|
+
// 10 most recent sessions across all projects
|
|
301
|
+
const recentAcrossProjects = await listSessions({
|
|
302
|
+
fetchOutsideCWD: true,
|
|
303
|
+
numSessions: 10,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Sessions for a specific other project
|
|
307
|
+
const other = await listSessions({ cwd: '/Users/me/other-repo' });
|
|
308
|
+
|
|
309
|
+
for (const s of current) {
|
|
310
|
+
console.log(`[${s.id}] ${s.title} (${s.messageCount} msgs)`);
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Each `SessionMetadata` record includes `id`, `title`, `sessionTitle`, `owner`, `messageCount`, `modifiedTime`, `createdTime`, `isFavorite`, `cwd`, `decompSessionType`, and `decompMissionId`. Archived sessions (those with an `archivedAt` in their settings file) are excluded automatically. Results are sorted by `modifiedTime` descending.
|
|
315
|
+
|
|
316
|
+
`ListSessionsOptions`:
|
|
317
|
+
|
|
318
|
+
- **`cwd`** — working directory to scope the listing to (default `process.cwd()`). Ignored when `fetchOutsideCWD` is `true`.
|
|
319
|
+
- **`fetchOutsideCWD`** — return sessions from every working directory on disk (default `false`)
|
|
320
|
+
- **`numSessions`** — cap on total sessions returned
|
|
321
|
+
- **`sessionsDir`** — override the sessions root (default `~/.factory/sessions/`)
|
|
322
|
+
|
|
72
323
|
## Permission Handling
|
|
73
324
|
|
|
74
325
|
Handle tool confirmation requests with a custom permission handler:
|
|
75
326
|
|
|
76
327
|
```ts
|
|
77
|
-
import {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
328
|
+
import {
|
|
329
|
+
DroidMessageType,
|
|
330
|
+
createSession,
|
|
331
|
+
ToolConfirmationOutcome,
|
|
332
|
+
} from '@factory/droid-sdk';
|
|
333
|
+
|
|
334
|
+
const session = await createSession({
|
|
335
|
+
cwd: '/my/project',
|
|
82
336
|
permissionHandler(params) {
|
|
83
|
-
console.log(
|
|
337
|
+
console.log('Tool permission requested:', params);
|
|
84
338
|
return ToolConfirmationOutcome.ProceedOnce;
|
|
85
339
|
},
|
|
86
340
|
});
|
|
87
341
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
342
|
+
try {
|
|
343
|
+
for await (const msg of session.stream('Create a hello.txt file')) {
|
|
344
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
345
|
+
process.stdout.write(msg.text);
|
|
346
|
+
}
|
|
91
347
|
}
|
|
348
|
+
} finally {
|
|
349
|
+
await session.close();
|
|
92
350
|
}
|
|
93
351
|
```
|
|
94
352
|
|
|
@@ -96,93 +354,159 @@ for await (const msg of stream) {
|
|
|
96
354
|
|
|
97
355
|
### Top-Level Functions
|
|
98
356
|
|
|
99
|
-
| Function
|
|
100
|
-
|
|
101
|
-
| `
|
|
102
|
-
| `createSession(options?)`
|
|
103
|
-
| `resumeSession(id, options?)` | Resume an existing session → `DroidSession`
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
Returns an async generator that yields `DroidMessage` events. The returned `DroidQuery` object also exposes:
|
|
108
|
-
|
|
109
|
-
- **`interrupt()`** — gracefully interrupt the agent's current turn
|
|
110
|
-
- **`abort()`** — forcefully kill the subprocess
|
|
111
|
-
- **`sessionId`** — the session ID (available after initialization)
|
|
357
|
+
| Function | Description |
|
|
358
|
+
| ----------------------------- | ---------------------------------------------------------------- |
|
|
359
|
+
| `run(prompt, options?)` | One-shot prompt → aggregated `DroidResult` |
|
|
360
|
+
| `createSession(options?)` | Create a new multi-turn session → `DroidSession` |
|
|
361
|
+
| `resumeSession(id, options?)` | Resume an existing session → `DroidSession` |
|
|
362
|
+
| `listSessions(options?)` | List droid sessions saved on disk → `Promise<SessionMetadata[]>` |
|
|
363
|
+
| `createSdkMcpServer(options)` | Create an SDK-managed MCP server for in-process tools |
|
|
364
|
+
| `tool(...)` | Define a typed SDK-backed MCP tool |
|
|
112
365
|
|
|
113
366
|
### `DroidSession`
|
|
114
367
|
|
|
115
368
|
Returned by `createSession()` and `resumeSession()`. Key methods:
|
|
116
369
|
|
|
117
|
-
- **`stream(
|
|
118
|
-
- **`send(text, options?)`** — send a message, returns aggregated `DroidResult`
|
|
370
|
+
- **`stream(prompt, options?)`** — stream `DroidMessage` events for one turn
|
|
119
371
|
- **`interrupt()`** — interrupt the current turn
|
|
120
372
|
- **`close()`** — close the session and release resources
|
|
121
373
|
- **`updateSettings(params)`** — update model, autonomy level, etc.
|
|
122
|
-
- **`
|
|
374
|
+
- **`enterSpecMode(params?)`** — switch the current session into spec mode
|
|
375
|
+
- **`forkSession()`** — create a forked server-side session and return its new session ID
|
|
376
|
+
- **`compactSession(params?)`** — compact session history and return the new session ID
|
|
377
|
+
- **`getContextStats()`** — read current context window utilization
|
|
378
|
+
- **`addMcpServer(params)`** / **`removeMcpServer(params)`** / **`toggleMcpServer(params)`** — manage MCP servers
|
|
379
|
+
- **`listMcpServers()`** / **`listMcpTools()`** / **`authenticateMcpServer(params)`** — inspect and authenticate MCP servers
|
|
380
|
+
- **`listTools(params?)`** — inspect the exec tool catalog and current allow/deny state
|
|
381
|
+
- **`renameSession(params)`** — rename the current session
|
|
382
|
+
- **`getRewindInfo(params)`** / **`executeRewind(params)`** — inspect and execute file rewind operations
|
|
123
383
|
- **`sessionId`** — the session ID
|
|
384
|
+
- **`initResult`** — cached `initialize_session` or `load_session` result
|
|
124
385
|
|
|
125
386
|
### `DroidResult`
|
|
126
387
|
|
|
127
|
-
Returned by `
|
|
388
|
+
Returned by `run()`:
|
|
128
389
|
|
|
390
|
+
- **`sessionId`** — session that produced the result
|
|
129
391
|
- **`text`** — concatenated assistant response text
|
|
130
392
|
- **`messages`** — all `DroidMessage` objects from the turn
|
|
131
393
|
- **`tokenUsage`** — final token usage, or `null`
|
|
394
|
+
- **`durationMs`** — wall-clock time spent consuming the turn
|
|
395
|
+
- **`turnCount`** — number of completed turns observed while consuming the stream
|
|
396
|
+
- **`error`** — first Droid error event from the turn, or `null`
|
|
397
|
+
- **`structuredOutput`** — parsed structured JSON object, or `null`
|
|
398
|
+
- **`success`** — `true` when no Droid error event was emitted
|
|
132
399
|
|
|
133
400
|
### `DroidMessage` Types
|
|
134
401
|
|
|
135
402
|
All messages have a discriminated `type` field:
|
|
136
403
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
|
146
|
-
|
|
|
147
|
-
| `
|
|
148
|
-
| `
|
|
404
|
+
```ts
|
|
405
|
+
import { DroidMessageType } from '@factory/droid-sdk';
|
|
406
|
+
|
|
407
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
408
|
+
process.stdout.write(msg.text);
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
| Type | Description |
|
|
413
|
+
| -------------------------- | ----------------------------------------------- |
|
|
414
|
+
| `assistant` | Complete assistant message |
|
|
415
|
+
| `user` | Complete user message |
|
|
416
|
+
| `assistant_text_delta` | Streaming text token from the assistant |
|
|
417
|
+
| `assistant_text_complete` | End of an assistant text block |
|
|
418
|
+
| `thinking_text_delta` | Streaming reasoning/thinking token |
|
|
419
|
+
| `thinking_text_complete` | End of a thinking block |
|
|
420
|
+
| `tool_call` | Tool invocation by the assistant |
|
|
421
|
+
| `tool_call_delta` | Streaming tool call input |
|
|
422
|
+
| `tool_result` | Result from a tool execution |
|
|
423
|
+
| `tool_progress` | Progress update during tool execution |
|
|
424
|
+
| `hook` | File hook execution event (started or finished) |
|
|
425
|
+
| `working_state_changed` | Agent working state transition |
|
|
426
|
+
| `token_usage_update` | Updated token usage counters |
|
|
427
|
+
| `result` | End-of-turn sentinel with aggregated metadata |
|
|
428
|
+
| `session_title_updated` | Session title changed |
|
|
429
|
+
| `settings_updated` | Session settings changed |
|
|
430
|
+
| `permission_resolved` | Tool permission request resolved |
|
|
431
|
+
| `mcp_status_changed` | MCP server status changed |
|
|
432
|
+
| `mcp_auth_required` | MCP authentication required |
|
|
433
|
+
| `mcp_auth_completed` | MCP authentication completed |
|
|
434
|
+
| `error` | Error event from the process |
|
|
435
|
+
| `mission_state_changed` | Mission state changed |
|
|
436
|
+
| `mission_features_changed` | Mission features changed |
|
|
437
|
+
| `mission_progress_entry` | Mission progress log changed |
|
|
438
|
+
| `mission_heartbeat` | Mission heartbeat |
|
|
439
|
+
| `mission_worker_started` | Mission worker started |
|
|
440
|
+
| `mission_worker_completed` | Mission worker completed |
|
|
149
441
|
|
|
150
442
|
### Options
|
|
151
443
|
|
|
152
|
-
`
|
|
444
|
+
Session creation options used by `run()` and `createSession()` include:
|
|
153
445
|
|
|
154
|
-
- **`prompt`** — the user prompt (query only)
|
|
155
446
|
- **`cwd`** — working directory for the session
|
|
447
|
+
- **`execPath`** — path to `droid` executable (default: `"droid"`)
|
|
448
|
+
- **`execArgs`** — extra CLI arguments for the spawned droid process
|
|
449
|
+
- **`env`** — environment variables for the spawned process
|
|
450
|
+
- **`transport`** — provide a custom transport instead of spawning a process
|
|
156
451
|
- **`modelId`** — LLM model identifier
|
|
157
452
|
- **`autonomyLevel`** — `AutonomyLevel` enum value
|
|
453
|
+
- **`interactionMode`** — `DroidInteractionMode` enum value
|
|
158
454
|
- **`reasoningEffort`** — `ReasoningEffort` enum value
|
|
455
|
+
- **`specModeModelId`** — override model used in spec mode
|
|
456
|
+
- **`specModeReasoningEffort`** — override reasoning level used in spec mode
|
|
457
|
+
- **`mcpServers`** — initial MCP server configurations, including SDK-backed MCP servers from `createSdkMcpServer()`
|
|
458
|
+
- **`enabledToolIds`** — explicit exec tool allowlist
|
|
459
|
+
- **`disabledToolIds`** — explicit exec tool denylist
|
|
159
460
|
- **`permissionHandler`** — callback for tool confirmations
|
|
160
461
|
- **`askUserHandler`** — callback for interactive questions
|
|
161
|
-
- **`
|
|
162
|
-
|
|
462
|
+
- **`abortSignal`** — standard `AbortSignal` for cancellation
|
|
463
|
+
|
|
464
|
+
`resumeSession()` accepts the process, transport, handler, `mcpServers`, and `abortSignal` options needed to reconnect to an existing session, but does not accept new-session-only options such as `modelId` or `interactionMode`. `cwd` is intentionally not accepted on resume: the persisted session's working directory is always used. To run in a different directory, create a new session or fork the existing one.
|
|
465
|
+
|
|
466
|
+
Message APIs (`run()` and `session.stream()`) also accept:
|
|
467
|
+
|
|
468
|
+
- **`images`** — base64 image attachments
|
|
469
|
+
- **`files`** — document/file attachments
|
|
470
|
+
- **`outputFormat`** — structured output request, currently `OutputFormatType.JsonSchema`
|
|
471
|
+
- **`abortSignal`** — standard `AbortSignal` for turn cancellation
|
|
163
472
|
|
|
164
473
|
### `DroidClient`
|
|
165
474
|
|
|
166
|
-
Low-level JSON-RPC client for advanced use. Provides typed methods for
|
|
475
|
+
Low-level JSON-RPC client for advanced use. Provides typed methods for the underlying protocol operations, including `listTools()`, `renameSession()`, `getRewindInfo()`, and `executeRewind()`. Most users should prefer `run()` and `createSession()`.
|
|
167
476
|
|
|
168
477
|
### Error Types
|
|
169
478
|
|
|
170
|
-
| Error
|
|
171
|
-
|
|
172
|
-
| `ConnectionError`
|
|
173
|
-
| `ProtocolError`
|
|
174
|
-
| `SessionError`
|
|
175
|
-
| `SessionNotFoundError` | Session ID not found
|
|
176
|
-
| `TimeoutError`
|
|
177
|
-
| `ProcessExitError`
|
|
479
|
+
| Error | Description |
|
|
480
|
+
| ---------------------- | -------------------------------------- |
|
|
481
|
+
| `ConnectionError` | Failed to connect to the droid process |
|
|
482
|
+
| `ProtocolError` | JSON-RPC protocol error |
|
|
483
|
+
| `SessionError` | Base session error |
|
|
484
|
+
| `SessionNotFoundError` | Session ID not found |
|
|
485
|
+
| `TimeoutError` | Request timed out |
|
|
486
|
+
| `ProcessExitError` | Droid subprocess exited unexpectedly |
|
|
178
487
|
|
|
179
488
|
## Examples
|
|
180
489
|
|
|
181
490
|
See the [`examples/`](./examples) directory for runnable examples:
|
|
182
491
|
|
|
183
|
-
- **[`
|
|
492
|
+
- **[`session-stream.ts`](./examples/session-stream.ts)** — session streaming output
|
|
493
|
+
- **[`run.ts`](./examples/run.ts)** — one-shot prompt with aggregated result
|
|
184
494
|
- **[`multi-turn-session.ts`](./examples/multi-turn-session.ts)** — multi-turn session lifecycle
|
|
495
|
+
- **[`abort-session-stream.ts`](./examples/abort-session-stream.ts)** — cancel an in-flight streaming session turn with `AbortSignal`
|
|
496
|
+
- **[`interrupt-session.ts`](./examples/interrupt-session.ts)** — interrupt a running session turn
|
|
497
|
+
- **[`init-metadata.ts`](./examples/init-metadata.ts)** — read initialization and load metadata from session APIs
|
|
498
|
+
- **[`result-metadata.ts`](./examples/result-metadata.ts)** — inspect `DroidResult` metadata from `run()`
|
|
499
|
+
- **[`structured-output.ts`](./examples/structured-output.ts)** — request and parse structured output
|
|
500
|
+
- **[`droid-dev-structured-output.ts`](./examples/droid-dev-structured-output.ts)** — structured output smoke test with configurable Droid executable
|
|
185
501
|
- **[`permission-handler.ts`](./examples/permission-handler.ts)** — custom permission handling
|
|
502
|
+
- **[`spec-mode-same-session.ts`](./examples/spec-mode-same-session.ts)** — approve a spec and continue in the same session
|
|
503
|
+
- **[`spec-mode-new-session.ts`](./examples/spec-mode-new-session.ts)** — approve a spec and hand off implementation to a new session
|
|
504
|
+
- **[`tool-controls.ts`](./examples/tool-controls.ts)** — configure allow/deny lists and inspect tool availability
|
|
505
|
+
- **[`sdk-mcp-tool.ts`](./examples/sdk-mcp-tool.ts)** — expose SDK-defined tools to Droid through MCP
|
|
506
|
+
- **[`fork-session.ts`](./examples/fork-session.ts)** — fork a session and continue from the new session ID
|
|
507
|
+
- **[`list-sessions.ts`](./examples/list-sessions.ts)** — discover droid sessions saved on disk
|
|
508
|
+
- **[`test-compact.ts`](./examples/test-compact.ts)** — compact session history
|
|
509
|
+
- **[`test-rewind.ts`](./examples/test-rewind.ts)** — use low-level rewind APIs
|
|
186
510
|
|
|
187
511
|
## License
|
|
188
512
|
|