@factory/droid-sdk 0.2.0 → 0.4.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 (41) hide show
  1. package/README.md +214 -82
  2. package/dist/index.cjs +2345 -692
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +2570 -232
  5. package/dist/index.d.ts +2570 -232
  6. package/dist/index.js +2298 -697
  7. package/dist/index.js.map +1 -1
  8. package/docs/daemon-usage-guide.md +544 -0
  9. package/docs/sdk-usage-guide.md +748 -0
  10. package/examples/abort-session-stream.ts +23 -56
  11. package/examples/daemon-multi-session.ts +67 -0
  12. package/examples/droid-dev-structured-output.ts +5 -5
  13. package/examples/fork-session.ts +33 -7
  14. package/examples/hook-execution.ts +68 -0
  15. package/examples/init-metadata.ts +14 -66
  16. package/examples/interrupt-session.ts +23 -55
  17. package/examples/list-sessions.ts +9 -55
  18. package/examples/multi-turn-session.ts +27 -54
  19. package/examples/permission-handler.ts +27 -84
  20. package/examples/readme-structured-output.ts +26 -0
  21. package/examples/result-metadata.ts +2 -1
  22. package/examples/run.ts +2 -1
  23. package/examples/sdk-mcp-tool.ts +14 -8
  24. package/examples/session-stream.ts +57 -0
  25. package/examples/spec-mode-new-session.ts +39 -99
  26. package/examples/spec-mode-same-session.ts +50 -111
  27. package/examples/structured-output.ts +17 -10
  28. package/examples/test-compact.ts +9 -4
  29. package/examples/test-rewind.ts +49 -117
  30. package/examples/tool-controls.ts +23 -55
  31. package/package.json +3 -1
  32. package/docs/examples/init-metadata.md +0 -79
  33. package/docs/examples/list-sessions.md +0 -64
  34. package/docs/examples/multi-turn-session.md +0 -77
  35. package/docs/examples/one-shot-query.md +0 -73
  36. package/docs/examples/permission-handler.md +0 -102
  37. package/docs/examples/run.md +0 -52
  38. package/docs/examples/spec-mode.md +0 -101
  39. package/docs/examples/tool-controls.md +0 -90
  40. package/docs/typescript-sdk-reference.md +0 -372
  41. package/examples/simple-query.ts +0 -53
@@ -1,67 +1,34 @@
1
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.
2
+ * Abort a running turn with AbortController.
6
3
  *
7
4
  * Usage:
8
5
  * npx tsx examples/abort-session-stream.ts
9
- * npx tsx examples/abort-session-stream.ts 3000
10
6
  */
11
7
 
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.';
8
+ import { createSession } from '@factory/droid-sdk';
36
9
 
