@factory/droid-sdk 0.1.3 → 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,79 @@
|
|
|
1
|
+
# Example: initialization metadata
|
|
2
|
+
|
|
3
|
+
Use this when you need the raw initialization payload, early session IDs, or model/settings metadata.
|
|
4
|
+
|
|
5
|
+
## What this example shows
|
|
6
|
+
|
|
7
|
+
- reading `query().sessionId` and `query().initResult`
|
|
8
|
+
- waiting for `query().initialized`
|
|
9
|
+
- reading `session.initResult` from `createSession()` and `resumeSession()`
|
|
10
|
+
|
|
11
|
+
## Key snippet: inspect a query before and after initialization
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const stream = query({
|
|
15
|
+
prompt: 'Reply with "ready" and nothing else.',
|
|
16
|
+
cwd: process.cwd(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
console.log(stream.sessionId);
|
|
20
|
+
console.log(stream.initResult);
|
|
21
|
+
|
|
22
|
+
const initialized = await stream.initialized;
|
|
23
|
+
console.log(initialized.sessionId);
|
|
24
|
+
console.log(initialized.settings.modelId);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Before initialization completes, `sessionId` and `initResult` are `null`.
|
|
28
|
+
|
|
29
|
+
## Key snippet: read metadata from sessions
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
33
|
+
const resumed = await resumeSession(session.sessionId, { cwd: process.cwd() });
|
|
34
|
+
|
|
35
|
+
console.log(session.initResult.settings.modelId);
|
|
36
|
+
console.log(resumed.initResult.cwd);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Full script
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { createSession, query, resumeSession } from '@factory/droid-sdk';
|
|
43
|
+
|
|
44
|
+
async function main(): Promise<void> {
|
|
45
|
+
const stream = query({
|
|
46
|
+
prompt: 'Reply with "ready" and nothing else.',
|
|
47
|
+
cwd: process.cwd(),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
console.log(stream.sessionId);
|
|
51
|
+
console.log(stream.initResult);
|
|
52
|
+
|
|
53
|
+
const initialized = await stream.initialized;
|
|
54
|
+
console.log(initialized.sessionId);
|
|
55
|
+
console.log(initialized.settings.modelId);
|
|
56
|
+
|
|
57
|
+
stream.abort();
|
|
58
|
+
|
|
59
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
60
|
+
let resumed: Awaited<ReturnType<typeof resumeSession>> | null = null;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
resumed = await resumeSession(session.sessionId, {
|
|
64
|
+
cwd: process.cwd(),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
console.log(session.initResult.settings.modelId);
|
|
68
|
+
console.log(resumed.initResult.cwd);
|
|
69
|
+
} finally {
|
|
70
|
+
await resumed?.close();
|
|
71
|
+
await session.close();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
main().catch((err: unknown) => {
|
|
76
|
+
console.error('Error:', err);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
});
|
|
79
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Example: list saved sessions
|
|
2
|
+
|
|
3
|
+
Use `listSessions()` when you need recent Droid session history without launching a subprocess.
|
|
4
|
+
|
|
5
|
+
## What this example shows
|
|
6
|
+
|
|
7
|
+
- listing recent sessions for the current project
|
|
8
|
+
- listing recent sessions across all projects
|
|
9
|
+
- formatting returned `SessionMetadata`
|
|
10
|
+
|
|
11
|
+
## Key snippet: project-scoped listing
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const currentProject = await listSessions({ numSessions: 10 });
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Key snippet: cross-project listing
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const allSessions = await listSessions({
|
|
21
|
+
fetchOutsideCWD: true,
|
|
22
|
+
numSessions: 5,
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
This helper reads local session files directly and returns `SessionMetadata[]`.
|
|
27
|
+
|
|
28
|
+
## Full script
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { listSessions } from '@factory/droid-sdk';
|
|
32
|
+
|
|
33
|
+
function formatDate(date: Date): string {
|
|
34
|
+
return date.toISOString().replace('T', ' ').slice(0, 19);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function main(): Promise<void> {
|
|
38
|
+
const currentProject = await listSessions({ numSessions: 10 });
|
|
39
|
+
|
|
40
|
+
for (const session of currentProject) {
|
|
41
|
+
const title = session.sessionTitle ?? session.title ?? '(untitled)';
|
|
42
|
+
console.log(
|
|
43
|
+
`[${session.id.slice(0, 8)}] ${formatDate(session.modifiedTime)} ${session.messageCount} msgs — ${title}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const allSessions = await listSessions({
|
|
48
|
+
fetchOutsideCWD: true,
|
|
49
|
+
numSessions: 5,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
for (const session of allSessions) {
|
|
53
|
+
const title = session.sessionTitle ?? session.title ?? '(untitled)';
|
|
54
|
+
console.log(
|
|
55
|
+
`[${session.id.slice(0, 8)}] ${formatDate(session.modifiedTime)} — ${title}`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
main().catch((err: unknown) => {
|
|
61
|
+
console.error('Error:', err);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
|
64
|
+
```
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Example: multi-turn session
|
|
2
|
+
|
|
3
|
+
Use `createSession()` when you want conversation state to persist across turns.
|
|
4
|
+
|
|
5
|
+
## What this example shows
|
|
6
|
+
|
|
7
|
+
- creating a persistent session
|
|
8
|
+
- streaming one turn with `session.stream()`
|
|
9
|
+
- collecting a later turn with `session.send()`
|
|
10
|
+
- closing the session cleanly
|
|
11
|
+
|
|
12
|
+
## Key snippet: create the session
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Create the session once and reuse it across prompts.
|
|
19
|
+
|
|
20
|
+
## Key snippet: stream a turn
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { DroidMessageType } from '@factory/droid-sdk';
|
|
24
|
+
|
|
25
|
+
for await (const msg of session.stream(
|
|
26
|
+
'List the TypeScript files in this project'
|
|
27
|
+
)) {
|
|
28
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
29
|
+
process.stdout.write(msg.text);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Use `stream()` when you want incremental output.
|
|
35
|
+
|
|
36
|
+
## Key snippet: collect a full response
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const result = await session.send('Summarize the project in one sentence');
|
|
40
|
+
console.log(result.text);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Use `send()` when you want the SDK to aggregate the turn for you.
|
|
44
|
+
|
|
45
|
+
## Full script
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { createSession, DroidMessageType } from '@factory/droid-sdk';
|
|
49
|
+
|
|
50
|
+
async function main(): Promise<void> {
|
|
51
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
52
|
+
|
|
53
|
+
console.log(`Session created: ${session.sessionId}\n`);
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
for await (const msg of session.stream(
|
|
57
|
+
'List the TypeScript files in this project'
|
|
58
|
+
)) {
|
|
59
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
60
|
+
process.stdout.write(msg.text);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log('\n');
|
|
65
|
+
|
|
66
|
+
const result = await session.send('Summarize the project in one sentence');
|
|
67
|
+
console.log(result.text);
|
|
68
|
+
} finally {
|
|
69
|
+
await session.close();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
main().catch((err: unknown) => {
|
|
74
|
+
console.error('Error:', err);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
});
|
|
77
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Example: one-shot query
|
|
2
|
+
|
|
3
|
+
Use this when you want a single prompt and want to stream output as it arrives.
|
|
4
|
+
|
|
5
|
+
## What this example shows
|
|
6
|
+
|
|
7
|
+
- starting a one-shot request with `query()`
|
|
8
|
+
- streaming assistant text incrementally
|
|
9
|
+
- observing tool activity and turn completion
|
|
10
|
+
|
|
11
|
+
## Key snippet: create the stream
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const stream = query({
|
|
15
|
+
prompt: 'List all TypeScript files in this project',
|
|
16
|
+
cwd: process.cwd(),
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- `prompt` is the first user message
|
|
21
|
+
- `cwd` is the directory Droid should operate in
|
|
22
|
+
|
|
23
|
+
## Key snippet: consume streamed messages
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { DroidMessageType } from '@factory/droid-sdk';
|
|
27
|
+
|
|
28
|
+
for await (const msg of stream) {
|
|
29
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
30
|
+
process.stdout.write(msg.text);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- `assistant_text_delta` gives you streaming text output
|
|
36
|
+
- you can also handle `tool_use`, `tool_result`, and `turn_complete`
|
|
37
|
+
|
|
38
|
+
## Full script
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { DroidMessageType, query } from '@factory/droid-sdk';
|
|
42
|
+
|
|
43
|
+
async function main(): Promise<void> {
|
|
44
|
+
const prompt = process.argv[2] ?? 'What files are in the current directory?';
|
|
45
|
+
|
|
46
|
+
const stream = query({
|
|
47
|
+
prompt,
|
|
48
|
+
cwd: process.cwd(),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
for await (const msg of stream) {
|
|
52
|
+
switch (msg.type) {
|
|
53
|
+
case DroidMessageType.AssistantTextDelta:
|
|
54
|
+
process.stdout.write(msg.text);
|
|
55
|
+
break;
|
|
56
|
+
case DroidMessageType.ToolUse:
|
|
57
|
+
console.log(`\n[Tool] ${msg.toolName}`);
|
|
58
|
+
break;
|
|
59
|
+
case DroidMessageType.ToolResult:
|
|
60
|
+
console.log(`[Tool Result] ${msg.isError ? 'Error' : 'OK'}`);
|
|
61
|
+
break;
|
|
62
|
+
case DroidMessageType.TurnComplete:
|
|
63
|
+
console.log('\n\n--- Turn complete ---');
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
main().catch((err: unknown) => {
|
|
70
|
+
console.error('Error:', err);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
});
|
|
73
|
+
```
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Example: permission handler
|
|
2
|
+
|
|
3
|
+
Use a `permissionHandler` when your app needs to inspect or approve tool calls.
|
|
4
|
+
|
|
5
|
+
## What this example shows
|
|
6
|
+
|
|
7
|
+
- receiving tool confirmation requests
|
|
8
|
+
- inspecting `params.toolUses`
|
|
9
|
+
- approving each request with `ToolConfirmationOutcome.ProceedOnce`
|
|
10
|
+
|
|
11
|
+
## Key snippet: define the handler
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
function permissionHandler(
|
|
15
|
+
allowedFilePath: string,
|
|
16
|
+
params: RequestPermissionRequestParams
|
|
17
|
+
): ToolConfirmationOutcome {
|
|
18
|
+
const onlyAllowedCreate = params.toolUses.every(
|
|
19
|
+
(item) =>
|
|
20
|
+
item.details.type === ToolConfirmationType.Create &&
|
|
21
|
+
item.details.filePath === allowedFilePath
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return onlyAllowedCreate
|
|
25
|
+
? ToolConfirmationOutcome.ProceedOnce
|
|
26
|
+
: ToolConfirmationOutcome.Cancel;
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- `params.toolUses` describes the pending tool calls
|
|
31
|
+
- `ProceedOnce` approves the current request only
|
|
32
|
+
|
|
33
|
+
## Key snippet: pass the handler to `query()`
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
const stream = query({
|
|
37
|
+
prompt: `Create a file called ${outputPath} with the text 'Hello, World!'`,
|
|
38
|
+
cwd: process.cwd(),
|
|
39
|
+
permissionHandler: (params) => permissionHandler(outputPath, params),
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Full script
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import {
|
|
47
|
+
DroidMessageType,
|
|
48
|
+
query,
|
|
49
|
+
ToolConfirmationOutcome,
|
|
50
|
+
ToolConfirmationType,
|
|
51
|
+
type RequestPermissionRequestParams,
|
|
52
|
+
} from '@factory/droid-sdk';
|
|
53
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
54
|
+
import { tmpdir } from 'node:os';
|
|
55
|
+
import { join } from 'node:path';
|
|
56
|
+
|
|
57
|
+
function permissionHandler(
|
|
58
|
+
allowedFilePath: string,
|
|
59
|
+
params: RequestPermissionRequestParams
|
|
60
|
+
): ToolConfirmationOutcome {
|
|
61
|
+
for (const item of params.toolUses) {
|
|
62
|
+
console.log(`\n[Permission] Tool: ${item.toolUse.name}`);
|
|
63
|
+
console.log(`Type: ${item.confirmationType}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const onlyAllowedCreate = params.toolUses.every(
|
|
67
|
+
(item) =>
|
|
68
|
+
item.details.type === ToolConfirmationType.Create &&
|
|
69
|
+
item.details.filePath === allowedFilePath
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return onlyAllowedCreate
|
|
73
|
+
? ToolConfirmationOutcome.ProceedOnce
|
|
74
|
+
: ToolConfirmationOutcome.Cancel;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function main(): Promise<void> {
|
|
78
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-permission-'));
|
|
79
|
+
const outputPath = join(tempDir, 'hello.txt');
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const stream = query({
|
|
83
|
+
prompt: `Create a file called ${outputPath} with the text 'Hello, World!'`,
|
|
84
|
+
cwd: process.cwd(),
|
|
85
|
+
permissionHandler: (params) => permissionHandler(outputPath, params),
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
for await (const msg of stream) {
|
|
89
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
90
|
+
process.stdout.write(msg.text);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} finally {
|
|
94
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
main().catch((err: unknown) => {
|
|
99
|
+
console.error('Error:', err);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
});
|
|
102
|
+
```
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Example: one-shot run
|
|
2
|
+
|
|
3
|
+
Use this when you want a single prompt and only need the final aggregated
|
|
4
|
+
result.
|
|
5
|
+
|
|
6
|
+
## What this example shows
|
|
7
|
+
|
|
8
|
+
- sending a one-shot request with `run()`
|
|
9
|
+
- reading the final assistant text
|
|
10
|
+
- inspecting collected messages and token usage
|
|
11
|
+
|
|
12
|
+
## Key snippet
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
const result = await run('What files are in the current directory?', {
|
|
16
|
+
cwd: process.cwd(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
console.log(result.text);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`run()` creates a temporary session, sends the prompt, returns a
|
|
23
|
+
`DroidResult`, and closes the session automatically.
|
|
24
|
+
|
|
25
|
+
## Full script
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { run } from '@factory/droid-sdk';
|
|
29
|
+
|
|
30
|
+
async function main(): Promise<void> {
|
|
31
|
+
const text = process.argv.slice(2).join(' ') || 'What is 2 + 2?';
|
|
32
|
+
|
|
33
|
+
const result = await run(text, {
|
|
34
|
+
cwd: process.cwd(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
console.log(result.text);
|
|
38
|
+
console.log(`Messages received: ${result.messages.length}`);
|
|
39
|
+
|
|
40
|
+
if (result.tokenUsage) {
|
|
41
|
+
console.log(
|
|
42
|
+
`Tokens — input: ${result.tokenUsage.inputTokens}, ` +
|
|
43
|
+
`output: ${result.tokenUsage.outputTokens}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
main().catch((err: unknown) => {
|
|
49
|
+
console.error('Error:', err);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
});
|
|
52
|
+
```
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Example: spec mode approval flow
|
|
2
|
+
|
|
3
|
+
Use spec mode when you want Droid to propose a plan first and only implement after approval.
|
|
4
|
+
|
|
5
|
+
## What this example shows
|
|
6
|
+
|
|
7
|
+
- starting a query in `DroidInteractionMode.Spec`
|
|
8
|
+
- detecting an `ExitSpecMode` confirmation request
|
|
9
|
+
- choosing whether implementation stays in the same session or moves to a new one
|
|
10
|
+
|
|
11
|
+
## Key snippet: start in spec mode
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const stream = query({
|
|
15
|
+
prompt,
|
|
16
|
+
cwd: process.cwd(),
|
|
17
|
+
interactionMode: DroidInteractionMode.Spec,
|
|
18
|
+
specModeReasoningEffort: ReasoningEffort.High,
|
|
19
|
+
permissionHandler(params) {
|
|
20
|
+
return ToolConfirmationOutcome.ProceedOnce;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Key snippet: detect the spec approval request
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
const exitSpec = params.toolUses.find(
|
|
29
|
+
(t) => t.confirmationType === ToolConfirmationType.ExitSpecMode
|
|
30
|
+
);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Choose the outcome you want:
|
|
34
|
+
|
|
35
|
+
- `ToolConfirmationOutcome.ProceedOnce` keeps implementation in the same session
|
|
36
|
+
- `ToolConfirmationOutcome.ProceedNewSessionHigh` hands implementation to a new session
|
|
37
|
+
|
|
38
|
+
## Full script
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import {
|
|
42
|
+
DroidMessageType,
|
|
43
|
+
DroidInteractionMode,
|
|
44
|
+
query,
|
|
45
|
+
ReasoningEffort,
|
|
46
|
+
ToolConfirmationOutcome,
|
|
47
|
+
ToolConfirmationType,
|
|
48
|
+
} from '@factory/droid-sdk';
|
|
49
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
50
|
+
import { tmpdir } from 'node:os';
|
|
51
|
+
import { join } from 'node:path';
|
|
52
|
+
|
|
53
|
+
async function main(): Promise<void> {
|
|
54
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-spec-'));
|
|
55
|
+
const outputPath = join(tempDir, 'hello-from-droid.txt');
|
|
56
|
+
const prompt =
|
|
57
|
+
`Plan how to create a small ${outputPath} file containing the text ` +
|
|
58
|
+
'"Hello from Droid". Keep the plan short and concrete.';
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const stream = query({
|
|
62
|
+
prompt,
|
|
63
|
+
cwd: process.cwd(),
|
|
64
|
+
interactionMode: DroidInteractionMode.Spec,
|
|
65
|
+
specModeReasoningEffort: ReasoningEffort.High,
|
|
66
|
+
permissionHandler(params) {
|
|
67
|
+
const exitSpec = params.toolUses.find(
|
|
68
|
+
(item) => item.details.type === ToolConfirmationType.ExitSpecMode
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (exitSpec) {
|
|
72
|
+
return ToolConfirmationOutcome.ProceedOnce;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const onlyAllowedCreate = params.toolUses.every(
|
|
76
|
+
(item) =>
|
|
77
|
+
item.details.type === ToolConfirmationType.Create &&
|
|
78
|
+
item.details.filePath === outputPath
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return onlyAllowedCreate
|
|
82
|
+
? ToolConfirmationOutcome.ProceedOnce
|
|
83
|
+
: ToolConfirmationOutcome.Cancel;
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
for await (const msg of stream) {
|
|
88
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
89
|
+
process.stdout.write(msg.text);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} finally {
|
|
93
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
main().catch((err: unknown) => {
|
|
98
|
+
console.error('Error:', err);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
});
|
|
101
|
+
```
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Example: tool controls
|
|
2
|
+
|
|
3
|
+
Use this when you want to programmatically shape which built-in exec tools are available.
|
|
4
|
+
|
|
5
|
+
## What this example shows
|
|
6
|
+
|
|
7
|
+
- setting initial tool overrides
|
|
8
|
+
- inspecting the current tool catalog with `listTools()`
|
|
9
|
+
- updating tool overrides later with `updateSettings()`
|
|
10
|
+
|
|
11
|
+
## Key snippet: set tool overrides at session creation
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const session = await createSession({
|
|
15
|
+
cwd: process.cwd(),
|
|
16
|
+
enabledToolIds: ['Read', 'Glob', 'Grep'],
|
|
17
|
+
disabledToolIds: ['Execute'],
|
|
18
|
+
});
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Key snippet: inspect tool state
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
const result = await session.listTools();
|
|
25
|
+
console.log(result.tools);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Key snippet: update tool state later
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
await session.updateSettings({
|
|
32
|
+
disabledToolIds: ['Read', 'Execute'],
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Full script
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { createSession, type ExecToolInfo } from '@factory/droid-sdk';
|
|
40
|
+
|
|
41
|
+
function printToolState(label: string, tool: ExecToolInfo | undefined): void {
|
|
42
|
+
if (!tool) {
|
|
43
|
+
console.log(`${label}: not present in tool catalog`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(
|
|
48
|
+
`${label}: defaultAllowed=${tool.defaultAllowed}, currentlyAllowed=${tool.currentlyAllowed}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function main(): Promise<void> {
|
|
53
|
+
const session = await createSession({
|
|
54
|
+
cwd: process.cwd(),
|
|
55
|
+
enabledToolIds: ['Read', 'Glob', 'Grep'],
|
|
56
|
+
disabledToolIds: ['Execute'],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const initial = await session.listTools();
|
|
61
|
+
const initialRead = initial.tools.find((tool) => tool.llmId === 'Read');
|
|
62
|
+
const initialExecute = initial.tools.find(
|
|
63
|
+
(tool) => tool.llmId === 'Execute'
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
printToolState('Read', initialRead);
|
|
67
|
+
printToolState('Execute', initialExecute);
|
|
68
|
+
|
|
69
|
+
await session.updateSettings({
|
|
70
|
+
disabledToolIds: ['Read', 'Execute'],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const updated = await session.listTools();
|
|
74
|
+
const updatedRead = updated.tools.find((tool) => tool.llmId === 'Read');
|
|
75
|
+
const updatedExecute = updated.tools.find(
|
|
76
|
+
(tool) => tool.llmId === 'Execute'
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
printToolState('Read', updatedRead);
|
|
80
|
+
printToolState('Execute', updatedExecute);
|
|
81
|
+
} finally {
|
|
82
|
+
await session.close();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
main().catch((err: unknown) => {
|
|
87
|
+
console.error('Error:', err);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
});
|
|
90
|
+
```
|