@factory/droid-sdk 0.6.0 → 0.8.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 +35 -482
- package/dist/chunk-4G2RKM2H.js +25831 -0
- package/dist/chunk-4G2RKM2H.js.map +1 -0
- package/dist/chunk-ROFAQUG4.mjs +25831 -0
- package/dist/chunk-ROFAQUG4.mjs.map +1 -0
- package/dist/{index-CwXX5IUH.d.cts → index-_nUPGHpH.d.mts} +92630 -79659
- package/dist/{index-CwXX5IUH.d.ts → index-_nUPGHpH.d.ts} +92630 -79659
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -93103
- package/dist/index.js +829 -11745
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +830 -0
- package/dist/index.mjs.map +1 -0
- package/dist/node.d.mts +757 -0
- package/dist/node.d.ts +757 -0
- package/dist/node.js +4731 -0
- package/dist/node.js.map +1 -0
- package/dist/node.mjs +4731 -0
- package/dist/node.mjs.map +1 -0
- package/docs/typescript-sdk-reference.md +1437 -0
- package/examples/browser/daemon-lifecycle.ts +95 -0
- package/examples/browser/daemon-model-discovery.ts +47 -0
- package/examples/browser/daemon-multi-session.ts +66 -0
- package/examples/browser/daemon-session.ts +73 -0
- package/examples/browser/index.html +69 -0
- package/examples/browser/list-sessions.ts +50 -0
- package/examples/node/abort-session-stream.ts +34 -0
- package/examples/node/ask-user-elicitation.ts +39 -0
- package/examples/node/compact-session.ts +63 -0
- package/examples/node/custom-system-prompt.ts +35 -0
- package/examples/node/document-input.ts +58 -0
- package/examples/node/enter-spec-mode.ts +87 -0
- package/examples/node/external-mcp.ts +45 -0
- package/examples/node/fork-session.ts +56 -0
- package/examples/node/hook-execution.ts +94 -0
- package/examples/node/image-attachment.ts +41 -0
- package/examples/node/init-metadata.ts +24 -0
- package/examples/node/interrupt-session.ts +55 -0
- package/examples/node/list-sessions.ts +20 -0
- package/examples/node/model-discovery.ts +28 -0
- package/examples/node/model-selection.ts +35 -0
- package/examples/node/multi-turn-session.ts +37 -0
- package/examples/node/observability.ts +75 -0
- package/examples/node/partial-stream.ts +56 -0
- package/examples/node/permission-handler.ts +60 -0
- package/examples/node/rename-session.ts +33 -0
- package/examples/node/result-metadata.ts +43 -0
- package/examples/node/resume-session.ts +41 -0
- package/examples/node/rewind-session.ts +79 -0
- package/examples/node/run.ts +32 -0
- package/examples/node/sdk-mcp-tool.ts +56 -0
- package/examples/node/session-settings.ts +35 -0
- package/examples/node/session-stream.ts +59 -0
- package/examples/node/skills.ts +24 -0
- package/examples/node/spec-mode-new-session.ts +120 -0
- package/examples/node/spec-mode-same-session.ts +119 -0
- package/examples/node/structured-output.ts +96 -0
- package/examples/node/tool-controls.ts +26 -0
- package/examples/package.json +3 -0
- package/package.json +20 -42
- package/dist/index.cjs +0 -12135
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -93103
- package/dist/protocol/index.cjs +0 -6466
- package/dist/protocol/index.cjs.map +0 -1
- package/dist/protocol/index.d.cts +0 -2
- package/dist/protocol/index.d.ts +0 -2
- package/dist/protocol/index.js +0 -6078
- package/dist/protocol/index.js.map +0 -1
- package/docs/daemon-usage-guide.md +0 -544
- package/docs/sdk-usage-guide.md +0 -748
- package/examples/abort-session-stream.ts +0 -34
- package/examples/daemon-multi-session.ts +0 -67
- package/examples/droid-dev-structured-output.ts +0 -44
- package/examples/fork-session.ts +0 -68
- package/examples/hook-execution.ts +0 -68
- package/examples/init-metadata.ts +0 -24
- package/examples/interrupt-session.ts +0 -35
- package/examples/list-sessions.ts +0 -19
- package/examples/multi-turn-session.ts +0 -40
- package/examples/permission-handler.ts +0 -41
- package/examples/readme-structured-output.ts +0 -26
- package/examples/result-metadata.ts +0 -63
- package/examples/run.ts +0 -39
- package/examples/sdk-mcp-tool.ts +0 -44
- package/examples/session-stream.ts +0 -57
- package/examples/spec-mode-new-session.ts +0 -61
- package/examples/spec-mode-same-session.ts +0 -72
- package/examples/structured-output.ts +0 -60
- package/examples/test-compact.ts +0 -65
- package/examples/test-rewind.ts +0 -87
- package/examples/tool-controls.ts +0 -37
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto to Spec to implementation example.
|
|
3
|
+
*
|
|
4
|
+
* Switches an Auto session to Spec mode, approves the plan, then allows the
|
|
5
|
+
* planned file creation in the same session.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx tsx examples/node/enter-spec-mode.ts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
12
|
+
import { tmpdir } from 'node:os';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
type ClientPermissionHandler,
|
|
17
|
+
DroidInteractionMode,
|
|
18
|
+
DroidMessageType,
|
|
19
|
+
ToolConfirmationOutcome,
|
|
20
|
+
ToolConfirmationType,
|
|
21
|
+
createSession,
|
|
22
|
+
} from '@factory/droid-sdk/node';
|
|
23
|
+
|
|
24
|
+
const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-enter-spec-'));
|
|
25
|
+
const outputPath = join(tempDir, 'hello.txt');
|
|
26
|
+
|
|
27
|
+
const handlePermissionRequest: ClientPermissionHandler = ({
|
|
28
|
+
toolUses,
|
|
29
|
+
options,
|
|
30
|
+
}) => {
|
|
31
|
+
const exitsSpecMode =
|
|
32
|
+
toolUses.length > 0 &&
|
|
33
|
+
toolUses.every(
|
|
34
|
+
(toolUse) => toolUse.details.type === ToolConfirmationType.ExitSpecMode
|
|
35
|
+
);
|
|
36
|
+
const createsExpectedFile =
|
|
37
|
+
toolUses.length > 0 &&
|
|
38
|
+
toolUses.every(
|
|
39
|
+
(toolUse) =>
|
|
40
|
+
toolUse.details.type === ToolConfirmationType.Create &&
|
|
41
|
+
toolUse.details.filePath === outputPath
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
if (exitsSpecMode) {
|
|
45
|
+
for (const toolUse of toolUses) {
|
|
46
|
+
if (toolUse.details.type === ToolConfirmationType.ExitSpecMode) {
|
|
47
|
+
console.log(toolUse.details.plan);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const outcome =
|
|
53
|
+
exitsSpecMode || createsExpectedFile
|
|
54
|
+
? ToolConfirmationOutcome.ProceedOnce
|
|
55
|
+
: ToolConfirmationOutcome.Cancel;
|
|
56
|
+
if (!options.some((option) => option.value === outcome)) {
|
|
57
|
+
throw new Error(`Outcome not offered: ${outcome}`);
|
|
58
|
+
}
|
|
59
|
+
return outcome;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const session = await createSession({
|
|
64
|
+
interactionMode: DroidInteractionMode.Auto,
|
|
65
|
+
permissionHandler: handlePermissionRequest,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await session.enterSpecMode();
|
|
70
|
+
|
|
71
|
+
for await (const message of session.stream(
|
|
72
|
+
`Create ${outputPath} containing "hello".`
|
|
73
|
+
)) {
|
|
74
|
+
if (message.type === DroidMessageType.Result && !message.success) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
message.error?.message ?? `Turn ended: ${message.subtype}`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} finally {
|
|
81
|
+
await session.close();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
console.log(await readFile(outputPath, 'utf8'));
|
|
85
|
+
} finally {
|
|
86
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
87
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External MCP server example.
|
|
3
|
+
*
|
|
4
|
+
* Connects an HTTP MCP server to one Node session, then lists its status and
|
|
5
|
+
* discovered tools.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* MCP_SERVER_URL=https://example.com/mcp \
|
|
9
|
+
* npx tsx examples/node/external-mcp.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createSession } from '@factory/droid-sdk/node';
|
|
13
|
+
|
|
14
|
+
const url = process.env.MCP_SERVER_URL;
|
|
15
|
+
if (!url) {
|
|
16
|
+
throw new Error('Set MCP_SERVER_URL to an HTTP MCP endpoint.');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const token = process.env.MCP_SERVER_TOKEN;
|
|
20
|
+
const session = await createSession({
|
|
21
|
+
mcpServers: [
|
|
22
|
+
{
|
|
23
|
+
name: 'external',
|
|
24
|
+
type: 'http',
|
|
25
|
+
url,
|
|
26
|
+
headers: token
|
|
27
|
+
? [{ name: 'Authorization', value: `Bearer ${token}` }]
|
|
28
|
+
: [],
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const { servers } = await session.listMcpServers();
|
|
35
|
+
const tools = await session.listMcpTools();
|
|
36
|
+
const server = servers.find((item) => item.name === 'external');
|
|
37
|
+
|
|
38
|
+
console.log(`status: ${server?.status ?? 'unknown'}`);
|
|
39
|
+
|
|
40
|
+
for (const tool of tools.filter((item) => item.serverName === 'external')) {
|
|
41
|
+
console.log(tool.name);
|
|
42
|
+
}
|
|
43
|
+
} finally {
|
|
44
|
+
await session.close();
|
|
45
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fork session example.
|
|
3
|
+
*
|
|
4
|
+
* Establishes context in one session, forks it with `fork()`, then uses the
|
|
5
|
+
* returned fork handle to show the copied context.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx tsx examples/node/fork-session.ts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { DroidMessageType, createSession } from '@factory/droid-sdk/node';
|
|
12
|
+
|
|
13
|
+
async function streamText(
|
|
14
|
+
session: Awaited<ReturnType<typeof createSession>>,
|
|
15
|
+
prompt: string
|
|
16
|
+
): Promise<string> {
|
|
17
|
+
let text = '';
|
|
18
|
+
for await (const msg of session.stream(prompt)) {
|
|
19
|
+
if (msg.type === DroidMessageType.Assistant) {
|
|
20
|
+
text += msg.text;
|
|
21
|
+
}
|
|
22
|
+
if (msg.type === DroidMessageType.Result && !msg.success) {
|
|
23
|
+
throw new Error(msg.error?.message ?? `Turn ended: ${msg.subtype}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (!text.trim()) {
|
|
27
|
+
throw new Error('The turn produced no assistant output.');
|
|
28
|
+
}
|
|
29
|
+
return text;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const session = await createSession();
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await streamText(session, 'Remember this phrase exactly: mango sunrise');
|
|
36
|
+
|
|
37
|
+
const fork = await session.fork();
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// `session` is now a retired wrapper. Its persisted ID can still be passed
|
|
41
|
+
// to resumeSession() later if the original branch is needed again.
|
|
42
|
+
console.log(`original session: ${session.id}`);
|
|
43
|
+
console.log(`forked session: ${fork.id}`);
|
|
44
|
+
|
|
45
|
+
const recalled = await streamText(
|
|
46
|
+
fork,
|
|
47
|
+
'What phrase did I ask you to remember?'
|
|
48
|
+
);
|
|
49
|
+
console.log(`fork recalls: ${recalled}`);
|
|
50
|
+
} finally {
|
|
51
|
+
await fork.close();
|
|
52
|
+
}
|
|
53
|
+
} finally {
|
|
54
|
+
// Closes the active source, or does nothing if it was retired.
|
|
55
|
+
await session.close();
|
|
56
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook execution example.
|
|
3
|
+
*
|
|
4
|
+
* Creates a temporary project with a PreToolUse hook and prints its stream
|
|
5
|
+
* events.
|
|
6
|
+
* User-level hooks still apply to the temporary project.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx tsx examples/node/hook-execution.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
13
|
+
import { tmpdir } from 'node:os';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
DroidMessageType,
|
|
18
|
+
ToolConfirmationOutcome,
|
|
19
|
+
ToolConfirmationType,
|
|
20
|
+
createSession,
|
|
21
|
+
} from '@factory/droid-sdk/node';
|
|
22
|
+
|
|
23
|
+
const hookSettings = {
|
|
24
|
+
hooksDisabled: false,
|
|
25
|
+
PreToolUse: [
|
|
26
|
+
{
|
|
27
|
+
matcher: 'Execute',
|
|
28
|
+
hooks: [{ type: 'command', command: 'echo hook-from-sdk-example' }],
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const projectDir = await mkdtemp(join(tmpdir(), 'droid-sdk-hook-project-'));
|
|
34
|
+
|
|
35
|
+
let hookCount = 0;
|
|
36
|
+
const expectedCommand = 'echo hello';
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
await mkdir(join(projectDir, '.factory'), { recursive: true });
|
|
40
|
+
await writeFile(
|
|
41
|
+
join(projectDir, '.factory', 'hooks.json'),
|
|
42
|
+
JSON.stringify(hookSettings, null, 2)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const session = await createSession({
|
|
46
|
+
cwd: projectDir,
|
|
47
|
+
permissionHandler({ toolUses, options }) {
|
|
48
|
+
const approvesExpectedCommand =
|
|
49
|
+
toolUses.length === 1 &&
|
|
50
|
+
toolUses[0].details.type === ToolConfirmationType.Execute &&
|
|
51
|
+
toolUses[0].details.fullCommand === expectedCommand;
|
|
52
|
+
const desired = approvesExpectedCommand
|
|
53
|
+
? ToolConfirmationOutcome.ProceedOnce
|
|
54
|
+
: ToolConfirmationOutcome.Cancel;
|
|
55
|
+
|
|
56
|
+
if (!options.some((option) => option.value === desired)) {
|
|
57
|
+
throw new Error(`Outcome not offered: ${desired}`);
|
|
58
|
+
}
|
|
59
|
+
return desired;
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
for await (const message of session.stream(
|
|
65
|
+
`Run the shell command \`${expectedCommand}\` using the Execute tool.`
|
|
66
|
+
)) {
|
|
67
|
+
if (message.type === DroidMessageType.Result && !message.success) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
message.error?.message ?? `Turn ended: ${message.subtype}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (message.type === DroidMessageType.Hook) {
|
|
73
|
+
hookCount++;
|
|
74
|
+
if (message.status === 'started') {
|
|
75
|
+
console.log(
|
|
76
|
+
`[hook started] ${message.eventName}: ${message.command}`
|
|
77
|
+
);
|
|
78
|
+
} else {
|
|
79
|
+
console.log(
|
|
80
|
+
`[hook ${message.status}] exit code ${String(message.exitCode)}: ${(message.stdout ?? '').trim()}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} finally {
|
|
86
|
+
await session.close();
|
|
87
|
+
}
|
|
88
|
+
} finally {
|
|
89
|
+
await rm(projectDir, { recursive: true, force: true });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (hookCount === 0) {
|
|
93
|
+
throw new Error('The PreToolUse hook did not run.');
|
|
94
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image attachment example.
|
|
3
|
+
*
|
|
4
|
+
* Attaches a base64 image to a prompt via `images`, so the model can see it.
|
|
5
|
+
* `run()` and `session.stream()` both accept the same `images` option.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx tsx examples/node/image-attachment.ts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { run } from '@factory/droid-sdk/node';
|
|
12
|
+
|
|
13
|
+
/** A 120x120 PNG: one purple square centered on white. */
|
|
14
|
+
const PURPLE_SQUARE_PNG =
|
|
15
|
+
'iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAIAAAC2BqGFAAAAzElEQVR42u3QAQ0A' +
|
|
16
|
+
'MAgEsZeOvLkCFyQLvZyCprVSEIAGLdCgQQs0aIEGDVqgQQs0aNACDVqgQYMWaNAC' +
|
|
17
|
+
'DRq0QIMW6GPQlffdoEGDBg0aNGjQoEGDBg0aNGjQoEGDBg0aNGjQoEGDBg0aNGjQ' +
|
|
18
|
+
'oEGDBg0aNGjQoEGDBg0aNGjQoEGDBg0aNGjQoEGDBg0aNGjQoEGDBg0aNGjQoEGD' +
|
|
19
|
+
'Bg0aNGjQoEGDFmjQAg0atECDFmjQoAUatECDBi3QoAUaNGiBBi3QoEELNGiBBn2g' +
|
|
20
|
+
'AU2hHmFuN1AZAAAAAElFTkSuQmCC';
|
|
21
|
+
|
|
22
|
+
const result = await run(
|
|
23
|
+
'What single shape and color is in this image? Answer in five words or fewer.',
|
|
24
|
+
{
|
|
25
|
+
images: [
|
|
26
|
+
{
|
|
27
|
+
type: 'base64',
|
|
28
|
+
data: PURPLE_SQUARE_PNG,
|
|
29
|
+
mediaType: 'image/png',
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (!result.success) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
result.error?.message ?? `Image description failed (${result.subtype}).`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.log(result.text);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Initialization metadata example.
|
|
3
|
+
*
|
|
4
|
+
* Reads direct metadata from a freshly created session: session ID, working
|
|
5
|
+
* directory, model, and settings.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx tsx examples/node/init-metadata.ts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createSession } from '@factory/droid-sdk/node';
|
|
12
|
+
|
|
13
|
+
const session = await createSession();
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const { settings } = session;
|
|
17
|
+
console.log(`session: ${session.id}`);
|
|
18
|
+
console.log(`cwd: ${String(session.cwd)}`);
|
|
19
|
+
console.log(`model: ${String(settings.modelId)}`);
|
|
20
|
+
console.log(`interaction mode: ${String(settings.interactionMode)}`);
|
|
21
|
+
console.log(`autonomy level: ${String(settings.autonomyLevel)}`);
|
|
22
|
+
} finally {
|
|
23
|
+
await session.close();
|
|
24
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interrupt a running turn.
|
|
3
|
+
*
|
|
4
|
+
* Calls `session.interrupt()` partway through streaming. Unlike
|
|
5
|
+
* abort-session-stream.ts, this stops the turn server-side and leaves the
|
|
6
|
+
* session usable for another prompt.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx tsx examples/node/interrupt-session.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
type DroidResultMessage,
|
|
14
|
+
createSession,
|
|
15
|
+
DroidMessageType,
|
|
16
|
+
} from '@factory/droid-sdk/node';
|
|
17
|
+
|
|
18
|
+
const session = await createSession();
|
|
19
|
+
let deltaCount = 0;
|
|
20
|
+
let result: DroidResultMessage | undefined;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
for await (const msg of session.stream('Write a long history of computing.', {
|
|
24
|
+
includePartialMessages: true,
|
|
25
|
+
})) {
|
|
26
|
+
if (msg.type === DroidMessageType.Result) {
|
|
27
|
+
result = msg;
|
|
28
|
+
if (!msg.success && !msg.interrupted) {
|
|
29
|
+
throw new Error(msg.error?.message ?? `Turn ended: ${msg.subtype}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (msg.type === DroidMessageType.AssistantTextDelta) {
|
|
34
|
+
process.stdout.write(msg.text);
|
|
35
|
+
deltaCount++;
|
|
36
|
+
|
|
37
|
+
if (deltaCount === 5) {
|
|
38
|
+
await session.interrupt();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!result) {
|
|
44
|
+
throw new Error('The stream ended without a result.');
|
|
45
|
+
}
|
|
46
|
+
if (result.interrupted) {
|
|
47
|
+
console.log('\nTurn was interrupted.');
|
|
48
|
+
} else if (deltaCount >= 5) {
|
|
49
|
+
console.log('\nThe turn completed before the interrupt took effect.');
|
|
50
|
+
} else {
|
|
51
|
+
console.log('\nThe turn completed before an interrupt was requested.');
|
|
52
|
+
}
|
|
53
|
+
} finally {
|
|
54
|
+
await session.close();
|
|
55
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List saved sessions example.
|
|
3
|
+
*
|
|
4
|
+
* Reads the droid sessions stored on disk for the current project.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx tsx examples/node/list-sessions.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { listSessions } from '@factory/droid-sdk/node';
|
|
11
|
+
|
|
12
|
+
const sessions = await listSessions({ limit: 5 });
|
|
13
|
+
|
|
14
|
+
if (sessions.length === 0) {
|
|
15
|
+
console.log('No sessions found for this project.');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
for (const session of sessions) {
|
|
19
|
+
console.log(`${session.id}: ${session.title || '(untitled)'}`);
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node model discovery example.
|
|
3
|
+
*
|
|
4
|
+
* Lists the models available to the current account and organization.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* FACTORY_API_KEY=... npx tsx examples/node/model-discovery.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { listModels } from '@factory/droid-sdk/node';
|
|
11
|
+
|
|
12
|
+
const apiKey = process.env.FACTORY_API_KEY;
|
|
13
|
+
if (!apiKey) {
|
|
14
|
+
throw new Error('Set FACTORY_API_KEY.');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const models = await listModels({ apiKey, includeDisabled: true });
|
|
18
|
+
|
|
19
|
+
for (const model of models) {
|
|
20
|
+
const status = model.disabled
|
|
21
|
+
? `disabled: ${model.disabledReason}`
|
|
22
|
+
: 'available';
|
|
23
|
+
console.log(
|
|
24
|
+
`${model.displayName} (${model.id})\n` +
|
|
25
|
+
` status: ${status}\n` +
|
|
26
|
+
` reasoning: ${model.supportedReasoningEfforts.join(', ')}`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model selection example.
|
|
3
|
+
*
|
|
4
|
+
* Runs one turn with a configured model ID.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* FACTORY_API_KEY=... npx tsx examples/node/model-selection.ts model-id
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createSession, DroidMessageType } from '@factory/droid-sdk/node';
|
|
11
|
+
|
|
12
|
+
const modelId = process.argv[2];
|
|
13
|
+
if (!modelId) {
|
|
14
|
+
throw new Error('Pass a configured model ID.');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const session = await createSession({
|
|
18
|
+
cwd: process.cwd(),
|
|
19
|
+
modelId,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
for await (const message of session.stream('Summarize this repository.')) {
|
|
24
|
+
if (message.type === DroidMessageType.Assistant) {
|
|
25
|
+
console.log(message.text);
|
|
26
|
+
}
|
|
27
|
+
if (message.type === DroidMessageType.Result && !message.success) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
message.error?.message ?? `Turn ended: ${message.subtype}`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} finally {
|
|
34
|
+
await session.close();
|
|
35
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-turn session example.
|
|
3
|
+
*
|
|
4
|
+
* Sends two prompts over one session so the second turn can build on the
|
|
5
|
+
* context established by the first.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx tsx examples/node/multi-turn-session.ts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { DroidMessageType, createSession } from '@factory/droid-sdk/node';
|
|
12
|
+
|
|
13
|
+
async function streamText(
|
|
14
|
+
session: Awaited<ReturnType<typeof createSession>>,
|
|
15
|
+
prompt: string
|
|
16
|
+
): Promise<string> {
|
|
17
|
+
let text = '';
|
|
18
|
+
for await (const msg of session.stream(prompt)) {
|
|
19
|
+
if (msg.type === DroidMessageType.Assistant) {
|
|
20
|
+
text += msg.text;
|
|
21
|
+
}
|
|
22
|
+
if (msg.type === DroidMessageType.Result && !msg.success) {
|
|
23
|
+
throw new Error(msg.error?.message ?? `Turn ended: ${msg.subtype}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (!text) throw new Error('The turn produced no assistant text.');
|
|
27
|
+
return text;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const session = await createSession();
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
console.log(await streamText(session, 'What is this project?'));
|
|
34
|
+
console.log(`\n${await streamText(session, 'What should I test first?')}`);
|
|
35
|
+
} finally {
|
|
36
|
+
await session.close();
|
|
37
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability example.
|
|
3
|
+
*
|
|
4
|
+
* Passes a `DroidObservability` bundle (logger, metrics, tracing) to
|
|
5
|
+
* `createSession()`, then runs one turn so the sinks observe real traffic.
|
|
6
|
+
*
|
|
7
|
+
* Sinks only ever receive structured, privacy-safe values — never raw
|
|
8
|
+
* stdout/stderr or message content.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* npx tsx examples/node/observability.ts
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
DroidMessageType,
|
|
16
|
+
createSession,
|
|
17
|
+
type DroidLogEvent,
|
|
18
|
+
type DroidMetricEvent,
|
|
19
|
+
type DroidObservability,
|
|
20
|
+
type DroidTraceContextCarrier,
|
|
21
|
+
} from '@factory/droid-sdk/node';
|
|
22
|
+
|
|
23
|
+
let logCount = 0;
|
|
24
|
+
|
|
25
|
+
const observability: DroidObservability = {
|
|
26
|
+
logger: {
|
|
27
|
+
log(event: DroidLogEvent): void {
|
|
28
|
+
logCount++;
|
|
29
|
+
console.log(
|
|
30
|
+
`[log:${event.level}] ${event.name} — ${event.message}`,
|
|
31
|
+
event.attributes ?? {}
|
|
32
|
+
);
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
metrics: {
|
|
36
|
+
record(event: DroidMetricEvent): void {
|
|
37
|
+
console.log(
|
|
38
|
+
`[metric] ${event.name}=${event.value}${event.unit} (${event.kind})`,
|
|
39
|
+
event.attributes ?? {}
|
|
40
|
+
);
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
tracing: {
|
|
44
|
+
inject(carrier: DroidTraceContextCarrier): void {
|
|
45
|
+
// Populate the carrier from your tracer's active span. The SDK copies
|
|
46
|
+
// traceparent/tracestate into each request's `_meta`.
|
|
47
|
+
carrier.traceparent =
|
|
48
|
+
'00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01';
|
|
49
|
+
carrier.tracestate = 'factory=1';
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const session = await createSession({ observability });
|
|
55
|
+
|
|
56
|
+
const logCountBeforeTurn = logCount;
|
|
57
|
+
let turnLogCount = 0;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
for await (const msg of session.stream(
|
|
61
|
+
'Reply with the single word: ready.'
|
|
62
|
+
)) {
|
|
63
|
+
if (msg.type === DroidMessageType.Result && !msg.success) {
|
|
64
|
+
throw new Error(msg.error?.message ?? `Turn ended: ${msg.subtype}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Measure before close(), which emits another request log.
|
|
68
|
+
turnLogCount = logCount - logCountBeforeTurn;
|
|
69
|
+
} finally {
|
|
70
|
+
await session.close();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (turnLogCount === 0) {
|
|
74
|
+
throw new Error('Observability sinks saw no logged requests for the turn.');
|
|
75
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Partial stream example.
|
|
3
|
+
*
|
|
4
|
+
* Prints assistant text as it arrives, tool progress, and cumulative session
|
|
5
|
+
* token updates.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx tsx examples/node/partial-stream.ts
|
|
9
|
+
* npx tsx examples/node/partial-stream.ts "Explain this repository."
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { DroidMessageType, createSession } from '@factory/droid-sdk/node';
|
|
13
|
+
|
|
14
|
+
const session = await createSession();
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const prompt =
|
|
18
|
+
process.argv.slice(2).join(' ') || 'Explain this repository briefly.';
|
|
19
|
+
let sawSuccessfulResult = false;
|
|
20
|
+
|
|
21
|
+
for await (const event of session.stream(prompt, {
|
|
22
|
+
includePartialMessages: true,
|
|
23
|
+
})) {
|
|
24
|
+
switch (event.type) {
|
|
25
|
+
case DroidMessageType.AssistantTextDelta:
|
|
26
|
+
process.stdout.write(event.text);
|
|
27
|
+
break;
|
|
28
|
+
|
|
29
|
+
case DroidMessageType.ToolProgress:
|
|
30
|
+
console.log(`\n[tool progress] ${event.toolUseId}`);
|
|
31
|
+
break;
|
|
32
|
+
|
|
33
|
+
case DroidMessageType.TokenUsageUpdate:
|
|
34
|
+
console.log(
|
|
35
|
+
`\n[session tokens] ${event.inputTokens} in, ${event.outputTokens} out`
|
|
36
|
+
);
|
|
37
|
+
break;
|
|
38
|
+
|
|
39
|
+
case DroidMessageType.Result:
|
|
40
|
+
if (!event.success) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
event.error?.message ?? `Turn ended: ${event.subtype}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
sawSuccessfulResult = true;
|
|
46
|
+
console.log(`\n[result] ${event.subtype}`);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!sawSuccessfulResult) {
|
|
52
|
+
throw new Error('The stream ended without a successful result.');
|
|
53
|
+
}
|
|
54
|
+
} finally {
|
|
55
|
+
await session.close();
|
|
56
|
+
}
|