@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
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
# SDK Usage Guide
|
|
2
|
+
|
|
3
|
+
## Getting Started
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @factory/droid-sdk
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Requires Node.js 18+ and the `droid` CLI on your PATH.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { run } from '@factory/droid-sdk';
|
|
13
|
+
|
|
14
|
+
const result = await run('What files are in this directory?', {
|
|
15
|
+
cwd: process.cwd(),
|
|
16
|
+
});
|
|
17
|
+
console.log(result.text);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## One-shot Run
|
|
23
|
+
|
|
24
|
+
Send a prompt, get a result, done. The session is created and closed automatically.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { run } from '@factory/droid-sdk';
|
|
28
|
+
|
|
29
|
+
const result = await run('What is 2 + 2?', { cwd: process.cwd() });
|
|
30
|
+
console.log(result.text);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Structured Output
|
|
34
|
+
|
|
35
|
+
Force the response to match a JSON schema. The validated object is available on `result.structuredOutput`.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { OutputFormatType, run } from '@factory/droid-sdk';
|
|
39
|
+
|
|
40
|
+
const result = await run('Pick a number between 1 and 42.', {
|
|
41
|
+
cwd: process.cwd(),
|
|
42
|
+
outputFormat: {
|
|
43
|
+
type: OutputFormatType.JsonSchema,
|
|
44
|
+
schema: {
|
|
45
|
+
type: 'object',
|
|
46
|
+
properties: { number: { type: 'number' } },
|
|
47
|
+
required: ['number'],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
console.log((result.structuredOutput as { number: number }).number);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Multi-turn Session
|
|
56
|
+
|
|
57
|
+
Create a session once, then call `stream()` multiple times. Context is preserved across turns.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { createSession } from '@factory/droid-sdk';
|
|
61
|
+
|
|
62
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
63
|
+
|
|
64
|
+
for await (const msg of session.stream('Remember the word "mango".')) {
|
|
65
|
+
// consume first turn
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for await (const msg of session.stream('What word did I say?')) {
|
|
69
|
+
if (msg.type === 'assistant') console.log(msg.text);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await session.close();
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Resume Session
|
|
76
|
+
|
|
77
|
+
Reconnect to a previously created session by its ID.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { resumeSession } from '@factory/droid-sdk';
|
|
81
|
+
|
|
82
|
+
const session = await resumeSession('existing-session-id');
|
|
83
|
+
|
|
84
|
+
for await (const msg of session.stream('Continue where we left off.')) {
|
|
85
|
+
if (msg.type === 'assistant') console.log(msg.text);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await session.close();
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Full Message Streaming
|
|
92
|
+
|
|
93
|
+
`stream()` yields complete messages: assistant text, tool calls, tool results, hooks, errors, and the final result.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { createSession, DroidMessageType } from '@factory/droid-sdk';
|
|
97
|
+
|
|
98
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
99
|
+
|
|
100
|
+
for await (const msg of session.stream(
|
|
101
|
+
'List files in the current directory.'
|
|
102
|
+
)) {
|
|
103
|
+
switch (msg.type) {
|
|
104
|
+
case DroidMessageType.Assistant:
|
|
105
|
+
console.log(msg.text);
|
|
106
|
+
break;
|
|
107
|
+
case DroidMessageType.ToolCall:
|
|
108
|
+
console.log(`[Tool] ${msg.toolUse.name}`);
|
|
109
|
+
break;
|
|
110
|
+
case DroidMessageType.ToolResult:
|
|
111
|
+
console.log(`[Result] ${msg.isError ? 'Error' : 'OK'}`);
|
|
112
|
+
break;
|
|
113
|
+
case DroidMessageType.Result:
|
|
114
|
+
console.log(`Done in ${msg.durationMs}ms`);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
await session.close();
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Partial Message Streaming
|
|
123
|
+
|
|
124
|
+
Enable `includePartialMessages` to get token-by-token deltas, thinking blocks, and tool progress as they arrive.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { createSession, DroidMessageType } from '@factory/droid-sdk';
|
|
128
|
+
|
|
129
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
130
|
+
|
|
131
|
+
for await (const msg of session.stream('Explain recursion.', {
|
|
132
|
+
includePartialMessages: true,
|
|
133
|
+
})) {
|
|
134
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
135
|
+
process.stdout.write(msg.text);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
await session.close();
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Interrupt or Cancel Running Work
|
|
143
|
+
|
|
144
|
+
Use `session.interrupt()` to stop the current turn server-side, or pass an `AbortSignal` to cancel from the client.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { createSession } from '@factory/droid-sdk';
|
|
148
|
+
|
|
149
|
+
// Interrupt after receiving some output
|
|
150
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
151
|
+
for await (const msg of session.stream('Write a long essay.')) {
|
|
152
|
+
if (msg.type === 'assistant') {
|
|
153
|
+
await session.interrupt();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
await session.close();
|
|
157
|
+
|
|
158
|
+
// Or cancel with AbortSignal
|
|
159
|
+
const session2 = await createSession({ cwd: process.cwd() });
|
|
160
|
+
const controller = new AbortController();
|
|
161
|
+
setTimeout(() => controller.abort(), 2000);
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
for await (const msg of session2.stream('Write a long essay.', {
|
|
165
|
+
abortSignal: controller.signal,
|
|
166
|
+
})) {
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
console.log('Aborted');
|
|
170
|
+
}
|
|
171
|
+
await session2.close();
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## SDK-backed MCP Tools
|
|
175
|
+
|
|
176
|
+
Define custom tools that Droid can call during a session. Tools are served via a local MCP server that the SDK manages automatically.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import {
|
|
180
|
+
createSession,
|
|
181
|
+
createSdkMcpServer,
|
|
182
|
+
tool,
|
|
183
|
+
ToolConfirmationOutcome,
|
|
184
|
+
} from '@factory/droid-sdk';
|
|
185
|
+
import { z } from 'zod';
|
|
186
|
+
|
|
187
|
+
const server = createSdkMcpServer({
|
|
188
|
+
name: 'my-tools',
|
|
189
|
+
tools: [
|
|
190
|
+
tool(
|
|
191
|
+
'lookup',
|
|
192
|
+
'Look up a user by name',
|
|
193
|
+
{ name: z.string() },
|
|
194
|
+
({ name }) => {
|
|
195
|
+
return `${name} is user #42.`;
|
|
196
|
+
}
|
|
197
|
+
),
|
|
198
|
+
],
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const session = await createSession({
|
|
202
|
+
cwd: process.cwd(),
|
|
203
|
+
mcpServers: [server],
|
|
204
|
+
permissionHandler: () => ToolConfirmationOutcome.ProceedOnce,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
for await (const msg of session.stream('Look up Alice.')) {
|
|
208
|
+
if (msg.type === 'assistant') console.log(msg.text);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
await session.close();
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Autonomy Levels
|
|
215
|
+
|
|
216
|
+
Control what Droid can do without asking for permission. Set at session creation or change mid-session.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { createSession, AutonomyLevel } from '@factory/droid-sdk';
|
|
220
|
+
|
|
221
|
+
const session = await createSession({
|
|
222
|
+
cwd: process.cwd(),
|
|
223
|
+
autonomyLevel: AutonomyLevel.High, // Off | Low | Medium | High
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Change mid-session
|
|
227
|
+
await session.updateSettings({ autonomyLevel: AutonomyLevel.Low });
|
|
228
|
+
await session.close();
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Enabled/Disabled Tools
|
|
232
|
+
|
|
233
|
+
Restrict which tools Droid can use. Accepts tool IDs like `'Read'`, `'Execute'`, `'Grep'`.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
import { createSession } from '@factory/droid-sdk';
|
|
237
|
+
|
|
238
|
+
const session = await createSession({
|
|
239
|
+
cwd: process.cwd(),
|
|
240
|
+
enabledToolIds: ['Read', 'Grep'],
|
|
241
|
+
disabledToolIds: ['Execute'],
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Change mid-session
|
|
245
|
+
await session.updateSettings({ disabledToolIds: ['Read', 'Execute'] });
|
|
246
|
+
await session.close();
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Permission Handler
|
|
250
|
+
|
|
251
|
+
Programmatically approve or reject tool calls instead of prompting a human. Receives full tool details including file paths and commands.
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
import {
|
|
255
|
+
run,
|
|
256
|
+
ToolConfirmationOutcome,
|
|
257
|
+
ToolConfirmationType,
|
|
258
|
+
} from '@factory/droid-sdk';
|
|
259
|
+
|
|
260
|
+
await run('Create hello.txt with "Hello, World!"', {
|
|
261
|
+
cwd: process.cwd(),
|
|
262
|
+
permissionHandler(params) {
|
|
263
|
+
const safe = params.toolUses.every(
|
|
264
|
+
(item) => item.details.type === ToolConfirmationType.Create
|
|
265
|
+
);
|
|
266
|
+
return safe
|
|
267
|
+
? ToolConfirmationOutcome.ProceedOnce
|
|
268
|
+
: ToolConfirmationOutcome.Cancel;
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Spec Mode
|
|
274
|
+
|
|
275
|
+
Start Droid in read-only planning mode. It will research and produce a plan, then request to exit spec mode for implementation.
|
|
276
|
+
|
|
277
|
+
```ts
|
|
278
|
+
import {
|
|
279
|
+
createSession,
|
|
280
|
+
DroidInteractionMode,
|
|
281
|
+
ToolConfirmationOutcome,
|
|
282
|
+
ToolConfirmationType,
|
|
283
|
+
} from '@factory/droid-sdk';
|
|
284
|
+
|
|
285
|
+
const session = await createSession({
|
|
286
|
+
cwd: process.cwd(),
|
|
287
|
+
interactionMode: DroidInteractionMode.Spec,
|
|
288
|
+
permissionHandler(params) {
|
|
289
|
+
const exitsSpec = params.toolUses.some(
|
|
290
|
+
(t) => t.details.type === ToolConfirmationType.ExitSpecMode
|
|
291
|
+
);
|
|
292
|
+
return exitsSpec
|
|
293
|
+
? ToolConfirmationOutcome.ProceedOnce
|
|
294
|
+
: ToolConfirmationOutcome.Cancel;
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
for await (const msg of session.stream('Plan a refactor of src/utils.ts')) {
|
|
299
|
+
}
|
|
300
|
+
await session.close();
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Multimodal Input
|
|
304
|
+
|
|
305
|
+
Send images or documents alongside your prompt. Images must be base64-encoded.
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
import { readFileSync } from 'node:fs';
|
|
309
|
+
import { createSession } from '@factory/droid-sdk';
|
|
310
|
+
|
|
311
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
312
|
+
|
|
313
|
+
for await (const msg of session.stream('Describe this image.', {
|
|
314
|
+
images: [
|
|
315
|
+
{
|
|
316
|
+
type: 'base64',
|
|
317
|
+
data: readFileSync('screenshot.png').toString('base64'),
|
|
318
|
+
mediaType: 'image/png',
|
|
319
|
+
},
|
|
320
|
+
],
|
|
321
|
+
})) {
|
|
322
|
+
if (msg.type === 'assistant') console.log(msg.text);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
await session.close();
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Fork Session
|
|
329
|
+
|
|
330
|
+
Create a copy of the current session with all context preserved. Useful for branching a conversation.
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
import { createSession, resumeSession } from '@factory/droid-sdk';
|
|
334
|
+
|
|
335
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
336
|
+
for await (const msg of session.stream('Remember: the password is "banana".')) {
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const { newSessionId } = await session.forkSession();
|
|
340
|
+
const fork = await resumeSession(newSessionId);
|
|
341
|
+
|
|
342
|
+
for await (const msg of fork.stream('What is the password?')) {
|
|
343
|
+
if (msg.type === 'assistant') console.log(msg.text);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
await fork.close();
|
|
347
|
+
await session.close();
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Compact Session
|
|
351
|
+
|
|
352
|
+
Summarize and remove old messages to free up context window space.
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
import { createSession } from '@factory/droid-sdk';
|
|
356
|
+
|
|
357
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
358
|
+
|
|
359
|
+
// ... after many turns ...
|
|
360
|
+
const result = await session.compactSession();
|
|
361
|
+
console.log(
|
|
362
|
+
`New session: ${result.newSessionId}, removed: ${result.removedCount} messages`
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
await session.close();
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## Rewind
|
|
369
|
+
|
|
370
|
+
Undo to a specific message and optionally restore files to their state at that point.
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
import {
|
|
374
|
+
DroidClient,
|
|
375
|
+
ProcessTransport,
|
|
376
|
+
AutonomyLevel,
|
|
377
|
+
} from '@factory/droid-sdk';
|
|
378
|
+
|
|
379
|
+
const transport = new ProcessTransport({ cwd: process.cwd() });
|
|
380
|
+
await transport.connect();
|
|
381
|
+
const client = new DroidClient({ transport });
|
|
382
|
+
|
|
383
|
+
await client.initializeSession({
|
|
384
|
+
machineId: 'default',
|
|
385
|
+
cwd: process.cwd(),
|
|
386
|
+
autonomyLevel: AutonomyLevel.High,
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
const messageId = 'target-message-id';
|
|
390
|
+
const info = await client.getRewindInfo({ messageId });
|
|
391
|
+
console.log(`Files available to restore: ${info.availableFiles.length}`);
|
|
392
|
+
|
|
393
|
+
const result = await client.executeRewind({
|
|
394
|
+
messageId,
|
|
395
|
+
filesToRestore: info.availableFiles,
|
|
396
|
+
filesToDelete: [],
|
|
397
|
+
forkTitle: 'Rewind checkpoint',
|
|
398
|
+
});
|
|
399
|
+
console.log(`Rewound into session: ${result.newSessionId}`);
|
|
400
|
+
|
|
401
|
+
await client.close();
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## List Sessions
|
|
405
|
+
|
|
406
|
+
Discover saved sessions on disk. Filters to the current project by default.
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
import { listSessions } from '@factory/droid-sdk';
|
|
410
|
+
|
|
411
|
+
const sessions = await listSessions({ numSessions: 10 });
|
|
412
|
+
|
|
413
|
+
for (const s of sessions) {
|
|
414
|
+
console.log(`${s.id}: ${s.sessionTitle ?? '(untitled)'}`);
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Model and Reasoning Effort
|
|
419
|
+
|
|
420
|
+
Choose which model to use and how much reasoning effort to apply. Configurable at creation or mid-session.
|
|
421
|
+
|
|
422
|
+
```ts
|
|
423
|
+
import { createSession, ReasoningEffort } from '@factory/droid-sdk';
|
|
424
|
+
|
|
425
|
+
const session = await createSession({
|
|
426
|
+
cwd: process.cwd(),
|
|
427
|
+
modelId: 'claude-sonnet-4-20250514',
|
|
428
|
+
reasoningEffort: ReasoningEffort.High,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Change mid-session
|
|
432
|
+
await session.updateSettings({
|
|
433
|
+
reasoningEffort: ReasoningEffort.Low,
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
await session.close();
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
## Hook Execution Monitoring
|
|
440
|
+
|
|
441
|
+
Observe file hooks (pre/post tool execution hooks) as they run during a session.
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
import { createSession, DroidMessageType } from '@factory/droid-sdk';
|
|
445
|
+
|
|
446
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
447
|
+
|
|
448
|
+
for await (const msg of session.stream('Create a new file.')) {
|
|
449
|
+
if (msg.type === DroidMessageType.Hook) {
|
|
450
|
+
if (msg.status === 'started') {
|
|
451
|
+
console.log(`[Hook] ${msg.command}`);
|
|
452
|
+
} else {
|
|
453
|
+
console.log(`[Hook ${msg.status}] exit=${msg.exitCode}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
await session.close();
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## Ask-User Handler
|
|
462
|
+
|
|
463
|
+
Programmatically answer questions that Droid asks the user during execution.
|
|
464
|
+
|
|
465
|
+
```ts
|
|
466
|
+
import { createSession } from '@factory/droid-sdk';
|
|
467
|
+
|
|
468
|
+
const session = await createSession({
|
|
469
|
+
cwd: process.cwd(),
|
|
470
|
+
askUserHandler(params) {
|
|
471
|
+
return {
|
|
472
|
+
cancelled: false,
|
|
473
|
+
answers: params.questions.map((q) => ({
|
|
474
|
+
index: q.index,
|
|
475
|
+
question: q.question,
|
|
476
|
+
answer: q.options[0] ?? 'yes',
|
|
477
|
+
})),
|
|
478
|
+
};
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
for await (const msg of session.stream('Help me set up this project.')) {
|
|
483
|
+
if (msg.type === 'assistant') console.log(msg.text);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
await session.close();
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
## MCP Server Management
|
|
490
|
+
|
|
491
|
+
Add, remove, toggle, and list MCP servers at runtime within an active session.
|
|
492
|
+
|
|
493
|
+
```ts
|
|
494
|
+
import { createSession, McpServerType } from '@factory/droid-sdk';
|
|
495
|
+
|
|
496
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
497
|
+
|
|
498
|
+
await session.addMcpServer({
|
|
499
|
+
name: 'my-server',
|
|
500
|
+
type: McpServerType.Http,
|
|
501
|
+
url: 'https://mcp.example.com/mcp',
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
const { servers, summary } = await session.listMcpServers();
|
|
505
|
+
console.log(`MCP status: ${summary.status}, servers: ${servers.length}`);
|
|
506
|
+
|
|
507
|
+
await session.close();
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
## Context Stats
|
|
511
|
+
|
|
512
|
+
Query current context window usage to understand how much capacity remains.
|
|
513
|
+
|
|
514
|
+
```ts
|
|
515
|
+
import { createSession } from '@factory/droid-sdk';
|
|
516
|
+
|
|
517
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
518
|
+
for await (const msg of session.stream('Hello')) {
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const stats = await session.getContextStats();
|
|
522
|
+
console.log(
|
|
523
|
+
`Used: ${stats.used}, Remaining: ${stats.remaining}, Limit: ${stats.limit}`
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
await session.close();
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
## Token Usage Tracking
|
|
530
|
+
|
|
531
|
+
Monitor token consumption in real-time via stream events, or read the final totals from the result.
|
|
532
|
+
|
|
533
|
+
```ts
|
|
534
|
+
import { createSession, DroidMessageType } from '@factory/droid-sdk';
|
|
535
|
+
|
|
536
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
537
|
+
|
|
538
|
+
for await (const msg of session.stream('Summarize this project.', {
|
|
539
|
+
includePartialMessages: true,
|
|
540
|
+
})) {
|
|
541
|
+
if (msg.type === DroidMessageType.TokenUsageUpdate) {
|
|
542
|
+
console.log(`Tokens — in: ${msg.inputTokens}, out: ${msg.outputTokens}`);
|
|
543
|
+
}
|
|
544
|
+
if (msg.type === DroidMessageType.Result && msg.tokenUsage) {
|
|
545
|
+
console.log(
|
|
546
|
+
`Final — in: ${msg.tokenUsage.inputTokens}, out: ${msg.tokenUsage.outputTokens}`
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
await session.close();
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
## List Skills
|
|
555
|
+
|
|
556
|
+
List all available skills in the current session.
|
|
557
|
+
|
|
558
|
+
```ts
|
|
559
|
+
import { createSession } from '@factory/droid-sdk';
|
|
560
|
+
|
|
561
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
562
|
+
const { skills } = await session.listSkills();
|
|
563
|
+
|
|
564
|
+
for (const skill of skills) {
|
|
565
|
+
console.log(`${skill.name} (${skill.location}): ${skill.description ?? ''}`);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
await session.close();
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
## Raw Notification Subscription
|
|
572
|
+
|
|
573
|
+
Subscribe to raw protocol notifications for custom event handling beyond the stream API.
|
|
574
|
+
|
|
575
|
+
```ts
|
|
576
|
+
import { createSession, SessionNotificationType } from '@factory/droid-sdk';
|
|
577
|
+
|
|
578
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
579
|
+
|
|
580
|
+
const unsubscribe = session.onNotification(
|
|
581
|
+
(notification) => {
|
|
582
|
+
console.log('Notification:', notification);
|
|
583
|
+
},
|
|
584
|
+
{ type: SessionNotificationType.ERROR }
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
// ... use the session ...
|
|
588
|
+
|
|
589
|
+
unsubscribe();
|
|
590
|
+
await session.close();
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
## Error Handling
|
|
594
|
+
|
|
595
|
+
The SDK throws typed errors with structured context. Catch specific error classes to handle different failure modes.
|
|
596
|
+
|
|
597
|
+
```ts
|
|
598
|
+
import {
|
|
599
|
+
resumeSession,
|
|
600
|
+
ConnectionError,
|
|
601
|
+
TimeoutError,
|
|
602
|
+
SessionNotFoundError,
|
|
603
|
+
ProtocolError,
|
|
604
|
+
} from '@factory/droid-sdk';
|
|
605
|
+
|
|
606
|
+
try {
|
|
607
|
+
const session = await resumeSession('nonexistent-id');
|
|
608
|
+
} catch (error) {
|
|
609
|
+
if (error instanceof SessionNotFoundError) {
|
|
610
|
+
console.log(`Session not found: ${error.sessionId}`);
|
|
611
|
+
} else if (error instanceof TimeoutError) {
|
|
612
|
+
console.log('Request timed out');
|
|
613
|
+
} else if (error instanceof ConnectionError) {
|
|
614
|
+
console.log(`Connection failed: ${error.message}`);
|
|
615
|
+
} else if (error instanceof ProtocolError) {
|
|
616
|
+
console.log(`Protocol error ${error.code}: ${error.message}`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
## Low-level APIs
|
|
622
|
+
|
|
623
|
+
Most users should use `run()` and `DroidSession`. For direct RPC access, the SDK also exports `ProcessTransport`, `ProtocolEngine`, and `DroidClient`.
|
|
624
|
+
|
|
625
|
+
`DroidClient` exposes additional methods not on `DroidSession`: `killWorkerSession()`, `cancelMcpAuth()`, `clearMcpAuth()`, `submitMcpAuthCode()`, `listMcpRegistry()`, `toggleMcpTool()`, and `submitBugReport()`.
|
|
626
|
+
|
|
627
|
+
The package also exports its full Zod schema surface from `src/schemas/index.ts` for runtime validation of protocol payloads.
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
## Configuration Reference
|
|
632
|
+
|
|
633
|
+
### `CreateSessionOptions`
|
|
634
|
+
|
|
635
|
+
| Field | Type | Description |
|
|
636
|
+
| :------------------------ | :----------------------- | :---------------------------------------------------------- |
|
|
637
|
+
| `cwd` | `string` | Working directory for the session |
|
|
638
|
+
| `machineId` | `string` | Machine identifier for initialization |
|
|
639
|
+
| `modelId` | `string` | LLM model identifier |
|
|
640
|
+
| `autonomyLevel` | `AutonomyLevel` | `Off` \| `Low` \| `Medium` \| `High` |
|
|
641
|
+
| `interactionMode` | `DroidInteractionMode` | `Auto` \| `Spec` \| `AGI` |
|
|
642
|
+
| `reasoningEffort` | `ReasoningEffort` | `None` \| `Low` \| `Medium` \| `High` \| `Max` (and others) |
|
|
643
|
+
| `specModeModelId` | `string` | Override model for spec mode |
|
|
644
|
+
| `specModeReasoningEffort` | `ReasoningEffort` | Override reasoning effort for spec mode |
|
|
645
|
+
| `mcpServers` | `DroidMcpServerConfig[]` | Initial MCP server configurations |
|
|
646
|
+
| `enabledToolIds` | `string[]` | Tool allowlist |
|
|
647
|
+
| `disabledToolIds` | `string[]` | Tool denylist |
|
|
648
|
+
| `tags` | `SessionTag[]` | Session tags for categorization |
|
|
649
|
+
| `permissionHandler` | `PermissionHandler` | Tool confirmation callback |
|
|
650
|
+
| `askUserHandler` | `AskUserHandler` | Structured user-input callback |
|
|
651
|
+
| `execPath` | `string` | Path to `droid` executable (default: `"droid"`) |
|
|
652
|
+
| `execArgs` | `string[]` | Extra CLI arguments for the subprocess |
|
|
653
|
+
| `env` | `Record<string, string>` | Environment variables for the subprocess |
|
|
654
|
+
| `transport` | `DroidClientTransport` | Custom transport (skips subprocess spawn) |
|
|
655
|
+
| `abortSignal` | `AbortSignal` | Cancellation signal |
|
|
656
|
+
|
|
657
|
+
### `MessageOptions`
|
|
658
|
+
|
|
659
|
+
Accepted by `session.stream()` and `run()`:
|
|
660
|
+
|
|
661
|
+
| Field | Type | Description |
|
|
662
|
+
| :----------------------- | :-------------------- | :------------------------------------------- |
|
|
663
|
+
| `images` | `Base64ImageSource[]` | Base64-encoded image attachments |
|
|
664
|
+
| `files` | `DocumentSource[]` | Document/file attachments |
|
|
665
|
+
| `outputFormat` | `OutputFormat` | Structured output JSON schema request |
|
|
666
|
+
| `includePartialMessages` | `boolean` | Yield token-level deltas and progress events |
|
|
667
|
+
| `abortSignal` | `AbortSignal` | Cancellation signal for this turn |
|
|
668
|
+
|
|
669
|
+
### Error Types
|
|
670
|
+
|
|
671
|
+
| Error | Description |
|
|
672
|
+
| :--------------------- | :---------------------------------------- |
|
|
673
|
+
| `ConnectionError` | Failed to connect to the droid subprocess |
|
|
674
|
+
| `ProtocolError` | JSON-RPC or protocol-level failure |
|
|
675
|
+
| `SessionError` | Base session error |
|
|
676
|
+
| `SessionNotFoundError` | Requested session does not exist |
|
|
677
|
+
| `TimeoutError` | RPC timed out |
|
|
678
|
+
| `ProcessExitError` | Subprocess exited unexpectedly |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abort a running turn with AbortController.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* npx tsx examples/abort-session-stream.ts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createSession } from '@factory/droid-sdk';
|
|
9
|
+
|
|
10
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
const timeout = setTimeout(
|
|
13
|
+
() => controller.abort(new Error('Stopped by AbortController')),
|
|
14
|
+
1000
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
for await (const _msg of session.stream(
|
|
19
|
+
'Write a long explanation of how compilers work.',
|
|
20
|
+
{
|
|
21
|
+
abortSignal: controller.signal,
|
|
22
|
+
}
|
|
23
|
+
)) {
|
|
24
|
+
void _msg;
|
|
25
|
+
}
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.log(error instanceof Error ? error.message : String(error));
|
|
28
|
+
} finally {
|
|
29
|
+
clearTimeout(timeout);
|
|
30
|
+
await session.close();
|
|
31
|
+
}
|