@factory/droid-sdk 0.6.0 → 0.7.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.
Files changed (80) hide show
  1. package/README.md +35 -482
  2. package/dist/chunk-5UXINOXG.mjs +24483 -0
  3. package/dist/chunk-5UXINOXG.mjs.map +1 -0
  4. package/dist/chunk-C3KHERVH.js +24483 -0
  5. package/dist/chunk-C3KHERVH.js.map +1 -0
  6. package/dist/{index-CwXX5IUH.d.cts → index-D_SzTnFR.d.mts} +83826 -81547
  7. package/dist/{index-CwXX5IUH.d.ts → index-D_SzTnFR.d.ts} +83826 -81547
  8. package/dist/index.d.mts +4 -0
  9. package/dist/index.d.ts +4 -93103
  10. package/dist/index.js +821 -11745
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +822 -0
  13. package/dist/index.mjs.map +1 -0
  14. package/dist/node.d.mts +736 -0
  15. package/dist/node.d.ts +736 -0
  16. package/dist/node.js +4601 -0
  17. package/dist/node.js.map +1 -0
  18. package/dist/node.mjs +4601 -0
  19. package/dist/node.mjs.map +1 -0
  20. package/docs/typescript-sdk-reference.md +1260 -0
  21. package/examples/browser/daemon-lifecycle.ts +95 -0
  22. package/examples/browser/daemon-multi-session.ts +66 -0
  23. package/examples/browser/daemon-session.ts +73 -0
  24. package/examples/browser/index.html +68 -0
  25. package/examples/browser/list-sessions.ts +52 -0
  26. package/examples/{abort-session-stream.ts → node/abort-session-stream.ts} +8 -10
  27. package/examples/node/ask-user-elicitation.ts +35 -0
  28. package/examples/node/compact-session.ts +48 -0
  29. package/examples/node/enter-spec-mode.ts +66 -0
  30. package/examples/node/fork-session.ts +45 -0
  31. package/examples/node/hook-execution.ts +94 -0
  32. package/examples/node/image-attachment.ts +35 -0
  33. package/examples/node/init-metadata.ts +24 -0
  34. package/examples/{interrupt-session.ts → node/interrupt-session.ts} +7 -6
  35. package/examples/node/list-sessions.ts +20 -0
  36. package/examples/node/multi-turn-session.ts +33 -0
  37. package/examples/node/observability.ts +88 -0
  38. package/examples/node/permission-handler.ts +55 -0
  39. package/examples/node/rename-session.ts +29 -0
  40. package/examples/node/result-metadata.ts +34 -0
  41. package/examples/node/rewind-session.ts +69 -0
  42. package/examples/node/run.ts +25 -0
  43. package/examples/{sdk-mcp-tool.ts → node/sdk-mcp-tool.ts} +22 -12
  44. package/examples/node/session-settings.ts +35 -0
  45. package/examples/node/session-stream.ts +44 -0
  46. package/examples/node/spec-mode-new-session.ts +92 -0
  47. package/examples/node/spec-mode-same-session.ts +85 -0
  48. package/examples/node/structured-output.ts +73 -0
  49. package/examples/node/tool-controls.ts +26 -0
  50. package/examples/package.json +3 -0
  51. package/package.json +20 -42
  52. package/dist/index.cjs +0 -12135
  53. package/dist/index.cjs.map +0 -1
  54. package/dist/index.d.cts +0 -93103
  55. package/dist/protocol/index.cjs +0 -6466
  56. package/dist/protocol/index.cjs.map +0 -1
  57. package/dist/protocol/index.d.cts +0 -2
  58. package/dist/protocol/index.d.ts +0 -2
  59. package/dist/protocol/index.js +0 -6078
  60. package/dist/protocol/index.js.map +0 -1
  61. package/docs/daemon-usage-guide.md +0 -544
  62. package/docs/sdk-usage-guide.md +0 -748
  63. package/examples/daemon-multi-session.ts +0 -67
  64. package/examples/droid-dev-structured-output.ts +0 -44
  65. package/examples/fork-session.ts +0 -68
  66. package/examples/hook-execution.ts +0 -68
  67. package/examples/init-metadata.ts +0 -24
  68. package/examples/list-sessions.ts +0 -19
  69. package/examples/multi-turn-session.ts +0 -40
  70. package/examples/permission-handler.ts +0 -41
  71. package/examples/readme-structured-output.ts +0 -26
  72. package/examples/result-metadata.ts +0 -63
  73. package/examples/run.ts +0 -39
  74. package/examples/session-stream.ts +0 -57
  75. package/examples/spec-mode-new-session.ts +0 -61
  76. package/examples/spec-mode-same-session.ts +0 -72
  77. package/examples/structured-output.ts +0 -60
  78. package/examples/test-compact.ts +0 -65
  79. package/examples/test-rewind.ts +0 -87
  80. package/examples/tool-controls.ts +0 -37
