@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.
Files changed (39) hide show
  1. package/README.md +214 -82
  2. package/dist/index.cjs +734 -611
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +1649 -206
  5. package/dist/index.d.ts +1649 -206
  6. package/dist/index.js +729 -624
  7. package/dist/index.js.map +1 -1
  8. package/docs/sdk-usage-guide.md +678 -0
  9. package/examples/abort-session-stream.ts +19 -55
  10. package/examples/droid-dev-structured-output.ts +4 -5
  11. package/examples/fork-session.ts +25 -6
  12. package/examples/hook-execution.ts +62 -0
  13. package/examples/init-metadata.ts +9 -66
  14. package/examples/interrupt-session.ts +19 -54
  15. package/examples/list-sessions.ts +9 -55
  16. package/examples/multi-turn-session.ts +21 -53
  17. package/examples/permission-handler.ts +26 -84
  18. package/examples/readme-structured-output.ts +25 -0
  19. package/examples/result-metadata.ts +1 -1
  20. package/examples/run.ts +1 -1
  21. package/examples/sdk-mcp-tool.ts +11 -7
  22. package/examples/session-stream.ts +54 -0
  23. package/examples/spec-mode-new-session.ts +38 -99
  24. package/examples/spec-mode-same-session.ts +49 -111
  25. package/examples/structured-output.ts +16 -10
  26. package/examples/test-compact.ts +5 -3
  27. package/examples/test-rewind.ts +49 -117
  28. package/examples/tool-controls.ts +22 -55
  29. package/package.json +1 -1
  30. package/docs/examples/init-metadata.md +0 -79
  31. package/docs/examples/list-sessions.md +0 -64
  32. package/docs/examples/multi-turn-session.md +0 -77
  33. package/docs/examples/one-shot-query.md +0 -73
  34. package/docs/examples/permission-handler.md +0 -102
  35. package/docs/examples/run.md +0 -52
  36. package/docs/examples/spec-mode.md +0 -101
  37. package/docs/examples/tool-controls.md +0 -90
  38. package/docs/typescript-sdk-reference.md +0 -372
  39. 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
- type RequestPermissionRequestParams,
22
- } from '../src/index.js';
23
-
24
- function permissionHandler(
25
- allowedFilePath: string,
26
- params: RequestPermissionRequestParams
27
- ): ToolConfirmationOutcome {
28
- for (const item of params.toolUses) {
29
- console.log(`\n[Permission] Tool: ${item.toolUse.name}`);
30
- console.log(` Type: ${item.confirmationType}`);
31
- console.log(` Input: ${JSON.stringify(item.toolUse.input, null, 2)}`);
32
- }
33
-
34
- const onlyAllowedCreate = params.toolUses.every(
35
- (item) =>
36
- item.details.type === ToolConfirmationType.Create &&
37
- item.details.filePath === allowedFilePath
38
- );
39
-
40
- if (!onlyAllowedCreate) {
41
- console.log(' → Cancelling unexpected tool request\n');
42
- return ToolConfirmationOutcome.Cancel;
43
- }
44
-
45
- console.log(' → Approving expected file creation with ProceedOnce\n');
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
+ );
@@ -12,7 +12,7 @@
12
12
 
13
13
  import assert from 'node:assert/strict';
14
14
 
15
- import { run } from '../src/index.js';
15
+ import { run } from '@factory/droid-sdk';
16
16
 
