@factory/droid-sdk 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -82
- package/dist/index.cjs +734 -611
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1649 -206
- package/dist/index.d.ts +1649 -206
- package/dist/index.js +729 -624
- package/dist/index.js.map +1 -1
- package/docs/sdk-usage-guide.md +678 -0
- package/examples/abort-session-stream.ts +19 -55
- package/examples/droid-dev-structured-output.ts +4 -5
- package/examples/fork-session.ts +25 -6
- package/examples/hook-execution.ts +62 -0
- package/examples/init-metadata.ts +9 -66
- package/examples/interrupt-session.ts +19 -54
- package/examples/list-sessions.ts +9 -55
- package/examples/multi-turn-session.ts +21 -53
- package/examples/permission-handler.ts +26 -84
- package/examples/readme-structured-output.ts +25 -0
- package/examples/result-metadata.ts +1 -1
- package/examples/run.ts +1 -1
- package/examples/sdk-mcp-tool.ts +11 -7
- package/examples/session-stream.ts +54 -0
- package/examples/spec-mode-new-session.ts +38 -99
- package/examples/spec-mode-same-session.ts +49 -111
- package/examples/structured-output.ts +16 -10
- package/examples/test-compact.ts +5 -3
- package/examples/test-rewind.ts +49 -117
- package/examples/tool-controls.ts +22 -55
- package/package.json +1 -1
- package/docs/examples/init-metadata.md +0 -79
- package/docs/examples/list-sessions.md +0 -64
- package/docs/examples/multi-turn-session.md +0 -77
- package/docs/examples/one-shot-query.md +0 -73
- package/docs/examples/permission-handler.md +0 -102
- package/docs/examples/run.md +0 -52
- package/docs/examples/spec-mode.md +0 -101
- package/docs/examples/tool-controls.md +0 -90
- package/docs/typescript-sdk-reference.md +0 -372
- package/examples/simple-query.ts +0 -53
|
@@ -1,98 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Permission handler example.
|
|
3
3
|
*
|
|
4
|
-
* Demonstrates using `query()` with a custom `permissionHandler` that
|
|
5
|
-
* logs tool confirmation details, approves only one expected file creation,
|
|
6
|
-
* and cancels any unexpected tool request.
|
|
7
|
-
*
|
|
8
4
|
* Usage:
|
|
9
5
|
* npx tsx examples/permission-handler.ts
|
|
10
6
|
*/
|
|
11
7
|
|
|
12
|
-
import { mkdtemp, rm } from 'node:fs/promises';
|
|
8
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
13
9
|
import { tmpdir } from 'node:os';
|
|
14
10
|
import { join } from 'node:path';
|
|
15
11
|
|
|
16
12
|
import {
|
|
17
|
-
DroidMessageType,
|
|
18
|
-
query,
|
|
19
13
|
ToolConfirmationOutcome,
|
|
20
14
|
ToolConfirmationType,
|
|
21
|
-
|
|
22
|
-
} from '
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return ToolConfirmationOutcome.ProceedOnce;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async function main(): Promise<void> {
|
|
50
|
-
const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-permission-'));
|
|
51
|
-
const outputPath = join(tempDir, 'hello.txt');
|
|
52
|
-
const prompt =
|
|
53
|
-
process.argv[2] ??
|
|
54
|
-
`Create a file called ${outputPath} with the text 'Hello, World!'`;
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
console.log(`Sending prompt: "${prompt}"\n`);
|
|
58
|
-
|
|
59
|
-
const stream = query({
|
|
60
|
-
prompt,
|
|
61
|
-
cwd: process.cwd(),
|
|
62
|
-
permissionHandler: (params) => permissionHandler(outputPath, params),
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
for await (const msg of stream) {
|
|
66
|
-
switch (msg.type) {
|
|
67
|
-
case DroidMessageType.AssistantTextDelta:
|
|
68
|
-
process.stdout.write(msg.text);
|
|
69
|
-
break;
|
|
70
|
-
|
|
71
|
-
case DroidMessageType.ToolUse:
|
|
72
|
-
console.log(`\n[Tool Use] ${msg.toolName}`);
|
|
73
|
-
break;
|
|
74
|
-
|
|
75
|
-
case DroidMessageType.ToolResult:
|
|
76
|
-
console.log(`[Tool Result] ${msg.isError ? 'Error' : 'Success'}`);
|
|
77
|
-
break;
|
|
78
|
-
|
|
79
|
-
case DroidMessageType.TurnComplete:
|
|
80
|
-
console.log('\n\n--- Turn complete ---');
|
|
81
|
-
if (msg.tokenUsage) {
|
|
82
|
-
console.log(
|
|
83
|
-
`Tokens — input: ${msg.tokenUsage.inputTokens}, ` +
|
|
84
|
-
`output: ${msg.tokenUsage.outputTokens}`
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
} finally {
|
|
91
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
92
|
-
}
|
|
15
|
+
run,
|
|
16
|
+
} from '@factory/droid-sdk';
|
|
17
|
+
|
|
18
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-permission-'));
|
|
19
|
+
const outputPath = join(tempDir, 'hello.txt');
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await run(`Create ${outputPath} with the text "Hello, World!"`, {
|
|
23
|
+
cwd: process.cwd(),
|
|
24
|
+
permissionHandler(params) {
|
|
25
|
+
const onlyCreatesExpectedFile = params.toolUses.every(
|
|
26
|
+
(item) =>
|
|
27
|
+
item.details.type === ToolConfirmationType.Create &&
|
|
28
|
+
item.details.filePath === outputPath
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
return onlyCreatesExpectedFile
|
|
32
|
+
? ToolConfirmationOutcome.ProceedOnce
|
|
33
|
+
: ToolConfirmationOutcome.Cancel;
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
console.log(await readFile(outputPath, 'utf8'));
|
|
38
|
+
} finally {
|
|
39
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
93
40
|
}
|
|
94
|
-
|
|
95
|
-
main().catch((err: unknown) => {
|
|
96
|
-
console.error('Error:', err);
|
|
97
|
-
process.exit(1);
|
|
98
|
-
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { OutputFormatType, run } from '@factory/droid-sdk';
|
|
2
|
+
|
|
3
|
+
type FavoriteNumber = { favoriteNumber: number };
|
|
4
|
+
|
|
5
|
+
const result = await run('Pick a favorite number between 1 and 42.', {
|
|
6
|
+
cwd: process.cwd(),
|
|
7
|
+
outputFormat: {
|
|
8
|
+
type: OutputFormatType.JsonSchema,
|
|
9
|
+
schema: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
favoriteNumber: {
|
|
13
|
+
type: 'number',
|
|
14
|
+
minimum: 1,
|
|
15
|
+
maximum: 42,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
required: ['favoriteNumber'],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
console.log(
|
|
24
|
+
(result.structuredOutput as FavoriteNumber | undefined)?.favoriteNumber
|
|
25
|
+
);
|
package/examples/run.ts
CHANGED
package/examples/sdk-mcp-tool.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
|
|
3
1
|
import {
|
|
2
|
+
DroidMessageType,
|
|
4
3
|
ToolConfirmationOutcome,
|
|
5
4
|
createSession,
|
|
6
5
|
createSdkMcpServer,
|
|
7
6
|
tool,
|
|
8
|
-
} from '
|
|
7
|
+
} from '@factory/droid-sdk';
|
|
8
|
+
import { z } from 'zod';
|
|
9
9
|
|
|
10
|
-
const execPath = process.env['DROID_EXEC_PATH'] ?? 'droid';
|
|
10
|
+
const execPath = process.env['DROID_EXEC_PATH'] ?? 'droid-dev';
|
|
11
11
|
|
|
12
12
|
const sdkTools = createSdkMcpServer({
|
|
13
13
|
name: 'sdk-tools',
|
|
@@ -29,10 +29,14 @@ const session = await createSession({
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
try {
|
|
32
|
-
const
|
|
32
|
+
for await (const msg of session.stream(
|
|
33
33
|
'Use the favorite_number tool for Ada and tell me the answer.'
|
|
34
|
-
)
|
|
35
|
-
|
|
34
|
+
)) {
|
|
35
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
36
|
+
process.stdout.write(msg.text);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
console.log();
|
|
36
40
|
} finally {
|
|
37
41
|
await session.close();
|
|
38
42
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple session streaming example.
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates using `session.stream()` to send a prompt, streaming
|
|
5
|
+
* full assistant/tool messages, and handling the final `result`.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx tsx examples/session-stream.ts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { DroidMessageType, createSession } from '@factory/droid-sdk';
|
|
12
|
+
|
|
13
|
+
async function main(): Promise<void> {
|
|
14
|
+
const prompt = process.argv[2] ?? 'What files are in the current directory?';
|
|
15
|
+
|
|
16
|
+
console.log(`Sending prompt: "${prompt}"\n`);
|
|
17
|
+
|
|
18
|
+
const session = await createSession({ cwd: process.cwd() });
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
for await (const msg of session.stream(prompt)) {
|
|
22
|
+
switch (msg.type) {
|
|
23
|
+
case DroidMessageType.Assistant:
|
|
24
|
+
process.stdout.write(msg.text);
|
|
25
|
+
break;
|
|
26
|
+
|
|
27
|
+
case DroidMessageType.ToolCall:
|
|
28
|
+
console.log(`\n[Tool] ${msg.toolUse.name}`);
|
|
29
|
+
break;
|
|
30
|
+
|
|
31
|
+
case DroidMessageType.ToolResult:
|
|
32
|
+
console.log(`[Tool Result] ${msg.isError ? 'Error' : 'OK'}`);
|
|
33
|
+
break;
|
|
34
|
+
|
|
35
|
+
case DroidMessageType.Result:
|
|
36
|
+
console.log('\n\n--- Turn complete ---');
|
|
37
|
+
if (msg.tokenUsage) {
|
|
38
|
+
console.log(
|
|
39
|
+
`Tokens — input: ${msg.tokenUsage.inputTokens}, ` +
|
|
40
|
+
`output: ${msg.tokenUsage.outputTokens}`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} finally {
|
|
47
|
+
await session.close();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
main().catch((err: unknown) => {
|
|
52
|
+
console.error('Error:', err);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
});
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Spec mode: approve and hand off to a new session.
|
|
3
|
-
*
|
|
4
|
-
* The agent drafts a plan in spec mode. On approval with
|
|
5
|
-
* ProceedNewSessionHigh, the server automatically creates a
|
|
6
|
-
* fresh session and implements the plan. The results stream
|
|
7
|
-
* back through the same query — no manual session creation needed.
|
|
2
|
+
* Spec mode: approve and hand off implementation to a new session.
|
|
8
3
|
*
|
|
9
4
|
* Usage:
|
|
10
5
|
* npx tsx examples/spec-mode-new-session.ts
|
|
@@ -15,107 +10,51 @@ import { tmpdir } from 'node:os';
|
|
|
15
10
|
import { join } from 'node:path';
|
|
16
11
|
|
|
17
12
|
import {
|
|
18
|
-
DroidMessageType,
|
|
19
13
|
DroidInteractionMode,
|
|
20
|
-
query,
|
|
21
14
|
ReasoningEffort,
|
|
22
15
|
ToolConfirmationOutcome,
|
|
23
16
|
ToolConfirmationType,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
specModeReasoningEffort: ReasoningEffort.High,
|
|
45
|
-
permissionHandler(params) {
|
|
46
|
-
const exitSpec = params.toolUses.find(
|
|
47
|
-
(item) => item.details.type === ToolConfirmationType.ExitSpecMode
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
if (exitSpec?.details.type === ToolConfirmationType.ExitSpecMode) {
|
|
51
|
-
console.log(
|
|
52
|
-
'\n--- Plan received, approving with new session handoff ---'
|
|
53
|
-
);
|
|
54
|
-
console.log(exitSpec.details.plan);
|
|
55
|
-
console.log('---\n');
|
|
56
|
-
|
|
57
|
-
// The server creates a fresh session and implements the plan.
|
|
58
|
-
// Results continue streaming through this same query.
|
|
59
|
-
return ToolConfirmationOutcome.ProceedNewSessionHigh;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const onlyAllowedCreate = params.toolUses.every(
|
|
63
|
-
(item) =>
|
|
64
|
-
item.details.type === ToolConfirmationType.Create &&
|
|
65
|
-
item.details.filePath === outputPath
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
return onlyAllowedCreate
|
|
69
|
-
? ToolConfirmationOutcome.ProceedOnce
|
|
70
|
-
: ToolConfirmationOutcome.Cancel;
|
|
71
|
-
},
|
|
72
|
-
});
|
|
17
|
+
createSession,
|
|
18
|
+
} from '@factory/droid-sdk';
|
|
19
|
+
|
|
20
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-spec-'));
|
|
21
|
+
const outputPath = join(tempDir, 'hello.txt');
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const session = await createSession({
|
|
25
|
+
cwd: process.cwd(),
|
|
26
|
+
interactionMode: DroidInteractionMode.Spec,
|
|
27
|
+
specModeReasoningEffort: ReasoningEffort.High,
|
|
28
|
+
permissionHandler(params) {
|
|
29
|
+
const canExitSpec = params.toolUses.some(
|
|
30
|
+
(item) => item.details.type === ToolConfirmationType.ExitSpecMode
|
|
31
|
+
);
|
|
32
|
+
const onlyCreatesFile = params.toolUses.every(
|
|
33
|
+
(item) =>
|
|
34
|
+
item.details.type === ToolConfirmationType.Create &&
|
|
35
|
+
item.details.filePath === outputPath
|
|
36
|
+
);
|
|
73
37
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
case DroidMessageType.AssistantTextDelta:
|
|
77
|
-
process.stdout.write(msg.text);
|
|
78
|
-
break;
|
|
79
|
-
case DroidMessageType.ToolUse:
|
|
80
|
-
console.log(`\n[Tool] ${msg.toolName}`);
|
|
81
|
-
break;
|
|
82
|
-
case DroidMessageType.ToolResult:
|
|
83
|
-
if (msg.isError) {
|
|
84
|
-
toolError = JSON.stringify(msg.content);
|
|
85
|
-
}
|
|
86
|
-
console.log(`[Tool Result] ${msg.isError ? 'Error' : 'OK'}`);
|
|
87
|
-
break;
|
|
88
|
-
case DroidMessageType.TurnComplete:
|
|
89
|
-
console.log('\n\n--- Turn complete ---');
|
|
90
|
-
if (msg.tokenUsage) {
|
|
91
|
-
console.log(
|
|
92
|
-
`Tokens — input: ${msg.tokenUsage.inputTokens}, output: ${msg.tokenUsage.outputTokens}`
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
break;
|
|
38
|
+
if (canExitSpec) {
|
|
39
|
+
return ToolConfirmationOutcome.ProceedNewSessionHigh;
|
|
96
40
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
);
|
|
103
|
-
}
|
|
41
|
+
return onlyCreatesFile
|
|
42
|
+
? ToolConfirmationOutcome.ProceedOnce
|
|
43
|
+
: ToolConfirmationOutcome.Cancel;
|
|
44
|
+
},
|
|
45
|
+
});
|
|
104
46
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
47
|
+
try {
|
|
48
|
+
for await (const _msg of session.stream(
|
|
49
|
+
`Plan then create ${outputPath} containing "Hello from Droid".`
|
|
50
|
+
)) {
|
|
51
|
+
// Consume the stream until the handoff implementation finishes.
|
|
110
52
|
}
|
|
111
|
-
|
|
112
|
-
console.log(`\nVerified ${outputPath}.`);
|
|
113
53
|
} finally {
|
|
114
|
-
await
|
|
54
|
+
await session.close();
|
|
115
55
|
}
|
|
116
|
-
}
|
|
117
56
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
57
|
+
console.log(await readFile(outputPath, 'utf8'));
|
|
58
|
+
} finally {
|
|
59
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
60
|
+
}
|
|
@@ -1,133 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Spec mode: approve and implement in the same session.
|
|
3
3
|
*
|
|
4
|
-
* The agent drafts a plan in spec mode, then implements it
|
|
5
|
-
* in the same query stream after approval.
|
|
6
|
-
*
|
|
7
4
|
* Usage:
|
|
8
5
|
* npx tsx examples/spec-mode-same-session.ts
|
|
9
6
|
*/
|
|
10
7
|
|
|
11
|
-
import { mkdtemp, rm } from 'node:fs/promises';
|
|
12
|
-
import { readFile } from 'node:fs/promises';
|
|
8
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
13
9
|
import { tmpdir } from 'node:os';
|
|
14
10
|
import { join } from 'node:path';
|
|
15
11
|
|
|
16
12
|
import {
|
|
17
|
-
DroidMessageType,
|
|
18
13
|
DroidInteractionMode,
|
|
19
|
-
query,
|
|
20
14
|
ReasoningEffort,
|
|
21
15
|
ToolConfirmationOutcome,
|
|
22
16
|
ToolConfirmationType,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
17
|
+
createSession,
|
|
18
|
+
} from '@factory/droid-sdk';
|
|
19
|
+
|
|
20
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-spec-'));
|
|
21
|
+
const outputPath = join(tempDir, 'hello.txt');
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const session = await createSession({
|
|
25
|
+
cwd: process.cwd(),
|
|
26
|
+
interactionMode: DroidInteractionMode.Spec,
|
|
27
|
+
specModeReasoningEffort: ReasoningEffort.High,
|
|
28
|
+
permissionHandler(params) {
|
|
29
|
+
const canExitSpec = params.toolUses.some(
|
|
30
|
+
(item) => item.details.type === ToolConfirmationType.ExitSpecMode
|
|
31
|
+
);
|
|
32
|
+
const onlyCreatesFile = params.toolUses.every(
|
|
33
|
+
(item) =>
|
|
34
|
+
item.details.type === ToolConfirmationType.Create &&
|
|
35
|
+
item.details.filePath === outputPath
|
|
36
|
+
);
|
|
37
|
+
const onlyEditsTempFile = params.toolUses.every(
|
|
38
|
+
(item) =>
|
|
39
|
+
item.details.type === ToolConfirmationType.ApplyPatch &&
|
|
40
|
+
item.details.filePath === outputPath
|
|
41
|
+
);
|
|
42
|
+
const onlyRunsTempCommand = params.toolUses.every(
|
|
43
|
+
(item) =>
|
|
44
|
+
item.details.type === ToolConfirmationType.Execute &&
|
|
45
|
+
item.details.fullCommand.includes(outputPath) &&
|
|
46
|
+
item.details.fullCommand.includes(tempDir)
|
|
47
|
+
);
|
|
33
48
|
|
|
34
|
-
|
|
49
|
+
return canExitSpec ||
|
|
50
|
+
onlyCreatesFile ||
|
|
51
|
+
onlyEditsTempFile ||
|
|
52
|
+
onlyRunsTempCommand
|
|
53
|
+
? ToolConfirmationOutcome.ProceedOnce
|
|
54
|
+
: ToolConfirmationOutcome.Cancel;
|
|
55
|
+
},
|
|
56
|
+
});
|
|
35
57
|
|
|
36
58
|
try {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
cwd: process.cwd(),
|
|
42
|
-
interactionMode: DroidInteractionMode.Spec,
|
|
43
|
-
specModeReasoningEffort: ReasoningEffort.High,
|
|
44
|
-
permissionHandler(params) {
|
|
45
|
-
const exitSpec = params.toolUses.find(
|
|
46
|
-
(item) => item.details.type === ToolConfirmationType.ExitSpecMode
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
if (exitSpec?.details.type === ToolConfirmationType.ExitSpecMode) {
|
|
50
|
-
console.log('\n--- Plan received, approving in same session ---');
|
|
51
|
-
console.log(exitSpec.details.plan);
|
|
52
|
-
console.log('---\n');
|
|
53
|
-
return ToolConfirmationOutcome.ProceedOnce;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const onlyAllowedCreate = params.toolUses.every(
|
|
57
|
-
(item) =>
|
|
58
|
-
item.details.type === ToolConfirmationType.Create &&
|
|
59
|
-
item.details.filePath === outputPath
|
|
60
|
-
);
|
|
61
|
-
const onlyAllowedApplyPatch = params.toolUses.every(
|
|
62
|
-
(item) =>
|
|
63
|
-
item.details.type === ToolConfirmationType.ApplyPatch &&
|
|
64
|
-
item.details.filePath === outputPath
|
|
65
|
-
);
|
|
66
|
-
const onlyAllowedExecute = params.toolUses.every((item) => {
|
|
67
|
-
if (item.details.type !== ToolConfirmationType.Execute) {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const command = item.details.fullCommand;
|
|
72
|
-
return (
|
|
73
|
-
command.includes(outputPath) &&
|
|
74
|
-
command.includes(tempDir) &&
|
|
75
|
-
!command.includes('sudo') &&
|
|
76
|
-
!command.includes('rm -rf /')
|
|
77
|
-
);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
return onlyAllowedCreate || onlyAllowedApplyPatch || onlyAllowedExecute
|
|
81
|
-
? ToolConfirmationOutcome.ProceedOnce
|
|
82
|
-
: ToolConfirmationOutcome.Cancel;
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
for await (const msg of stream) {
|
|
87
|
-
switch (msg.type) {
|
|
88
|
-
case DroidMessageType.AssistantTextDelta:
|
|
89
|
-
process.stdout.write(msg.text);
|
|
90
|
-
break;
|
|
91
|
-
case DroidMessageType.ToolUse:
|
|
92
|
-
console.log(`\n[Tool] ${msg.toolName}`);
|
|
93
|
-
break;
|
|
94
|
-
case DroidMessageType.ToolResult:
|
|
95
|
-
if (msg.isError) {
|
|
96
|
-
toolError = JSON.stringify(msg.content);
|
|
97
|
-
}
|
|
98
|
-
console.log(`[Tool Result] ${msg.isError ? 'Error' : 'OK'}`);
|
|
99
|
-
break;
|
|
100
|
-
case DroidMessageType.TurnComplete:
|
|
101
|
-
console.log('\n\n--- Turn complete ---');
|
|
102
|
-
if (msg.tokenUsage) {
|
|
103
|
-
console.log(
|
|
104
|
-
`Tokens — input: ${msg.tokenUsage.inputTokens}, output: ${msg.tokenUsage.outputTokens}`
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (toolError) {
|
|
112
|
-
throw new Error(
|
|
113
|
-
`Tool failed during spec-mode implementation: ${toolError}`
|
|
114
|
-
);
|
|
59
|
+
for await (const _msg of session.stream(
|
|
60
|
+
`Plan then create ${outputPath} containing "Hello from Droid".`
|
|
61
|
+
)) {
|
|
62
|
+
// Consume the stream until implementation finishes.
|
|
115
63
|
}
|
|
116
|
-
|
|
117
|
-
const content = await readFile(outputPath, 'utf8');
|
|
118
|
-
if (content.trim() !== EXPECTED_CONTENT) {
|
|
119
|
-
throw new Error(
|
|
120
|
-
`Expected ${outputPath} to contain ${JSON.stringify(EXPECTED_CONTENT)}, got ${JSON.stringify(content)}`
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
console.log(`\nVerified ${outputPath}.`);
|
|
125
64
|
} finally {
|
|
126
|
-
await
|
|
65
|
+
await session.close();
|
|
127
66
|
}
|
|
128
|
-
}
|
|
129
67
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
68
|
+
console.log(await readFile(outputPath, 'utf8'));
|
|
69
|
+
} finally {
|
|
70
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
71
|
+
}
|
|
@@ -6,27 +6,32 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* npx tsx examples/structured-output.ts
|
|
9
|
-
* npx tsx examples/structured-output.ts "
|
|
9
|
+
* npx tsx examples/structured-output.ts "Pick a favorite number between 1 and 42"
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import assert from 'node:assert/strict';
|
|
13
13
|
|
|
14
|
-
import { OutputFormatType, run } from '
|
|
14
|
+
import { OutputFormatType, run } from '@factory/droid-sdk';
|
|
15
|
+
|
|
16
|
+
type FavoriteNumber = { favoriteNumber: number };
|
|
15
17
|
|
|
16
18
|
async function main(): Promise<void> {
|
|
17
19
|
const prompt =
|
|
18
20
|
process.argv.slice(2).join(' ') ||
|
|
19
|
-
'
|
|
21
|
+
'Pick a favorite number between 1 and 42.';
|
|
20
22
|
|
|
21
23
|
const outputFormat = {
|
|
22
24
|
type: OutputFormatType.JsonSchema,
|
|
23
25
|
schema: {
|
|
24
26
|
type: 'object',
|
|
25
27
|
properties: {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
favoriteNumber: {
|
|
29
|
+
type: 'number',
|
|
30
|
+
minimum: 1,
|
|
31
|
+
maximum: 42,
|
|
32
|
+
},
|
|
28
33
|
},
|
|
29
|
-
required: ['
|
|
34
|
+
required: ['favoriteNumber'],
|
|
30
35
|
},
|
|
31
36
|
};
|
|
32
37
|
|
|
@@ -37,12 +42,13 @@ async function main(): Promise<void> {
|
|
|
37
42
|
outputFormat,
|
|
38
43
|
});
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
assert.
|
|
45
|
+
const structuredOutput = result.structuredOutput as FavoriteNumber | null;
|
|
46
|
+
|
|
47
|
+
assert.ok(structuredOutput, 'Expected structuredOutput to be set');
|
|
48
|
+
assert.equal(typeof structuredOutput.favoriteNumber, 'number');
|
|
43
49
|
|
|
44
50
|
console.log('=== Structured output ===');
|
|
45
|
-
console.log(JSON.stringify(
|
|
51
|
+
console.log(JSON.stringify(structuredOutput, null, 2));
|
|
46
52
|
|
|
47
53
|
console.log('\nStructured output example passed');
|
|
48
54
|
}
|