@factory/droid-sdk 0.7.0 → 0.8.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/dist/{chunk-C3KHERVH.js → chunk-4G2RKM2H.js} +4976 -3628
- package/dist/chunk-4G2RKM2H.js.map +1 -0
- package/dist/{chunk-5UXINOXG.mjs → chunk-ROFAQUG4.mjs} +7574 -6226
- package/dist/chunk-ROFAQUG4.mjs.map +1 -0
- package/dist/{index-D_SzTnFR.d.mts → index-_nUPGHpH.d.mts} +16625 -5933
- package/dist/{index-D_SzTnFR.d.ts → index-_nUPGHpH.d.ts} +16625 -5933
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +10 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9 -1
- package/dist/node.d.mts +24 -3
- package/dist/node.d.ts +24 -3
- package/dist/node.js +504 -374
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +153 -23
- package/dist/node.mjs.map +1 -1
- package/docs/typescript-sdk-reference.md +903 -726
- package/examples/browser/daemon-model-discovery.ts +47 -0
- package/examples/browser/index.html +2 -1
- package/examples/browser/list-sessions.ts +1 -3
- package/examples/node/abort-session-stream.ts +2 -0
- package/examples/node/ask-user-elicitation.ts +11 -7
- package/examples/node/compact-session.ts +33 -18
- package/examples/node/custom-system-prompt.ts +35 -0
- package/examples/node/document-input.ts +58 -0
- package/examples/node/enter-spec-mode.ts +50 -29
- package/examples/node/external-mcp.ts +45 -0
- package/examples/node/fork-session.ts +21 -10
- package/examples/node/hook-execution.ts +37 -37
- package/examples/node/image-attachment.ts +8 -2
- package/examples/node/interrupt-session.ts +27 -8
- package/examples/node/model-discovery.ts +28 -0
- package/examples/node/model-selection.ts +35 -0
- package/examples/node/multi-turn-session.ts +4 -0
- package/examples/node/observability.ts +4 -17
- package/examples/node/partial-stream.ts +56 -0
- package/examples/node/permission-handler.ts +9 -4
- package/examples/node/rename-session.ts +7 -3
- package/examples/node/result-metadata.ts +10 -1
- package/examples/node/resume-session.ts +41 -0
- package/examples/node/rewind-session.ts +19 -9
- package/examples/node/run.ts +14 -7
- package/examples/node/sdk-mcp-tool.ts +10 -8
- package/examples/node/session-settings.ts +9 -9
- package/examples/node/session-stream.ts +17 -2
- package/examples/node/skills.ts +24 -0
- package/examples/node/spec-mode-new-session.ts +38 -10
- package/examples/node/spec-mode-same-session.ts +37 -3
- package/examples/node/structured-output.ts +30 -7
- package/package.json +1 -1
- package/dist/chunk-5UXINOXG.mjs.map +0 -1
- package/dist/chunk-C3KHERVH.js.map +0 -1
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
# Factory TypeScript SDK
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
stream work, control tools, and connect to a running Droid daemon.
|
|
3
|
+
Use the Factory TypeScript SDK to run Droid from Node.js.
|
|
5
4
|
|
|
6
5
|
The public npm package is `@factory/droid-sdk`.
|
|
7
6
|
|
|
8
|
-
This
|
|
9
|
-
|
|
7
|
+
This guide covers the high-level public API. The package's exported TypeScript
|
|
8
|
+
declarations define the complete compile-time API.
|
|
10
9
|
|
|
11
10
|
## Overview
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
Import the SDK from `@factory/droid-sdk/node`. It starts a Droid CLI subprocess
|
|
13
|
+
and provides session management, streaming, local session discovery, and
|
|
14
|
+
in-process MCP tool APIs.
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
| ------- | ------------------------- | --------------------------------------------------------------- |
|
|
17
|
-
| Node | `@factory/droid-sdk/node` | Scripts, CI, local tools, and apps that can start the Droid CLI |
|
|
18
|
-
| Daemon | `@factory/droid-sdk` | Browser or Node apps that connect to an existing Droid daemon |
|
|
16
|
+
### Choose an API
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
| Goal | API |
|
|
19
|
+
| --------------------------------------- | ----------------- |
|
|
20
|
+
| Run one prompt and get the final result | `run()` |
|
|
21
|
+
| Keep context across prompts | `createSession()` |
|
|
22
|
+
| Continue a saved session | `resumeSession()` |
|
|
23
|
+
| Find saved sessions | `listSessions()` |
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
Start with `run()` for one prompt. Use a session when later prompts need the
|
|
26
|
+
same conversation history.
|
|
26
27
|
|
|
27
|
-
##
|
|
28
|
+
## Install and authenticate
|
|
28
29
|
|
|
29
30
|
Install the package:
|
|
30
31
|
|
|
@@ -34,9 +35,8 @@ npm install @factory/droid-sdk
|
|
|
34
35
|
|
|
35
36
|
The SDK requires Node.js 18 or later.
|
|
36
37
|
|
|
37
|
-
By default,
|
|
38
|
-
|
|
39
|
-
subprocess creation and this requirement.
|
|
38
|
+
By default, the SDK starts the `droid` CLI from `PATH`. Pass `execPath` to use a
|
|
39
|
+
different `droid` executable.
|
|
40
40
|
|
|
41
41
|
Set an API key:
|
|
42
42
|
|
|
@@ -44,42 +44,30 @@ Set an API key:
|
|
|
44
44
|
export FACTORY_API_KEY="your-key"
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
### Browser security
|
|
50
|
-
|
|
51
|
-
The daemon client currently authenticates with a Factory API key. Use browser
|
|
52
|
-
examples only in trusted local tools. Do not ship a Factory API key in a public
|
|
53
|
-
website, commit it to source control, or put it in a URL.
|
|
54
|
-
|
|
55
|
-
An HTTPS page must connect with `wss://`. Local pages served over HTTP can use
|
|
56
|
-
`ws://`.
|
|
47
|
+
The SDK reads `FACTORY_API_KEY` from the process environment by default. You
|
|
48
|
+
can also pass `apiKey` to `run()`, `createSession()`, or `resumeSession()`.
|
|
57
49
|
|
|
58
50
|
## Quick start
|
|
59
51
|
|
|
60
52
|
### Run one prompt
|
|
61
53
|
|
|
62
|
-
**Runtime: Node**
|
|
63
|
-
|
|
64
54
|
```ts
|
|
65
55
|
import { run } from '@factory/droid-sdk/node';
|
|
66
56
|
|
|
67
57
|
const result = await run('Summarize this repository.');
|
|
68
58
|
|
|
69
59
|
if (!result.success) {
|
|
70
|
-
throw new Error(result.error?.message ??
|
|
60
|
+
throw new Error(result.error?.message ?? `Run failed: ${result.subtype}`);
|
|
71
61
|
}
|
|
72
62
|
|
|
73
63
|
console.log(result.text);
|
|
74
64
|
```
|
|
75
65
|
|
|
76
|
-
`run()` creates a session,
|
|
77
|
-
|
|
66
|
+
`run()` creates a session, consumes its complete message stream, returns the
|
|
67
|
+
terminal `DroidResult`, and closes the session.
|
|
78
68
|
|
|
79
69
|
### Continue a conversation
|
|
80
70
|
|
|
81
|
-
**Runtime: Node**
|
|
82
|
-
|
|
83
71
|
```ts
|
|
84
72
|
import { createSession, DroidMessageType } from '@factory/droid-sdk/node';
|
|
85
73
|
|
|
@@ -102,169 +90,353 @@ try {
|
|
|
102
90
|
}
|
|
103
91
|
```
|
|
104
92
|
|
|
105
|
-
|
|
93
|
+
In the above example, the second prompt uses context from the first turn.
|
|
94
|
+
Remember to close sessions that your code creates.
|
|
95
|
+
|
|
96
|
+
## Core concepts
|
|
97
|
+
|
|
98
|
+
### Session
|
|
99
|
+
|
|
100
|
+
A session holds conversation history, settings, and a working directory. Create
|
|
101
|
+
one with `createSession()` or load a saved session with `resumeSession()`.
|
|
102
|
+
|
|
103
|
+
### Turn
|
|
104
|
+
|
|
105
|
+
A turn starts when you send one prompt with `session.stream()`. A session can
|
|
106
|
+
run one turn at a time.
|
|
107
|
+
|
|
108
|
+
### Stream message
|
|
109
|
+
|
|
110
|
+
`session.stream()` returns an async iterable of complete messages. Narrow each
|
|
111
|
+
message by its `type`. Messages can report user input, assistant output, tool
|
|
112
|
+
activity, hooks, errors, and a terminal `DroidResult`.
|
|
113
|
+
|
|
114
|
+
### Result
|
|
115
|
+
|
|
116
|
+
A normally completed turn ends with a `DroidResult`. `run()` consumes the stream and returns that same result type. See [Streaming and results](#streaming-and-results) for result handling, errors, token usage, and cancellation.
|
|
106
117
|
|
|
107
|
-
###
|
|
118
|
+
### Ownership and cleanup
|
|
108
119
|
|
|
109
|
-
|
|
120
|
+
Cleanup depends on how the session was created.
|
|
121
|
+
|
|
122
|
+
| API | Cleanup |
|
|
123
|
+
| ----------------- | -------------------------------- |
|
|
124
|
+
| `run()` | Closes its session automatically |
|
|
125
|
+
| `createSession()` | Call `await session.close()` |
|
|
126
|
+
| `resumeSession()` | Call `await session.close()` |
|
|
127
|
+
|
|
128
|
+
Use `finally` blocks so cleanup also runs after an error.
|
|
129
|
+
|
|
130
|
+
## Models
|
|
131
|
+
|
|
132
|
+
Model IDs are strings because availability depends on the account,
|
|
133
|
+
organization policy, and daemon configuration. Omit `modelId` to use the Droid
|
|
134
|
+
default.
|
|
135
|
+
|
|
136
|
+
### Discover available models
|
|
137
|
+
|
|
138
|
+
**Runtime: Node.js**
|
|
110
139
|
|
|
111
140
|
```ts
|
|
112
|
-
import {
|
|
141
|
+
import { listModels } from '@factory/droid-sdk/node';
|
|
113
142
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
auth: { apiKey },
|
|
118
|
-
});
|
|
143
|
+
const models = await listModels();
|
|
144
|
+
const catalog = await listModels({ includeDisabled: true });
|
|
145
|
+
```
|
|
119
146
|
|
|
120
|
-
|
|
147
|
+
The Node helper starts a one-shot Droid process, requests the catalog without
|
|
148
|
+
creating a session, and closes the process before returning.
|
|
121
149
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
150
|
+
**Runtime: Daemon** (`droid` is a client returned by `connectToDaemon()`)
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
const models = await droid.models.list();
|
|
154
|
+
const catalog = await droid.models.list({ includeDisabled: true });
|
|
155
|
+
|
|
156
|
+
for (const model of models) {
|
|
157
|
+
console.log(model.id, model.supportedReasoningEfforts);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
`includeDisabled` defaults to `false`, so both APIs return only selectable
|
|
162
|
+
models when options are omitted. When disabled models are requested, every
|
|
163
|
+
disabled entry includes a `disabledReason` for display. Regular organization
|
|
164
|
+
members cannot enable these models. An organization manager may be able to
|
|
165
|
+
change the model policy or complete a required opt-in.
|
|
166
|
+
|
|
167
|
+
Runnable examples:
|
|
168
|
+
|
|
169
|
+
- Node: [`examples/node/model-discovery.ts`](../examples/node/model-discovery.ts)
|
|
170
|
+
- Browser and daemon: [`examples/browser/daemon-model-discovery.ts`](../examples/browser/daemon-model-discovery.ts)
|
|
171
|
+
|
|
172
|
+
Each available model includes:
|
|
173
|
+
|
|
174
|
+
| Field | Meaning |
|
|
175
|
+
| --------------------------- | ---------------------------------------------- |
|
|
176
|
+
| `id` | Value accepted by `modelId` |
|
|
177
|
+
| `displayName` | Full display name |
|
|
178
|
+
| `shortDisplayName` | Compact display name |
|
|
179
|
+
| `modelProvider` | Model provider |
|
|
180
|
+
| `supportedReasoningEfforts` | Reasoning levels accepted by the model |
|
|
181
|
+
| `defaultReasoningEffort` | Reasoning level used when none is supplied |
|
|
182
|
+
| `isCustom` | Whether the model is user-configured |
|
|
183
|
+
| `disabled` | Whether policy prevents selecting the model |
|
|
184
|
+
| `disabledReason` | Required explanation when `disabled` is `true` |
|
|
185
|
+
| `noImageSupport` | Whether image input is unavailable |
|
|
186
|
+
| `supportsImageGeneration` | Whether the model can generate images |
|
|
187
|
+
| `kind` | Model or router classification, when present |
|
|
188
|
+
|
|
189
|
+
Other optional fields provide tier, token multiplier, and display badges.
|
|
190
|
+
|
|
191
|
+
### Node and daemon discovery
|
|
192
|
+
|
|
193
|
+
| Runtime | Discovery API | Lifecycle |
|
|
194
|
+
| ------- | --------------------- | ---------------------------------------- |
|
|
195
|
+
| Node | `listModels(options)` | Uses and closes a one-shot Droid process |
|
|
196
|
+
| Daemon | `droid.models.list()` | Reuses the connected daemon client |
|
|
197
|
+
|
|
198
|
+
Both APIs return the same `ModelInfo[]` type and include built-in models plus
|
|
199
|
+
configured custom models.
|
|
200
|
+
|
|
201
|
+
### Select a model
|
|
202
|
+
|
|
203
|
+
Pass a configured model ID when creating a session or running one prompt.
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import {
|
|
207
|
+
createSession,
|
|
208
|
+
DroidMessageType,
|
|
209
|
+
ReasoningEffort,
|
|
210
|
+
} from '@factory/droid-sdk/node';
|
|
211
|
+
|
|
212
|
+
const session = await createSession({
|
|
213
|
+
modelId: 'model-id',
|
|
214
|
+
reasoningEffort: ReasoningEffort.High,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
for await (const message of session.stream('Review this repository.')) {
|
|
219
|
+
if (message.type === DroidMessageType.Assistant) {
|
|
220
|
+
console.log(message.text);
|
|
127
221
|
}
|
|
128
|
-
} finally {
|
|
129
|
-
await Promise.allSettled([session.close()]);
|
|
130
|
-
droid.disconnect();
|
|
131
222
|
}
|
|
223
|
+
} finally {
|
|
224
|
+
await session.close();
|
|
132
225
|
}
|
|
133
226
|
```
|
|
134
227
|
|
|
135
|
-
|
|
136
|
-
does not make an API key secret from browser JavaScript, so keep this pattern
|
|
137
|
-
restricted to trusted local pages.
|
|
228
|
+
For an existing session, call `updateSettings()`:
|
|
138
229
|
|
|
139
|
-
|
|
230
|
+
```ts
|
|
231
|
+
await session.updateSettings({
|
|
232
|
+
modelId: 'model-id',
|
|
233
|
+
reasoningEffort: ReasoningEffort.High,
|
|
234
|
+
});
|
|
235
|
+
```
|
|
140
236
|
|
|
141
|
-
|
|
237
|
+
Set `reasoningEffort` only to a level supported by the selected model. Omit it
|
|
238
|
+
to use the model's configured default.
|
|
142
239
|
|
|
143
|
-
|
|
240
|
+
Runnable example: [`examples/node/model-selection.ts`](../examples/node/model-selection.ts)
|
|
144
241
|
|
|
145
|
-
|
|
146
|
-
`resumeSession()`. Daemon sessions are created or resumed through
|
|
147
|
-
`droid.sessions`.
|
|
242
|
+
### Use Auto Router
|
|
148
243
|
|
|
149
|
-
|
|
244
|
+
Set `modelId` to `"auto"` to let Factory choose a model for each turn.
|
|
150
245
|
|
|
151
|
-
|
|
152
|
-
|
|
246
|
+
```ts
|
|
247
|
+
import { run } from '@factory/droid-sdk/node';
|
|
153
248
|
|
|
154
|
-
|
|
249
|
+
const result = await run('Find the cause of the failing tests.', {
|
|
250
|
+
modelId: 'auto',
|
|
251
|
+
});
|
|
155
252
|
|
|
156
|
-
|
|
157
|
-
|
|
253
|
+
if (!result.success) {
|
|
254
|
+
throw new Error(result.error?.message ?? `Run failed: ${result.subtype}`);
|
|
255
|
+
}
|
|
158
256
|
|
|
159
|
-
|
|
257
|
+
console.log(result.text);
|
|
258
|
+
```
|
|
160
259
|
|
|
161
|
-
|
|
162
|
-
success, interruption, or failure and includes final text, messages, duration,
|
|
163
|
-
turn count, and token usage when available.
|
|
260
|
+
The selected model can change between turns. Choose a fixed model ID when runs must use the same model.
|
|
164
261
|
|
|
165
|
-
###
|
|
262
|
+
### Choose a spec-mode model
|
|
166
263
|
|
|
167
|
-
|
|
264
|
+
The primary model handles normal turns. The spec-mode model handles turns in
|
|
265
|
+
spec mode.
|
|
168
266
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
267
|
+
```ts
|
|
268
|
+
import {
|
|
269
|
+
createSession,
|
|
270
|
+
DroidInteractionMode,
|
|
271
|
+
ReasoningEffort,
|
|
272
|
+
} from '@factory/droid-sdk/node';
|
|
174
273
|
|
|
175
|
-
|
|
274
|
+
const session = await createSession({
|
|
275
|
+
modelId: 'auto',
|
|
276
|
+
interactionMode: DroidInteractionMode.Spec,
|
|
277
|
+
specModeModelId: 'model-id',
|
|
278
|
+
specModeReasoningEffort: ReasoningEffort.High,
|
|
279
|
+
});
|
|
176
280
|
|
|
177
|
-
|
|
281
|
+
try {
|
|
282
|
+
for await (const _message of session.stream(
|
|
283
|
+
'Draft an implementation plan.'
|
|
284
|
+
)) {
|
|
285
|
+
// Consume messages.
|
|
286
|
+
}
|
|
287
|
+
} finally {
|
|
288
|
+
await session.close();
|
|
289
|
+
}
|
|
290
|
+
```
|
|
178
291
|
|
|
179
|
-
|
|
292
|
+
You can also choose the model when entering spec mode:
|
|
180
293
|
|
|
181
294
|
```ts
|
|
182
|
-
|
|
295
|
+
await session.enterSpecMode({
|
|
296
|
+
specModeModelId: 'model-id',
|
|
297
|
+
specModeReasoningEffort: ReasoningEffort.High,
|
|
298
|
+
});
|
|
183
299
|
```
|
|
184
300
|
|
|
185
|
-
|
|
186
|
-
|
|
301
|
+
### Use a custom model
|
|
302
|
+
|
|
303
|
+
Configure custom models in `~/.factory/settings.json`. See [Custom Models (BYOK)](https://docs.factory.ai/cli/byok/overview) for provider, credential, and model settings.
|
|
304
|
+
|
|
305
|
+
After configuration, pass the custom model ID anywhere the SDK accepts `modelId`. The ID uses `custom:` followed by the configured `model` value.
|
|
187
306
|
|
|
188
307
|
```ts
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
disabledToolIds: ['Execute'],
|
|
308
|
+
import { run } from '@factory/droid-sdk/node';
|
|
309
|
+
|
|
310
|
+
const result = await run('Summarize this repository.', {
|
|
311
|
+
modelId: 'custom:gpt-4o-mini',
|
|
194
312
|
});
|
|
195
|
-
```
|
|
196
313
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
| `modelId` | Model selection |
|
|
204
|
-
| `reasoningEffort` | Model reasoning level |
|
|
205
|
-
| `autonomyLevel` | Permission policy level |
|
|
206
|
-
| `interactionMode` | Auto, spec, or mission mode. AGI is deprecated |
|
|
207
|
-
| `specModeModelId` | Model used in spec mode |
|
|
208
|
-
| `specModeReasoningEffort` | Reasoning level used in spec mode |
|
|
209
|
-
| `disabledToolIds` | Tools that Droid cannot use |
|
|
210
|
-
| `autoRejectPermissionRequests` | Reject all permission requests automatically |
|
|
211
|
-
| `disableBuiltinSkills` | Hide Factory-provided built-in skills |
|
|
212
|
-
| `permissionHandler` | Handles permission requests |
|
|
213
|
-
| `askUserHandler` | Answers AskUser requests |
|
|
214
|
-
| `tags` | Session attribution tags |
|
|
215
|
-
| `sessionSource` | Session source attribution |
|
|
216
|
-
| `images` | Base64 image attachments |
|
|
217
|
-
| `files` | Text or PDF attachments |
|
|
218
|
-
| `outputFormat` | JSON schema for structured output |
|
|
219
|
-
| `abortSignal` | Cancels the run |
|
|
220
|
-
| `mcpServers` | MCP servers available to the session |
|
|
221
|
-
| `observability` | Logging, metrics, and trace integration |
|
|
222
|
-
| `execPath` | Droid executable path |
|
|
223
|
-
| `execArgs` | Additional Droid process arguments |
|
|
224
|
-
| `env` | Additional Droid process environment variables |
|
|
225
|
-
| `transport` | Custom string-framed transport |
|
|
314
|
+
if (!result.success) {
|
|
315
|
+
throw new Error(result.error?.message ?? `Run failed: ${result.subtype}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
console.log(result.text);
|
|
319
|
+
```
|
|
226
320
|
|
|
227
321
|
## Sessions
|
|
228
322
|
|
|
229
|
-
|
|
323
|
+
Use a session when prompts need shared conversation history. A session owns its working directory, settings, and one active turn at a time.
|
|
324
|
+
|
|
325
|
+
### Create and use a session
|
|
230
326
|
|
|
231
327
|
```ts
|
|
232
|
-
import { createSession } from '@factory/droid-sdk/node';
|
|
328
|
+
import { createSession, DroidMessageType } from '@factory/droid-sdk/node';
|
|
233
329
|
|
|
234
330
|
const session = await createSession({
|
|
235
331
|
cwd: process.cwd(),
|
|
236
|
-
modelId: 'model-id',
|
|
237
332
|
});
|
|
333
|
+
|
|
334
|
+
async function send(prompt: string) {
|
|
335
|
+
for await (const message of session.stream(prompt)) {
|
|
336
|
+
if (message.type === DroidMessageType.Assistant) {
|
|
337
|
+
console.log(message.text);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
await send('What does this project do?');
|
|
344
|
+
await send('What should I test first?');
|
|
345
|
+
} finally {
|
|
346
|
+
await session.close();
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
The second turn uses context from the first. `cwd` defaults to `process.cwd()`. Model and reasoning defaults come from Droid settings.
|
|
351
|
+
|
|
352
|
+
Runnable example:
|
|
353
|
+
[`examples/node/multi-turn-session.ts`](../examples/node/multi-turn-session.ts)
|
|
354
|
+
|
|
355
|
+
### Resume a saved session
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
import { DroidMessageType, resumeSession } from '@factory/droid-sdk/node';
|
|
359
|
+
|
|
360
|
+
async function continueSession(sessionId: string) {
|
|
361
|
+
const session = await resumeSession(sessionId);
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
for await (const message of session.stream(
|
|
365
|
+
'Continue from the last conversation.'
|
|
366
|
+
)) {
|
|
367
|
+
if (message.type === DroidMessageType.Assistant) {
|
|
368
|
+
console.log(message.text);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
} finally {
|
|
372
|
+
await session.close();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
238
375
|
```
|
|
239
376
|
|
|
240
|
-
|
|
241
|
-
|
|
377
|
+
`resumeSession()` restores the saved conversation, working directory, and session settings. It does not accept `cwd`, `modelId`, or reasoning options.
|
|
378
|
+
|
|
379
|
+
Resume options can provide new permission and AskUser handlers, disabled tools, MCP servers, an abort signal, and observability sinks.
|
|
380
|
+
|
|
381
|
+
### Customize the system prompt
|
|
242
382
|
|
|
243
|
-
|
|
383
|
+
Use `systemPrompt` to append instructions to Droid's effective built-in prompt
|
|
384
|
+
or replace the behavioral prompt:
|
|
244
385
|
|
|
245
386
|
```ts
|
|
246
|
-
|
|
387
|
+
const appended = await createSession({
|
|
388
|
+
systemPrompt: {
|
|
389
|
+
type: 'preset',
|
|
390
|
+
preset: 'droid',
|
|
391
|
+
append: 'Prioritize security findings and cite relevant files.',
|
|
392
|
+
},
|
|
393
|
+
});
|
|
247
394
|
|
|
248
|
-
const
|
|
395
|
+
const replaced = await createSession({
|
|
396
|
+
systemPrompt: 'Act as a focused dependency-analysis agent.',
|
|
397
|
+
});
|
|
249
398
|
```
|
|
250
399
|
|
|
251
|
-
The
|
|
252
|
-
|
|
253
|
-
|
|
400
|
+
The Droid preset retains the prompt that the session would normally use,
|
|
401
|
+
including specialized exec and mission prompts, and appends your instructions.
|
|
402
|
+
A string replaces that behavioral prompt. Both forms retain the mandatory
|
|
403
|
+
Droid identity block and required model/tool guidance.
|
|
404
|
+
|
|
405
|
+
The option is available to `run()`, `createSession()`, and
|
|
406
|
+
`droid.sessions.create()`. The prompt is fixed when the session is created.
|
|
407
|
+
It is saved in the session settings file, restored by `resumeSession()` and
|
|
408
|
+
`droid.sessions.resume()`, and inherited unchanged by forks.
|
|
409
|
+
|
|
410
|
+
The prompt configuration is stored in the sibling session settings file, not
|
|
411
|
+
serialized as a system event in the transcript JSONL. As with any instruction,
|
|
412
|
+
the model can still repeat prompt text in its generated messages or thinking,
|
|
413
|
+
which are part of the transcript. Treat both files as sensitive.
|
|
414
|
+
|
|
415
|
+
Runnable example: [`examples/node/custom-system-prompt.ts`](../examples/node/custom-system-prompt.ts)
|
|
416
|
+
|
|
417
|
+
### Read session state
|
|
254
418
|
|
|
255
|
-
|
|
419
|
+
Each handle exposes its ID, current working directory, and current settings.
|
|
256
420
|
|
|
257
421
|
```ts
|
|
258
422
|
console.log(session.id);
|
|
259
423
|
console.log(session.cwd);
|
|
260
424
|
console.log(session.settings.modelId);
|
|
425
|
+
console.log(session.settings.reasoningEffort);
|
|
426
|
+
console.log(session.settings.interactionMode);
|
|
261
427
|
```
|
|
262
428
|
|
|
263
|
-
`settings` and `cwd`
|
|
429
|
+
`settings` is read-only. The SDK updates `settings` and `cwd` when Droid reports a change.
|
|
264
430
|
|
|
265
|
-
|
|
431
|
+
Runnable example: [`examples/node/init-metadata.ts`](../examples/node/init-metadata.ts)
|
|
432
|
+
|
|
433
|
+
### Update active-session settings
|
|
434
|
+
|
|
435
|
+
Use `updateSettings()` for changes that should apply to later turns in the current session.
|
|
266
436
|
|
|
267
437
|
```ts
|
|
438
|
+
import { AutonomyLevel, ReasoningEffort } from '@factory/droid-sdk/node';
|
|
439
|
+
|
|
268
440
|
await session.updateSettings({
|
|
269
441
|
modelId: 'model-id',
|
|
270
442
|
reasoningEffort: ReasoningEffort.High,
|
|
@@ -273,32 +445,82 @@ await session.updateSettings({
|
|
|
273
445
|
});
|
|
274
446
|
```
|
|
275
447
|
|
|
276
|
-
|
|
277
|
-
|
|
448
|
+
Updatable settings include:
|
|
449
|
+
|
|
450
|
+
- model and reasoning effort
|
|
451
|
+
- interaction mode and autonomy level
|
|
452
|
+
- spec-mode model settings
|
|
453
|
+
- tool availability overrides
|
|
454
|
+
|
|
455
|
+
Interaction mode controls whether Droid operates normally in Auto mode or produces a read-only plan in Spec mode. Autonomy level controls which actions require approval while Droid is in Auto mode. Tool availability overrides can enable, disable, or restrict the tools available to the session.
|
|
456
|
+
|
|
457
|
+
When only changing interaction modes, use `enterSpecMode()` and `exitSpecMode()` instead of setting `interactionMode` directly.
|
|
458
|
+
|
|
459
|
+
The updated values are available through `session.settings`.
|
|
460
|
+
|
|
461
|
+
Runnable example: [`examples/node/session-settings.ts`](../examples/node/session-settings.ts)
|
|
278
462
|
|
|
279
463
|
### Rename a session
|
|
280
464
|
|
|
465
|
+
Use `rename()` after the first turn to replace the generated title.
|
|
466
|
+
|
|
281
467
|
```ts
|
|
282
468
|
await session.rename({ title: 'Authentication review' });
|
|
283
469
|
```
|
|
284
470
|
|
|
285
|
-
|
|
471
|
+
Runnable example:
|
|
472
|
+
[`examples/node/rename-session.ts`](../examples/node/rename-session.ts)
|
|
473
|
+
|
|
474
|
+
### List saved sessions
|
|
475
|
+
|
|
476
|
+
`listSessions()` reads local Droid session storage. It does not start Droid or
|
|
477
|
+
call the Factory API.
|
|
478
|
+
|
|
479
|
+
List sessions for the current working directory:
|
|
286
480
|
|
|
287
481
|
```ts
|
|
288
482
|
import { listSessions } from '@factory/droid-sdk/node';
|
|
289
483
|
|
|
484
|
+
const sessions = await listSessions({ limit: 10 });
|
|
485
|
+
|
|
486
|
+
for (const session of sessions) {
|
|
487
|
+
console.log(session.id, session.title, session.modifiedTime);
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
List sessions across all working directories:
|
|
492
|
+
|
|
493
|
+
```ts
|
|
290
494
|
const sessions = await listSessions({
|
|
291
|
-
|
|
495
|
+
fetchOutsideCWD: true,
|
|
292
496
|
limit: 10,
|
|
293
497
|
});
|
|
294
498
|
```
|
|
295
499
|
|
|
296
|
-
|
|
297
|
-
|
|
500
|
+
Results are sorted by `modifiedTime`, newest first. Each item includes its ID, title, owner, message count, creation time, modification time, and working directory when available.
|
|
501
|
+
|
|
502
|
+
Runnable example: [`examples/node/list-sessions.ts`](../examples/node/list-sessions.ts)
|
|
503
|
+
|
|
504
|
+
### What persists
|
|
505
|
+
|
|
506
|
+
Droid saves the session data needed to continue later.
|
|
507
|
+
|
|
508
|
+
| Restored from the saved session | Attach again when resuming |
|
|
509
|
+
| ------------------------------- | -------------------------- |
|
|
510
|
+
| Conversation history | Permission handler |
|
|
511
|
+
| Working directory | AskUser handler |
|
|
512
|
+
| Title | SDK MCP servers |
|
|
513
|
+
| Session settings | Observability sinks |
|
|
514
|
+
| | Abort signal |
|
|
515
|
+
|
|
516
|
+
Session settings include model, reasoning, Auto or Spec interaction mode, autonomy, spec-mode model settings, and tool availability overrides.
|
|
517
|
+
|
|
518
|
+
Handlers, observability sinks, abort signals, and SDK MCP servers are runtime objects. Droid cannot serialize them with the session, so attach them again when calling `resumeSession()`. They do not need to be the same object instances or implementations used when the session was created.
|
|
298
519
|
|
|
299
|
-
### Close
|
|
520
|
+
### Close sessions safely
|
|
300
521
|
|
|
301
|
-
Call `close()` in `finally` when your code
|
|
522
|
+
Call `close()` in `finally` when your code creates or resumes a session.
|
|
523
|
+
Closing releases the subprocess, subscriptions, and SDK-owned MCP servers.
|
|
302
524
|
|
|
303
525
|
```ts
|
|
304
526
|
const session = await createSession();
|
|
@@ -310,11 +532,13 @@ try {
|
|
|
310
532
|
}
|
|
311
533
|
```
|
|
312
534
|
|
|
313
|
-
## Streaming
|
|
535
|
+
## Streaming and results
|
|
314
536
|
|
|
315
|
-
|
|
537
|
+
Each call to `session.stream()` starts one turn and returns an async iterable.
|
|
538
|
+
|
|
539
|
+
By default, it yields complete messages and ends with a terminal `DroidResult`.
|
|
316
540
|
|
|
317
|
-
Complete messages
|
|
541
|
+
### Complete messages
|
|
318
542
|
|
|
319
543
|
```ts
|
|
320
544
|
for await (const message of session.stream('Find the failing test.')) {
|
|
@@ -342,12 +566,60 @@ Default message types:
|
|
|
342
566
|
| `tool_result` | Complete tool result |
|
|
343
567
|
| `hook` | Hook execution |
|
|
344
568
|
| `error` | Runtime error event |
|
|
345
|
-
| `result` |
|
|
569
|
+
| `result` | Terminal `DroidResult` |
|
|
570
|
+
|
|
571
|
+
Runnable example: [`examples/node/session-stream.ts`](../examples/node/session-stream.ts)
|
|
572
|
+
|
|
573
|
+
### Handle the result
|
|
574
|
+
|
|
575
|
+
`DroidResult` is a discriminated union. Check `subtype` or `success` before using the response.
|
|
576
|
+
|
|
577
|
+
```ts
|
|
578
|
+
const result = await run('Run the test suite.');
|
|
579
|
+
|
|
580
|
+
switch (result.subtype) {
|
|
581
|
+
case 'success':
|
|
582
|
+
console.log(result.text);
|
|
583
|
+
break;
|
|
584
|
+
case 'interrupted':
|
|
585
|
+
console.log('The run was interrupted.');
|
|
586
|
+
break;
|
|
587
|
+
case 'error_during_execution':
|
|
588
|
+
case 'error_structured_output':
|
|
589
|
+
console.error(
|
|
590
|
+
result.structuredOutputError?.message ??
|
|
591
|
+
result.error?.message ??
|
|
592
|
+
'Unknown error'
|
|
593
|
+
);
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
| Subtype | Meaning |
|
|
599
|
+
| ------------------------- | ------------------------------------------------- |
|
|
600
|
+
| `success` | The turn completed successfully |
|
|
601
|
+
| `interrupted` | The turn was cancelled or permission declined |
|
|
602
|
+
| `error_during_execution` | Droid could not complete the requested work |
|
|
603
|
+
| `error_structured_output` | Structured-output generation or validation failed |
|
|
604
|
+
|
|
605
|
+
Every result includes:
|
|
606
|
+
|
|
607
|
+
```ts
|
|
608
|
+
console.log(result.sessionId);
|
|
609
|
+
console.log(result.durationMs);
|
|
610
|
+
console.log(result.turnCount);
|
|
611
|
+
console.log(result.messages);
|
|
612
|
+
console.log(result.text);
|
|
613
|
+
console.log(result.tokenUsage);
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
`run()` returns the terminal result directly. When consuming `session.stream()`, handle the message whose type is `DroidMessageType.Result`.
|
|
617
|
+
|
|
618
|
+
Runnable example: [`examples/node/result-metadata.ts`](../examples/node/result-metadata.ts)
|
|
346
619
|
|
|
347
620
|
### Partial events
|
|
348
621
|
|
|
349
|
-
|
|
350
|
-
progress, usage, or state updates.
|
|
622
|
+
Enable partial events only when the application needs live text, thinking, tool progress, token usage, or state updates.
|
|
351
623
|
|
|
352
624
|
```ts
|
|
353
625
|
for await (const event of session.stream('Explain the test failure.', {
|
|
@@ -361,21 +633,152 @@ for await (const event of session.stream('Explain the test failure.', {
|
|
|
361
633
|
|
|
362
634
|
Partial streams can also include thinking deltas, tool-call deltas, tool
|
|
363
635
|
progress, token updates, permission results, settings changes, working-state
|
|
364
|
-
changes, MCP status
|
|
636
|
+
changes, and MCP status.
|
|
637
|
+
|
|
638
|
+
Runnable example: [`examples/node/partial-stream.ts`](../examples/node/partial-stream.ts)
|
|
639
|
+
|
|
640
|
+
### Token and context usage
|
|
641
|
+
|
|
642
|
+
`DroidResult.tokenUsage` reports raw token counts for the current SDK turn and,
|
|
643
|
+
when available, the Factory Service Credits (FSC) charged for that turn. One
|
|
644
|
+
turn is one `run()` or `session.stream()` call. This is not the session's
|
|
645
|
+
lifetime total.
|
|
646
|
+
|
|
647
|
+
```ts
|
|
648
|
+
if (result.tokenUsage) {
|
|
649
|
+
console.log(`input: ${result.tokenUsage.inputTokens}`);
|
|
650
|
+
console.log(`output: ${result.tokenUsage.outputTokens}`);
|
|
651
|
+
console.log(`cache read: ${result.tokenUsage.cacheReadTokens}`);
|
|
652
|
+
console.log(`cache created: ${result.tokenUsage.cacheCreationTokens}`);
|
|
653
|
+
if (result.tokenUsage.factoryCredits !== undefined) {
|
|
654
|
+
console.log(`Factory Service Credits: ${result.tokenUsage.factoryCredits}`);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
| Field | What it measures during the turn |
|
|
660
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
661
|
+
| `inputTokens` | Provider-reported input across model calls, which can include instructions, tools, conversation history, and the latest message |
|
|
662
|
+
| `outputTokens` | Output generated across model calls, not only the final visible response |
|
|
663
|
+
| `cacheReadTokens` | Input tokens reused from the prompt cache |
|
|
664
|
+
| `cacheCreationTokens` | Input tokens written to the prompt cache |
|
|
665
|
+
| `thinkingTokens` | Reasoning tokens reported separately when available |
|
|
666
|
+
| `factoryCredits` | Factory Service Credits charged, when available |
|
|
667
|
+
|
|
668
|
+
One turn can make several model calls while Droid uses tools. Their usage is combined in the result, along with usage from delegated work. Earlier conversation can be counted again when it is included as input to a later turn. Cached input is also reported through the separate cache fields. `tokenUsage` is `null` when usage is unavailable.
|
|
669
|
+
|
|
670
|
+
Partial streams can emit `DroidMessageType.TokenUsageUpdate`. Unlike the per-turn result, these events contain cumulative committed usage for the session.
|
|
671
|
+
|
|
672
|
+
`getContextStats()` measures current context occupancy, not cumulative token usage:
|
|
673
|
+
|
|
674
|
+
```ts
|
|
675
|
+
const stats = await session.getContextStats();
|
|
676
|
+
|
|
677
|
+
console.log(stats.used, stats.remaining, stats.limit, stats.accuracy);
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
| Field | Meaning |
|
|
681
|
+
| ----------- | ----------------------------------------------------------------------------------------------------- |
|
|
682
|
+
| `used` | Estimated tokens currently occupied by prompts, tools, instructions, and conversation history |
|
|
683
|
+
| `limit` | Active model's maximum input window; unresolved model IDs fall back to the effective compaction limit |
|
|
684
|
+
| `remaining` | `Math.max(0, limit - used)` |
|
|
685
|
+
| `accuracy` | Currently `estimated`, because Droid approximates tokens from character counts |
|
|
686
|
+
|
|
687
|
+
`remaining` estimates room in the model's input window. It does not
|
|
688
|
+
necessarily mean tokens remaining before compaction. Droid can compact at a
|
|
689
|
+
lower configured threshold, commonly 250,000 tokens, even when the active
|
|
690
|
+
model's input window is larger.
|
|
691
|
+
|
|
692
|
+
### Errors
|
|
693
|
+
|
|
694
|
+
An error event and a thrown exception mean different things:
|
|
695
|
+
|
|
696
|
+
- An `error` message reports a runtime problem during the turn. A terminal `DroidResult` normally follows and describes the final outcome.
|
|
697
|
+
- A failed `DroidResult` means the stream completed, but the requested work or structured output failed.
|
|
698
|
+
- A thrown exception means the SDK operation itself could not finish, such as an abort, connection failure, protocol error, or concurrent stream attempt.
|
|
699
|
+
|
|
700
|
+
Handle stream exceptions around the iteration:
|
|
701
|
+
|
|
702
|
+
```ts
|
|
703
|
+
try {
|
|
704
|
+
for await (const message of session.stream('Run the tests.')) {
|
|
705
|
+
if (message.type === DroidMessageType.Error) {
|
|
706
|
+
console.error(message.message);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
} catch (error) {
|
|
710
|
+
console.error('The stream could not finish:', error);
|
|
711
|
+
}
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
`run()` follows the same distinction: agent failures are returned as failed results, while setup, transport, protocol, and abort failures are thrown.
|
|
365
715
|
|
|
366
716
|
### Stream concurrency
|
|
367
717
|
|
|
368
718
|
One session handle can have one active stream. Starting another active stream
|
|
369
719
|
throws `ConcurrentStreamError`.
|
|
370
720
|
|
|
371
|
-
|
|
721
|
+
### Cancellation and interruption
|
|
722
|
+
|
|
723
|
+
Use an abort signal to cancel one turn:
|
|
724
|
+
|
|
725
|
+
```ts
|
|
726
|
+
const controller = new AbortController();
|
|
727
|
+
|
|
728
|
+
setTimeout(
|
|
729
|
+
() => controller.abort(new Error('Timed out after 5 seconds')),
|
|
730
|
+
5_000
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
for await (const message of session.stream('Perform a long review.', {
|
|
734
|
+
abortSignal: controller.signal,
|
|
735
|
+
})) {
|
|
736
|
+
// Handle messages.
|
|
737
|
+
}
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
The stream interrupts the active turn and throws the abort reason.
|
|
741
|
+
|
|
742
|
+
Use `session.interrupt()` when another part of the application needs to stop
|
|
743
|
+
the active turn:
|
|
744
|
+
|
|
745
|
+
```ts
|
|
746
|
+
await session.interrupt();
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
The stream normally continues to a terminal result with subtype `interrupted`.
|
|
750
|
+
The session remains available for later prompts.
|
|
751
|
+
|
|
752
|
+
Breaking out of the stream also interrupts the active turn:
|
|
753
|
+
|
|
754
|
+
```ts
|
|
755
|
+
for await (const message of session.stream('Investigate every failing test.')) {
|
|
756
|
+
if (message.type === DroidMessageType.Assistant) {
|
|
757
|
+
console.log(message.text);
|
|
758
|
+
|
|
759
|
+
// Interrupt this turn instead of merely hiding its remaining output.
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
Breaking out of the loop before the turn finishes sends an interrupt request
|
|
766
|
+
to Droid. This cancels the remaining model and tool work for that turn. The
|
|
767
|
+
session remains open and can accept another prompt, but this loop does not
|
|
768
|
+
receive the interrupted turn's terminal `DroidResult`.
|
|
769
|
+
|
|
770
|
+
A session-level abort signal passed to `createSession()` closes the entire
|
|
771
|
+
session when aborted.
|
|
372
772
|
|
|
373
|
-
|
|
773
|
+
Runnable examples:
|
|
374
774
|
|
|
375
|
-
|
|
775
|
+
- [`examples/node/abort-session-stream.ts`](../examples/node/abort-session-stream.ts)
|
|
776
|
+
- [`examples/node/interrupt-session.ts`](../examples/node/interrupt-session.ts)
|
|
376
777
|
|
|
377
778
|
## Inputs and outputs
|
|
378
779
|
|
|
780
|
+
Pass images, documents, or an output schema in the options for the turn that should use them. Both `run()` and `session.stream()` accept these options.
|
|
781
|
+
|
|
379
782
|
### Images
|
|
380
783
|
|
|
381
784
|
```ts
|
|
@@ -390,12 +793,14 @@ const result = await run('Describe this image.', {
|
|
|
390
793
|
});
|
|
391
794
|
```
|
|
392
795
|
|
|
393
|
-
|
|
796
|
+
`data` contains the base64-encoded image bytes. Supported media types are `image/jpeg`, `image/png`, `image/gif`, and `image/webp`.
|
|
797
|
+
|
|
798
|
+
Runnable example: [`examples/node/image-attachment.ts`](../examples/node/image-attachment.ts)
|
|
394
799
|
|
|
395
800
|
### Documents
|
|
396
801
|
|
|
397
802
|
```ts
|
|
398
|
-
|
|
803
|
+
const result = await run('Summarize this document.', {
|
|
399
804
|
files: [
|
|
400
805
|
{
|
|
401
806
|
type: 'text',
|
|
@@ -404,109 +809,69 @@ for await (const _message of session.stream('Summarize this document.', {
|
|
|
404
809
|
name: 'report.txt',
|
|
405
810
|
},
|
|
406
811
|
],
|
|
407
|
-
})) {
|
|
408
|
-
// Consume messages.
|
|
409
|
-
}
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
Document inputs support plain text and base64 PDF data.
|
|
413
|
-
|
|
414
|
-
### Structured output
|
|
415
|
-
|
|
416
|
-
Use structured output when another system needs a predictable result.
|
|
417
|
-
|
|
418
|
-
```ts
|
|
419
|
-
import { OutputFormatType, run } from '@factory/droid-sdk/node';
|
|
420
|
-
|
|
421
|
-
const result = await run('Review this function for correctness issues.', {
|
|
422
|
-
outputFormat: {
|
|
423
|
-
type: OutputFormatType.JsonSchema,
|
|
424
|
-
schema: {
|
|
425
|
-
type: 'object',
|
|
426
|
-
properties: {
|
|
427
|
-
summary: { type: 'string' },
|
|
428
|
-
findings: {
|
|
429
|
-
type: 'array',
|
|
430
|
-
items: {
|
|
431
|
-
type: 'object',
|
|
432
|
-
properties: {
|
|
433
|
-
line: { type: 'number' },
|
|
434
|
-
severity: {
|
|
435
|
-
type: 'string',
|
|
436
|
-
enum: ['low', 'medium', 'high'],
|
|
437
|
-
},
|
|
438
|
-
message: { type: 'string' },
|
|
439
|
-
},
|
|
440
|
-
required: ['line', 'severity', 'message'],
|
|
441
|
-
},
|
|
442
|
-
},
|
|
443
|
-
},
|
|
444
|
-
required: ['summary', 'findings'],
|
|
445
|
-
},
|
|
446
|
-
},
|
|
447
812
|
});
|
|
448
|
-
|
|
449
|
-
if (!result.success) {
|
|
450
|
-
throw new Error(
|
|
451
|
-
result.structuredOutputError?.message ??
|
|
452
|
-
result.error?.message ??
|
|
453
|
-
'Structured output failed.'
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
console.log(result.structuredOutput);
|
|
458
813
|
```
|
|
459
814
|
|
|
460
|
-
|
|
461
|
-
it to another system. A successful result can omit structured output.
|
|
462
|
-
|
|
463
|
-
### Result metadata
|
|
815
|
+
Document inputs support:
|
|
464
816
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
console.log(result.tokenUsage);
|
|
470
|
-
console.log(result.messages);
|
|
471
|
-
```
|
|
817
|
+
| Source | `type` | `mediaType` | `data` |
|
|
818
|
+
| ------------ | -------- | ----------------- | ----------------------------- |
|
|
819
|
+
| Plain text | `text` | `text/plain` | The document text |
|
|
820
|
+
| PDF document | `base64` | `application/pdf` | Base64-encoded PDF file bytes |
|
|
472
821
|
|
|
473
|
-
`
|
|
822
|
+
`name` is optional for both forms. Attach content through `data`; a filesystem path by itself is not a document upload.
|
|
474
823
|
|
|
475
|
-
|
|
824
|
+
Runnable example: [`examples/node/document-input.ts`](../examples/node/document-input.ts)
|
|
476
825
|
|
|
477
|
-
###
|
|
826
|
+
### Structured output
|
|
827
|
+
|
|
828
|
+
Use `outputFormat` when another system needs a JSON object that follows a schema.
|
|
478
829
|
|
|
479
830
|
```ts
|
|
480
|
-
|
|
831
|
+
import { OutputFormatType, run } from '@factory/droid-sdk/node';
|
|
481
832
|
|
|
482
|
-
|
|
833
|
+
const result = await run('Return the repository name and test command.', {
|
|
834
|
+
outputFormat: {
|
|
835
|
+
type: OutputFormatType.JsonSchema,
|
|
836
|
+
schema: {
|
|
837
|
+
type: 'object',
|
|
838
|
+
properties: {
|
|
839
|
+
repository: { type: 'string' },
|
|
840
|
+
testCommand: { type: 'string' },
|
|
841
|
+
},
|
|
842
|
+
required: ['repository', 'testCommand'],
|
|
843
|
+
additionalProperties: false,
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
});
|
|
483
847
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
848
|
+
if (!result.success) {
|
|
849
|
+
throw new Error(
|
|
850
|
+
result.structuredOutputError?.message ??
|
|
851
|
+
result.error?.message ??
|
|
852
|
+
`Structured output failed (${result.subtype}).`
|
|
853
|
+
);
|
|
488
854
|
}
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
The stream throws the abort reason after interrupting the active turn.
|
|
492
855
|
|
|
493
|
-
|
|
856
|
+
if (!result.structuredOutput) {
|
|
857
|
+
throw new Error('No structured output was returned.');
|
|
858
|
+
}
|
|
494
859
|
|
|
495
|
-
|
|
496
|
-
await session.interrupt();
|
|
860
|
+
console.log(JSON.stringify(result.structuredOutput, null, 2));
|
|
497
861
|
```
|
|
498
862
|
|
|
499
|
-
|
|
500
|
-
terminal result, normally with subtype `interrupted`. The session remains
|
|
501
|
-
usable for later prompts.
|
|
863
|
+
Invalid or missing schema output normally produces a failed result with subtype `error_structured_output`; `structuredOutputError` contains the available validation details.
|
|
502
864
|
|
|
503
|
-
|
|
504
|
-
|
|
865
|
+
`structuredOutput` is typed as `unknown`, because a JSON Schema does not provide a TypeScript runtime validator. Check that it is present and validate or narrow its shape before reading fields or passing it to another system. A successful result can still omit structured output, so do not use `result.success` as the only check.
|
|
866
|
+
|
|
867
|
+
Runnable example: [`examples/node/structured-output.ts`](../examples/node/structured-output.ts)
|
|
505
868
|
|
|
506
869
|
## Permissions and user input
|
|
507
870
|
|
|
508
871
|
### Autonomy
|
|
509
872
|
|
|
873
|
+
Autonomy controls which actions require approval in Auto mode.
|
|
874
|
+
|
|
510
875
|
```ts
|
|
511
876
|
import { AutonomyLevel } from '@factory/droid-sdk/node';
|
|
512
877
|
|
|
@@ -515,11 +880,20 @@ const session = await createSession({
|
|
|
515
880
|
});
|
|
516
881
|
```
|
|
517
882
|
|
|
518
|
-
|
|
519
|
-
|
|
883
|
+
| Level | Behavior |
|
|
884
|
+
| -------- | --------------------------------------- |
|
|
885
|
+
| `Off` | Ask before every action |
|
|
886
|
+
| `Low` | Allow file edits and read-only commands |
|
|
887
|
+
| `Medium` | Allow reversible commands |
|
|
888
|
+
| `High` | Allow commands without approval prompts |
|
|
889
|
+
|
|
890
|
+
When omitted, Droid uses the configured default. Autonomy does not select Auto or Spec mode; `interactionMode` does that.
|
|
520
891
|
|
|
521
892
|
### Permission handler
|
|
522
893
|
|
|
894
|
+
Use `permissionHandler` to approve or reject requests that reach the client.
|
|
895
|
+
The handler may be synchronous or asynchronous.
|
|
896
|
+
|
|
523
897
|
```ts
|
|
524
898
|
import {
|
|
525
899
|
ToolConfirmationOutcome,
|
|
@@ -528,32 +902,45 @@ import {
|
|
|
528
902
|
|
|
529
903
|
const session = await createSession({
|
|
530
904
|
autonomyLevel: AutonomyLevel.Off,
|
|
531
|
-
permissionHandler(
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
return safe
|
|
905
|
+
permissionHandler({ toolUses, options }) {
|
|
906
|
+
const createsOnly =
|
|
907
|
+
toolUses.length > 0 &&
|
|
908
|
+
toolUses.every((use) => use.details.type === ToolConfirmationType.Create);
|
|
909
|
+
|
|
910
|
+
const desired = createsOnly
|
|
539
911
|
? ToolConfirmationOutcome.ProceedOnce
|
|
540
912
|
: ToolConfirmationOutcome.Cancel;
|
|
913
|
+
|
|
914
|
+
const offered = options.find((option) => option.value === desired);
|
|
915
|
+
if (!offered) throw new Error(`Outcome not offered: ${desired}`);
|
|
916
|
+
|
|
917
|
+
return desired;
|
|
541
918
|
},
|
|
542
919
|
});
|
|
543
920
|
```
|
|
544
921
|
|
|
545
|
-
|
|
546
|
-
|
|
922
|
+
`toolUses` describes the pending actions. `options` contains the outcomes available for this request. Return one of those values. Common outcomes are:
|
|
923
|
+
|
|
924
|
+
| Outcome | Effect |
|
|
925
|
+
| --------------- | --------------------------------------- |
|
|
926
|
+
| `ProceedOnce` | Approve this request |
|
|
927
|
+
| `ProceedAlways` | Approve and persist the applicable rule |
|
|
928
|
+
| `Cancel` | Reject the request |
|
|
547
929
|
|
|
548
|
-
|
|
930
|
+
The available outcomes vary by request. Returning an unavailable value, throwing from the handler, or omitting the handler cancels the request.
|
|
931
|
+
|
|
932
|
+
Runnable example:
|
|
933
|
+
[`examples/node/permission-handler.ts`](../examples/node/permission-handler.ts)
|
|
549
934
|
|
|
550
935
|
### AskUser handler
|
|
551
936
|
|
|
937
|
+
`askUserHandler` answers questions Droid asks during a turn. Connect it to a form, prompt, or other application UI.
|
|
938
|
+
|
|
552
939
|
```ts
|
|
553
940
|
const result = await run('Ask me which environment to deploy.', {
|
|
554
|
-
askUserHandler(
|
|
941
|
+
askUserHandler({ questions }) {
|
|
555
942
|
return {
|
|
556
|
-
answers:
|
|
943
|
+
answers: questions.map((question) => ({
|
|
557
944
|
index: question.index,
|
|
558
945
|
question: question.question,
|
|
559
946
|
answer: question.options[0] ?? 'none',
|
|
@@ -563,7 +950,7 @@ const result = await run('Ask me which environment to deploy.', {
|
|
|
563
950
|
});
|
|
564
951
|
```
|
|
565
952
|
|
|
566
|
-
To decline:
|
|
953
|
+
Each answer must copy the question's `index` and `question`. To decline the questionnaire:
|
|
567
954
|
|
|
568
955
|
```ts
|
|
569
956
|
return { cancelled: true, answers: [] };
|
|
@@ -571,16 +958,24 @@ return { cancelled: true, answers: [] };
|
|
|
571
958
|
|
|
572
959
|
Without a handler, AskUser requests are declined.
|
|
573
960
|
|
|
574
|
-
|
|
961
|
+
Runnable example:
|
|
962
|
+
[`examples/node/ask-user-elicitation.ts`](../examples/node/ask-user-elicitation.ts)
|
|
963
|
+
|
|
964
|
+
### Tool controls
|
|
965
|
+
|
|
966
|
+
Disable tools when creating, resuming, or updating a session:
|
|
575
967
|
|
|
576
968
|
```ts
|
|
577
969
|
const session = await createSession({
|
|
578
970
|
disabledToolIds: ['Execute'],
|
|
579
971
|
});
|
|
972
|
+
|
|
973
|
+
await session.updateSettings({
|
|
974
|
+
disabledToolIds: ['Execute', 'Edit'],
|
|
975
|
+
});
|
|
580
976
|
```
|
|
581
977
|
|
|
582
|
-
Tool IDs are strings. Use `listTools()` to inspect the tools available to the
|
|
583
|
-
current session.
|
|
978
|
+
Tool IDs are strings. Use `listTools()` to inspect the tools available to the current session.
|
|
584
979
|
|
|
585
980
|
```ts
|
|
586
981
|
const tools = await session.listTools();
|
|
@@ -593,27 +988,50 @@ for (const tool of tools) {
|
|
|
593
988
|
The public SDK provides subtractive `disabledToolIds`. It does not expose a
|
|
594
989
|
restrictive allowlist.
|
|
595
990
|
|
|
991
|
+
Runnable example: [`examples/node/tool-controls.ts`](../examples/node/tool-controls.ts)
|
|
992
|
+
|
|
596
993
|
## Tools and extensions
|
|
597
994
|
|
|
995
|
+
Use the extension that matches the job:
|
|
996
|
+
|
|
997
|
+
| Extension | Use it for |
|
|
998
|
+
| -------------------- | ------------------------------------------------ |
|
|
999
|
+
| Skills | Reusable instructions and supporting files |
|
|
1000
|
+
| SDK MCP tools | In-process TypeScript functions exposed to Droid |
|
|
1001
|
+
| External MCP servers | Tools provided by another process or service |
|
|
1002
|
+
| Hooks | Commands that run at defined lifecycle events |
|
|
1003
|
+
|
|
598
1004
|
### Skills
|
|
599
1005
|
|
|
1006
|
+
List the skills available to the session:
|
|
1007
|
+
|
|
600
1008
|
```ts
|
|
601
1009
|
const { skills } = await session.listSkills();
|
|
602
1010
|
|
|
1011
|
+
for (const skill of skills) {
|
|
1012
|
+
console.log(skill.name, skill.enabled);
|
|
1013
|
+
}
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
Enable or disable a skill at the user or project level:
|
|
1017
|
+
|
|
1018
|
+
```ts
|
|
1019
|
+
import { SettingsLevel } from '@factory/droid-sdk/node';
|
|
1020
|
+
|
|
603
1021
|
await session.setSkillDisabled({
|
|
604
1022
|
skillName: 'skill-name',
|
|
605
1023
|
disabled: true,
|
|
606
|
-
settingsLevel:
|
|
1024
|
+
settingsLevel: SettingsLevel.Project,
|
|
607
1025
|
});
|
|
608
1026
|
```
|
|
609
1027
|
|
|
610
|
-
Skills
|
|
1028
|
+
Skills can come from project, personal, built-in, or automation settings.
|
|
611
1029
|
|
|
612
|
-
|
|
1030
|
+
Runnable example: [`examples/node/skills.ts`](../examples/node/skills.ts)
|
|
613
1031
|
|
|
614
|
-
|
|
1032
|
+
### SDK MCP tools
|
|
615
1033
|
|
|
616
|
-
Use
|
|
1034
|
+
Use `tool()` to define an in-process TypeScript function with a Zod input schema.
|
|
617
1035
|
|
|
618
1036
|
```ts
|
|
619
1037
|
import { z } from 'zod';
|
|
@@ -623,7 +1041,7 @@ import {
|
|
|
623
1041
|
tool,
|
|
624
1042
|
} from '@factory/droid-sdk/node';
|
|
625
1043
|
|
|
626
|
-
const
|
|
1044
|
+
const server = createSdkMcpServer({
|
|
627
1045
|
name: 'review-tools',
|
|
628
1046
|
tools: [
|
|
629
1047
|
tool(
|
|
@@ -636,35 +1054,91 @@ const tools = createSdkMcpServer({
|
|
|
636
1054
|
});
|
|
637
1055
|
|
|
638
1056
|
const session = await createSession({
|
|
639
|
-
mcpServers: [
|
|
1057
|
+
mcpServers: [server],
|
|
640
1058
|
});
|
|
1059
|
+
|
|
1060
|
+
try {
|
|
1061
|
+
for await (const _message of session.stream(
|
|
1062
|
+
'Find the owner of src/index.ts.'
|
|
1063
|
+
)) {
|
|
1064
|
+
// Consume messages.
|
|
1065
|
+
}
|
|
1066
|
+
} finally {
|
|
1067
|
+
await session.close();
|
|
1068
|
+
}
|
|
641
1069
|
```
|
|
642
1070
|
|
|
643
|
-
The SDK starts an authenticated loopback MCP server and closes it with the
|
|
644
|
-
session.
|
|
1071
|
+
The SDK starts an authenticated loopback MCP server and closes it with the session.
|
|
1072
|
+
Tool calls still follow the session's autonomy and permission rules.
|
|
1073
|
+
Attach SDK MCP servers again when resuming a saved session because they are runtime objects, not persisted session data.
|
|
1074
|
+
|
|
1075
|
+
Runnable example: [`examples/node/sdk-mcp-tool.ts`](../examples/node/sdk-mcp-tool.ts)
|
|
645
1076
|
|
|
646
1077
|
### External MCP servers
|
|
647
1078
|
|
|
648
|
-
|
|
1079
|
+
Pass external MCP configuration when creating or resuming a session:
|
|
649
1080
|
|
|
650
1081
|
```ts
|
|
651
|
-
await
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
1082
|
+
const session = await createSession({
|
|
1083
|
+
mcpServers: [
|
|
1084
|
+
{
|
|
1085
|
+
name: 'docs',
|
|
1086
|
+
type: 'http',
|
|
1087
|
+
url: 'https://example.com/mcp',
|
|
1088
|
+
headers: [],
|
|
1089
|
+
},
|
|
1090
|
+
],
|
|
655
1091
|
});
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
Supported session configuration:
|
|
1095
|
+
|
|
1096
|
+
| Transport | `type` field | Required configuration |
|
|
1097
|
+
| ------------- | ------------ | ---------------------- |
|
|
1098
|
+
| Local process | Omit | `command` and `args` |
|
|
1099
|
+
| HTTP | `http` | `url` and `headers` |
|
|
1100
|
+
| SSE | `sse` | `url` and `headers` |
|
|
1101
|
+
|
|
1102
|
+
Inspect server status and discovered tools:
|
|
1103
|
+
|
|
1104
|
+
```ts
|
|
1105
|
+
try {
|
|
1106
|
+
const { servers, summary } = await session.listMcpServers();
|
|
1107
|
+
const tools = await session.listMcpTools();
|
|
656
1108
|
|
|
657
|
-
|
|
658
|
-
|
|
1109
|
+
console.log(servers, summary, tools);
|
|
1110
|
+
} finally {
|
|
1111
|
+
await session.close();
|
|
1112
|
+
}
|
|
659
1113
|
```
|
|
660
1114
|
|
|
661
|
-
|
|
1115
|
+
`addMcpServer()`, `removeMcpServer()`, and `toggleMcpServer()` change the user's Droid configuration.
|
|
1116
|
+
Use `authenticateMcpServer({ serverName })` when a server requires authentication.
|
|
1117
|
+
|
|
1118
|
+
Runnable example: [`examples/node/external-mcp.ts`](../examples/node/external-mcp.ts)
|
|
662
1119
|
|
|
663
1120
|
### Hooks
|
|
664
1121
|
|
|
665
|
-
Hooks
|
|
666
|
-
|
|
667
|
-
|
|
1122
|
+
Hooks run shell commands at defined lifecycle events.
|
|
1123
|
+
Configure them in `.factory/hooks.json`:
|
|
1124
|
+
|
|
1125
|
+
```json
|
|
1126
|
+
{
|
|
1127
|
+
"PreToolUse": [
|
|
1128
|
+
{
|
|
1129
|
+
"matcher": "Execute",
|
|
1130
|
+
"hooks": [
|
|
1131
|
+
{
|
|
1132
|
+
"type": "command",
|
|
1133
|
+
"command": "echo before Execute"
|
|
1134
|
+
}
|
|
1135
|
+
]
|
|
1136
|
+
}
|
|
1137
|
+
]
|
|
1138
|
+
}
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
Hooks can run before or after tools, when prompts are submitted, when notifications arrive, during compaction, and when sessions or subagents stop.
|
|
668
1142
|
|
|
669
1143
|
Hook events are available in the stream:
|
|
670
1144
|
|
|
@@ -676,32 +1150,17 @@ for await (const message of session.stream('Run the tests.')) {
|
|
|
676
1150
|
}
|
|
677
1151
|
```
|
|
678
1152
|
|
|
679
|
-
The SDK exposes hook schemas and
|
|
680
|
-
hook-registration API.
|
|
681
|
-
|
|
682
|
-
### Raw notifications
|
|
683
|
-
|
|
684
|
-
**Runtime: Node**
|
|
685
|
-
|
|
686
|
-
```ts
|
|
687
|
-
const unsubscribe = session.onNotification(
|
|
688
|
-
(notification) => console.log(notification),
|
|
689
|
-
{ type: 'settings_updated' }
|
|
690
|
-
);
|
|
691
|
-
|
|
692
|
-
unsubscribe();
|
|
693
|
-
```
|
|
1153
|
+
The SDK exposes hook schemas and stream events, but it does not provide a programmatic hook-registration API.
|
|
694
1154
|
|
|
695
|
-
|
|
696
|
-
need.
|
|
1155
|
+
Runnable example: [`examples/node/hook-execution.ts`](../examples/node/hook-execution.ts)
|
|
697
1156
|
|
|
698
1157
|
## Session lifecycle
|
|
699
1158
|
|
|
700
|
-
###
|
|
1159
|
+
### Replacement handles
|
|
701
1160
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1161
|
+
`fork()` returns a ready successor handle. `compact()` and `rewind()` return
|
|
1162
|
+
outcome objects containing a ready successor in `session`. Each operation
|
|
1163
|
+
retires its source wrapper.
|
|
705
1164
|
|
|
706
1165
|
```ts
|
|
707
1166
|
const forked = await session.fork();
|
|
@@ -727,362 +1186,159 @@ with `resumeSession(sourceId)`.
|
|
|
727
1186
|
|
|
728
1187
|
Do not replace a session while it has an active stream.
|
|
729
1188
|
|
|
730
|
-
### Daemon replacement IDs
|
|
731
|
-
|
|
732
|
-
Daemon operations return a new session ID. The source handle remains usable.
|
|
733
|
-
|
|
734
|
-
```ts
|
|
735
|
-
const { newSessionId } = await source.compact();
|
|
736
|
-
const compacted = await droid.sessions.resume(newSessionId);
|
|
737
|
-
```
|
|
738
|
-
|
|
739
|
-
The same rule applies to `fork()` and `rewind()`.
|
|
740
|
-
|
|
741
|
-
```ts
|
|
742
|
-
const forkResult = await source.fork();
|
|
743
|
-
const fork = await droid.sessions.resume(forkResult.newSessionId);
|
|
744
|
-
|
|
745
|
-
const rewindResult = await source.rewind({
|
|
746
|
-
messageId,
|
|
747
|
-
filesToRestore,
|
|
748
|
-
filesToDelete,
|
|
749
|
-
forkTitle: 'Before the failed change',
|
|
750
|
-
});
|
|
751
|
-
const rewound = await droid.sessions.resume(rewindResult.newSessionId);
|
|
752
|
-
```
|
|
753
|
-
|
|
754
|
-
Daemon replacement locking is local to one client handle. Avoid replacing or
|
|
755
|
-
streaming the same session through several daemon clients at once.
|
|
756
|
-
|
|
757
1189
|
## Spec mode
|
|
758
1190
|
|
|
759
|
-
|
|
1191
|
+
Spec mode lets Droid inspect the codebase and propose a plan without changing files. Start a session in Spec mode or switch an existing session with `enterSpecMode()`:
|
|
760
1192
|
|
|
761
1193
|
```ts
|
|
762
|
-
import {
|
|
763
|
-
createSession,
|
|
764
|
-
DroidInteractionMode,
|
|
765
|
-
ReasoningEffort,
|
|
766
|
-
} from '@factory/droid-sdk/node';
|
|
767
|
-
|
|
768
1194
|
const session = await createSession({
|
|
769
1195
|
interactionMode: DroidInteractionMode.Spec,
|
|
770
|
-
specModeReasoningEffort: ReasoningEffort.High,
|
|
771
1196
|
});
|
|
1197
|
+
|
|
1198
|
+
// Or switch an existing Auto session.
|
|
1199
|
+
await session.enterSpecMode();
|
|
772
1200
|
```
|
|
773
1201
|
|
|
774
|
-
|
|
1202
|
+
`enterSpecMode()` can also set the model used for planning:
|
|
775
1203
|
|
|
776
1204
|
```ts
|
|
777
1205
|
await session.enterSpecMode({
|
|
1206
|
+
specModeModelId: 'model-id',
|
|
778
1207
|
specModeReasoningEffort: ReasoningEffort.High,
|
|
779
1208
|
});
|
|
780
1209
|
```
|
|
781
1210
|
|
|
782
|
-
|
|
783
|
-
implementation in the same session or hand it to a new session.
|
|
1211
|
+
### Leave without approving a plan
|
|
784
1212
|
|
|
785
|
-
|
|
786
|
-
| ----------------------- | --------------------------------------------------- |
|
|
787
|
-
| `ProceedOnce` | Continue in the same session |
|
|
788
|
-
| `ProceedNewSessionHigh` | Start implementation in a new high-autonomy session |
|
|
1213
|
+
Call `exitSpecMode()` to return the session to Auto mode without approving a plan or starting implementation:
|
|
789
1214
|
|
|
790
|
-
|
|
1215
|
+
```ts
|
|
1216
|
+
await session.enterSpecMode();
|
|
791
1217
|
|
|
792
|
-
|
|
1218
|
+
// Return to Auto mode without approving or implementing the plan.
|
|
1219
|
+
await session.exitSpecMode();
|
|
1220
|
+
```
|
|
793
1221
|
|
|
794
|
-
|
|
1222
|
+
`exitSpecMode()` changes the interaction mode only. It does not select a `ToolConfirmationOutcome` or approve an `ExitSpecMode` permission request.
|
|
795
1223
|
|
|
796
|
-
|
|
797
|
-
const droid = await connectToDaemon({
|
|
798
|
-
url: 'ws://127.0.0.1:37643',
|
|
799
|
-
auth: { apiKey },
|
|
800
|
-
permissionHandler,
|
|
801
|
-
askUserHandler,
|
|
802
|
-
onError(error) {
|
|
803
|
-
console.error(error);
|
|
804
|
-
},
|
|
805
|
-
});
|
|
806
|
-
```
|
|
1224
|
+
The method is available on Node `DroidSession` handles returned by `createSession()`, `resumeSession()`, and replacement operations such as `fork()`, `compact()`, and `rewind()`. Daemon clients continue to change the mode with `droid.sessions.updateSettings()`.
|
|
807
1225
|
|
|
808
|
-
|
|
809
|
-
Per-session handlers override them.
|
|
1226
|
+
### Approve a plan
|
|
810
1227
|
|
|
811
|
-
|
|
1228
|
+
When the plan is ready, Droid sends an `ExitSpecMode` permission request. Its
|
|
1229
|
+
details contain the plan. Unlike calling `exitSpecMode()`, approving it with `ProceedOnce` accepts the completed plan, returns to Auto mode, and continues implementation in the same session:
|
|
812
1230
|
|
|
813
1231
|
```ts
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1232
|
+
import {
|
|
1233
|
+
AutonomyLevel,
|
|
1234
|
+
createSession,
|
|
1235
|
+
DroidInteractionMode,
|
|
1236
|
+
DroidMessageType,
|
|
1237
|
+
ToolConfirmationOutcome,
|
|
1238
|
+
ToolConfirmationType,
|
|
1239
|
+
} from '@factory/droid-sdk/node';
|
|
820
1240
|
|
|
821
|
-
|
|
1241
|
+
const session = await createSession({
|
|
1242
|
+
interactionMode: DroidInteractionMode.Auto,
|
|
1243
|
+
autonomyLevel: AutonomyLevel.Low,
|
|
1244
|
+
permissionHandler({ toolUses }) {
|
|
1245
|
+
const request = toolUses.find(
|
|
1246
|
+
({ details }) => details.type === ToolConfirmationType.ExitSpecMode
|
|
1247
|
+
);
|
|
822
1248
|
|
|
823
|
-
|
|
1249
|
+
if (request?.details.type === ToolConfirmationType.ExitSpecMode) {
|
|
1250
|
+
console.log(request.details.plan);
|
|
824
1251
|
|
|
825
|
-
|
|
826
|
-
|
|
1252
|
+
return ToolConfirmationOutcome.ProceedOnce;
|
|
1253
|
+
}
|
|
827
1254
|
|
|
828
|
-
|
|
829
|
-
|
|
1255
|
+
return ToolConfirmationOutcome.Cancel;
|
|
1256
|
+
},
|
|
830
1257
|
});
|
|
831
1258
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1259
|
+
try {
|
|
1260
|
+
await session.enterSpecMode();
|
|
1261
|
+
|
|
1262
|
+
for await (const message of session.stream('Plan and update README.md.')) {
|
|
1263
|
+
if (message.type === DroidMessageType.Result) {
|
|
1264
|
+
console.log(message.subtype);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
} finally {
|
|
1268
|
+
await session.close();
|
|
1269
|
+
}
|
|
835
1270
|
```
|
|
836
1271
|
|
|
837
|
-
The
|
|
838
|
-
archive/unarchive, rename, settings, context information, and queued-message
|
|
839
|
-
operations.
|
|
1272
|
+
The handler can be asynchronous. An interactive application should display the plan and wait for the user's decision before returning.
|
|
840
1273
|
|
|
841
|
-
|
|
1274
|
+
Return `Cancel` to reject the plan. The turn ends with an `interrupted` result, and the session remains available for another prompt.
|
|
842
1275
|
|
|
843
|
-
|
|
1276
|
+
Some `ExitSpecMode` requests offer `ProceedNewSession` variants. Return one of those offered values to hand implementation to a new session. Return only values present in the request's `options`.
|
|
844
1277
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
consume(first.stream('Review the API.')),
|
|
848
|
-
consume(second.stream('Review the tests.')),
|
|
849
|
-
]);
|
|
850
|
-
```
|
|
1278
|
+
Runnable example:
|
|
1279
|
+
[`examples/node/enter-spec-mode.ts`](../examples/node/enter-spec-mode.ts)
|
|
851
1280
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
| -------------------- | -------------- | ---------------------- | ---------- |
|
|
856
|
-
| `session.detach()` | Keeps running | Detached | Retained |
|
|
857
|
-
| `session.close()` | Ends | Detached after success | Retained |
|
|
858
|
-
| `droid.disconnect()` | Keeps running | All detached | Destroyed |
|
|
859
|
-
|
|
860
|
-
Detached handles cannot be reused. Resume the session to obtain a new handle.
|
|
861
|
-
|
|
862
|
-
### Stable daemon resources
|
|
863
|
-
|
|
864
|
-
| Namespace | Main methods |
|
|
865
|
-
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
866
|
-
| `sessions` | `list`, `listOpened`, `create`, `resume`, `getMessages`, `search`, `archive`, `unarchive`, `rename`, `updateSettings`, `resolveQueuedMessage`, `killWorker`, `getRewindInfo`, `rewind`, `compact`, `fork`, `getContextBreakdown` |
|
|
867
|
-
| `workspace` | `changeDirectory`, `validateDirectory`, `checkTrust`, `trust`, `listFiles`, `searchFiles`, `getFileContent` |
|
|
868
|
-
| `settings` | `getDefaults`, `updateDefaults` |
|
|
869
|
-
| `customModels` | `list`, `upsert`, `delete` |
|
|
870
|
-
| `ssh` | `installKey` |
|
|
871
|
-
| `updates` | `trigger` |
|
|
872
|
-
| `relay` | `start`, `stop`, `status` |
|
|
873
|
-
| `terminals` | `create`, `write`, `resize`, `close`, `list` |
|
|
874
|
-
| `mcp` | `getConfig`, `updateConfig`, `addServer`, `removeServer`, `toggleServer`, authentication, registry, and tool methods |
|
|
875
|
-
| `skills` | `list`, `setDisabled` |
|
|
876
|
-
| `commands` | `list` |
|
|
877
|
-
| `plugins` | `listAvailable`, `listInstalled`, `install`, `uninstall`, `setEnabled`, `update` |
|
|
878
|
-
| `marketplaces` | `list`, `add`, `remove`, `update` |
|
|
879
|
-
| `automations` | `list`, `run`, `pause`, `resume`, history, visual, create, update, rename, delete, fork, and config methods |
|
|
880
|
-
| `git` | `getDiff`, `listBranches`, `checkoutBranch`, `push`, `commit`, `createPullRequest`, `resolvePullRequestStatuses` |
|
|
881
|
-
| `feedback` | `submitBugReport` |
|
|
882
|
-
|
|
883
|
-
### Facade return shapes
|
|
884
|
-
|
|
885
|
-
The namespaced daemon facade normalizes common collection results:
|
|
886
|
-
|
|
887
|
-
- `sessions.list()` returns session summaries with `id`, `messageCount`, and
|
|
888
|
-
`modifiedTime`.
|
|
889
|
-
- `sessions.getMessages()` returns the message array directly.
|
|
890
|
-
- Collection methods such as `customModels.list()`, `terminals.list()`,
|
|
891
|
-
`mcp.listRegistry()`, `mcp.listTools()`, `commands.list()`, and the plugin,
|
|
892
|
-
marketplace, automation, and Software Factory list methods return arrays.
|
|
893
|
-
- Methods whose protocol result includes additional metadata retain the result
|
|
894
|
-
object. This includes `sessions.search()`, `mcp.listServers()`, and
|
|
895
|
-
`skills.list()`.
|
|
896
|
-
|
|
897
|
-
The low-level `DaemonClient` returns canonical protocol response types instead
|
|
898
|
-
of these normalized facade shapes.
|
|
899
|
-
|
|
900
|
-
## Observability
|
|
901
|
-
|
|
902
|
-
**Runtime: Node**
|
|
1281
|
+
## Operations
|
|
1282
|
+
|
|
1283
|
+
### Observability
|
|
903
1284
|
|
|
904
1285
|
```ts
|
|
905
|
-
|
|
1286
|
+
import { run } from '@factory/droid-sdk/node';
|
|
1287
|
+
|
|
1288
|
+
const result = await run('Check the repository status.', {
|
|
906
1289
|
observability: {
|
|
907
1290
|
logger: {
|
|
908
1291
|
log(event) {
|
|
909
|
-
console.log(event.level, event.
|
|
910
|
-
},
|
|
911
|
-
},
|
|
912
|
-
metrics: {
|
|
913
|
-
record(event) {
|
|
914
|
-
console.log(event.name, event.value);
|
|
915
|
-
},
|
|
916
|
-
},
|
|
917
|
-
tracing: {
|
|
918
|
-
inject(carrier) {
|
|
919
|
-
carrier.traceparent = currentTraceparent;
|
|
1292
|
+
console.log(event.level, event.name, event.attributes);
|
|
920
1293
|
},
|
|
921
1294
|
},
|
|
922
1295
|
},
|
|
923
1296
|
});
|
|
924
|
-
```
|
|
925
|
-
|
|
926
|
-
Observability sinks must not throw into SDK operations. The SDK avoids sending
|
|
927
|
-
prompt text, message content, tool inputs, raw output, and stack traces through
|
|
928
|
-
its observability contracts.
|
|
929
1297
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
### Low-level daemon integration
|
|
933
|
-
|
|
934
|
-
Most applications should use `connectToDaemon()`. The root entrypoint also
|
|
935
|
-
exports `DaemonClient`, `DaemonSessionController`, `MultiSessionStateManager`,
|
|
936
|
-
`MultiMissionStateManager`, `WebSocketDaemonTransport`, daemon events, and
|
|
937
|
-
canonical protocol types for hosts that need custom connection or state
|
|
938
|
-
ownership.
|
|
939
|
-
|
|
940
|
-
Node-only IPC and in-process transports come from the `/node` entrypoint:
|
|
941
|
-
|
|
942
|
-
```ts
|
|
943
|
-
import { DaemonClient, MachineType } from '@factory/droid-sdk';
|
|
944
|
-
import {
|
|
945
|
-
InProcessDaemonClientTransport,
|
|
946
|
-
type InProcessDaemonClientTransportOptions,
|
|
947
|
-
type InProcessMessageHandler,
|
|
948
|
-
} from '@factory/droid-sdk/node';
|
|
949
|
-
|
|
950
|
-
function createInProcessClient(
|
|
951
|
-
sendMessage: InProcessMessageHandler,
|
|
952
|
-
onMessage: NonNullable<InProcessDaemonClientTransportOptions['onMessage']>
|
|
953
|
-
) {
|
|
954
|
-
const transport = new InProcessDaemonClientTransport({
|
|
955
|
-
sendMessage,
|
|
956
|
-
onMessage,
|
|
957
|
-
});
|
|
958
|
-
|
|
959
|
-
return new DaemonClient({
|
|
960
|
-
machineType: MachineType.Local,
|
|
961
|
-
transport,
|
|
962
|
-
});
|
|
1298
|
+
if (!result.success) {
|
|
1299
|
+
throw new Error(result.error?.message ?? `Run failed: ${result.subtype}`);
|
|
963
1300
|
}
|
|
964
1301
|
```
|
|
965
1302
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
```ts
|
|
973
|
-
import { configureDaemonCoreDeps } from '@factory/droid-sdk';
|
|
974
|
-
|
|
975
|
-
configureDaemonCoreDeps({
|
|
976
|
-
logger: {
|
|
977
|
-
info: (message, metadata) => logger.info(message, metadata),
|
|
978
|
-
error: (message, metadata) => logger.error(message, metadata),
|
|
979
|
-
},
|
|
980
|
-
});
|
|
981
|
-
```
|
|
982
|
-
|
|
983
|
-
Unspecified dependencies keep their standalone defaults.
|
|
984
|
-
|
|
985
|
-
### Low-level Node integration
|
|
986
|
-
|
|
987
|
-
Most Node applications should use `run()` or `DroidSession`. The `/node`
|
|
988
|
-
entrypoint also exports `ProcessTransport` and `DroidClient` for direct
|
|
989
|
-
JSON-RPC integrations.
|
|
990
|
-
|
|
991
|
-
Low-level RPC methods return complete response envelopes. Read successful
|
|
992
|
-
payloads from `response.result`. Direct permission and AskUser handlers receive
|
|
993
|
-
complete request events, while high-level session handlers receive request
|
|
994
|
-
parameters.
|
|
995
|
-
|
|
996
|
-
`ProcessTransport` uses `droidExecPath` and `droidExecExtraArgs` for executable
|
|
997
|
-
configuration. A custom `StringFramedDroidClientTransport` exchanges
|
|
998
|
-
newline-delimited JSON-RPC strings and can replace subprocess transport
|
|
999
|
-
entirely.
|
|
1000
|
-
|
|
1001
|
-
### Context and token usage
|
|
1002
|
-
|
|
1003
|
-
Read the current context-window estimate:
|
|
1004
|
-
|
|
1005
|
-
```ts
|
|
1006
|
-
const stats = await session.getContextStats();
|
|
1303
|
+
Pass `logger`, `metrics`, or `tracing` sinks through `observability`.
|
|
1304
|
+
Sink methods must not throw.
|
|
1305
|
+
The SDK excludes prompts, messages, tool inputs, raw output, and stack traces from observability events.
|
|
1306
|
+
Process startup emits telemetry before the first turn, and `close()` emits a request log.
|
|
1307
|
+
To verify turn-specific logs, snapshot the count after creating the session and check it before closing.
|
|
1007
1308
|
|
|
1008
|
-
|
|
1009
|
-
|
|
1309
|
+
Runnable example:
|
|
1310
|
+
[`examples/node/observability.ts`](../examples/node/observability.ts)
|
|
1010
1311
|
|
|
1011
|
-
|
|
1312
|
+
### Low-level APIs
|
|
1012
1313
|
|
|
1013
|
-
|
|
1014
|
-
for await (const message of session.stream('Summarize this repository.', {
|
|
1015
|
-
includePartialMessages: true,
|
|
1016
|
-
})) {
|
|
1017
|
-
if (message.type === DroidMessageType.TokenUsageUpdate) {
|
|
1018
|
-
console.log(message.inputTokens, message.outputTokens);
|
|
1019
|
-
}
|
|
1020
|
-
}
|
|
1021
|
-
```
|
|
1314
|
+
Use the high-level APIs unless your application owns the transport or protocol lifecycle.
|
|
1022
1315
|
|
|
1023
|
-
|
|
1316
|
+
| Need | Preferred API | Low-level alternative |
|
|
1317
|
+
| ---------------------------- | ------------------ | --------------------- |
|
|
1318
|
+
| Run a session | `createSession()` | `DroidClient` |
|
|
1319
|
+
| Consume typed session events | `session.stream()` | Raw notifications |
|
|
1024
1320
|
|
|
1025
|
-
|
|
1321
|
+
The `/node` entrypoint exports `DroidClient` for direct JSON-RPC integrations.
|
|
1322
|
+
Low-level methods return response envelopes, so successful payloads are under `response.result`.
|
|
1026
1323
|
|
|
1027
|
-
|
|
1028
|
-
templates, metrics, dependency installation, and remote-session listing.
|
|
1324
|
+
Use `onNotification()` only when `session.stream()` does not expose the event you need:
|
|
1029
1325
|
|
|
1030
1326
|
```ts
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1327
|
+
const unsubscribe = session.onNotification(
|
|
1328
|
+
(notification) => console.log(notification.type),
|
|
1329
|
+
{ type: 'settings_updated' }
|
|
1330
|
+
);
|
|
1034
1331
|
|
|
1035
|
-
|
|
1036
|
-
const sessions = await listRemoteSessions(options);
|
|
1332
|
+
unsubscribe();
|
|
1037
1333
|
```
|
|
1038
1334
|
|
|
1039
|
-
|
|
1040
|
-
different Factory API host.
|
|
1041
|
-
|
|
1042
|
-
| Resource | Functions |
|
|
1043
|
-
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1044
|
-
| Machine templates | `listMachineTemplates`, `getMachineTemplate` |
|
|
1045
|
-
| Computers | `listComputers`, `getComputer`, `getComputerByName`, `createComputer`, `updateComputer`, `deleteComputer`, `restartComputer`, `refreshComputer` |
|
|
1046
|
-
| Computer operations | `getComputerMetrics`, `retryInstallDeps` |
|
|
1047
|
-
| Remote sessions | `listRemoteSessions` |
|
|
1048
|
-
|
|
1049
|
-
### Unstable daemon resources
|
|
1050
|
-
|
|
1051
|
-
APIs under `droid.unstable` may change between SDK releases.
|
|
1052
|
-
|
|
1053
|
-
| Namespace | Main methods or resources |
|
|
1054
|
-
| ----------------- | ------------------------------------------------------------------------- |
|
|
1055
|
-
| `crons` | `list`, `create`, `update`, `delete`, `holdSession`, `resumeSession` |
|
|
1056
|
-
| `missions` | `inspectReadiness`, `acknowledgeReadinessWarning` |
|
|
1057
|
-
| `semanticDiff` | `getCache`, `saveCache`, `generate` |
|
|
1058
|
-
| `fileTransfers` | `push`, `pull` |
|
|
1059
|
-
| `proxy` | `getToken` |
|
|
1060
|
-
| `softwareFactory` | `workstreams`, `signals`, `changes`, `activities`, and `events` resources |
|
|
1061
|
-
|
|
1062
|
-
Keep unstable API use behind an application-owned adapter.
|
|
1063
|
-
|
|
1064
|
-
For example, create and manage a scheduled prompt through the unstable cron
|
|
1065
|
-
resource:
|
|
1066
|
-
|
|
1067
|
-
```ts
|
|
1068
|
-
const cron = await droid.unstable.crons.create({
|
|
1069
|
-
kind: 'root_prompt',
|
|
1070
|
-
scope: { type: 'root' },
|
|
1071
|
-
schedule: { expression: '0 * * * *', recurring: true },
|
|
1072
|
-
payload: {
|
|
1073
|
-
type: 'prompt',
|
|
1074
|
-
prompt: 'Check repository health',
|
|
1075
|
-
target: { type: 'new_session', cwd: '/workspace' },
|
|
1076
|
-
},
|
|
1077
|
-
});
|
|
1335
|
+
## API reference
|
|
1078
1336
|
|
|
1079
|
-
|
|
1080
|
-
await droid.unstable.crons.delete(cron.id);
|
|
1081
|
-
```
|
|
1337
|
+
### Entrypoint
|
|
1082
1338
|
|
|
1083
|
-
|
|
1339
|
+
Import functions, classes, enums, and types from `@factory/droid-sdk/node`.
|
|
1084
1340
|
|
|
1085
|
-
###
|
|
1341
|
+
### Functions
|
|
1086
1342
|
|
|
1087
1343
|
| API | Returns |
|
|
1088
1344
|
| ------------------------------------ | ---------------------------- |
|
|
@@ -1090,37 +1346,52 @@ await droid.unstable.crons.delete(cron.id);
|
|
|
1090
1346
|
| `createSession(options?)` | `Promise<DroidSession>` |
|
|
1091
1347
|
| `resumeSession(sessionId, options?)` | `Promise<DroidSession>` |
|
|
1092
1348
|
| `listSessions(options?)` | `Promise<SessionMetadata[]>` |
|
|
1349
|
+
| `listModels(options?)` | `Promise<ModelInfo[]>` |
|
|
1093
1350
|
| `createSdkMcpServer(options)` | `SdkMcpServer` |
|
|
1094
1351
|
| `tool(...)` | `DroidTool` |
|
|
1095
1352
|
|
|
1096
1353
|
### `DroidSession`
|
|
1097
1354
|
|
|
1098
|
-
| Member | Purpose
|
|
1099
|
-
| ------------------------- |
|
|
1100
|
-
| `id` | Session ID
|
|
1101
|
-
| `cwd` | Live working directory
|
|
1102
|
-
| `settings` | Live read-only settings
|
|
1103
|
-
| `stream()` | Run one turn
|
|
1104
|
-
| `interrupt()` | Stop the active turn
|
|
1105
|
-
| `updateSettings()` | Update session settings
|
|
1106
|
-
| `enterSpecMode()` | Change to spec mode
|
|
1107
|
-
| `
|
|
1108
|
-
| `
|
|
1109
|
-
| `
|
|
1110
|
-
| `
|
|
1111
|
-
| `
|
|
1112
|
-
| `
|
|
1113
|
-
| `
|
|
1114
|
-
| `
|
|
1115
|
-
| `
|
|
1116
|
-
| `
|
|
1117
|
-
| `
|
|
1118
|
-
| `
|
|
1119
|
-
| `
|
|
1120
|
-
| `
|
|
1121
|
-
| `
|
|
1122
|
-
| `
|
|
1123
|
-
| `
|
|
1355
|
+
| Member | Purpose |
|
|
1356
|
+
| ------------------------- | -------------------------------------------- |
|
|
1357
|
+
| `id` | Session ID |
|
|
1358
|
+
| `cwd` | Live working directory |
|
|
1359
|
+
| `settings` | Live read-only settings |
|
|
1360
|
+
| `stream()` | Run one turn |
|
|
1361
|
+
| `interrupt()` | Stop the active turn |
|
|
1362
|
+
| `updateSettings()` | Update session settings |
|
|
1363
|
+
| `enterSpecMode()` | Change to spec mode |
|
|
1364
|
+
| `exitSpecMode()` | Return to Auto mode without approving a plan |
|
|
1365
|
+
| `listTools()` | List normalized tools |
|
|
1366
|
+
| `listSkills()` | List skills |
|
|
1367
|
+
| `setSkillDisabled()` | Enable or disable a skill |
|
|
1368
|
+
| `addMcpServer()` | Add an MCP server |
|
|
1369
|
+
| `removeMcpServer()` | Remove an MCP server |
|
|
1370
|
+
| `toggleMcpServer()` | Enable or disable an MCP server |
|
|
1371
|
+
| `listMcpServers()` | List MCP servers |
|
|
1372
|
+
| `listMcpTools()` | List MCP tools |
|
|
1373
|
+
| `authenticateMcpServer()` | Start MCP authentication |
|
|
1374
|
+
| `getContextStats()` | Read context usage |
|
|
1375
|
+
| `getRewindInfo()` | Inspect rewindable file changes |
|
|
1376
|
+
| `rewind()` | Create a rewound successor |
|
|
1377
|
+
| `compact()` | Create a compacted successor |
|
|
1378
|
+
| `fork()` | Create a forked successor |
|
|
1379
|
+
| `rename()` | Change the session title |
|
|
1380
|
+
| `onNotification()` | Subscribe to raw notifications |
|
|
1381
|
+
| `close()` | Close the session and owned resources |
|
|
1382
|
+
|
|
1383
|
+
### Stream and model types
|
|
1384
|
+
|
|
1385
|
+
| Type | Purpose |
|
|
1386
|
+
| -------------------- | ------------------------------------------------- |
|
|
1387
|
+
| `DroidStreamMessage` | Complete messages returned by the default stream |
|
|
1388
|
+
| `DroidStreamEvent` | Complete messages plus partial events |
|
|
1389
|
+
| `DroidResult` | Terminal result returned by `run()` |
|
|
1390
|
+
| `DroidMessageType` | Runtime constants for narrowing stream messages |
|
|
1391
|
+
| `TokenUsage` | Token counts and optional Factory Service Credits |
|
|
1392
|
+
| `ModelInfo` | Model catalog entry returned by discovery APIs |
|
|
1393
|
+
|
|
1394
|
+
`session.stream()` yields `DroidStreamMessage` by default and `DroidStreamEvent` when `includePartialMessages` is `true`.
|
|
1124
1395
|
|
|
1125
1396
|
### Result states
|
|
1126
1397
|
|
|
@@ -1131,130 +1402,36 @@ await droid.unstable.crons.delete(cron.id);
|
|
|
1131
1402
|
| `error_during_execution` | `false` | `false` | Runtime failure |
|
|
1132
1403
|
| `error_structured_output` | `false` | `false` | Structured-output failure |
|
|
1133
1404
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
```ts
|
|
1137
|
-
{
|
|
1138
|
-
type: 'result';
|
|
1139
|
-
subtype:
|
|
1140
|
-
| 'success'
|
|
1141
|
-
| 'interrupted'
|
|
1142
|
-
| 'error_during_execution'
|
|
1143
|
-
| 'error_structured_output';
|
|
1144
|
-
sessionId: string;
|
|
1145
|
-
durationMs: number;
|
|
1146
|
-
tokenUsage: TokenUsage | null;
|
|
1147
|
-
messages: DroidStreamEvent[];
|
|
1148
|
-
text: string;
|
|
1149
|
-
turnCount: number;
|
|
1150
|
-
success: boolean;
|
|
1151
|
-
interrupted: boolean;
|
|
1152
|
-
structuredOutput?: unknown;
|
|
1153
|
-
structuredOutputError?: StructuredOutputError | null;
|
|
1154
|
-
error: ErrorEvent | null;
|
|
1155
|
-
}
|
|
1156
|
-
```
|
|
1405
|
+
Every result includes `sessionId`, `durationMs`, `tokenUsage`, `messages`, `text`, and `turnCount`.
|
|
1406
|
+
Check `success` before using successful output.
|
|
1157
1407
|
|
|
1158
1408
|
### Main enums
|
|
1159
1409
|
|
|
1160
1410
|
| Enum | Values |
|
|
1161
1411
|
| ---------------------- | -------------------------------------------------------------------------------- |
|
|
1162
1412
|
| `AutonomyLevel` | `Off`, `Low`, `Medium`, `High` |
|
|
1163
|
-
| `DroidInteractionMode` | `Auto`, `Spec
|
|
1413
|
+
| `DroidInteractionMode` | `Auto`, `Spec` |
|
|
1164
1414
|
| `ReasoningEffort` | `None`, `Dynamic`, `Off`, `Minimal`, `Low`, `Medium`, `High`, `ExtraHigh`, `Max` |
|
|
1165
1415
|
| `OutputFormatType` | `JsonSchema` |
|
|
1166
1416
|
|
|
1167
|
-
Model IDs and tool IDs are strings
|
|
1168
|
-
runtime instead of treating them as closed enums.
|
|
1417
|
+
Model IDs and tool IDs are strings rather than closed enums.
|
|
1169
1418
|
|
|
1170
1419
|
### Main errors
|
|
1171
1420
|
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
|
1175
|
-
|
|
|
1176
|
-
| `
|
|
1177
|
-
| `
|
|
1178
|
-
| `
|
|
1179
|
-
| `
|
|
1180
|
-
| `
|
|
1181
|
-
| `
|
|
1182
|
-
| `
|
|
1183
|
-
| `
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
| Error | Meaning |
|
|
1188
|
-
| ------------------------ | -------------------------------------------- |
|
|
1189
|
-
| `DroidClientError` | Base Node client error |
|
|
1190
|
-
| `ConnectionError` | Droid process or transport connection failed |
|
|
1191
|
-
| `TimeoutError` | A request timed out |
|
|
1192
|
-
| `ProtocolError` | Protocol or API response failed |
|
|
1193
|
-
| `SessionError` | Base session error |
|
|
1194
|
-
| `SessionNotFoundError` | Saved session was not found |
|
|
1195
|
-
| `InvalidSessionCwdError` | Saved working directory is invalid |
|
|
1196
|
-
| `ProcessExitError` | Droid subprocess exited unexpectedly |
|
|
1197
|
-
|
|
1198
|
-
### Custom transports
|
|
1199
|
-
|
|
1200
|
-
**Runtime: Node, advanced**
|
|
1201
|
-
|
|
1202
|
-
```ts
|
|
1203
|
-
interface StringFramedDroidClientTransport {
|
|
1204
|
-
isConnected: boolean;
|
|
1205
|
-
send(message: string): Promise<void>;
|
|
1206
|
-
onMessage(handler: (message: string) => void): void;
|
|
1207
|
-
onError(handler: (error: Error) => void): void;
|
|
1208
|
-
close(): Promise<void>;
|
|
1209
|
-
}
|
|
1210
|
-
```
|
|
1211
|
-
|
|
1212
|
-
Pass a ready transport through `run()`, `createSession()`, or
|
|
1213
|
-
`resumeSession()`. A custom transport bypasses subprocess creation and the
|
|
1214
|
-
Node API-key requirement.
|
|
1215
|
-
|
|
1216
|
-
## Errors and limitations
|
|
1217
|
-
|
|
1218
|
-
### Handle results explicitly
|
|
1219
|
-
|
|
1220
|
-
```ts
|
|
1221
|
-
const result = await run('Run the test suite.');
|
|
1222
|
-
|
|
1223
|
-
switch (result.subtype) {
|
|
1224
|
-
case 'success':
|
|
1225
|
-
console.log(result.text);
|
|
1226
|
-
break;
|
|
1227
|
-
case 'interrupted':
|
|
1228
|
-
console.log('The run was interrupted.');
|
|
1229
|
-
break;
|
|
1230
|
-
case 'error_during_execution':
|
|
1231
|
-
case 'error_structured_output':
|
|
1232
|
-
console.error(
|
|
1233
|
-
result.structuredOutputError?.message ??
|
|
1234
|
-
result.error?.message ??
|
|
1235
|
-
'Unknown error'
|
|
1236
|
-
);
|
|
1237
|
-
break;
|
|
1238
|
-
}
|
|
1239
|
-
```
|
|
1240
|
-
|
|
1241
|
-
### Known limitations
|
|
1421
|
+
| Error | Meaning |
|
|
1422
|
+
| ------------------------- | --------------------------------------------- |
|
|
1423
|
+
| `ConcurrentStreamError` | A session handle already has an active stream |
|
|
1424
|
+
| `SessionReplacedError` | A source wrapper was replaced |
|
|
1425
|
+
| `SessionReplacementError` | Successor loading or rollback failed |
|
|
1426
|
+
| `DroidClientError` | Base client error |
|
|
1427
|
+
| `ConnectionError` | Droid process connection failed |
|
|
1428
|
+
| `TimeoutError` | A request timed out |
|
|
1429
|
+
| `ProtocolError` | Protocol or API response failed |
|
|
1430
|
+
| `SessionError` | Base session error |
|
|
1431
|
+
| `SessionNotFoundError` | Saved session was not found |
|
|
1432
|
+
| `InvalidSessionCwdError` | Saved working directory is invalid |
|
|
1433
|
+
| `ProcessExitError` | Droid subprocess exited unexpectedly |
|
|
1434
|
+
|
|
1435
|
+
## Known limitations
|
|
1242
1436
|
|
|
1243
|
-
- Daemon replacement locking does not coordinate separate clients.
|
|
1244
|
-
- Browser daemon authentication currently requires an API key.
|
|
1245
1437
|
- New-session spec handoff IDs require raw notification inspection.
|
|
1246
|
-
- REST remote-session support currently provides listing only.
|
|
1247
|
-
|
|
1248
|
-
## Runnable examples
|
|
1249
|
-
|
|
1250
|
-
The package includes runnable examples for one-shot runs, streaming, sessions,
|
|
1251
|
-
permissions, AskUser, attachments, structured output, MCP tools, hooks,
|
|
1252
|
-
observability, spec mode, replacement lifecycle, daemon sessions, concurrent
|
|
1253
|
-
browser sessions, and daemon lifecycle operations.
|
|
1254
|
-
|
|
1255
|
-
All examples are under:
|
|
1256
|
-
|
|
1257
|
-
```text
|
|
1258
|
-
examples/node/
|
|
1259
|
-
examples/browser/
|
|
1260
|
-
```
|