@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.
- package/README.md +214 -82
- package/dist/index.cjs +2345 -692
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2570 -232
- package/dist/index.d.ts +2570 -232
- package/dist/index.js +2298 -697
- package/dist/index.js.map +1 -1
- package/docs/daemon-usage-guide.md +544 -0
- package/docs/sdk-usage-guide.md +748 -0
- package/examples/abort-session-stream.ts +23 -56
- package/examples/daemon-multi-session.ts +67 -0
- package/examples/droid-dev-structured-output.ts +5 -5
- package/examples/fork-session.ts +33 -7
- package/examples/hook-execution.ts +68 -0
- package/examples/init-metadata.ts +14 -66
- package/examples/interrupt-session.ts +23 -55
- package/examples/list-sessions.ts +9 -55
- package/examples/multi-turn-session.ts +27 -54
- package/examples/permission-handler.ts +27 -84
- package/examples/readme-structured-output.ts +26 -0
- package/examples/result-metadata.ts +2 -1
- package/examples/run.ts +2 -1
- package/examples/sdk-mcp-tool.ts +14 -8
- package/examples/session-stream.ts +57 -0
- package/examples/spec-mode-new-session.ts +39 -99
- package/examples/spec-mode-same-session.ts +50 -111
- package/examples/structured-output.ts +17 -10
- package/examples/test-compact.ts +9 -4
- package/examples/test-rewind.ts +49 -117
- package/examples/tool-controls.ts +23 -55
- package/package.json +3 -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,67 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
}
|
package/examples/fork-session.ts
CHANGED
|
@@ -10,25 +10,51 @@
|
|
|
10
10
|
* npx tsx examples/fork-session.ts
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
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({
|
|
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, {
|
|
48
|
+
fork = await resumeSession(newSessionId, {
|
|
49
|
+
apiKey: process.env.FACTORY_API_KEY!,
|
|
50
|
+
});
|
|
28
51
|
|
|
29
|
-
const result = await
|
|
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
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
process.exit(1);
|
|
76
|
-
});
|
|
23
|
+
await resumed.close();
|
|
24
|
+
await session.close();
|
|
@@ -1,67 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Interrupt
|
|
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 '
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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 '
|
|
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
|
-
|
|
26
|
-
const currentProject = await listSessions({ numSessions: 10 });
|
|
10
|
+
const sessions = await listSessions({ numSessions: 5 });
|
|
27
11
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
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
|
-
|
|
63
|
-
console.
|
|
64
|
-
|
|
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 {
|
|
13
|
-
|
|
14
|
-
async function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
46
|
-
|
|
30
|
+
try {
|
|
31
|
+
console.log(`Session: ${session.sessionId}\n`);
|
|
47
32
|
|
|
48
|
-
|
|
33
|
+
const first = await streamText(session, 'What is this project?');
|
|
34
|
+
console.log(first);
|
|
49
35
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
});
|