@factory/droid-sdk 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +239 -47
- package/dist/index.cjs +1869 -1296
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6734 -4444
- package/dist/index.d.ts +6734 -4444
- package/dist/index.js +1815 -1298
- package/dist/index.js.map +1 -1
- package/docs/examples/init-metadata.md +79 -0
- package/docs/examples/list-sessions.md +64 -0
- package/docs/examples/multi-turn-session.md +77 -0
- package/docs/examples/one-shot-query.md +73 -0
- package/docs/examples/permission-handler.md +102 -0
- package/docs/examples/run.md +52 -0
- package/docs/examples/spec-mode.md +101 -0
- package/docs/examples/tool-controls.md +90 -0
- package/docs/typescript-sdk-reference.md +372 -0
- package/examples/.gitkeep +1 -0
- package/examples/abort-session-stream.ts +67 -0
- package/examples/droid-dev-structured-output.ts +44 -0
- package/examples/fork-session.ts +42 -0
- package/examples/init-metadata.ts +76 -0
- package/examples/interrupt-session.ts +67 -0
- package/examples/list-sessions.ts +65 -0
- package/examples/multi-turn-session.ts +67 -0
- package/examples/permission-handler.ts +98 -0
- package/examples/result-metadata.ts +62 -0
- package/examples/run.ts +38 -0
- package/examples/sdk-mcp-tool.ts +38 -0
- package/examples/simple-query.ts +53 -0
- package/examples/spec-mode-new-session.ts +121 -0
- package/examples/spec-mode-same-session.ts +133 -0
- package/examples/structured-output.ts +53 -0
- package/examples/test-compact.ts +60 -0
- package/examples/test-rewind.ts +155 -0
- package/examples/tool-controls.ts +69 -0
- package/package.json +26 -4
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
# Agent SDK reference - TypeScript
|
|
2
|
+
|
|
3
|
+
> Public API reference for `@factory/droid-sdk`, the TypeScript SDK for the Factory Droid CLI.
|
|
4
|
+
|
|
5
|
+
## Documentation Index
|
|
6
|
+
|
|
7
|
+
Use these pages together:
|
|
8
|
+
|
|
9
|
+
- API reference: `docs/typescript-sdk-reference.md`
|
|
10
|
+
- Example walkthroughs: `docs/examples/`
|
|
11
|
+
- Source exports: [`src/index.ts`](../src/index.ts)
|
|
12
|
+
- Runnable repo examples: [`examples/`](../examples)
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @factory/droid-sdk
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- Node.js `18+`
|
|
23
|
+
- The `droid` CLI installed and available on your `PATH`
|
|
24
|
+
|
|
25
|
+
## What this SDK provides
|
|
26
|
+
|
|
27
|
+
The SDK wraps `droid exec` as a subprocess and exposes two main patterns:
|
|
28
|
+
|
|
29
|
+
- `query()` for one-shot prompt/response flows
|
|
30
|
+
- `createSession()` / `resumeSession()` for multi-turn sessions
|
|
31
|
+
|
|
32
|
+
It also includes:
|
|
33
|
+
|
|
34
|
+
- streaming message events
|
|
35
|
+
- permission and ask-user handlers
|
|
36
|
+
- MCP server management
|
|
37
|
+
- spec mode controls
|
|
38
|
+
- tool allow/deny controls
|
|
39
|
+
- session utilities such as rename, fork, compact, and rewind
|
|
40
|
+
- local session discovery with `listSessions()`
|
|
41
|
+
- low-level access via `ProcessTransport`, `ProtocolEngine`, and `DroidClient`
|
|
42
|
+
|
|
43
|
+
## Example walkthroughs
|
|
44
|
+
|
|
45
|
+
For copy-pasteable walkthroughs and complete scripts, see:
|
|
46
|
+
|
|
47
|
+
- [One-shot query](./examples/one-shot-query.md)
|
|
48
|
+
- [Multi-turn session](./examples/multi-turn-session.md)
|
|
49
|
+
- [Permission handler](./examples/permission-handler.md)
|
|
50
|
+
- [Initialization metadata](./examples/init-metadata.md)
|
|
51
|
+
- [Spec mode approval flow](./examples/spec-mode.md)
|
|
52
|
+
- [Tool controls](./examples/tool-controls.md)
|
|
53
|
+
- [List saved sessions](./examples/list-sessions.md)
|
|
54
|
+
|
|
55
|
+
## Functions
|
|
56
|
+
|
|
57
|
+
### `query()`
|
|
58
|
+
|
|
59
|
+
Starts a one-shot prompt, streams `DroidMessage` events, and cleans up automatically when the stream ends.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
function query(options: QueryOptions): DroidQuery;
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### `QueryOptions`
|
|
66
|
+
|
|
67
|
+
`QueryOptions` extends `CreateSessionOptions` and adds:
|
|
68
|
+
|
|
69
|
+
| Field | Type | Description |
|
|
70
|
+
| :------- | :------- | :--------------------------------------- |
|
|
71
|
+
| `prompt` | `string` | Prompt to send as the first user message |
|
|
72
|
+
|
|
73
|
+
#### `DroidQuery`
|
|
74
|
+
|
|
75
|
+
`DroidQuery` is an `AsyncGenerator<DroidMessage>` with extra controls:
|
|
76
|
+
|
|
77
|
+
| Member | Type | Description |
|
|
78
|
+
| :------------ | :--------------------------------- | :---------------------------------------- |
|
|
79
|
+
| `interrupt()` | `() => Promise<void>` | Gracefully interrupts the current turn |
|
|
80
|
+
| `abort()` | `() => void` | Forcefully closes the subprocess |
|
|
81
|
+
| `sessionId` | `string \| null` | Session ID after initialization completes |
|
|
82
|
+
| `initResult` | `InitializeSessionResult \| null` | Raw initialization result |
|
|
83
|
+
| `initialized` | `Promise<InitializeSessionResult>` | Resolves when initialization finishes |
|
|
84
|
+
|
|
85
|
+
#### Notes
|
|
86
|
+
|
|
87
|
+
- `query()` accepts `abortSignal`
|
|
88
|
+
- `query()` sends text only; image/file message attachments are supported on `DroidSession`, not `query()`
|
|
89
|
+
|
|
90
|
+
### `createSession()`
|
|
91
|
+
|
|
92
|
+
Creates a persistent `DroidSession` for multi-turn conversations.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
function createSession(options?: CreateSessionOptions): Promise<DroidSession>;
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### `resumeSession()`
|
|
99
|
+
|
|
100
|
+
Reconnects to an existing session by ID.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
function resumeSession(
|
|
104
|
+
sessionId: string,
|
|
105
|
+
options?: ResumeSessionOptions
|
|
106
|
+
): Promise<DroidSession>;
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `listSessions()`
|
|
110
|
+
|
|
111
|
+
Discovers saved sessions directly from `~/.factory/sessions/` without spawning `droid`.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
function listSessions(
|
|
115
|
+
options?: ListSessionsOptions
|
|
116
|
+
): Promise<SessionMetadata[]>;
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### `ListSessionsOptions`
|
|
120
|
+
|
|
121
|
+
| Field | Type | Description |
|
|
122
|
+
| :---------------- | :-------- | :---------------------------------------------------------------- |
|
|
123
|
+
| `cwd` | `string` | Scope results to a project directory. Defaults to `process.cwd()` |
|
|
124
|
+
| `fetchOutsideCWD` | `boolean` | Return sessions across all projects |
|
|
125
|
+
| `numSessions` | `number` | Maximum number of sessions to return |
|
|
126
|
+
| `sessionsDir` | `string` | Override the session storage root |
|
|
127
|
+
|
|
128
|
+
#### Returned `SessionMetadata`
|
|
129
|
+
|
|
130
|
+
Common fields include:
|
|
131
|
+
|
|
132
|
+
- `id`
|
|
133
|
+
- `title`
|
|
134
|
+
- `sessionTitle`
|
|
135
|
+
- `owner`
|
|
136
|
+
- `messageCount`
|
|
137
|
+
- `modifiedTime`
|
|
138
|
+
- `createdTime`
|
|
139
|
+
- `isFavorite`
|
|
140
|
+
- `cwd`
|
|
141
|
+
- `decompSessionType`
|
|
142
|
+
- `decompMissionId`
|
|
143
|
+
|
|
144
|
+
Archived sessions are excluded automatically, and results are sorted by `modifiedTime` descending.
|
|
145
|
+
|
|
146
|
+
## `DroidSession`
|
|
147
|
+
|
|
148
|
+
Returned by `createSession()` and `resumeSession()`.
|
|
149
|
+
|
|
150
|
+
### Core methods
|
|
151
|
+
|
|
152
|
+
| Method | Description |
|
|
153
|
+
| :----------------------- | :--------------------------------------------------------------------- |
|
|
154
|
+
| `stream(text, options?)` | Sends a message and yields `DroidMessage` events until `turn_complete` |
|
|
155
|
+
| `send(text, options?)` | Sends a message and returns aggregated text, messages, and token usage |
|
|
156
|
+
| `interrupt()` | Gracefully interrupts the current turn |
|
|
157
|
+
| `close()` | Closes the underlying connection |
|
|
158
|
+
| `updateSettings(params)` | Updates model/session settings |
|
|
159
|
+
| `enterSpecMode(params?)` | Switches the current session into spec mode |
|
|
160
|
+
|
|
161
|
+
### Session utilities
|
|
162
|
+
|
|
163
|
+
| Method | Description |
|
|
164
|
+
| :------------------------ | :----------------------------------------------------- |
|
|
165
|
+
| `forkSession()` | Creates a new server-side session from the current one |
|
|
166
|
+
| `renameSession(params)` | Renames the current session |
|
|
167
|
+
| `compactSession(params?)` | Requests server-side compaction |
|
|
168
|
+
| `getRewindInfo(params)` | Fetches rewind metadata |
|
|
169
|
+
| `executeRewind(params)` | Executes a rewind |
|
|
170
|
+
|
|
171
|
+
### MCP and discovery helpers
|
|
172
|
+
|
|
173
|
+
| Method | Description |
|
|
174
|
+
| :---------------------------------- | :------------------------------------------------------- |
|
|
175
|
+
| `addMcpServer(params)` | Adds an MCP server |
|
|
176
|
+
| `removeMcpServer(params)` | Removes an MCP server |
|
|
177
|
+
| `toggleMcpServer(params)` | Enables or disables a server |
|
|
178
|
+
| `listMcpServers()` | Lists configured MCP servers |
|
|
179
|
+
| `listMcpTools()` | Lists tools exposed by connected MCP servers |
|
|
180
|
+
| `authenticateMcpServer(params)` | Starts MCP OAuth/authentication flow |
|
|
181
|
+
| `listSkills()` | Lists available skills |
|
|
182
|
+
| `listTools(params?)` | Lists the exec tool catalog and current allow/deny state |
|
|
183
|
+
| `onNotification(callback, filter?)` | Subscribes to raw session notifications |
|
|
184
|
+
|
|
185
|
+
### Properties
|
|
186
|
+
|
|
187
|
+
| Property | Type | Description |
|
|
188
|
+
| :----------- | :--------------------------------------------- | :-------------------------- |
|
|
189
|
+
| `sessionId` | `string` | Active session ID |
|
|
190
|
+
| `initResult` | `InitializeSessionResult \| LoadSessionResult` | Raw initialize/load payload |
|
|
191
|
+
|
|
192
|
+
### Message attachments
|
|
193
|
+
|
|
194
|
+
`session.stream()` and `session.send()` accept `MessageOptions`:
|
|
195
|
+
|
|
196
|
+
| Field | Type | Description |
|
|
197
|
+
| :------- | :-------------------- | :------------------------------- |
|
|
198
|
+
| `images` | `Base64ImageSource[]` | Inline image attachments |
|
|
199
|
+
| `files` | `DocumentSource[]` | Inline file/document attachments |
|
|
200
|
+
|
|
201
|
+
### `DroidResult`
|
|
202
|
+
|
|
203
|
+
Returned by `session.send()`:
|
|
204
|
+
|
|
205
|
+
| Field | Type | Description |
|
|
206
|
+
| :----------- | :------------------------- | :-------------------------------- |
|
|
207
|
+
| `text` | `string` | Concatenated assistant text |
|
|
208
|
+
| `messages` | `DroidMessage[]` | All stream messages from the turn |
|
|
209
|
+
| `tokenUsage` | `TokenUsageUpdate \| null` | Final token usage if available |
|
|
210
|
+
|
|
211
|
+
## Stream message model
|
|
212
|
+
|
|
213
|
+
All streamed events are part of the `DroidMessage` union and are discriminated by `type`.
|
|
214
|
+
|
|
215
|
+
### Common message types
|
|
216
|
+
|
|
217
|
+
| Type | Description |
|
|
218
|
+
| :---------------------- | :------------------------------------------ |
|
|
219
|
+
| `assistant_text_delta` | Assistant text token |
|
|
220
|
+
| `thinking_text_delta` | Thinking/reasoning token |
|
|
221
|
+
| `tool_use` | Tool invocation |
|
|
222
|
+
| `tool_result` | Tool result |
|
|
223
|
+
| `tool_progress` | Tool progress update |
|
|
224
|
+
| `working_state_changed` | Agent state transition |
|
|
225
|
+
| `token_usage_update` | Token counter update |
|
|
226
|
+
| `create_message` | Full assistant message |
|
|
227
|
+
| `permission_resolved` | Permission outcome |
|
|
228
|
+
| `settings_updated` | Session settings changed |
|
|
229
|
+
| `session_title_updated` | Session title changed |
|
|
230
|
+
| `mcp_status_changed` | MCP status changed |
|
|
231
|
+
| `mcp_auth_required` | MCP authentication required |
|
|
232
|
+
| `mcp_auth_completed` | MCP authentication completed |
|
|
233
|
+
| `error` | Error from the process |
|
|
234
|
+
| `turn_complete` | End-of-turn sentinel synthesized by the SDK |
|
|
235
|
+
|
|
236
|
+
### Mission and AGI messages
|
|
237
|
+
|
|
238
|
+
When running in `DroidInteractionMode.AGI`, the stream can also emit:
|
|
239
|
+
|
|
240
|
+
- `mission_state_changed`
|
|
241
|
+
- `mission_features_changed`
|
|
242
|
+
- `mission_progress_entry`
|
|
243
|
+
- `mission_heartbeat`
|
|
244
|
+
- `mission_worker_started`
|
|
245
|
+
- `mission_worker_completed`
|
|
246
|
+
|
|
247
|
+
## Configuration and enums
|
|
248
|
+
|
|
249
|
+
High-value exported enums include:
|
|
250
|
+
|
|
251
|
+
- `DroidInteractionMode`
|
|
252
|
+
- `AutonomyLevel`
|
|
253
|
+
- `ReasoningEffort`
|
|
254
|
+
- `ToolConfirmationOutcome`
|
|
255
|
+
- `ToolConfirmationType`
|
|
256
|
+
- `SessionNotificationType`
|
|
257
|
+
- `DroidWorkingState`
|
|
258
|
+
- `McpServerType`
|
|
259
|
+
- `McpServerStatus`
|
|
260
|
+
- `McpAuthOutcome`
|
|
261
|
+
- `MissionState`
|
|
262
|
+
- `FeatureStatus`
|
|
263
|
+
- `SettingsLevel`
|
|
264
|
+
- `SkillLocation`
|
|
265
|
+
|
|
266
|
+
### Common `CreateSessionOptions`
|
|
267
|
+
|
|
268
|
+
`CreateSessionOptions` supports the main runtime controls:
|
|
269
|
+
|
|
270
|
+
| Field | Description |
|
|
271
|
+
| :------------------------------ | :------------------------------------------------- |
|
|
272
|
+
| `cwd` | Working directory |
|
|
273
|
+
| `machineId` | Machine identifier passed during initialization |
|
|
274
|
+
| `modelId` | Default model |
|
|
275
|
+
| `autonomyLevel` | Command autonomy level |
|
|
276
|
+
| `interactionMode` | `Auto`, `Spec`, or `AGI` |
|
|
277
|
+
| `reasoningEffort` | LLM reasoning depth |
|
|
278
|
+
| `specModeModelId` | Spec-mode model override |
|
|
279
|
+
| `specModeReasoningEffort` | Spec-mode reasoning override |
|
|
280
|
+
| `mcpServers` | Initial MCP server configs |
|
|
281
|
+
| `enabledToolIds` | Explicit tool allowlist |
|
|
282
|
+
| `disabledToolIds` | Explicit tool denylist |
|
|
283
|
+
| `tags` | Session tags; the SDK also appends its own SDK tag |
|
|
284
|
+
| `permissionHandler` | Tool confirmation callback |
|
|
285
|
+
| `askUserHandler` | Structured user-input callback |
|
|
286
|
+
| `transport` | Custom transport override |
|
|
287
|
+
| `execPath` / `execArgs` / `env` | Subprocess configuration |
|
|
288
|
+
| `abortSignal` | External cancellation signal |
|
|
289
|
+
|
|
290
|
+
## Permission and ask-user handling
|
|
291
|
+
|
|
292
|
+
The high-level APIs support:
|
|
293
|
+
|
|
294
|
+
- `permissionHandler` for tool approval decisions
|
|
295
|
+
- `askUserHandler` for structured clarification flows
|
|
296
|
+
|
|
297
|
+
For spec mode, approval flows also support fresh-session outcomes such as `ToolConfirmationOutcome.ProceedNewSessionHigh`.
|
|
298
|
+
|
|
299
|
+
## MCP support
|
|
300
|
+
|
|
301
|
+
The SDK supports:
|
|
302
|
+
|
|
303
|
+
- configuring MCP servers at session startup
|
|
304
|
+
- adding/removing/toggling servers during a session
|
|
305
|
+
- listing MCP servers and tools
|
|
306
|
+
- starting authentication flows with `authenticateMcpServer()`
|
|
307
|
+
- receiving `mcp_auth_required` and `mcp_auth_completed` stream events
|
|
308
|
+
|
|
309
|
+
Advanced auth controls such as cancelling or clearing MCP auth are available on the low-level `DroidClient`.
|
|
310
|
+
|
|
311
|
+
## Low-level APIs
|
|
312
|
+
|
|
313
|
+
Most users should use `query()` and `DroidSession`, but the package also exports lower layers.
|
|
314
|
+
|
|
315
|
+
### `ProcessTransport`
|
|
316
|
+
|
|
317
|
+
Spawns `droid exec --input-format stream-jsonrpc --output-format stream-jsonrpc` and implements the transport interface used by the client.
|
|
318
|
+
|
|
319
|
+
### `ProtocolEngine`
|
|
320
|
+
|
|
321
|
+
Handles JSON-RPC framing, request/response correlation, notifications, permission requests, and ask-user requests.
|
|
322
|
+
|
|
323
|
+
### `DroidClient`
|
|
324
|
+
|
|
325
|
+
Low-level typed JSON-RPC client. Use it when you need direct RPC access beyond `DroidSession`.
|
|
326
|
+
|
|
327
|
+
Notable client-only capabilities include:
|
|
328
|
+
|
|
329
|
+
- `killWorkerSession()`
|
|
330
|
+
- `cancelMcpAuth()`
|
|
331
|
+
- `clearMcpAuth()`
|
|
332
|
+
- `submitMcpAuthCode()`
|
|
333
|
+
- `listMcpRegistry()`
|
|
334
|
+
- `toggleMcpTool()`
|
|
335
|
+
- `submitBugReport()`
|
|
336
|
+
|
|
337
|
+
## Runtime validation and schemas
|
|
338
|
+
|
|
339
|
+
The package exports its Zod schema surface from `src/schemas/index.ts`, including:
|
|
340
|
+
|
|
341
|
+
- request and response schemas
|
|
342
|
+
- notification schemas
|
|
343
|
+
- enum schemas and constants
|
|
344
|
+
- message/content schemas
|
|
345
|
+
- mission schemas
|
|
346
|
+
- MCP schemas
|
|
347
|
+
|
|
348
|
+
This makes it possible to validate protocol payloads at runtime in advanced integrations.
|
|
349
|
+
|
|
350
|
+
## Errors
|
|
351
|
+
|
|
352
|
+
The SDK exports these custom errors:
|
|
353
|
+
|
|
354
|
+
| Error | Description |
|
|
355
|
+
| :--------------------- | :---------------------------------------- |
|
|
356
|
+
| `ConnectionError` | Failed to connect to the Droid subprocess |
|
|
357
|
+
| `ProtocolError` | JSON-RPC or protocol-level failure |
|
|
358
|
+
| `SessionError` | Base session error |
|
|
359
|
+
| `SessionNotFoundError` | Requested session does not exist |
|
|
360
|
+
| `TimeoutError` | RPC timed out |
|
|
361
|
+
| `ProcessExitError` | The subprocess exited unexpectedly |
|
|
362
|
+
|
|
363
|
+
## Feature coverage in this document
|
|
364
|
+
|
|
365
|
+
This reference intentionally documents only features confirmed in the current SDK implementation and recent shipped commits, including:
|
|
366
|
+
|
|
367
|
+
- spec mode controls
|
|
368
|
+
- tool controls
|
|
369
|
+
- initialization metadata
|
|
370
|
+
- session forking
|
|
371
|
+
- filesystem-based session discovery
|
|
372
|
+
- rename, compact, rewind, and MCP/session management APIs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manual smoke test for cancelling `session.stream(..., { abortSignal })`.
|
|
3
|
+
*
|
|
4
|
+
* Starts a long-running streaming turn, aborts it after a short delay, then
|
|
5
|
+
* verifies the stream rejects with the abort reason.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx tsx examples/abort-session-stream.ts
|
|
9
|
+
* npx tsx examples/abort-session-stream.ts 3000
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createSession, DroidMessageType } from '../src/index.js';
|
|
13
|
+
|
|
14
|
+
async function main(): Promise<void> {
|
|
15
|
+
const abortAfterMs = Number(process.argv[2] ?? 2000);
|
|
16
|
+
if (!Number.isFinite(abortAfterMs) || abortAfterMs <= 0) {
|
|
17
|
+
throw new Error('Abort delay must be a positive number of milliseconds.');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const controller = new AbortController();
|
|
21
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
22
|
+
let textDeltaCount = 0;
|
|
23
|
+
let receivedText = '';
|
|
24
|
+
|
|
25
|
+
const timeout = setTimeout(() => {
|
|
26
|
+
controller.abort(new Error(`Aborted after ${abortAfterMs}ms`));
|
|
27
|
+
}, abortAfterMs);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
console.log(`Session created: ${session.sessionId}`);
|
|
31
|
+
console.log(`Aborting after ${abortAfterMs}ms\n`);
|
|
32
|
+
|
|
33
|
+
const prompt =
|
|
34
|
+
'Write a long, detailed explanation of how compilers work, including ' +
|
|
35
|
+
'lexing, parsing, semantic analysis, optimization, and code generation.';
|
|
36
|
+
|
|
37
|
+
for await (const message of session.stream(prompt, {
|
|
38
|
+
abortSignal: controller.signal,
|
|
39
|
+
})) {
|
|
40
|
+
if (message.type === DroidMessageType.AssistantTextDelta) {
|
|
41
|
+
textDeltaCount++;
|
|
42
|
+
receivedText += message.text;
|
|
43
|
+
process.stdout.write(message.text);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw new Error('Expected stream to abort, but it completed normally.');
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (error instanceof Error && error.message.includes('Aborted after')) {
|
|
50
|
+
console.log('\n\n=== Abort confirmed ===');
|
|
51
|
+
console.log(`Received text deltas before abort: ${textDeltaCount}`);
|
|
52
|
+
console.log(`Received characters before abort: ${receivedText.length}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
throw error;
|
|
57
|
+
} finally {
|
|
58
|
+
clearTimeout(timeout);
|
|
59
|
+
await session.close();
|
|
60
|
+
console.log('Session closed.');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
main().catch((err: unknown) => {
|
|
65
|
+
console.error('Error:', err);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-off integration smoke test for JSON output against droid-dev.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* npx tsx examples/droid-dev-structured-output.ts
|
|
6
|
+
* DROID_EXEC_PATH=/path/to/droid-dev npx tsx examples/droid-dev-structured-output.ts
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
|
|
11
|
+
import { run } from '../src/index.js';
|
|
12
|
+
|
|
13
|
+
const PersonSchema = z.object({
|
|
14
|
+
name: z.literal('Ada Lovelace'),
|
|
15
|
+
language: z.literal('TypeScript'),
|
|
16
|
+
score: z.number(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
async function main(): Promise<void> {
|
|
20
|
+
const execPath = process.env['DROID_EXEC_PATH'] ?? 'droid-dev';
|
|
21
|
+
|
|
22
|
+
const result = await run(
|
|
23
|
+
[
|
|
24
|
+
'Return a structured object for Ada Lovelace.',
|
|
25
|
+
'Use name "Ada Lovelace", language "TypeScript", and score 99.',
|
|
26
|
+
'Return only valid JSON and do not include markdown fences.',
|
|
27
|
+
].join(' '),
|
|
28
|
+
{
|
|
29
|
+
execPath,
|
|
30
|
+
cwd: process.cwd(),
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const parsed = PersonSchema.parse(JSON.parse(result.text));
|
|
35
|
+
|
|
36
|
+
console.log(`droid executable: ${execPath}`);
|
|
37
|
+
console.log('structured output:', JSON.stringify(parsed, null, 2));
|
|
38
|
+
console.log(`messages received: ${result.messages.length}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
main().catch((error: unknown) => {
|
|
42
|
+
console.error(error);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fork session example.
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates:
|
|
5
|
+
* - creating a session and establishing some context
|
|
6
|
+
* - calling `forkSession()` to get a new server-side session ID
|
|
7
|
+
* - resuming the fork and continuing from the copied context
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npx tsx examples/fork-session.ts
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createSession, resumeSession } from '../src/index.js';
|
|
14
|
+
|
|
15
|
+
async function main(): Promise<void> {
|
|
16
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
17
|
+
let fork: Awaited<ReturnType<typeof resumeSession>> | null = null;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
console.log(`Original session: ${session.sessionId}\n`);
|
|
21
|
+
|
|
22
|
+
await session.send('Remember this phrase exactly: mango sunrise');
|
|
23
|
+
|
|
24
|
+
const { newSessionId } = await session.forkSession();
|
|
25
|
+
console.log(`Forked session: ${newSessionId}\n`);
|
|
26
|
+
|
|
27
|
+
fork = await resumeSession(newSessionId, { cwd: process.cwd() });
|
|
28
|
+
|
|
29
|
+
const result = await fork.send('What phrase did I ask you to remember?');
|
|
30
|
+
console.log('Fork response:');
|
|
31
|
+
console.log(result.text);
|
|
32
|
+
} finally {
|
|
33
|
+
await fork?.close();
|
|
34
|
+
await session.close();
|
|
35
|
+
console.log('\nBoth sessions closed.');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
main().catch((err: unknown) => {
|
|
40
|
+
console.error('Error:', err);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Initialization metadata example.
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates:
|
|
5
|
+
* - `query().sessionId`, `query().initResult`, and `query().initialized`
|
|
6
|
+
* - `session.initResult` from `createSession()`
|
|
7
|
+
* - `resumed.initResult` from `resumeSession()`
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npx tsx examples/init-metadata.ts
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
createSession,
|
|
15
|
+
DroidMessageType,
|
|
16
|
+
query,
|
|
17
|
+
resumeSession,
|
|
18
|
+
} from '../src/index.js';
|
|
19
|
+
|
|
20
|
+
async function main(): Promise<void> {
|
|
21
|
+
const stream = query({
|
|
22
|
+
prompt: 'Reply with "ready" and nothing else.',
|
|
23
|
+
cwd: process.cwd(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
console.log('=== query() metadata ===');
|
|
27
|
+
console.log(`Before initialization: sessionId=${String(stream.sessionId)}`);
|
|
28
|
+
console.log(`Before initialization: initResult=${String(stream.initResult)}`);
|
|
29
|
+
|
|
30
|
+
const initialized = await stream.initialized;
|
|
31
|
+
console.log(`After initialization: sessionId=${initialized.sessionId}`);
|
|
32
|
+
console.log(`Model: ${String(initialized.settings.modelId)}`);
|
|
33
|
+
console.log(
|
|
34
|
+
`Cached initResult matches initialized: ${String(stream.initResult === initialized)}`
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
let text = '';
|
|
38
|
+
for await (const msg of stream) {
|
|
39
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
40
|
+
text += msg.text;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
console.log(`Response: ${JSON.stringify(text.trim())}\n`);
|
|
44
|
+
|
|
45
|
+
console.log('=== createSession() / resumeSession() metadata ===');
|
|
46
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
47
|
+
let resumed: Awaited<ReturnType<typeof resumeSession>> | null = null;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
console.log(`Created session: ${session.sessionId}`);
|
|
51
|
+
console.log(
|
|
52
|
+
`createSession().initResult.settings.modelId=${String(session.initResult.settings.modelId)}`
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
resumed = await resumeSession(session.sessionId, {
|
|
56
|
+
cwd: process.cwd(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
console.log(`Resumed session: ${resumed.sessionId}`);
|
|
60
|
+
console.log(
|
|
61
|
+
`resumeSession().initResult.cwd=${String(resumed.initResult.cwd)}`
|
|
62
|
+
);
|
|
63
|
+
console.log(
|
|
64
|
+
`resumeSession().initResult.availableModels=${String(resumed.initResult.availableModels?.length ?? 0)}`
|
|
65
|
+
);
|
|
66
|
+
} finally {
|
|
67
|
+
await resumed?.close();
|
|
68
|
+
await session.close();
|
|
69
|
+
console.log('\nSessions closed.');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
main().catch((err: unknown) => {
|
|
74
|
+
console.error('Error:', err);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interrupt session example.
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates using `session.interrupt()` to stop the agent mid-turn.
|
|
5
|
+
* Starts a streaming turn with a long-running prompt, waits for a few
|
|
6
|
+
* `AssistantTextDelta` messages, then sends an interrupt and continues
|
|
7
|
+
* consuming the stream until `TurnComplete`.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npx tsx examples/interrupt-session.ts
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createSession, DroidMessageType } from '../src/index.js';
|
|
14
|
+
|
|
15
|
+
async function main(): Promise<void> {
|
|
16
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
17
|
+
try {
|
|
18
|
+
console.log(`Session created: ${session.sessionId}\n`);
|
|
19
|
+
|
|
20
|
+
const prompt =
|
|
21
|
+
'Write a detailed essay about the history of computing, from the ' +
|
|
22
|
+
'earliest mechanical calculators to modern quantum computers.';
|
|
23
|
+
console.log(`Prompt: "${prompt}"\n`);
|
|
24
|
+
|
|
25
|
+
let deltaCount = 0;
|
|
26
|
+
let interrupted = false;
|
|
27
|
+
|
|
28
|
+
for await (const msg of session.stream(prompt)) {
|
|
29
|
+
switch (msg.type) {
|
|
30
|
+
case DroidMessageType.AssistantTextDelta:
|
|
31
|
+
deltaCount++;
|
|
32
|
+
process.stdout.write(msg.text);
|
|
33
|
+
|
|
34
|
+
if (deltaCount === 5 && !interrupted) {
|
|
35
|
+
interrupted = true;
|
|
36
|
+
console.log('\n\n>>> Sending interrupt after 5 text deltas...\n');
|
|
37
|
+
await session.interrupt();
|
|
38
|
+
}
|
|
39
|
+
break;
|
|
40
|
+
|
|
41
|
+
case DroidMessageType.TurnComplete:
|
|
42
|
+
console.log('\n\n--- Turn complete ---');
|
|
43
|
+
console.log(`Total text deltas received: ${deltaCount}`);
|
|
44
|
+
console.log(
|
|
45
|
+
interrupted
|
|
46
|
+
? 'Session was interrupted successfully.'
|
|
47
|
+
: 'Session completed without interruption.'
|
|
48
|
+
);
|
|
49
|
+
if (msg.tokenUsage) {
|
|
50
|
+
console.log(
|
|
51
|
+
`Tokens — input: ${msg.tokenUsage.inputTokens}, ` +
|
|
52
|
+
`output: ${msg.tokenUsage.outputTokens}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} finally {
|
|
59
|
+
await session.close();
|
|
60
|
+
console.log('\nSession closed.');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
main().catch((err: unknown) => {
|
|
65
|
+
console.error('Error:', err);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
});
|