37
- for await (const message of session.stream(prompt, {
10
+ const session = await createSession({
11
+ apiKey: process.env.FACTORY_API_KEY!,
12
+ cwd: process.cwd(),
13
+ });
14
+ const controller = new AbortController();
15
+ const timeout = setTimeout(
16
+ () => controller.abort(new Error('Stopped by AbortController')),
17
+ 1000
18
+ );
19
+
20
+ try {
21
+ for await (const _msg of session.stream(
22
+ 'Write a long explanation of how compilers work.',
23
+ {
38
24
  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
25
  }
55
-
56
- throw error;
57
- } finally {
58
- clearTimeout(timeout);
59
- await session.close();
60
- console.log('Session closed.');
26
+ )) {
27
+ void _msg;
61
28
  }
29
+ } catch (error) {
30
+ console.log(error instanceof Error ? error.message : String(error));
31
+ } finally {
32
+ clearTimeout(timeout);
33
+ await session.close();
62
34
  }
63
-
64
- main().catch((err: unknown) => {
65
- console.error('Error:', err);
66
- process.exit(1);
67
- });
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Manual smoke test for the daemon SDK — multiple concurrent sessions.
3
+ *
4
+ * Spawns a local daemon, creates two sessions in separate /tmp directories,
5
+ * and runs them concurrently over a single WebSocket connection.
6
+ *
7
+ * Usage:
8
+ * npx tsx examples/daemon-multi-session.ts
9
+ */
10
+
11
+ import { connectDaemon, DroidMessageType } from '@factory/droid-sdk';
12
+
13
+ async function main(): Promise<void> {
14
+ console.log('Connecting to local daemon...\n');
15
+ const daemon = await connectDaemon({ apiKey: process.env.FACTORY_API_KEY! });
16
+ console.log('Connected!\n');
17
+
18
+ const frontend = await daemon.createSession({
19
+ cwd: '/tmp/daemon-test-frontend',
20
+ });
21
+ const backend = await daemon.createSession({
22
+ cwd: '/tmp/daemon-test-backend',
23
+ });
24
+
25
+ console.log('Two sessions created. Running concurrently...\n');
26
+
27
+ await Promise.all([
28
+ (async () => {
29
+ for await (const msg of frontend.stream(
30
+ 'Create a file called hello.md with a short greeting message. Just a few lines.'
31
+ )) {
32
+ if (msg.type === DroidMessageType.Assistant) {
33
+ process.stdout.write(`[frontend] ${msg.text}\n`);
34
+ } else if (msg.type === DroidMessageType.ToolCall) {
35
+ console.log(`[frontend] [tool] ${msg.toolUse.name}`);
36
+ } else if (msg.type === DroidMessageType.Result) {
37
+ console.log(`[frontend] Done in ${msg.durationMs}ms`);
38
+ }
39
+ }
40
+ await frontend.close();
41
+ })(),
42
+ (async () => {
43
+ for await (const msg of backend.stream(
44
+ 'Create a file called notes.md with 3 random fun facts. Keep it short.'
45
+ )) {
46
+ if (msg.type === DroidMessageType.Assistant) {
47
+ process.stdout.write(`[backend] ${msg.text}\n`);
48
+ } else if (msg.type === DroidMessageType.ToolCall) {
49
+ console.log(`[backend] [tool] ${msg.toolUse.name}`);
50
+ } else if (msg.type === DroidMessageType.Result) {
51
+ console.log(`[backend] Done in ${msg.durationMs}ms`);
52
+ }
53
+ }
54
+ await backend.close();
55
+ })(),
56
+ ]);
57
+
58
+ await daemon.close();
59
+ console.log(
60
+ '\nDone. Check /tmp/daemon-test-frontend/hello.md and /tmp/daemon-test-backend/notes.md'
61
+ );
62
+ }
63
+
64
+ main().catch((err: unknown) => {
65
+ console.error('Error:', err);
66
+ process.exit(1);
67
+ });
@@ -1,15 +1,14 @@
1
1
  /**
2
- * One-off integration smoke test for JSON output against droid-dev.
2
+ * One-off integration smoke test for JSON output against Droid.
3
3
  *
4
4
  * Usage:
5
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
6
+ * DROID_EXEC_PATH=/path/to/droid npx tsx examples/droid-dev-structured-output.ts
7
7
  */
8
8
 
9
+ import { run } from '@factory/droid-sdk';
9
10
  import { z } from 'zod';
10
11
 
11
- import { run } from '../src/index.js';
12
-
13
12
  const PersonSchema = z.object({
14
13
  name: z.literal('Ada Lovelace'),
15
14
  language: z.literal('TypeScript'),
@@ -17,7 +16,7 @@ const PersonSchema = z.object({
17
16
  });
18
17
 
19
18
  async function main(): Promise<void> {
20
- const execPath = process.env['DROID_EXEC_PATH'] ?? 'droid-dev';
19
+ const execPath = process.env['DROID_EXEC_PATH'] ?? 'droid';
21
20
 
22
21
  const result = await run(
23
22
  [
@@ -26,6 +25,7 @@ async function main(): Promise<void> {
26
25
  'Return only valid JSON and do not include markdown fences.',
27
26
  ].join(' '),
28
27
  {
28
+ apiKey: process.env.FACTORY_API_KEY!,
29
29
  execPath,
30
30
  cwd: process.cwd(),
31
31
  }
@@ -10,25 +10,51 @@
10
10
  * npx tsx examples/fork-session.ts
11
11
  */
12
12
 
13
- import { createSession, resumeSession } from '../src/index.js';
13
+ import {
14
+ DroidMessageType,
15
+ createSession,
16
+ resumeSession,
17
+ } from '@factory/droid-sdk';
18
+
19
+ async function streamText(
20
+ session: Awaited<ReturnType<typeof createSession>>,
21
+ prompt: string
22
+ ): Promise<string> {
23
+ let text = '';
24
+ for await (const msg of session.stream(prompt, {
25
+ includePartialMessages: true,
26
+ })) {
27
+ if (msg.type === DroidMessageType.AssistantTextDelta) {
28
+ text += msg.text;
29
+ }
30
+ }
31
+ return text;
32
+ }
14
33
 
15
34
  async function main(): Promise<void> {
16
- const session = await createSession({ cwd: process.cwd() });
35
+ const session = await createSession({
36
+ apiKey: process.env.FACTORY_API_KEY!,
37
+ cwd: process.cwd(),
38
+ });
17
39
  let fork: Awaited<ReturnType<typeof resumeSession>> | null = null;
18
40
 
19
41
  try {
20
42
  console.log(`Original session: ${session.sessionId}\n`);
21
-
22
- await session.send('Remember this phrase exactly: mango sunrise');
43
+ await streamText(session, 'Remember this phrase exactly: mango sunrise');
23
44
 
24
45
  const { newSessionId } = await session.forkSession();
25
46
  console.log(`Forked session: ${newSessionId}\n`);
26
47
 
27
- fork = await resumeSession(newSessionId, { cwd: process.cwd() });
48
+ fork = await resumeSession(newSessionId, {
49
+ apiKey: process.env.FACTORY_API_KEY!,
50
+ });
28
51
 
29
- const result = await fork.send('What phrase did I ask you to remember?');
52
+ const result = await streamText(
53
+ fork,
54
+ 'What phrase did I ask you to remember?'
55
+ );
30
56
  console.log('Fork response:');
31
- console.log(result.text);
57
+ console.log(result);
32
58
  } finally {
33
59
  await fork?.close();
34
60
  await session.close();
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Hook execution streaming example.
3
+ *
4
+ * Demonstrates how to handle hook execution messages in a session stream.
5
+ *
6
+ * Usage:
7
+ * npx tsx examples/hook-execution.ts
8
+ */
9
+
10
+ import { DroidMessageType, createSession } from '../src/index.js';
11
+
12
+ async function main(): Promise<void> {
13
+ const prompt = 'Run a simple shell command using Execute tool.';
14
+
15
+ console.log(`Sending prompt: "${prompt}"\n`);
16
+
17
+ // Note: To actually see hooks, you need to have hooks configured in your droid settings.
18
+ const apiKey = process.env.FACTORY_API_KEY;
19
+ if (!apiKey) {
20
+ console.error('Set FACTORY_API_KEY environment variable.');
21
+ process.exit(1);
22
+ }
23
+
24
+ const session = await createSession({ apiKey, cwd: process.cwd() });
25
+
26
+ try {
27
+ for await (const msg of session.stream(prompt)) {
28
+ switch (msg.type) {
29
+ case DroidMessageType.Assistant:
30
+ process.stdout.write(msg.text);
31
+ break;
32
+
33
+ case DroidMessageType.ToolCall:
34
+ console.log(`\n[Tool Call] ${msg.toolUse.name} (${msg.toolUse.id})`);
35
+ break;
36
+
37
+ case DroidMessageType.Hook:
38
+ if (msg.status === 'started') {
39
+ console.log(
40
+ ` [Hook Started] ID: ${msg.hookId}, Event: ${msg.eventName}, Command: ${msg.command}`
41
+ );
42
+ } else {
43
+ console.log(
44
+ ` [Hook ${msg.status}] ID: ${msg.hookId}, Exit Code: ${msg.exitCode}`
45
+ );
46
+ if (msg.stdout) console.log(` stdout: ${msg.stdout.trim()}`);
47
+ if (msg.stderr) console.log(` stderr: ${msg.stderr.trim()}`);
48
+ }
49
+ break;
50
+
51
+ case DroidMessageType.ToolResult:
52
+ console.log(`[Tool Result] ${msg.isError ? 'Error' : 'OK'}`);
53
+ break;
54
+
55
+ case DroidMessageType.Result:
56
+ console.log('\n\n--- Turn complete ---');
57
+ break;
58
+ }
59
+ }
60
+ } finally {
61
+ await session.close();
62
+ }
63
+ }
64
+
65
+ main().catch((err: unknown) => {
66
+ console.error('Error:', err);
67
+ process.exit(1);
68
+ });
@@ -1,76 +1,24 @@
1
1
  /**
2
2
  * Initialization metadata example.
3
3
  *
4
- * Demonstrates:
5
- * - `query().sessionId`, `query().initResult`, and `query().initialized`
6
- * - `session.initResult` from `createSession()`
7
- * - `resumed.initResult` from `resumeSession()`
8
- *
9
4
  * Usage:
10
5
  * npx tsx examples/init-metadata.ts
11
6
  */
12
7
 
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`);
8
+ import { createSession, resumeSession } from '@factory/droid-sdk';
44
9
 
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
- });
10
+ const session = await createSession({
11
+ apiKey: process.env.FACTORY_API_KEY!,
12
+ cwd: process.cwd(),
13
+ });
14
+ const resumed = await resumeSession(session.sessionId, {
15
+ apiKey: process.env.FACTORY_API_KEY!,
16
+ });
58
17
 
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
- }
18
+ console.log(`created session: ${session.sessionId}`);
19
+ console.log(`resumed session: ${resumed.sessionId}`);
20
+ console.log(`created model: ${String(session.initResult.settings.modelId)}`);
21
+ console.log(`resumed cwd: ${String(resumed.initResult.cwd)}`);
72
22
 
73
- main().catch((err: unknown) => {
74
- console.error('Error:', err);
75
- process.exit(1);
76
- });
23
+ await resumed.close();
24
+ await session.close();
@@ -1,67 +1,35 @@
1
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`.
2
+ * Interrupt a running turn.
8
3
  *
9
4
  * Usage:
10
5
  * npx tsx examples/interrupt-session.ts
11
6
  */
12
7
 
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`);
8
+ import { createSession, DroidMessageType } from '@factory/droid-sdk';
24
9
 
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);
10
+ const session = await createSession({
11
+ apiKey: process.env.FACTORY_API_KEY!,
12
+ cwd: process.cwd(),
13
+ });
14
+ let deltaCount = 0;
15
+
16
+ try {
17
+ for await (const msg of session.stream('Write a long history of computing.', {
18
+ includePartialMessages: true,
19
+ })) {
20
+ if (msg.type !== DroidMessageType.AssistantTextDelta) {
21
+ continue;
22
+ }
33
23
 
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;
24
+ process.stdout.write(msg.text);
25
+ deltaCount++;
40
26
 
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
- }
27
+ if (deltaCount === 5) {
28
+ await session.interrupt();
57
29
  }
58
- } finally {
59
- await session.close();
60
- console.log('\nSession closed.');
61
30
  }
62
- }
63
31
 
64
- main().catch((err: unknown) => {
65
- console.error('Error:', err);
66
- process.exit(1);
67
- });
32
+ console.log('\nInterrupted turn.');
33
+ } finally {
34
+ await session.close();
35
+ }
@@ -1,65 +1,19 @@
1
1
  /**
2
- * List sessions example.
3
- *
4
- * Demonstrates:
5
- * - scanning ~/.factory/sessions/ for saved droid sessions
6
- * - filtering by current working directory
7
- * - including sessions from other projects with a cap
8
- *
9
- * This helper reads the local filesystem directly — no droid process is
10
- * spawned — so it's safe to call even when no droid is running.
2
+ * List saved sessions example.
11
3
  *
12
4
  * Usage:
13
5
  * npx tsx examples/list-sessions.ts
14
6
  */
15
7
 
16
- import { listSessions } from '../src/index.js';
17
-
18
- function formatDate(date: Date): string {
19
- return date.toISOString().replace('T', ' ').slice(0, 19);
20
- }
21
-
22
- async function main(): Promise<void> {
23
- console.log(`=== Sessions for ${process.cwd()} ===\n`);
8
+ import { listSessions } from '@factory/droid-sdk';
24
9
 
25
- // Default call: scopes to process.cwd().
26
- const currentProject = await listSessions({ numSessions: 10 });
10
+ const sessions = await listSessions({ numSessions: 5 });
27
11
 
28
- if (currentProject.length === 0) {
29
- console.log('(no sessions recorded for this directory yet)');
30
- } else {
31
- console.log(`10 most recent for this project:\n`);
32
- for (const session of currentProject) {
33
- const fav = session.isFavorite ? '*' : ' ';
34
- const title = session.sessionTitle ?? session.title ?? '(untitled)';
35
- console.log(
36
- ` ${fav} [${session.id.slice(0, 8)}] ${formatDate(session.modifiedTime)} ${session.messageCount} msgs — ${title}`
37
- );
38
- }
39
- }
40
-
41
- console.log('\n=== 5 most recent sessions across all projects ===\n');
42
-
43
- const allSessions = await listSessions({
44
- fetchOutsideCWD: true,
45
- numSessions: 5,
46
- });
47
-
48
- if (allSessions.length === 0) {
49
- console.log('(no sessions found)');
50
- } else {
51
- for (const session of allSessions) {
52
- const title = session.sessionTitle ?? session.title ?? '(untitled)';
53
- const cwd = session.cwd ?? '(no cwd)';
54
- console.log(
55
- ` [${session.id.slice(0, 8)}] ${formatDate(session.modifiedTime)} — ${title}`
56
- );
57
- console.log(` cwd: ${cwd}`);
58
- }
59
- }
12
+ for (const session of sessions) {
13
+ const title = session.sessionTitle ?? session.title ?? '(untitled)';
14
+ console.log(`${session.id}: ${title}`);
60
15
  }
61
16
 
62
- main().catch((err: unknown) => {
63
- console.error('Error:', err);
64
- process.exit(1);
65
- });
17
+ if (sessions.length === 0) {
18
+ console.log('No sessions found for this project.');
19
+ }
@@ -1,67 +1,40 @@
1
1
  /**
2
2
  * Multi-turn session example.
3
3
  *
4
- * Demonstrates `createSession()` for a persistent session, multiple
5
- * `stream()` calls for streaming turns, `send()` for a non-streaming
6
- * turn, and `close()` for cleanup.
7
- *
8
4
  * Usage:
9
5
  * npx tsx examples/multi-turn-session.ts
10
6
  */
11
7
 
12
- import { createSession, DroidMessageType } from '../src/index.js';
13
-
14
- async function main(): Promise<void> {
15
- const session = await createSession({ cwd: process.cwd() });
16
- try {
17
- console.log(`Session created: ${session.sessionId}\n`);
18
-
19
- console.log('=== Turn 1 (streaming) ===');
20
- console.log('Prompt: "List the TypeScript files in this project"\n');
21
-
22
- for await (const msg of session.stream(
23
- 'List the TypeScript files in this project'
24
- )) {
25
- if (msg.type === DroidMessageType.AssistantTextDelta) {
26
- process.stdout.write(msg.text);
27
- }
28
- if (msg.type === DroidMessageType.TurnComplete) {
29
- console.log('\n');
30
- }
8
+ import { DroidMessageType, createSession } from '@factory/droid-sdk';
9
+
10
+ async function streamText(
11
+ session: Awaited<ReturnType<typeof createSession>>,
12
+ prompt: string
13
+ ): Promise<string> {
14
+ let text = '';
15
+ for await (const msg of session.stream(prompt, {
16
+ includePartialMessages: true,
17
+ })) {
18
+ if (msg.type === DroidMessageType.AssistantTextDelta) {
19
+ text += msg.text;
31
20
  }
21
+ }
22
+ return text;
23
+ }
32
24
 
33
- console.log('=== Turn 2 (streaming) ===');
34
- console.log('Prompt: "How many lines of code total?"\n');
35
-
36
- for await (const msg of session.stream('How many lines of code total?')) {
37
- if (msg.type === DroidMessageType.AssistantTextDelta) {
38
- process.stdout.write(msg.text);
39
- }
40
- if (msg.type === DroidMessageType.TurnComplete) {
41
- console.log('\n');
42
- }
43
- }
25
+ const session = await createSession({
26
+ apiKey: process.env.FACTORY_API_KEY!,
27
+ cwd: process.cwd(),
28
+ });
44
29
 
45
- console.log('=== Turn 3 (non-streaming) ===');
46
- console.log('Prompt: "Summarize the project in one sentence"\n');
30
+ try {
31
+ console.log(`Session: ${session.sessionId}\n`);
47
32
 
48
- const result = await session.send('Summarize the project in one sentence');
33
+ const first = await streamText(session, 'What is this project?');
34
+ console.log(first);
49
35
 
50
- console.log('Response:', result.text);
51
- console.log(`Messages received: ${result.messages.length}`);
52
- if (result.tokenUsage) {
53
- console.log(
54
- `Tokens — input: ${result.tokenUsage.inputTokens}, ` +
55
- `output: ${result.tokenUsage.outputTokens}`
56
- );
57
- }
58
- } finally {
59
- await session.close();
60
- console.log('\nSession closed.');
61
- }
36
+ const second = await streamText(session, 'What should I test first?');
37
+ console.log('\n', second);
38
+ } finally {
39
+ await session.close();
62
40
  }
63
-
64
- main().catch((err: unknown) => {
65
- console.error('Error:', err);
66
- process.exit(1);
67
- });