agentbox-sdk 0.0.0 → 0.1.1

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