@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,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Forks, compacts, and rewinds a daemon session.
|
|
3
|
+
*
|
|
4
|
+
* Daemon operations return a new session ID. The source session stays usable,
|
|
5
|
+
* and the successor must be resumed explicitly.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npm run build
|
|
9
|
+
* FACTORY_API_KEY=... DROID_SDK_EXAMPLE_CWD=/repo npm run serve:browser-example
|
|
10
|
+
* open http://127.0.0.1:8420/?example=daemon-lifecycle
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
connectToDaemon,
|
|
15
|
+
DroidMessageType,
|
|
16
|
+
type ConnectedDroidSession,
|
|
17
|
+
} from '@factory/droid-sdk';
|
|
18
|
+
|
|
19
|
+
const { daemonUrl, apiKey, cwd } = (
|
|
20
|
+
globalThis as typeof globalThis & {
|
|
21
|
+
__DROID_SDK_EXAMPLE_CONFIG__: {
|
|
22
|
+
daemonUrl: string;
|
|
23
|
+
apiKey: string;
|
|
24
|
+
cwd: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
).__DROID_SDK_EXAMPLE_CONFIG__;
|
|
28
|
+
if (!apiKey || !cwd) {
|
|
29
|
+
throw new Error('Set FACTORY_API_KEY and DROID_SDK_EXAMPLE_CWD.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const droid = await connectToDaemon({
|
|
33
|
+
url: daemonUrl,
|
|
34
|
+
auth: { apiKey },
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
async function send(
|
|
38
|
+
session: ConnectedDroidSession,
|
|
39
|
+
prompt: string
|
|
40
|
+
): Promise<string> {
|
|
41
|
+
let messageId: string | undefined;
|
|
42
|
+
|
|
43
|
+
for await (const message of session.stream(prompt)) {
|
|
44
|
+
if (
|
|
45
|
+
message.type === DroidMessageType.User &&
|
|
46
|
+
message.message.visibility !== 'llm_only'
|
|
47
|
+
) {
|
|
48
|
+
messageId ??= message.message.id;
|
|
49
|
+
}
|
|
50
|
+
if (message.type === DroidMessageType.Assistant) {
|
|
51
|
+
console.log(message.text);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!messageId) throw new Error('The turn produced no user message ID.');
|
|
56
|
+
return messageId;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const sessions: ConnectedDroidSession[] = [];
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const source = await droid.sessions.create({ cwd });
|
|
63
|
+
sessions.push(source);
|
|
64
|
+
|
|
65
|
+
await send(source, 'Remember this phrase exactly: mango sunrise');
|
|
66
|
+
const secondTurn = await send(
|
|
67
|
+
source,
|
|
68
|
+
'Now forget it and remember: cobalt harbor'
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const { newSessionId: forkedId } = await source.fork();
|
|
72
|
+
const forked = await droid.sessions.resume(forkedId);
|
|
73
|
+
sessions.push(forked);
|
|
74
|
+
console.log(`Forked session: ${forked.id}`);
|
|
75
|
+
|
|
76
|
+
const { newSessionId: compactedId } = await source.compact();
|
|
77
|
+
const compacted = await droid.sessions.resume(compactedId);
|
|
78
|
+
sessions.push(compacted);
|
|
79
|
+
console.log(`Compacted session: ${compacted.id}`);
|
|
80
|
+
|
|
81
|
+
const { newSessionId: rewoundId } = await source.rewind({
|
|
82
|
+
messageId: secondTurn,
|
|
83
|
+
filesToRestore: [],
|
|
84
|
+
filesToDelete: [],
|
|
85
|
+
forkTitle: 'Before the second phrase',
|
|
86
|
+
});
|
|
87
|
+
const rewound = await droid.sessions.resume(rewoundId);
|
|
88
|
+
sessions.push(rewound);
|
|
89
|
+
console.log(`Rewound session: ${rewound.id}`);
|
|
90
|
+
|
|
91
|
+
await send(source, 'Say "source still works" and nothing else.');
|
|
92
|
+
} finally {
|
|
93
|
+
await Promise.allSettled(sessions.map((session) => session.close()));
|
|
94
|
+
droid.disconnect();
|
|
95
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lists canonical model metadata through a daemon connection.
|
|
3
|
+
*
|
|
4
|
+
* Runs in a real browser (no `process`, no Node builtins). From a source
|
|
5
|
+
* checkout of this repository:
|
|
6
|
+
* npm run build
|
|
7
|
+
* FACTORY_API_KEY=... npm run serve:browser-example
|
|
8
|
+
* open http://127.0.0.1:8420/?example=daemon-model-discovery
|
|
9
|
+
*
|
|
10
|
+
* Never commit a real API key or deploy it in browser code.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { connectToDaemon } from '@factory/droid-sdk';
|
|
14
|
+
|
|
15
|
+
const { daemonUrl, apiKey } = (
|
|
16
|
+
globalThis as typeof globalThis & {
|
|
17
|
+
__DROID_SDK_EXAMPLE_CONFIG__: {
|
|
18
|
+
daemonUrl: string;
|
|
19
|
+
apiKey: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
).__DROID_SDK_EXAMPLE_CONFIG__;
|
|
23
|
+
if (!apiKey) {
|
|
24
|
+
throw new Error('Set FACTORY_API_KEY.');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const droid = await connectToDaemon({
|
|
28
|
+
url: daemonUrl,
|
|
29
|
+
auth: { apiKey },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const models = await droid.models.list({ includeDisabled: true });
|
|
34
|
+
|
|
35
|
+
for (const model of models) {
|
|
36
|
+
const status = model.disabled
|
|
37
|
+
? `disabled: ${model.disabledReason}`
|
|
38
|
+
: 'available';
|
|
39
|
+
console.log(
|
|
40
|
+
`${model.displayName} (${model.id})\n` +
|
|
41
|
+
` status: ${status}\n` +
|
|
42
|
+
` reasoning: ${model.supportedReasoningEfforts.join(', ')}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
} finally {
|
|
46
|
+
droid.disconnect();
|
|
47
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates two sessions concurrently over one daemon connection.
|
|
3
|
+
*
|
|
4
|
+
* Runs in a real browser (no `process`, no Node builtins). From a source
|
|
5
|
+
* checkout of this repository:
|
|
6
|
+
* npm run build
|
|
7
|
+
* FACTORY_API_KEY=... DROID_SDK_EXAMPLE_CWD=/repo npm run serve:browser-example
|
|
8
|
+
* open http://127.0.0.1:8420/?example=daemon-multi-session
|
|
9
|
+
*
|
|
10
|
+
* Never commit a real API key or deploy it in browser code.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
connectToDaemon,
|
|
15
|
+
DroidMessageType,
|
|
16
|
+
type ConnectedDroidSession,
|
|
17
|
+
} from '@factory/droid-sdk';
|
|
18
|
+
|
|
19
|
+
const { daemonUrl, apiKey, cwd } = (
|
|
20
|
+
globalThis as typeof globalThis & {
|
|
21
|
+
__DROID_SDK_EXAMPLE_CONFIG__: {
|
|
22
|
+
daemonUrl: string;
|
|
23
|
+
apiKey: string;
|
|
24
|
+
cwd: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
).__DROID_SDK_EXAMPLE_CONFIG__;
|
|
28
|
+
if (!apiKey || !cwd) {
|
|
29
|
+
throw new Error('Set FACTORY_API_KEY and DROID_SDK_EXAMPLE_CWD.');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const droid = await connectToDaemon({
|
|
33
|
+
url: daemonUrl,
|
|
34
|
+
auth: { apiKey },
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
async function runTurn(
|
|
38
|
+
session: ConnectedDroidSession,
|
|
39
|
+
label: string,
|
|
40
|
+
prompt: string
|
|
41
|
+
): Promise<void> {
|
|
42
|
+
for await (const message of session.stream(prompt)) {
|
|
43
|
+
if (message.type === DroidMessageType.Assistant) {
|
|
44
|
+
console.log(`${label}: ${message.text}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const sessions: ConnectedDroidSession[] = [];
|
|
50
|
+
try {
|
|
51
|
+
const first = await droid.sessions.create({ cwd });
|
|
52
|
+
sessions.push(first);
|
|
53
|
+
const second = await droid.sessions.create({ cwd });
|
|
54
|
+
sessions.push(second);
|
|
55
|
+
|
|
56
|
+
console.log(`First session: ${first.id}`);
|
|
57
|
+
console.log(`Second session: ${second.id}`);
|
|
58
|
+
|
|
59
|
+
await Promise.all([
|
|
60
|
+
runTurn(first, 'First', 'Say "one" and nothing else.'),
|
|
61
|
+
runTurn(second, 'Second', 'Say "two" and nothing else.'),
|
|
62
|
+
]);
|
|
63
|
+
} finally {
|
|
64
|
+
await Promise.allSettled(sessions.map((session) => session.close()));
|
|
65
|
+
droid.disconnect();
|
|
66
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon connection from a browser: run a turn and approve its tool use.
|
|
3
|
+
*
|
|
4
|
+
* Waits for the turn to end rather than for the permission request, since a
|
|
5
|
+
* turn can finish without asking.
|
|
6
|
+
*
|
|
7
|
+
* Runs in a real browser (no `process`, no Node builtins). From a source
|
|
8
|
+
* checkout of this repository:
|
|
9
|
+
* npm run build
|
|
10
|
+
* FACTORY_API_KEY=... DROID_SDK_EXAMPLE_CWD=/repo npm run serve:browser-example
|
|
11
|
+
* open http://127.0.0.1:8420/?example=daemon-session
|
|
12
|
+
*
|
|
13
|
+
* Notes:
|
|
14
|
+
* - If your page is served over https://, the browser will block ws:// URLs as
|
|
15
|
+
* mixed content. Use wss:// (or serve the page over http:// for localhost).
|
|
16
|
+
* - Never commit a real API key or deploy it in browser code.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
AutonomyLevel,
|
|
21
|
+
connectToDaemon,
|
|
22
|
+
DroidMessageType,
|
|
23
|
+
ToolConfirmationOutcome,
|
|
24
|
+
type ConnectedDroidSession,
|
|
25
|
+
} from '@factory/droid-sdk';
|
|
26
|
+
|
|
27
|
+
const { daemonUrl, apiKey, cwd } = (
|
|
28
|
+
globalThis as typeof globalThis & {
|
|
29
|
+
__DROID_SDK_EXAMPLE_CONFIG__: {
|
|
30
|
+
daemonUrl: string;
|
|
31
|
+
apiKey: string;
|
|
32
|
+
cwd: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
).__DROID_SDK_EXAMPLE_CONFIG__;
|
|
36
|
+
if (!apiKey || !cwd) {
|
|
37
|
+
throw new Error('Set FACTORY_API_KEY and DROID_SDK_EXAMPLE_CWD.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const droid = await connectToDaemon({
|
|
41
|
+
url: daemonUrl,
|
|
42
|
+
auth: { apiKey },
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
let approvedPermission = false;
|
|
46
|
+
let session: ConnectedDroidSession | undefined;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
session = await droid.sessions.create({
|
|
50
|
+
cwd,
|
|
51
|
+
// Off ensures even low-risk commands exercise the permission handler.
|
|
52
|
+
autonomyLevel: AutonomyLevel.Off,
|
|
53
|
+
permissionHandler: () => {
|
|
54
|
+
approvedPermission = true;
|
|
55
|
+
return ToolConfirmationOutcome.ProceedOnce;
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
for await (const message of session.stream(
|
|
60
|
+
'Run `echo hello from the browser example` and show me the output.'
|
|
61
|
+
)) {
|
|
62
|
+
if (message.type === DroidMessageType.Assistant) {
|
|
63
|
+
console.log(message.text);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!approvedPermission) {
|
|
68
|
+
throw new Error('Turn finished without requesting permission.');
|
|
69
|
+
}
|
|
70
|
+
} finally {
|
|
71
|
+
await Promise.allSettled(session ? [session.close()] : []);
|
|
72
|
+
droid.disconnect();
|
|
73
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<title>Droid SDK browser examples</title>
|
|
6
|
+
<style>
|
|
7
|
+
body {
|
|
8
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
9
|
+
margin: 2rem;
|
|
10
|
+
line-height: 1.5;
|
|
11
|
+
}
|
|
12
|
+
pre {
|
|
13
|
+
background: #f4f4f5;
|
|
14
|
+
padding: 0.75rem;
|
|
15
|
+
overflow-x: auto;
|
|
16
|
+
white-space: pre-wrap;
|
|
17
|
+
}
|
|
18
|
+
#error:empty {
|
|
19
|
+
display: none;
|
|
20
|
+
}
|
|
21
|
+
#error {
|
|
22
|
+
background: #fee2e2;
|
|
23
|
+
padding: 0.75rem;
|
|
24
|
+
white-space: pre-wrap;
|
|
25
|
+
}
|
|
26
|
+
</style>
|
|
27
|
+
</head>
|
|
28
|
+
<body>
|
|
29
|
+
<h1>Droid SDK browser examples</h1>
|
|
30
|
+
<p>
|
|
31
|
+
Served by <code>npm run serve:browser-example</code>. The example module
|
|
32
|
+
is bundled for the browser on each request, so a reload picks up edits.
|
|
33
|
+
</p>
|
|
34
|
+
<p>
|
|
35
|
+
Set <code>FACTORY_API_KEY</code> and
|
|
36
|
+
<code>DROID_SDK_EXAMPLE_CWD</code> before starting the server, then pick
|
|
37
|
+
one with <code>?example=daemon-session</code> (default),
|
|
38
|
+
<code>?example=daemon-multi-session</code>,
|
|
39
|
+
<code>?example=daemon-lifecycle</code>,
|
|
40
|
+
<code>?example=daemon-model-discovery</code>, or
|
|
41
|
+
<code>?example=list-sessions</code>.
|
|
42
|
+
</p>
|
|
43
|
+
|
|
44
|
+
<div id="error"></div>
|
|
45
|
+
<div id="output"></div>
|
|
46
|
+
|
|
47
|
+
<script>
|
|
48
|
+
globalThis.__DROID_SDK_EXAMPLE_CONFIG__ =
|
|
49
|
+
__INJECTED_DROID_SDK_EXAMPLE_CONFIG__;
|
|
50
|
+
</script>
|
|
51
|
+
<script type="module">
|
|
52
|
+
// Surface load-time failures (for example a missing config value, which
|
|
53
|
+
// throws during module evaluation) instead of leaving a blank page.
|
|
54
|
+
const errorBox = document.querySelector('#error');
|
|
55
|
+
const showError = (err) => {
|
|
56
|
+
errorBox.textContent = String(
|
|
57
|
+
(err && (err.stack || err.message)) || err
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
window.addEventListener('error', (e) => showError(e.error ?? e.message));
|
|
61
|
+
window.addEventListener('unhandledrejection', (e) => showError(e.reason));
|
|
62
|
+
|
|
63
|
+
const params = new URLSearchParams(location.search);
|
|
64
|
+
const example = params.get('example') ?? 'daemon-session';
|
|
65
|
+
|
|
66
|
+
import(`./${example}.js${location.search}`).catch(showError);
|
|
67
|
+
</script>
|
|
68
|
+
</body>
|
|
69
|
+
</html>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lists recent daemon sessions and their messages.
|
|
3
|
+
*
|
|
4
|
+
* Runs in a real browser (no `process`, no Node builtins). From a source
|
|
5
|
+
* checkout of this repository:
|
|
6
|
+
* npm run build
|
|
7
|
+
* FACTORY_API_KEY=... npm run serve:browser-example
|
|
8
|
+
* open http://127.0.0.1:8420/?example=list-sessions
|
|
9
|
+
*
|
|
10
|
+
* Never commit a real API key or deploy it in browser code.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { connectToDaemon } from '@factory/droid-sdk';
|
|
14
|
+
|
|
15
|
+
const { daemonUrl, apiKey } = (
|
|
16
|
+
globalThis as typeof globalThis & {
|
|
17
|
+
__DROID_SDK_EXAMPLE_CONFIG__: {
|
|
18
|
+
daemonUrl: string;
|
|
19
|
+
apiKey: string;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
).__DROID_SDK_EXAMPLE_CONFIG__;
|
|
23
|
+
if (!apiKey) {
|
|
24
|
+
throw new Error('Set FACTORY_API_KEY.');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const droid = await connectToDaemon({
|
|
28
|
+
url: daemonUrl,
|
|
29
|
+
auth: { apiKey },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const sessions = await droid.sessions.list({ limit: 10 });
|
|
34
|
+
|
|
35
|
+
if (sessions.length === 0) {
|
|
36
|
+
console.log('No sessions found.');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const session of sessions) {
|
|
40
|
+
const messages = await session.getMessages({ limit: 20 });
|
|
41
|
+
|
|
42
|
+
console.log(
|
|
43
|
+
session.id,
|
|
44
|
+
session.title ?? '(untitled)',
|
|
45
|
+
`${messages.length} messages`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
} finally {
|
|
49
|
+
droid.disconnect();
|
|
50
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abort a running turn with AbortController.
|
|
3
|
+
*
|
|
4
|
+
* Passing an `abortSignal` to `session.stream()` makes the stream throw when
|
|
5
|
+
* the signal fires. See interrupt-session.ts for the server-side equivalent.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx tsx examples/node/abort-session-stream.ts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createSession } from '@factory/droid-sdk/node';
|
|
12
|
+
|
|
13
|
+
const session = await createSession();
|
|
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
|
+
{ abortSignal: controller.signal }
|
|
24
|
+
)) {
|
|
25
|
+
// Consume the stream until the abort signal cuts it short.
|
|
26
|
+
}
|
|
27
|
+
console.log('The turn completed before the abort signal fired.');
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (!controller.signal.aborted) throw error;
|
|
30
|
+
console.log(error instanceof Error ? error.message : String(error));
|
|
31
|
+
} finally {
|
|
32
|
+
clearTimeout(timeout);
|
|
33
|
+
await session.close();
|
|
34
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AskUser elicitation example.
|
|
3
|
+
*
|
|
4
|
+
* When Droid needs input mid-turn, `askUserHandler` answers it. Without a
|
|
5
|
+
* handler, the request is declined.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx tsx examples/node/ask-user-elicitation.ts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { run } from '@factory/droid-sdk/node';
|
|
12
|
+
|
|
13
|
+
const result = await run(
|
|
14
|
+
'Use the AskUser tool to ask me which color I prefer, then reply with the color I picked.',
|
|
15
|
+
{
|
|
16
|
+
askUserHandler({ questions }) {
|
|
17
|
+
for (const question of questions) {
|
|
18
|
+
console.log(`[asked] ${question.question}`);
|
|
19
|
+
console.log(`[options] ${question.options.join(', ')}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Answer every question with its first option. A real integration would
|
|
23
|
+
// prompt the user; returning `cancelled: true` declines instead.
|
|
24
|
+
return {
|
|
25
|
+
answers: questions.map((question) => ({
|
|
26
|
+
index: question.index,
|
|
27
|
+
question: question.question,
|
|
28
|
+
answer: question.options[0] ?? 'no preference',
|
|
29
|
+
})),
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (!result.success) {
|
|
36
|
+
throw new Error(result.error?.message ?? 'The turn was interrupted.');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.log(`\n${result.text}`);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact session example.
|
|
3
|
+
*
|
|
4
|
+
* Builds up history over a few turns, then calls `compact()` to
|
|
5
|
+
* summarize it into a new session.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx tsx examples/node/compact-session.ts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createSession, DroidMessageType } from '@factory/droid-sdk/node';
|
|
12
|
+
|
|
13
|
+
async function streamText(
|
|
14
|
+
activeSession: Awaited<ReturnType<typeof createSession>>,
|
|
15
|
+
prompt: string
|
|
16
|
+
): Promise<string> {
|
|
17
|
+
let text = '';
|
|
18
|
+
for await (const msg of activeSession.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
|
+
for (const prompt of [
|
|
36
|
+
'Say "one" and nothing else',
|
|
37
|
+
'Say "two" and nothing else',
|
|
38
|
+
'Say "three" and nothing else',
|
|
39
|
+
]) {
|
|
40
|
+
console.log(await streamText(session, prompt));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { session: compacted, removedCount } = await session.compact();
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
// `session` is now a retired wrapper. Its persisted ID can still be passed
|
|
47
|
+
// to resumeSession() later if the uncompacted history is needed again.
|
|
48
|
+
console.log(`\noriginal session: ${session.id}`);
|
|
49
|
+
console.log(`compacted into: ${compacted.id}`);
|
|
50
|
+
console.log(`messages removed: ${removedCount}`);
|
|
51
|
+
|
|
52
|
+
const continued = await streamText(
|
|
53
|
+
compacted,
|
|
54
|
+
'Which numbers did I ask you to say?'
|
|
55
|
+
);
|
|
56
|
+
console.log(`continued: ${continued}`);
|
|
57
|
+
} finally {
|
|
58
|
+
await compacted.close();
|
|
59
|
+
}
|
|
60
|
+
} finally {
|
|
61
|
+
// Closes the active source, or does nothing if it was retired.
|
|
62
|
+
await session.close();
|
|
63
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom system prompt example.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* npx tsx examples/node/custom-system-prompt.ts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createSession, DroidMessageType } from '@factory/droid-sdk/node';
|
|
9
|
+
|
|
10
|
+
const session = await createSession({
|
|
11
|
+
systemPrompt: {
|
|
12
|
+
type: 'preset',
|
|
13
|
+
preset: 'droid',
|
|
14
|
+
append: 'Keep answers concise and cite relevant files.',
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
let text = '';
|
|
20
|
+
for await (const message of session.stream(
|
|
21
|
+
'Summarize the main entry points in this project.'
|
|
22
|
+
)) {
|
|
23
|
+
if (message.type === DroidMessageType.Assistant) {
|
|
24
|
+
text += message.text;
|
|
25
|
+
}
|
|
26
|
+
if (message.type === DroidMessageType.Result && !message.success) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
message.error?.message ?? `Turn ended: ${message.subtype}`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
console.log(text);
|
|
33
|
+
} finally {
|
|
34
|
+
await session.close();
|
|
35
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document input example.
|
|
3
|
+
*
|
|
4
|
+
* Attaches plain text or a base64 PDF through `files`. With no path, it uses
|
|
5
|
+
* an embedded text document so the example runs as-is.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx tsx examples/node/document-input.ts
|
|
9
|
+
* npx tsx examples/node/document-input.ts ./report.txt
|
|
10
|
+
* npx tsx examples/node/document-input.ts ./report.pdf
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFile } from 'node:fs/promises';
|
|
14
|
+
import { basename, extname } from 'node:path';
|
|
15
|
+
|
|
16
|
+
import { run, type DocumentSource } from '@factory/droid-sdk/node';
|
|
17
|
+
|
|
18
|
+
const inputPath = process.argv[2];
|
|
19
|
+
let document: DocumentSource;
|
|
20
|
+
|
|
21
|
+
if (!inputPath) {
|
|
22
|
+
document = {
|
|
23
|
+
type: 'text',
|
|
24
|
+
mediaType: 'text/plain',
|
|
25
|
+
data: [
|
|
26
|
+
'Release: 2.4.0',
|
|
27
|
+
'Status: Ready',
|
|
28
|
+
'Main change: Added document input to the TypeScript SDK guide.',
|
|
29
|
+
].join('\n'),
|
|
30
|
+
name: 'release-notes.txt',
|
|
31
|
+
};
|
|
32
|
+
} else if (extname(inputPath).toLowerCase() === '.pdf') {
|
|
33
|
+
document = {
|
|
34
|
+
type: 'base64',
|
|
35
|
+
mediaType: 'application/pdf',
|
|
36
|
+
data: (await readFile(inputPath)).toString('base64'),
|
|
37
|
+
name: basename(inputPath),
|
|
38
|
+
};
|
|
39
|
+
} else {
|
|
40
|
+
document = {
|
|
41
|
+
type: 'text',
|
|
42
|
+
mediaType: 'text/plain',
|
|
43
|
+
data: await readFile(inputPath, 'utf8'),
|
|
44
|
+
name: basename(inputPath),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const result = await run('Summarize this document in one sentence.', {
|
|
49
|
+
files: [document],
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (!result.success) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
result.error?.message ?? `Document summary failed (${result.subtype}).`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log(result.text);
|