17
17
  async function main(): Promise<void> {
18
18
  const prompt =
package/examples/run.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * npx tsx examples/run.ts "What is 2 + 2?"
10
10
  */
11
11
 
12
- import { run } from '../src/index.js';
12
+ import { run } from '@factory/droid-sdk';
13
13
 
14
14
  async function main(): Promise<void> {
15
15
  const text = process.argv.slice(2).join(' ') || 'What is 2 + 2?';
@@ -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 '../src/index.js';
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 result = await session.send(
32
+ for await (const msg of session.stream(
33
33
  'Use the favorite_number tool for Ada and tell me the answer.'
34
- );
35
- console.log(result.text);
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
- } from '../src/index.js';
25
-
26
- const EXPECTED_CONTENT = 'Hello from Droid';
27
-
28
- async function main(): Promise<void> {
29
- const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-spec-'));
30
- const outputPath = join(tempDir, 'hello-from-droid.txt');
31
- const prompt =
32
- `Plan how to create a small ${outputPath} file containing the text ` +
33
- '"Hello from Droid". Keep the plan short and concrete.';
34
-
35
- console.log('=== Spec Mode: New Session Handoff ===\n');
36
-
37
- try {
38
- let toolError: string | null = null;
39
-
40
- const stream = query({
41
- prompt,
42
- cwd: process.cwd(),
43
- interactionMode: DroidInteractionMode.Spec,
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
- for await (const msg of stream) {
75
- switch (msg.type) {
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
- if (toolError) {
100
- throw new Error(
101
- `Tool failed during spec-mode implementation: ${toolError}`
102
- );
103
- }
41
+ return onlyCreatesFile
42
+ ? ToolConfirmationOutcome.ProceedOnce
43
+ : ToolConfirmationOutcome.Cancel;
44
+ },
45
+ });
104
46
 
105
- const content = await readFile(outputPath, 'utf8');
106
- if (content.trim() !== EXPECTED_CONTENT) {
107
- throw new Error(
108
- `Expected ${outputPath} to contain ${JSON.stringify(EXPECTED_CONTENT)}, got ${JSON.stringify(content)}`
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 rm(tempDir, { recursive: true, force: true });
54
+ await session.close();
115
55
  }
116
- }
117
56
 
118
- main().catch((err: unknown) => {
119
- console.error('Error:', err);
120
- process.exit(1);
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
- } from '../src/index.js';
24
-
25
- const EXPECTED_CONTENT = 'Hello from Droid';
26
-
27
- async function main(): Promise<void> {
28
- const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-spec-'));
29
- const outputPath = join(tempDir, 'hello-from-droid.txt');
30
- const prompt =
31
- `Plan how to create a small ${outputPath} file containing the text ` +
32
- '"Hello from Droid". Keep the plan short and concrete.';
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
- console.log('=== Spec Mode: Same Session ===\n');
49
+ return canExitSpec ||
50
+ onlyCreatesFile ||
51
+ onlyEditsTempFile ||
52
+ onlyRunsTempCommand
53
+ ? ToolConfirmationOutcome.ProceedOnce
54
+ : ToolConfirmationOutcome.Cancel;
55
+ },
56
+ });
35
57
 
36
58
  try {
37
- let toolError: string | null = null;
38
-
39
- const stream = query({
40
- prompt,
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 rm(tempDir, { recursive: true, force: true });
65
+ await session.close();
127
66
  }
128
- }
129
67
 
130
- main().catch((err: unknown) => {
131
- console.error('Error:', err);
132
- process.exit(1);
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 "Return a TypeScript pioneer"
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 '../src/index.js';
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
- 'Return exactly this person: name Ada Lovelace, language TypeScript.';
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
- name: { type: 'string' },
27
- language: { type: 'string' },
28
+ favoriteNumber: {
29
+ type: 'number',
30
+ minimum: 1,
31
+ maximum: 42,
32
+ },
28
33
  },
29
- required: ['name', 'language'],
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
- assert.ok(result.structuredOutput, 'Expected structuredOutput to be set');
41
- assert.equal(typeof result.structuredOutput['name'], 'string');
42
- assert.equal(typeof result.structuredOutput['language'], 'string');
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(result.structuredOutput, null, 2));
51
+ console.log(JSON.stringify(structuredOutput, null, 2));
46
52
 
47
53
  console.log('\nStructured output example passed');
48
54
  }