agentbox-sdk 0.1.0 → 0.1.3
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 +72 -18
- package/dist/{Sandbox-DTprxRZf.d.ts → Sandbox-CByFJI8X.d.ts} +102 -6
- package/dist/agents/index.d.ts +74 -5
- package/dist/agents/index.js +14 -5
- package/dist/chunk-4MBB6QHD.js +4658 -0
- package/dist/chunk-GOFJNFAD.js +18 -0
- package/dist/{chunk-HMBWQSVN.js → chunk-INMA52FV.js} +83 -23
- package/dist/{chunk-QRQFQTGH.js → chunk-LPKKT6YT.js} +742 -50
- package/dist/chunk-ZOWBRUQR.js +476 -0
- package/dist/cli.js +16 -8
- package/dist/enums.d.ts +25 -0
- package/dist/enums.js +8 -0
- package/dist/events/index.d.ts +86 -2
- package/dist/events/index.js +4 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +18 -5
- package/dist/sandboxes/index.d.ts +58 -4
- package/dist/sandboxes/index.js +7 -3
- package/dist/{types-BwcoN0n-.d.ts → types-B3N-Qo2q.d.ts} +147 -6
- package/images/browser-agent.mjs +3 -3
- package/package.json +13 -3
- package/dist/chunk-7FLLQJ6J.js +0 -185
- package/dist/chunk-BW43ESRM.js +0 -4381
- /package/dist/{chunk-JFDP556Q.js → chunk-NSJM57Z4.js} +0 -0
package/dist/chunk-BW43ESRM.js
DELETED
|
@@ -1,4381 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createNormalizedEvent,
|
|
3
|
-
normalizeRawAgentEvent,
|
|
4
|
-
toAISDKStream
|
|
5
|
-
} from "./chunk-7FLLQJ6J.js";
|
|
6
|
-
import {
|
|
7
|
-
AgentBoxError,
|
|
8
|
-
AsyncQueue,
|
|
9
|
-
UnsupportedProviderError,
|
|
10
|
-
asError,
|
|
11
|
-
getAvailablePort,
|
|
12
|
-
linesFromTextChunks,
|
|
13
|
-
sleep,
|
|
14
|
-
waitFor
|
|
15
|
-
} from "./chunk-HMBWQSVN.js";
|
|
16
|
-
import {
|
|
17
|
-
shellQuote
|
|
18
|
-
} from "./chunk-JFDP556Q.js";
|
|
19
|
-
|
|
20
|
-
// src/agents/Agent.ts
|
|
21
|
-
import { randomUUID as randomUUID3 } from "crypto";
|
|
22
|
-
|
|
23
|
-
// src/agents/providers/claude-code.ts
|
|
24
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
25
|
-
import path8 from "path";
|
|
26
|
-
|
|
27
|
-
// src/agents/approval.ts
|
|
28
|
-
function getApprovalMode(options) {
|
|
29
|
-
return options.approvalMode ?? "auto";
|
|
30
|
-
}
|
|
31
|
-
function isInteractiveApproval(options) {
|
|
32
|
-
return getApprovalMode(options) === "interactive";
|
|
33
|
-
}
|
|
34
|
-
function shouldAutoApproveClaudeTools(options) {
|
|
35
|
-
if (options.provider?.autoApproveTools !== void 0) {
|
|
36
|
-
return options.provider.autoApproveTools;
|
|
37
|
-
}
|
|
38
|
-
return !isInteractiveApproval(options);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// src/agents/input.ts
|
|
42
|
-
import { readFile } from "fs/promises";
|
|
43
|
-
import path from "path";
|
|
44
|
-
import { fileURLToPath } from "url";
|
|
45
|
-
var IMAGE_MEDIA_TYPE_BY_EXTENSION = {
|
|
46
|
-
".gif": "image/gif",
|
|
47
|
-
".jpg": "image/jpeg",
|
|
48
|
-
".jpeg": "image/jpeg",
|
|
49
|
-
".png": "image/png",
|
|
50
|
-
".webp": "image/webp"
|
|
51
|
-
};
|
|
52
|
-
var FILE_MEDIA_TYPE_BY_EXTENSION = {
|
|
53
|
-
...IMAGE_MEDIA_TYPE_BY_EXTENSION,
|
|
54
|
-
".csv": "text/csv",
|
|
55
|
-
".htm": "text/html",
|
|
56
|
-
".html": "text/html",
|
|
57
|
-
".json": "application/json",
|
|
58
|
-
".md": "text/markdown",
|
|
59
|
-
".pdf": "application/pdf",
|
|
60
|
-
".txt": "text/plain",
|
|
61
|
-
".xml": "application/xml",
|
|
62
|
-
".yaml": "application/yaml",
|
|
63
|
-
".yml": "application/yaml"
|
|
64
|
-
};
|
|
65
|
-
var CLAUDE_IMAGE_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
66
|
-
"image/gif",
|
|
67
|
-
"image/jpeg",
|
|
68
|
-
"image/png",
|
|
69
|
-
"image/webp"
|
|
70
|
-
]);
|
|
71
|
-
var CLAUDE_TEXT_LIKE_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
72
|
-
"application/json",
|
|
73
|
-
"application/xml",
|
|
74
|
-
"application/yaml",
|
|
75
|
-
"text/csv",
|
|
76
|
-
"text/html",
|
|
77
|
-
"text/markdown",
|
|
78
|
-
"text/plain",
|
|
79
|
-
"text/xml"
|
|
80
|
-
]);
|
|
81
|
-
function normalizeUserInput(input) {
|
|
82
|
-
return typeof input === "string" ? [{ type: "text", text: input }] : input;
|
|
83
|
-
}
|
|
84
|
-
async function resolveUserInputParts(input) {
|
|
85
|
-
const parts = normalizeUserInput(input);
|
|
86
|
-
return Promise.all(parts.map((part) => resolveUserInputPart(part)));
|
|
87
|
-
}
|
|
88
|
-
async function validateProviderUserInput(provider, input) {
|
|
89
|
-
const parts = await resolveUserInputParts(input);
|
|
90
|
-
if (provider === "codex") {
|
|
91
|
-
const unsupportedPart = parts.find((part) => part.type === "file");
|
|
92
|
-
if (unsupportedPart) {
|
|
93
|
-
throw new AgentBoxError(
|
|
94
|
-
`The codex provider does not yet support "${unsupportedPart.type}" input parts through codex app-server. Codex currently supports text and image input items.`,
|
|
95
|
-
{
|
|
96
|
-
code: "UNSUPPORTED_INPUT_PART",
|
|
97
|
-
details: {
|
|
98
|
-
provider,
|
|
99
|
-
partType: unsupportedPart.type
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
return parts;
|
|
105
|
-
}
|
|
106
|
-
if (provider === "claude-code") {
|
|
107
|
-
for (const part of parts) {
|
|
108
|
-
if (part.type === "image") {
|
|
109
|
-
if (!CLAUDE_IMAGE_MEDIA_TYPES.has(part.mediaType)) {
|
|
110
|
-
throw new AgentBoxError(
|
|
111
|
-
`Claude Code only supports image inputs with one of these media types: ${Array.from(CLAUDE_IMAGE_MEDIA_TYPES).join(", ")}.`,
|
|
112
|
-
{
|
|
113
|
-
code: "UNSUPPORTED_INPUT_MEDIA_TYPE",
|
|
114
|
-
details: {
|
|
115
|
-
provider,
|
|
116
|
-
partType: part.type,
|
|
117
|
-
mediaType: part.mediaType
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (part.type === "file") {
|
|
124
|
-
if (part.mediaType !== "application/pdf" && !isClaudeTextLikeMediaType(part.mediaType)) {
|
|
125
|
-
throw new AgentBoxError(
|
|
126
|
-
`Claude Code only supports PDF and text-like file inputs. Received "${part.mediaType}".`,
|
|
127
|
-
{
|
|
128
|
-
code: "UNSUPPORTED_INPUT_MEDIA_TYPE",
|
|
129
|
-
details: {
|
|
130
|
-
provider,
|
|
131
|
-
partType: part.type,
|
|
132
|
-
mediaType: part.mediaType
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
if (part.source.type === "url" && part.mediaType !== "application/pdf" && isClaudeTextLikeMediaType(part.mediaType)) {
|
|
138
|
-
throw new AgentBoxError(
|
|
139
|
-
"Claude Code text-like file inputs must be provided as inline data, not a remote URL.",
|
|
140
|
-
{
|
|
141
|
-
code: "UNSUPPORTED_INPUT_SOURCE",
|
|
142
|
-
details: {
|
|
143
|
-
provider,
|
|
144
|
-
partType: part.type,
|
|
145
|
-
mediaType: part.mediaType,
|
|
146
|
-
sourceType: part.source.type
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
return parts;
|
|
155
|
-
}
|
|
156
|
-
function mapToOpenCodeParts(parts) {
|
|
157
|
-
return parts.map((part) => {
|
|
158
|
-
if (part.type === "text") {
|
|
159
|
-
return {
|
|
160
|
-
type: "text",
|
|
161
|
-
text: part.text
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
return {
|
|
165
|
-
type: "file",
|
|
166
|
-
url: binarySourceToUrl(part.source, part.mediaType),
|
|
167
|
-
mime: part.mediaType,
|
|
168
|
-
...part.type === "file" && part.filename ? { filename: part.filename } : {}
|
|
169
|
-
};
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
function mapToClaudeUserContent(parts) {
|
|
173
|
-
if (parts.every((part) => part.type === "text")) {
|
|
174
|
-
return joinTextParts(parts);
|
|
175
|
-
}
|
|
176
|
-
return parts.map((part) => {
|
|
177
|
-
if (part.type === "text") {
|
|
178
|
-
return {
|
|
179
|
-
type: "text",
|
|
180
|
-
text: part.text
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
if (part.type === "image") {
|
|
184
|
-
if (part.source.type === "url" && isRemoteUrl(part.source.url)) {
|
|
185
|
-
return {
|
|
186
|
-
type: "image",
|
|
187
|
-
source: {
|
|
188
|
-
type: "url",
|
|
189
|
-
url: part.source.url
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
return {
|
|
194
|
-
type: "image",
|
|
195
|
-
source: {
|
|
196
|
-
type: "base64",
|
|
197
|
-
media_type: part.mediaType,
|
|
198
|
-
data: part.source.type === "base64" ? part.source.data : dataUrlToBase64(part.source.url)
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
if (part.mediaType === "application/pdf") {
|
|
203
|
-
if (part.source.type === "url" && isRemoteUrl(part.source.url)) {
|
|
204
|
-
return {
|
|
205
|
-
type: "document",
|
|
206
|
-
source: {
|
|
207
|
-
type: "url",
|
|
208
|
-
url: part.source.url
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
return {
|
|
213
|
-
type: "document",
|
|
214
|
-
source: {
|
|
215
|
-
type: "base64",
|
|
216
|
-
media_type: part.mediaType,
|
|
217
|
-
data: part.source.type === "base64" ? part.source.data : dataUrlToBase64(part.source.url)
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
if (!isClaudeTextLikeMediaType(part.mediaType)) {
|
|
222
|
-
throw new AgentBoxError(
|
|
223
|
-
`Claude Code cannot map file inputs with media type "${part.mediaType}".`,
|
|
224
|
-
{
|
|
225
|
-
code: "UNSUPPORTED_INPUT_MEDIA_TYPE",
|
|
226
|
-
details: {
|
|
227
|
-
partType: part.type,
|
|
228
|
-
mediaType: part.mediaType
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
return {
|
|
234
|
-
type: "document",
|
|
235
|
-
source: {
|
|
236
|
-
type: "text",
|
|
237
|
-
media_type: "text/plain",
|
|
238
|
-
data: Buffer.from(
|
|
239
|
-
part.source.type === "base64" ? part.source.data : dataUrlToBase64(part.source.url),
|
|
240
|
-
"base64"
|
|
241
|
-
).toString("utf8")
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
async function mapToCodexPromptParts(parts, materializeLocalImage) {
|
|
247
|
-
const mapped = [];
|
|
248
|
-
for (const [index, part] of parts.entries()) {
|
|
249
|
-
if (part.type !== "image") {
|
|
250
|
-
continue;
|
|
251
|
-
}
|
|
252
|
-
if (part.source.type === "url" && isRemoteUrl(part.source.url)) {
|
|
253
|
-
mapped.push({
|
|
254
|
-
type: "image",
|
|
255
|
-
url: part.source.url
|
|
256
|
-
});
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
mapped.push({
|
|
260
|
-
type: "localImage",
|
|
261
|
-
path: await materializeLocalImage(part, index)
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
return mapped;
|
|
265
|
-
}
|
|
266
|
-
function joinTextParts(parts) {
|
|
267
|
-
return parts.map((part) => {
|
|
268
|
-
if (part.type !== "text") {
|
|
269
|
-
throw new AgentBoxError(
|
|
270
|
-
`Cannot join "${part.type}" input parts into a text-only prompt.`,
|
|
271
|
-
{
|
|
272
|
-
code: "UNSUPPORTED_INPUT_PART",
|
|
273
|
-
details: {
|
|
274
|
-
partType: part.type
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
return part.text;
|
|
280
|
-
}).join("");
|
|
281
|
-
}
|
|
282
|
-
async function resolveUserInputPart(part) {
|
|
283
|
-
if (part.type === "text") {
|
|
284
|
-
return part;
|
|
285
|
-
}
|
|
286
|
-
if (part.type === "image") {
|
|
287
|
-
const resolved2 = await resolveBinaryContent(part.image, {
|
|
288
|
-
kind: "image",
|
|
289
|
-
mediaType: part.mediaType
|
|
290
|
-
});
|
|
291
|
-
return {
|
|
292
|
-
type: "image",
|
|
293
|
-
mediaType: resolved2.mediaType,
|
|
294
|
-
source: resolved2.source
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
const resolved = await resolveBinaryContent(part.data, {
|
|
298
|
-
kind: "file",
|
|
299
|
-
mediaType: part.mediaType,
|
|
300
|
-
filename: part.filename
|
|
301
|
-
});
|
|
302
|
-
return {
|
|
303
|
-
type: "file",
|
|
304
|
-
mediaType: resolved.mediaType,
|
|
305
|
-
filename: resolved.filename,
|
|
306
|
-
source: resolved.source
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
async function resolveBinaryContent(content, options) {
|
|
310
|
-
if (content instanceof URL) {
|
|
311
|
-
return resolveUrlContent(content, options);
|
|
312
|
-
}
|
|
313
|
-
if (typeof content === "string") {
|
|
314
|
-
const url = parseStructuredUrl(content);
|
|
315
|
-
if (url) {
|
|
316
|
-
return resolveUrlContent(url, options);
|
|
317
|
-
}
|
|
318
|
-
const mediaType = resolveMediaType("", options);
|
|
319
|
-
return {
|
|
320
|
-
mediaType,
|
|
321
|
-
filename: options.filename,
|
|
322
|
-
source: {
|
|
323
|
-
type: "base64",
|
|
324
|
-
data: content
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
if (Buffer.isBuffer(content)) {
|
|
329
|
-
return {
|
|
330
|
-
mediaType: resolveMediaType("", options),
|
|
331
|
-
filename: options.filename,
|
|
332
|
-
source: {
|
|
333
|
-
type: "base64",
|
|
334
|
-
data: content.toString("base64")
|
|
335
|
-
}
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
if (content instanceof Uint8Array) {
|
|
339
|
-
return {
|
|
340
|
-
mediaType: resolveMediaType("", options),
|
|
341
|
-
filename: options.filename,
|
|
342
|
-
source: {
|
|
343
|
-
type: "base64",
|
|
344
|
-
data: Buffer.from(content).toString("base64")
|
|
345
|
-
}
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
if (content instanceof ArrayBuffer) {
|
|
349
|
-
return {
|
|
350
|
-
mediaType: resolveMediaType("", options),
|
|
351
|
-
filename: options.filename,
|
|
352
|
-
source: {
|
|
353
|
-
type: "base64",
|
|
354
|
-
data: Buffer.from(content).toString("base64")
|
|
355
|
-
}
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
throw new AgentBoxError("Unsupported input content type.", {
|
|
359
|
-
code: "UNSUPPORTED_INPUT_SOURCE",
|
|
360
|
-
details: {
|
|
361
|
-
kind: options.kind,
|
|
362
|
-
valueType: typeof content
|
|
363
|
-
}
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
async function resolveUrlContent(url, options) {
|
|
367
|
-
if (url.protocol === "data:") {
|
|
368
|
-
const parsed = parseDataUrl(url.toString());
|
|
369
|
-
return {
|
|
370
|
-
mediaType: resolveMediaType("", options, parsed.mediaType),
|
|
371
|
-
filename: options.filename,
|
|
372
|
-
source: {
|
|
373
|
-
type: "base64",
|
|
374
|
-
data: parsed.base64Data
|
|
375
|
-
}
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
if (url.protocol === "file:") {
|
|
379
|
-
const filePath = fileURLToPath(url);
|
|
380
|
-
const buffer = await readFile(filePath);
|
|
381
|
-
return {
|
|
382
|
-
mediaType: resolveMediaType(filePath, options),
|
|
383
|
-
filename: options.filename ?? path.basename(filePath),
|
|
384
|
-
source: {
|
|
385
|
-
type: "base64",
|
|
386
|
-
data: buffer.toString("base64")
|
|
387
|
-
}
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
391
|
-
return {
|
|
392
|
-
mediaType: resolveMediaType(url.pathname, options),
|
|
393
|
-
filename: options.filename ?? inferFilename(url.pathname),
|
|
394
|
-
source: {
|
|
395
|
-
type: "url",
|
|
396
|
-
url: url.toString()
|
|
397
|
-
}
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
throw new AgentBoxError(
|
|
401
|
-
`Unsupported input URL protocol "${url.protocol}" for ${options.kind} parts.`,
|
|
402
|
-
{
|
|
403
|
-
code: "UNSUPPORTED_INPUT_SOURCE",
|
|
404
|
-
details: {
|
|
405
|
-
kind: options.kind,
|
|
406
|
-
protocol: url.protocol
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
function resolveMediaType(pathname, options, parsedMediaType) {
|
|
412
|
-
const explicitMediaType = options.mediaType ?? parsedMediaType;
|
|
413
|
-
if (explicitMediaType) {
|
|
414
|
-
return explicitMediaType;
|
|
415
|
-
}
|
|
416
|
-
const inferredMediaType = inferMediaType(pathname);
|
|
417
|
-
if (inferredMediaType) {
|
|
418
|
-
return inferredMediaType;
|
|
419
|
-
}
|
|
420
|
-
throw new AgentBoxError(
|
|
421
|
-
`Could not determine a media type for the ${options.kind} input part.`,
|
|
422
|
-
{
|
|
423
|
-
code: "MISSING_INPUT_MEDIA_TYPE",
|
|
424
|
-
details: {
|
|
425
|
-
kind: options.kind,
|
|
426
|
-
pathname
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
function inferMediaType(pathname) {
|
|
432
|
-
const extension = path.extname(pathname).toLowerCase();
|
|
433
|
-
return FILE_MEDIA_TYPE_BY_EXTENSION[extension];
|
|
434
|
-
}
|
|
435
|
-
function inferFilename(pathname) {
|
|
436
|
-
const name = path.basename(pathname);
|
|
437
|
-
return name && name !== "." ? name : void 0;
|
|
438
|
-
}
|
|
439
|
-
function parseStructuredUrl(value) {
|
|
440
|
-
if (!/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(value)) {
|
|
441
|
-
return null;
|
|
442
|
-
}
|
|
443
|
-
try {
|
|
444
|
-
return new URL(value);
|
|
445
|
-
} catch {
|
|
446
|
-
return null;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
function parseDataUrl(value) {
|
|
450
|
-
const separatorIndex = value.indexOf(",");
|
|
451
|
-
if (separatorIndex === -1) {
|
|
452
|
-
throw new AgentBoxError("Invalid data URL input.", {
|
|
453
|
-
code: "INVALID_INPUT_DATA_URL"
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
const header = value.slice(5, separatorIndex);
|
|
457
|
-
const body = value.slice(separatorIndex + 1);
|
|
458
|
-
const mediaType = header.split(";")[0] || void 0;
|
|
459
|
-
const isBase64 = header.includes(";base64");
|
|
460
|
-
return {
|
|
461
|
-
mediaType,
|
|
462
|
-
base64Data: isBase64 ? body : Buffer.from(decodeURIComponent(body), "utf8").toString("base64")
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
function binarySourceToUrl(source, mediaType) {
|
|
466
|
-
return source.type === "url" ? source.url : `data:${mediaType};base64,${source.data}`;
|
|
467
|
-
}
|
|
468
|
-
function dataUrlToBase64(value) {
|
|
469
|
-
return parseDataUrl(value).base64Data;
|
|
470
|
-
}
|
|
471
|
-
function isRemoteUrl(value) {
|
|
472
|
-
return value.startsWith("http://") || value.startsWith("https://");
|
|
473
|
-
}
|
|
474
|
-
function isClaudeTextLikeMediaType(mediaType) {
|
|
475
|
-
return mediaType.startsWith("text/") || CLAUDE_TEXT_LIKE_MEDIA_TYPES.has(mediaType);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// src/agents/config/commands.ts
|
|
479
|
-
import path2 from "path";
|
|
480
|
-
function buildFrontmatter(values) {
|
|
481
|
-
const lines = Object.entries(values).filter(([, value]) => value !== void 0).map(([key, value]) => `${key}: ${JSON.stringify(value)}`);
|
|
482
|
-
return lines.length > 0 ? `---
|
|
483
|
-
${lines.join("\n")}
|
|
484
|
-
---
|
|
485
|
-
|
|
486
|
-
` : "";
|
|
487
|
-
}
|
|
488
|
-
function buildClaudeCommandArtifacts(commands, layout) {
|
|
489
|
-
return (commands ?? []).map((command) => ({
|
|
490
|
-
path: path2.join(layout.claudeDir, "commands", `${command.name}.md`),
|
|
491
|
-
content: buildFrontmatter({
|
|
492
|
-
description: command.description
|
|
493
|
-
}) + command.template
|
|
494
|
-
}));
|
|
495
|
-
}
|
|
496
|
-
function buildOpenCodeCommandsConfig(commands) {
|
|
497
|
-
if (!commands || commands.length === 0) {
|
|
498
|
-
return void 0;
|
|
499
|
-
}
|
|
500
|
-
return Object.fromEntries(
|
|
501
|
-
commands.map((command) => [
|
|
502
|
-
command.name,
|
|
503
|
-
{
|
|
504
|
-
template: command.template,
|
|
505
|
-
...command.description ? { description: command.description } : {},
|
|
506
|
-
...command.agent ? { agent: command.agent } : {},
|
|
507
|
-
...command.model ? { model: command.model } : {},
|
|
508
|
-
...command.subtask !== void 0 ? { subtask: command.subtask } : {}
|
|
509
|
-
}
|
|
510
|
-
])
|
|
511
|
-
);
|
|
512
|
-
}
|
|
513
|
-
function assertCommandsSupported(provider, commands) {
|
|
514
|
-
if (!commands || commands.length === 0) {
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
if (provider === "codex") {
|
|
518
|
-
throw new Error(
|
|
519
|
-
"Custom commands are not supported for Codex in this package yet."
|
|
520
|
-
);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// src/agents/config/hooks.ts
|
|
525
|
-
import path3 from "path";
|
|
526
|
-
function hasHookEntries(hooks) {
|
|
527
|
-
if (!hooks || typeof hooks !== "object") {
|
|
528
|
-
return false;
|
|
529
|
-
}
|
|
530
|
-
return Object.values(hooks).some(
|
|
531
|
-
(groups) => Array.isArray(groups) && groups.length > 0
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
function readTopLevelHooks(options) {
|
|
535
|
-
if (!options || typeof options !== "object" || !("hooks" in options)) {
|
|
536
|
-
return void 0;
|
|
537
|
-
}
|
|
538
|
-
return options.hooks;
|
|
539
|
-
}
|
|
540
|
-
function readProviderHooks(options) {
|
|
541
|
-
if (!options || typeof options !== "object" || !("provider" in options)) {
|
|
542
|
-
return void 0;
|
|
543
|
-
}
|
|
544
|
-
return options.provider?.hooks;
|
|
545
|
-
}
|
|
546
|
-
function readProviderPlugins(options) {
|
|
547
|
-
if (!options || typeof options !== "object" || !("provider" in options)) {
|
|
548
|
-
return void 0;
|
|
549
|
-
}
|
|
550
|
-
return options.provider?.plugins;
|
|
551
|
-
}
|
|
552
|
-
function legacySharedHooksError(provider) {
|
|
553
|
-
return provider === "opencode" ? "OpenCode hook plugins must be configured on options.provider.plugins. The shared options.hooks field was removed because hook semantics differ by provider." : `${provider === "claude-code" ? "Claude Code" : "Codex"} hooks must be configured on options.provider.hooks. The shared options.hooks field was removed because hook semantics differ by provider.`;
|
|
554
|
-
}
|
|
555
|
-
function invalidGroupedHooksShapeError(provider) {
|
|
556
|
-
return `${provider === "claude-code" ? "Claude Code" : "Codex"} hooks must use the native grouped hooks object shape under options.provider.hooks, with each event mapped to an array of matcher groups.`;
|
|
557
|
-
}
|
|
558
|
-
function hasMalformedGroupedHookEntries(hooks) {
|
|
559
|
-
return Object.values(hooks).some(
|
|
560
|
-
(groups) => groups !== void 0 && !Array.isArray(groups)
|
|
561
|
-
);
|
|
562
|
-
}
|
|
563
|
-
function opencodeHooksFieldError() {
|
|
564
|
-
return "OpenCode uses options.provider.plugins for native hook support. options.provider.hooks is not supported for opencode.";
|
|
565
|
-
}
|
|
566
|
-
function unexpectedPluginsFieldError(provider) {
|
|
567
|
-
return `OpenCode plugins are only supported for the opencode provider in this package. Configure them on options.provider.plugins for opencode; received plugin configuration for ${provider}.`;
|
|
568
|
-
}
|
|
569
|
-
function buildClaudeHookSettings(hooks) {
|
|
570
|
-
if (!hasHookEntries(hooks)) {
|
|
571
|
-
return void 0;
|
|
572
|
-
}
|
|
573
|
-
return { hooks };
|
|
574
|
-
}
|
|
575
|
-
function buildCodexHooksFile(hooks) {
|
|
576
|
-
if (!hasHookEntries(hooks)) {
|
|
577
|
-
return void 0;
|
|
578
|
-
}
|
|
579
|
-
return { hooks };
|
|
580
|
-
}
|
|
581
|
-
function toPluginFileName(name, extension) {
|
|
582
|
-
const base = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
583
|
-
return `${base || "agentbox-plugin"}.${extension}`;
|
|
584
|
-
}
|
|
585
|
-
function toPluginExportName(name) {
|
|
586
|
-
const base = name.replace(/[^a-zA-Z0-9]+/g, " ").trim();
|
|
587
|
-
const parts = base.split(/\s+/).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1));
|
|
588
|
-
const identifier = parts.join("") || "AgentBoxPlugin";
|
|
589
|
-
return /^[A-Za-z_]/.test(identifier) ? identifier : `AgentBoxPlugin${identifier}`;
|
|
590
|
-
}
|
|
591
|
-
function indentBlock(input, spaces) {
|
|
592
|
-
const indent = " ".repeat(spaces);
|
|
593
|
-
return input.split("\n").map((line) => `${indent}${line}`).join("\n");
|
|
594
|
-
}
|
|
595
|
-
function renderOpenCodePluginHook(hook) {
|
|
596
|
-
const body = hook.body.trim().length > 0 ? hook.body.trim() : "return undefined;";
|
|
597
|
-
return [
|
|
598
|
-
` ${JSON.stringify(hook.event)}: async (input, output) => {`,
|
|
599
|
-
indentBlock(body, 6),
|
|
600
|
-
" },"
|
|
601
|
-
].join("\n");
|
|
602
|
-
}
|
|
603
|
-
function buildOpenCodePluginSource(plugin) {
|
|
604
|
-
const exportName = toPluginExportName(plugin.name);
|
|
605
|
-
const setup = plugin.setup?.trim();
|
|
606
|
-
const preamble = plugin.preamble?.trim();
|
|
607
|
-
return [
|
|
608
|
-
...preamble ? [preamble, ""] : [],
|
|
609
|
-
`export const ${exportName} = async (ctx) => {`,
|
|
610
|
-
...setup ? [indentBlock(setup, 2)] : [],
|
|
611
|
-
" return {",
|
|
612
|
-
...plugin.hooks.map((hook) => renderOpenCodePluginHook(hook)),
|
|
613
|
-
" };",
|
|
614
|
-
"};",
|
|
615
|
-
"",
|
|
616
|
-
`export default ${exportName};`,
|
|
617
|
-
""
|
|
618
|
-
].join("\n");
|
|
619
|
-
}
|
|
620
|
-
function buildOpenCodePluginArtifacts(plugins, opencodeDir) {
|
|
621
|
-
if (!plugins || plugins.length === 0) {
|
|
622
|
-
return [];
|
|
623
|
-
}
|
|
624
|
-
const seenFileNames = /* @__PURE__ */ new Set();
|
|
625
|
-
return plugins.map((plugin) => {
|
|
626
|
-
const fileExtension = plugin.fileExtension ?? "ts";
|
|
627
|
-
const fileName = toPluginFileName(plugin.name, fileExtension);
|
|
628
|
-
if (seenFileNames.has(fileName)) {
|
|
629
|
-
throw new Error(
|
|
630
|
-
`OpenCode plugin names must be unique after normalization. Duplicate plugin file: ${fileName}`
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
|
-
seenFileNames.add(fileName);
|
|
634
|
-
return {
|
|
635
|
-
path: path3.join(opencodeDir, "plugins", fileName),
|
|
636
|
-
content: buildOpenCodePluginSource(plugin)
|
|
637
|
-
};
|
|
638
|
-
});
|
|
639
|
-
}
|
|
640
|
-
function assertHooksSupported(provider, options) {
|
|
641
|
-
const topLevelHooks = readTopLevelHooks(options);
|
|
642
|
-
if (topLevelHooks !== void 0) {
|
|
643
|
-
throw new Error(legacySharedHooksError(provider));
|
|
644
|
-
}
|
|
645
|
-
const providerHooks = readProviderHooks(options);
|
|
646
|
-
const providerPlugins = readProviderPlugins(options);
|
|
647
|
-
if (provider === "opencode") {
|
|
648
|
-
if (providerHooks !== void 0) {
|
|
649
|
-
throw new Error(opencodeHooksFieldError());
|
|
650
|
-
}
|
|
651
|
-
if (providerPlugins === void 0) {
|
|
652
|
-
return void 0;
|
|
653
|
-
}
|
|
654
|
-
if (!Array.isArray(providerPlugins)) {
|
|
655
|
-
throw new Error(
|
|
656
|
-
"OpenCode plugins must be configured as an array on options.provider.plugins."
|
|
657
|
-
);
|
|
658
|
-
}
|
|
659
|
-
return providerPlugins.length > 0 ? providerPlugins : void 0;
|
|
660
|
-
}
|
|
661
|
-
if (providerPlugins !== void 0) {
|
|
662
|
-
throw new Error(unexpectedPluginsFieldError(provider));
|
|
663
|
-
}
|
|
664
|
-
if (providerHooks === void 0) {
|
|
665
|
-
return void 0;
|
|
666
|
-
}
|
|
667
|
-
if (!providerHooks || typeof providerHooks !== "object" || Array.isArray(providerHooks)) {
|
|
668
|
-
throw new Error(invalidGroupedHooksShapeError(provider));
|
|
669
|
-
}
|
|
670
|
-
if (hasMalformedGroupedHookEntries(providerHooks)) {
|
|
671
|
-
throw new Error(invalidGroupedHooksShapeError(provider));
|
|
672
|
-
}
|
|
673
|
-
if (!hasHookEntries(providerHooks)) {
|
|
674
|
-
return void 0;
|
|
675
|
-
}
|
|
676
|
-
return providerHooks;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
// src/agents/config/mcp.ts
|
|
680
|
-
import path4 from "path";
|
|
681
|
-
var SAFE_TOML_KEY = /^[a-zA-Z0-9_-]+$/;
|
|
682
|
-
function assertSafeTomlKey(name, context) {
|
|
683
|
-
if (!SAFE_TOML_KEY.test(name)) {
|
|
684
|
-
throw new Error(
|
|
685
|
-
`${context} name ${JSON.stringify(name)} contains characters that are not safe for TOML keys. Use only alphanumeric characters, hyphens, and underscores.`
|
|
686
|
-
);
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
function tomlString(value) {
|
|
690
|
-
return JSON.stringify(value);
|
|
691
|
-
}
|
|
692
|
-
function tomlStringArray(values) {
|
|
693
|
-
return `[${values.map(tomlString).join(", ")}]`;
|
|
694
|
-
}
|
|
695
|
-
function buildClaudeMcpConfig(mcps) {
|
|
696
|
-
if (!mcps || mcps.length === 0) {
|
|
697
|
-
return void 0;
|
|
698
|
-
}
|
|
699
|
-
const mcpServers = Object.fromEntries(
|
|
700
|
-
mcps.filter((mcp) => mcp.enabled !== false).map((mcp) => {
|
|
701
|
-
if (mcp.type === "remote") {
|
|
702
|
-
const headers = {
|
|
703
|
-
...mcp.headers ?? {},
|
|
704
|
-
...mcp.bearerTokenEnvVar ? {
|
|
705
|
-
Authorization: `Bearer \${${mcp.bearerTokenEnvVar}}`
|
|
706
|
-
} : {}
|
|
707
|
-
};
|
|
708
|
-
return [
|
|
709
|
-
mcp.name,
|
|
710
|
-
{
|
|
711
|
-
type: "http",
|
|
712
|
-
url: mcp.url,
|
|
713
|
-
...Object.keys(headers).length > 0 ? { headers } : {}
|
|
714
|
-
}
|
|
715
|
-
];
|
|
716
|
-
}
|
|
717
|
-
return [
|
|
718
|
-
mcp.name,
|
|
719
|
-
{
|
|
720
|
-
type: "stdio",
|
|
721
|
-
command: mcp.command,
|
|
722
|
-
...mcp.args?.length ? { args: mcp.args } : {},
|
|
723
|
-
...mcp.env ? { env: mcp.env } : {}
|
|
724
|
-
}
|
|
725
|
-
];
|
|
726
|
-
})
|
|
727
|
-
);
|
|
728
|
-
return JSON.stringify({ mcpServers }, null, 2);
|
|
729
|
-
}
|
|
730
|
-
function buildOpenCodeMcpConfig(mcps) {
|
|
731
|
-
if (!mcps || mcps.length === 0) {
|
|
732
|
-
return void 0;
|
|
733
|
-
}
|
|
734
|
-
return Object.fromEntries(
|
|
735
|
-
mcps.filter((mcp) => mcp.enabled !== false).map((mcp) => {
|
|
736
|
-
if (mcp.type === "remote") {
|
|
737
|
-
const headers = {
|
|
738
|
-
...mcp.headers ?? {},
|
|
739
|
-
...mcp.bearerTokenEnvVar ? { Authorization: `Bearer {env:${mcp.bearerTokenEnvVar}}` } : {}
|
|
740
|
-
};
|
|
741
|
-
return [
|
|
742
|
-
mcp.name,
|
|
743
|
-
{
|
|
744
|
-
type: "remote",
|
|
745
|
-
url: mcp.url,
|
|
746
|
-
enabled: true,
|
|
747
|
-
...Object.keys(headers).length > 0 ? { headers } : {}
|
|
748
|
-
}
|
|
749
|
-
];
|
|
750
|
-
}
|
|
751
|
-
return [
|
|
752
|
-
mcp.name,
|
|
753
|
-
{
|
|
754
|
-
type: "local",
|
|
755
|
-
command: [mcp.command, ...mcp.args ?? []],
|
|
756
|
-
enabled: true,
|
|
757
|
-
...mcp.env ? { env: mcp.env } : {}
|
|
758
|
-
}
|
|
759
|
-
];
|
|
760
|
-
})
|
|
761
|
-
);
|
|
762
|
-
}
|
|
763
|
-
function buildCodexConfigToml(mcps, agentSections = [], enableHooks = false) {
|
|
764
|
-
const blocks = [];
|
|
765
|
-
for (const mcp of mcps ?? []) {
|
|
766
|
-
if (mcp.enabled === false) {
|
|
767
|
-
continue;
|
|
768
|
-
}
|
|
769
|
-
assertSafeTomlKey(mcp.name, "MCP server");
|
|
770
|
-
if (mcp.type === "remote") {
|
|
771
|
-
if (mcp.headers && Object.keys(mcp.headers).length > 0) {
|
|
772
|
-
throw new Error(
|
|
773
|
-
`Codex only supports remote MCPs with bearerTokenEnvVar in this package. MCP "${mcp.name}" includes raw headers.`
|
|
774
|
-
);
|
|
775
|
-
}
|
|
776
|
-
blocks.push(`[mcp_servers.${mcp.name}]`);
|
|
777
|
-
blocks.push(`url = ${tomlString(mcp.url)}`);
|
|
778
|
-
if (mcp.bearerTokenEnvVar) {
|
|
779
|
-
blocks.push(
|
|
780
|
-
`bearer_token_env_var = ${tomlString(mcp.bearerTokenEnvVar)}`
|
|
781
|
-
);
|
|
782
|
-
}
|
|
783
|
-
blocks.push("");
|
|
784
|
-
continue;
|
|
785
|
-
}
|
|
786
|
-
blocks.push(`[mcp_servers.${mcp.name}]`);
|
|
787
|
-
blocks.push(`command = ${tomlString(mcp.command)}`);
|
|
788
|
-
if (mcp.args?.length) {
|
|
789
|
-
blocks.push(`args = ${tomlStringArray(mcp.args)}`);
|
|
790
|
-
}
|
|
791
|
-
if (mcp.env && Object.keys(mcp.env).length > 0) {
|
|
792
|
-
blocks.push(`env_vars = ${tomlStringArray(Object.keys(mcp.env))}`);
|
|
793
|
-
}
|
|
794
|
-
blocks.push("");
|
|
795
|
-
}
|
|
796
|
-
if (enableHooks) {
|
|
797
|
-
blocks.push("[features]");
|
|
798
|
-
blocks.push("codex_hooks = true");
|
|
799
|
-
blocks.push("");
|
|
800
|
-
}
|
|
801
|
-
blocks.push(...agentSections);
|
|
802
|
-
if (blocks.length === 0) {
|
|
803
|
-
return void 0;
|
|
804
|
-
}
|
|
805
|
-
return `${blocks.join("\n").trim()}
|
|
806
|
-
`;
|
|
807
|
-
}
|
|
808
|
-
function buildClaudeMcpArtifact(mcps, claudeDir) {
|
|
809
|
-
const content = buildClaudeMcpConfig(mcps);
|
|
810
|
-
if (!content) {
|
|
811
|
-
return void 0;
|
|
812
|
-
}
|
|
813
|
-
return {
|
|
814
|
-
path: path4.join(claudeDir, "agentbox-mcp.json"),
|
|
815
|
-
content
|
|
816
|
-
};
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
// src/agents/config/runtime.ts
|
|
820
|
-
import { mkdtemp, mkdir, chmod, rm, writeFile } from "fs/promises";
|
|
821
|
-
import os from "os";
|
|
822
|
-
import path5 from "path";
|
|
823
|
-
|
|
824
|
-
// src/agents/transports/spawn.ts
|
|
825
|
-
import { spawn } from "child_process";
|
|
826
|
-
import { createInterface } from "readline";
|
|
827
|
-
function spawnCommand(options) {
|
|
828
|
-
const child = spawn(options.command, options.args ?? [], {
|
|
829
|
-
cwd: options.cwd,
|
|
830
|
-
env: options.env,
|
|
831
|
-
stdio: "pipe",
|
|
832
|
-
shell: process.platform === "win32",
|
|
833
|
-
windowsHide: true
|
|
834
|
-
});
|
|
835
|
-
const exitPromise = new Promise((resolve, reject) => {
|
|
836
|
-
child.once("error", reject);
|
|
837
|
-
child.once("close", (code) => resolve(code ?? 0));
|
|
838
|
-
});
|
|
839
|
-
return {
|
|
840
|
-
child,
|
|
841
|
-
wait: () => exitPromise,
|
|
842
|
-
kill: async (signal = "SIGTERM") => {
|
|
843
|
-
child.kill(signal);
|
|
844
|
-
await exitPromise.catch(() => void 0);
|
|
845
|
-
}
|
|
846
|
-
};
|
|
847
|
-
}
|
|
848
|
-
async function waitForHttpReady(url, options) {
|
|
849
|
-
await waitFor(
|
|
850
|
-
async () => {
|
|
851
|
-
try {
|
|
852
|
-
const response = await fetch(url, options?.init);
|
|
853
|
-
return response.ok;
|
|
854
|
-
} catch {
|
|
855
|
-
return false;
|
|
856
|
-
}
|
|
857
|
-
},
|
|
858
|
-
{
|
|
859
|
-
timeoutMs: options?.timeoutMs,
|
|
860
|
-
intervalMs: options?.intervalMs
|
|
861
|
-
}
|
|
862
|
-
);
|
|
863
|
-
}
|
|
864
|
-
async function* linesFromNodeStream(stream) {
|
|
865
|
-
const lineReader = createInterface({ input: stream });
|
|
866
|
-
try {
|
|
867
|
-
for await (const line of lineReader) {
|
|
868
|
-
yield line;
|
|
869
|
-
}
|
|
870
|
-
} finally {
|
|
871
|
-
lineReader.close();
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
// src/agents/config/runtime.ts
|
|
876
|
-
function createLayout(homeDir) {
|
|
877
|
-
const xdgConfigHome = path5.join(homeDir, ".config");
|
|
878
|
-
const codexDir = path5.join(homeDir, ".codex");
|
|
879
|
-
return {
|
|
880
|
-
rootDir: homeDir,
|
|
881
|
-
homeDir,
|
|
882
|
-
xdgConfigHome,
|
|
883
|
-
agentsDir: path5.join(homeDir, ".agents"),
|
|
884
|
-
claudeDir: path5.join(homeDir, ".claude"),
|
|
885
|
-
opencodeDir: path5.join(xdgConfigHome, "opencode"),
|
|
886
|
-
codexDir
|
|
887
|
-
};
|
|
888
|
-
}
|
|
889
|
-
var HostRuntimeTarget = class {
|
|
890
|
-
constructor(provider, layout, cwd, baseEnv) {
|
|
891
|
-
this.provider = provider;
|
|
892
|
-
this.layout = layout;
|
|
893
|
-
this.cwd = cwd;
|
|
894
|
-
this.baseEnv = baseEnv;
|
|
895
|
-
this.env = {};
|
|
896
|
-
}
|
|
897
|
-
provider;
|
|
898
|
-
layout;
|
|
899
|
-
cwd;
|
|
900
|
-
baseEnv;
|
|
901
|
-
env;
|
|
902
|
-
async writeArtifact(artifact) {
|
|
903
|
-
await mkdir(path5.dirname(artifact.path), { recursive: true });
|
|
904
|
-
await writeFile(artifact.path, artifact.content, "utf8");
|
|
905
|
-
if (artifact.executable) {
|
|
906
|
-
await chmod(artifact.path, 493);
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
async runCommand(command, extraEnv) {
|
|
910
|
-
const handle = spawnCommand({
|
|
911
|
-
command: process.env.SHELL || "sh",
|
|
912
|
-
args: ["-lc", command],
|
|
913
|
-
cwd: this.cwd,
|
|
914
|
-
env: {
|
|
915
|
-
...process.env,
|
|
916
|
-
...this.baseEnv,
|
|
917
|
-
...this.env,
|
|
918
|
-
...extraEnv ?? {}
|
|
919
|
-
}
|
|
920
|
-
});
|
|
921
|
-
const exitCode = await handle.wait();
|
|
922
|
-
if (exitCode !== 0) {
|
|
923
|
-
throw new Error(`Setup command failed (${exitCode}): ${command}`);
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
async cleanup() {
|
|
927
|
-
await rm(this.layout.rootDir, { recursive: true, force: true });
|
|
928
|
-
}
|
|
929
|
-
};
|
|
930
|
-
var SandboxRuntimeTarget = class {
|
|
931
|
-
constructor(provider, layout, options) {
|
|
932
|
-
this.provider = provider;
|
|
933
|
-
this.layout = layout;
|
|
934
|
-
this.options = options;
|
|
935
|
-
this.env = {};
|
|
936
|
-
}
|
|
937
|
-
provider;
|
|
938
|
-
layout;
|
|
939
|
-
options;
|
|
940
|
-
env;
|
|
941
|
-
async writeArtifact(artifact) {
|
|
942
|
-
const marker = `__AGENTBOX_${Math.random().toString(36).slice(2)}__`;
|
|
943
|
-
const command = `mkdir -p ${shellQuote(
|
|
944
|
-
path5.posix.dirname(artifact.path)
|
|
945
|
-
)} && cat > ${shellQuote(artifact.path)} <<'${marker}'
|
|
946
|
-
${artifact.content}
|
|
947
|
-
${marker}`;
|
|
948
|
-
await this.options.sandbox?.run(command, {
|
|
949
|
-
cwd: this.options.cwd,
|
|
950
|
-
env: {
|
|
951
|
-
...this.options.env ?? {},
|
|
952
|
-
...this.env
|
|
953
|
-
}
|
|
954
|
-
});
|
|
955
|
-
if (artifact.executable) {
|
|
956
|
-
await this.options.sandbox?.run(`chmod +x ${shellQuote(artifact.path)}`, {
|
|
957
|
-
cwd: this.options.cwd,
|
|
958
|
-
env: {
|
|
959
|
-
...this.options.env ?? {},
|
|
960
|
-
...this.env
|
|
961
|
-
}
|
|
962
|
-
});
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
async runCommand(command, extraEnv) {
|
|
966
|
-
const result = await this.options.sandbox?.run(command, {
|
|
967
|
-
cwd: this.options.cwd,
|
|
968
|
-
env: {
|
|
969
|
-
...this.options.env ?? {},
|
|
970
|
-
...this.env,
|
|
971
|
-
...extraEnv ?? {}
|
|
972
|
-
}
|
|
973
|
-
});
|
|
974
|
-
if (result && result.exitCode !== 0) {
|
|
975
|
-
throw new Error(
|
|
976
|
-
`Sandbox setup command failed (${result.exitCode}): ${command}`
|
|
977
|
-
);
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
async cleanup() {
|
|
981
|
-
await this.options.sandbox?.run(
|
|
982
|
-
`rm -rf ${shellQuote(this.layout.rootDir)}`,
|
|
983
|
-
{
|
|
984
|
-
cwd: this.options.cwd,
|
|
985
|
-
env: {
|
|
986
|
-
...this.options.env ?? {},
|
|
987
|
-
...this.env
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
);
|
|
991
|
-
}
|
|
992
|
-
};
|
|
993
|
-
async function createRuntimeTarget(provider, runId, options) {
|
|
994
|
-
if (options.sandbox) {
|
|
995
|
-
const layout2 = createLayout(`/tmp/agentbox/${provider}/${runId}`);
|
|
996
|
-
await options.sandbox.run(
|
|
997
|
-
[
|
|
998
|
-
`mkdir -p ${shellQuote(layout2.homeDir)}`,
|
|
999
|
-
`mkdir -p ${shellQuote(layout2.xdgConfigHome)}`,
|
|
1000
|
-
`mkdir -p ${shellQuote(layout2.agentsDir)}`,
|
|
1001
|
-
`mkdir -p ${shellQuote(layout2.claudeDir)}`,
|
|
1002
|
-
`mkdir -p ${shellQuote(layout2.opencodeDir)}`,
|
|
1003
|
-
`mkdir -p ${shellQuote(layout2.codexDir)}`
|
|
1004
|
-
].join(" && "),
|
|
1005
|
-
{
|
|
1006
|
-
cwd: options.cwd,
|
|
1007
|
-
env: {
|
|
1008
|
-
...options.env ?? {}
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
);
|
|
1012
|
-
return new SandboxRuntimeTarget(provider, layout2, options);
|
|
1013
|
-
}
|
|
1014
|
-
const rootDir = await mkdtemp(
|
|
1015
|
-
path5.join(os.tmpdir(), `agentbox-${provider}-`)
|
|
1016
|
-
);
|
|
1017
|
-
const layout = createLayout(rootDir);
|
|
1018
|
-
await mkdir(layout.homeDir, { recursive: true });
|
|
1019
|
-
await mkdir(layout.xdgConfigHome, { recursive: true });
|
|
1020
|
-
await mkdir(layout.agentsDir, { recursive: true });
|
|
1021
|
-
await mkdir(layout.claudeDir, { recursive: true });
|
|
1022
|
-
await mkdir(layout.opencodeDir, { recursive: true });
|
|
1023
|
-
await mkdir(layout.codexDir, { recursive: true });
|
|
1024
|
-
return new HostRuntimeTarget(
|
|
1025
|
-
provider,
|
|
1026
|
-
layout,
|
|
1027
|
-
options.cwd ?? process.cwd(),
|
|
1028
|
-
options.env ?? {}
|
|
1029
|
-
);
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
// src/agents/config/skills.ts
|
|
1033
|
-
import path6 from "path";
|
|
1034
|
-
function getSkillTargetDir(provider, layout, skillName) {
|
|
1035
|
-
switch (provider) {
|
|
1036
|
-
case "claude-code":
|
|
1037
|
-
return path6.join(layout.claudeDir, "skills", skillName);
|
|
1038
|
-
case "opencode":
|
|
1039
|
-
return path6.join(layout.opencodeDir, "skills", skillName);
|
|
1040
|
-
case "codex":
|
|
1041
|
-
return path6.join(layout.agentsDir, "skills", skillName);
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
function buildSkillsInstallerCommand(provider, skill) {
|
|
1045
|
-
const repo = skill.repo ?? "https://github.com/anthropics/skills";
|
|
1046
|
-
const agent = provider === "claude-code" ? "claude-code" : provider;
|
|
1047
|
-
return `npx skills add ${shellQuote(repo)} -g --skill ${shellQuote(skill.name)} --agent ${shellQuote(agent)} -y`;
|
|
1048
|
-
}
|
|
1049
|
-
async function prepareSkillArtifacts(provider, skills, layout) {
|
|
1050
|
-
const artifacts = [];
|
|
1051
|
-
const installCommands = [];
|
|
1052
|
-
const preparedSkills = [];
|
|
1053
|
-
for (const skill of skills ?? []) {
|
|
1054
|
-
const targetDir = getSkillTargetDir(provider, layout, skill.name);
|
|
1055
|
-
const skillFilePath = path6.join(targetDir, "SKILL.md");
|
|
1056
|
-
if (!("files" in skill)) {
|
|
1057
|
-
installCommands.push(buildSkillsInstallerCommand(provider, skill));
|
|
1058
|
-
preparedSkills.push({ name: skill.name, skillFilePath });
|
|
1059
|
-
continue;
|
|
1060
|
-
}
|
|
1061
|
-
for (const [relativePath, content] of Object.entries(skill.files)) {
|
|
1062
|
-
artifacts.push({
|
|
1063
|
-
path: path6.join(targetDir, relativePath),
|
|
1064
|
-
content,
|
|
1065
|
-
executable: relativePath.startsWith("scripts/")
|
|
1066
|
-
});
|
|
1067
|
-
}
|
|
1068
|
-
preparedSkills.push({ name: skill.name, skillFilePath });
|
|
1069
|
-
}
|
|
1070
|
-
return {
|
|
1071
|
-
artifacts,
|
|
1072
|
-
installCommands,
|
|
1073
|
-
preparedSkills
|
|
1074
|
-
};
|
|
1075
|
-
}
|
|
1076
|
-
async function installSkills(target, installCommands, extraEnv) {
|
|
1077
|
-
for (const command of installCommands) {
|
|
1078
|
-
await target.runCommand(command, extraEnv);
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
// src/agents/config/subagents.ts
|
|
1083
|
-
import path7 from "path";
|
|
1084
|
-
function toToolsArray(tools) {
|
|
1085
|
-
const filtered = tools?.map((tool) => tool.trim()).filter(Boolean);
|
|
1086
|
-
return filtered && filtered.length > 0 ? filtered : void 0;
|
|
1087
|
-
}
|
|
1088
|
-
function tomlString2(value) {
|
|
1089
|
-
return JSON.stringify(value);
|
|
1090
|
-
}
|
|
1091
|
-
function buildClaudeAgentsConfig(subAgents) {
|
|
1092
|
-
if (!subAgents || subAgents.length === 0) {
|
|
1093
|
-
return void 0;
|
|
1094
|
-
}
|
|
1095
|
-
return Object.fromEntries(
|
|
1096
|
-
subAgents.map((subAgent) => [
|
|
1097
|
-
subAgent.name,
|
|
1098
|
-
{
|
|
1099
|
-
description: subAgent.description,
|
|
1100
|
-
prompt: subAgent.instructions,
|
|
1101
|
-
...subAgent.model ? { model: subAgent.model } : {},
|
|
1102
|
-
...toToolsArray(subAgent.tools) ? { tools: toToolsArray(subAgent.tools) } : {}
|
|
1103
|
-
}
|
|
1104
|
-
])
|
|
1105
|
-
);
|
|
1106
|
-
}
|
|
1107
|
-
function mapOpenCodeTools(tools) {
|
|
1108
|
-
if (!tools || tools.length === 0) {
|
|
1109
|
-
return void 0;
|
|
1110
|
-
}
|
|
1111
|
-
return Object.fromEntries(tools.map((tool) => [tool, true]));
|
|
1112
|
-
}
|
|
1113
|
-
function buildOpenCodeSubagentConfig(subAgents) {
|
|
1114
|
-
return Object.fromEntries(
|
|
1115
|
-
(subAgents ?? []).map((subAgent) => [
|
|
1116
|
-
subAgent.name,
|
|
1117
|
-
{
|
|
1118
|
-
mode: "subagent",
|
|
1119
|
-
description: subAgent.description,
|
|
1120
|
-
prompt: subAgent.instructions,
|
|
1121
|
-
...mapOpenCodeTools(subAgent.tools) ? { tools: mapOpenCodeTools(subAgent.tools) } : {}
|
|
1122
|
-
}
|
|
1123
|
-
])
|
|
1124
|
-
);
|
|
1125
|
-
}
|
|
1126
|
-
function buildCodexSubagentArtifacts(subAgents, layout) {
|
|
1127
|
-
const artifacts = [];
|
|
1128
|
-
const agentSections = [];
|
|
1129
|
-
for (const subAgent of subAgents ?? []) {
|
|
1130
|
-
if (subAgent.model) {
|
|
1131
|
-
throw new Error(
|
|
1132
|
-
`Codex sub-agent "${subAgent.name}" specifies a model override, which is not supported in this package yet.`
|
|
1133
|
-
);
|
|
1134
|
-
}
|
|
1135
|
-
const roleConfigRelativePath = `./agents/${subAgent.name}.toml`;
|
|
1136
|
-
const roleConfigPromptPath = `../prompts/${subAgent.name}.md`;
|
|
1137
|
-
artifacts.push({
|
|
1138
|
-
path: path7.join(layout.codexDir, "prompts", `${subAgent.name}.md`),
|
|
1139
|
-
content: subAgent.instructions
|
|
1140
|
-
});
|
|
1141
|
-
artifacts.push({
|
|
1142
|
-
path: path7.join(layout.codexDir, "agents", `${subAgent.name}.toml`),
|
|
1143
|
-
content: [
|
|
1144
|
-
`model_instructions_file = ${tomlString2(roleConfigPromptPath)}`,
|
|
1145
|
-
`model_reasoning_effort = ${tomlString2("medium")}`,
|
|
1146
|
-
"",
|
|
1147
|
-
"[features]",
|
|
1148
|
-
"multi_agent = false",
|
|
1149
|
-
""
|
|
1150
|
-
].join("\n")
|
|
1151
|
-
});
|
|
1152
|
-
agentSections.push(
|
|
1153
|
-
`[agents.${subAgent.name}]`,
|
|
1154
|
-
`description = ${tomlString2(subAgent.description)}`,
|
|
1155
|
-
`config_file = ${tomlString2(roleConfigRelativePath)}`,
|
|
1156
|
-
""
|
|
1157
|
-
);
|
|
1158
|
-
}
|
|
1159
|
-
return {
|
|
1160
|
-
artifacts,
|
|
1161
|
-
agentSections,
|
|
1162
|
-
enableMultiAgent: (subAgents?.length ?? 0) > 0
|
|
1163
|
-
};
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
// src/agents/transports/sdk-ws.ts
|
|
1167
|
-
import { randomUUID } from "crypto";
|
|
1168
|
-
import {
|
|
1169
|
-
WebSocket,
|
|
1170
|
-
WebSocketServer
|
|
1171
|
-
} from "ws";
|
|
1172
|
-
function handleIncomingMessage(text, messagesQueue, pendingResponses) {
|
|
1173
|
-
const lines = text.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
1174
|
-
for (const line of lines) {
|
|
1175
|
-
let message;
|
|
1176
|
-
try {
|
|
1177
|
-
message = JSON.parse(line);
|
|
1178
|
-
} catch {
|
|
1179
|
-
continue;
|
|
1180
|
-
}
|
|
1181
|
-
if (message.type === "control_response" && typeof message.response === "object" && message.response !== null) {
|
|
1182
|
-
const requestId = String(
|
|
1183
|
-
message.response.request_id ?? ""
|
|
1184
|
-
);
|
|
1185
|
-
const pending = pendingResponses.get(requestId);
|
|
1186
|
-
if (pending) {
|
|
1187
|
-
pendingResponses.delete(requestId);
|
|
1188
|
-
pending.resolve(message);
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
messagesQueue.push(message);
|
|
1192
|
-
}
|
|
1193
|
-
}
|
|
1194
|
-
var SdkWsServer = class {
|
|
1195
|
-
server;
|
|
1196
|
-
socket;
|
|
1197
|
-
messagesQueue = new AsyncQueue();
|
|
1198
|
-
pendingResponses = /* @__PURE__ */ new Map();
|
|
1199
|
-
host;
|
|
1200
|
-
port;
|
|
1201
|
-
constructor(options) {
|
|
1202
|
-
this.host = options?.host ?? "127.0.0.1";
|
|
1203
|
-
this.port = options?.port;
|
|
1204
|
-
}
|
|
1205
|
-
get url() {
|
|
1206
|
-
if (!this.port) {
|
|
1207
|
-
throw new Error("SDK WebSocket server has not been started yet.");
|
|
1208
|
-
}
|
|
1209
|
-
return `ws://${this.host}:${this.port}`;
|
|
1210
|
-
}
|
|
1211
|
-
async start() {
|
|
1212
|
-
if (this.server) {
|
|
1213
|
-
return;
|
|
1214
|
-
}
|
|
1215
|
-
this.port ??= await getAvailablePort(this.host);
|
|
1216
|
-
this.server = new WebSocketServer({ host: this.host, port: this.port });
|
|
1217
|
-
this.server.on("connection", (socket) => {
|
|
1218
|
-
this.socket = socket;
|
|
1219
|
-
socket.on("message", (data) => this.handleMessage(data));
|
|
1220
|
-
socket.on("close", () => {
|
|
1221
|
-
if (this.socket === socket) {
|
|
1222
|
-
this.socket = void 0;
|
|
1223
|
-
}
|
|
1224
|
-
});
|
|
1225
|
-
});
|
|
1226
|
-
this.server.on("error", (error) => {
|
|
1227
|
-
this.messagesQueue.fail(error);
|
|
1228
|
-
});
|
|
1229
|
-
}
|
|
1230
|
-
async waitForConnection(timeoutMs = 15e3) {
|
|
1231
|
-
await waitFor(async () => Boolean(this.socket), {
|
|
1232
|
-
timeoutMs,
|
|
1233
|
-
intervalMs: 100
|
|
1234
|
-
});
|
|
1235
|
-
}
|
|
1236
|
-
async send(message) {
|
|
1237
|
-
await this.waitForConnection();
|
|
1238
|
-
const socket = this.socket;
|
|
1239
|
-
if (!socket) {
|
|
1240
|
-
throw new Error("SDK WebSocket server has no active connection.");
|
|
1241
|
-
}
|
|
1242
|
-
await new Promise((resolve, reject) => {
|
|
1243
|
-
socket.send(`${JSON.stringify(message)}
|
|
1244
|
-
`, (error) => {
|
|
1245
|
-
if (error) {
|
|
1246
|
-
reject(error);
|
|
1247
|
-
return;
|
|
1248
|
-
}
|
|
1249
|
-
resolve();
|
|
1250
|
-
});
|
|
1251
|
-
});
|
|
1252
|
-
}
|
|
1253
|
-
async request(request) {
|
|
1254
|
-
const requestId = randomUUID();
|
|
1255
|
-
const response = new Promise((resolve, reject) => {
|
|
1256
|
-
this.pendingResponses.set(requestId, { resolve, reject });
|
|
1257
|
-
});
|
|
1258
|
-
await this.send({
|
|
1259
|
-
type: "control_request",
|
|
1260
|
-
request_id: requestId,
|
|
1261
|
-
request
|
|
1262
|
-
});
|
|
1263
|
-
return response;
|
|
1264
|
-
}
|
|
1265
|
-
async close() {
|
|
1266
|
-
this.messagesQueue.finish();
|
|
1267
|
-
for (const pending of this.pendingResponses.values()) {
|
|
1268
|
-
pending.reject(new Error("SDK WebSocket server closed."));
|
|
1269
|
-
}
|
|
1270
|
-
this.pendingResponses.clear();
|
|
1271
|
-
this.socket?.close();
|
|
1272
|
-
if (!this.server) {
|
|
1273
|
-
return;
|
|
1274
|
-
}
|
|
1275
|
-
await new Promise((resolve, reject) => {
|
|
1276
|
-
this.server?.close((error) => {
|
|
1277
|
-
if (error) {
|
|
1278
|
-
reject(error);
|
|
1279
|
-
return;
|
|
1280
|
-
}
|
|
1281
|
-
resolve();
|
|
1282
|
-
});
|
|
1283
|
-
});
|
|
1284
|
-
this.server = void 0;
|
|
1285
|
-
this.socket = void 0;
|
|
1286
|
-
}
|
|
1287
|
-
messages() {
|
|
1288
|
-
return this.messagesQueue;
|
|
1289
|
-
}
|
|
1290
|
-
handleMessage(rawData) {
|
|
1291
|
-
handleIncomingMessage(
|
|
1292
|
-
rawData.toString(),
|
|
1293
|
-
this.messagesQueue,
|
|
1294
|
-
this.pendingResponses
|
|
1295
|
-
);
|
|
1296
|
-
}
|
|
1297
|
-
};
|
|
1298
|
-
var SharedSdkWsChannel = class {
|
|
1299
|
-
constructor(connection, runId, onClose) {
|
|
1300
|
-
this.connection = connection;
|
|
1301
|
-
this.runId = runId;
|
|
1302
|
-
this.onClose = onClose;
|
|
1303
|
-
}
|
|
1304
|
-
connection;
|
|
1305
|
-
runId;
|
|
1306
|
-
onClose;
|
|
1307
|
-
messagesQueue = new AsyncQueue();
|
|
1308
|
-
pendingResponses = /* @__PURE__ */ new Map();
|
|
1309
|
-
closed = false;
|
|
1310
|
-
handleIncomingMessage(message) {
|
|
1311
|
-
if (this.closed) {
|
|
1312
|
-
return;
|
|
1313
|
-
}
|
|
1314
|
-
if (message.type === "control_response" && typeof message.response === "object" && message.response !== null) {
|
|
1315
|
-
const requestId = String(
|
|
1316
|
-
message.response.request_id ?? ""
|
|
1317
|
-
);
|
|
1318
|
-
const pending = this.pendingResponses.get(requestId);
|
|
1319
|
-
if (pending) {
|
|
1320
|
-
this.pendingResponses.delete(requestId);
|
|
1321
|
-
pending.resolve(message);
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
this.messagesQueue.push(message);
|
|
1325
|
-
}
|
|
1326
|
-
handleConnectionClosed(error) {
|
|
1327
|
-
if (this.closed) {
|
|
1328
|
-
return;
|
|
1329
|
-
}
|
|
1330
|
-
for (const pending of this.pendingResponses.values()) {
|
|
1331
|
-
pending.reject(error);
|
|
1332
|
-
}
|
|
1333
|
-
this.pendingResponses.clear();
|
|
1334
|
-
this.messagesQueue.fail(error);
|
|
1335
|
-
}
|
|
1336
|
-
async waitForConnection(timeoutMs = 15e3) {
|
|
1337
|
-
await this.connection.waitForConnection(timeoutMs);
|
|
1338
|
-
}
|
|
1339
|
-
async send(message) {
|
|
1340
|
-
await this.connection.sendToRun(this.runId, message);
|
|
1341
|
-
}
|
|
1342
|
-
async request(request) {
|
|
1343
|
-
const requestId = randomUUID();
|
|
1344
|
-
const response = new Promise((resolve, reject) => {
|
|
1345
|
-
this.pendingResponses.set(requestId, { resolve, reject });
|
|
1346
|
-
});
|
|
1347
|
-
await this.send({
|
|
1348
|
-
type: "control_request",
|
|
1349
|
-
request_id: requestId,
|
|
1350
|
-
request
|
|
1351
|
-
});
|
|
1352
|
-
return response;
|
|
1353
|
-
}
|
|
1354
|
-
async close() {
|
|
1355
|
-
if (this.closed) {
|
|
1356
|
-
return;
|
|
1357
|
-
}
|
|
1358
|
-
this.closed = true;
|
|
1359
|
-
for (const pending of this.pendingResponses.values()) {
|
|
1360
|
-
pending.reject(new Error("SDK WebSocket channel closed."));
|
|
1361
|
-
}
|
|
1362
|
-
this.pendingResponses.clear();
|
|
1363
|
-
this.messagesQueue.finish();
|
|
1364
|
-
this.onClose();
|
|
1365
|
-
}
|
|
1366
|
-
messages() {
|
|
1367
|
-
return this.messagesQueue;
|
|
1368
|
-
}
|
|
1369
|
-
};
|
|
1370
|
-
var MAX_PENDING_MESSAGES_PER_RUN = 1e3;
|
|
1371
|
-
var SharedSdkWsConnection = class {
|
|
1372
|
-
constructor(url) {
|
|
1373
|
-
this.url = url;
|
|
1374
|
-
}
|
|
1375
|
-
url;
|
|
1376
|
-
socket;
|
|
1377
|
-
channels = /* @__PURE__ */ new Map();
|
|
1378
|
-
pendingMessages = /* @__PURE__ */ new Map();
|
|
1379
|
-
async start() {
|
|
1380
|
-
if (this.socket?.readyState === WebSocket.OPEN) {
|
|
1381
|
-
return;
|
|
1382
|
-
}
|
|
1383
|
-
const socket = new WebSocket(this.url);
|
|
1384
|
-
this.socket = socket;
|
|
1385
|
-
socket.on("message", (data) => {
|
|
1386
|
-
const lines = data.toString().split("\n").map((line) => line.trim()).filter(Boolean);
|
|
1387
|
-
for (const line of lines) {
|
|
1388
|
-
try {
|
|
1389
|
-
const envelope = JSON.parse(line);
|
|
1390
|
-
if (!envelope.runId || !envelope.message) {
|
|
1391
|
-
continue;
|
|
1392
|
-
}
|
|
1393
|
-
const channel = this.channels.get(envelope.runId);
|
|
1394
|
-
if (channel) {
|
|
1395
|
-
channel.handleIncomingMessage(envelope.message);
|
|
1396
|
-
continue;
|
|
1397
|
-
}
|
|
1398
|
-
const pending = this.pendingMessages.get(envelope.runId) ?? [];
|
|
1399
|
-
if (pending.length < MAX_PENDING_MESSAGES_PER_RUN) {
|
|
1400
|
-
pending.push(envelope.message);
|
|
1401
|
-
this.pendingMessages.set(envelope.runId, pending);
|
|
1402
|
-
}
|
|
1403
|
-
} catch (error) {
|
|
1404
|
-
const failure = error instanceof Error ? error : new Error("Failed to parse shared SDK message.");
|
|
1405
|
-
for (const channel of this.channels.values()) {
|
|
1406
|
-
channel.handleConnectionClosed(failure);
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
}
|
|
1410
|
-
});
|
|
1411
|
-
socket.on("close", () => {
|
|
1412
|
-
if (this.socket === socket) {
|
|
1413
|
-
this.socket = void 0;
|
|
1414
|
-
}
|
|
1415
|
-
const error = new Error("Shared SDK WebSocket connection closed.");
|
|
1416
|
-
for (const channel of this.channels.values()) {
|
|
1417
|
-
channel.handleConnectionClosed(error);
|
|
1418
|
-
}
|
|
1419
|
-
});
|
|
1420
|
-
socket.on("error", (error) => {
|
|
1421
|
-
for (const channel of this.channels.values()) {
|
|
1422
|
-
channel.handleConnectionClosed(error);
|
|
1423
|
-
}
|
|
1424
|
-
});
|
|
1425
|
-
await new Promise((resolve, reject) => {
|
|
1426
|
-
const cleanup = () => {
|
|
1427
|
-
socket.off("open", handleOpen);
|
|
1428
|
-
socket.off("error", handleError);
|
|
1429
|
-
};
|
|
1430
|
-
const handleOpen = () => {
|
|
1431
|
-
cleanup();
|
|
1432
|
-
resolve();
|
|
1433
|
-
};
|
|
1434
|
-
const handleError = (error) => {
|
|
1435
|
-
cleanup();
|
|
1436
|
-
reject(error);
|
|
1437
|
-
};
|
|
1438
|
-
socket.once("open", handleOpen);
|
|
1439
|
-
socket.once("error", handleError);
|
|
1440
|
-
});
|
|
1441
|
-
}
|
|
1442
|
-
async waitForConnection(timeoutMs = 15e3) {
|
|
1443
|
-
await waitFor(async () => this.socket?.readyState === WebSocket.OPEN, {
|
|
1444
|
-
timeoutMs,
|
|
1445
|
-
intervalMs: 100
|
|
1446
|
-
});
|
|
1447
|
-
}
|
|
1448
|
-
createChannel(runId) {
|
|
1449
|
-
const existing = this.channels.get(runId);
|
|
1450
|
-
if (existing) {
|
|
1451
|
-
return existing;
|
|
1452
|
-
}
|
|
1453
|
-
const channel = new SharedSdkWsChannel(this, runId, () => {
|
|
1454
|
-
this.channels.delete(runId);
|
|
1455
|
-
});
|
|
1456
|
-
this.channels.set(runId, channel);
|
|
1457
|
-
const pending = this.pendingMessages.get(runId);
|
|
1458
|
-
if (pending?.length) {
|
|
1459
|
-
for (const message of pending) {
|
|
1460
|
-
channel.handleIncomingMessage(message);
|
|
1461
|
-
}
|
|
1462
|
-
this.pendingMessages.delete(runId);
|
|
1463
|
-
}
|
|
1464
|
-
return channel;
|
|
1465
|
-
}
|
|
1466
|
-
async sendToRun(runId, message) {
|
|
1467
|
-
await this.waitForConnection();
|
|
1468
|
-
const socket = this.socket;
|
|
1469
|
-
if (!socket) {
|
|
1470
|
-
throw new Error("Shared SDK WebSocket connection is not open.");
|
|
1471
|
-
}
|
|
1472
|
-
await new Promise((resolve, reject) => {
|
|
1473
|
-
socket.send(`${JSON.stringify({ runId, message })}
|
|
1474
|
-
`, (error) => {
|
|
1475
|
-
if (error) {
|
|
1476
|
-
reject(error);
|
|
1477
|
-
return;
|
|
1478
|
-
}
|
|
1479
|
-
resolve();
|
|
1480
|
-
});
|
|
1481
|
-
});
|
|
1482
|
-
}
|
|
1483
|
-
async close() {
|
|
1484
|
-
for (const channel of this.channels.values()) {
|
|
1485
|
-
await channel.close();
|
|
1486
|
-
}
|
|
1487
|
-
this.channels.clear();
|
|
1488
|
-
this.pendingMessages.clear();
|
|
1489
|
-
if (!this.socket) {
|
|
1490
|
-
return;
|
|
1491
|
-
}
|
|
1492
|
-
await new Promise((resolve) => {
|
|
1493
|
-
const socket = this.socket;
|
|
1494
|
-
this.socket = void 0;
|
|
1495
|
-
if (!socket) {
|
|
1496
|
-
resolve();
|
|
1497
|
-
return;
|
|
1498
|
-
}
|
|
1499
|
-
if (socket.readyState === WebSocket.CLOSED || socket.readyState === WebSocket.CLOSING) {
|
|
1500
|
-
resolve();
|
|
1501
|
-
return;
|
|
1502
|
-
}
|
|
1503
|
-
socket.once("close", () => resolve());
|
|
1504
|
-
socket.close();
|
|
1505
|
-
});
|
|
1506
|
-
}
|
|
1507
|
-
};
|
|
1508
|
-
|
|
1509
|
-
// src/agents/providers/claude-code.ts
|
|
1510
|
-
var REMOTE_SDK_RELAY_PORT = 43180;
|
|
1511
|
-
var REMOTE_SDK_RELAY_PATH = "/tmp/agentbox/claude-code/relay.mjs";
|
|
1512
|
-
var sharedRemoteConnectionBySandbox = /* @__PURE__ */ new WeakMap();
|
|
1513
|
-
function toRawEvent(runId, payload, type) {
|
|
1514
|
-
return {
|
|
1515
|
-
provider: "claude-code",
|
|
1516
|
-
runId,
|
|
1517
|
-
type,
|
|
1518
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1519
|
-
payload
|
|
1520
|
-
};
|
|
1521
|
-
}
|
|
1522
|
-
function extractAssistantText(message) {
|
|
1523
|
-
const content = message.message;
|
|
1524
|
-
if (Array.isArray(content)) {
|
|
1525
|
-
return content.filter((block) => block.type === "text").map((block) => String(block.text ?? "")).join("");
|
|
1526
|
-
}
|
|
1527
|
-
if (content && Array.isArray(content.content)) {
|
|
1528
|
-
return content.content.filter((block) => block.type === "text").map((block) => String(block.text ?? "")).join("");
|
|
1529
|
-
}
|
|
1530
|
-
return "";
|
|
1531
|
-
}
|
|
1532
|
-
function extractStreamDelta(message) {
|
|
1533
|
-
const event = message.event;
|
|
1534
|
-
if (!event) {
|
|
1535
|
-
return "";
|
|
1536
|
-
}
|
|
1537
|
-
const delta = event.delta;
|
|
1538
|
-
if (typeof delta?.text === "string") {
|
|
1539
|
-
return delta.text;
|
|
1540
|
-
}
|
|
1541
|
-
if (typeof event.text === "string") {
|
|
1542
|
-
return event.text;
|
|
1543
|
-
}
|
|
1544
|
-
return "";
|
|
1545
|
-
}
|
|
1546
|
-
function createClaudePermissionEvent(request, message) {
|
|
1547
|
-
const requestPayload = message.request ?? {};
|
|
1548
|
-
return createNormalizedEvent(
|
|
1549
|
-
"permission.requested",
|
|
1550
|
-
{
|
|
1551
|
-
provider: request.provider,
|
|
1552
|
-
runId: request.runId,
|
|
1553
|
-
raw: toRawEvent(request.runId, message, message.type)
|
|
1554
|
-
},
|
|
1555
|
-
{
|
|
1556
|
-
requestId: String(message.request_id ?? ""),
|
|
1557
|
-
kind: "tool",
|
|
1558
|
-
title: `Approve ${String(requestPayload.tool_name ?? "tool")} tool`,
|
|
1559
|
-
message: `Claude wants to use ${String(requestPayload.tool_name ?? "tool")}.`,
|
|
1560
|
-
input: requestPayload.input,
|
|
1561
|
-
canRemember: false
|
|
1562
|
-
}
|
|
1563
|
-
);
|
|
1564
|
-
}
|
|
1565
|
-
async function prepareClaudeRuntime(request) {
|
|
1566
|
-
const options = request.options;
|
|
1567
|
-
const provider = options.provider;
|
|
1568
|
-
const target = await createRuntimeTarget(
|
|
1569
|
-
request.provider,
|
|
1570
|
-
request.runId,
|
|
1571
|
-
options
|
|
1572
|
-
);
|
|
1573
|
-
const hooks = assertHooksSupported(request.provider, options);
|
|
1574
|
-
assertCommandsSupported(request.provider, options.commands);
|
|
1575
|
-
const { artifacts: skillArtifacts, installCommands } = await prepareSkillArtifacts(
|
|
1576
|
-
request.provider,
|
|
1577
|
-
options.skills,
|
|
1578
|
-
target.layout
|
|
1579
|
-
);
|
|
1580
|
-
const artifacts = [
|
|
1581
|
-
...skillArtifacts,
|
|
1582
|
-
...buildClaudeCommandArtifacts(options.commands, target.layout)
|
|
1583
|
-
];
|
|
1584
|
-
const mcpArtifact = buildClaudeMcpArtifact(
|
|
1585
|
-
options.mcps,
|
|
1586
|
-
target.layout.claudeDir
|
|
1587
|
-
);
|
|
1588
|
-
if (mcpArtifact) {
|
|
1589
|
-
artifacts.push(mcpArtifact);
|
|
1590
|
-
}
|
|
1591
|
-
const hookSettings = buildClaudeHookSettings(hooks);
|
|
1592
|
-
let settingsPath;
|
|
1593
|
-
if (hookSettings) {
|
|
1594
|
-
settingsPath = path8.join(target.layout.claudeDir, "settings.json");
|
|
1595
|
-
artifacts.push({
|
|
1596
|
-
path: settingsPath,
|
|
1597
|
-
content: JSON.stringify(hookSettings, null, 2)
|
|
1598
|
-
});
|
|
1599
|
-
}
|
|
1600
|
-
for (const artifact of artifacts) {
|
|
1601
|
-
await target.writeArtifact(artifact);
|
|
1602
|
-
}
|
|
1603
|
-
await installSkills(target, installCommands);
|
|
1604
|
-
const agents = buildClaudeAgentsConfig(options.subAgents);
|
|
1605
|
-
const initializeRequest = Object.keys({
|
|
1606
|
-
...request.run.systemPrompt ? { systemPrompt: request.run.systemPrompt } : {},
|
|
1607
|
-
...agents ? { agents } : {}
|
|
1608
|
-
}).length ? {
|
|
1609
|
-
subtype: "initialize",
|
|
1610
|
-
...request.run.systemPrompt ? { systemPrompt: request.run.systemPrompt } : {},
|
|
1611
|
-
...agents ? { agents } : {}
|
|
1612
|
-
} : void 0;
|
|
1613
|
-
const buildArgs = (sdkUrl) => [
|
|
1614
|
-
"--sdk-url",
|
|
1615
|
-
sdkUrl,
|
|
1616
|
-
"--print",
|
|
1617
|
-
"--output-format",
|
|
1618
|
-
"stream-json",
|
|
1619
|
-
"--input-format",
|
|
1620
|
-
"stream-json",
|
|
1621
|
-
...provider?.verbose ? ["--verbose"] : [],
|
|
1622
|
-
...request.run.model ? ["--model", request.run.model] : [],
|
|
1623
|
-
...provider?.permissionMode ? ["--permission-mode", provider.permissionMode] : [],
|
|
1624
|
-
...provider?.allowedTools?.length ? ["--allowedTools", provider.allowedTools.join(",")] : [],
|
|
1625
|
-
...request.run.resumeSessionId ? ["-r", request.run.resumeSessionId] : [],
|
|
1626
|
-
...settingsPath ? ["--settings", settingsPath] : [],
|
|
1627
|
-
...mcpArtifact ? ["--mcp-config", mcpArtifact.path] : [],
|
|
1628
|
-
...provider?.args ?? [],
|
|
1629
|
-
"-p",
|
|
1630
|
-
""
|
|
1631
|
-
];
|
|
1632
|
-
const env = {
|
|
1633
|
-
...options.env ?? {},
|
|
1634
|
-
...target.env
|
|
1635
|
-
};
|
|
1636
|
-
return {
|
|
1637
|
-
target,
|
|
1638
|
-
buildArgs,
|
|
1639
|
-
env,
|
|
1640
|
-
initializeRequest
|
|
1641
|
-
};
|
|
1642
|
-
}
|
|
1643
|
-
function createRemoteSdkRelayScript() {
|
|
1644
|
-
return `
|
|
1645
|
-
import crypto from "node:crypto";
|
|
1646
|
-
import http from "node:http";
|
|
1647
|
-
|
|
1648
|
-
const port = Number(process.argv[2] ?? "43180");
|
|
1649
|
-
const magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
1650
|
-
const channels = new Map();
|
|
1651
|
-
let hostSocket = null;
|
|
1652
|
-
|
|
1653
|
-
function getChannel(runId) {
|
|
1654
|
-
let channel = channels.get(runId);
|
|
1655
|
-
if (!channel) {
|
|
1656
|
-
channel = {
|
|
1657
|
-
claude: null,
|
|
1658
|
-
pending: {
|
|
1659
|
-
toHost: [],
|
|
1660
|
-
claude: [],
|
|
1661
|
-
},
|
|
1662
|
-
};
|
|
1663
|
-
channels.set(runId, channel);
|
|
1664
|
-
}
|
|
1665
|
-
return channel;
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
function sendFrame(socket, payload, opcode = 0x1) {
|
|
1669
|
-
const length = payload.length;
|
|
1670
|
-
let header;
|
|
1671
|
-
if (length < 126) {
|
|
1672
|
-
header = Buffer.alloc(2);
|
|
1673
|
-
header[1] = length;
|
|
1674
|
-
} else if (length < 65536) {
|
|
1675
|
-
header = Buffer.alloc(4);
|
|
1676
|
-
header[1] = 126;
|
|
1677
|
-
header.writeUInt16BE(length, 2);
|
|
1678
|
-
} else {
|
|
1679
|
-
header = Buffer.alloc(10);
|
|
1680
|
-
header[1] = 127;
|
|
1681
|
-
header.writeBigUInt64BE(BigInt(length), 2);
|
|
1682
|
-
}
|
|
1683
|
-
header[0] = 0x80 | opcode;
|
|
1684
|
-
socket.write(Buffer.concat([header, payload]));
|
|
1685
|
-
}
|
|
1686
|
-
|
|
1687
|
-
function parseFrame(buffer) {
|
|
1688
|
-
if (buffer.length < 2) {
|
|
1689
|
-
return null;
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
const first = buffer[0];
|
|
1693
|
-
const second = buffer[1];
|
|
1694
|
-
const fin = (first & 0x80) !== 0;
|
|
1695
|
-
const opcode = first & 0x0f;
|
|
1696
|
-
const masked = (second & 0x80) !== 0;
|
|
1697
|
-
let length = second & 0x7f;
|
|
1698
|
-
let offset = 2;
|
|
1699
|
-
|
|
1700
|
-
if (length === 126) {
|
|
1701
|
-
if (buffer.length < offset + 2) {
|
|
1702
|
-
return null;
|
|
1703
|
-
}
|
|
1704
|
-
length = buffer.readUInt16BE(offset);
|
|
1705
|
-
offset += 2;
|
|
1706
|
-
} else if (length === 127) {
|
|
1707
|
-
if (buffer.length < offset + 8) {
|
|
1708
|
-
return null;
|
|
1709
|
-
}
|
|
1710
|
-
length = Number(buffer.readBigUInt64BE(offset));
|
|
1711
|
-
offset += 8;
|
|
1712
|
-
}
|
|
1713
|
-
|
|
1714
|
-
let mask;
|
|
1715
|
-
if (masked) {
|
|
1716
|
-
if (buffer.length < offset + 4) {
|
|
1717
|
-
return null;
|
|
1718
|
-
}
|
|
1719
|
-
mask = buffer.subarray(offset, offset + 4);
|
|
1720
|
-
offset += 4;
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
if (buffer.length < offset + length) {
|
|
1724
|
-
return null;
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
let payload = buffer.subarray(offset, offset + length);
|
|
1728
|
-
if (mask) {
|
|
1729
|
-
payload = Buffer.from(payload);
|
|
1730
|
-
for (let index = 0; index < payload.length; index += 1) {
|
|
1731
|
-
payload[index] ^= mask[index % 4];
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
return {
|
|
1736
|
-
fin,
|
|
1737
|
-
opcode,
|
|
1738
|
-
payload,
|
|
1739
|
-
bytesUsed: offset + length,
|
|
1740
|
-
};
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
function sendHostEnvelope(runId, message) {
|
|
1744
|
-
if (!hostSocket) {
|
|
1745
|
-
return false;
|
|
1746
|
-
}
|
|
1747
|
-
sendFrame(
|
|
1748
|
-
hostSocket,
|
|
1749
|
-
Buffer.from(JSON.stringify({ runId, message }), "utf8"),
|
|
1750
|
-
);
|
|
1751
|
-
return true;
|
|
1752
|
-
}
|
|
1753
|
-
|
|
1754
|
-
function flushClaude(channel) {
|
|
1755
|
-
const socket = channel.claude;
|
|
1756
|
-
if (!socket) {
|
|
1757
|
-
return;
|
|
1758
|
-
}
|
|
1759
|
-
while (channel.pending.claude.length > 0) {
|
|
1760
|
-
sendFrame(socket, Buffer.from(channel.pending.claude.shift(), "utf8"));
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
function flushHostBacklog() {
|
|
1765
|
-
if (!hostSocket) {
|
|
1766
|
-
return;
|
|
1767
|
-
}
|
|
1768
|
-
for (const [runId, channel] of channels.entries()) {
|
|
1769
|
-
while (channel.pending.toHost.length > 0) {
|
|
1770
|
-
sendHostEnvelope(runId, channel.pending.toHost.shift());
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
}
|
|
1774
|
-
|
|
1775
|
-
function relayFromClaude(runId, message) {
|
|
1776
|
-
const channel = getChannel(runId);
|
|
1777
|
-
if (!sendHostEnvelope(runId, message)) {
|
|
1778
|
-
channel.pending.toHost.push(message);
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
function registerClaudePeer(socket, runId) {
|
|
1783
|
-
const channel = getChannel(runId);
|
|
1784
|
-
channel.claude = socket;
|
|
1785
|
-
flushClaude(channel);
|
|
1786
|
-
let buffer = Buffer.alloc(0);
|
|
1787
|
-
let fragments = [];
|
|
1788
|
-
|
|
1789
|
-
socket.on("data", (chunk) => {
|
|
1790
|
-
buffer = Buffer.concat([buffer, chunk]);
|
|
1791
|
-
|
|
1792
|
-
while (true) {
|
|
1793
|
-
const frame = parseFrame(buffer);
|
|
1794
|
-
if (!frame) {
|
|
1795
|
-
return;
|
|
1796
|
-
}
|
|
1797
|
-
buffer = buffer.subarray(frame.bytesUsed);
|
|
1798
|
-
|
|
1799
|
-
if (frame.opcode === 0x8) {
|
|
1800
|
-
socket.end();
|
|
1801
|
-
return;
|
|
1802
|
-
}
|
|
1803
|
-
if (frame.opcode === 0x9) {
|
|
1804
|
-
sendFrame(socket, frame.payload, 0xA);
|
|
1805
|
-
continue;
|
|
1806
|
-
}
|
|
1807
|
-
if (frame.opcode === 0xA) {
|
|
1808
|
-
continue;
|
|
1809
|
-
}
|
|
1810
|
-
if (frame.opcode === 0x1) {
|
|
1811
|
-
fragments = [frame.payload];
|
|
1812
|
-
} else if (frame.opcode === 0x0) {
|
|
1813
|
-
fragments.push(frame.payload);
|
|
1814
|
-
} else {
|
|
1815
|
-
continue;
|
|
1816
|
-
}
|
|
1817
|
-
|
|
1818
|
-
if (frame.fin) {
|
|
1819
|
-
const text = Buffer.concat(fragments).toString("utf8");
|
|
1820
|
-
for (const line of text
|
|
1821
|
-
.split("\\n")
|
|
1822
|
-
.map((value) => value.trim())
|
|
1823
|
-
.filter(Boolean)) {
|
|
1824
|
-
relayFromClaude(runId, JSON.parse(line));
|
|
1825
|
-
}
|
|
1826
|
-
fragments = [];
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
});
|
|
1830
|
-
|
|
1831
|
-
const clearPeer = () => {
|
|
1832
|
-
const latestChannel = channels.get(runId);
|
|
1833
|
-
if (!latestChannel) {
|
|
1834
|
-
return;
|
|
1835
|
-
}
|
|
1836
|
-
if (latestChannel.claude === socket) {
|
|
1837
|
-
latestChannel.claude = null;
|
|
1838
|
-
}
|
|
1839
|
-
if (
|
|
1840
|
-
latestChannel.claude === null &&
|
|
1841
|
-
latestChannel.pending.toHost.length === 0 &&
|
|
1842
|
-
latestChannel.pending.claude.length === 0
|
|
1843
|
-
) {
|
|
1844
|
-
channels.delete(runId);
|
|
1845
|
-
}
|
|
1846
|
-
};
|
|
1847
|
-
socket.on("close", clearPeer);
|
|
1848
|
-
socket.on("error", clearPeer);
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
function registerHostPeer(socket) {
|
|
1852
|
-
hostSocket = socket;
|
|
1853
|
-
flushHostBacklog();
|
|
1854
|
-
|
|
1855
|
-
let buffer = Buffer.alloc(0);
|
|
1856
|
-
let fragments = [];
|
|
1857
|
-
|
|
1858
|
-
socket.on("data", (chunk) => {
|
|
1859
|
-
buffer = Buffer.concat([buffer, chunk]);
|
|
1860
|
-
|
|
1861
|
-
while (true) {
|
|
1862
|
-
const frame = parseFrame(buffer);
|
|
1863
|
-
if (!frame) {
|
|
1864
|
-
return;
|
|
1865
|
-
}
|
|
1866
|
-
buffer = buffer.subarray(frame.bytesUsed);
|
|
1867
|
-
|
|
1868
|
-
if (frame.opcode === 0x8) {
|
|
1869
|
-
socket.end();
|
|
1870
|
-
return;
|
|
1871
|
-
}
|
|
1872
|
-
if (frame.opcode === 0x9) {
|
|
1873
|
-
sendFrame(socket, frame.payload, 0xA);
|
|
1874
|
-
continue;
|
|
1875
|
-
}
|
|
1876
|
-
if (frame.opcode === 0xA) {
|
|
1877
|
-
continue;
|
|
1878
|
-
}
|
|
1879
|
-
if (frame.opcode === 0x1) {
|
|
1880
|
-
fragments = [frame.payload];
|
|
1881
|
-
} else if (frame.opcode === 0x0) {
|
|
1882
|
-
fragments.push(frame.payload);
|
|
1883
|
-
} else {
|
|
1884
|
-
continue;
|
|
1885
|
-
}
|
|
1886
|
-
|
|
1887
|
-
if (frame.fin) {
|
|
1888
|
-
const envelope = JSON.parse(Buffer.concat(fragments).toString("utf8"));
|
|
1889
|
-
const runId = String(envelope.runId ?? "");
|
|
1890
|
-
const message = envelope.message;
|
|
1891
|
-
if (!runId || !message) {
|
|
1892
|
-
fragments = [];
|
|
1893
|
-
continue;
|
|
1894
|
-
}
|
|
1895
|
-
const channel = getChannel(runId);
|
|
1896
|
-
if (channel.claude) {
|
|
1897
|
-
sendFrame(
|
|
1898
|
-
channel.claude,
|
|
1899
|
-
Buffer.from(JSON.stringify(message) + "\\n", "utf8"),
|
|
1900
|
-
);
|
|
1901
|
-
} else {
|
|
1902
|
-
channel.pending.claude.push(JSON.stringify(message) + "\\n");
|
|
1903
|
-
}
|
|
1904
|
-
fragments = [];
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
});
|
|
1908
|
-
|
|
1909
|
-
const clearHost = () => {
|
|
1910
|
-
if (hostSocket === socket) {
|
|
1911
|
-
hostSocket = null;
|
|
1912
|
-
}
|
|
1913
|
-
};
|
|
1914
|
-
socket.on("close", clearHost);
|
|
1915
|
-
socket.on("error", clearHost);
|
|
1916
|
-
}
|
|
1917
|
-
|
|
1918
|
-
const server = http.createServer((_request, response) => {
|
|
1919
|
-
response.writeHead(426, { "content-type": "text/plain" });
|
|
1920
|
-
response.end("Upgrade Required");
|
|
1921
|
-
});
|
|
1922
|
-
|
|
1923
|
-
server.on("upgrade", (request, socket) => {
|
|
1924
|
-
if (request.headers.upgrade?.toLowerCase() !== "websocket") {
|
|
1925
|
-
socket.destroy();
|
|
1926
|
-
return;
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
const key = request.headers["sec-websocket-key"];
|
|
1930
|
-
if (typeof key !== "string") {
|
|
1931
|
-
socket.destroy();
|
|
1932
|
-
return;
|
|
1933
|
-
}
|
|
1934
|
-
|
|
1935
|
-
const accept = crypto
|
|
1936
|
-
.createHash("sha1")
|
|
1937
|
-
.update(key + magic)
|
|
1938
|
-
.digest("base64");
|
|
1939
|
-
socket.write(
|
|
1940
|
-
[
|
|
1941
|
-
"HTTP/1.1 101 Switching Protocols",
|
|
1942
|
-
"Upgrade: websocket",
|
|
1943
|
-
"Connection: Upgrade",
|
|
1944
|
-
"Sec-WebSocket-Accept: " + accept,
|
|
1945
|
-
"",
|
|
1946
|
-
"",
|
|
1947
|
-
].join("\\r\\n"),
|
|
1948
|
-
);
|
|
1949
|
-
|
|
1950
|
-
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
1951
|
-
const role = url.searchParams.get("role") === "host" ? "host" : "claude";
|
|
1952
|
-
if (role === "host") {
|
|
1953
|
-
registerHostPeer(socket);
|
|
1954
|
-
return;
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1957
|
-
const runId = url.searchParams.get("runId") ?? "default";
|
|
1958
|
-
registerClaudePeer(socket, runId);
|
|
1959
|
-
});
|
|
1960
|
-
|
|
1961
|
-
server.listen(port, "0.0.0.0");
|
|
1962
|
-
|
|
1963
|
-
const shutdown = () => {
|
|
1964
|
-
hostSocket?.destroy();
|
|
1965
|
-
for (const channel of channels.values()) {
|
|
1966
|
-
channel.claude?.destroy();
|
|
1967
|
-
}
|
|
1968
|
-
server.close(() => process.exit(0));
|
|
1969
|
-
};
|
|
1970
|
-
|
|
1971
|
-
process.on("SIGINT", shutdown);
|
|
1972
|
-
process.on("SIGTERM", shutdown);
|
|
1973
|
-
`.trimStart();
|
|
1974
|
-
}
|
|
1975
|
-
function toWebSocketUrl(url) {
|
|
1976
|
-
const parsed = new URL(url);
|
|
1977
|
-
parsed.protocol = parsed.protocol === "https:" ? "wss:" : "ws:";
|
|
1978
|
-
return parsed.toString();
|
|
1979
|
-
}
|
|
1980
|
-
function toSharedHostWebSocketUrl(url) {
|
|
1981
|
-
const parsed = new URL(toWebSocketUrl(url));
|
|
1982
|
-
parsed.searchParams.set("role", "host");
|
|
1983
|
-
parsed.searchParams.delete("runId");
|
|
1984
|
-
return parsed.toString();
|
|
1985
|
-
}
|
|
1986
|
-
function toClaudeRelayUrl(port, runId) {
|
|
1987
|
-
const parsed = new URL(`ws://127.0.0.1:${port}/`);
|
|
1988
|
-
parsed.searchParams.set("role", "claude");
|
|
1989
|
-
parsed.searchParams.set("runId", runId);
|
|
1990
|
-
return parsed.toString();
|
|
1991
|
-
}
|
|
1992
|
-
function buildLocalSdkUrl(server, sandboxProvider) {
|
|
1993
|
-
if (sandboxProvider === "local-docker") {
|
|
1994
|
-
return server.url.replace("127.0.0.1", "host.docker.internal").replace("0.0.0.0", "host.docker.internal");
|
|
1995
|
-
}
|
|
1996
|
-
return server.url;
|
|
1997
|
-
}
|
|
1998
|
-
async function connectRemoteTransport(url) {
|
|
1999
|
-
const startedAt = Date.now();
|
|
2000
|
-
let lastError;
|
|
2001
|
-
while (Date.now() - startedAt < 3e4) {
|
|
2002
|
-
const client = new SharedSdkWsConnection(url);
|
|
2003
|
-
try {
|
|
2004
|
-
await Promise.race([
|
|
2005
|
-
client.start(),
|
|
2006
|
-
sleep(2e3).then(() => {
|
|
2007
|
-
throw new Error(
|
|
2008
|
-
`Timed out connecting to remote SDK bridge at ${url}.`
|
|
2009
|
-
);
|
|
2010
|
-
})
|
|
2011
|
-
]);
|
|
2012
|
-
return client;
|
|
2013
|
-
} catch (error) {
|
|
2014
|
-
lastError = error;
|
|
2015
|
-
await client.close().catch(() => void 0);
|
|
2016
|
-
await sleep(250);
|
|
2017
|
-
}
|
|
2018
|
-
}
|
|
2019
|
-
throw lastError ?? new Error(`Could not connect to remote SDK bridge at ${url}.`);
|
|
2020
|
-
}
|
|
2021
|
-
async function canConnectToRemoteRelay(previewUrl) {
|
|
2022
|
-
const parsed = new URL(toWebSocketUrl(previewUrl));
|
|
2023
|
-
parsed.searchParams.set("role", "claude");
|
|
2024
|
-
parsed.searchParams.set("runId", "__probe__");
|
|
2025
|
-
const client = new SharedSdkWsConnection(parsed.toString());
|
|
2026
|
-
try {
|
|
2027
|
-
await Promise.race([
|
|
2028
|
-
client.start(),
|
|
2029
|
-
sleep(2e3).then(() => {
|
|
2030
|
-
throw new Error("Timed out connecting to remote relay.");
|
|
2031
|
-
})
|
|
2032
|
-
]);
|
|
2033
|
-
return true;
|
|
2034
|
-
} catch {
|
|
2035
|
-
return false;
|
|
2036
|
-
} finally {
|
|
2037
|
-
await client.close().catch(() => void 0);
|
|
2038
|
-
}
|
|
2039
|
-
}
|
|
2040
|
-
async function ensureSharedRemoteConnection(sandbox, previewUrl) {
|
|
2041
|
-
const key = sandbox;
|
|
2042
|
-
const existing = sharedRemoteConnectionBySandbox.get(key);
|
|
2043
|
-
if (existing) {
|
|
2044
|
-
try {
|
|
2045
|
-
const connection = await existing;
|
|
2046
|
-
await connection.connection.waitForConnection(1e3);
|
|
2047
|
-
return connection;
|
|
2048
|
-
} catch {
|
|
2049
|
-
try {
|
|
2050
|
-
const stale = await existing;
|
|
2051
|
-
await stale.connection.close().catch(() => void 0);
|
|
2052
|
-
} catch {
|
|
2053
|
-
}
|
|
2054
|
-
sharedRemoteConnectionBySandbox.delete(key);
|
|
2055
|
-
}
|
|
2056
|
-
}
|
|
2057
|
-
const created = (async () => {
|
|
2058
|
-
const url = toSharedHostWebSocketUrl(previewUrl);
|
|
2059
|
-
const connection = await connectRemoteTransport(url);
|
|
2060
|
-
return { previewUrl, connection };
|
|
2061
|
-
})();
|
|
2062
|
-
sharedRemoteConnectionBySandbox.set(key, created);
|
|
2063
|
-
try {
|
|
2064
|
-
return await created;
|
|
2065
|
-
} catch (error) {
|
|
2066
|
-
sharedRemoteConnectionBySandbox.delete(key);
|
|
2067
|
-
throw error;
|
|
2068
|
-
}
|
|
2069
|
-
}
|
|
2070
|
-
async function ensureRemoteRelay(request, prepared) {
|
|
2071
|
-
const sandbox = request.options.sandbox;
|
|
2072
|
-
await sandbox.openPort(REMOTE_SDK_RELAY_PORT);
|
|
2073
|
-
const previewUrl = await sandbox.getPreviewLink(REMOTE_SDK_RELAY_PORT);
|
|
2074
|
-
if (await canConnectToRemoteRelay(previewUrl)) {
|
|
2075
|
-
return {
|
|
2076
|
-
relayPort: REMOTE_SDK_RELAY_PORT,
|
|
2077
|
-
relayPath: REMOTE_SDK_RELAY_PATH,
|
|
2078
|
-
previewUrl
|
|
2079
|
-
};
|
|
2080
|
-
}
|
|
2081
|
-
await prepared.target.writeArtifact({
|
|
2082
|
-
path: REMOTE_SDK_RELAY_PATH,
|
|
2083
|
-
content: createRemoteSdkRelayScript()
|
|
2084
|
-
});
|
|
2085
|
-
const relayLogPath = "/tmp/agentbox/claude-code/relay.log";
|
|
2086
|
-
const relayHandle = await sandbox.runAsync(
|
|
2087
|
-
[
|
|
2088
|
-
`mkdir -p ${shellQuote(path8.posix.dirname(REMOTE_SDK_RELAY_PATH))}`,
|
|
2089
|
-
`mkdir -p ${shellQuote(path8.posix.dirname(relayLogPath))}`,
|
|
2090
|
-
`node ${shellQuote(REMOTE_SDK_RELAY_PATH)} ${shellQuote(String(REMOTE_SDK_RELAY_PORT))} > ${shellQuote(relayLogPath)} 2>&1`
|
|
2091
|
-
].join(" && "),
|
|
2092
|
-
{
|
|
2093
|
-
cwd: request.options.cwd,
|
|
2094
|
-
env: { ...prepared.env, IS_SANDBOX: "1" }
|
|
2095
|
-
}
|
|
2096
|
-
);
|
|
2097
|
-
let relayExit;
|
|
2098
|
-
void relayHandle.wait().then((result) => {
|
|
2099
|
-
relayExit = result;
|
|
2100
|
-
}).catch((error) => {
|
|
2101
|
-
relayExit = error;
|
|
2102
|
-
});
|
|
2103
|
-
const startedAt = Date.now();
|
|
2104
|
-
while (Date.now() - startedAt < 3e4) {
|
|
2105
|
-
if (await canConnectToRemoteRelay(previewUrl)) {
|
|
2106
|
-
return {
|
|
2107
|
-
relayPort: REMOTE_SDK_RELAY_PORT,
|
|
2108
|
-
relayPath: REMOTE_SDK_RELAY_PATH,
|
|
2109
|
-
previewUrl,
|
|
2110
|
-
handle: relayHandle
|
|
2111
|
-
};
|
|
2112
|
-
}
|
|
2113
|
-
if (relayExit !== void 0) {
|
|
2114
|
-
break;
|
|
2115
|
-
}
|
|
2116
|
-
await sleep(250);
|
|
2117
|
-
}
|
|
2118
|
-
await relayHandle.kill().catch(() => void 0);
|
|
2119
|
-
throw new Error(`Timed out waiting for Claude relay on ${previewUrl}.`);
|
|
2120
|
-
}
|
|
2121
|
-
async function createLocalRuntime(request, prepared) {
|
|
2122
|
-
const sandboxProvider = request.options.sandbox?.provider;
|
|
2123
|
-
const transport = new SdkWsServer({
|
|
2124
|
-
host: sandboxProvider === "local-docker" ? "0.0.0.0" : "127.0.0.1"
|
|
2125
|
-
});
|
|
2126
|
-
await transport.start();
|
|
2127
|
-
const args = prepared.buildArgs(buildLocalSdkUrl(transport, sandboxProvider));
|
|
2128
|
-
if (request.options.sandbox) {
|
|
2129
|
-
const handle = await request.options.sandbox.runAsync(
|
|
2130
|
-
[request.options.provider?.binary ?? "claude", ...args],
|
|
2131
|
-
{
|
|
2132
|
-
cwd: request.options.cwd,
|
|
2133
|
-
env: {
|
|
2134
|
-
...prepared.env
|
|
2135
|
-
},
|
|
2136
|
-
pty: true
|
|
2137
|
-
}
|
|
2138
|
-
);
|
|
2139
|
-
return {
|
|
2140
|
-
transport,
|
|
2141
|
-
cleanup: async () => {
|
|
2142
|
-
await handle.kill();
|
|
2143
|
-
await transport.close();
|
|
2144
|
-
await prepared.target.cleanup();
|
|
2145
|
-
},
|
|
2146
|
-
raw: { transport, handle, layout: prepared.target.layout },
|
|
2147
|
-
initializeRequest: prepared.initializeRequest
|
|
2148
|
-
};
|
|
2149
|
-
}
|
|
2150
|
-
const processHandle = spawnCommand({
|
|
2151
|
-
command: request.options.provider?.binary ?? "claude",
|
|
2152
|
-
args,
|
|
2153
|
-
cwd: request.options.cwd,
|
|
2154
|
-
env: {
|
|
2155
|
-
...process.env,
|
|
2156
|
-
...prepared.env
|
|
2157
|
-
}
|
|
2158
|
-
});
|
|
2159
|
-
return {
|
|
2160
|
-
transport,
|
|
2161
|
-
cleanup: async () => {
|
|
2162
|
-
await processHandle.kill();
|
|
2163
|
-
await transport.close();
|
|
2164
|
-
await prepared.target.cleanup();
|
|
2165
|
-
},
|
|
2166
|
-
raw: { transport, processHandle, layout: prepared.target.layout },
|
|
2167
|
-
initializeRequest: prepared.initializeRequest
|
|
2168
|
-
};
|
|
2169
|
-
}
|
|
2170
|
-
async function createRemoteSandboxRuntime(request, prepared) {
|
|
2171
|
-
const sandbox = request.options.sandbox;
|
|
2172
|
-
const relay = await ensureRemoteRelay(request, prepared);
|
|
2173
|
-
const args = prepared.buildArgs(
|
|
2174
|
-
toClaudeRelayUrl(relay.relayPort, request.runId)
|
|
2175
|
-
);
|
|
2176
|
-
const handle = await sandbox.runAsync(
|
|
2177
|
-
[request.options.provider?.binary ?? "claude", ...args],
|
|
2178
|
-
{
|
|
2179
|
-
cwd: request.options.cwd,
|
|
2180
|
-
env: { ...prepared.env, IS_SANDBOX: "1" },
|
|
2181
|
-
pty: true
|
|
2182
|
-
}
|
|
2183
|
-
);
|
|
2184
|
-
const sharedConnection = await ensureSharedRemoteConnection(
|
|
2185
|
-
sandbox,
|
|
2186
|
-
relay.previewUrl
|
|
2187
|
-
);
|
|
2188
|
-
const transport = sharedConnection.connection.createChannel(request.runId);
|
|
2189
|
-
return {
|
|
2190
|
-
transport,
|
|
2191
|
-
cleanup: async () => {
|
|
2192
|
-
await handle.kill().catch(() => void 0);
|
|
2193
|
-
await transport.close().catch(() => void 0);
|
|
2194
|
-
await prepared.target.cleanup();
|
|
2195
|
-
},
|
|
2196
|
-
raw: { transport, handle, relay, layout: prepared.target.layout },
|
|
2197
|
-
initializeRequest: prepared.initializeRequest
|
|
2198
|
-
};
|
|
2199
|
-
}
|
|
2200
|
-
async function createRuntime(request) {
|
|
2201
|
-
if (request.options.sandbox && request.options.sandbox.provider !== "local-docker") {
|
|
2202
|
-
await request.options.sandbox.openPort(REMOTE_SDK_RELAY_PORT);
|
|
2203
|
-
const prepared2 = await prepareClaudeRuntime(request);
|
|
2204
|
-
return createRemoteSandboxRuntime(request, prepared2);
|
|
2205
|
-
}
|
|
2206
|
-
const prepared = await prepareClaudeRuntime(request);
|
|
2207
|
-
return createLocalRuntime(request, prepared);
|
|
2208
|
-
}
|
|
2209
|
-
function buildDirectClaudeArgs(buildArgs, prompt) {
|
|
2210
|
-
const args = buildArgs("ws://127.0.0.1:9/");
|
|
2211
|
-
const directArgs = [];
|
|
2212
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
2213
|
-
const current = args[index];
|
|
2214
|
-
if (current === void 0) {
|
|
2215
|
-
continue;
|
|
2216
|
-
}
|
|
2217
|
-
if (current === "--sdk-url" || current === "--input-format") {
|
|
2218
|
-
index += 1;
|
|
2219
|
-
continue;
|
|
2220
|
-
}
|
|
2221
|
-
directArgs.push(current);
|
|
2222
|
-
}
|
|
2223
|
-
if (!directArgs.includes("--verbose")) {
|
|
2224
|
-
const printIndex = directArgs.indexOf("--print");
|
|
2225
|
-
if (printIndex === -1) {
|
|
2226
|
-
directArgs.unshift("--verbose");
|
|
2227
|
-
} else {
|
|
2228
|
-
directArgs.splice(printIndex + 1, 0, "--verbose");
|
|
2229
|
-
}
|
|
2230
|
-
}
|
|
2231
|
-
const promptIndex = directArgs.lastIndexOf("-p");
|
|
2232
|
-
if (promptIndex !== -1 && promptIndex + 1 < directArgs.length) {
|
|
2233
|
-
directArgs[promptIndex + 1] = prompt;
|
|
2234
|
-
} else {
|
|
2235
|
-
directArgs.push("-p", prompt);
|
|
2236
|
-
}
|
|
2237
|
-
return directArgs;
|
|
2238
|
-
}
|
|
2239
|
-
var ClaudeCodeAgentAdapter = class {
|
|
2240
|
-
async execute(request, sink) {
|
|
2241
|
-
const inputParts = await validateProviderUserInput(
|
|
2242
|
-
request.provider,
|
|
2243
|
-
request.run.input
|
|
2244
|
-
);
|
|
2245
|
-
const userContent = mapToClaudeUserContent(inputParts);
|
|
2246
|
-
if (request.options.sandbox?.provider === "e2b" && typeof userContent === "string" && !request.run.systemPrompt && !request.options.subAgents?.length) {
|
|
2247
|
-
const prepared = await prepareClaudeRuntime(request);
|
|
2248
|
-
const args = buildDirectClaudeArgs(prepared.buildArgs, userContent);
|
|
2249
|
-
const handle = await request.options.sandbox.runAsync(
|
|
2250
|
-
[request.options.provider?.binary ?? "claude", ...args],
|
|
2251
|
-
{
|
|
2252
|
-
cwd: request.options.cwd,
|
|
2253
|
-
env: {
|
|
2254
|
-
...prepared.env,
|
|
2255
|
-
IS_SANDBOX: "1"
|
|
2256
|
-
},
|
|
2257
|
-
timeoutMs: 0
|
|
2258
|
-
}
|
|
2259
|
-
);
|
|
2260
|
-
sink.setRaw({
|
|
2261
|
-
handle,
|
|
2262
|
-
layout: prepared.target.layout,
|
|
2263
|
-
mode: "e2b-direct"
|
|
2264
|
-
});
|
|
2265
|
-
sink.setAbort(async () => {
|
|
2266
|
-
await handle.kill().catch(() => void 0);
|
|
2267
|
-
await handle.wait().catch(() => void 0);
|
|
2268
|
-
await prepared.target.cleanup().catch(() => void 0);
|
|
2269
|
-
});
|
|
2270
|
-
sink.emitEvent(
|
|
2271
|
-
createNormalizedEvent("run.started", {
|
|
2272
|
-
provider: request.provider,
|
|
2273
|
-
runId: request.runId
|
|
2274
|
-
})
|
|
2275
|
-
);
|
|
2276
|
-
const completion2 = new Promise((resolve, reject) => {
|
|
2277
|
-
let accumulatedText2 = "";
|
|
2278
|
-
async function* stdoutChunks() {
|
|
2279
|
-
for await (const event of handle) {
|
|
2280
|
-
if (event.type === "stdout" && event.chunk) {
|
|
2281
|
-
yield event.chunk;
|
|
2282
|
-
}
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
void (async () => {
|
|
2286
|
-
for await (const line of linesFromTextChunks(stdoutChunks())) {
|
|
2287
|
-
const trimmed = line.trim();
|
|
2288
|
-
if (!trimmed) {
|
|
2289
|
-
continue;
|
|
2290
|
-
}
|
|
2291
|
-
let message;
|
|
2292
|
-
try {
|
|
2293
|
-
message = JSON.parse(trimmed);
|
|
2294
|
-
} catch {
|
|
2295
|
-
continue;
|
|
2296
|
-
}
|
|
2297
|
-
sink.emitRaw(toRawEvent(request.runId, message, message.type));
|
|
2298
|
-
if (message.type === "system" && message.subtype === "init") {
|
|
2299
|
-
const sessionId2 = String(message.session_id ?? "");
|
|
2300
|
-
if (sessionId2) {
|
|
2301
|
-
sink.setSessionId(sessionId2);
|
|
2302
|
-
}
|
|
2303
|
-
sink.emitEvent(
|
|
2304
|
-
createNormalizedEvent("message.started", {
|
|
2305
|
-
provider: request.provider,
|
|
2306
|
-
runId: request.runId
|
|
2307
|
-
})
|
|
2308
|
-
);
|
|
2309
|
-
continue;
|
|
2310
|
-
}
|
|
2311
|
-
if (message.type === "stream_event") {
|
|
2312
|
-
const delta = extractStreamDelta(message);
|
|
2313
|
-
if (delta) {
|
|
2314
|
-
accumulatedText2 += delta;
|
|
2315
|
-
sink.emitEvent(
|
|
2316
|
-
createNormalizedEvent(
|
|
2317
|
-
"text.delta",
|
|
2318
|
-
{
|
|
2319
|
-
provider: request.provider,
|
|
2320
|
-
runId: request.runId
|
|
2321
|
-
},
|
|
2322
|
-
{ delta }
|
|
2323
|
-
)
|
|
2324
|
-
);
|
|
2325
|
-
}
|
|
2326
|
-
continue;
|
|
2327
|
-
}
|
|
2328
|
-
if (message.type === "assistant") {
|
|
2329
|
-
const text = extractAssistantText(message);
|
|
2330
|
-
if (text) {
|
|
2331
|
-
accumulatedText2 = text;
|
|
2332
|
-
sink.emitEvent(
|
|
2333
|
-
createNormalizedEvent(
|
|
2334
|
-
"text.delta",
|
|
2335
|
-
{
|
|
2336
|
-
provider: request.provider,
|
|
2337
|
-
runId: request.runId
|
|
2338
|
-
},
|
|
2339
|
-
{ delta: text }
|
|
2340
|
-
)
|
|
2341
|
-
);
|
|
2342
|
-
}
|
|
2343
|
-
sink.emitEvent(
|
|
2344
|
-
createNormalizedEvent(
|
|
2345
|
-
"message.completed",
|
|
2346
|
-
{
|
|
2347
|
-
provider: request.provider,
|
|
2348
|
-
runId: request.runId
|
|
2349
|
-
},
|
|
2350
|
-
{ text }
|
|
2351
|
-
)
|
|
2352
|
-
);
|
|
2353
|
-
continue;
|
|
2354
|
-
}
|
|
2355
|
-
if (message.type === "result") {
|
|
2356
|
-
if (String(message.subtype ?? "success") === "success") {
|
|
2357
|
-
resolve({ text: accumulatedText2 });
|
|
2358
|
-
} else {
|
|
2359
|
-
reject(
|
|
2360
|
-
new Error(
|
|
2361
|
-
String(
|
|
2362
|
-
message.result ?? message.error ?? "Claude Code run failed."
|
|
2363
|
-
)
|
|
2364
|
-
)
|
|
2365
|
-
);
|
|
2366
|
-
}
|
|
2367
|
-
return;
|
|
2368
|
-
}
|
|
2369
|
-
if (message.type === "auth_status" && message.authenticated === false) {
|
|
2370
|
-
reject(
|
|
2371
|
-
new Error("Claude Code reported an authentication failure.")
|
|
2372
|
-
);
|
|
2373
|
-
return;
|
|
2374
|
-
}
|
|
2375
|
-
if (message.type === "control_request") {
|
|
2376
|
-
reject(
|
|
2377
|
-
new Error(
|
|
2378
|
-
"Claude Code direct E2B mode does not yet support interactive control requests."
|
|
2379
|
-
)
|
|
2380
|
-
);
|
|
2381
|
-
return;
|
|
2382
|
-
}
|
|
2383
|
-
}
|
|
2384
|
-
const result = await handle.wait().catch((error) => error);
|
|
2385
|
-
reject(
|
|
2386
|
-
new Error(
|
|
2387
|
-
result instanceof Error ? String(result) : "Claude Code direct E2B mode ended before returning a result."
|
|
2388
|
-
)
|
|
2389
|
-
);
|
|
2390
|
-
})().catch(reject);
|
|
2391
|
-
});
|
|
2392
|
-
try {
|
|
2393
|
-
const { text } = await completion2;
|
|
2394
|
-
await handle.wait().catch(() => void 0);
|
|
2395
|
-
sink.emitEvent(
|
|
2396
|
-
createNormalizedEvent(
|
|
2397
|
-
"run.completed",
|
|
2398
|
-
{ provider: request.provider, runId: request.runId },
|
|
2399
|
-
{ text }
|
|
2400
|
-
)
|
|
2401
|
-
);
|
|
2402
|
-
sink.complete({ text });
|
|
2403
|
-
} catch (error) {
|
|
2404
|
-
await handle.kill().catch(() => void 0);
|
|
2405
|
-
await handle.wait().catch(() => void 0);
|
|
2406
|
-
sink.fail(error);
|
|
2407
|
-
} finally {
|
|
2408
|
-
await prepared.target.cleanup().catch(() => void 0);
|
|
2409
|
-
}
|
|
2410
|
-
return async () => {
|
|
2411
|
-
await handle.kill().catch(() => void 0);
|
|
2412
|
-
await handle.wait().catch(() => void 0);
|
|
2413
|
-
};
|
|
2414
|
-
}
|
|
2415
|
-
const runtime = await createRuntime(request);
|
|
2416
|
-
sink.setRaw(runtime.raw);
|
|
2417
|
-
sink.setAbort(runtime.cleanup);
|
|
2418
|
-
sink.emitEvent(
|
|
2419
|
-
createNormalizedEvent("run.started", {
|
|
2420
|
-
provider: request.provider,
|
|
2421
|
-
runId: request.runId
|
|
2422
|
-
})
|
|
2423
|
-
);
|
|
2424
|
-
let sessionId = "";
|
|
2425
|
-
let accumulatedText = "";
|
|
2426
|
-
let usedStreaming = false;
|
|
2427
|
-
let pendingMessages = 1;
|
|
2428
|
-
const autoApproveTools = shouldAutoApproveClaudeTools(request.options);
|
|
2429
|
-
sink.onMessage(async (content) => {
|
|
2430
|
-
pendingMessages++;
|
|
2431
|
-
const parts = await validateProviderUserInput(request.provider, content);
|
|
2432
|
-
const mapped = mapToClaudeUserContent(parts);
|
|
2433
|
-
accumulatedText = "";
|
|
2434
|
-
usedStreaming = false;
|
|
2435
|
-
await runtime.transport.send({
|
|
2436
|
-
type: "user",
|
|
2437
|
-
message: { role: "user", content: mapped },
|
|
2438
|
-
parent_tool_use_id: null,
|
|
2439
|
-
session_id: sessionId || request.run.resumeSessionId || "",
|
|
2440
|
-
uuid: randomUUID2()
|
|
2441
|
-
});
|
|
2442
|
-
});
|
|
2443
|
-
const completion = new Promise((resolve, reject) => {
|
|
2444
|
-
void (async () => {
|
|
2445
|
-
for await (const message of runtime.transport.messages()) {
|
|
2446
|
-
sink.emitRaw(toRawEvent(request.runId, message, message.type));
|
|
2447
|
-
if (message.type === "system" && message.subtype === "init") {
|
|
2448
|
-
sessionId = String(message.session_id ?? "");
|
|
2449
|
-
if (sessionId) {
|
|
2450
|
-
sink.setSessionId(sessionId);
|
|
2451
|
-
}
|
|
2452
|
-
continue;
|
|
2453
|
-
}
|
|
2454
|
-
if (message.type === "control_request" && message.request?.subtype === "can_use_tool") {
|
|
2455
|
-
const requestId = String(message.request_id ?? "");
|
|
2456
|
-
const requestPayload = message.request;
|
|
2457
|
-
const response = autoApproveTools ? {
|
|
2458
|
-
requestId,
|
|
2459
|
-
decision: "allow"
|
|
2460
|
-
} : await sink.requestPermission(
|
|
2461
|
-
createClaudePermissionEvent(request, message)
|
|
2462
|
-
);
|
|
2463
|
-
if (response.decision === "allow") {
|
|
2464
|
-
sink.emitEvent(
|
|
2465
|
-
createNormalizedEvent(
|
|
2466
|
-
"tool.call.started",
|
|
2467
|
-
{
|
|
2468
|
-
provider: request.provider,
|
|
2469
|
-
runId: request.runId
|
|
2470
|
-
},
|
|
2471
|
-
{
|
|
2472
|
-
toolName: String(requestPayload.tool_name ?? "tool"),
|
|
2473
|
-
callId: requestId,
|
|
2474
|
-
input: requestPayload.input
|
|
2475
|
-
}
|
|
2476
|
-
)
|
|
2477
|
-
);
|
|
2478
|
-
}
|
|
2479
|
-
await runtime.transport.send({
|
|
2480
|
-
type: "control_response",
|
|
2481
|
-
response: {
|
|
2482
|
-
subtype: "success",
|
|
2483
|
-
request_id: requestId,
|
|
2484
|
-
response: response.decision === "allow" ? {
|
|
2485
|
-
behavior: "allow",
|
|
2486
|
-
updatedInput: requestPayload.input
|
|
2487
|
-
} : {
|
|
2488
|
-
behavior: "deny",
|
|
2489
|
-
message: "User denied this action."
|
|
2490
|
-
}
|
|
2491
|
-
}
|
|
2492
|
-
});
|
|
2493
|
-
continue;
|
|
2494
|
-
}
|
|
2495
|
-
if (message.type === "stream_event") {
|
|
2496
|
-
const delta = extractStreamDelta(message);
|
|
2497
|
-
if (delta) {
|
|
2498
|
-
usedStreaming = true;
|
|
2499
|
-
accumulatedText += delta;
|
|
2500
|
-
sink.emitEvent(
|
|
2501
|
-
createNormalizedEvent(
|
|
2502
|
-
"text.delta",
|
|
2503
|
-
{
|
|
2504
|
-
provider: request.provider,
|
|
2505
|
-
runId: request.runId
|
|
2506
|
-
},
|
|
2507
|
-
{
|
|
2508
|
-
delta
|
|
2509
|
-
}
|
|
2510
|
-
)
|
|
2511
|
-
);
|
|
2512
|
-
}
|
|
2513
|
-
continue;
|
|
2514
|
-
}
|
|
2515
|
-
if (message.type === "assistant") {
|
|
2516
|
-
const text = extractAssistantText(message);
|
|
2517
|
-
if (!usedStreaming && text) {
|
|
2518
|
-
accumulatedText = text;
|
|
2519
|
-
sink.emitEvent(
|
|
2520
|
-
createNormalizedEvent(
|
|
2521
|
-
"text.delta",
|
|
2522
|
-
{
|
|
2523
|
-
provider: request.provider,
|
|
2524
|
-
runId: request.runId
|
|
2525
|
-
},
|
|
2526
|
-
{
|
|
2527
|
-
delta: text
|
|
2528
|
-
}
|
|
2529
|
-
)
|
|
2530
|
-
);
|
|
2531
|
-
}
|
|
2532
|
-
sink.emitEvent(
|
|
2533
|
-
createNormalizedEvent(
|
|
2534
|
-
"message.completed",
|
|
2535
|
-
{
|
|
2536
|
-
provider: request.provider,
|
|
2537
|
-
runId: request.runId
|
|
2538
|
-
},
|
|
2539
|
-
{
|
|
2540
|
-
text
|
|
2541
|
-
}
|
|
2542
|
-
)
|
|
2543
|
-
);
|
|
2544
|
-
continue;
|
|
2545
|
-
}
|
|
2546
|
-
if (message.type === "result") {
|
|
2547
|
-
const subtype = String(message.subtype ?? "success");
|
|
2548
|
-
if (subtype === "success") {
|
|
2549
|
-
pendingMessages--;
|
|
2550
|
-
if (pendingMessages <= 0) {
|
|
2551
|
-
resolve({ text: accumulatedText });
|
|
2552
|
-
return;
|
|
2553
|
-
}
|
|
2554
|
-
continue;
|
|
2555
|
-
} else {
|
|
2556
|
-
reject(
|
|
2557
|
-
new Error(
|
|
2558
|
-
String(
|
|
2559
|
-
message.result ?? message.error ?? "Claude Code run failed."
|
|
2560
|
-
)
|
|
2561
|
-
)
|
|
2562
|
-
);
|
|
2563
|
-
}
|
|
2564
|
-
return;
|
|
2565
|
-
}
|
|
2566
|
-
if (message.type === "auth_status" && message.authenticated === false) {
|
|
2567
|
-
reject(
|
|
2568
|
-
new Error("Claude Code reported an authentication failure.")
|
|
2569
|
-
);
|
|
2570
|
-
return;
|
|
2571
|
-
}
|
|
2572
|
-
}
|
|
2573
|
-
reject(new Error("Claude Code transport closed before run completed."));
|
|
2574
|
-
})().catch(reject);
|
|
2575
|
-
});
|
|
2576
|
-
try {
|
|
2577
|
-
await runtime.transport.waitForConnection(3e4);
|
|
2578
|
-
if (runtime.initializeRequest) {
|
|
2579
|
-
const response = await runtime.transport.request(
|
|
2580
|
-
runtime.initializeRequest
|
|
2581
|
-
);
|
|
2582
|
-
sink.emitRaw(
|
|
2583
|
-
toRawEvent(request.runId, response, "control_response:initialize")
|
|
2584
|
-
);
|
|
2585
|
-
}
|
|
2586
|
-
await runtime.transport.send({
|
|
2587
|
-
type: "user",
|
|
2588
|
-
message: {
|
|
2589
|
-
role: "user",
|
|
2590
|
-
content: userContent
|
|
2591
|
-
},
|
|
2592
|
-
parent_tool_use_id: null,
|
|
2593
|
-
session_id: request.run.resumeSessionId ?? "",
|
|
2594
|
-
uuid: randomUUID2()
|
|
2595
|
-
});
|
|
2596
|
-
sink.emitEvent(
|
|
2597
|
-
createNormalizedEvent("message.started", {
|
|
2598
|
-
provider: request.provider,
|
|
2599
|
-
runId: request.runId
|
|
2600
|
-
})
|
|
2601
|
-
);
|
|
2602
|
-
const { text } = await completion;
|
|
2603
|
-
sink.emitEvent(
|
|
2604
|
-
createNormalizedEvent(
|
|
2605
|
-
"run.completed",
|
|
2606
|
-
{
|
|
2607
|
-
provider: request.provider,
|
|
2608
|
-
runId: request.runId
|
|
2609
|
-
},
|
|
2610
|
-
{
|
|
2611
|
-
text
|
|
2612
|
-
}
|
|
2613
|
-
)
|
|
2614
|
-
);
|
|
2615
|
-
sink.complete({ text });
|
|
2616
|
-
} finally {
|
|
2617
|
-
await runtime.cleanup().catch(() => void 0);
|
|
2618
|
-
}
|
|
2619
|
-
return async () => void 0;
|
|
2620
|
-
}
|
|
2621
|
-
};
|
|
2622
|
-
|
|
2623
|
-
// src/agents/providers/codex.ts
|
|
2624
|
-
import path9 from "path";
|
|
2625
|
-
|
|
2626
|
-
// src/agents/transports/app-server.ts
|
|
2627
|
-
import { createParser } from "eventsource-parser";
|
|
2628
|
-
import { WebSocket as WebSocket2 } from "ws";
|
|
2629
|
-
async function fetchJson(url, init) {
|
|
2630
|
-
const response = await fetch(url, init);
|
|
2631
|
-
if (!response.ok) {
|
|
2632
|
-
throw new Error(`Request to ${url} failed with ${response.status}.`);
|
|
2633
|
-
}
|
|
2634
|
-
return await response.json();
|
|
2635
|
-
}
|
|
2636
|
-
async function* streamSse(url, init) {
|
|
2637
|
-
const response = await fetch(url, init);
|
|
2638
|
-
if (!response.ok || !response.body) {
|
|
2639
|
-
throw new Error(`Could not open SSE stream at ${url}.`);
|
|
2640
|
-
}
|
|
2641
|
-
const queue = new AsyncQueue();
|
|
2642
|
-
const parser = createParser({
|
|
2643
|
-
onEvent(event) {
|
|
2644
|
-
queue.push({
|
|
2645
|
-
event: event.event || void 0,
|
|
2646
|
-
id: event.id || void 0,
|
|
2647
|
-
data: event.data
|
|
2648
|
-
});
|
|
2649
|
-
},
|
|
2650
|
-
onError(error) {
|
|
2651
|
-
queue.fail(error);
|
|
2652
|
-
}
|
|
2653
|
-
});
|
|
2654
|
-
const reader = response.body.getReader();
|
|
2655
|
-
const decoder = new TextDecoder();
|
|
2656
|
-
void (async () => {
|
|
2657
|
-
try {
|
|
2658
|
-
while (true) {
|
|
2659
|
-
const { done, value } = await reader.read();
|
|
2660
|
-
if (done) {
|
|
2661
|
-
break;
|
|
2662
|
-
}
|
|
2663
|
-
parser.feed(decoder.decode(value, { stream: true }));
|
|
2664
|
-
}
|
|
2665
|
-
parser.feed(decoder.decode());
|
|
2666
|
-
queue.finish();
|
|
2667
|
-
} catch (error) {
|
|
2668
|
-
queue.fail(error);
|
|
2669
|
-
} finally {
|
|
2670
|
-
reader.releaseLock();
|
|
2671
|
-
}
|
|
2672
|
-
})();
|
|
2673
|
-
yield* queue;
|
|
2674
|
-
}
|
|
2675
|
-
async function connectJsonRpcWebSocket(url) {
|
|
2676
|
-
const notifications = new AsyncQueue();
|
|
2677
|
-
const socket = new WebSocket2(url);
|
|
2678
|
-
await new Promise((resolve, reject) => {
|
|
2679
|
-
const cleanup = () => {
|
|
2680
|
-
socket.off("open", handleOpen);
|
|
2681
|
-
socket.off("error", handleError);
|
|
2682
|
-
};
|
|
2683
|
-
const handleOpen = () => {
|
|
2684
|
-
cleanup();
|
|
2685
|
-
resolve();
|
|
2686
|
-
};
|
|
2687
|
-
const handleError = (error) => {
|
|
2688
|
-
cleanup();
|
|
2689
|
-
reject(error);
|
|
2690
|
-
};
|
|
2691
|
-
socket.once("open", handleOpen);
|
|
2692
|
-
socket.once("error", handleError);
|
|
2693
|
-
});
|
|
2694
|
-
socket.on("message", (data) => {
|
|
2695
|
-
notifications.push(data.toString());
|
|
2696
|
-
});
|
|
2697
|
-
socket.on("close", () => {
|
|
2698
|
-
notifications.finish();
|
|
2699
|
-
});
|
|
2700
|
-
socket.on("error", (error) => {
|
|
2701
|
-
notifications.fail(error);
|
|
2702
|
-
});
|
|
2703
|
-
return {
|
|
2704
|
-
source: notifications,
|
|
2705
|
-
send: async (line) => {
|
|
2706
|
-
await new Promise((resolve, reject) => {
|
|
2707
|
-
socket.send(line, (error) => {
|
|
2708
|
-
if (error) {
|
|
2709
|
-
reject(error);
|
|
2710
|
-
return;
|
|
2711
|
-
}
|
|
2712
|
-
resolve();
|
|
2713
|
-
});
|
|
2714
|
-
});
|
|
2715
|
-
},
|
|
2716
|
-
close: async () => {
|
|
2717
|
-
await new Promise((resolve) => {
|
|
2718
|
-
if (socket.readyState === WebSocket2.CLOSED || socket.readyState === WebSocket2.CLOSING) {
|
|
2719
|
-
resolve();
|
|
2720
|
-
return;
|
|
2721
|
-
}
|
|
2722
|
-
socket.once("close", () => resolve());
|
|
2723
|
-
socket.close();
|
|
2724
|
-
});
|
|
2725
|
-
},
|
|
2726
|
-
raw: socket
|
|
2727
|
-
};
|
|
2728
|
-
}
|
|
2729
|
-
var JsonRpcLineClient = class {
|
|
2730
|
-
constructor(source, writeLine) {
|
|
2731
|
-
this.writeLine = writeLine;
|
|
2732
|
-
void this.consume(source);
|
|
2733
|
-
}
|
|
2734
|
-
writeLine;
|
|
2735
|
-
pending = /* @__PURE__ */ new Map();
|
|
2736
|
-
notifications = new AsyncQueue();
|
|
2737
|
-
nextId = 1;
|
|
2738
|
-
async request(method, params) {
|
|
2739
|
-
const id = this.nextId++;
|
|
2740
|
-
const response = new Promise((resolve, reject) => {
|
|
2741
|
-
this.pending.set(id, {
|
|
2742
|
-
resolve,
|
|
2743
|
-
reject
|
|
2744
|
-
});
|
|
2745
|
-
});
|
|
2746
|
-
await this.writeLine(JSON.stringify({ id, method, params }));
|
|
2747
|
-
return response;
|
|
2748
|
-
}
|
|
2749
|
-
async notify(method, params) {
|
|
2750
|
-
await this.writeLine(JSON.stringify({ method, params: params ?? {} }));
|
|
2751
|
-
}
|
|
2752
|
-
async respond(id, result) {
|
|
2753
|
-
await this.writeLine(JSON.stringify({ id, result }));
|
|
2754
|
-
}
|
|
2755
|
-
async respondError(id, error) {
|
|
2756
|
-
await this.writeLine(JSON.stringify({ id, error }));
|
|
2757
|
-
}
|
|
2758
|
-
async *messages() {
|
|
2759
|
-
yield* this.notifications;
|
|
2760
|
-
}
|
|
2761
|
-
async consume(source) {
|
|
2762
|
-
try {
|
|
2763
|
-
for await (const line of source) {
|
|
2764
|
-
if (!line.trim()) {
|
|
2765
|
-
continue;
|
|
2766
|
-
}
|
|
2767
|
-
const message = JSON.parse(line);
|
|
2768
|
-
if (typeof message.method === "string") {
|
|
2769
|
-
this.notifications.push(message);
|
|
2770
|
-
continue;
|
|
2771
|
-
}
|
|
2772
|
-
if (typeof message.id === "number") {
|
|
2773
|
-
const pending = this.pending.get(message.id);
|
|
2774
|
-
if (!pending) {
|
|
2775
|
-
continue;
|
|
2776
|
-
}
|
|
2777
|
-
this.pending.delete(message.id);
|
|
2778
|
-
if (message.error) {
|
|
2779
|
-
pending.reject(message.error);
|
|
2780
|
-
} else {
|
|
2781
|
-
pending.resolve(message.result);
|
|
2782
|
-
}
|
|
2783
|
-
continue;
|
|
2784
|
-
}
|
|
2785
|
-
}
|
|
2786
|
-
const closeError = new Error("JSON-RPC transport closed.");
|
|
2787
|
-
for (const pending of this.pending.values()) {
|
|
2788
|
-
pending.reject(closeError);
|
|
2789
|
-
}
|
|
2790
|
-
this.pending.clear();
|
|
2791
|
-
this.notifications.finish();
|
|
2792
|
-
} catch (error) {
|
|
2793
|
-
for (const pending of this.pending.values()) {
|
|
2794
|
-
pending.reject(error);
|
|
2795
|
-
}
|
|
2796
|
-
this.pending.clear();
|
|
2797
|
-
this.notifications.fail(error);
|
|
2798
|
-
}
|
|
2799
|
-
}
|
|
2800
|
-
};
|
|
2801
|
-
|
|
2802
|
-
// src/agents/providers/codex.ts
|
|
2803
|
-
var REMOTE_CODEX_APP_SERVER_PORT = 43181;
|
|
2804
|
-
var REMOTE_CODEX_APP_SERVER_ID = "shared-app-server";
|
|
2805
|
-
function compactEnv(values) {
|
|
2806
|
-
return Object.fromEntries(
|
|
2807
|
-
Object.entries(values).filter(([, value]) => value !== void 0)
|
|
2808
|
-
);
|
|
2809
|
-
}
|
|
2810
|
-
function buildCodexSandboxMode(options) {
|
|
2811
|
-
return options.sandbox ? "workspace-write" : "read-only";
|
|
2812
|
-
}
|
|
2813
|
-
function buildThreadParams(cwd, options, request) {
|
|
2814
|
-
return {
|
|
2815
|
-
cwd,
|
|
2816
|
-
model: request.run.model ?? null,
|
|
2817
|
-
approvalPolicy: isInteractiveApproval(options) ? "untrusted" : "never",
|
|
2818
|
-
sandbox: buildCodexSandboxMode(options),
|
|
2819
|
-
serviceName: "agentbox",
|
|
2820
|
-
ephemeral: true,
|
|
2821
|
-
experimentalRawEvents: true
|
|
2822
|
-
};
|
|
2823
|
-
}
|
|
2824
|
-
function buildResumeParams(cwd, options, request) {
|
|
2825
|
-
return {
|
|
2826
|
-
threadId: request.run.resumeSessionId,
|
|
2827
|
-
cwd,
|
|
2828
|
-
model: request.run.model ?? null,
|
|
2829
|
-
approvalPolicy: isInteractiveApproval(options) ? "untrusted" : "never",
|
|
2830
|
-
sandbox: buildCodexSandboxMode(options)
|
|
2831
|
-
};
|
|
2832
|
-
}
|
|
2833
|
-
function buildTurnSandboxPolicy(options) {
|
|
2834
|
-
if (!options.sandbox) {
|
|
2835
|
-
return void 0;
|
|
2836
|
-
}
|
|
2837
|
-
if (options.sandbox.provider === "local-docker") {
|
|
2838
|
-
return {
|
|
2839
|
-
type: "workspaceWrite",
|
|
2840
|
-
networkAccess: true
|
|
2841
|
-
};
|
|
2842
|
-
}
|
|
2843
|
-
return {
|
|
2844
|
-
type: "externalSandbox",
|
|
2845
|
-
networkAccess: "enabled"
|
|
2846
|
-
};
|
|
2847
|
-
}
|
|
2848
|
-
function buildTurnCollaborationMode(request) {
|
|
2849
|
-
if (!request.run.systemPrompt) {
|
|
2850
|
-
return void 0;
|
|
2851
|
-
}
|
|
2852
|
-
return {
|
|
2853
|
-
mode: "custom",
|
|
2854
|
-
settings: {
|
|
2855
|
-
developer_instructions: request.run.systemPrompt
|
|
2856
|
-
}
|
|
2857
|
-
};
|
|
2858
|
-
}
|
|
2859
|
-
function toRawEvent2(runId, payload, type) {
|
|
2860
|
-
return {
|
|
2861
|
-
provider: "codex",
|
|
2862
|
-
runId,
|
|
2863
|
-
type,
|
|
2864
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2865
|
-
payload
|
|
2866
|
-
};
|
|
2867
|
-
}
|
|
2868
|
-
function shouldIgnoreCodexError(notification) {
|
|
2869
|
-
if (notification.method !== "error") {
|
|
2870
|
-
return false;
|
|
2871
|
-
}
|
|
2872
|
-
return notification.params?.willRetry === true;
|
|
2873
|
-
}
|
|
2874
|
-
function buildCodexCommandArgs(binary, args) {
|
|
2875
|
-
return ["-u", "CODEX_HOME", "-u", "XDG_CONFIG_HOME", binary, ...args];
|
|
2876
|
-
}
|
|
2877
|
-
function toNormalizedCodexEvents(runId, notification) {
|
|
2878
|
-
const base = {
|
|
2879
|
-
provider: "codex",
|
|
2880
|
-
runId,
|
|
2881
|
-
raw: toRawEvent2(runId, notification, notification.method)
|
|
2882
|
-
};
|
|
2883
|
-
if (notification.method === "turn/started") {
|
|
2884
|
-
return [createNormalizedEvent("message.started", base)];
|
|
2885
|
-
}
|
|
2886
|
-
if (notification.method === "item/completed") {
|
|
2887
|
-
const item = notification.params?.item;
|
|
2888
|
-
if (!item) {
|
|
2889
|
-
return [];
|
|
2890
|
-
}
|
|
2891
|
-
if (item.type === "agentMessage" && typeof item.text === "string") {
|
|
2892
|
-
return [
|
|
2893
|
-
createNormalizedEvent("text.delta", base, { delta: item.text }),
|
|
2894
|
-
createNormalizedEvent("message.completed", base, { text: item.text })
|
|
2895
|
-
];
|
|
2896
|
-
}
|
|
2897
|
-
if (item.type === "reasoning" && item.summary) {
|
|
2898
|
-
return [
|
|
2899
|
-
createNormalizedEvent("reasoning.delta", base, {
|
|
2900
|
-
delta: typeof item.summary === "string" ? item.summary : JSON.stringify(item.summary)
|
|
2901
|
-
})
|
|
2902
|
-
];
|
|
2903
|
-
}
|
|
2904
|
-
if (item.type === "commandExecution" || item.type === "dynamicToolCall" || item.type === "mcpToolCall" || item.type === "webSearch") {
|
|
2905
|
-
return [
|
|
2906
|
-
createNormalizedEvent("tool.call.completed", base, {
|
|
2907
|
-
toolName: String(
|
|
2908
|
-
item.tool ?? item.command ?? item.server ?? item.query ?? item.type
|
|
2909
|
-
),
|
|
2910
|
-
callId: String(item.id ?? ""),
|
|
2911
|
-
output: item
|
|
2912
|
-
})
|
|
2913
|
-
];
|
|
2914
|
-
}
|
|
2915
|
-
}
|
|
2916
|
-
if (notification.method === "item/started") {
|
|
2917
|
-
const item = notification.params?.item;
|
|
2918
|
-
if (item && (item.type === "commandExecution" || item.type === "dynamicToolCall" || item.type === "mcpToolCall" || item.type === "webSearch")) {
|
|
2919
|
-
return [
|
|
2920
|
-
createNormalizedEvent("tool.call.started", base, {
|
|
2921
|
-
toolName: String(
|
|
2922
|
-
item.tool ?? item.command ?? item.server ?? item.query ?? item.type
|
|
2923
|
-
),
|
|
2924
|
-
callId: String(item.id ?? ""),
|
|
2925
|
-
input: item
|
|
2926
|
-
})
|
|
2927
|
-
];
|
|
2928
|
-
}
|
|
2929
|
-
}
|
|
2930
|
-
if (notification.method === "turn/completed") {
|
|
2931
|
-
const turn = notification.params?.turn;
|
|
2932
|
-
const text = typeof turn?.lastAgentMessage === "string" ? turn.lastAgentMessage : void 0;
|
|
2933
|
-
return [createNormalizedEvent("run.completed", base, { text })];
|
|
2934
|
-
}
|
|
2935
|
-
if (notification.method === "error") {
|
|
2936
|
-
const error = notification.params?.error;
|
|
2937
|
-
return [
|
|
2938
|
-
createNormalizedEvent("run.error", base, {
|
|
2939
|
-
error: String(error?.message ?? "Codex app-server error")
|
|
2940
|
-
})
|
|
2941
|
-
];
|
|
2942
|
-
}
|
|
2943
|
-
return [];
|
|
2944
|
-
}
|
|
2945
|
-
function createCodexPermissionEvent(request, notification) {
|
|
2946
|
-
const raw = toRawEvent2(request.runId, notification, notification.method);
|
|
2947
|
-
const params = notification.params;
|
|
2948
|
-
const requestId = notification.id;
|
|
2949
|
-
if (!params || requestId === void 0) {
|
|
2950
|
-
return null;
|
|
2951
|
-
}
|
|
2952
|
-
if (notification.method === "item/commandExecution/requestApproval") {
|
|
2953
|
-
const networkContext = params.networkApprovalContext;
|
|
2954
|
-
const availableDecisions = Array.isArray(params.availableDecisions) ? params.availableDecisions : [];
|
|
2955
|
-
const title = networkContext ? "Approve network access" : "Approve command execution";
|
|
2956
|
-
const message = typeof params.reason === "string" ? params.reason : typeof params.command === "string" ? params.command : void 0;
|
|
2957
|
-
return createNormalizedEvent(
|
|
2958
|
-
"permission.requested",
|
|
2959
|
-
{
|
|
2960
|
-
provider: request.provider,
|
|
2961
|
-
runId: request.runId,
|
|
2962
|
-
raw
|
|
2963
|
-
},
|
|
2964
|
-
{
|
|
2965
|
-
requestId: String(requestId),
|
|
2966
|
-
kind: networkContext ? "network" : "bash",
|
|
2967
|
-
title,
|
|
2968
|
-
message,
|
|
2969
|
-
input: params,
|
|
2970
|
-
canRemember: availableDecisions.includes("acceptForSession")
|
|
2971
|
-
}
|
|
2972
|
-
);
|
|
2973
|
-
}
|
|
2974
|
-
if (notification.method === "item/fileChange/requestApproval") {
|
|
2975
|
-
const availableDecisions = Array.isArray(params.availableDecisions) ? params.availableDecisions : [];
|
|
2976
|
-
return createNormalizedEvent(
|
|
2977
|
-
"permission.requested",
|
|
2978
|
-
{
|
|
2979
|
-
provider: request.provider,
|
|
2980
|
-
runId: request.runId,
|
|
2981
|
-
raw
|
|
2982
|
-
},
|
|
2983
|
-
{
|
|
2984
|
-
requestId: String(requestId),
|
|
2985
|
-
kind: "file-change",
|
|
2986
|
-
title: "Approve file changes",
|
|
2987
|
-
message: typeof params.reason === "string" ? params.reason : "Codex wants to modify files.",
|
|
2988
|
-
input: params,
|
|
2989
|
-
canRemember: availableDecisions.includes("acceptForSession")
|
|
2990
|
-
}
|
|
2991
|
-
);
|
|
2992
|
-
}
|
|
2993
|
-
return null;
|
|
2994
|
-
}
|
|
2995
|
-
function toCodexApprovalDecision(notification, response) {
|
|
2996
|
-
const params = notification.params ?? {};
|
|
2997
|
-
const availableDecisions = Array.isArray(params.availableDecisions) ? params.availableDecisions : [];
|
|
2998
|
-
const proposedExecpolicyAmendment = Array.isArray(
|
|
2999
|
-
params.proposedExecpolicyAmendment
|
|
3000
|
-
) ? params.proposedExecpolicyAmendment.filter(
|
|
3001
|
-
(part) => typeof part === "string"
|
|
3002
|
-
) : [];
|
|
3003
|
-
if (response.decision === "deny") {
|
|
3004
|
-
return availableDecisions.includes("decline") ? "decline" : "cancel";
|
|
3005
|
-
}
|
|
3006
|
-
if (response.remember && availableDecisions.includes("acceptForSession")) {
|
|
3007
|
-
return "acceptForSession";
|
|
3008
|
-
}
|
|
3009
|
-
if (proposedExecpolicyAmendment.length > 0 && availableDecisions.some(
|
|
3010
|
-
(decision) => typeof decision === "object" && decision !== null && "acceptWithExecpolicyAmendment" in decision
|
|
3011
|
-
)) {
|
|
3012
|
-
return {
|
|
3013
|
-
acceptWithExecpolicyAmendment: {
|
|
3014
|
-
execpolicy_amendment: proposedExecpolicyAmendment
|
|
3015
|
-
}
|
|
3016
|
-
};
|
|
3017
|
-
}
|
|
3018
|
-
return "accept";
|
|
3019
|
-
}
|
|
3020
|
-
function buildCodexSkillInputItems(skills) {
|
|
3021
|
-
return skills.map((skill) => ({
|
|
3022
|
-
type: "skill",
|
|
3023
|
-
name: skill.name,
|
|
3024
|
-
path: skill.skillFilePath
|
|
3025
|
-
}));
|
|
3026
|
-
}
|
|
3027
|
-
function buildCodexPromptText(prompt, skills) {
|
|
3028
|
-
if (skills.length === 0) {
|
|
3029
|
-
return prompt;
|
|
3030
|
-
}
|
|
3031
|
-
return [
|
|
3032
|
-
`Available skills for this run: ${skills.map((skill) => `$${skill.name}`).join(", ")}.`,
|
|
3033
|
-
prompt
|
|
3034
|
-
].join("\n\n");
|
|
3035
|
-
}
|
|
3036
|
-
function codexImageExtension(mediaType) {
|
|
3037
|
-
switch (mediaType) {
|
|
3038
|
-
case "image/gif":
|
|
3039
|
-
return ".gif";
|
|
3040
|
-
case "image/jpeg":
|
|
3041
|
-
return ".jpg";
|
|
3042
|
-
case "image/png":
|
|
3043
|
-
return ".png";
|
|
3044
|
-
case "image/webp":
|
|
3045
|
-
return ".webp";
|
|
3046
|
-
default:
|
|
3047
|
-
return ".img";
|
|
3048
|
-
}
|
|
3049
|
-
}
|
|
3050
|
-
async function materializeCodexImage(target, part, index) {
|
|
3051
|
-
if (part.source.type === "url") {
|
|
3052
|
-
return part.source.url;
|
|
3053
|
-
}
|
|
3054
|
-
const imagePath = path9.join(
|
|
3055
|
-
target.layout.rootDir,
|
|
3056
|
-
"inputs",
|
|
3057
|
-
`codex-image-${index}${codexImageExtension(part.mediaType)}`
|
|
3058
|
-
);
|
|
3059
|
-
const encodedPath = `${imagePath}.b64`;
|
|
3060
|
-
await target.writeArtifact({
|
|
3061
|
-
path: encodedPath,
|
|
3062
|
-
content: part.source.data
|
|
3063
|
-
});
|
|
3064
|
-
await target.runCommand(
|
|
3065
|
-
[
|
|
3066
|
-
`mkdir -p ${shellQuote(path9.posix.dirname(imagePath))}`,
|
|
3067
|
-
`(base64 --decode < ${shellQuote(encodedPath)} > ${shellQuote(imagePath)} || base64 -D < ${shellQuote(encodedPath)} > ${shellQuote(imagePath)})`,
|
|
3068
|
-
`rm -f ${shellQuote(encodedPath)}`
|
|
3069
|
-
].join(" && ")
|
|
3070
|
-
);
|
|
3071
|
-
return imagePath;
|
|
3072
|
-
}
|
|
3073
|
-
async function ensureCodexLogin(request, target) {
|
|
3074
|
-
const openAiApiKey = request.options.env?.OPENAI_API_KEY;
|
|
3075
|
-
if (!openAiApiKey) {
|
|
3076
|
-
return;
|
|
3077
|
-
}
|
|
3078
|
-
await target.runCommand(
|
|
3079
|
-
'if [ -z "$OPENAI_API_KEY" ]; then exit 1; fi; printenv OPENAI_API_KEY | env -u CODEX_HOME -u XDG_CONFIG_HOME codex login --with-api-key >/dev/null 2>&1',
|
|
3080
|
-
{
|
|
3081
|
-
OPENAI_API_KEY: openAiApiKey
|
|
3082
|
-
}
|
|
3083
|
-
);
|
|
3084
|
-
}
|
|
3085
|
-
function toRemoteCodexWebSocketUrl(url) {
|
|
3086
|
-
const parsed = new URL(url);
|
|
3087
|
-
parsed.protocol = parsed.protocol === "https:" ? "wss:" : "ws:";
|
|
3088
|
-
return parsed.toString();
|
|
3089
|
-
}
|
|
3090
|
-
async function connectRemoteCodexAppServer(url) {
|
|
3091
|
-
const startedAt = Date.now();
|
|
3092
|
-
let lastError;
|
|
3093
|
-
while (Date.now() - startedAt < 3e4) {
|
|
3094
|
-
try {
|
|
3095
|
-
return await connectJsonRpcWebSocket(url);
|
|
3096
|
-
} catch (error) {
|
|
3097
|
-
lastError = error;
|
|
3098
|
-
await sleep(250);
|
|
3099
|
-
}
|
|
3100
|
-
}
|
|
3101
|
-
throw lastError ?? new Error(`Could not connect to Codex app-server at ${url}.`);
|
|
3102
|
-
}
|
|
3103
|
-
async function waitForInternalCodexReady(sandbox, port, cwd, env) {
|
|
3104
|
-
const startedAt = Date.now();
|
|
3105
|
-
while (Date.now() - startedAt < 6e4) {
|
|
3106
|
-
const result = await sandbox.run(`curl -fsS http://127.0.0.1:${port}/readyz >/dev/null`, {
|
|
3107
|
-
cwd,
|
|
3108
|
-
env,
|
|
3109
|
-
timeoutMs: 5e3
|
|
3110
|
-
}).catch(() => void 0);
|
|
3111
|
-
if (result?.exitCode === 0) {
|
|
3112
|
-
return;
|
|
3113
|
-
}
|
|
3114
|
-
await sleep(250);
|
|
3115
|
-
}
|
|
3116
|
-
throw new Error(`Codex internal app-server did not become ready on ${port}.`);
|
|
3117
|
-
}
|
|
3118
|
-
async function createRuntime2(request, inputParts) {
|
|
3119
|
-
const options = request.options;
|
|
3120
|
-
const usesRemoteWebSocket = options.sandbox && options.sandbox.provider !== "local-docker";
|
|
3121
|
-
const hooks = assertHooksSupported(request.provider, options);
|
|
3122
|
-
assertCommandsSupported(request.provider, options.commands);
|
|
3123
|
-
if (usesRemoteWebSocket && options.sandbox) {
|
|
3124
|
-
const sandbox = options.sandbox;
|
|
3125
|
-
await sandbox.openPort(REMOTE_CODEX_APP_SERVER_PORT);
|
|
3126
|
-
const sharedTarget = await createRuntimeTarget(
|
|
3127
|
-
request.provider,
|
|
3128
|
-
REMOTE_CODEX_APP_SERVER_ID,
|
|
3129
|
-
options
|
|
3130
|
-
);
|
|
3131
|
-
await ensureCodexLogin(request, sharedTarget);
|
|
3132
|
-
const env2 = compactEnv({
|
|
3133
|
-
...options.env ?? {},
|
|
3134
|
-
...sharedTarget.env,
|
|
3135
|
-
...options.provider?.env ?? {}
|
|
3136
|
-
});
|
|
3137
|
-
const serverCwd = sharedTarget.layout.rootDir;
|
|
3138
|
-
const previewUrl = await sandbox.getPreviewLink(
|
|
3139
|
-
REMOTE_CODEX_APP_SERVER_PORT
|
|
3140
|
-
);
|
|
3141
|
-
const {
|
|
3142
|
-
artifacts: subAgentArtifacts2,
|
|
3143
|
-
agentSections: agentSections2,
|
|
3144
|
-
enableMultiAgent: enableMultiAgent2
|
|
3145
|
-
} = buildCodexSubagentArtifacts(options.subAgents, sharedTarget.layout);
|
|
3146
|
-
const serverArtifacts = [...subAgentArtifacts2];
|
|
3147
|
-
const hooksFile2 = buildCodexHooksFile(hooks);
|
|
3148
|
-
const configToml2 = buildCodexConfigToml(
|
|
3149
|
-
options.mcps,
|
|
3150
|
-
agentSections2,
|
|
3151
|
-
Boolean(hooksFile2)
|
|
3152
|
-
);
|
|
3153
|
-
if (configToml2) {
|
|
3154
|
-
serverArtifacts.push({
|
|
3155
|
-
path: path9.join(sharedTarget.layout.codexDir, "config.toml"),
|
|
3156
|
-
content: configToml2
|
|
3157
|
-
});
|
|
3158
|
-
}
|
|
3159
|
-
if (hooksFile2) {
|
|
3160
|
-
serverArtifacts.push({
|
|
3161
|
-
path: path9.join(sharedTarget.layout.codexDir, "hooks.json"),
|
|
3162
|
-
content: JSON.stringify(hooksFile2, null, 2)
|
|
3163
|
-
});
|
|
3164
|
-
}
|
|
3165
|
-
for (const artifact of serverArtifacts) {
|
|
3166
|
-
await sharedTarget.writeArtifact(artifact);
|
|
3167
|
-
}
|
|
3168
|
-
const configArgs2 = [];
|
|
3169
|
-
configArgs2.push("-c", `features.multi_agent=${enableMultiAgent2}`);
|
|
3170
|
-
const binary = options.provider?.binary ?? "codex";
|
|
3171
|
-
const pidFilePath = path9.posix.join(
|
|
3172
|
-
sharedTarget.layout.rootDir,
|
|
3173
|
-
"codex-app-server.pid"
|
|
3174
|
-
);
|
|
3175
|
-
const logFilePath = path9.posix.join(
|
|
3176
|
-
sharedTarget.layout.rootDir,
|
|
3177
|
-
"codex-app-server.log"
|
|
3178
|
-
);
|
|
3179
|
-
const launchResult = await sandbox.run(
|
|
3180
|
-
[
|
|
3181
|
-
`mkdir -p ${shellQuote(sharedTarget.layout.rootDir)}`,
|
|
3182
|
-
`if curl -fsS http://127.0.0.1:${REMOTE_CODEX_APP_SERVER_PORT}/readyz >/dev/null 2>&1; then exit 0; fi`,
|
|
3183
|
-
`if [ -f ${shellQuote(pidFilePath)} ]; then kill "$(cat ${shellQuote(pidFilePath)})" >/dev/null 2>&1 || true; rm -f ${shellQuote(pidFilePath)}; fi`,
|
|
3184
|
-
`(${[
|
|
3185
|
-
`nohup ${[
|
|
3186
|
-
"env",
|
|
3187
|
-
...buildCodexCommandArgs(binary, [
|
|
3188
|
-
...configArgs2,
|
|
3189
|
-
"app-server",
|
|
3190
|
-
"--listen",
|
|
3191
|
-
`ws://0.0.0.0:${REMOTE_CODEX_APP_SERVER_PORT}`
|
|
3192
|
-
])
|
|
3193
|
-
].map(shellQuote).join(" ")} > ${shellQuote(logFilePath)} 2>&1 &`,
|
|
3194
|
-
`echo $! > ${shellQuote(pidFilePath)}`
|
|
3195
|
-
].join(" ")})`
|
|
3196
|
-
].join(" && "),
|
|
3197
|
-
{
|
|
3198
|
-
cwd: serverCwd,
|
|
3199
|
-
env: env2
|
|
3200
|
-
}
|
|
3201
|
-
);
|
|
3202
|
-
if (launchResult.exitCode !== 0) {
|
|
3203
|
-
throw new Error(
|
|
3204
|
-
`Could not start Codex app-server: ${launchResult.combinedOutput || launchResult.stderr}`
|
|
3205
|
-
);
|
|
3206
|
-
}
|
|
3207
|
-
await waitForInternalCodexReady(
|
|
3208
|
-
sandbox,
|
|
3209
|
-
REMOTE_CODEX_APP_SERVER_PORT,
|
|
3210
|
-
serverCwd,
|
|
3211
|
-
env2
|
|
3212
|
-
);
|
|
3213
|
-
const target2 = await createRuntimeTarget(
|
|
3214
|
-
request.provider,
|
|
3215
|
-
request.runId,
|
|
3216
|
-
options
|
|
3217
|
-
);
|
|
3218
|
-
try {
|
|
3219
|
-
const {
|
|
3220
|
-
artifacts: skillArtifacts2,
|
|
3221
|
-
installCommands: installCommands2,
|
|
3222
|
-
preparedSkills: preparedSkills2
|
|
3223
|
-
} = await prepareSkillArtifacts(
|
|
3224
|
-
request.provider,
|
|
3225
|
-
options.skills,
|
|
3226
|
-
target2.layout
|
|
3227
|
-
);
|
|
3228
|
-
for (const artifact of skillArtifacts2) {
|
|
3229
|
-
await target2.writeArtifact(artifact);
|
|
3230
|
-
}
|
|
3231
|
-
await installSkills(target2, installCommands2);
|
|
3232
|
-
const textPrompt2 = joinTextParts(
|
|
3233
|
-
inputParts.filter(
|
|
3234
|
-
(part) => part.type === "text"
|
|
3235
|
-
)
|
|
3236
|
-
);
|
|
3237
|
-
const codexPromptText2 = buildCodexPromptText(textPrompt2, preparedSkills2);
|
|
3238
|
-
const inputItems2 = [];
|
|
3239
|
-
if (codexPromptText2.trim().length > 0) {
|
|
3240
|
-
inputItems2.push({
|
|
3241
|
-
type: "text",
|
|
3242
|
-
text: codexPromptText2,
|
|
3243
|
-
text_elements: []
|
|
3244
|
-
});
|
|
3245
|
-
}
|
|
3246
|
-
inputItems2.push(
|
|
3247
|
-
...await mapToCodexPromptParts(
|
|
3248
|
-
inputParts,
|
|
3249
|
-
async (part, index) => materializeCodexImage(target2, part, index)
|
|
3250
|
-
)
|
|
3251
|
-
);
|
|
3252
|
-
inputItems2.push(...buildCodexSkillInputItems(preparedSkills2));
|
|
3253
|
-
const transport = await connectRemoteCodexAppServer(
|
|
3254
|
-
toRemoteCodexWebSocketUrl(previewUrl)
|
|
3255
|
-
);
|
|
3256
|
-
return {
|
|
3257
|
-
source: transport.source,
|
|
3258
|
-
writeLine: transport.send,
|
|
3259
|
-
cleanup: async () => {
|
|
3260
|
-
await transport?.close().catch(() => void 0);
|
|
3261
|
-
await target2.cleanup().catch(() => void 0);
|
|
3262
|
-
},
|
|
3263
|
-
raw: {
|
|
3264
|
-
transport: transport.raw,
|
|
3265
|
-
previewUrl,
|
|
3266
|
-
port: REMOTE_CODEX_APP_SERVER_PORT,
|
|
3267
|
-
serverLayout: sharedTarget.layout,
|
|
3268
|
-
layout: target2.layout
|
|
3269
|
-
},
|
|
3270
|
-
inputItems: inputItems2,
|
|
3271
|
-
turnStartOverrides: buildTurnCollaborationMode(request)
|
|
3272
|
-
};
|
|
3273
|
-
} catch (error) {
|
|
3274
|
-
await target2.cleanup().catch(() => void 0);
|
|
3275
|
-
throw error;
|
|
3276
|
-
}
|
|
3277
|
-
}
|
|
3278
|
-
const target = await createRuntimeTarget(
|
|
3279
|
-
request.provider,
|
|
3280
|
-
request.runId,
|
|
3281
|
-
options
|
|
3282
|
-
);
|
|
3283
|
-
try {
|
|
3284
|
-
await ensureCodexLogin(request, target);
|
|
3285
|
-
} catch (error) {
|
|
3286
|
-
await target.cleanup().catch(() => void 0);
|
|
3287
|
-
throw error;
|
|
3288
|
-
}
|
|
3289
|
-
const env = compactEnv({
|
|
3290
|
-
...options.env ?? {},
|
|
3291
|
-
...target.env,
|
|
3292
|
-
...options.provider?.env ?? {}
|
|
3293
|
-
});
|
|
3294
|
-
const runtimeCwd = target.layout.rootDir;
|
|
3295
|
-
const {
|
|
3296
|
-
artifacts: skillArtifacts,
|
|
3297
|
-
installCommands,
|
|
3298
|
-
preparedSkills
|
|
3299
|
-
} = await prepareSkillArtifacts(
|
|
3300
|
-
request.provider,
|
|
3301
|
-
options.skills,
|
|
3302
|
-
target.layout
|
|
3303
|
-
);
|
|
3304
|
-
const {
|
|
3305
|
-
artifacts: subAgentArtifacts,
|
|
3306
|
-
agentSections,
|
|
3307
|
-
enableMultiAgent
|
|
3308
|
-
} = buildCodexSubagentArtifacts(options.subAgents, target.layout);
|
|
3309
|
-
const artifacts = [...skillArtifacts, ...subAgentArtifacts];
|
|
3310
|
-
const hooksFile = buildCodexHooksFile(hooks);
|
|
3311
|
-
const configToml = buildCodexConfigToml(
|
|
3312
|
-
options.mcps,
|
|
3313
|
-
agentSections,
|
|
3314
|
-
Boolean(hooksFile)
|
|
3315
|
-
);
|
|
3316
|
-
if (configToml) {
|
|
3317
|
-
artifacts.push({
|
|
3318
|
-
path: path9.join(target.layout.codexDir, "config.toml"),
|
|
3319
|
-
content: configToml
|
|
3320
|
-
});
|
|
3321
|
-
}
|
|
3322
|
-
if (hooksFile) {
|
|
3323
|
-
artifacts.push({
|
|
3324
|
-
path: path9.join(target.layout.codexDir, "hooks.json"),
|
|
3325
|
-
content: JSON.stringify(hooksFile, null, 2)
|
|
3326
|
-
});
|
|
3327
|
-
}
|
|
3328
|
-
let instructionsFilePath;
|
|
3329
|
-
if (request.run.systemPrompt) {
|
|
3330
|
-
instructionsFilePath = path9.join(
|
|
3331
|
-
target.layout.codexDir,
|
|
3332
|
-
"prompts",
|
|
3333
|
-
"agentbox-system.md"
|
|
3334
|
-
);
|
|
3335
|
-
artifacts.push({
|
|
3336
|
-
path: instructionsFilePath,
|
|
3337
|
-
content: request.run.systemPrompt
|
|
3338
|
-
});
|
|
3339
|
-
}
|
|
3340
|
-
for (const artifact of artifacts) {
|
|
3341
|
-
await target.writeArtifact(artifact);
|
|
3342
|
-
}
|
|
3343
|
-
await installSkills(target, installCommands);
|
|
3344
|
-
const configArgs = [];
|
|
3345
|
-
if (instructionsFilePath) {
|
|
3346
|
-
configArgs.push(
|
|
3347
|
-
"-c",
|
|
3348
|
-
`model_instructions_file=${JSON.stringify(instructionsFilePath)}`
|
|
3349
|
-
);
|
|
3350
|
-
}
|
|
3351
|
-
configArgs.push("-c", `features.multi_agent=${enableMultiAgent}`);
|
|
3352
|
-
const textPrompt = joinTextParts(
|
|
3353
|
-
inputParts.filter(
|
|
3354
|
-
(part) => part.type === "text"
|
|
3355
|
-
)
|
|
3356
|
-
);
|
|
3357
|
-
const codexPromptText = buildCodexPromptText(textPrompt, preparedSkills);
|
|
3358
|
-
const inputItems = [];
|
|
3359
|
-
if (codexPromptText.trim().length > 0) {
|
|
3360
|
-
inputItems.push({
|
|
3361
|
-
type: "text",
|
|
3362
|
-
text: codexPromptText,
|
|
3363
|
-
text_elements: []
|
|
3364
|
-
});
|
|
3365
|
-
}
|
|
3366
|
-
inputItems.push(
|
|
3367
|
-
...await mapToCodexPromptParts(
|
|
3368
|
-
inputParts,
|
|
3369
|
-
async (part, index) => materializeCodexImage(target, part, index)
|
|
3370
|
-
)
|
|
3371
|
-
);
|
|
3372
|
-
inputItems.push(...buildCodexSkillInputItems(preparedSkills));
|
|
3373
|
-
if (options.sandbox) {
|
|
3374
|
-
const handle = await options.sandbox.runAsync(
|
|
3375
|
-
[
|
|
3376
|
-
"env",
|
|
3377
|
-
...buildCodexCommandArgs(options.provider?.binary ?? "codex", [
|
|
3378
|
-
...configArgs,
|
|
3379
|
-
"app-server"
|
|
3380
|
-
])
|
|
3381
|
-
],
|
|
3382
|
-
{
|
|
3383
|
-
cwd: runtimeCwd,
|
|
3384
|
-
env
|
|
3385
|
-
}
|
|
3386
|
-
);
|
|
3387
|
-
if (!handle.write) {
|
|
3388
|
-
throw new Error(
|
|
3389
|
-
"The selected sandbox does not expose an interactive stdin channel for Codex."
|
|
3390
|
-
);
|
|
3391
|
-
}
|
|
3392
|
-
async function* stdoutLines() {
|
|
3393
|
-
async function* stdoutChunks() {
|
|
3394
|
-
for await (const event of handle) {
|
|
3395
|
-
if (event.type === "stdout" && event.chunk) {
|
|
3396
|
-
yield event.chunk;
|
|
3397
|
-
}
|
|
3398
|
-
}
|
|
3399
|
-
}
|
|
3400
|
-
yield* linesFromTextChunks(stdoutChunks());
|
|
3401
|
-
}
|
|
3402
|
-
return {
|
|
3403
|
-
source: stdoutLines(),
|
|
3404
|
-
writeLine: async (line) => {
|
|
3405
|
-
await handle.write?.(`${line}
|
|
3406
|
-
`);
|
|
3407
|
-
},
|
|
3408
|
-
cleanup: async () => {
|
|
3409
|
-
await handle.kill();
|
|
3410
|
-
await target.cleanup();
|
|
3411
|
-
},
|
|
3412
|
-
raw: { handle, layout: target.layout },
|
|
3413
|
-
inputItems
|
|
3414
|
-
};
|
|
3415
|
-
}
|
|
3416
|
-
const processHandle = spawnCommand({
|
|
3417
|
-
command: "env",
|
|
3418
|
-
args: buildCodexCommandArgs(options.provider?.binary ?? "codex", [
|
|
3419
|
-
...configArgs,
|
|
3420
|
-
"app-server"
|
|
3421
|
-
]),
|
|
3422
|
-
cwd: runtimeCwd,
|
|
3423
|
-
env: {
|
|
3424
|
-
...process.env,
|
|
3425
|
-
...env
|
|
3426
|
-
}
|
|
3427
|
-
});
|
|
3428
|
-
return {
|
|
3429
|
-
source: linesFromNodeStream(processHandle.child.stdout),
|
|
3430
|
-
writeLine: async (line) => {
|
|
3431
|
-
processHandle.child.stdin.write(`${line}
|
|
3432
|
-
`);
|
|
3433
|
-
},
|
|
3434
|
-
cleanup: async () => {
|
|
3435
|
-
await processHandle.kill();
|
|
3436
|
-
await target.cleanup();
|
|
3437
|
-
},
|
|
3438
|
-
raw: { processHandle, layout: target.layout },
|
|
3439
|
-
inputItems
|
|
3440
|
-
};
|
|
3441
|
-
}
|
|
3442
|
-
var CodexAgentAdapter = class {
|
|
3443
|
-
async execute(request, sink) {
|
|
3444
|
-
const inputParts = await validateProviderUserInput(
|
|
3445
|
-
request.provider,
|
|
3446
|
-
request.run.input
|
|
3447
|
-
);
|
|
3448
|
-
const runtime = await createRuntime2(request, inputParts);
|
|
3449
|
-
sink.setRaw(runtime.raw);
|
|
3450
|
-
sink.setAbort(runtime.cleanup);
|
|
3451
|
-
sink.emitEvent(
|
|
3452
|
-
createNormalizedEvent("run.started", {
|
|
3453
|
-
provider: request.provider,
|
|
3454
|
-
runId: request.runId
|
|
3455
|
-
})
|
|
3456
|
-
);
|
|
3457
|
-
const client = runtime.client ?? new JsonRpcLineClient(
|
|
3458
|
-
runtime.source,
|
|
3459
|
-
runtime.writeLine
|
|
3460
|
-
);
|
|
3461
|
-
const interactiveApproval = isInteractiveApproval(request.options);
|
|
3462
|
-
let rootThreadId;
|
|
3463
|
-
let turnId;
|
|
3464
|
-
let pendingTurns = 1;
|
|
3465
|
-
const sendTurn = async (content) => {
|
|
3466
|
-
if (!rootThreadId) {
|
|
3467
|
-
throw new Error("Cannot send message before thread is started.");
|
|
3468
|
-
}
|
|
3469
|
-
const parts = normalizeUserInput(content);
|
|
3470
|
-
const text = parts.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
3471
|
-
const inputItems = [];
|
|
3472
|
-
if (text.trim().length > 0) {
|
|
3473
|
-
inputItems.push({ type: "text", text, text_elements: [] });
|
|
3474
|
-
}
|
|
3475
|
-
pendingTurns++;
|
|
3476
|
-
const sandboxPolicy = buildTurnSandboxPolicy(request.options);
|
|
3477
|
-
await client.request("turn/start", {
|
|
3478
|
-
threadId: rootThreadId,
|
|
3479
|
-
input: inputItems,
|
|
3480
|
-
approvalPolicy: isInteractiveApproval(request.options) ? "untrusted" : "never",
|
|
3481
|
-
...sandboxPolicy ? { sandboxPolicy } : {},
|
|
3482
|
-
model: request.run.model ?? null,
|
|
3483
|
-
effort: null,
|
|
3484
|
-
outputSchema: null
|
|
3485
|
-
});
|
|
3486
|
-
};
|
|
3487
|
-
sink.onMessage(sendTurn);
|
|
3488
|
-
const completion = new Promise((resolve, reject) => {
|
|
3489
|
-
let finalText = "";
|
|
3490
|
-
void (async () => {
|
|
3491
|
-
for await (const message of client.messages()) {
|
|
3492
|
-
const raw = toRawEvent2(request.runId, message, message.method);
|
|
3493
|
-
sink.emitRaw(raw);
|
|
3494
|
-
if (message.method === "tool/requestUserInput" && message.id !== void 0) {
|
|
3495
|
-
reject(
|
|
3496
|
-
new Error(
|
|
3497
|
-
"Codex tool/requestUserInput approvals are not yet supported by AgentBox."
|
|
3498
|
-
)
|
|
3499
|
-
);
|
|
3500
|
-
return;
|
|
3501
|
-
}
|
|
3502
|
-
const permissionEvent = createCodexPermissionEvent(request, message);
|
|
3503
|
-
if (permissionEvent && message.id !== void 0) {
|
|
3504
|
-
const response = interactiveApproval ? await sink.requestPermission(permissionEvent) : {
|
|
3505
|
-
requestId: permissionEvent.requestId,
|
|
3506
|
-
decision: "allow"
|
|
3507
|
-
};
|
|
3508
|
-
await client.respond(message.id, {
|
|
3509
|
-
decision: toCodexApprovalDecision(message, response)
|
|
3510
|
-
});
|
|
3511
|
-
continue;
|
|
3512
|
-
}
|
|
3513
|
-
for (const event of toNormalizedCodexEvents(request.runId, message)) {
|
|
3514
|
-
sink.emitEvent(event);
|
|
3515
|
-
if (event.type === "text.delta") {
|
|
3516
|
-
finalText += event.delta;
|
|
3517
|
-
}
|
|
3518
|
-
}
|
|
3519
|
-
if (message.method === "thread/started" && !rootThreadId) {
|
|
3520
|
-
rootThreadId = message.params?.thread?.id ?? rootThreadId;
|
|
3521
|
-
}
|
|
3522
|
-
if (message.method === "turn/started") {
|
|
3523
|
-
turnId = message.params?.turn?.id ?? turnId;
|
|
3524
|
-
}
|
|
3525
|
-
if (message.method === "turn/completed" && (!message.params?.threadId || message.params.threadId === rootThreadId)) {
|
|
3526
|
-
pendingTurns--;
|
|
3527
|
-
if (pendingTurns <= 0) {
|
|
3528
|
-
resolve({ text: finalText, turnId, threadId: rootThreadId });
|
|
3529
|
-
return;
|
|
3530
|
-
}
|
|
3531
|
-
}
|
|
3532
|
-
if (message.method === "error" && !shouldIgnoreCodexError(message)) {
|
|
3533
|
-
reject(message);
|
|
3534
|
-
return;
|
|
3535
|
-
}
|
|
3536
|
-
}
|
|
3537
|
-
reject(new Error("Codex transport closed before run completed."));
|
|
3538
|
-
})().catch(reject);
|
|
3539
|
-
});
|
|
3540
|
-
try {
|
|
3541
|
-
if (!runtime.client) {
|
|
3542
|
-
await client.request("initialize", {
|
|
3543
|
-
clientInfo: {
|
|
3544
|
-
title: "AgentBox",
|
|
3545
|
-
name: "AgentBox",
|
|
3546
|
-
version: "0.1.0"
|
|
3547
|
-
},
|
|
3548
|
-
capabilities: {
|
|
3549
|
-
experimentalApi: true
|
|
3550
|
-
}
|
|
3551
|
-
});
|
|
3552
|
-
await client.notify("initialized", {});
|
|
3553
|
-
}
|
|
3554
|
-
const cwd = request.options.cwd ?? process.cwd();
|
|
3555
|
-
const threadResponse = request.run.resumeSessionId ? await client.request(
|
|
3556
|
-
"thread/resume",
|
|
3557
|
-
buildResumeParams(cwd, request.options, request)
|
|
3558
|
-
) : await client.request(
|
|
3559
|
-
"thread/start",
|
|
3560
|
-
buildThreadParams(cwd, request.options, request)
|
|
3561
|
-
);
|
|
3562
|
-
rootThreadId = threadResponse.thread.id;
|
|
3563
|
-
if ("bindThread" in client && typeof client.bindThread === "function") {
|
|
3564
|
-
client.bindThread(threadResponse.thread.id);
|
|
3565
|
-
}
|
|
3566
|
-
sink.setSessionId(threadResponse.thread.id);
|
|
3567
|
-
sink.emitRaw(
|
|
3568
|
-
toRawEvent2(
|
|
3569
|
-
request.runId,
|
|
3570
|
-
threadResponse,
|
|
3571
|
-
request.run.resumeSessionId ? "thread/resume:result" : "thread/start:result"
|
|
3572
|
-
)
|
|
3573
|
-
);
|
|
3574
|
-
const sandboxPolicy = buildTurnSandboxPolicy(request.options);
|
|
3575
|
-
await client.request("turn/start", {
|
|
3576
|
-
threadId: threadResponse.thread.id,
|
|
3577
|
-
input: runtime.inputItems,
|
|
3578
|
-
approvalPolicy: isInteractiveApproval(request.options) ? "untrusted" : "never",
|
|
3579
|
-
...sandboxPolicy ? { sandboxPolicy } : {},
|
|
3580
|
-
...runtime.turnStartOverrides ?? {},
|
|
3581
|
-
model: request.run.model ?? null,
|
|
3582
|
-
effort: null,
|
|
3583
|
-
outputSchema: null
|
|
3584
|
-
});
|
|
3585
|
-
const { text } = await completion;
|
|
3586
|
-
sink.complete({ text });
|
|
3587
|
-
} finally {
|
|
3588
|
-
await runtime.cleanup().catch(() => void 0);
|
|
3589
|
-
}
|
|
3590
|
-
return async () => void 0;
|
|
3591
|
-
}
|
|
3592
|
-
};
|
|
3593
|
-
|
|
3594
|
-
// src/agents/providers/opencode.ts
|
|
3595
|
-
import path10 from "path";
|
|
3596
|
-
var SANDBOX_OPENCODE_PORT = 4096;
|
|
3597
|
-
var SANDBOX_OPENCODE_READY_TIMEOUT_MS = 9e4;
|
|
3598
|
-
var SHARED_OPENCODE_TARGET_ID = "shared-opencode-server";
|
|
3599
|
-
function toRawEvent3(runId, payload, type) {
|
|
3600
|
-
return {
|
|
3601
|
-
provider: "opencode",
|
|
3602
|
-
runId,
|
|
3603
|
-
type,
|
|
3604
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3605
|
-
payload
|
|
3606
|
-
};
|
|
3607
|
-
}
|
|
3608
|
-
function extractText(value) {
|
|
3609
|
-
if (!value) {
|
|
3610
|
-
return "";
|
|
3611
|
-
}
|
|
3612
|
-
if (typeof value === "string") {
|
|
3613
|
-
return value;
|
|
3614
|
-
}
|
|
3615
|
-
if (Array.isArray(value)) {
|
|
3616
|
-
return value.map(extractText).filter(Boolean).join("");
|
|
3617
|
-
}
|
|
3618
|
-
if (typeof value === "object") {
|
|
3619
|
-
const record = value;
|
|
3620
|
-
if (record.type === "text" && typeof record.text === "string") {
|
|
3621
|
-
return record.text;
|
|
3622
|
-
}
|
|
3623
|
-
if (record.type === "reasoning") {
|
|
3624
|
-
return "";
|
|
3625
|
-
}
|
|
3626
|
-
if (record.message) {
|
|
3627
|
-
return extractText(record.message);
|
|
3628
|
-
}
|
|
3629
|
-
if (record.content) {
|
|
3630
|
-
return extractText(record.content);
|
|
3631
|
-
}
|
|
3632
|
-
if (record.parts) {
|
|
3633
|
-
return extractText(record.parts);
|
|
3634
|
-
}
|
|
3635
|
-
if (record.text) {
|
|
3636
|
-
return extractText(record.text);
|
|
3637
|
-
}
|
|
3638
|
-
}
|
|
3639
|
-
return "";
|
|
3640
|
-
}
|
|
3641
|
-
function toOpenCodeModel(model) {
|
|
3642
|
-
if (!model) {
|
|
3643
|
-
return void 0;
|
|
3644
|
-
}
|
|
3645
|
-
const slashIndex = model.indexOf("/");
|
|
3646
|
-
if (slashIndex === -1) {
|
|
3647
|
-
return { modelID: model };
|
|
3648
|
-
}
|
|
3649
|
-
const providerID = model.slice(0, slashIndex).trim();
|
|
3650
|
-
const modelID = model.slice(slashIndex + 1).trim();
|
|
3651
|
-
if (!providerID || !modelID) {
|
|
3652
|
-
return { modelID: model };
|
|
3653
|
-
}
|
|
3654
|
-
return { providerID, modelID };
|
|
3655
|
-
}
|
|
3656
|
-
function buildOpenCodePermissionConfig(interactive) {
|
|
3657
|
-
if (!interactive) {
|
|
3658
|
-
return {
|
|
3659
|
-
read: { "*": "allow" },
|
|
3660
|
-
edit: "allow",
|
|
3661
|
-
bash: "allow",
|
|
3662
|
-
webfetch: "allow",
|
|
3663
|
-
external_directory: "allow",
|
|
3664
|
-
skill: { "*": "allow" },
|
|
3665
|
-
task: "allow"
|
|
3666
|
-
};
|
|
3667
|
-
}
|
|
3668
|
-
return {
|
|
3669
|
-
read: { "*": "allow" },
|
|
3670
|
-
edit: "ask",
|
|
3671
|
-
bash: "ask",
|
|
3672
|
-
webfetch: "ask",
|
|
3673
|
-
external_directory: "ask",
|
|
3674
|
-
skill: { "*": "allow" },
|
|
3675
|
-
task: "ask"
|
|
3676
|
-
};
|
|
3677
|
-
}
|
|
3678
|
-
function createOpenCodePermissionEvent(request, raw, payload) {
|
|
3679
|
-
const properties = payload.properties ?? {};
|
|
3680
|
-
const permission = String(properties.permission ?? "tool");
|
|
3681
|
-
return createNormalizedEvent(
|
|
3682
|
-
"permission.requested",
|
|
3683
|
-
{
|
|
3684
|
-
provider: request.provider,
|
|
3685
|
-
runId: request.runId,
|
|
3686
|
-
raw
|
|
3687
|
-
},
|
|
3688
|
-
{
|
|
3689
|
-
requestId: String(properties.id ?? ""),
|
|
3690
|
-
kind: permission === "bash" ? "bash" : permission === "edit" ? "edit" : permission === "external_directory" ? "file-change" : permission === "webfetch" ? "network" : permission === "task" ? "tool" : "unknown",
|
|
3691
|
-
title: `Approve ${permission} permission`,
|
|
3692
|
-
message: typeof properties.metadata === "object" && properties.metadata !== null ? JSON.stringify(properties.metadata) : `OpenCode requested ${permission} permission.`,
|
|
3693
|
-
input: properties,
|
|
3694
|
-
canRemember: Array.isArray(properties.always) && properties.always.length > 0
|
|
3695
|
-
}
|
|
3696
|
-
);
|
|
3697
|
-
}
|
|
3698
|
-
function buildOpenCodeConfig(request, interactiveApproval) {
|
|
3699
|
-
const options = request.options;
|
|
3700
|
-
const mcpConfig = buildOpenCodeMcpConfig(options.mcps);
|
|
3701
|
-
const commandsConfig = buildOpenCodeCommandsConfig(options.commands);
|
|
3702
|
-
return {
|
|
3703
|
-
$schema: "https://opencode.ai/config.json",
|
|
3704
|
-
...mcpConfig ? { mcp: mcpConfig } : {},
|
|
3705
|
-
...commandsConfig ? { command: commandsConfig } : {},
|
|
3706
|
-
agent: {
|
|
3707
|
-
agentbox: {
|
|
3708
|
-
mode: "primary",
|
|
3709
|
-
prompt: request.run.systemPrompt ?? "",
|
|
3710
|
-
permission: buildOpenCodePermissionConfig(interactiveApproval),
|
|
3711
|
-
tools: {
|
|
3712
|
-
write: true,
|
|
3713
|
-
edit: true,
|
|
3714
|
-
bash: true,
|
|
3715
|
-
webfetch: true,
|
|
3716
|
-
skill: true
|
|
3717
|
-
}
|
|
3718
|
-
},
|
|
3719
|
-
...buildOpenCodeSubagentConfig(options.subAgents)
|
|
3720
|
-
}
|
|
3721
|
-
};
|
|
3722
|
-
}
|
|
3723
|
-
async function ensureSandboxOpenCodeServer(request) {
|
|
3724
|
-
const sandbox = request.options.sandbox;
|
|
3725
|
-
const options = request.options;
|
|
3726
|
-
const port = SANDBOX_OPENCODE_PORT;
|
|
3727
|
-
await sandbox.openPort(port);
|
|
3728
|
-
const healthCheck = await sandbox.run(
|
|
3729
|
-
`curl -fsS http://127.0.0.1:${port}/global/health >/dev/null 2>&1`,
|
|
3730
|
-
{ cwd: options.cwd, timeoutMs: 5e3 }
|
|
3731
|
-
);
|
|
3732
|
-
if (healthCheck.exitCode === 0) {
|
|
3733
|
-
const baseUrl2 = (await sandbox.getPreviewLink(port)).replace(/\/$/, "");
|
|
3734
|
-
return {
|
|
3735
|
-
baseUrl: baseUrl2,
|
|
3736
|
-
cleanup: async () => {
|
|
3737
|
-
},
|
|
3738
|
-
raw: { baseUrl: baseUrl2, port, reused: true }
|
|
3739
|
-
};
|
|
3740
|
-
}
|
|
3741
|
-
const plugins = assertHooksSupported(request.provider, options);
|
|
3742
|
-
assertCommandsSupported(request.provider, options.commands);
|
|
3743
|
-
const interactiveApproval = isInteractiveApproval(options);
|
|
3744
|
-
const target = await createRuntimeTarget(
|
|
3745
|
-
request.provider,
|
|
3746
|
-
SHARED_OPENCODE_TARGET_ID,
|
|
3747
|
-
options
|
|
3748
|
-
);
|
|
3749
|
-
const { artifacts: skillArtifacts, installCommands } = await prepareSkillArtifacts(
|
|
3750
|
-
request.provider,
|
|
3751
|
-
options.skills,
|
|
3752
|
-
target.layout
|
|
3753
|
-
);
|
|
3754
|
-
const pluginArtifacts = buildOpenCodePluginArtifacts(
|
|
3755
|
-
plugins,
|
|
3756
|
-
target.layout.opencodeDir
|
|
3757
|
-
);
|
|
3758
|
-
for (const artifact of [...skillArtifacts, ...pluginArtifacts]) {
|
|
3759
|
-
await target.writeArtifact(artifact);
|
|
3760
|
-
}
|
|
3761
|
-
const configPath = path10.join(target.layout.opencodeDir, "agentbox.json");
|
|
3762
|
-
const openCodeConfig = buildOpenCodeConfig(request, interactiveApproval);
|
|
3763
|
-
await target.writeArtifact({
|
|
3764
|
-
path: configPath,
|
|
3765
|
-
content: JSON.stringify(openCodeConfig, null, 2)
|
|
3766
|
-
});
|
|
3767
|
-
const commonEnv = {
|
|
3768
|
-
OPENCODE_CONFIG: configPath,
|
|
3769
|
-
OPENCODE_CONFIG_DIR: target.layout.opencodeDir,
|
|
3770
|
-
OPENCODE_DISABLE_DEFAULT_PLUGINS: "true"
|
|
3771
|
-
};
|
|
3772
|
-
await installSkills(target, installCommands, commonEnv);
|
|
3773
|
-
const binary = options.provider?.binary ?? "opencode";
|
|
3774
|
-
const pidFilePath = path10.posix.join(
|
|
3775
|
-
target.layout.rootDir,
|
|
3776
|
-
"opencode-serve.pid"
|
|
3777
|
-
);
|
|
3778
|
-
const logFilePath = path10.posix.join(
|
|
3779
|
-
target.layout.rootDir,
|
|
3780
|
-
"opencode-serve.log"
|
|
3781
|
-
);
|
|
3782
|
-
const serveEnv = { ...options.env ?? {}, ...commonEnv };
|
|
3783
|
-
const launchCommand = [
|
|
3784
|
-
`mkdir -p ${shellQuote(target.layout.rootDir)}`,
|
|
3785
|
-
`(${[
|
|
3786
|
-
`nohup ${[
|
|
3787
|
-
binary,
|
|
3788
|
-
"serve",
|
|
3789
|
-
"--hostname",
|
|
3790
|
-
"0.0.0.0",
|
|
3791
|
-
"--port",
|
|
3792
|
-
String(port),
|
|
3793
|
-
...options.provider?.args ?? []
|
|
3794
|
-
].map(shellQuote).join(" ")} > ${shellQuote(logFilePath)} 2>&1 &`,
|
|
3795
|
-
`echo $! > ${shellQuote(pidFilePath)}`
|
|
3796
|
-
].join(" ")})`
|
|
3797
|
-
].join(" && ");
|
|
3798
|
-
const launchHandle = await sandbox.runAsync(launchCommand, {
|
|
3799
|
-
cwd: options.cwd,
|
|
3800
|
-
env: serveEnv
|
|
3801
|
-
});
|
|
3802
|
-
const launchResult = await launchHandle.wait();
|
|
3803
|
-
if (launchResult.exitCode !== 0) {
|
|
3804
|
-
await target.cleanup().catch(() => void 0);
|
|
3805
|
-
throw new Error(
|
|
3806
|
-
`Could not start OpenCode server: ${launchResult.combinedOutput || launchResult.stderr}`
|
|
3807
|
-
);
|
|
3808
|
-
}
|
|
3809
|
-
const baseUrl = (await sandbox.getPreviewLink(port)).replace(/\/$/, "");
|
|
3810
|
-
await waitForHttpReady(`${baseUrl}/global/health`, {
|
|
3811
|
-
timeoutMs: SANDBOX_OPENCODE_READY_TIMEOUT_MS
|
|
3812
|
-
});
|
|
3813
|
-
return {
|
|
3814
|
-
baseUrl,
|
|
3815
|
-
cleanup: async () => {
|
|
3816
|
-
await target.cleanup().catch(() => void 0);
|
|
3817
|
-
},
|
|
3818
|
-
raw: { pidFilePath, logFilePath, baseUrl, layout: target.layout, port }
|
|
3819
|
-
};
|
|
3820
|
-
}
|
|
3821
|
-
async function createLocalRuntime2(request) {
|
|
3822
|
-
const options = request.options;
|
|
3823
|
-
const plugins = assertHooksSupported(request.provider, options);
|
|
3824
|
-
assertCommandsSupported(request.provider, options.commands);
|
|
3825
|
-
const interactiveApproval = isInteractiveApproval(options);
|
|
3826
|
-
const target = await createRuntimeTarget(
|
|
3827
|
-
request.provider,
|
|
3828
|
-
request.runId,
|
|
3829
|
-
options
|
|
3830
|
-
);
|
|
3831
|
-
const { artifacts: skillArtifacts, installCommands } = await prepareSkillArtifacts(
|
|
3832
|
-
request.provider,
|
|
3833
|
-
options.skills,
|
|
3834
|
-
target.layout
|
|
3835
|
-
);
|
|
3836
|
-
const pluginArtifacts = buildOpenCodePluginArtifacts(
|
|
3837
|
-
plugins,
|
|
3838
|
-
target.layout.opencodeDir
|
|
3839
|
-
);
|
|
3840
|
-
for (const artifact of [...skillArtifacts, ...pluginArtifacts]) {
|
|
3841
|
-
await target.writeArtifact(artifact);
|
|
3842
|
-
}
|
|
3843
|
-
const configPath = path10.join(target.layout.opencodeDir, "agentbox.json");
|
|
3844
|
-
const openCodeConfig = buildOpenCodeConfig(request, interactiveApproval);
|
|
3845
|
-
await target.writeArtifact({
|
|
3846
|
-
path: configPath,
|
|
3847
|
-
content: JSON.stringify(openCodeConfig, null, 2)
|
|
3848
|
-
});
|
|
3849
|
-
const commonEnv = {
|
|
3850
|
-
OPENCODE_CONFIG: configPath,
|
|
3851
|
-
OPENCODE_CONFIG_DIR: target.layout.opencodeDir,
|
|
3852
|
-
OPENCODE_DISABLE_DEFAULT_PLUGINS: "true"
|
|
3853
|
-
};
|
|
3854
|
-
await installSkills(target, installCommands, commonEnv);
|
|
3855
|
-
const hostPort = await getAvailablePort();
|
|
3856
|
-
const processHandle = spawnCommand({
|
|
3857
|
-
command: options.provider?.binary ?? "opencode",
|
|
3858
|
-
args: [
|
|
3859
|
-
"serve",
|
|
3860
|
-
"--hostname",
|
|
3861
|
-
"127.0.0.1",
|
|
3862
|
-
"--port",
|
|
3863
|
-
String(hostPort),
|
|
3864
|
-
...options.provider?.args ?? []
|
|
3865
|
-
],
|
|
3866
|
-
cwd: options.cwd,
|
|
3867
|
-
env: {
|
|
3868
|
-
...process.env,
|
|
3869
|
-
...options.env ?? {},
|
|
3870
|
-
...commonEnv
|
|
3871
|
-
}
|
|
3872
|
-
});
|
|
3873
|
-
const baseUrl = `http://127.0.0.1:${hostPort}`;
|
|
3874
|
-
await waitForHttpReady(`${baseUrl}/global/health`, { timeoutMs: 2e4 });
|
|
3875
|
-
return {
|
|
3876
|
-
baseUrl,
|
|
3877
|
-
cleanup: async () => {
|
|
3878
|
-
await processHandle.kill();
|
|
3879
|
-
await target.cleanup();
|
|
3880
|
-
},
|
|
3881
|
-
raw: { processHandle, layout: target.layout }
|
|
3882
|
-
};
|
|
3883
|
-
}
|
|
3884
|
-
async function createRuntime3(request) {
|
|
3885
|
-
if (request.options.sandbox) {
|
|
3886
|
-
return ensureSandboxOpenCodeServer(request);
|
|
3887
|
-
}
|
|
3888
|
-
return createLocalRuntime2(request);
|
|
3889
|
-
}
|
|
3890
|
-
var OpenCodeAgentAdapter = class {
|
|
3891
|
-
async execute(request, sink) {
|
|
3892
|
-
const inputParts = await validateProviderUserInput(
|
|
3893
|
-
request.provider,
|
|
3894
|
-
request.run.input
|
|
3895
|
-
);
|
|
3896
|
-
const runtime = await createRuntime3(request);
|
|
3897
|
-
sink.setRaw(runtime.raw);
|
|
3898
|
-
sink.setAbort(runtime.cleanup);
|
|
3899
|
-
sink.emitEvent(
|
|
3900
|
-
createNormalizedEvent("run.started", {
|
|
3901
|
-
provider: request.provider,
|
|
3902
|
-
runId: request.runId
|
|
3903
|
-
})
|
|
3904
|
-
);
|
|
3905
|
-
const sseAbort = new AbortController();
|
|
3906
|
-
let sseTask;
|
|
3907
|
-
try {
|
|
3908
|
-
const interactiveApproval = isInteractiveApproval(request.options);
|
|
3909
|
-
const createdSession = request.run.resumeSessionId ? null : await fetchJson(
|
|
3910
|
-
`${runtime.baseUrl}/session`,
|
|
3911
|
-
{
|
|
3912
|
-
method: "POST",
|
|
3913
|
-
headers: { "content-type": "application/json" },
|
|
3914
|
-
body: JSON.stringify({
|
|
3915
|
-
title: `AgentBox ${request.runId}`
|
|
3916
|
-
})
|
|
3917
|
-
}
|
|
3918
|
-
);
|
|
3919
|
-
const sessionId = request.run.resumeSessionId ?? createdSession?.id ?? createdSession?.sessionId;
|
|
3920
|
-
if (!sessionId) {
|
|
3921
|
-
throw new Error("OpenCode did not return a session id.");
|
|
3922
|
-
}
|
|
3923
|
-
sseTask = (async () => {
|
|
3924
|
-
try {
|
|
3925
|
-
for await (const event of streamSse(`${runtime.baseUrl}/event`, {
|
|
3926
|
-
signal: sseAbort.signal
|
|
3927
|
-
})) {
|
|
3928
|
-
let payload = event.data;
|
|
3929
|
-
try {
|
|
3930
|
-
payload = JSON.parse(event.data);
|
|
3931
|
-
} catch {
|
|
3932
|
-
}
|
|
3933
|
-
const raw = toRawEvent3(
|
|
3934
|
-
request.runId,
|
|
3935
|
-
payload,
|
|
3936
|
-
`sse:${event.event ?? "message"}`
|
|
3937
|
-
);
|
|
3938
|
-
sink.emitRaw(raw);
|
|
3939
|
-
const eventType = typeof payload?.type === "string" ? String(payload.type) : event.event;
|
|
3940
|
-
if (eventType === "permission.asked") {
|
|
3941
|
-
const properties = payload.properties;
|
|
3942
|
-
if (properties && typeof properties.sessionID === "string" && properties.sessionID === sessionId) {
|
|
3943
|
-
const permissionEvent = createOpenCodePermissionEvent(
|
|
3944
|
-
request,
|
|
3945
|
-
raw,
|
|
3946
|
-
payload
|
|
3947
|
-
);
|
|
3948
|
-
const response2 = interactiveApproval ? await sink.requestPermission(permissionEvent) : {
|
|
3949
|
-
requestId: permissionEvent.requestId,
|
|
3950
|
-
decision: "allow"
|
|
3951
|
-
};
|
|
3952
|
-
await fetchJson(
|
|
3953
|
-
`${runtime.baseUrl}/session/${sessionId}/permissions/${permissionEvent.requestId}`,
|
|
3954
|
-
{
|
|
3955
|
-
method: "POST",
|
|
3956
|
-
headers: { "content-type": "application/json" },
|
|
3957
|
-
body: JSON.stringify({
|
|
3958
|
-
response: response2.decision === "allow" ? response2.remember ? "always" : "once" : "reject"
|
|
3959
|
-
})
|
|
3960
|
-
}
|
|
3961
|
-
);
|
|
3962
|
-
}
|
|
3963
|
-
continue;
|
|
3964
|
-
}
|
|
3965
|
-
for (const normalized of normalizeRawAgentEvent(raw)) {
|
|
3966
|
-
sink.emitEvent(normalized);
|
|
3967
|
-
}
|
|
3968
|
-
}
|
|
3969
|
-
} catch {
|
|
3970
|
-
}
|
|
3971
|
-
})();
|
|
3972
|
-
sink.onMessage(async () => {
|
|
3973
|
-
console.warn(
|
|
3974
|
-
"[agentbox] sendMessage is not yet supported for the opencode provider. Use resumeSessionId for follow-ups instead."
|
|
3975
|
-
);
|
|
3976
|
-
});
|
|
3977
|
-
sink.setSessionId(sessionId);
|
|
3978
|
-
sink.emitRaw(
|
|
3979
|
-
toRawEvent3(
|
|
3980
|
-
request.runId,
|
|
3981
|
-
createdSession ?? { sessionId },
|
|
3982
|
-
request.run.resumeSessionId ? "session.resumed" : "session.created"
|
|
3983
|
-
)
|
|
3984
|
-
);
|
|
3985
|
-
sink.emitEvent(
|
|
3986
|
-
createNormalizedEvent("message.started", {
|
|
3987
|
-
provider: request.provider,
|
|
3988
|
-
runId: request.runId
|
|
3989
|
-
})
|
|
3990
|
-
);
|
|
3991
|
-
const response = await fetchJson(
|
|
3992
|
-
`${runtime.baseUrl}/session/${sessionId}/message`,
|
|
3993
|
-
{
|
|
3994
|
-
method: "POST",
|
|
3995
|
-
headers: { "content-type": "application/json" },
|
|
3996
|
-
body: JSON.stringify({
|
|
3997
|
-
...request.run.model ? { model: toOpenCodeModel(request.run.model) } : {},
|
|
3998
|
-
agent: "agentbox",
|
|
3999
|
-
parts: mapToOpenCodeParts(inputParts)
|
|
4000
|
-
})
|
|
4001
|
-
}
|
|
4002
|
-
);
|
|
4003
|
-
const rawResponse = toRawEvent3(
|
|
4004
|
-
request.runId,
|
|
4005
|
-
response,
|
|
4006
|
-
"message.response"
|
|
4007
|
-
);
|
|
4008
|
-
sink.emitRaw(rawResponse);
|
|
4009
|
-
for (const event of normalizeRawAgentEvent(rawResponse)) {
|
|
4010
|
-
sink.emitEvent(event);
|
|
4011
|
-
}
|
|
4012
|
-
const text = extractText(response);
|
|
4013
|
-
if (text) {
|
|
4014
|
-
sink.emitEvent(
|
|
4015
|
-
createNormalizedEvent(
|
|
4016
|
-
"text.delta",
|
|
4017
|
-
{
|
|
4018
|
-
provider: request.provider,
|
|
4019
|
-
runId: request.runId
|
|
4020
|
-
},
|
|
4021
|
-
{ delta: text }
|
|
4022
|
-
)
|
|
4023
|
-
);
|
|
4024
|
-
}
|
|
4025
|
-
sink.emitEvent(
|
|
4026
|
-
createNormalizedEvent(
|
|
4027
|
-
"run.completed",
|
|
4028
|
-
{
|
|
4029
|
-
provider: request.provider,
|
|
4030
|
-
runId: request.runId
|
|
4031
|
-
},
|
|
4032
|
-
{ text }
|
|
4033
|
-
)
|
|
4034
|
-
);
|
|
4035
|
-
sseAbort.abort();
|
|
4036
|
-
await sseTask;
|
|
4037
|
-
sink.complete({ text });
|
|
4038
|
-
} finally {
|
|
4039
|
-
sseAbort.abort();
|
|
4040
|
-
if (sseTask) {
|
|
4041
|
-
await sseTask.catch(() => void 0);
|
|
4042
|
-
}
|
|
4043
|
-
await runtime.cleanup().catch(() => void 0);
|
|
4044
|
-
}
|
|
4045
|
-
return async () => void 0;
|
|
4046
|
-
}
|
|
4047
|
-
};
|
|
4048
|
-
|
|
4049
|
-
// src/agents/Agent.ts
|
|
4050
|
-
function buildAgentOptionsSystemAppendix(options) {
|
|
4051
|
-
const sections = [];
|
|
4052
|
-
if (options.mcps?.length) {
|
|
4053
|
-
sections.push(
|
|
4054
|
-
[
|
|
4055
|
-
"Configured MCP servers are available for this run:",
|
|
4056
|
-
...options.mcps.map((mcp) => `- ${mcp.name}`)
|
|
4057
|
-
].join("\n")
|
|
4058
|
-
);
|
|
4059
|
-
}
|
|
4060
|
-
if (options.skills?.length) {
|
|
4061
|
-
sections.push(
|
|
4062
|
-
[
|
|
4063
|
-
"Configured skills are available for this run:",
|
|
4064
|
-
...options.skills.map((skill) => `- ${skill.name}`)
|
|
4065
|
-
].join("\n")
|
|
4066
|
-
);
|
|
4067
|
-
}
|
|
4068
|
-
if (options.subAgents?.length) {
|
|
4069
|
-
sections.push(
|
|
4070
|
-
[
|
|
4071
|
-
"Configured sub-agents are available for delegation:",
|
|
4072
|
-
...options.subAgents.map(
|
|
4073
|
-
(subAgent) => `- ${subAgent.name}: ${subAgent.description}`
|
|
4074
|
-
)
|
|
4075
|
-
].join("\n")
|
|
4076
|
-
);
|
|
4077
|
-
}
|
|
4078
|
-
if (options.commands?.length) {
|
|
4079
|
-
sections.push(
|
|
4080
|
-
[
|
|
4081
|
-
"Custom commands are installed for this environment:",
|
|
4082
|
-
...options.commands.map(
|
|
4083
|
-
(command) => `- /${command.name}${command.description ? `: ${command.description}` : ""}`
|
|
4084
|
-
)
|
|
4085
|
-
].join("\n")
|
|
4086
|
-
);
|
|
4087
|
-
}
|
|
4088
|
-
return sections.length > 0 ? sections.join("\n\n") : void 0;
|
|
4089
|
-
}
|
|
4090
|
-
function buildRunConfig(options, runConfig) {
|
|
4091
|
-
const appendix = buildAgentOptionsSystemAppendix(options);
|
|
4092
|
-
const systemPrompt = [runConfig.systemPrompt, appendix].filter(Boolean).join("\n\n");
|
|
4093
|
-
return {
|
|
4094
|
-
...runConfig,
|
|
4095
|
-
...systemPrompt ? { systemPrompt } : {}
|
|
4096
|
-
};
|
|
4097
|
-
}
|
|
4098
|
-
function createAdapter(provider) {
|
|
4099
|
-
switch (provider) {
|
|
4100
|
-
case "codex":
|
|
4101
|
-
return new CodexAgentAdapter();
|
|
4102
|
-
case "opencode":
|
|
4103
|
-
return new OpenCodeAgentAdapter();
|
|
4104
|
-
case "claude-code":
|
|
4105
|
-
return new ClaudeCodeAgentAdapter();
|
|
4106
|
-
default:
|
|
4107
|
-
throw new UnsupportedProviderError("agent", provider);
|
|
4108
|
-
}
|
|
4109
|
-
}
|
|
4110
|
-
function prepareAgentOptions(provider, options) {
|
|
4111
|
-
if (provider === "opencode") {
|
|
4112
|
-
const openCodeOptions = options;
|
|
4113
|
-
const port = 4096;
|
|
4114
|
-
openCodeOptions.sandbox?.openPort(port);
|
|
4115
|
-
}
|
|
4116
|
-
return options;
|
|
4117
|
-
}
|
|
4118
|
-
var AgentRunController = class {
|
|
4119
|
-
id;
|
|
4120
|
-
provider;
|
|
4121
|
-
sessionId;
|
|
4122
|
-
raw;
|
|
4123
|
-
sessionIdReady;
|
|
4124
|
-
abortHandler = async () => void 0;
|
|
4125
|
-
abortRequested = false;
|
|
4126
|
-
eventQueue = new AsyncQueue();
|
|
4127
|
-
rawQueue = new AsyncQueue();
|
|
4128
|
-
events = [];
|
|
4129
|
-
rawEventsList = [];
|
|
4130
|
-
pendingPermissions = /* @__PURE__ */ new Map();
|
|
4131
|
-
messageHandler;
|
|
4132
|
-
text = "";
|
|
4133
|
-
settled = false;
|
|
4134
|
-
finished;
|
|
4135
|
-
resolveSessionIdReady;
|
|
4136
|
-
rejectSessionIdReady;
|
|
4137
|
-
resolveFinished;
|
|
4138
|
-
rejectFinished;
|
|
4139
|
-
constructor(provider, id) {
|
|
4140
|
-
this.provider = provider;
|
|
4141
|
-
this.id = id;
|
|
4142
|
-
let resolveFinished;
|
|
4143
|
-
let rejectFinished;
|
|
4144
|
-
let resolveSessionIdReady;
|
|
4145
|
-
let rejectSessionIdReady;
|
|
4146
|
-
this.finished = new Promise((resolve, reject) => {
|
|
4147
|
-
resolveFinished = resolve;
|
|
4148
|
-
rejectFinished = reject;
|
|
4149
|
-
});
|
|
4150
|
-
this.sessionIdReady = new Promise((resolve, reject) => {
|
|
4151
|
-
resolveSessionIdReady = resolve;
|
|
4152
|
-
rejectSessionIdReady = reject;
|
|
4153
|
-
});
|
|
4154
|
-
void this.sessionIdReady.catch(() => void 0);
|
|
4155
|
-
this.resolveFinished = resolveFinished;
|
|
4156
|
-
this.rejectFinished = rejectFinished;
|
|
4157
|
-
this.resolveSessionIdReady = resolveSessionIdReady;
|
|
4158
|
-
this.rejectSessionIdReady = rejectSessionIdReady;
|
|
4159
|
-
}
|
|
4160
|
-
setRaw(raw) {
|
|
4161
|
-
this.raw = raw;
|
|
4162
|
-
}
|
|
4163
|
-
setAbort(abort) {
|
|
4164
|
-
this.abortHandler = abort;
|
|
4165
|
-
if (this.abortRequested) {
|
|
4166
|
-
void abort();
|
|
4167
|
-
}
|
|
4168
|
-
}
|
|
4169
|
-
setSessionId(sessionId) {
|
|
4170
|
-
if (this.sessionId) {
|
|
4171
|
-
return;
|
|
4172
|
-
}
|
|
4173
|
-
this.sessionId = sessionId;
|
|
4174
|
-
this.resolveSessionIdReady(sessionId);
|
|
4175
|
-
}
|
|
4176
|
-
emitRaw(event) {
|
|
4177
|
-
this.rawEventsList.push(event);
|
|
4178
|
-
this.rawQueue.push(event);
|
|
4179
|
-
}
|
|
4180
|
-
pushEvent(event) {
|
|
4181
|
-
this.events.push(event);
|
|
4182
|
-
if (event.type === "text.delta") {
|
|
4183
|
-
this.text += event.delta;
|
|
4184
|
-
} else if ((event.type === "message.completed" || event.type === "run.completed") && event.text) {
|
|
4185
|
-
this.text = event.text;
|
|
4186
|
-
}
|
|
4187
|
-
this.eventQueue.push(event);
|
|
4188
|
-
}
|
|
4189
|
-
emitEvent(event) {
|
|
4190
|
-
this.pushEvent(event);
|
|
4191
|
-
}
|
|
4192
|
-
requestPermission(event) {
|
|
4193
|
-
if (this.settled) {
|
|
4194
|
-
throw new Error("Cannot request permission on a settled agent run.");
|
|
4195
|
-
}
|
|
4196
|
-
if (this.pendingPermissions.has(event.requestId)) {
|
|
4197
|
-
throw new Error(
|
|
4198
|
-
`Permission request ${event.requestId} is already pending for this run.`
|
|
4199
|
-
);
|
|
4200
|
-
}
|
|
4201
|
-
const response = new Promise((resolve, reject) => {
|
|
4202
|
-
this.pendingPermissions.set(event.requestId, {
|
|
4203
|
-
event,
|
|
4204
|
-
resolve,
|
|
4205
|
-
reject
|
|
4206
|
-
});
|
|
4207
|
-
});
|
|
4208
|
-
this.pushEvent(event);
|
|
4209
|
-
return response;
|
|
4210
|
-
}
|
|
4211
|
-
onMessage(handler) {
|
|
4212
|
-
this.messageHandler = handler;
|
|
4213
|
-
}
|
|
4214
|
-
async sendMessage(content) {
|
|
4215
|
-
if (this.settled) {
|
|
4216
|
-
throw new Error("Cannot send a message on a settled agent run.");
|
|
4217
|
-
}
|
|
4218
|
-
if (!this.messageHandler) {
|
|
4219
|
-
throw new Error(
|
|
4220
|
-
"This provider does not support sending messages during a run."
|
|
4221
|
-
);
|
|
4222
|
-
}
|
|
4223
|
-
const textContent = normalizeUserInput(content).filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
4224
|
-
this.pushEvent(
|
|
4225
|
-
createNormalizedEvent(
|
|
4226
|
-
"message.injected",
|
|
4227
|
-
{ provider: this.provider, runId: this.id },
|
|
4228
|
-
{ content: textContent || "(non-text content)" }
|
|
4229
|
-
)
|
|
4230
|
-
);
|
|
4231
|
-
await this.messageHandler(content);
|
|
4232
|
-
}
|
|
4233
|
-
async respondToPermission(response) {
|
|
4234
|
-
const pending = this.pendingPermissions.get(response.requestId);
|
|
4235
|
-
if (!pending) {
|
|
4236
|
-
throw new Error(
|
|
4237
|
-
`Permission request ${response.requestId} is not pending for this run.`
|
|
4238
|
-
);
|
|
4239
|
-
}
|
|
4240
|
-
this.pendingPermissions.delete(response.requestId);
|
|
4241
|
-
const remember = pending.event.canRemember ? response.remember : void 0;
|
|
4242
|
-
const resolvedResponse = {
|
|
4243
|
-
requestId: response.requestId,
|
|
4244
|
-
decision: response.decision,
|
|
4245
|
-
...remember !== void 0 ? { remember } : {}
|
|
4246
|
-
};
|
|
4247
|
-
this.pushEvent(
|
|
4248
|
-
createNormalizedEvent(
|
|
4249
|
-
"permission.resolved",
|
|
4250
|
-
{
|
|
4251
|
-
provider: this.provider,
|
|
4252
|
-
runId: this.id
|
|
4253
|
-
},
|
|
4254
|
-
{
|
|
4255
|
-
requestId: response.requestId,
|
|
4256
|
-
decision: response.decision,
|
|
4257
|
-
...remember !== void 0 ? { remember } : {}
|
|
4258
|
-
}
|
|
4259
|
-
)
|
|
4260
|
-
);
|
|
4261
|
-
pending.resolve(resolvedResponse);
|
|
4262
|
-
}
|
|
4263
|
-
clearPendingPermissions(error) {
|
|
4264
|
-
for (const pending of this.pendingPermissions.values()) {
|
|
4265
|
-
pending.reject(error);
|
|
4266
|
-
}
|
|
4267
|
-
this.pendingPermissions.clear();
|
|
4268
|
-
}
|
|
4269
|
-
complete(result) {
|
|
4270
|
-
if (this.settled) {
|
|
4271
|
-
return;
|
|
4272
|
-
}
|
|
4273
|
-
this.settled = true;
|
|
4274
|
-
this.clearPendingPermissions(
|
|
4275
|
-
new Error(
|
|
4276
|
-
"Agent run completed before pending permission requests resolved."
|
|
4277
|
-
)
|
|
4278
|
-
);
|
|
4279
|
-
if (result?.text) {
|
|
4280
|
-
this.text = result.text;
|
|
4281
|
-
}
|
|
4282
|
-
this.eventQueue.finish();
|
|
4283
|
-
this.rawQueue.finish();
|
|
4284
|
-
if (!this.sessionId) {
|
|
4285
|
-
const error = new Error(
|
|
4286
|
-
"Agent run completed before a provider session id was set."
|
|
4287
|
-
);
|
|
4288
|
-
this.rejectSessionIdReady(error);
|
|
4289
|
-
this.rejectFinished(error);
|
|
4290
|
-
return;
|
|
4291
|
-
}
|
|
4292
|
-
this.resolveFinished({
|
|
4293
|
-
id: this.id,
|
|
4294
|
-
provider: this.provider,
|
|
4295
|
-
sessionId: this.sessionId,
|
|
4296
|
-
text: this.text,
|
|
4297
|
-
rawEvents: [...this.rawEventsList],
|
|
4298
|
-
events: [...this.events]
|
|
4299
|
-
});
|
|
4300
|
-
}
|
|
4301
|
-
fail(error) {
|
|
4302
|
-
if (this.settled) {
|
|
4303
|
-
return;
|
|
4304
|
-
}
|
|
4305
|
-
const normalizedError = asError(error);
|
|
4306
|
-
this.clearPendingPermissions(normalizedError);
|
|
4307
|
-
this.emitEvent(
|
|
4308
|
-
createNormalizedEvent(
|
|
4309
|
-
"run.error",
|
|
4310
|
-
{
|
|
4311
|
-
provider: this.provider,
|
|
4312
|
-
runId: this.id
|
|
4313
|
-
},
|
|
4314
|
-
{
|
|
4315
|
-
error: normalizedError.message
|
|
4316
|
-
}
|
|
4317
|
-
)
|
|
4318
|
-
);
|
|
4319
|
-
this.settled = true;
|
|
4320
|
-
this.eventQueue.finish();
|
|
4321
|
-
this.rawQueue.finish();
|
|
4322
|
-
if (!this.sessionId) {
|
|
4323
|
-
this.rejectSessionIdReady(normalizedError);
|
|
4324
|
-
}
|
|
4325
|
-
this.rejectFinished(normalizedError);
|
|
4326
|
-
}
|
|
4327
|
-
async abort() {
|
|
4328
|
-
this.abortRequested = true;
|
|
4329
|
-
await this.abortHandler();
|
|
4330
|
-
}
|
|
4331
|
-
rawEvents() {
|
|
4332
|
-
return this.rawQueue;
|
|
4333
|
-
}
|
|
4334
|
-
toAISDKEvents() {
|
|
4335
|
-
return toAISDKStream(this);
|
|
4336
|
-
}
|
|
4337
|
-
[Symbol.asyncIterator]() {
|
|
4338
|
-
return this.eventQueue[Symbol.asyncIterator]();
|
|
4339
|
-
}
|
|
4340
|
-
};
|
|
4341
|
-
var Agent = class {
|
|
4342
|
-
adapter;
|
|
4343
|
-
provider;
|
|
4344
|
-
options;
|
|
4345
|
-
constructor(provider, options) {
|
|
4346
|
-
this.provider = provider;
|
|
4347
|
-
this.options = prepareAgentOptions(provider, options);
|
|
4348
|
-
this.adapter = createAdapter(provider);
|
|
4349
|
-
}
|
|
4350
|
-
stream(runConfig) {
|
|
4351
|
-
const runId = randomUUID3();
|
|
4352
|
-
const run = new AgentRunController(this.provider, runId);
|
|
4353
|
-
const request = {
|
|
4354
|
-
runId,
|
|
4355
|
-
provider: this.provider,
|
|
4356
|
-
options: this.options,
|
|
4357
|
-
run: buildRunConfig(this.options, runConfig)
|
|
4358
|
-
};
|
|
4359
|
-
void (async () => {
|
|
4360
|
-
try {
|
|
4361
|
-
const cleanup = await this.adapter.execute(request, run);
|
|
4362
|
-
run.setAbort(async () => {
|
|
4363
|
-
await cleanup();
|
|
4364
|
-
});
|
|
4365
|
-
} catch (error) {
|
|
4366
|
-
run.fail(error);
|
|
4367
|
-
}
|
|
4368
|
-
})();
|
|
4369
|
-
return run;
|
|
4370
|
-
}
|
|
4371
|
-
async run(runConfig) {
|
|
4372
|
-
return this.stream(runConfig).finished;
|
|
4373
|
-
}
|
|
4374
|
-
rawEvents(runConfig) {
|
|
4375
|
-
return this.stream(runConfig).rawEvents();
|
|
4376
|
-
}
|
|
4377
|
-
};
|
|
4378
|
-
|
|
4379
|
-
export {
|
|
4380
|
-
Agent
|
|
4381
|
-
};
|