agentbox-sdk 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4381 @@
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
+ };