agentbox-sdk 0.1.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.
@@ -4,6 +4,7 @@ import {
4
4
  toAISDKStream
5
5
  } from "./chunk-7FLLQJ6J.js";
6
6
  import {
7
+ AGENT_RESERVED_PORTS,
7
8
  AgentBoxError,
8
9
  AsyncQueue,
9
10
  UnsupportedProviderError,
@@ -12,10 +13,14 @@ import {
12
13
  linesFromTextChunks,
13
14
  sleep,
14
15
  waitFor
15
- } from "./chunk-HMBWQSVN.js";
16
+ } from "./chunk-O7HCJXKW.js";
16
17
  import {
17
18
  shellQuote
18
- } from "./chunk-JFDP556Q.js";
19
+ } from "./chunk-NSJM57Z4.js";
20
+ import {
21
+ AgentProvider,
22
+ SandboxProvider
23
+ } from "./chunk-2NKMDGYH.js";
19
24
 
20
25
  // src/agents/Agent.ts
21
26
  import { randomUUID as randomUUID3 } from "crypto";
@@ -87,7 +92,7 @@ async function resolveUserInputParts(input) {
87
92
  }
88
93
  async function validateProviderUserInput(provider, input) {
89
94
  const parts = await resolveUserInputParts(input);
90
- if (provider === "codex") {
95
+ if (provider === AgentProvider.Codex) {
91
96
  const unsupportedPart = parts.find((part) => part.type === "file");
92
97
  if (unsupportedPart) {
93
98
  throw new AgentBoxError(
@@ -103,7 +108,7 @@ async function validateProviderUserInput(provider, input) {
103
108
  }
104
109
  return parts;
105
110
  }
106
- if (provider === "claude-code") {
111
+ if (provider === AgentProvider.ClaudeCode) {
107
112
  for (const part of parts) {
108
113
  if (part.type === "image") {
109
114
  if (!CLAUDE_IMAGE_MEDIA_TYPES.has(part.mediaType)) {
@@ -514,7 +519,7 @@ function assertCommandsSupported(provider, commands) {
514
519
  if (!commands || commands.length === 0) {
515
520
  return;
516
521
  }
517
- if (provider === "codex") {
522
+ if (provider === AgentProvider.Codex) {
518
523
  throw new Error(
519
524
  "Custom commands are not supported for Codex in this package yet."
520
525
  );
@@ -550,10 +555,10 @@ function readProviderPlugins(options) {
550
555
  return options.provider?.plugins;
551
556
  }
552
557
  function legacySharedHooksError(provider) {
553
- return provider === "opencode" ? "OpenCode hook plugins must be configured on options.provider.plugins. The shared options.hooks field was removed because hook semantics differ by provider." : `${provider === "claude-code" ? "Claude Code" : "Codex"} hooks must be configured on options.provider.hooks. The shared options.hooks field was removed because hook semantics differ by provider.`;
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.`;
554
559
  }
555
560
  function invalidGroupedHooksShapeError(provider) {
556
- return `${provider === "claude-code" ? "Claude Code" : "Codex"} hooks must use the native grouped hooks object shape under options.provider.hooks, with each event mapped to an array of matcher groups.`;
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.`;
557
562
  }
558
563
  function hasMalformedGroupedHookEntries(hooks) {
559
564
  return Object.values(hooks).some(
@@ -644,7 +649,7 @@ function assertHooksSupported(provider, options) {
644
649
  }
645
650
  const providerHooks = readProviderHooks(options);
646
651
  const providerPlugins = readProviderPlugins(options);
647
- if (provider === "opencode") {
652
+ if (provider === AgentProvider.OpenCode) {
648
653
  if (providerHooks !== void 0) {
649
654
  throw new Error(opencodeHooksFieldError());
650
655
  }
@@ -1033,18 +1038,17 @@ async function createRuntimeTarget(provider, runId, options) {
1033
1038
  import path6 from "path";
1034
1039
  function getSkillTargetDir(provider, layout, skillName) {
1035
1040
  switch (provider) {
1036
- case "claude-code":
1041
+ case AgentProvider.ClaudeCode:
1037
1042
  return path6.join(layout.claudeDir, "skills", skillName);
1038
- case "opencode":
1043
+ case AgentProvider.OpenCode:
1039
1044
  return path6.join(layout.opencodeDir, "skills", skillName);
1040
- case "codex":
1045
+ case AgentProvider.Codex:
1041
1046
  return path6.join(layout.agentsDir, "skills", skillName);
1042
1047
  }
1043
1048
  }
1044
1049
  function buildSkillsInstallerCommand(provider, skill) {
1045
1050
  const repo = skill.repo ?? "https://github.com/anthropics/skills";
1046
- const agent = provider === "claude-code" ? "claude-code" : provider;
1047
- return `npx skills add ${shellQuote(repo)} -g --skill ${shellQuote(skill.name)} --agent ${shellQuote(agent)} -y`;
1051
+ return `npx skills add ${shellQuote(repo)} -g --skill ${shellQuote(skill.name)} --agent ${shellQuote(provider)} -y`;
1048
1052
  }
1049
1053
  async function prepareSkillArtifacts(provider, skills, layout) {
1050
1054
  const artifacts = [];
@@ -1369,10 +1373,12 @@ var SharedSdkWsChannel = class {
1369
1373
  };
1370
1374
  var MAX_PENDING_MESSAGES_PER_RUN = 1e3;
1371
1375
  var SharedSdkWsConnection = class {
1372
- constructor(url) {
1376
+ constructor(url, headers = {}) {
1373
1377
  this.url = url;
1378
+ this.headers = headers;
1374
1379
  }
1375
1380
  url;
1381
+ headers;
1376
1382
  socket;
1377
1383
  channels = /* @__PURE__ */ new Map();
1378
1384
  pendingMessages = /* @__PURE__ */ new Map();
@@ -1380,7 +1386,7 @@ var SharedSdkWsConnection = class {
1380
1386
  if (this.socket?.readyState === WebSocket.OPEN) {
1381
1387
  return;
1382
1388
  }
1383
- const socket = new WebSocket(this.url);
1389
+ const socket = new WebSocket(this.url, { headers: this.headers });
1384
1390
  this.socket = socket;
1385
1391
  socket.on("message", (data) => {
1386
1392
  const lines = data.toString().split("\n").map((line) => line.trim()).filter(Boolean);
@@ -1512,7 +1518,7 @@ var REMOTE_SDK_RELAY_PATH = "/tmp/agentbox/claude-code/relay.mjs";
1512
1518
  var sharedRemoteConnectionBySandbox = /* @__PURE__ */ new WeakMap();
1513
1519
  function toRawEvent(runId, payload, type) {
1514
1520
  return {
1515
- provider: "claude-code",
1521
+ provider: AgentProvider.ClaudeCode,
1516
1522
  runId,
1517
1523
  type,
1518
1524
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1990,16 +1996,16 @@ function toClaudeRelayUrl(port, runId) {
1990
1996
  return parsed.toString();
1991
1997
  }
1992
1998
  function buildLocalSdkUrl(server, sandboxProvider) {
1993
- if (sandboxProvider === "local-docker") {
1999
+ if (sandboxProvider === SandboxProvider.LocalDocker) {
1994
2000
  return server.url.replace("127.0.0.1", "host.docker.internal").replace("0.0.0.0", "host.docker.internal");
1995
2001
  }
1996
2002
  return server.url;
1997
2003
  }
1998
- async function connectRemoteTransport(url) {
2004
+ async function connectRemoteTransport(url, headers = {}) {
1999
2005
  const startedAt = Date.now();
2000
2006
  let lastError;
2001
2007
  while (Date.now() - startedAt < 3e4) {
2002
- const client = new SharedSdkWsConnection(url);
2008
+ const client = new SharedSdkWsConnection(url, headers);
2003
2009
  try {
2004
2010
  await Promise.race([
2005
2011
  client.start(),
@@ -2018,11 +2024,11 @@ async function connectRemoteTransport(url) {
2018
2024
  }
2019
2025
  throw lastError ?? new Error(`Could not connect to remote SDK bridge at ${url}.`);
2020
2026
  }
2021
- async function canConnectToRemoteRelay(previewUrl) {
2027
+ async function canConnectToRemoteRelay(previewUrl, headers = {}) {
2022
2028
  const parsed = new URL(toWebSocketUrl(previewUrl));
2023
2029
  parsed.searchParams.set("role", "claude");
2024
2030
  parsed.searchParams.set("runId", "__probe__");
2025
- const client = new SharedSdkWsConnection(parsed.toString());
2031
+ const client = new SharedSdkWsConnection(parsed.toString(), headers);
2026
2032
  try {
2027
2033
  await Promise.race([
2028
2034
  client.start(),
@@ -2056,7 +2062,7 @@ async function ensureSharedRemoteConnection(sandbox, previewUrl) {
2056
2062
  }
2057
2063
  const created = (async () => {
2058
2064
  const url = toSharedHostWebSocketUrl(previewUrl);
2059
- const connection = await connectRemoteTransport(url);
2065
+ const connection = await connectRemoteTransport(url, sandbox.previewHeaders);
2060
2066
  return { previewUrl, connection };
2061
2067
  })();
2062
2068
  sharedRemoteConnectionBySandbox.set(key, created);
@@ -2071,7 +2077,8 @@ async function ensureRemoteRelay(request, prepared) {
2071
2077
  const sandbox = request.options.sandbox;
2072
2078
  await sandbox.openPort(REMOTE_SDK_RELAY_PORT);
2073
2079
  const previewUrl = await sandbox.getPreviewLink(REMOTE_SDK_RELAY_PORT);
2074
- if (await canConnectToRemoteRelay(previewUrl)) {
2080
+ const previewHeaders = sandbox.previewHeaders;
2081
+ if (await canConnectToRemoteRelay(previewUrl, previewHeaders)) {
2075
2082
  return {
2076
2083
  relayPort: REMOTE_SDK_RELAY_PORT,
2077
2084
  relayPath: REMOTE_SDK_RELAY_PATH,
@@ -2102,7 +2109,7 @@ async function ensureRemoteRelay(request, prepared) {
2102
2109
  });
2103
2110
  const startedAt = Date.now();
2104
2111
  while (Date.now() - startedAt < 3e4) {
2105
- if (await canConnectToRemoteRelay(previewUrl)) {
2112
+ if (await canConnectToRemoteRelay(previewUrl, previewHeaders)) {
2106
2113
  return {
2107
2114
  relayPort: REMOTE_SDK_RELAY_PORT,
2108
2115
  relayPath: REMOTE_SDK_RELAY_PATH,
@@ -2121,7 +2128,7 @@ async function ensureRemoteRelay(request, prepared) {
2121
2128
  async function createLocalRuntime(request, prepared) {
2122
2129
  const sandboxProvider = request.options.sandbox?.provider;
2123
2130
  const transport = new SdkWsServer({
2124
- host: sandboxProvider === "local-docker" ? "0.0.0.0" : "127.0.0.1"
2131
+ host: sandboxProvider === SandboxProvider.LocalDocker ? "0.0.0.0" : "127.0.0.1"
2125
2132
  });
2126
2133
  await transport.start();
2127
2134
  const args = prepared.buildArgs(buildLocalSdkUrl(transport, sandboxProvider));
@@ -2198,7 +2205,7 @@ async function createRemoteSandboxRuntime(request, prepared) {
2198
2205
  };
2199
2206
  }
2200
2207
  async function createRuntime(request) {
2201
- if (request.options.sandbox && request.options.sandbox.provider !== "local-docker") {
2208
+ if (request.options.sandbox && request.options.sandbox.provider !== SandboxProvider.LocalDocker) {
2202
2209
  await request.options.sandbox.openPort(REMOTE_SDK_RELAY_PORT);
2203
2210
  const prepared2 = await prepareClaudeRuntime(request);
2204
2211
  return createRemoteSandboxRuntime(request, prepared2);
@@ -2206,36 +2213,6 @@ async function createRuntime(request) {
2206
2213
  const prepared = await prepareClaudeRuntime(request);
2207
2214
  return createLocalRuntime(request, prepared);
2208
2215
  }
2209
- function buildDirectClaudeArgs(buildArgs, prompt) {
2210
- const args = buildArgs("ws://127.0.0.1:9/");
2211
- const directArgs = [];
2212
- for (let index = 0; index < args.length; index += 1) {
2213
- const current = args[index];
2214
- if (current === void 0) {
2215
- continue;
2216
- }
2217
- if (current === "--sdk-url" || current === "--input-format") {
2218
- index += 1;
2219
- continue;
2220
- }
2221
- directArgs.push(current);
2222
- }
2223
- if (!directArgs.includes("--verbose")) {
2224
- const printIndex = directArgs.indexOf("--print");
2225
- if (printIndex === -1) {
2226
- directArgs.unshift("--verbose");
2227
- } else {
2228
- directArgs.splice(printIndex + 1, 0, "--verbose");
2229
- }
2230
- }
2231
- const promptIndex = directArgs.lastIndexOf("-p");
2232
- if (promptIndex !== -1 && promptIndex + 1 < directArgs.length) {
2233
- directArgs[promptIndex + 1] = prompt;
2234
- } else {
2235
- directArgs.push("-p", prompt);
2236
- }
2237
- return directArgs;
2238
- }
2239
2216
  var ClaudeCodeAgentAdapter = class {
2240
2217
  async execute(request, sink) {
2241
2218
  const inputParts = await validateProviderUserInput(
@@ -2243,203 +2220,42 @@ var ClaudeCodeAgentAdapter = class {
2243
2220
  request.run.input
2244
2221
  );
2245
2222
  const userContent = mapToClaudeUserContent(inputParts);
2246
- if (request.options.sandbox?.provider === "e2b" && typeof userContent === "string" && !request.run.systemPrompt && !request.options.subAgents?.length) {
2247
- const prepared = await prepareClaudeRuntime(request);
2248
- const args = buildDirectClaudeArgs(prepared.buildArgs, userContent);
2249
- const handle = await request.options.sandbox.runAsync(
2250
- [request.options.provider?.binary ?? "claude", ...args],
2251
- {
2252
- cwd: request.options.cwd,
2253
- env: {
2254
- ...prepared.env,
2255
- IS_SANDBOX: "1"
2256
- },
2257
- timeoutMs: 0
2258
- }
2259
- );
2260
- sink.setRaw({
2261
- handle,
2262
- layout: prepared.target.layout,
2263
- mode: "e2b-direct"
2264
- });
2265
- sink.setAbort(async () => {
2266
- await handle.kill().catch(() => void 0);
2267
- await handle.wait().catch(() => void 0);
2268
- await prepared.target.cleanup().catch(() => void 0);
2269
- });
2270
- sink.emitEvent(
2271
- createNormalizedEvent("run.started", {
2272
- provider: request.provider,
2273
- runId: request.runId
2274
- })
2275
- );
2276
- const completion2 = new Promise((resolve, reject) => {
2277
- let accumulatedText2 = "";
2278
- async function* stdoutChunks() {
2279
- for await (const event of handle) {
2280
- if (event.type === "stdout" && event.chunk) {
2281
- yield event.chunk;
2282
- }
2283
- }
2284
- }
2285
- void (async () => {
2286
- for await (const line of linesFromTextChunks(stdoutChunks())) {
2287
- const trimmed = line.trim();
2288
- if (!trimmed) {
2289
- continue;
2290
- }
2291
- let message;
2292
- try {
2293
- message = JSON.parse(trimmed);
2294
- } catch {
2295
- continue;
2296
- }
2297
- sink.emitRaw(toRawEvent(request.runId, message, message.type));
2298
- if (message.type === "system" && message.subtype === "init") {
2299
- const sessionId2 = String(message.session_id ?? "");
2300
- if (sessionId2) {
2301
- sink.setSessionId(sessionId2);
2302
- }
2303
- sink.emitEvent(
2304
- createNormalizedEvent("message.started", {
2305
- provider: request.provider,
2306
- runId: request.runId
2307
- })
2308
- );
2309
- continue;
2310
- }
2311
- if (message.type === "stream_event") {
2312
- const delta = extractStreamDelta(message);
2313
- if (delta) {
2314
- accumulatedText2 += delta;
2315
- sink.emitEvent(
2316
- createNormalizedEvent(
2317
- "text.delta",
2318
- {
2319
- provider: request.provider,
2320
- runId: request.runId
2321
- },
2322
- { delta }
2323
- )
2324
- );
2325
- }
2326
- continue;
2327
- }
2328
- if (message.type === "assistant") {
2329
- const text = extractAssistantText(message);
2330
- if (text) {
2331
- accumulatedText2 = text;
2332
- sink.emitEvent(
2333
- createNormalizedEvent(
2334
- "text.delta",
2335
- {
2336
- provider: request.provider,
2337
- runId: request.runId
2338
- },
2339
- { delta: text }
2340
- )
2341
- );
2342
- }
2343
- sink.emitEvent(
2344
- createNormalizedEvent(
2345
- "message.completed",
2346
- {
2347
- provider: request.provider,
2348
- runId: request.runId
2349
- },
2350
- { text }
2351
- )
2352
- );
2353
- continue;
2354
- }
2355
- if (message.type === "result") {
2356
- if (String(message.subtype ?? "success") === "success") {
2357
- resolve({ text: accumulatedText2 });
2358
- } else {
2359
- reject(
2360
- new Error(
2361
- String(
2362
- message.result ?? message.error ?? "Claude Code run failed."
2363
- )
2364
- )
2365
- );
2366
- }
2367
- return;
2368
- }
2369
- if (message.type === "auth_status" && message.authenticated === false) {
2370
- reject(
2371
- new Error("Claude Code reported an authentication failure.")
2372
- );
2373
- return;
2374
- }
2375
- if (message.type === "control_request") {
2376
- reject(
2377
- new Error(
2378
- "Claude Code direct E2B mode does not yet support interactive control requests."
2379
- )
2380
- );
2381
- return;
2382
- }
2383
- }
2384
- const result = await handle.wait().catch((error) => error);
2385
- reject(
2386
- new Error(
2387
- result instanceof Error ? String(result) : "Claude Code direct E2B mode ended before returning a result."
2388
- )
2389
- );
2390
- })().catch(reject);
2391
- });
2392
- try {
2393
- const { text } = await completion2;
2394
- await handle.wait().catch(() => void 0);
2395
- sink.emitEvent(
2396
- createNormalizedEvent(
2397
- "run.completed",
2398
- { provider: request.provider, runId: request.runId },
2399
- { text }
2400
- )
2401
- );
2402
- sink.complete({ text });
2403
- } catch (error) {
2404
- await handle.kill().catch(() => void 0);
2405
- await handle.wait().catch(() => void 0);
2406
- sink.fail(error);
2407
- } finally {
2408
- await prepared.target.cleanup().catch(() => void 0);
2409
- }
2410
- return async () => {
2411
- await handle.kill().catch(() => void 0);
2412
- await handle.wait().catch(() => void 0);
2413
- };
2414
- }
2415
- const runtime = await createRuntime(request);
2416
- sink.setRaw(runtime.raw);
2417
- sink.setAbort(runtime.cleanup);
2418
- sink.emitEvent(
2419
- createNormalizedEvent("run.started", {
2420
- provider: request.provider,
2421
- runId: request.runId
2422
- })
2423
- );
2424
2223
  let sessionId = "";
2425
2224
  let accumulatedText = "";
2426
2225
  let usedStreaming = false;
2427
2226
  let pendingMessages = 1;
2428
2227
  const autoApproveTools = shouldAutoApproveClaudeTools(request.options);
2228
+ const transportRef = {};
2229
+ const queuedSends = [];
2429
2230
  sink.onMessage(async (content) => {
2430
2231
  pendingMessages++;
2431
2232
  const parts = await validateProviderUserInput(request.provider, content);
2432
2233
  const mapped = mapToClaudeUserContent(parts);
2433
2234
  accumulatedText = "";
2434
2235
  usedStreaming = false;
2435
- await runtime.transport.send({
2236
+ const payload = {
2436
2237
  type: "user",
2437
2238
  message: { role: "user", content: mapped },
2438
2239
  parent_tool_use_id: null,
2439
2240
  session_id: sessionId || request.run.resumeSessionId || "",
2440
2241
  uuid: randomUUID2()
2441
- });
2242
+ };
2243
+ if (transportRef.current) {
2244
+ await transportRef.current.send(payload);
2245
+ } else {
2246
+ queuedSends.push(payload);
2247
+ }
2442
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
+ );
2443
2259
  const completion = new Promise((resolve, reject) => {
2444
2260
  void (async () => {
2445
2261
  for await (const message of runtime.transport.messages()) {
@@ -2599,6 +2415,9 @@ var ClaudeCodeAgentAdapter = class {
2599
2415
  runId: request.runId
2600
2416
  })
2601
2417
  );
2418
+ for (const queued of queuedSends.splice(0)) {
2419
+ await runtime.transport.send(queued);
2420
+ }
2602
2421
  const { text } = await completion;
2603
2422
  sink.emitEvent(
2604
2423
  createNormalizedEvent(
@@ -2631,7 +2450,21 @@ async function fetchJson(url, init) {
2631
2450
  if (!response.ok) {
2632
2451
  throw new Error(`Request to ${url} failed with ${response.status}.`);
2633
2452
  }
2634
- return await response.json();
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
+ }
2635
2468
  }
2636
2469
  async function* streamSse(url, init) {
2637
2470
  const response = await fetch(url, init);
@@ -2672,9 +2505,9 @@ async function* streamSse(url, init) {
2672
2505
  })();
2673
2506
  yield* queue;
2674
2507
  }
2675
- async function connectJsonRpcWebSocket(url) {
2508
+ async function connectJsonRpcWebSocket(url, options) {
2676
2509
  const notifications = new AsyncQueue();
2677
- const socket = new WebSocket2(url);
2510
+ const socket = new WebSocket2(url, { headers: options?.headers });
2678
2511
  await new Promise((resolve, reject) => {
2679
2512
  const cleanup = () => {
2680
2513
  socket.off("open", handleOpen);
@@ -2817,7 +2650,9 @@ function buildThreadParams(cwd, options, request) {
2817
2650
  approvalPolicy: isInteractiveApproval(options) ? "untrusted" : "never",
2818
2651
  sandbox: buildCodexSandboxMode(options),
2819
2652
  serviceName: "agentbox",
2820
- ephemeral: true,
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 ...".
2821
2656
  experimentalRawEvents: true
2822
2657
  };
2823
2658
  }
@@ -2834,7 +2669,7 @@ function buildTurnSandboxPolicy(options) {
2834
2669
  if (!options.sandbox) {
2835
2670
  return void 0;
2836
2671
  }
2837
- if (options.sandbox.provider === "local-docker") {
2672
+ if (options.sandbox.provider === SandboxProvider.LocalDocker) {
2838
2673
  return {
2839
2674
  type: "workspaceWrite",
2840
2675
  networkAccess: true
@@ -2858,7 +2693,7 @@ function buildTurnCollaborationMode(request) {
2858
2693
  }
2859
2694
  function toRawEvent2(runId, payload, type) {
2860
2695
  return {
2861
- provider: "codex",
2696
+ provider: AgentProvider.Codex,
2862
2697
  runId,
2863
2698
  type,
2864
2699
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -2876,7 +2711,7 @@ function buildCodexCommandArgs(binary, args) {
2876
2711
  }
2877
2712
  function toNormalizedCodexEvents(runId, notification) {
2878
2713
  const base = {
2879
- provider: "codex",
2714
+ provider: AgentProvider.Codex,
2880
2715
  runId,
2881
2716
  raw: toRawEvent2(runId, notification, notification.method)
2882
2717
  };
@@ -3070,16 +2905,22 @@ async function materializeCodexImage(target, part, index) {
3070
2905
  );
3071
2906
  return imagePath;
3072
2907
  }
2908
+ function resolveCodexOpenAiBaseUrl(request) {
2909
+ return request.options.env?.OPENAI_BASE_URL ?? request.options.provider?.env?.OPENAI_BASE_URL;
2910
+ }
3073
2911
  async function ensureCodexLogin(request, target) {
3074
- const openAiApiKey = request.options.env?.OPENAI_API_KEY;
3075
- if (!openAiApiKey) {
3076
- return;
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;
3077
2920
  }
3078
2921
  await target.runCommand(
3079
- 'if [ -z "$OPENAI_API_KEY" ]; then exit 1; fi; printenv OPENAI_API_KEY | env -u CODEX_HOME -u XDG_CONFIG_HOME codex login --with-api-key >/dev/null 2>&1',
3080
- {
3081
- OPENAI_API_KEY: openAiApiKey
3082
- }
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
3083
2924
  );
3084
2925
  }
3085
2926
  function toRemoteCodexWebSocketUrl(url) {
@@ -3087,12 +2928,12 @@ function toRemoteCodexWebSocketUrl(url) {
3087
2928
  parsed.protocol = parsed.protocol === "https:" ? "wss:" : "ws:";
3088
2929
  return parsed.toString();
3089
2930
  }
3090
- async function connectRemoteCodexAppServer(url) {
2931
+ async function connectRemoteCodexAppServer(url, headers = {}) {
3091
2932
  const startedAt = Date.now();
3092
2933
  let lastError;
3093
2934
  while (Date.now() - startedAt < 3e4) {
3094
2935
  try {
3095
- return await connectJsonRpcWebSocket(url);
2936
+ return await connectJsonRpcWebSocket(url, { headers });
3096
2937
  } catch (error) {
3097
2938
  lastError = error;
3098
2939
  await sleep(250);
@@ -3117,7 +2958,7 @@ async function waitForInternalCodexReady(sandbox, port, cwd, env) {
3117
2958
  }
3118
2959
  async function createRuntime2(request, inputParts) {
3119
2960
  const options = request.options;
3120
- const usesRemoteWebSocket = options.sandbox && options.sandbox.provider !== "local-docker";
2961
+ const usesRemoteWebSocket = options.sandbox && options.sandbox.provider !== SandboxProvider.LocalDocker;
3121
2962
  const hooks = assertHooksSupported(request.provider, options);
3122
2963
  assertCommandsSupported(request.provider, options.commands);
3123
2964
  if (usesRemoteWebSocket && options.sandbox) {
@@ -3167,6 +3008,10 @@ async function createRuntime2(request, inputParts) {
3167
3008
  }
3168
3009
  const configArgs2 = [];
3169
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
+ }
3170
3015
  const binary = options.provider?.binary ?? "codex";
3171
3016
  const pidFilePath = path9.posix.join(
3172
3017
  sharedTarget.layout.rootDir,
@@ -3251,7 +3096,8 @@ async function createRuntime2(request, inputParts) {
3251
3096
  );
3252
3097
  inputItems2.push(...buildCodexSkillInputItems(preparedSkills2));
3253
3098
  const transport = await connectRemoteCodexAppServer(
3254
- toRemoteCodexWebSocketUrl(previewUrl)
3099
+ toRemoteCodexWebSocketUrl(previewUrl),
3100
+ sandbox.previewHeaders
3255
3101
  );
3256
3102
  return {
3257
3103
  source: transport.source,
@@ -3349,6 +3195,10 @@ async function createRuntime2(request, inputParts) {
3349
3195
  );
3350
3196
  }
3351
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
+ }
3352
3202
  const textPrompt = joinTextParts(
3353
3203
  inputParts.filter(
3354
3204
  (part) => part.type === "text"
@@ -3447,7 +3297,6 @@ var CodexAgentAdapter = class {
3447
3297
  );
3448
3298
  const runtime = await createRuntime2(request, inputParts);
3449
3299
  sink.setRaw(runtime.raw);
3450
- sink.setAbort(runtime.cleanup);
3451
3300
  sink.emitEvent(
3452
3301
  createNormalizedEvent("run.started", {
3453
3302
  provider: request.provider,
@@ -3462,6 +3311,28 @@ var CodexAgentAdapter = class {
3462
3311
  let rootThreadId;
3463
3312
  let turnId;
3464
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
+ });
3465
3336
  const sendTurn = async (content) => {
3466
3337
  if (!rootThreadId) {
3467
3338
  throw new Error("Cannot send message before thread is started.");
@@ -3598,7 +3469,7 @@ var SANDBOX_OPENCODE_READY_TIMEOUT_MS = 9e4;
3598
3469
  var SHARED_OPENCODE_TARGET_ID = "shared-opencode-server";
3599
3470
  function toRawEvent3(runId, payload, type) {
3600
3471
  return {
3601
- provider: "opencode",
3472
+ provider: AgentProvider.OpenCode,
3602
3473
  runId,
3603
3474
  type,
3604
3475
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -3725,6 +3596,7 @@ async function ensureSandboxOpenCodeServer(request) {
3725
3596
  const options = request.options;
3726
3597
  const port = SANDBOX_OPENCODE_PORT;
3727
3598
  await sandbox.openPort(port);
3599
+ const previewHeaders = sandbox.previewHeaders;
3728
3600
  const healthCheck = await sandbox.run(
3729
3601
  `curl -fsS http://127.0.0.1:${port}/global/health >/dev/null 2>&1`,
3730
3602
  { cwd: options.cwd, timeoutMs: 5e3 }
@@ -3733,6 +3605,7 @@ async function ensureSandboxOpenCodeServer(request) {
3733
3605
  const baseUrl2 = (await sandbox.getPreviewLink(port)).replace(/\/$/, "");
3734
3606
  return {
3735
3607
  baseUrl: baseUrl2,
3608
+ previewHeaders,
3736
3609
  cleanup: async () => {
3737
3610
  },
3738
3611
  raw: { baseUrl: baseUrl2, port, reused: true }
@@ -3806,12 +3679,29 @@ async function ensureSandboxOpenCodeServer(request) {
3806
3679
  `Could not start OpenCode server: ${launchResult.combinedOutput || launchResult.stderr}`
3807
3680
  );
3808
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
+ }
3809
3701
  const baseUrl = (await sandbox.getPreviewLink(port)).replace(/\/$/, "");
3810
- await waitForHttpReady(`${baseUrl}/global/health`, {
3811
- timeoutMs: SANDBOX_OPENCODE_READY_TIMEOUT_MS
3812
- });
3813
3702
  return {
3814
3703
  baseUrl,
3704
+ previewHeaders,
3815
3705
  cleanup: async () => {
3816
3706
  await target.cleanup().catch(() => void 0);
3817
3707
  },
@@ -3874,6 +3764,7 @@ async function createLocalRuntime2(request) {
3874
3764
  await waitForHttpReady(`${baseUrl}/global/health`, { timeoutMs: 2e4 });
3875
3765
  return {
3876
3766
  baseUrl,
3767
+ previewHeaders: {},
3877
3768
  cleanup: async () => {
3878
3769
  await processHandle.kill();
3879
3770
  await target.cleanup();
@@ -3893,9 +3784,41 @@ var OpenCodeAgentAdapter = class {
3893
3784
  request.provider,
3894
3785
  request.run.input
3895
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
+ });
3896
3820
  const runtime = await createRuntime3(request);
3897
3821
  sink.setRaw(runtime.raw);
3898
- sink.setAbort(runtime.cleanup);
3899
3822
  sink.emitEvent(
3900
3823
  createNormalizedEvent("run.started", {
3901
3824
  provider: request.provider,
@@ -3904,13 +3827,47 @@ var OpenCodeAgentAdapter = class {
3904
3827
  );
3905
3828
  const sseAbort = new AbortController();
3906
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
+ });
3907
3861
  try {
3908
3862
  const interactiveApproval = isInteractiveApproval(request.options);
3909
3863
  const createdSession = request.run.resumeSessionId ? null : await fetchJson(
3910
3864
  `${runtime.baseUrl}/session`,
3911
3865
  {
3912
3866
  method: "POST",
3913
- headers: { "content-type": "application/json" },
3867
+ headers: {
3868
+ "content-type": "application/json",
3869
+ ...runtime.previewHeaders
3870
+ },
3914
3871
  body: JSON.stringify({
3915
3872
  title: `AgentBox ${request.runId}`
3916
3873
  })
@@ -3923,6 +3880,7 @@ var OpenCodeAgentAdapter = class {
3923
3880
  sseTask = (async () => {
3924
3881
  try {
3925
3882
  for await (const event of streamSse(`${runtime.baseUrl}/event`, {
3883
+ headers: runtime.previewHeaders,
3926
3884
  signal: sseAbort.signal
3927
3885
  })) {
3928
3886
  let payload = event.data;
@@ -3945,7 +3903,7 @@ var OpenCodeAgentAdapter = class {
3945
3903
  raw,
3946
3904
  payload
3947
3905
  );
3948
- const response2 = interactiveApproval ? await sink.requestPermission(permissionEvent) : {
3906
+ const response = interactiveApproval ? await sink.requestPermission(permissionEvent) : {
3949
3907
  requestId: permissionEvent.requestId,
3950
3908
  decision: "allow"
3951
3909
  };
@@ -3953,9 +3911,12 @@ var OpenCodeAgentAdapter = class {
3953
3911
  `${runtime.baseUrl}/session/${sessionId}/permissions/${permissionEvent.requestId}`,
3954
3912
  {
3955
3913
  method: "POST",
3956
- headers: { "content-type": "application/json" },
3914
+ headers: {
3915
+ "content-type": "application/json",
3916
+ ...runtime.previewHeaders
3917
+ },
3957
3918
  body: JSON.stringify({
3958
- response: response2.decision === "allow" ? response2.remember ? "always" : "once" : "reject"
3919
+ response: response.decision === "allow" ? response.remember ? "always" : "once" : "reject"
3959
3920
  })
3960
3921
  }
3961
3922
  );
@@ -3969,11 +3930,7 @@ var OpenCodeAgentAdapter = class {
3969
3930
  } catch {
3970
3931
  }
3971
3932
  })();
3972
- sink.onMessage(async () => {
3973
- console.warn(
3974
- "[agentbox] sendMessage is not yet supported for the opencode provider. Use resumeSessionId for follow-ups instead."
3975
- );
3976
- });
3933
+ capturedSessionId = sessionId;
3977
3934
  sink.setSessionId(sessionId);
3978
3935
  sink.emitRaw(
3979
3936
  toRawEvent3(
@@ -3988,39 +3945,67 @@ var OpenCodeAgentAdapter = class {
3988
3945
  runId: request.runId
3989
3946
  })
3990
3947
  );
3991
- const response = await fetchJson(
3992
- `${runtime.baseUrl}/session/${sessionId}/message`,
3993
- {
3994
- method: "POST",
3995
- headers: { "content-type": "application/json" },
3996
- body: JSON.stringify({
3997
- ...request.run.model ? { model: toOpenCodeModel(request.run.model) } : {},
3998
- agent: "agentbox",
3999
- parts: mapToOpenCodeParts(inputParts)
4000
- })
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();
4001
3996
  }
4002
- );
4003
- const rawResponse = toRawEvent3(
4004
- request.runId,
4005
- response,
4006
- "message.response"
4007
- );
4008
- sink.emitRaw(rawResponse);
4009
- for (const event of normalizeRawAgentEvent(rawResponse)) {
4010
- sink.emitEvent(event);
3997
+ };
3998
+ sendToSession = (parts) => {
3999
+ void dispatchMessage(parts);
4000
+ };
4001
+ for (const queued of queuedParts.splice(0)) {
4002
+ sendToSession(queued);
4011
4003
  }
4012
- const text = extractText(response);
4013
- if (text) {
4014
- sink.emitEvent(
4015
- createNormalizedEvent(
4016
- "text.delta",
4017
- {
4018
- provider: request.provider,
4019
- runId: request.runId
4020
- },
4021
- { delta: text }
4022
- )
4023
- );
4004
+ pendingMessages++;
4005
+ void dispatchMessage(mapToOpenCodeParts(inputParts));
4006
+ await allDone;
4007
+ if (dispatchError) {
4008
+ throw dispatchError;
4024
4009
  }
4025
4010
  sink.emitEvent(
4026
4011
  createNormalizedEvent(
@@ -4029,12 +4014,12 @@ var OpenCodeAgentAdapter = class {
4029
4014
  provider: request.provider,
4030
4015
  runId: request.runId
4031
4016
  },
4032
- { text }
4017
+ { text: finalText }
4033
4018
  )
4034
4019
  );
4035
4020
  sseAbort.abort();
4036
4021
  await sseTask;
4037
- sink.complete({ text });
4022
+ sink.complete({ text: finalText });
4038
4023
  } finally {
4039
4024
  sseAbort.abort();
4040
4025
  if (sseTask) {
@@ -4097,21 +4082,20 @@ function buildRunConfig(options, runConfig) {
4097
4082
  }
4098
4083
  function createAdapter(provider) {
4099
4084
  switch (provider) {
4100
- case "codex":
4085
+ case AgentProvider.Codex:
4101
4086
  return new CodexAgentAdapter();
4102
- case "opencode":
4087
+ case AgentProvider.OpenCode:
4103
4088
  return new OpenCodeAgentAdapter();
4104
- case "claude-code":
4089
+ case AgentProvider.ClaudeCode:
4105
4090
  return new ClaudeCodeAgentAdapter();
4106
4091
  default:
4107
4092
  throw new UnsupportedProviderError("agent", provider);
4108
4093
  }
4109
4094
  }
4110
4095
  function prepareAgentOptions(provider, options) {
4111
- if (provider === "opencode") {
4112
- const openCodeOptions = options;
4113
- const port = 4096;
4114
- openCodeOptions.sandbox?.openPort(port);
4096
+ const ports = AGENT_RESERVED_PORTS[provider] ?? [];
4097
+ for (const port of ports) {
4098
+ void options.sandbox?.openPort(port);
4115
4099
  }
4116
4100
  return options;
4117
4101
  }