@@ -1,544 +0,0 @@
1
- # Daemon SDK Usage Guide
2
-
3
- The daemon SDK connects to a running `droid daemon` process over WebSocket instead of spawning a new subprocess per session. This enables multiple concurrent sessions over a single connection and is the transport used by Slack, Linear, REST API, and Automations integrations.
4
-
5
- ## Getting Started
6
-
7
- ```bash
8
- npm install @factory/droid-sdk
9
- ```
10
-
11
- Requires a running `droid daemon` (the SDK will auto-start one locally) and a `FACTORY_API_KEY`. For simpler use cases that don't need concurrent sessions, see the [SDK Usage Guide](./sdk-usage-guide.md) which covers exec mode (`run()`, `createSession()`).
12
-
13
- ```ts
14
- import { connectDaemon, DroidMessageType } from '@factory/droid-sdk';
15
-
16
- const connection = await connectDaemon({
17
- apiKey: process.env.FACTORY_API_KEY!,
18
- });
19
- const session = await connection.createSession({ cwd: process.cwd() });
20
-
21
- for await (const msg of session.stream('What files are in this directory?')) {
22
- if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
23
- if (msg.type === DroidMessageType.Result)
24
- console.log(`Done in ${msg.durationMs}ms`);
25
- }
26
-
27
- await session.close();
28
- await connection.close();
29
- ```
30
-
31
- ---
32
-
33
- ## Daemon vs Exec Mode
34
-
35
- | | Exec mode (`run`, `createSession`) | Daemon mode (`connectDaemon`) |
36
- | --------- | -------------------------------------- | --------------------------------------------- |
37
- | Transport | Spawns `droid exec` subprocess (stdio) | WebSocket to `droid daemon` |
38
- | Sessions | One per subprocess | Multiple per connection |
39
- | Auth | Explicit (`apiKey`) | Explicit (`apiKey`) |
40
- | Use case | Simple scripts, CI | Server-side integrations, long-lived services |
41
-
42
- Use daemon mode when you need multiple concurrent sessions, want to avoid subprocess overhead, or are building a server-side integration.
43
-
44
- ---
45
-
46
- ## Connect to Local Daemon
47
-
48
- The simplest form -- the SDK spawns a local daemon and authenticates with the provided API key.
49
-
50
- ```ts
51
- import { connectDaemon } from '@factory/droid-sdk';
52
-
53
- const connection = await connectDaemon({
54
- apiKey: process.env.FACTORY_API_KEY!,
55
- });
56
- ```
57
-
58
- ### Connect to Remote Machine
59
-
60
- ```ts
61
- import { connectDaemon, MachineType } from '@factory/droid-sdk';
62
-
63
- // Ephemeral sandbox (e2b)
64
- const connection = await connectDaemon({
65
- apiKey: process.env.FACTORY_API_KEY!,
66
- machine: {
67
- type: MachineType.Ephemeral,
68
- sandboxId: 'sandbox-abc123',
69
- workspaceId: 'ws-xyz',
70
- },
71
- });
72
-
73
- // Computer relay
74
- const connection2 = await connectDaemon({
75
- apiKey: process.env.FACTORY_API_KEY!,
76
- machine: {
77
- type: MachineType.Computer,
78
- computerId: 'comp-abc123',
79
- },
80
- });
81
- ```
82
-
83
- ### Direct URL
84
-
85
- Skip machine-based resolution and connect to a specific WebSocket endpoint.
86
-
87
- ```ts
88
- const connection = await connectDaemon({
89
- url: 'ws://127.0.0.1:37643',
90
- apiKey: process.env.FACTORY_API_KEY!,
91
- });
92
- ```
93
-
94
- ### Connection Retries
95
-
96
- ```ts
97
- const connection = await connectDaemon({
98
- apiKey: process.env.FACTORY_API_KEY!,
99
- maxRetries: 3, // Retry connect+authenticate cycle up to 3 times
100
- });
101
- ```
102
-
103
- ---
104
-
105
- ## Create a Session
106
-
107
- ```ts
108
- import {
109
- connectDaemon,
110
- AutonomyLevel,
111
- ReasoningEffort,
112
- } from '@factory/droid-sdk';
113
-
114
- const connection = await connectDaemon({
115
- apiKey: process.env.FACTORY_API_KEY!,
116
- });
117
-
118
- const session = await connection.createSession({
119
- cwd: '/path/to/project',
120
- modelId: 'claude-sonnet-4-20250514',
121
- autonomyLevel: AutonomyLevel.High,
122
- reasoningEffort: ReasoningEffort.High,
123
- });
124
- ```
125
-
126
- ---
127
-
128
- ## Stream a Response
129
-
130
- `stream()` yields complete messages by default: assistant text, tool calls, tool results, and the final result.
131
-
132
- ```ts
133
- import { DroidMessageType } from '@factory/droid-sdk';
134
-
135
- for await (const msg of session.stream('Refactor the utils module.')) {
136
- switch (msg.type) {
137
- case DroidMessageType.Assistant:
138
- console.log(msg.text);
139
- break;
140
- case DroidMessageType.ToolCall:
141
- console.log(`[Tool] ${msg.toolUse.name}`);
142
- break;
143
- case DroidMessageType.ToolResult:
144
- console.log(`[Result] ${msg.isError ? 'Error' : 'OK'}`);
145
- break;
146
- case DroidMessageType.Result:
147
- console.log(`Done in ${msg.durationMs}ms, turns: ${msg.numTurns}`);
148
- break;
149
- }
150
- }
151
- ```
152
-
153
- ## Partial Message Streaming
154
-
155
- Enable `includePartialMessages` to get token-by-token deltas as they arrive.
156
-
157
- ```ts
158
- import { DroidMessageType } from '@factory/droid-sdk';
159
-
160
- for await (const msg of session.stream('Explain recursion.', {
161
- includePartialMessages: true,
162
- })) {
163
- if (msg.type === DroidMessageType.AssistantTextDelta) {
164
- process.stdout.write(msg.text);
165
- }
166
- }
167
- ```
168
-
169
- ---
170
-
171
- ## Fire-and-Forget with send()
172
-
173
- `send()` dispatches a prompt and returns after the daemon acknowledges it. The agent runs in the background -- you don't wait for completion. Use this for delegation workflows (e.g., Slack bot triggers a task).
174
-
175
- ```ts
176
- await session.send('Fix all lint errors in src/.');
177
- // Returns immediately after daemon ACK.
178
- // The agent works in the background.
179
- ```
180
-
181
- ---
182
-
183
- ## Multi-turn Session
184
-
185
- Context is preserved across `stream()` calls on the same session.
186
-
187
- ```ts
188
- import { DroidMessageType } from '@factory/droid-sdk';
189
-
190
- const session = await connection.createSession({ cwd: process.cwd() });
191
-
192
- for await (const msg of session.stream('Remember: the secret is 42.')) {
193
- // consume first turn
194
- }
195
-
196
- for await (const msg of session.stream('What is the secret?')) {
197
- if (msg.type === DroidMessageType.Assistant) console.log(msg.text); // "42"
198
- }
199
-
200
- await session.close();
201
- ```
202
-
203
- ---
204
-
205
- ## Resume a Previous Session
206
-
207
- Reconnect to a session that was created earlier (on this connection or a previous one).
208
-
209
- ```ts
210
- import { DroidMessageType } from '@factory/droid-sdk';
211
-
212
- const session = await connection.resumeSession('existing-session-id');
213
-
214
- for await (const msg of session.stream('Continue where we left off.')) {
215
- if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
216
- }
217
-
218
- await session.close();
219
- ```
220
-
221
- ---
222
-
223
- ## Concurrent Sessions
224
-
225
- A single daemon connection supports multiple sessions running simultaneously. The SDK routes notifications to the correct session automatically.
226
-
227
- ```ts
228
- import {
229
- connectDaemon,
230
- DaemonSession,
231
- DroidMessageType,
232
- } from '@factory/droid-sdk';
233
-
234
- const connection = await connectDaemon({
235
- apiKey: process.env.FACTORY_API_KEY!,
236
- });
237
-
238
- const [session1, session2] = await Promise.all([
239
- connection.createSession({ cwd: '/project-a' }),
240
- connection.createSession({ cwd: '/project-b' }),
241
- ]);
242
-
243
- // Stream on both concurrently
244
- const [result1, result2] = await Promise.all([
245
- collectResult(session1, 'Fix tests in project A.'),
246
- collectResult(session2, 'Add logging to project B.'),
247
- ]);
248
-
249
- await session1.close();
250
- await session2.close();
251
- await connection.close();
252
-
253
- async function collectResult(
254
- session: DaemonSession,
255
- prompt: string
256
- ): Promise<string> {
257
- let text = '';
258
- for await (const msg of session.stream(prompt)) {
259
- if (msg.type === DroidMessageType.Result) text = msg.result;
260
- }
261
- return text;
262
- }
263
- ```
264
-
265
- ---
266
-
267
- ## Interrupt a Session
268
-
269
- ### From the session object
270
-
271
- ```ts
272
- import { DroidMessageType } from '@factory/droid-sdk';
273
-
274
- // Interrupt after receiving some output
275
- for await (const msg of session.stream('Write a long essay.')) {
276
- if (msg.type === DroidMessageType.Assistant) {
277
- await session.interrupt();
278
- break;
279
- }
280
- }
281
- ```
282
-
283
- ### With AbortSignal
284
-
285
- ```ts
286
- const controller = new AbortController();
287
- setTimeout(() => controller.abort(), 5000);
288
-
289
- try {
290
- for await (const msg of session.stream('Write a long essay.', {
291
- abortSignal: controller.signal,
292
- })) {
293
- // ...
294
- }
295
- } catch {
296
- console.log('Aborted after 5 seconds');
297
- }
298
- ```
299
-
300
- ### From the connection (without a session object)
301
-
302
- ```ts
303
- await connection.interruptSession('session-id-to-interrupt');
304
- ```
305
-
306
- ---
307
-
308
- ## Permission Handler
309
-
310
- Programmatically approve or reject tool calls.
311
-
312
- ```ts
313
- import {
314
- ToolConfirmationOutcome,
315
- ToolConfirmationType,
316
- } from '@factory/droid-sdk';
317
-
318
- const session = await connection.createSession({
319
- cwd: process.cwd(),
320
- permissionHandler(params) {
321
- const safe = params.toolUses.every(
322
- (item) => item.details.type === ToolConfirmationType.Create
323
- );
324
- return safe
325
- ? ToolConfirmationOutcome.ProceedOnce
326
- : ToolConfirmationOutcome.Cancel;
327
- },
328
- });
329
- ```
330
-
331
- ---
332
-
333
- ## Ask-User Handler
334
-
335
- Programmatically answer questions that Droid asks during execution.
336
-
337
- ```ts
338
- const session = await connection.createSession({
339
- cwd: process.cwd(),
340
- askUserHandler(params) {
341
- return {
342
- cancelled: false,
343
- answers: params.questions.map((q) => ({
344
- index: q.index,
345
- question: q.question,
346
- answer: q.options[0] ?? 'yes',
347
- })),
348
- };
349
- },
350
- });
351
- ```
352
-
353
- ---
354
-
355
- ## SDK-backed MCP Tools
356
-
357
- Define custom tools that Droid can call during a session.
358
-
359
- ```ts
360
- import {
361
- connectDaemon,
362
- createSdkMcpServer,
363
- DroidMessageType,
364
- tool,
365
- ToolConfirmationOutcome,
366
- } from '@factory/droid-sdk';
367
- import { z } from 'zod';
368
-
369
- const server = createSdkMcpServer({
370
- name: 'my-tools',
371
- tools: [
372
- tool(
373
- 'lookup',
374
- 'Look up a user by name',
375
- { name: z.string() },
376
- ({ name }) => `${name} is user #42.`
377
- ),
378
- ],
379
- });
380
-
381
- const connection = await connectDaemon({
382
- apiKey: process.env.FACTORY_API_KEY!,
383
- });
384
- const session = await connection.createSession({
385
- cwd: process.cwd(),
386
- mcpServers: [server],
387
- permissionHandler: () => ToolConfirmationOutcome.ProceedOnce,
388
- });
389
-
390
- for await (const msg of session.stream('Look up Alice.')) {
391
- if (msg.type === DroidMessageType.Assistant) console.log(msg.text);
392
- }
393
-
394
- await session.close();
395
- await connection.close();
396
- ```
397
-
398
- ---
399
-
400
- ## Raw Notification Subscription
401
-
402
- Subscribe to raw protocol notifications for custom event handling.
403
-
404
- ```ts
405
- import { SessionNotificationType } from '@factory/droid-sdk';
406
-
407
- const unsubscribe = session.onNotification(
408
- (notification) => {
409
- console.log('Notification:', notification);
410
- },
411
- { type: SessionNotificationType.DROID_WORKING_STATE_CHANGED }
412
- );
413
-
414
- // ... use the session ...
415
-
416
- unsubscribe();
417
- ```
418
-
419
- ---
420
-
421
- ## Error Handling
422
-
423
- The daemon SDK throws the same typed errors as exec mode.
424
-
425
- ```ts
426
- import {
427
- connectDaemon,
428
- ConnectionError,
429
- TimeoutError,
430
- SessionNotFoundError,
431
- ProtocolError,
432
- } from '@factory/droid-sdk';
433
-
434
- try {
435
- const connection = await connectDaemon({
436
- apiKey: process.env.FACTORY_API_KEY!,
437
- });
438
- const session = await connection.resumeSession('nonexistent-id');
439
- } catch (error) {
440
- if (error instanceof SessionNotFoundError) {
441
- console.log(`Session not found: ${error.sessionId}`);
442
- } else if (error instanceof TimeoutError) {
443
- console.log('Request timed out');
444
- } else if (error instanceof ConnectionError) {
445
- console.log(`Connection failed: ${error.message}`);
446
- } else if (error instanceof ProtocolError) {
447
- console.log(`Protocol error ${error.code}: ${error.message}`);
448
- }
449
- }
450
- ```
451
-
452
- ---
453
-
454
- ## Lifecycle Pattern
455
-
456
- Always close sessions and connections when done.
457
-
458
- ```ts
459
- import { connectDaemon } from '@factory/droid-sdk';
460
-
461
- const connection = await connectDaemon({
462
- apiKey: process.env.FACTORY_API_KEY!,
463
- });
464
-
465
- try {
466
- const session = await connection.createSession({ cwd: process.cwd() });
467
- try {
468
- for await (const msg of session.stream('Do the thing.')) {
469
- // ...
470
- }
471
- } finally {
472
- await session.close();
473
- }
474
- } finally {
475
- await connection.close();
476
- }
477
- ```
478
-
479
- ---
480
-
481
- ## Configuration Reference
482
-
483
- ### `ConnectDaemonOptions`
484
-
485
- | Field | Type | Required | Description |
486
- | :------------- | :----------------- | :------- | :--------------------------------------------------------------------- |
487
- | `apiKey` | `string` | **Yes** | Factory API key for authentication. |
488
- | `machine` | `SDKMachineConfig` | No | Machine target. Defaults to local daemon. |
489
- | `url` | `string` | No | Direct WebSocket URL. Overrides machine resolution. |
490
- | `maxRetries` | `number` | No | Retry budget for connect+authenticate cycle. |
491
- | `daemonPort` | `number` | No | WebSocket port override. Default: `37643`. |
492
- | `relayBaseUrl` | `string` | No | Relay URL for computer connections. Default: `wss://relay.factory.ai`. |
493
-
494
- ### `SDKMachineConfig`
495
-
496
- | Variant | Fields | Description |
497
- | :---------- | :--------------------------------- | :---------------------------- |
498
- | `Local` | `{ type: MachineType.Local }` | Local daemon on this machine. |
499
- | `Ephemeral` | `{ type, sandboxId, workspaceId }` | e2b sandbox. |
500
- | `Computer` | `{ type, computerId }` | Remote computer via relay. |
501
-
502
- ### `DaemonSessionOptions`
503
-
504
- | Field | Type | Description |
505
- | :------------------------ | :----------------------- | :--------------------------------------------- |
506
- | `cwd` | `string` | Working directory for the session. |
507
- | `modelId` | `string` | LLM model identifier. |
508
- | `autonomyLevel` | `AutonomyLevel` | `Off` \| `Low` \| `Medium` \| `High`. |
509
- | `interactionMode` | `DroidInteractionMode` | `Auto` \| `Spec` \| `AGI`. |
510
- | `reasoningEffort` | `ReasoningEffort` | `Off` \| `Low` \| `Medium` \| `High` \| `Max`. |
511
- | `specModeModelId` | `string` | Override model for spec mode. |
512
- | `specModeReasoningEffort` | `ReasoningEffort` | Override reasoning effort for spec mode. |
513
- | `mcpServers` | `DroidMcpServerConfig[]` | MCP server configurations. |
514
- | `enabledToolIds` | `string[]` | Tool allowlist. |
515
- | `disabledToolIds` | `string[]` | Tool denylist. |
516
- | `tags` | `SessionTag[]` | Session tags for categorization. |
517
- | `permissionHandler` | `PermissionHandler` | Tool confirmation callback. |
518
- | `askUserHandler` | `AskUserHandler` | Structured user-input callback. |
519
- | `sessionSource` | `SessionSource` | Attribution metadata. |
520
-
521
- ### `DaemonResumeOptions`
522
-
523
- | Field | Type | Description |
524
- | :------------------ | :----------------------- | :------------------------------ |
525
- | `permissionHandler` | `PermissionHandler` | Tool confirmation callback. |
526
- | `askUserHandler` | `AskUserHandler` | Structured user-input callback. |
527
- | `mcpServers` | `DroidMcpServerConfig[]` | MCP servers to attach. |
528
-
529
- ### `SendOptions`
530
-
531
- | Field | Type | Description |
532
- | :------------- | :-------------------- | :------------------------------------- |
533
- | `images` | `Base64ImageSource[]` | Base64-encoded image attachments. |
534
- | `files` | `DocumentSource[]` | Document/file attachments. |
535
- | `outputFormat` | `OutputFormat` | Structured output JSON schema request. |
536
-
537
- ### Key Classes
538
-
539
- | Class | Description |
540
- | :------------------- | :------------------------------------------------------------------------- |
541
- | `DaemonConnection` | Manages the WebSocket connection. Creates/resumes sessions. |
542
- | `DaemonSession` | A single session. Provides `stream()`, `send()`, `interrupt()`, `close()`. |
543
- | `DaemonClient` | Low-level RPC client. Used internally by `DaemonSession`. |
544
- | `WebSocketTransport` | WebSocket transport with retry and reconnection. |