-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathdaemon-multi-session.ts
More file actions
141 lines (132 loc) · 4.37 KB
/
Copy pathdaemon-multi-session.ts
File metadata and controls
141 lines (132 loc) · 4.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/**
* Daemon: multiple concurrent sessions.
*
* Connects to a local daemon, creates two sessions in separate temporary
* directories, and runs them concurrently over a single WebSocket connection.
*
* Usage:
* npx tsx examples/daemon-multi-session.ts
*
* Requirements: droid CLI installed, plus a real FACTORY_API_KEY.
* Daemon authentication has no stored-credential fallback, so this
* example skips itself when the env var is unset.
*/
import { mkdtemp, mkdir, readFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
connectDaemon,
DroidMessageType,
ToolConfirmationOutcome,
ToolConfirmationType,
type ToolConfirmationDetails,
} from '@factory/droid-sdk';
function canWriteFile(
filePath: string,
details: ToolConfirmationDetails
): boolean {
switch (details.type) {
case ToolConfirmationType.Create:
case ToolConfirmationType.Edit:
case ToolConfirmationType.ApplyPatch:
return details.filePath === filePath;
default:
return false;
}
}
async function main(): Promise<void> {
if (!process.env.FACTORY_API_KEY) {
console.log(
'FACTORY_API_KEY is not set. Daemon authentication requires a real ' +
'API key (stored CLI credentials are not used). Skipping.'
);
return;
}
console.log('Connecting to local daemon...\n');
const daemon = await connectDaemon({ apiKey: process.env.FACTORY_API_KEY });
console.log('Connected!\n');
const tempDir = await mkdtemp(join(tmpdir(), 'droid-sdk-daemon-'));
const frontendDir = join(tempDir, 'frontend');
const backendDir = join(tempDir, 'backend');
const frontendFile = join(frontendDir, 'hello.md');
const backendFile = join(backendDir, 'notes.md');
await Promise.all([
mkdir(frontendDir, { recursive: true }),
mkdir(backendDir, { recursive: true }),
]);
try {
const frontend = await daemon.createSession({
cwd: frontendDir,
permissionHandler(params) {
return params.toolUses.every((item) =>
canWriteFile(frontendFile, item.details)
)
? ToolConfirmationOutcome.ProceedOnce
: ToolConfirmationOutcome.Cancel;
},
});
const backend = await daemon.createSession({
cwd: backendDir,
permissionHandler(params) {
return params.toolUses.every((item) =>
canWriteFile(backendFile, item.details)
)
? ToolConfirmationOutcome.ProceedOnce
: ToolConfirmationOutcome.Cancel;
},
});
console.log('Two sessions created. Running concurrently...\n');
await Promise.all([
(async () => {
try {
for await (const msg of frontend.stream(
'Create a file called hello.md with a short greeting message. Just a few lines.'
)) {
if (msg.type === DroidMessageType.Assistant) {
process.stdout.write(`[frontend] ${msg.text}\n`);
} else if (msg.type === DroidMessageType.ToolCall) {
console.log(`[frontend] [tool] ${msg.toolUse.name}`);
} else if (msg.type === DroidMessageType.Result) {
console.log(`[frontend] Done in ${msg.durationMs}ms`);
}
}
} finally {
await frontend.close();
}
})(),
(async () => {
try {
for await (const msg of backend.stream(
'Create a file called notes.md with 3 random fun facts. Keep it short.'
)) {
if (msg.type === DroidMessageType.Assistant) {
process.stdout.write(`[backend] ${msg.text}\n`);
} else if (msg.type === DroidMessageType.ToolCall) {
console.log(`[backend] [tool] ${msg.toolUse.name}`);
} else if (msg.type === DroidMessageType.Result) {
console.log(`[backend] Done in ${msg.durationMs}ms`);
}
}
} finally {
await backend.close();
}
})(),
]);
} finally {
await daemon.close();
try {
const [greeting, notes] = await Promise.all([
readFile(frontendFile, 'utf8'),
readFile(backendFile, 'utf8'),
]);
console.log(`\n=== hello.md ===\n${greeting.trim()}`);
console.log(`\n=== notes.md ===\n${notes.trim()}`);
} finally {
await rm(tempDir, { recursive: true, force: true });
}
}
}
main().catch((err: unknown) => {
console.error('Error:', err);
process.exit(1);
});