agentbox-sdk 0.1.323 → 0.1.500

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -145,6 +145,62 @@ await agent.run({
145
145
 
146
146
  `xhigh` requires a model that supports it (e.g. Claude Opus 4.7+, Codex `gpt-5.4`).
147
147
 
148
+ ### Open-source & custom models (OpenRouter, OSS)
149
+
150
+ Codex isn't limited to OpenAI models — it can route through any
151
+ OpenAI-compatible endpoint (OpenRouter, a local Ollama/LM Studio/vLLM
152
+ server, a proxy). Just like the opencode provider lights up OpenRouter
153
+ from `OPENROUTER_API_KEY`, the codex provider does too: set the key in the
154
+ agent env and pass an OpenRouter model slug.
155
+
156
+ ```ts
157
+ const agent = new Agent("codex", {
158
+ sandbox,
159
+ cwd: "/workspace",
160
+ approvalMode: "auto",
161
+ env: { OPENROUTER_API_KEY: process.env.OPENROUTER_API_KEY! },
162
+ });
163
+
164
+ await agent.run({
165
+ model: "openai/gpt-5.3-codex", // any OpenRouter model slug
166
+ input: "Explain the project structure.",
167
+ });
168
+ ```
169
+
170
+ When `OPENROUTER_API_KEY` is present (and `OPENAI_API_KEY` is not), AgentBox
171
+ auto-registers an `openrouter` model provider pointing at
172
+ `https://openrouter.ai/api/v1` and selects it. Override the endpoint with
173
+ `OPENROUTER_BASE_URL`.
174
+
175
+ For any other OpenAI-compatible endpoint, declare providers explicitly via
176
+ `provider.modelProviders` and pick one with `provider.modelProvider`:
177
+
178
+ ```ts
179
+ new Agent("codex", {
180
+ sandbox,
181
+ cwd: "/workspace",
182
+ env: { TOGETHER_API_KEY: process.env.TOGETHER_API_KEY! },
183
+ provider: {
184
+ modelProvider: "together",
185
+ modelProviders: {
186
+ together: {
187
+ name: "Together",
188
+ baseUrl: "https://api.together.xyz/v1",
189
+ envKey: "TOGETHER_API_KEY",
190
+ wireApi: "responses", // codex removed the "chat" wire API
191
+ },
192
+ },
193
+ },
194
+ });
195
+ ```
196
+
197
+ These are written into Codex's `config.toml` as `[model_providers.*]`
198
+ blocks, which the codex app-server reads via `CODEX_HOME`. The model slug
199
+ stays a per-run value; the provider is agent-level config. Note that codex
200
+ dropped the Chat Completions wire API in early 2026 — providers must speak
201
+ the Responses API (`wire_api = "responses"`), which OpenRouter and LM
202
+ Studio support; chat-only backends need a responses→chat proxy.
203
+
148
204
  ## Sandboxes
149
205
 
150
206
  Five sandbox providers are supported. Each gives you an isolated environment with the same interface:
@@ -1,5 +1,5 @@
1
- import { o as AgentProviderName, h as AgentOptions, t as AgentRunConfig, s as AgentRun, r as AgentResult, a3 as RawAgentEvent, b as AgentAttachRequest, y as AttachedRun, aa as SetupLayout } from '../types-t01PuLUJ.js';
2
- export { a as AgentApprovalMode, c as AgentCommandConfig, d as AgentCostData, e as AgentExecutionRequest, f as AgentLocalMcpConfig, g as AgentMcpConfig, i as AgentOptionsBase, j as AgentOptionsMap, k as AgentPermissionDecision, l as AgentPermissionKind, m as AgentPermissionResponse, n as AgentProviderAdapter, p as AgentReasoningEffort, q as AgentRemoteMcpConfig, u as AgentRunSink, v as AgentSetupRequest, w as AgentSkillConfig, x as AgentSubAgentConfig, C as ClaudeCodeAgentOptions, z as ClaudeCodeHookConfig, B as ClaudeCodeHookEvent, D as ClaudeCodeHookHandler, E as ClaudeCodeHookMatcherGroup, F as ClaudeCodeHooksConfig, G as ClaudeCodeProviderOptions, H as CodexAgentOptions, I as CodexCommandHook, J as CodexHookEvent, K as CodexHookMatcherGroup, L as CodexHooksConfig, M as CodexProviderOptions, N as DataContent, O as EmbeddedSkillConfig, P as FilePart, Q as ImagePart, X as OpenCodeAgentOptions, Y as OpenCodePluginConfig, Z as OpenCodePluginEvent, _ as OpenCodePluginHookConfig, $ as OpenCodeProviderOptions, a0 as OpenRouterPlugin, a5 as RepoSkillConfig, ac as TextPart, ag as UserContent, ah as UserContentPart } from '../types-t01PuLUJ.js';
1
+ import { o as AgentProviderName, h as AgentOptions, t as AgentRunConfig, s as AgentRun, r as AgentResult, a4 as RawAgentEvent, b as AgentAttachRequest, y as AttachedRun, ab as SetupLayout } from '../types-1seasIwq.js';
2
+ export { a as AgentApprovalMode, c as AgentCommandConfig, d as AgentCostData, e as AgentExecutionRequest, f as AgentLocalMcpConfig, g as AgentMcpConfig, i as AgentOptionsBase, j as AgentOptionsMap, k as AgentPermissionDecision, l as AgentPermissionKind, m as AgentPermissionResponse, n as AgentProviderAdapter, p as AgentReasoningEffort, q as AgentRemoteMcpConfig, u as AgentRunSink, v as AgentSetupRequest, w as AgentSkillConfig, x as AgentSubAgentConfig, C as ClaudeCodeAgentOptions, z as ClaudeCodeHookConfig, B as ClaudeCodeHookEvent, D as ClaudeCodeHookHandler, E as ClaudeCodeHookMatcherGroup, F as ClaudeCodeHooksConfig, G as ClaudeCodeProviderOptions, H as CodexAgentOptions, I as CodexCommandHook, J as CodexHookEvent, K as CodexHookMatcherGroup, L as CodexHooksConfig, M as CodexModelProviderConfig, N as CodexProviderOptions, O as DataContent, P as EmbeddedSkillConfig, Q as FilePart, R as ImagePart, Y as OpenCodeAgentOptions, Z as OpenCodePluginConfig, _ as OpenCodePluginEvent, $ as OpenCodePluginHookConfig, a0 as OpenCodeProviderOptions, a1 as OpenRouterPlugin, a6 as RepoSkillConfig, ad as TextPart, ah as UserContent, ai as UserContentPart } from '../types-1seasIwq.js';
3
3
  import { S as Sandbox } from '../Sandbox-DcKAU-E3.js';
4
4
  export { AgentProvider } from '../enums.js';
5
5
  import 'e2b';
@@ -2,7 +2,7 @@ import {
2
2
  Agent,
3
3
  agentboxRoot,
4
4
  getAgentLayout
5
- } from "../chunk-XONT46YC.js";
5
+ } from "../chunk-JYI7MRGO.js";
6
6
  import "../chunk-775FIGGL.js";
7
7
  import {
8
8
  AGENT_RESERVED_PORTS,
@@ -583,6 +583,9 @@ function buildClaudeHookSettings(hooks) {
583
583
  }
584
584
  return { hooks };
585
585
  }
586
+ function buildClaudeWorkflowSettings(ultracode) {
587
+ return ultracode ? { enableWorkflows: true, ultracode: true } : {};
588
+ }
586
589
  function buildCodexHooksFile(hooks) {
587
590
  if (!hasHookEntries(hooks)) {
588
591
  return void 0;
@@ -702,6 +705,47 @@ function tomlString(value) {
702
705
  function tomlStringArray(values) {
703
706
  return `[${values.map(tomlString).join(", ")}]`;
704
707
  }
708
+ function tomlInlineTable(values) {
709
+ const entries = Object.entries(values).map(
710
+ ([key, value]) => `${tomlString(key)} = ${tomlString(value)}`
711
+ );
712
+ return `{ ${entries.join(", ")} }`;
713
+ }
714
+ function appendCodexModelProviderBlock(blocks, id, cfg) {
715
+ assertSafeTomlKey(id, "Model provider");
716
+ blocks.push(`[model_providers.${id}]`);
717
+ blocks.push(`name = ${tomlString(cfg.name ?? id)}`);
718
+ if (cfg.baseUrl) {
719
+ blocks.push(`base_url = ${tomlString(cfg.baseUrl)}`);
720
+ }
721
+ if (cfg.envKey) {
722
+ blocks.push(`env_key = ${tomlString(cfg.envKey)}`);
723
+ }
724
+ if (cfg.wireApi) {
725
+ blocks.push(`wire_api = ${tomlString(cfg.wireApi)}`);
726
+ }
727
+ if (cfg.queryParams && Object.keys(cfg.queryParams).length > 0) {
728
+ blocks.push(`query_params = ${tomlInlineTable(cfg.queryParams)}`);
729
+ }
730
+ if (cfg.httpHeaders && Object.keys(cfg.httpHeaders).length > 0) {
731
+ blocks.push(`http_headers = ${tomlInlineTable(cfg.httpHeaders)}`);
732
+ }
733
+ if (cfg.envHttpHeaders && Object.keys(cfg.envHttpHeaders).length > 0) {
734
+ blocks.push(`env_http_headers = ${tomlInlineTable(cfg.envHttpHeaders)}`);
735
+ }
736
+ if (cfg.requestMaxRetries !== void 0) {
737
+ blocks.push(`request_max_retries = ${Math.trunc(cfg.requestMaxRetries)}`);
738
+ }
739
+ if (cfg.streamMaxRetries !== void 0) {
740
+ blocks.push(`stream_max_retries = ${Math.trunc(cfg.streamMaxRetries)}`);
741
+ }
742
+ if (cfg.streamIdleTimeoutMs !== void 0) {
743
+ blocks.push(
744
+ `stream_idle_timeout_ms = ${Math.trunc(cfg.streamIdleTimeoutMs)}`
745
+ );
746
+ }
747
+ blocks.push("");
748
+ }
705
749
  function buildClaudeMcpConfig(mcps) {
706
750
  if (!mcps || mcps.length === 0) {
707
751
  return void 0;
@@ -777,13 +821,27 @@ function buildCodexConfigToml(opts = {}) {
777
821
  enableHooks = false,
778
822
  enableSkills = false,
779
823
  enableMultiAgent = false,
780
- openAiBaseUrl
824
+ openAiBaseUrl,
825
+ model,
826
+ modelProvider,
827
+ modelProviders
781
828
  } = opts;
782
829
  const blocks = [];
783
830
  if (openAiBaseUrl) {
784
831
  blocks.push(`openai_base_url = ${tomlString(openAiBaseUrl)}`);
785
832
  blocks.push("");
786
833
  }
834
+ if (model) {
835
+ blocks.push(`model = ${tomlString(model)}`);
836
+ blocks.push("");
837
+ }
838
+ if (modelProvider) {
839
+ blocks.push(`model_provider = ${tomlString(modelProvider)}`);
840
+ blocks.push("");
841
+ }
842
+ for (const [id, cfg] of Object.entries(modelProviders ?? {})) {
843
+ appendCodexModelProviderBlock(blocks, id, cfg);
844
+ }
787
845
  for (const mcp of mcps ?? []) {
788
846
  if (mcp.enabled === false) {
789
847
  continue;
@@ -1228,13 +1286,14 @@ async function preflightSetup(target, setupId, daemon) {
1228
1286
  ];
1229
1287
  if (daemon) {
1230
1288
  const url = `http://127.0.0.1:${daemon.port}${daemon.healthPath}`;
1289
+ const auth = daemon.curlAuthArg ? `${daemon.curlAuthArg} ` : "";
1231
1290
  if (daemon.expectedVersionMatch) {
1232
1291
  checks.push(
1233
- `curl -fsS --max-time 2 ${shellQuote(url)} 2>/dev/null | grep -q ${shellQuote(daemon.expectedVersionMatch)}`
1292
+ `curl -fsS --max-time 2 ${auth}${shellQuote(url)} 2>/dev/null | grep -q ${shellQuote(daemon.expectedVersionMatch)}`
1234
1293
  );
1235
1294
  } else {
1236
1295
  checks.push(
1237
- `curl -fsS --max-time 2 ${shellQuote(url)} >/dev/null 2>&1`
1296
+ `curl -fsS --max-time 2 ${auth}${shellQuote(url)} >/dev/null 2>&1`
1238
1297
  );
1239
1298
  }
1240
1299
  }
@@ -1467,12 +1526,18 @@ function buildOpenCodeSubagentConfig(subAgents) {
1467
1526
  mode: "subagent",
1468
1527
  description: subAgent.description,
1469
1528
  prompt: subAgent.instructions,
1529
+ // opencode reads a per-agent `model` as a "<providerID>/<modelID>"
1530
+ // string (e.g. "openrouter/deepseek/deepseek-v4-flash"). Without it,
1531
+ // the sub-agent silently inherits the orchestrator's model instead of
1532
+ // the one it was configured with — unlike claude-code (frontmatter
1533
+ // `model`) and codex (TOML `model`), which both carry it through.
1534
+ ...subAgent.model ? { model: subAgent.model } : {},
1470
1535
  ...mapOpenCodeTools(subAgent.tools) ? { tools: mapOpenCodeTools(subAgent.tools) } : {}
1471
1536
  }
1472
1537
  ])
1473
1538
  );
1474
1539
  }
1475
- function buildCodexSubagentArtifacts(subAgents, layout) {
1540
+ function buildCodexSubagentArtifacts(subAgents, layout, defaultModel) {
1476
1541
  const artifacts = [];
1477
1542
  const agentSections = [];
1478
1543
  for (const subAgent of subAgents ?? []) {
@@ -1485,8 +1550,9 @@ function buildCodexSubagentArtifacts(subAgents, layout) {
1485
1550
  const tomlLines = [
1486
1551
  `model_instructions_file = ${tomlString2(roleConfigPromptPath)}`
1487
1552
  ];
1488
- if (subAgent.model) {
1489
- tomlLines.push(`model = ${tomlString2(subAgent.model)}`);
1553
+ const model = subAgent.model ?? defaultModel;
1554
+ if (model) {
1555
+ tomlLines.push(`model = ${tomlString2(model)}`);
1490
1556
  }
1491
1557
  tomlLines.push(
1492
1558
  `model_reasoning_effort = ${tomlString2("medium")}`,
@@ -1513,6 +1579,46 @@ function buildCodexSubagentArtifacts(subAgents, layout) {
1513
1579
  };
1514
1580
  }
1515
1581
 
1582
+ // src/agents/config/capability-token.ts
1583
+ import crypto from "crypto";
1584
+ var tokenCache = /* @__PURE__ */ new WeakMap();
1585
+ async function readCapabilityTokenFile(sandbox, tokenFilePath) {
1586
+ const result = await sandbox.run(
1587
+ `if [ -f ${shellQuote(tokenFilePath)} ]; then cat ${shellQuote(tokenFilePath)}; fi`
1588
+ );
1589
+ if (result.exitCode !== 0) return void 0;
1590
+ const value = result.stdout.trim();
1591
+ return value.length > 0 ? value : void 0;
1592
+ }
1593
+ function resolveCapabilityToken(sandbox, tokenFilePath, create) {
1594
+ let byPath = tokenCache.get(sandbox);
1595
+ if (!byPath) {
1596
+ byPath = /* @__PURE__ */ new Map();
1597
+ tokenCache.set(sandbox, byPath);
1598
+ }
1599
+ const map = byPath;
1600
+ let cached = map.get(tokenFilePath);
1601
+ if (!cached) {
1602
+ const pending = (async () => {
1603
+ const existing = await readCapabilityTokenFile(sandbox, tokenFilePath);
1604
+ if (existing) return existing;
1605
+ if (create) return crypto.randomBytes(32).toString("hex");
1606
+ throw new Error(
1607
+ `Capability token file is missing at ${tokenFilePath}. setup() must run before connecting to this sandbox server.`
1608
+ );
1609
+ })().catch((error) => {
1610
+ map.delete(tokenFilePath);
1611
+ throw error;
1612
+ });
1613
+ map.set(tokenFilePath, pending);
1614
+ cached = pending;
1615
+ }
1616
+ return cached;
1617
+ }
1618
+ function withBearerToken(base, token) {
1619
+ return { ...base, Authorization: `Bearer ${token}` };
1620
+ }
1621
+
1516
1622
  // src/agents/cost.ts
1517
1623
  function addIfNumber(target, key, value) {
1518
1624
  if (typeof value !== "number" || !Number.isFinite(value)) {
@@ -1643,11 +1749,12 @@ function extractOpenCodeCostData(events) {
1643
1749
  }
1644
1750
 
1645
1751
  // src/agents/providers/claude-code.ts
1646
- var DAEMON_PROTOCOL_VERSION = "2";
1752
+ var DAEMON_PROTOCOL_VERSION = "3";
1647
1753
  var DAEMON_PORT = 43180;
1648
1754
  var DAEMON_PATH = "/tmp/agentbox/claude-code/daemon.mjs";
1649
1755
  var DAEMON_LOG_PATH = "/tmp/agentbox/claude-code/daemon.log";
1650
1756
  var DAEMON_PID_PATH = "/tmp/agentbox/claude-code/daemon.pid";
1757
+ var DAEMON_TOKEN_PATH = "/tmp/agentbox/claude-code/daemon-token";
1651
1758
  var DAEMON_READY_TIMEOUT_MS = 3e4;
1652
1759
  var DAEMON_READY_POLL_INTERVAL_MS = 250;
1653
1760
  function claudeConfigDir(options) {
@@ -1672,6 +1779,7 @@ function buildClaudeQueryOptions(params) {
1672
1779
  extraArgs["append-system-prompt"] = run.systemPrompt;
1673
1780
  }
1674
1781
  const includeHookEvents = provider?.includeHookEvents ?? false;
1782
+ const effort = provider?.ultracode ? "xhigh" : run.reasoning;
1675
1783
  return {
1676
1784
  cwd: params.cwd ?? params.request.options.cwd,
1677
1785
  env: params.env,
@@ -1684,7 +1792,7 @@ function buildClaudeQueryOptions(params) {
1684
1792
  thinking: { type: "adaptive", display: "summarized" },
1685
1793
  ...provider?.additionalDirectories?.length ? { additionalDirectories: provider.additionalDirectories } : {},
1686
1794
  ...run.model ? { model: run.model } : {},
1687
- ...run.reasoning ? { effort: run.reasoning } : {},
1795
+ ...effort ? { effort } : {},
1688
1796
  ...provider?.permissionMode ? { permissionMode: provider.permissionMode } : {},
1689
1797
  ...provider?.permissionMode === "bypassPermissions" ? { allowDangerouslySkipPermissions: true } : {},
1690
1798
  ...provider?.allowedTools?.length ? { allowedTools: provider.allowedTools } : {},
@@ -1741,11 +1849,30 @@ function createClaudeCodeDaemonScript() {
1741
1849
  const version = JSON.stringify(DAEMON_PROTOCOL_VERSION);
1742
1850
  return `import http from "node:http";
1743
1851
  import { execSync } from "node:child_process";
1744
- import { existsSync } from "node:fs";
1852
+ import { existsSync, readFileSync } from "node:fs";
1853
+ import { timingSafeEqual } from "node:crypto";
1745
1854
  import { query, getSessionInfo } from "@anthropic-ai/claude-agent-sdk";
1746
1855
 
1747
1856
  const VERSION = ${version};
1748
1857
  const port = Number(process.argv[2] ?? "${DAEMON_PORT}");
1858
+ // Capability token: every route except the loopback /__version probe
1859
+ // requires \`Authorization: Bearer <token>\`. Loaded once at boot from the
1860
+ // 0600 file the host writes alongside this script. Fails closed (rejects
1861
+ // all run-control requests) when the file is missing or empty.
1862
+ const tokenFilePath = process.argv[3] ?? "";
1863
+ let AUTH_TOKEN = "";
1864
+ try { if (tokenFilePath) AUTH_TOKEN = readFileSync(tokenFilePath, "utf8").trim(); } catch {}
1865
+
1866
+ function isAuthorized(req) {
1867
+ if (!AUTH_TOKEN) return false;
1868
+ const header = req.headers["authorization"] || "";
1869
+ const presented = header.startsWith("Bearer ") ? header.slice(7) : "";
1870
+ if (!presented) return false;
1871
+ const a = Buffer.from(presented);
1872
+ const b = Buffer.from(AUTH_TOKEN);
1873
+ return a.length === b.length && timingSafeEqual(a, b);
1874
+ }
1875
+
1749
1876
  const liveRuns = new Map();
1750
1877
 
1751
1878
  // The SDK's default spawn does \`existsSync(pathToClaudeCodeExecutable)\`
@@ -1982,6 +2109,14 @@ const server = http.createServer((req, res) => {
1982
2109
  res.end(VERSION);
1983
2110
  return;
1984
2111
  }
2112
+ // Capability-token gate for every run-control route. The /__version probe
2113
+ // above stays open: it carries no secret and the host probes it over
2114
+ // loopback (no bearer) to detect daemon liveness/version.
2115
+ if (!isAuthorized(req)) {
2116
+ res.writeHead(401, { "content-type": "text/plain" });
2117
+ res.end("unauthorized");
2118
+ return;
2119
+ }
1985
2120
  const url = req.url ?? "";
1986
2121
  let m;
1987
2122
  if (req.method === "POST" && (m = url.match(/^\\/runs\\/([^/]+)\\/start$/))) {
@@ -2052,6 +2187,11 @@ async function ensureClaudeCodeDaemonUncached(options, env) {
2052
2187
  );
2053
2188
  return;
2054
2189
  }
2190
+ const daemonToken = await resolveCapabilityToken(
2191
+ sandbox,
2192
+ DAEMON_TOKEN_PATH,
2193
+ true
2194
+ );
2055
2195
  const daemonDir = path8.posix.dirname(DAEMON_PATH);
2056
2196
  const daemonNodeModules = `${daemonDir}/node_modules/@anthropic-ai`;
2057
2197
  const launchCommand = [
@@ -2059,13 +2199,18 @@ async function ensureClaudeCodeDaemonUncached(options, env) {
2059
2199
  `if [ -z "$NPM_ROOT" ] || [ ! -d "$NPM_ROOT/@anthropic-ai/claude-agent-sdk" ]; then echo "claude-code daemon launch: @anthropic-ai/claude-agent-sdk not found under $NPM_ROOT" >&2; exit 1; fi`,
2060
2200
  `mkdir -p ${shellQuote(daemonNodeModules)}`,
2061
2201
  `ln -sfn "$NPM_ROOT/@anthropic-ai/claude-agent-sdk" ${shellQuote(daemonNodeModules + "/claude-agent-sdk")}`,
2202
+ // The base uploadAndRun only chmods execute-bit artifacts, so the 0600
2203
+ // tarball mode is dropped on Daytona (and other non-Modal providers).
2204
+ // Tighten the just-written token file explicitly — matching codex —
2205
+ // so the bearer is never group/world-readable inside the sandbox.
2206
+ `chmod 600 ${shellQuote(DAEMON_TOKEN_PATH)}`,
2062
2207
  `if [ -f ${shellQuote(DAEMON_PID_PATH)} ]; then kill -TERM "$(cat ${shellQuote(DAEMON_PID_PATH)})" 2>/dev/null || true; fi`,
2063
2208
  `(fuser -k -n tcp ${DAEMON_PORT} 2>/dev/null || true)`,
2064
2209
  // Brief grace so the kernel releases the port before the new
2065
2210
  // daemon's listen() — only matters on warm-sandbox respawns;
2066
2211
  // adds 200ms otherwise.
2067
2212
  `sleep 0.2`,
2068
- `(nohup node ${shellQuote(DAEMON_PATH)} ${DAEMON_PORT} > ${shellQuote(DAEMON_LOG_PATH)} 2>&1 & echo $! > ${shellQuote(DAEMON_PID_PATH)})`
2213
+ `(nohup node ${shellQuote(DAEMON_PATH)} ${DAEMON_PORT} ${shellQuote(DAEMON_TOKEN_PATH)} > ${shellQuote(DAEMON_LOG_PATH)} 2>&1 & echo $! > ${shellQuote(DAEMON_PID_PATH)})`
2069
2214
  ].join(" && ");
2070
2215
  const launch = await time(
2071
2216
  debugRelay,
@@ -2076,6 +2221,11 @@ async function ensureClaudeCodeDaemonUncached(options, env) {
2076
2221
  path: DAEMON_PATH,
2077
2222
  content: createClaudeCodeDaemonScript(),
2078
2223
  mode: 420
2224
+ },
2225
+ {
2226
+ path: DAEMON_TOKEN_PATH,
2227
+ content: daemonToken,
2228
+ mode: 384
2079
2229
  }
2080
2230
  ],
2081
2231
  launchCommand,
@@ -2171,6 +2321,10 @@ async function daemonBaseUrl(sandbox) {
2171
2321
  const url = await sandbox.getPreviewLink(DAEMON_PORT);
2172
2322
  return url.replace(/\/$/, "");
2173
2323
  }
2324
+ async function daemonAuthHeaders(sandbox) {
2325
+ const token = await resolveCapabilityToken(sandbox, DAEMON_TOKEN_PATH, false);
2326
+ return withBearerToken(sandbox.previewHeaders, token);
2327
+ }
2174
2328
  var ClaudeCodeAgentAdapter = class {
2175
2329
  /**
2176
2330
  * Sandbox-side preparation. Uploads `.claude/` artifacts and ensures
@@ -2223,12 +2377,19 @@ var ClaudeCodeAgentAdapter = class {
2223
2377
  () => prepareSkillArtifacts(provider, options.skills, target.layout)
2224
2378
  );
2225
2379
  const hookSettings = buildClaudeHookSettings(hooks) ?? {};
2380
+ const workflowSettings = buildClaudeWorkflowSettings(
2381
+ options.provider?.ultracode
2382
+ );
2383
+ const claudeSettings = { ...hookSettings, ...workflowSettings };
2226
2384
  const mcpConfigJson = buildClaudeMcpConfig(options.mcps) ?? JSON.stringify({ mcpServers: {} }, null, 2);
2227
2385
  const artifacts = [
2228
2386
  ...skillArtifacts,
2229
2387
  ...buildClaudeCommandArtifacts(options.commands, target.layout),
2230
2388
  ...buildClaudeSubagentArtifacts(options.subAgents, target.layout),
2231
- { path: settingsPath, content: JSON.stringify(hookSettings, null, 2) },
2389
+ {
2390
+ path: settingsPath,
2391
+ content: JSON.stringify(claudeSettings, null, 2)
2392
+ },
2232
2393
  { path: mcpConfigPath, content: mcpConfigJson }
2233
2394
  ];
2234
2395
  const enableRtk = options.enableRtk === true;
@@ -2283,6 +2444,12 @@ var ClaudeCodeAgentAdapter = class {
2283
2444
  // user inside our images.
2284
2445
  IS_SANDBOX: "1"
2285
2446
  };
2447
+ const customHeaders = request.options.customHeaders;
2448
+ if (customHeaders && Object.keys(customHeaders).length > 0) {
2449
+ const serialized = Object.entries(customHeaders).map(([name, value]) => `${name}: ${value}`).join("\n");
2450
+ env.ANTHROPIC_CUSTOM_HEADERS = env.ANTHROPIC_CUSTOM_HEADERS ? `${env.ANTHROPIC_CUSTOM_HEADERS}
2451
+ ${serialized}` : serialized;
2452
+ }
2286
2453
  const inputParts = await time(
2287
2454
  debugClaude,
2288
2455
  "validateProviderUserInput",
@@ -2297,6 +2464,7 @@ var ClaudeCodeAgentAdapter = class {
2297
2464
  "getPreviewLink daemon",
2298
2465
  () => daemonBaseUrl(sandbox)
2299
2466
  );
2467
+ const authHeaders = await daemonAuthHeaders(sandbox);
2300
2468
  const startUrl = `${baseUrl}/runs/${encodeURIComponent(request.runId)}/start`;
2301
2469
  const sdkOptions = buildClaudeQueryOptions({
2302
2470
  request,
@@ -2328,7 +2496,7 @@ var ClaudeCodeAgentAdapter = class {
2328
2496
  try {
2329
2497
  await fetch(
2330
2498
  `${baseUrl}/runs/${encodeURIComponent(request.runId)}/abort`,
2331
- { method: "POST", headers: sandbox.previewHeaders }
2499
+ { method: "POST", headers: authHeaders }
2332
2500
  );
2333
2501
  } catch {
2334
2502
  }
@@ -2345,7 +2513,7 @@ var ClaudeCodeAgentAdapter = class {
2345
2513
  method: "POST",
2346
2514
  headers: {
2347
2515
  "content-type": "application/json",
2348
- ...sandbox.previewHeaders
2516
+ ...authHeaders
2349
2517
  },
2350
2518
  body: JSON.stringify({ content: mapped })
2351
2519
  }
@@ -2360,7 +2528,7 @@ var ClaudeCodeAgentAdapter = class {
2360
2528
  signal: fetchAbort.signal,
2361
2529
  headers: {
2362
2530
  "content-type": "application/json",
2363
- ...sandbox.previewHeaders
2531
+ ...authHeaders
2364
2532
  },
2365
2533
  body: JSON.stringify(requestBody)
2366
2534
  })
@@ -2588,6 +2756,7 @@ var ClaudeCodeAgentAdapter = class {
2588
2756
  */
2589
2757
  async attachAbort(request) {
2590
2758
  const baseUrl = await daemonBaseUrl(request.sandbox);
2759
+ const authHeaders = await daemonAuthHeaders(request.sandbox);
2591
2760
  const controller = new AbortController();
2592
2761
  const timeout = setTimeout(() => controller.abort(), 3e3);
2593
2762
  try {
@@ -2596,7 +2765,7 @@ var ClaudeCodeAgentAdapter = class {
2596
2765
  {
2597
2766
  method: "POST",
2598
2767
  signal: controller.signal,
2599
- headers: request.sandbox.previewHeaders
2768
+ headers: authHeaders
2600
2769
  }
2601
2770
  ).catch((error) => {
2602
2771
  debugClaude("attachAbort POST failed: %o", error);
@@ -2613,6 +2782,7 @@ var ClaudeCodeAgentAdapter = class {
2613
2782
  */
2614
2783
  async attachSendMessage(request, content) {
2615
2784
  const baseUrl = await daemonBaseUrl(request.sandbox);
2785
+ const authHeaders = await daemonAuthHeaders(request.sandbox);
2616
2786
  const inputParts = await validateProviderUserInput(
2617
2787
  AgentProvider.ClaudeCode,
2618
2788
  content
@@ -2624,7 +2794,7 @@ var ClaudeCodeAgentAdapter = class {
2624
2794
  method: "POST",
2625
2795
  headers: {
2626
2796
  "content-type": "application/json",
2627
- ...request.sandbox.previewHeaders
2797
+ ...authHeaders
2628
2798
  },
2629
2799
  body: JSON.stringify({ content: mapped })
2630
2800
  }
@@ -2638,7 +2808,7 @@ var ClaudeCodeAgentAdapter = class {
2638
2808
  };
2639
2809
 
2640
2810
  // src/agents/providers/codex.ts
2641
- import crypto from "crypto";
2811
+ import crypto2 from "crypto";
2642
2812
  import path9 from "path";
2643
2813
 
2644
2814
  // src/agents/transports/app-server.ts
@@ -2895,7 +3065,7 @@ function resolveCodexAppServerToken(sandbox, tokenFilePath, create) {
2895
3065
  return existing;
2896
3066
  }
2897
3067
  if (create) {
2898
- return crypto.randomBytes(32).toString("hex");
3068
+ return crypto2.randomBytes(32).toString("hex");
2899
3069
  }
2900
3070
  throw new Error(
2901
3071
  `Codex app-server token file is missing at ${tokenFilePath}. setup() must run before connecting to the codex app-server.`
@@ -3216,6 +3386,45 @@ async function materializeCodexImage(options, part, index) {
3216
3386
  function resolveCodexOpenAiBaseUrlFromOptions(options) {
3217
3387
  return options.env?.OPENAI_BASE_URL ?? options.provider?.env?.OPENAI_BASE_URL;
3218
3388
  }
3389
+ function codexCredentialEnv(options) {
3390
+ return { ...options.env ?? {}, ...options.provider?.env ?? {} };
3391
+ }
3392
+ function resolveCodexModelProviders(options) {
3393
+ const modelProviders = {
3394
+ ...options.provider?.modelProviders ?? {}
3395
+ };
3396
+ const modelProvider = options.provider?.modelProvider;
3397
+ const customHeaders = options.customHeaders;
3398
+ if (customHeaders && Object.keys(customHeaders).length > 0) {
3399
+ for (const [id, cfg] of Object.entries(modelProviders)) {
3400
+ modelProviders[id] = {
3401
+ ...cfg,
3402
+ httpHeaders: { ...cfg.httpHeaders ?? {}, ...customHeaders }
3403
+ };
3404
+ }
3405
+ }
3406
+ return { modelProviders, modelProvider };
3407
+ }
3408
+ function hashCodexProviderCredentials(options, modelProviders) {
3409
+ const env = codexCredentialEnv(options);
3410
+ const envKeyNames = /* @__PURE__ */ new Set(["OPENAI_API_KEY"]);
3411
+ for (const cfg of Object.values(modelProviders)) {
3412
+ if (cfg.envKey) {
3413
+ envKeyNames.add(cfg.envKey);
3414
+ }
3415
+ for (const envVar of Object.values(cfg.envHttpHeaders ?? {})) {
3416
+ envKeyNames.add(envVar);
3417
+ }
3418
+ }
3419
+ const hasher = crypto2.createHash("sha256");
3420
+ for (const name of [...envKeyNames].sort()) {
3421
+ if (env[name] !== void 0) {
3422
+ hasher.update(`${name}=${env[name]}
3423
+ `);
3424
+ }
3425
+ }
3426
+ return hasher.digest("hex");
3427
+ }
3219
3428
  async function ensureCodexLoginViaConfig(request, target) {
3220
3429
  const options = request.options;
3221
3430
  const openAiApiKey = options.env?.OPENAI_API_KEY ?? options.provider?.env?.OPENAI_API_KEY;
@@ -3300,9 +3509,19 @@ async function setupCodex(request) {
3300
3509
  const provider = request.provider;
3301
3510
  const hooks = assertHooksSupported(provider, options);
3302
3511
  assertCommandsSupported(provider, options.commands);
3512
+ const { modelProviders, modelProvider } = resolveCodexModelProviders(options);
3513
+ const providerCredHash = hashCodexProviderCredentials(
3514
+ options,
3515
+ modelProviders
3516
+ );
3303
3517
  const usesRemoteWebSocket = options.sandbox && options.sandbox.provider !== SandboxProvider.LocalDocker;
3304
3518
  function buildArtifactsFor(layoutTarget) {
3305
- const { artifacts: subAgentArtifacts, agentSections } = buildCodexSubagentArtifacts(options.subAgents, layoutTarget.layout);
3519
+ const defaultModel = options.provider?.defaultModel;
3520
+ const { artifacts: subAgentArtifacts, agentSections } = buildCodexSubagentArtifacts(
3521
+ options.subAgents,
3522
+ layoutTarget.layout,
3523
+ defaultModel
3524
+ );
3306
3525
  const hooksFile = buildCodexHooksFile(hooks);
3307
3526
  const enableMultiAgent = (options.subAgents?.length ?? 0) > 0;
3308
3527
  const enableSkills = (options.skills?.length ?? 0) > 0;
@@ -3313,7 +3532,10 @@ async function setupCodex(request) {
3313
3532
  enableHooks: Boolean(hooksFile),
3314
3533
  enableSkills,
3315
3534
  enableMultiAgent,
3316
- openAiBaseUrl
3535
+ openAiBaseUrl,
3536
+ model: defaultModel,
3537
+ modelProvider,
3538
+ modelProviders
3317
3539
  });
3318
3540
  const artifacts = [...subAgentArtifacts];
3319
3541
  if (configToml) {
@@ -3366,7 +3588,7 @@ async function setupCodex(request) {
3366
3588
  artifacts: [...serverArtifacts, ...skillArtifacts2],
3367
3589
  installCommands: installCommands2,
3368
3590
  daemon: daemonInfo,
3369
- extras: [`enableRtk:${enableRtk2}`]
3591
+ extras: [`enableRtk:${enableRtk2}`, `providerCreds:${providerCredHash}`]
3370
3592
  });
3371
3593
  if (await preflightSetup(sharedTarget, setupId2, daemonInfo)) {
3372
3594
  debugCodex("codex remote setup() preflight hit \u2014 skipping");
@@ -3390,12 +3612,18 @@ async function setupCodex(request) {
3390
3612
  const serverCwd = sharedTarget.layout.rootDir;
3391
3613
  const launchResult = await time(
3392
3614
  debugCodex,
3393
- "launch app-server (probe + spawn-if-cold)",
3615
+ "restart app-server after setup miss",
3394
3616
  () => sandbox.run(
3395
3617
  [
3396
3618
  `mkdir -p ${shellQuote(sharedTarget.layout.rootDir)}`,
3397
- `if curl -fsS http://127.0.0.1:${REMOTE_CODEX_APP_SERVER_PORT}/readyz >/dev/null 2>&1; then exit 0; fi`,
3398
- `if [ -f ${shellQuote(pidFilePath)} ]; then kill "$(cat ${shellQuote(pidFilePath)})" >/dev/null 2>&1 || true; rm -f ${shellQuote(pidFilePath)}; fi`,
3619
+ [
3620
+ `if curl -fsS http://127.0.0.1:${REMOTE_CODEX_APP_SERVER_PORT}/readyz >/dev/null 2>&1; then`,
3621
+ ` if [ -f ${shellQuote(pidFilePath)} ]; then kill "$(cat ${shellQuote(pidFilePath)})" >/dev/null 2>&1 || true; else fuser -k -n tcp ${REMOTE_CODEX_APP_SERVER_PORT} >/dev/null 2>&1 || true; fi`,
3622
+ ` for i in 1 2 3 4 5; do if ! curl -fsS http://127.0.0.1:${REMOTE_CODEX_APP_SERVER_PORT}/readyz >/dev/null 2>&1; then break; fi; sleep 0.2; done`,
3623
+ ` if curl -fsS http://127.0.0.1:${REMOTE_CODEX_APP_SERVER_PORT}/readyz >/dev/null 2>&1; then if [ -f ${shellQuote(pidFilePath)} ]; then kill -9 "$(cat ${shellQuote(pidFilePath)})" >/dev/null 2>&1 || true; else fuser -k -n tcp ${REMOTE_CODEX_APP_SERVER_PORT} >/dev/null 2>&1 || true; fi; fi`,
3624
+ ` rm -f ${shellQuote(pidFilePath)}`,
3625
+ `fi`
3626
+ ].join("\n"),
3399
3627
  `chmod 600 ${shellQuote(tokenFilePath)}`,
3400
3628
  `(${[
3401
3629
  `nohup ${[
@@ -3452,7 +3680,7 @@ async function setupCodex(request) {
3452
3680
  const setupId = computeSetupId({
3453
3681
  artifacts: allArtifacts,
3454
3682
  installCommands,
3455
- extras: [`enableRtk:${enableRtk}`]
3683
+ extras: [`enableRtk:${enableRtk}`, `providerCreds:${providerCredHash}`]
3456
3684
  });
3457
3685
  if (await preflightSetup(target, setupId)) {
3458
3686
  debugCodex("codex local setup() preflight hit \u2014 skipping");
@@ -3967,6 +4195,37 @@ var LOCAL_OPENCODE_PORT = 4096;
3967
4195
  var SANDBOX_OPENCODE_READY_TIMEOUT_MS = 9e4;
3968
4196
  var LOCAL_OPENCODE_READY_TIMEOUT_MS = 2e4;
3969
4197
  var SHARED_OPENCODE_TARGET_ID = "shared-opencode-server";
4198
+ var OPENCODE_AUTH_USERNAME = "opencode";
4199
+ function opencodeServerTokenPath() {
4200
+ return path10.posix.join(
4201
+ agentboxRoot(AgentProvider.OpenCode, true),
4202
+ "opencode-auth-token"
4203
+ );
4204
+ }
4205
+ function opencodeCurlAuthArg() {
4206
+ return `-u "opencode:$(cat ${shellQuote(opencodeServerTokenPath())} 2>/dev/null)"`;
4207
+ }
4208
+ function opencodeHealthCurl(port) {
4209
+ return `curl -fsS --max-time 2 ${opencodeCurlAuthArg()} http://127.0.0.1:${port}/global/health >/dev/null 2>&1`;
4210
+ }
4211
+ async function isSandboxOpenCodeServerAuthEnforced(sandbox, cwd, port) {
4212
+ const probe = await sandbox.run(
4213
+ `test "$(curl -s -o /dev/null -w '%{http_code}' --max-time 2 http://127.0.0.1:${port}/global/health)" = "401"`,
4214
+ { cwd, timeoutMs: 5e3 }
4215
+ ).catch(() => void 0);
4216
+ return probe?.exitCode === 0;
4217
+ }
4218
+ async function opencodeAuthHeaders(sandbox) {
4219
+ const token = await resolveCapabilityToken(
4220
+ sandbox,
4221
+ opencodeServerTokenPath(),
4222
+ false
4223
+ );
4224
+ const basic = Buffer.from(`${OPENCODE_AUTH_USERNAME}:${token}`).toString(
4225
+ "base64"
4226
+ );
4227
+ return { ...sandbox.previewHeaders, Authorization: `Basic ${basic}` };
4228
+ }
3970
4229
  var LLM_API_KEY_ENV_VARS = [
3971
4230
  "OPENROUTER_API_KEY",
3972
4231
  "OPENAI_API_KEY",
@@ -4017,10 +4276,10 @@ async function killSandboxOpenCodeServer(sandbox, pidFilePath, cwd, port) {
4017
4276
  ).catch(() => void 0);
4018
4277
  const deadline = Date.now() + 5e3;
4019
4278
  while (Date.now() < deadline) {
4020
- const probe = await sandbox.run(
4021
- `curl -fsS --max-time 2 http://127.0.0.1:${port}/global/health >/dev/null 2>&1`,
4022
- { cwd, timeoutMs: 5e3 }
4023
- );
4279
+ const probe = await sandbox.run(opencodeHealthCurl(port), {
4280
+ cwd,
4281
+ timeoutMs: 5e3
4282
+ });
4024
4283
  if (probe.exitCode !== 0) {
4025
4284
  return;
4026
4285
  }
@@ -4127,6 +4386,8 @@ function buildOpenCodeConfig(options, interactiveApproval) {
4127
4386
  transforms: ["middle-out"],
4128
4387
  ...openRouterPlugins ? { plugins: openRouterPlugins } : {}
4129
4388
  };
4389
+ const customHeaders = options.customHeaders && Object.keys(options.customHeaders).length > 0 ? options.customHeaders : void 0;
4390
+ const headerOpts = customHeaders ? { headers: customHeaders } : {};
4130
4391
  return {
4131
4392
  $schema: "https://opencode.ai/config.json",
4132
4393
  ...mcpConfig ? { mcp: mcpConfig } : {},
@@ -4135,10 +4396,12 @@ function buildOpenCodeConfig(options, interactiveApproval) {
4135
4396
  openrouter: {
4136
4397
  options: {
4137
4398
  baseURL: openRouterBaseUrl || "https://openrouter.ai/api/v1",
4138
- extraBody: openRouterExtraBody
4399
+ extraBody: openRouterExtraBody,
4400
+ ...headerOpts
4139
4401
  }
4140
4402
  },
4141
- ...googleBaseUrl ? { google: { options: { baseURL: googleBaseUrl } } } : {}
4403
+ ...googleBaseUrl ? { google: { options: { baseURL: googleBaseUrl, ...headerOpts } } } : {},
4404
+ ...customHeaders ? { anthropic: { options: { ...headerOpts } } } : {}
4142
4405
  },
4143
4406
  agent: {
4144
4407
  agentbox: baseAgent,
@@ -4160,6 +4423,12 @@ async function ensureSandboxOpenCodeServer(request) {
4160
4423
  SHARED_OPENCODE_TARGET_ID,
4161
4424
  options
4162
4425
  );
4426
+ const serverTokenPath = opencodeServerTokenPath();
4427
+ const serverToken = await resolveCapabilityToken(
4428
+ sandbox,
4429
+ serverTokenPath,
4430
+ true
4431
+ );
4163
4432
  const { artifacts: skillArtifacts, installCommands } = await prepareSkillArtifacts(
4164
4433
  request.provider,
4165
4434
  options.skills,
@@ -4177,10 +4446,18 @@ async function ensureSandboxOpenCodeServer(request) {
4177
4446
  {
4178
4447
  path: configPath,
4179
4448
  content: JSON.stringify(openCodeConfig, null, 2)
4449
+ },
4450
+ {
4451
+ path: serverTokenPath,
4452
+ content: serverToken
4180
4453
  }
4181
4454
  ];
4182
4455
  const enableRtk = options.enableRtk === true;
4183
- const daemonInfo = { port, healthPath: "/global/health" };
4456
+ const daemonInfo = {
4457
+ port,
4458
+ healthPath: "/global/health",
4459
+ curlAuthArg: opencodeCurlAuthArg()
4460
+ };
4184
4461
  const setupId = computeSetupId({
4185
4462
  artifacts: allArtifacts,
4186
4463
  installCommands,
@@ -4195,10 +4472,15 @@ async function ensureSandboxOpenCodeServer(request) {
4195
4472
  return;
4196
4473
  }
4197
4474
  if (await isSandboxOpenCodeServerHealthy(sandbox, options.cwd, port)) {
4475
+ if (await isSandboxOpenCodeServerAuthEnforced(sandbox, options.cwd, port)) {
4476
+ debugOpencode(
4477
+ "opencode server already running but setup drifted \u2014 reusing it without restart; call agent.killServer() to apply the new config"
4478
+ );
4479
+ return;
4480
+ }
4198
4481
  debugOpencode(
4199
- "opencode server already running but setup drifted \u2014 reusing it without restart; call agent.killServer() to apply the new config"
4482
+ "opencode server running without capability auth \u2014 restarting to enforce it"
4200
4483
  );
4201
- return;
4202
4484
  }
4203
4485
  const commonEnv = {
4204
4486
  OPENCODE_CONFIG: configPath,
@@ -4218,9 +4500,18 @@ async function ensureSandboxOpenCodeServer(request) {
4218
4500
  target.layout.rootDir,
4219
4501
  "opencode-serve.log"
4220
4502
  );
4221
- const serveEnv = { ...options.env ?? {}, ...commonEnv };
4503
+ const serveEnv = {
4504
+ ...options.env ?? {},
4505
+ ...commonEnv,
4506
+ // Native opencode HTTP basic auth. The host presents this token on
4507
+ // every request (see opencodeAuthHeaders); the in-sandbox health probes
4508
+ // present it too (see opencodeHealthCurl).
4509
+ OPENCODE_SERVER_USERNAME: OPENCODE_AUTH_USERNAME,
4510
+ OPENCODE_SERVER_PASSWORD: serverToken
4511
+ };
4222
4512
  const launchCommand = [
4223
4513
  `mkdir -p ${shellQuote(target.layout.rootDir)}`,
4514
+ `chmod 600 ${shellQuote(serverTokenPath)} 2>/dev/null || true`,
4224
4515
  `(${[
4225
4516
  `setsid nohup ${[
4226
4517
  binary,
@@ -4263,10 +4554,10 @@ async function ensureSandboxOpenCodeServer(request) {
4263
4554
  );
4264
4555
  }
4265
4556
  while (Date.now() < readyDeadline) {
4266
- const probe = await sandbox.run(
4267
- `curl -fsS http://127.0.0.1:${port}/global/health >/dev/null 2>&1`,
4268
- { cwd: options.cwd, timeoutMs: 5e3 }
4269
- );
4557
+ const probe = await sandbox.run(opencodeHealthCurl(port), {
4558
+ cwd: options.cwd,
4559
+ timeoutMs: 5e3
4560
+ });
4270
4561
  if (probe.exitCode === 0) {
4271
4562
  debugOpencode("ready on attempt %d", attempt);
4272
4563
  return true;
@@ -4308,6 +4599,18 @@ opencode log:
4308
4599
  ${lastLog}` : "")
4309
4600
  );
4310
4601
  }
4602
+ if (!await isSandboxOpenCodeServerAuthEnforced(sandbox, options.cwd, port)) {
4603
+ await killSandboxOpenCodeServer(
4604
+ sandbox,
4605
+ pidFilePath,
4606
+ options.cwd,
4607
+ port
4608
+ ).catch(() => void 0);
4609
+ await target.cleanup().catch(() => void 0);
4610
+ throw new Error(
4611
+ "OpenCode server started but is not enforcing the capability token (OPENCODE_SERVER_PASSWORD ignored). Upgrade opencode-ai to a version that supports server authentication."
4612
+ );
4613
+ }
4311
4614
  await markSetupComplete(target, setupId);
4312
4615
  });
4313
4616
  }
@@ -4399,10 +4702,7 @@ async function setupOpenCode(request) {
4399
4702
  await ensureLocalOpenCodeServer(request);
4400
4703
  }
4401
4704
  async function isSandboxOpenCodeServerHealthy(sandbox, cwd, port) {
4402
- const probe = await sandbox.run(
4403
- `curl -fsS --max-time 2 http://127.0.0.1:${port}/global/health >/dev/null 2>&1`,
4404
- { cwd, timeoutMs: 5e3 }
4405
- ).catch(() => void 0);
4705
+ const probe = await sandbox.run(opencodeHealthCurl(port), { cwd, timeoutMs: 5e3 }).catch(() => void 0);
4406
4706
  return probe?.exitCode === 0;
4407
4707
  }
4408
4708
  async function isLocalOpenCodeServerHealthy() {
@@ -4443,7 +4743,7 @@ async function buildOpenCodeRuntime(options) {
4443
4743
  const baseUrl2 = (await sandbox.getPreviewLink(SANDBOX_OPENCODE_PORT)).replace(/\/$/, "");
4444
4744
  return {
4445
4745
  baseUrl: baseUrl2,
4446
- previewHeaders: sandbox.previewHeaders,
4746
+ previewHeaders: await opencodeAuthHeaders(sandbox),
4447
4747
  raw: { baseUrl: baseUrl2, port: SANDBOX_OPENCODE_PORT }
4448
4748
  };
4449
4749
  }
@@ -4977,6 +5277,7 @@ var OpenCodeAgentAdapter = class {
4977
5277
  );
4978
5278
  }
4979
5279
  const baseUrl = (await request.sandbox.getPreviewLink(SANDBOX_OPENCODE_PORT)).replace(/\/$/, "");
5280
+ const authHeaders = await opencodeAuthHeaders(request.sandbox);
4980
5281
  const controller = new AbortController();
4981
5282
  const timeout = setTimeout(() => controller.abort(), 3e3);
4982
5283
  try {
@@ -4985,7 +5286,7 @@ var OpenCodeAgentAdapter = class {
4985
5286
  signal: controller.signal,
4986
5287
  headers: {
4987
5288
  "content-type": "application/json",
4988
- ...request.sandbox.previewHeaders
5289
+ ...authHeaders
4989
5290
  }
4990
5291
  }).catch((error) => {
4991
5292
  debugOpencode(
@@ -5016,12 +5317,13 @@ var OpenCodeAgentAdapter = class {
5016
5317
  content
5017
5318
  );
5018
5319
  const parts = mapToOpenCodeParts(inputParts);
5320
+ const authHeaders = await opencodeAuthHeaders(request.sandbox);
5019
5321
  const url = `${baseUrl}/session/${request.sessionId}/prompt_async`;
5020
5322
  const response = await fetch(url, {
5021
5323
  method: "POST",
5022
5324
  headers: {
5023
5325
  "content-type": "application/json",
5024
- ...request.sandbox.previewHeaders
5326
+ ...authHeaders
5025
5327
  },
5026
5328
  body: JSON.stringify({
5027
5329
  agent: openCodeAgentSlug(void 0),
@@ -1,4 +1,4 @@
1
- export { A as AISDKEvent, R as MessageCompletedEvent, S as MessageInjectedEvent, T as MessageStartedEvent, U as NormalizedAgentEvent, V as NormalizedAgentEventBase, W as NormalizedAgentEventType, a1 as PermissionRequestedEvent, a2 as PermissionResolvedEvent, a3 as RawAgentEvent, a4 as ReasoningDeltaEvent, a6 as RunCancelledEvent, a7 as RunCompletedEvent, a8 as RunErrorEvent, a9 as RunStartedEvent, ab as TextDeltaEvent, ad as ToolCallCompletedEvent, ae as ToolCallDeltaEvent, af as ToolCallStartedEvent, ai as createNormalizedEvent, aj as normalizeRawAgentEvent, ak as toAISDKEvent, al as toAISDKStream } from '../types-t01PuLUJ.js';
1
+ export { A as AISDKEvent, S as MessageCompletedEvent, T as MessageInjectedEvent, U as MessageStartedEvent, V as NormalizedAgentEvent, W as NormalizedAgentEventBase, X as NormalizedAgentEventType, a2 as PermissionRequestedEvent, a3 as PermissionResolvedEvent, a4 as RawAgentEvent, a5 as ReasoningDeltaEvent, a7 as RunCancelledEvent, a8 as RunCompletedEvent, a9 as RunErrorEvent, aa as RunStartedEvent, ac as TextDeltaEvent, ae as ToolCallCompletedEvent, af as ToolCallDeltaEvent, ag as ToolCallStartedEvent, aj as createNormalizedEvent, ak as normalizeRawAgentEvent, al as toAISDKEvent, am as toAISDKStream } from '../types-1seasIwq.js';
2
2
  import { AgentProvider } from '../enums.js';
3
3
  import '../Sandbox-DcKAU-E3.js';
4
4
  import 'e2b';
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { A as AISDKEvent, a as AgentApprovalMode, b as AgentAttachRequest, c as AgentCommandConfig, d as AgentCostData, e as AgentExecutionRequest, f as AgentLocalMcpConfig, g as AgentMcpConfig, h as AgentOptions, i as AgentOptionsBase, j as AgentOptionsMap, k as AgentPermissionDecision, l as AgentPermissionKind, m as AgentPermissionResponse, n as AgentProviderAdapter, o as AgentProviderName, p as AgentReasoningEffort, q as AgentRemoteMcpConfig, r as AgentResult, s as AgentRun, t as AgentRunConfig, u as AgentRunSink, v as AgentSetupRequest, w as AgentSkillConfig, x as AgentSubAgentConfig, y as AttachedRun, C as ClaudeCodeAgentOptions, z as ClaudeCodeHookConfig, B as ClaudeCodeHookEvent, D as ClaudeCodeHookHandler, E as ClaudeCodeHookMatcherGroup, F as ClaudeCodeHooksConfig, G as ClaudeCodeProviderOptions, H as CodexAgentOptions, I as CodexCommandHook, J as CodexHookEvent, K as CodexHookMatcherGroup, L as CodexHooksConfig, M as CodexProviderOptions, N as DataContent, O as EmbeddedSkillConfig, P as FilePart, Q as ImagePart, R as MessageCompletedEvent, S as MessageInjectedEvent, T as MessageStartedEvent, U as NormalizedAgentEvent, V as NormalizedAgentEventBase, W as NormalizedAgentEventType, X as OpenCodeAgentOptions, Y as OpenCodePluginConfig, Z as OpenCodePluginEvent, _ as OpenCodePluginHookConfig, $ as OpenCodeProviderOptions, a0 as OpenRouterPlugin, a1 as PermissionRequestedEvent, a2 as PermissionResolvedEvent, a3 as RawAgentEvent, a4 as ReasoningDeltaEvent, a5 as RepoSkillConfig, a6 as RunCancelledEvent, a7 as RunCompletedEvent, a8 as RunErrorEvent, a9 as RunStartedEvent, aa as SetupLayout, ab as TextDeltaEvent, ac as TextPart, ad as ToolCallCompletedEvent, ae as ToolCallDeltaEvent, af as ToolCallStartedEvent, ag as UserContent, ah as UserContentPart, ai as createNormalizedEvent, aj as normalizeRawAgentEvent, ak as toAISDKEvent, al as toAISDKStream } from './types-t01PuLUJ.js';
1
+ export { A as AISDKEvent, a as AgentApprovalMode, b as AgentAttachRequest, c as AgentCommandConfig, d as AgentCostData, e as AgentExecutionRequest, f as AgentLocalMcpConfig, g as AgentMcpConfig, h as AgentOptions, i as AgentOptionsBase, j as AgentOptionsMap, k as AgentPermissionDecision, l as AgentPermissionKind, m as AgentPermissionResponse, n as AgentProviderAdapter, o as AgentProviderName, p as AgentReasoningEffort, q as AgentRemoteMcpConfig, r as AgentResult, s as AgentRun, t as AgentRunConfig, u as AgentRunSink, v as AgentSetupRequest, w as AgentSkillConfig, x as AgentSubAgentConfig, y as AttachedRun, C as ClaudeCodeAgentOptions, z as ClaudeCodeHookConfig, B as ClaudeCodeHookEvent, D as ClaudeCodeHookHandler, E as ClaudeCodeHookMatcherGroup, F as ClaudeCodeHooksConfig, G as ClaudeCodeProviderOptions, H as CodexAgentOptions, I as CodexCommandHook, J as CodexHookEvent, K as CodexHookMatcherGroup, L as CodexHooksConfig, M as CodexModelProviderConfig, N as CodexProviderOptions, O as DataContent, P as EmbeddedSkillConfig, Q as FilePart, R as ImagePart, S as MessageCompletedEvent, T as MessageInjectedEvent, U as MessageStartedEvent, V as NormalizedAgentEvent, W as NormalizedAgentEventBase, X as NormalizedAgentEventType, Y as OpenCodeAgentOptions, Z as OpenCodePluginConfig, _ as OpenCodePluginEvent, $ as OpenCodePluginHookConfig, a0 as OpenCodeProviderOptions, a1 as OpenRouterPlugin, a2 as PermissionRequestedEvent, a3 as PermissionResolvedEvent, a4 as RawAgentEvent, a5 as ReasoningDeltaEvent, a6 as RepoSkillConfig, a7 as RunCancelledEvent, a8 as RunCompletedEvent, a9 as RunErrorEvent, aa as RunStartedEvent, ab as SetupLayout, ac as TextDeltaEvent, ad as TextPart, ae as ToolCallCompletedEvent, af as ToolCallDeltaEvent, ag as ToolCallStartedEvent, ah as UserContent, ai as UserContentPart, aj as createNormalizedEvent, ak as normalizeRawAgentEvent, al as toAISDKEvent, am as toAISDKStream } from './types-1seasIwq.js';
2
2
  export { AGENT_RESERVED_PORTS, Agent, agentboxRoot, collectAllAgentReservedPorts, getAgentLayout } from './agents/index.js';
3
3
  export { A as AsyncCommandHandle, C as CommandEvent, a as CommandOptions, b as CommandResult, D as DaytonaProviderOptions, c as DaytonaSandboxOptions, E as E2bProviderOptions, d as E2bSandboxOptions, G as GitCloneOptions, L as LocalDockerProviderOptions, e as LocalDockerSandboxOptions, M as ModalProviderOptions, f as ModalSandboxOptions, S as Sandbox, g as SandboxDescriptor, h as SandboxListOptions, i as SandboxOptions, j as SandboxOptionsBase, k as SandboxOptionsMap, l as SandboxProviderName, m as SandboxRaw, n as SandboxRawMap, o as SandboxResourceSpec, T as TarballEntry, V as VercelGitSource, p as VercelProviderOptions, q as VercelSandboxOptions } from './Sandbox-DcKAU-E3.js';
4
4
  export { SandboxAdapter, buildGitCloneCommand } from './sandboxes/index.js';
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  Agent,
3
3
  agentboxRoot,
4
4
  getAgentLayout
5
- } from "./chunk-XONT46YC.js";
5
+ } from "./chunk-JYI7MRGO.js";
6
6
  import {
7
7
  ProviderLogAssembler,
8
8
  createNormalizedEvent,
@@ -264,6 +264,48 @@ interface CodexHookMatcherGroup {
264
264
  hooks: CodexCommandHook[];
265
265
  }
266
266
  type CodexHooksConfig = Partial<Record<CodexHookEvent, CodexHookMatcherGroup[]>>;
267
+ /**
268
+ * One OpenAI-compatible model provider for Codex, serialized into a
269
+ * `[model_providers.<id>]` block in config.toml. Mirrors codex's
270
+ * `ModelProviderInfo` (which uses snake_case keys and
271
+ * `deny_unknown_fields`) but with the camelCase field names AgentBox
272
+ * exposes publicly. Use it to point Codex at a local Ollama/LM Studio/vLLM
273
+ * server or any other OpenAI-compatible endpoint.
274
+ */
275
+ interface CodexModelProviderConfig {
276
+ /** Friendly display name. Defaults to the provider id when omitted. */
277
+ name?: string;
278
+ /** Base URL of the OpenAI-compatible API (e.g. `http://localhost:8000/v1`). */
279
+ baseUrl?: string;
280
+ /**
281
+ * Name of the environment variable holding the API key. The variable
282
+ * must be present in the agent env (`options.env` / `provider.env`) so
283
+ * the codex process can read it — AgentBox never writes the secret into
284
+ * config.toml.
285
+ */
286
+ envKey?: string;
287
+ /**
288
+ * Wire protocol the endpoint speaks. Codex removed the `"chat"` (Chat
289
+ * Completions) wire API in Feb 2026 and now rejects it at config load,
290
+ * so prefer `"responses"` (the Responses API) — what LM Studio and modern
291
+ * Ollama expose. Omit to use codex's default
292
+ * (`"responses"`). `"chat"` remains in the type only for older codex
293
+ * builds / chat→responses proxies.
294
+ */
295
+ wireApi?: "chat" | "responses" | "responses_websocket";
296
+ /** Extra query-string params appended to every request. */
297
+ queryParams?: Record<string, string>;
298
+ /** Static HTTP headers added to every request. */
299
+ httpHeaders?: Record<string, string>;
300
+ /** HTTP headers whose values come from env vars (header name -> env var name). */
301
+ envHttpHeaders?: Record<string, string>;
302
+ /** Maximum number of times to retry a failed request. */
303
+ requestMaxRetries?: number;
304
+ /** Maximum number of reconnect attempts for a dropped stream. */
305
+ streamMaxRetries?: number;
306
+ /** Idle timeout (ms) before a stalled stream is treated as lost. */
307
+ streamIdleTimeoutMs?: number;
308
+ }
267
309
  type OpenCodePluginEvent = "command.executed" | "file.edited" | "file.watcher.updated" | "installation.updated" | "lsp.client.diagnostics" | "lsp.updated" | "message.part.removed" | "message.part.updated" | "message.removed" | "message.updated" | "permission.asked" | "permission.replied" | "server.connected" | "session.created" | "session.compacted" | "session.deleted" | "session.diff" | "session.error" | "session.idle" | "session.status" | "session.updated" | "todo.updated" | "shell.env" | "tool.execute.after" | "tool.execute.before" | "tui.prompt.append" | "tui.command.execute" | "tui.toast.show" | "experimental.session.compacting";
268
310
  interface OpenCodePluginHookConfig {
269
311
  event: OpenCodePluginEvent;
@@ -367,6 +409,22 @@ interface AgentOptionsBase {
367
409
  * is a no-op when already active. Toggle invalidates the setup cache.
368
410
  */
369
411
  enableRtk?: boolean;
412
+ /**
413
+ * Custom HTTP headers to attach to the agent's outbound LLM API requests.
414
+ *
415
+ * Forwarded per provider via whatever native mechanism each CLI exposes:
416
+ * - claude-code: `ANTHROPIC_CUSTOM_HEADERS` (applies to all Anthropic calls)
417
+ * - codex: `http_headers` on the active `[model_providers.*]` block in
418
+ * config.toml. When Codex falls back to its built-in `openai` provider,
419
+ * there is no provider block to carry headers, so headers are ignored.
420
+ * - open-code: `provider.<id>.options.headers` in the opencode config
421
+ *
422
+ * Typical use: spend-tracking / routing tags for an LLM gateway, e.g.
423
+ * `{ "x-litellm-tags": "task:123" }`. Whether a header actually reaches the
424
+ * upstream depends on the CLI and provider; open-code's config-level headers
425
+ * in particular are subject to upstream support.
426
+ */
427
+ customHeaders?: Record<string, string>;
370
428
  approvalMode?: AgentApprovalMode;
371
429
  mcps?: AgentMcpConfig[];
372
430
  skills?: AgentSkillConfig[];
@@ -379,6 +437,37 @@ interface CodexProviderOptions {
379
437
  brokerEndpoint?: string;
380
438
  useBroker?: boolean;
381
439
  hooks?: CodexHooksConfig;
440
+ /**
441
+ * Extra OpenAI-compatible model providers, written as
442
+ * `[model_providers.<id>]` blocks in Codex's config.toml. Lets Codex
443
+ * talk to a local Ollama/LM Studio/vLLM server or another
444
+ * OpenAI-compatible endpoint.
445
+ */
446
+ modelProviders?: Record<string, CodexModelProviderConfig>;
447
+ /**
448
+ * Top-level `model_provider` written into Codex's config.toml — selects
449
+ * which {@link modelProviders} table Codex routes every run through (the
450
+ * per-run {@link AgentRunConfig.model} stays a plain model slug).
451
+ *
452
+ * When omitted, Codex falls back to its built-in `openai` provider.
453
+ */
454
+ modelProvider?: string;
455
+ /**
456
+ * Default model slug written as the top-level `model` in Codex's
457
+ * config.toml and used as the fallback `model` for any sub-agent
458
+ * ({@link AgentSubAgentConfig}) that does not set its own.
459
+ *
460
+ * Why this exists: when Codex spawns a *named* sub-agent it reloads the
461
+ * child config from the on-disk config-layer stack (config.toml + the
462
+ * role's TOML) and drops the parent turn's runtime model. If neither
463
+ * layer carries a `model`, the child's model resolves to `None` and
464
+ * `spawn_agent` fails service-tier validation with "could not resolve the
465
+ * child model". Setting this guarantees a resolvable model is always on
466
+ * disk. The per-run {@link AgentRunConfig.model} still overrides it for
467
+ * the root turn via `thread/start`; this only backstops role spawns and
468
+ * runs that omit a model.
469
+ */
470
+ defaultModel?: string;
382
471
  /**
383
472
  * When `false`, writes `supports_websockets = false` into Codex's
384
473
  * config.toml. Useful in environments where outbound WebSocket
@@ -418,6 +507,18 @@ interface ClaudeCodeProviderOptions {
418
507
  * silence them when hook noise drowns out the rest of the event stream.
419
508
  */
420
509
  includeHookEvents?: boolean;
510
+ /**
511
+ * Enable "ultracode" for claude-code runs: xhigh reasoning effort plus
512
+ * standing dynamic-workflow orchestration (the built-in `Workflow` tool).
513
+ *
514
+ * When `true`, agentbox writes `enableWorkflows: true` + `ultracode: true`
515
+ * into the managed `settings.json` and forces `effort: "xhigh"` on the query.
516
+ *
517
+ * Requires an xhigh-capable model (e.g. Opus) and a recent runtime — the
518
+ * Workflows feature shipped around Claude Code 2.1.154 / Agent SDK 0.3.149.
519
+ * On older CLIs the settings keys are simply ignored.
520
+ */
521
+ ultracode?: boolean;
421
522
  }
422
523
  interface CodexAgentOptions extends AgentOptionsBase {
423
524
  provider?: CodexProviderOptions;
@@ -629,4 +730,4 @@ interface AgentProviderAdapter<P extends AgentProviderName = AgentProviderName>
629
730
  attachSendMessage(request: AgentAttachRequest<P>, content: UserContent): Promise<void>;
630
731
  }
631
732
 
632
- export { type OpenCodeProviderOptions as $, type AISDKEvent as A, type ClaudeCodeHookEvent as B, type ClaudeCodeAgentOptions as C, type ClaudeCodeHookHandler as D, type ClaudeCodeHookMatcherGroup as E, type ClaudeCodeHooksConfig as F, type ClaudeCodeProviderOptions as G, type CodexAgentOptions as H, type CodexCommandHook as I, type CodexHookEvent as J, type CodexHookMatcherGroup as K, type CodexHooksConfig as L, type CodexProviderOptions as M, type DataContent as N, type EmbeddedSkillConfig as O, type FilePart as P, type ImagePart as Q, type MessageCompletedEvent as R, type MessageInjectedEvent as S, type MessageStartedEvent as T, type NormalizedAgentEvent as U, type NormalizedAgentEventBase as V, type NormalizedAgentEventType as W, type OpenCodeAgentOptions as X, type OpenCodePluginConfig as Y, type OpenCodePluginEvent as Z, type OpenCodePluginHookConfig as _, type AgentApprovalMode as a, type OpenRouterPlugin as a0, type PermissionRequestedEvent as a1, type PermissionResolvedEvent as a2, type RawAgentEvent as a3, type ReasoningDeltaEvent as a4, type RepoSkillConfig as a5, type RunCancelledEvent as a6, type RunCompletedEvent as a7, type RunErrorEvent as a8, type RunStartedEvent as a9, type SetupLayout as aa, type TextDeltaEvent as ab, type TextPart as ac, type ToolCallCompletedEvent as ad, type ToolCallDeltaEvent as ae, type ToolCallStartedEvent as af, type UserContent as ag, type UserContentPart as ah, createNormalizedEvent as ai, normalizeRawAgentEvent as aj, toAISDKEvent as ak, toAISDKStream as al, type AgentAttachRequest as b, type AgentCommandConfig as c, type AgentCostData as d, type AgentExecutionRequest as e, type AgentLocalMcpConfig as f, type AgentMcpConfig as g, type AgentOptions as h, type AgentOptionsBase as i, type AgentOptionsMap as j, type AgentPermissionDecision as k, type AgentPermissionKind as l, type AgentPermissionResponse as m, type AgentProviderAdapter as n, type AgentProviderName as o, type AgentReasoningEffort as p, type AgentRemoteMcpConfig as q, type AgentResult as r, type AgentRun as s, type AgentRunConfig as t, type AgentRunSink as u, type AgentSetupRequest as v, type AgentSkillConfig as w, type AgentSubAgentConfig as x, type AttachedRun as y, type ClaudeCodeHookConfig as z };
733
+ export { type OpenCodePluginHookConfig as $, type AISDKEvent as A, type ClaudeCodeHookEvent as B, type ClaudeCodeAgentOptions as C, type ClaudeCodeHookHandler as D, type ClaudeCodeHookMatcherGroup as E, type ClaudeCodeHooksConfig as F, type ClaudeCodeProviderOptions as G, type CodexAgentOptions as H, type CodexCommandHook as I, type CodexHookEvent as J, type CodexHookMatcherGroup as K, type CodexHooksConfig as L, type CodexModelProviderConfig as M, type CodexProviderOptions as N, type DataContent as O, type EmbeddedSkillConfig as P, type FilePart as Q, type ImagePart as R, type MessageCompletedEvent as S, type MessageInjectedEvent as T, type MessageStartedEvent as U, type NormalizedAgentEvent as V, type NormalizedAgentEventBase as W, type NormalizedAgentEventType as X, type OpenCodeAgentOptions as Y, type OpenCodePluginConfig as Z, type OpenCodePluginEvent as _, type AgentApprovalMode as a, type OpenCodeProviderOptions as a0, type OpenRouterPlugin as a1, type PermissionRequestedEvent as a2, type PermissionResolvedEvent as a3, type RawAgentEvent as a4, type ReasoningDeltaEvent as a5, type RepoSkillConfig as a6, type RunCancelledEvent as a7, type RunCompletedEvent as a8, type RunErrorEvent as a9, type RunStartedEvent as aa, type SetupLayout as ab, type TextDeltaEvent as ac, type TextPart as ad, type ToolCallCompletedEvent as ae, type ToolCallDeltaEvent as af, type ToolCallStartedEvent as ag, type UserContent as ah, type UserContentPart as ai, createNormalizedEvent as aj, normalizeRawAgentEvent as ak, toAISDKEvent as al, toAISDKStream as am, type AgentAttachRequest as b, type AgentCommandConfig as c, type AgentCostData as d, type AgentExecutionRequest as e, type AgentLocalMcpConfig as f, type AgentMcpConfig as g, type AgentOptions as h, type AgentOptionsBase as i, type AgentOptionsMap as j, type AgentPermissionDecision as k, type AgentPermissionKind as l, type AgentPermissionResponse as m, type AgentProviderAdapter as n, type AgentProviderName as o, type AgentReasoningEffort as p, type AgentRemoteMcpConfig as q, type AgentResult as r, type AgentRun as s, type AgentRunConfig as t, type AgentRunSink as u, type AgentSetupRequest as v, type AgentSkillConfig as w, type AgentSubAgentConfig as x, type AttachedRun as y, type ClaudeCodeHookConfig as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentbox-sdk",
3
- "version": "0.1.323",
3
+ "version": "0.1.500",
4
4
  "description": "Swappable coding agents and sandbox providers for Bun and TypeScript.",
5
5
  "license": "MIT",
6
6
  "repository": {