@ai-sdk/harness-codex 1.0.12 → 1.0.14

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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @ai-sdk/harness-codex
2
2
 
3
+ ## 1.0.14
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [8c616f0]
8
+ - @ai-sdk/provider-utils@5.0.3
9
+ - @ai-sdk/harness@1.0.13
10
+
11
+ ## 1.0.13
12
+
13
+ ### Patch Changes
14
+
15
+ - 7859cea: feat(harness): add tool filtering via `activeTools` and `inactiveTools`
16
+ - c857346: feat(harness): add utility functions for certain duplicated layers in harnesses
17
+ - Updated dependencies [7859cea]
18
+ - Updated dependencies [c857346]
19
+ - @ai-sdk/harness@1.0.12
20
+
3
21
  ## 1.0.12
4
22
 
5
23
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/bridge/host-tool-mcp.ts"],"sourcesContent":["#!/usr/bin/env node\n// MCP-stdio tool server spawned by the codex CLI when\n// `mcp_servers.harness-tools` is configured. Exposes host-defined tools\n// over MCP-stdio and round-trips each call to the bridge's HTTP relay.\n//\n// Env vars (set by the bridge when starting a turn):\n// TOOL_SCHEMAS — JSON array of { name, description, inputSchema }\n// TOOL_RELAY_URL — http://127.0.0.1:<port> of the bridge relay server\n// Relay authorization is issued by bridge runtime events, not an env token.\n\n/*\n * CONSTRAINT — the third-party imports below are NEVER bundled into the\n * compiled `bridge/host-tool-mcp.mjs`. They are declared `external` in\n * tsup.config.ts and resolved at runtime from the node_modules that the\n * bridge installs *inside the sandbox* from `src/bridge/package.json` (and\n * its pinned `pnpm-lock.yaml`). That bridge package.json — NOT this host\n * package — is the single source of truth for these packages and their\n * versions; the published `@ai-sdk/harness-codex` package does not provide\n * them at runtime.\n *\n * When adding or changing a third-party import here you MUST keep all three\n * in sync, or this server will either get the dependency bundled in or fail\n * to resolve it in the sandbox:\n * 1. the import statement below,\n * 2. the `external` array in tsup.config.ts, and\n * 3. the dependency entry in `src/bridge/package.json`.\n */\nimport * as mcpServerModule from '@modelcontextprotocol/sdk/server/mcp.js';\nimport * as mcpStdioModule from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { z } from 'zod/v4';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst { McpServer } = mcpServerModule as any;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst { StdioServerTransport } = mcpStdioModule as any;\n\ntype ToolSchema = {\n name: string;\n description?: string;\n inputSchema?: JsonSchemaObject;\n};\n\ntype JsonSchemaObject = {\n type?: string | string[];\n description?: string;\n properties?: Record<string, JsonSchemaObject>;\n required?: string[];\n items?: JsonSchemaObject;\n enum?: unknown[];\n const?: unknown;\n oneOf?: JsonSchemaObject[];\n anyOf?: JsonSchemaObject[];\n additionalProperties?: boolean | JsonSchemaObject;\n nullable?: boolean;\n};\n\nconst schemas: ToolSchema[] = JSON.parse(process.env.TOOL_SCHEMAS || '[]');\nconst relayUrl = process.env.TOOL_RELAY_URL || '';\n\nif (!schemas.length || !relayUrl) {\n process.stderr.write(\n '[host-tool-mcp] Missing TOOL_SCHEMAS or TOOL_RELAY_URL; exiting\\n',\n );\n process.exit(0);\n}\n\nconst server = new McpServer({ name: 'harness-tools', version: '1.0.0' });\n\nfor (const schema of schemas) {\n const shape = toZodShape(schema.inputSchema);\n server.tool(\n schema.name,\n schema.description ?? '',\n shape,\n async (input: Record<string, unknown>) => {\n const requestId = crypto.randomUUID();\n try {\n const res = await fetch(relayUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ requestId, toolName: schema.name, input }),\n });\n if (!res.ok) {\n const body = await res.text();\n throw new Error(\n `Tool relay ${schema.name} failed with ${res.status}: ${body.slice(0, 500)}`,\n );\n }\n const data = (await res.json()) as { result?: unknown };\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(data.result ?? null),\n },\n ],\n };\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Error: ${String(err)}` }],\n isError: true,\n };\n }\n },\n );\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction toZodShape(schema: JsonSchemaObject | undefined): Record<string, any> {\n if (!schema?.properties) return {};\n const required = new Set(schema.required ?? []);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const shape: Record<string, any> = {};\n for (const [key, propSchema] of Object.entries(schema.properties)) {\n const propType = toZodType(propSchema);\n shape[key] = required.has(key) ? propType : propType.optional();\n }\n return shape;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction toZodType(schema: JsonSchemaObject | undefined): any {\n if (!schema) return z.any();\n const types = Array.isArray(schema.type)\n ? schema.type.filter((t): t is string => t !== 'null')\n : ([schema.type].filter(Boolean) as string[]);\n let zType;\n switch (types[0]) {\n case 'string':\n zType = z.string();\n break;\n case 'number':\n zType = z.number();\n break;\n case 'integer':\n zType = z.number().int();\n break;\n case 'boolean':\n zType = z.boolean();\n break;\n case 'array':\n zType = z.array(toZodType(schema.items));\n break;\n case 'object':\n zType = z.object(toZodShape(schema));\n break;\n case 'null':\n zType = z.null();\n break;\n default:\n zType = z.any();\n }\n if (schema.description) zType = zType.describe(schema.description);\n if (schema.nullable) zType = zType.nullable();\n return zType;\n}\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/bridge/host-tool-mcp.ts"],"sourcesContent":["#!/usr/bin/env node\n// MCP-stdio tool server spawned by the codex CLI when\n// `mcp_servers.harness-tools` is configured. Exposes host-defined tools\n// over MCP-stdio and round-trips each call to the bridge's HTTP relay.\n//\n// Env vars (set by the bridge when starting a turn):\n// TOOL_SCHEMAS — JSON array of { name, description, inputSchema }\n// TOOL_RELAY_URL — http://127.0.0.1:<port> of the bridge relay server\n// Relay authorization is issued by bridge runtime events, not an env token.\n\n/*\n * CONSTRAINT — the third-party imports below are NEVER bundled into the\n * compiled `bridge/host-tool-mcp.mjs`. They are declared `external` in\n * tsup.config.ts and resolved at runtime from the node_modules that the\n * bridge installs *inside the sandbox* from `src/bridge/package.json` (and\n * its pinned `pnpm-lock.yaml`). That bridge package.json — NOT this host\n * package — is the single source of truth for these packages and their\n * versions; the published `@ai-sdk/harness-codex` package does not provide\n * them at runtime.\n *\n * When adding or changing a third-party import here you MUST keep all three\n * in sync, or this server will either get the dependency bundled in or fail\n * to resolve it in the sandbox:\n * 1. the import statement below,\n * 2. the `external` array in tsup.config.ts, and\n * 3. the dependency entry in `src/bridge/package.json`.\n */\nimport * as mcpServerModule from '@modelcontextprotocol/sdk/server/mcp.js';\nimport * as mcpStdioModule from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { z } from 'zod/v4';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst { McpServer } = mcpServerModule as any;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nconst { StdioServerTransport } = mcpStdioModule as any;\n\ntype ToolSchema = {\n name: string;\n description?: string;\n inputSchema?: JsonSchemaObject;\n};\n\ntype JsonSchemaObject = {\n type?: string | string[];\n description?: string;\n properties?: Record<string, JsonSchemaObject>;\n required?: string[];\n items?: JsonSchemaObject;\n enum?: unknown[];\n const?: unknown;\n oneOf?: JsonSchemaObject[];\n anyOf?: JsonSchemaObject[];\n additionalProperties?: boolean | JsonSchemaObject;\n nullable?: boolean;\n};\n\nconst schemas: ToolSchema[] = JSON.parse(process.env.TOOL_SCHEMAS || '[]');\nconst relayUrl = process.env.TOOL_RELAY_URL || '';\n\nif (!schemas.length || !relayUrl) {\n process.stderr.write(\n '[host-tool-mcp] Missing TOOL_SCHEMAS or TOOL_RELAY_URL; exiting\\n',\n );\n process.exit(0);\n}\n\nconst server = new McpServer({ name: 'harness-tools', version: '1.0.0' });\n\nfor (const schema of schemas) {\n const shape = toZodShape(schema.inputSchema);\n server.tool(\n schema.name,\n schema.description ?? '',\n shape,\n async (input: Record<string, unknown>) => {\n const requestId = crypto.randomUUID();\n try {\n const res = await fetch(relayUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ requestId, toolName: schema.name, input }),\n });\n if (!res.ok) {\n const body = await res.text();\n throw new Error(\n `Tool relay ${schema.name} failed with ${res.status}: ${body.slice(0, 500)}`,\n );\n }\n const data = (await res.json()) as { result?: unknown };\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(data.result ?? null),\n },\n ],\n };\n } catch (err) {\n return {\n content: [{ type: 'text' as const, text: `Error: ${String(err)}` }],\n isError: true,\n };\n }\n },\n );\n}\n\nfunction toZodShape(\n schema: JsonSchemaObject | undefined,\n): Record<string, z.ZodTypeAny> {\n if (!schema?.properties) return {};\n const required = new Set(schema.required ?? []);\n const shape: Record<string, z.ZodTypeAny> = {};\n for (const [key, propSchema] of Object.entries(schema.properties)) {\n const propType = toZodType(propSchema);\n shape[key] = required.has(key) ? propType : propType.optional();\n }\n return shape;\n}\n\nfunction toZodType(schema: JsonSchemaObject | undefined): z.ZodTypeAny {\n if (!schema) return z.any();\n const types = Array.isArray(schema.type)\n ? schema.type.filter((t): t is string => t !== 'null')\n : ([schema.type].filter(Boolean) as string[]);\n let zType: z.ZodTypeAny;\n switch (types[0]) {\n case 'string':\n zType = z.string();\n break;\n case 'number':\n zType = z.number();\n break;\n case 'integer':\n zType = z.number().int();\n break;\n case 'boolean':\n zType = z.boolean();\n break;\n case 'array':\n zType = z.array(toZodType(schema.items));\n break;\n case 'object':\n zType = z.object(toZodShape(schema));\n break;\n case 'null':\n zType = z.null();\n break;\n default:\n zType = z.any();\n }\n if (schema.description) zType = zType.describe(schema.description);\n if (schema.nullable) zType = zType.nullable();\n return zType;\n}\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -11,8 +11,11 @@ import {
11
11
  import {
12
12
  classifyDiskLog,
13
13
  markBridgeStarting,
14
+ resolveSandboxHomeDir,
14
15
  SandboxChannel,
15
- waitForBridgeReady
16
+ shellQuote,
17
+ waitForBridgeReady,
18
+ writeSkills as writeHarnessSkills
16
19
  } from "@ai-sdk/harness/utils";
17
20
  import { WebSocket } from "ws";
18
21
  import { z as z2 } from "zod/v4";
@@ -181,6 +184,12 @@ function createCodex(settings = {}) {
181
184
  return cachedBootstrap;
182
185
  },
183
186
  doStart: async (startOpts) => {
187
+ if (startOpts.builtinToolFiltering != null) {
188
+ throw new HarnessCapabilityUnsupportedError({
189
+ message: "Harness 'codex' does not support built-in tool filtering controls.",
190
+ harnessId: "codex"
191
+ });
192
+ }
184
193
  if (startOpts.permissionMode != null && startOpts.permissionMode !== "allow-all") {
185
194
  throw new HarnessCapabilityUnsupportedError({
186
195
  message: "Harness 'codex' does not support built-in tool approval requests; use permissionMode: 'allow-all'.",
@@ -259,7 +268,7 @@ function createCodex(settings = {}) {
259
268
  }
260
269
  const port = resolveBridgePort(sandboxSession, settings.port);
261
270
  const token = randomBytes(32).toString("hex");
262
- const codexSkillSetup = startOpts.skills && startOpts.skills.length > 0 ? await writeSkills({
271
+ const codexSkillSetup = startOpts.skills && startOpts.skills.length > 0 ? await writeCodexSkills({
263
272
  sandbox: session,
264
273
  skills: startOpts.skills,
265
274
  abortSignal: startOpts.abortSignal
@@ -365,20 +374,11 @@ async function readBridgeAsset(name) {
365
374
  }
366
375
  throw lastErr ?? new Error(`bridge asset not found: ${name}`);
367
376
  }
368
- async function writeSkills({
377
+ async function writeCodexSkills({
369
378
  sandbox,
370
379
  skills,
371
380
  abortSignal
372
381
  }) {
373
- for (const skill of skills) {
374
- safeCodexSkillName(skill.name);
375
- for (const file of skill.files ?? []) {
376
- safeCodexSkillFilePath({
377
- skillName: skill.name,
378
- filePath: file.path
379
- });
380
- }
381
- }
382
382
  const homeDir = await resolveSandboxHomeDir({ sandbox, abortSignal });
383
383
  const codexHomeDir = path.posix.join(homeDir, ".codex");
384
384
  await sandbox.run({
@@ -386,78 +386,19 @@ async function writeSkills({
386
386
  abortSignal
387
387
  });
388
388
  const rootDir = path.posix.join(homeDir, ".agents", "skills");
389
- await sandbox.run({
390
- command: `mkdir -p ${shellQuote(rootDir)}`,
391
- abortSignal
389
+ await writeHarnessSkills({
390
+ sandbox,
391
+ rootDir,
392
+ skills,
393
+ abortSignal,
394
+ invalidSkillNameMessage: ({ name }) => `Invalid Codex skill name: ${name}`,
395
+ invalidSkillFilePathMessage: ({ skillName, filePath }) => `Invalid Codex skill file path for ${skillName}: ${filePath}`
392
396
  });
393
- for (const skill of skills) {
394
- const name = safeCodexSkillName(skill.name);
395
- const skillDir = path.posix.join(rootDir, name);
396
- const content = `---
397
- name: ${skill.name}
398
- description: ${skill.description}
399
- ---
400
-
401
- ${skill.content}`;
402
- await sandbox.writeTextFile({
403
- path: path.posix.join(skillDir, "SKILL.md"),
404
- content,
405
- abortSignal
406
- });
407
- for (const file of skill.files ?? []) {
408
- const filePath = safeCodexSkillFilePath({
409
- skillName: skill.name,
410
- filePath: file.path
411
- });
412
- await sandbox.writeTextFile({
413
- path: path.posix.join(skillDir, filePath),
414
- content: file.content,
415
- abortSignal
416
- });
417
- }
418
- }
419
397
  return {
420
398
  homeDir,
421
399
  codexHomeDir
422
400
  };
423
401
  }
424
- async function resolveSandboxHomeDir({
425
- sandbox,
426
- abortSignal
427
- }) {
428
- const result = await sandbox.run({
429
- command: 'printf "%s" "$HOME"',
430
- abortSignal
431
- });
432
- const homeDir = result.stdout.trim();
433
- if (result.exitCode !== 0 || !homeDir || !path.posix.isAbsolute(homeDir)) {
434
- throw new Error(
435
- `Unable to resolve sandbox HOME directory: ${result.stderr || result.stdout}`
436
- );
437
- }
438
- return homeDir;
439
- }
440
- function safeCodexSkillName(name) {
441
- if (!/^[A-Za-z0-9._-]+$/.test(name) || name === "." || name === "..") {
442
- throw new Error(`Invalid Codex skill name: ${name}`);
443
- }
444
- return name;
445
- }
446
- function safeCodexSkillFilePath({
447
- skillName,
448
- filePath
449
- }) {
450
- const normalized = path.posix.normalize(filePath);
451
- if (normalized === "." || normalized.startsWith("../") || path.posix.isAbsolute(normalized)) {
452
- throw new Error(
453
- `Invalid Codex skill file path for ${skillName}: ${filePath}`
454
- );
455
- }
456
- return normalized;
457
- }
458
- function shellQuote(value) {
459
- return `'${value.replace(/'/g, `'\\''`)}'`;
460
- }
461
402
  async function forwardBridgeStderr(stream) {
462
403
  try {
463
404
  const reader = stream.pipeThrough(new TextDecoderStream()).getReader();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/codex-harness.ts","../src/codex-auth.ts","../src/codex-bridge-protocol.ts","../src/bridge/cli-relay.ts","../src/index.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n commonTool,\n HarnessCapabilityUnsupportedError,\n harnessV1DiagnosticFromBridgeFrame,\n type HarnessV1,\n type HarnessV1Bootstrap,\n type HarnessV1DebugConfig,\n type HarnessV1BuiltinTool,\n type HarnessV1ContinueTurnState,\n type HarnessV1Prompt,\n type HarnessV1PromptControl,\n type HarnessV1ResumeSessionState,\n type HarnessV1NetworkSandboxSession,\n type HarnessV1PermissionMode,\n type HarnessV1Session,\n type HarnessV1Skill,\n type HarnessV1StreamPart,\n} from '@ai-sdk/harness';\nimport {\n classifyDiskLog,\n markBridgeStarting,\n SandboxChannel,\n waitForBridgeReady,\n} from '@ai-sdk/harness/utils';\nimport {\n type Experimental_SandboxProcess,\n type Experimental_SandboxSession,\n} from '@ai-sdk/provider-utils';\nimport { WebSocket } from 'ws';\nimport { z } from 'zod/v4';\nimport { resolveCodexEnv, type CodexAuthOptions } from './codex-auth';\nimport {\n outboundMessageSchema,\n type InboundMessage,\n type OutboundMessage,\n} from './codex-bridge-protocol';\nimport { CLI_SHIM_FILENAME } from './bridge/cli-relay';\n\ntype CodexChannel = SandboxChannel<OutboundMessage, InboundMessage>;\ntype CodexRespawnStrategy = 'replay' | 'rerun';\n\ntype WriteSkillsResult = {\n readonly homeDir: string;\n readonly codexHomeDir: string;\n};\n\n/*\n * The model the adapter pins when the consumer configures none. The Codex SDK\n * does not report the model it resolves to at runtime (no model field on any\n * event), and exposes no default-model constant, so we pin the latest\n * codex-specialized model available for the bundled `@openai/codex@0.130.0`\n * (published 2026-05-08): `gpt-5.3-codex` (released 2026-02). Keep this in sync\n * when bumping the codex SDK/binary. Passing it explicitly makes the resolved\n * model deterministic and the telemetry (`gen_ai.request.model`) accurate.\n */\nconst DEFAULT_CODEX_MODEL = 'gpt-5.3-codex';\n\nexport type CodexHarnessSettings = {\n readonly auth?: CodexAuthOptions;\n /**\n * OpenAI model id the underlying `codex` CLI should use. Leaving this unset\n * pins the adapter default (`DEFAULT_CODEX_MODEL`).\n */\n readonly model?: string;\n /**\n * Reasoning effort for reasoning-capable models. Leaving this unset\n * defers to the CLI's default.\n */\n readonly reasoningEffort?: 'low' | 'medium' | 'high';\n /**\n * When `true`, allow the underlying runtime to use live web search.\n */\n readonly webSearch?: boolean;\n /**\n * Override the port the bridge binds inside the sandbox. By default the\n * adapter uses the first port the sandbox declares via `sandbox.ports`.\n * Only set this if the sandbox declares multiple ports and the first one\n * is reserved for something else.\n */\n readonly port?: number;\n /** Maximum milliseconds to wait for the bridge to advertise its port. Defaults to 120000. */\n readonly startupTimeoutMs?: number;\n};\n\n/*\n * Every native tool the Codex CLI can invoke as a model-callable tool,\n * declared as a `ToolSet` keyed by what the bridge emits as `toolName` on\n * the wire (`commonName ?? nativeName`). Schemas reflect the `ThreadItem`\n * union in `@openai/codex-sdk`'s `dist/index.d.ts`.\n *\n * Codex's other native operations (`apply_patch`, todo planning) surface\n * only as side-effect events (`file_change`, `todo_list`) and are not\n * model-callable tools — they don't appear here.\n */\nconst CODEX_BUILTIN_TOOLS = {\n bash: commonTool('bash', {\n nativeName: 'shell',\n toolUseKind: 'bash',\n description: 'Execute a shell command',\n inputSchema: z.object({ command: z.string() }),\n }),\n webSearch: commonTool('webSearch', {\n nativeName: 'web_search',\n toolUseKind: 'readonly',\n description: 'Search the web',\n inputSchema: z.object({ query: z.string() }),\n }),\n} as const satisfies Record<string, HarnessV1BuiltinTool<any, any>>;\n\n/*\n * Bootstrap lives in /tmp because it's pure derived state — the harness can\n * reinstall the CLI and bridge files on any fresh sandbox from the recipe.\n * Persistence comes from the sandbox provider's snapshot, not the path.\n *\n * The session work dir (`startOpts.sessionWorkDir`) lives under the sandbox's\n * default working directory — the provider's persistent mount — so any files\n * the agent edits survive both detach -> attach and stop -> snapshot -> resume\n * cycles. Harness infra derived from `sandboxSession.defaultWorkingDirectory`\n * lives under `.agent-runs`, outside the agent workdir.\n */\nconst BOOTSTRAP_DIR = '/tmp/harness/codex';\n\n/**\n * Live bridge coordinates returned by `doDetach()` and `doSuspendTurn()`. A\n * future process uses them to reopen a socket to the still-running bridge\n * (`attach`) instead of re-spawning it. Absent on a `doStop()` payload.\n */\nconst codexBridgeCoordsSchema = z.object({\n port: z.number(),\n token: z.string(),\n lastSeenEventId: z.number(),\n sandboxId: z.string().optional(),\n});\n\n/**\n * Schema for the adapter-specific lifecycle `data` payload Codex produces.\n * `threadId` is what `codex.resumeThread(...)` requires for the replay/rerun\n * rungs; the sandbox lookup is handled separately via\n * `provider.resumeSession({ sessionId })`. `bridge` carries live coordinates\n * for cross-process `attach` (present on `doDetach()` and `doSuspendTurn()`\n * payloads).\n */\nconst codexResumeStateSchema = z.object({\n threadId: z.string().optional(),\n bridge: codexBridgeCoordsSchema.optional(),\n});\n\ntype CodexBridgeCoords = z.infer<typeof codexBridgeCoordsSchema>;\n\nexport function createCodex(\n settings: CodexHarnessSettings = {},\n): HarnessV1<typeof CODEX_BUILTIN_TOOLS> {\n let cachedBootstrap: HarnessV1Bootstrap | undefined;\n\n return {\n specificationVersion: 'harness-v1',\n harnessId: 'codex',\n builtinTools: CODEX_BUILTIN_TOOLS,\n supportsBuiltinToolApprovals: false,\n lifecycleStateSchema: codexResumeStateSchema,\n getBootstrap: async () => {\n if (cachedBootstrap != null) return cachedBootstrap;\n const [pkg, lock, bridge, hostToolMcp] = await Promise.all([\n readBridgeAsset('package.json'),\n readBridgeAsset('pnpm-lock.yaml'),\n readBridgeAsset('index.mjs'),\n readBridgeAsset('host-tool-mcp.mjs'),\n ]);\n cachedBootstrap = {\n harnessId: 'codex',\n bootstrapDir: BOOTSTRAP_DIR,\n files: [\n { path: `${BOOTSTRAP_DIR}/package.json`, content: pkg },\n { path: `${BOOTSTRAP_DIR}/pnpm-lock.yaml`, content: lock },\n { path: `${BOOTSTRAP_DIR}/bridge.mjs`, content: bridge },\n {\n path: `${BOOTSTRAP_DIR}/host-tool-mcp.mjs`,\n content: hostToolMcp,\n },\n ],\n commands: [\n { command: `mkdir -p ${BOOTSTRAP_DIR}` },\n {\n command: `pnpm --dir ${BOOTSTRAP_DIR} install --frozen-lockfile --store-dir ${BOOTSTRAP_DIR}/.pnpm-store`,\n },\n ],\n };\n return cachedBootstrap;\n },\n doStart: async startOpts => {\n if (\n startOpts.permissionMode != null &&\n startOpts.permissionMode !== 'allow-all'\n ) {\n throw new HarnessCapabilityUnsupportedError({\n message:\n \"Harness 'codex' does not support built-in tool approval requests; use permissionMode: 'allow-all'.\",\n harnessId: 'codex',\n });\n }\n const sandboxSession = startOpts.sandboxSession;\n const session = sandboxSession.restricted();\n const sandboxId = sandboxSession.id;\n const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;\n const isResume = lifecycleState != null;\n const isContinue = startOpts.continueFrom != null;\n const resumeData =\n isResume && typeof lifecycleState?.data === 'object'\n ? (lifecycleState.data as {\n threadId?: unknown;\n bridge?: CodexBridgeCoords;\n })\n : undefined;\n const resumeThreadId = resumeData?.threadId;\n const resumeThreadIdString =\n typeof resumeThreadId === 'string' && resumeThreadId.length > 0\n ? resumeThreadId\n : undefined;\n const coords = resumeData?.bridge;\n\n const workDir = startOpts.sessionWorkDir;\n const sessionDataDir = `${sandboxSession.defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;\n const bridgeStateDir = `${sessionDataDir}/bridge`;\n const cliShimDir = `${sessionDataDir}/codex`;\n const cliShimPath = `${cliShimDir}/${CLI_SHIM_FILENAME}`;\n const timeoutMs = settings.startupTimeoutMs ?? 120_000;\n\n // Normalize each forwarded bridge diagnostics frame into the general\n // `HarnessV1Diagnostic` and report it. The adapter does no telemetry work\n // beyond this transport→emission mapping.\n const report = startOpts.observability?.report;\n const onDiagnostic = report\n ? (frame: Parameters<typeof harnessV1DiagnosticFromBridgeFrame>[0]) =>\n report(\n harnessV1DiagnosticFromBridgeFrame(frame, {\n sessionId: startOpts.sessionId,\n timestamp: Date.now(),\n }),\n )\n : undefined;\n\n /*\n * Rung 1 — ATTACH. With live coordinates, reopen a socket to the\n * still-running bridge. Parked between-turn sessions just attach and wait\n * for the next `start`; suspended in-flight turns request replay of\n * everything past the persisted cursor. No spawn, no fresh token. If the\n * bridge is gone the open throws and we fall through to a spawn-based\n * recovery.\n */\n if (coords) {\n try {\n const attachUrl =\n (await sandboxSession.getPortUrl({\n port: coords.port,\n protocol: 'ws',\n })) + `?agent_bridge_token=${encodeURIComponent(coords.token)}`;\n const attachChannel: CodexChannel = new SandboxChannel({\n connect: () => openWebSocket(attachUrl),\n outboundSchema: outboundMessageSchema,\n initialLastSeenEventId: coords.lastSeenEventId,\n onDiagnostic,\n });\n await attachChannel.open(isContinue ? { resume: true } : undefined);\n return createSession({\n sessionId: startOpts.sessionId,\n channel: attachChannel,\n cliShimPath,\n // The live bridge was spawned by another process; no process handle.\n proc: undefined,\n model: settings.model ?? DEFAULT_CODEX_MODEL,\n reasoningEffort: settings.reasoningEffort,\n webSearch: settings.webSearch,\n resumeThreadId: resumeThreadIdString,\n isResume: true,\n seedResumeThreadOnFirstPrompt: false,\n rerunContinue: false,\n bridgePort: coords.port,\n bridgeToken: coords.token,\n sandboxId,\n debug: startOpts.observability?.debug,\n permissionMode: startOpts.permissionMode,\n });\n } catch {\n // Bridge no longer reachable — recover by respawning below.\n }\n }\n\n /*\n * Rungs 2/3 — REPLAY vs RERUN. Respawn the bridge. `replay` is only sound\n * for `continueFrom`: those coordinates include the cursor the on-disk\n * log is replayed *from*. `resumeFrom` is a between-turn resume; even when\n * it carries bridge coordinates, replaying the previous turn would\n * re-deliver stale events into the next turn. Those resumes always `rerun`\n * via `codex.resumeThread(threadId)` when attach is unavailable.\n */\n let respawnStrategy: CodexRespawnStrategy | undefined = isResume\n ? 'rerun'\n : undefined;\n if (coords && isContinue) {\n const logRaw = await Promise.resolve(\n session.readTextFile({\n path: `${bridgeStateDir}/event-log.ndjson`,\n abortSignal: startOpts.abortSignal,\n }),\n ).catch(() => null);\n if ((await classifyDiskLog(logRaw)) === 'replay') {\n respawnStrategy = 'replay';\n }\n }\n\n const port = resolveBridgePort(sandboxSession, settings.port);\n const token = randomBytes(32).toString('hex');\n const codexSkillSetup =\n startOpts.skills && startOpts.skills.length > 0\n ? await writeSkills({\n sandbox: session,\n skills: startOpts.skills,\n abortSignal: startOpts.abortSignal,\n })\n : undefined;\n const env = {\n ...resolveCodexEnv(settings.auth),\n BRIDGE_CHANNEL_TOKEN: token,\n BRIDGE_WS_PORT: String(port),\n ...(codexSkillSetup\n ? {\n HOME: codexSkillSetup.homeDir,\n CODEX_HOME: codexSkillSetup.codexHomeDir,\n }\n : {}),\n ...(respawnStrategy === 'replay'\n ? { BRIDGE_REPLAY_FROM_DISK: '1' }\n : {}),\n };\n\n if (respawnStrategy === undefined) {\n await session.run({\n command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)}`,\n abortSignal: startOpts.abortSignal,\n });\n }\n\n await markBridgeStarting({\n sandbox: session,\n bridgeStateDir,\n bridgeType: 'codex',\n abortSignal: startOpts.abortSignal,\n });\n\n const proc = await session.spawn({\n command: `node ${BOOTSTRAP_DIR}/bridge.mjs --workdir ${shellQuote(workDir)} --bridge-state-dir ${shellQuote(bridgeStateDir)} --bootstrap-dir ${shellQuote(BOOTSTRAP_DIR)} --cli-shim-dir ${shellQuote(cliShimDir)}`,\n env,\n abortSignal: startOpts.abortSignal,\n });\n\n const { port: boundPort } = await waitForBridgeReady({\n proc,\n sandbox: session,\n bridgeStateDir,\n bridgeType: 'codex',\n timeoutMs,\n abortSignal: startOpts.abortSignal,\n createTimeoutError: () =>\n new Error('codex bridge did not become ready in time.'),\n createExitError: () =>\n new Error('codex bridge exited before becoming ready.'),\n });\n void drainRest(proc.stdout);\n /*\n * Bridge stderr is the only diagnostic channel for what happens\n * inside the sandbox once the bridge is running (uncaught\n * exceptions, Codex SDK errors, network failures). Forward it\n * line-by-line to the host console so a mid-turn bridge crash can\n * be inspected from `pnpm dev` logs without redeploying. The\n * bridge itself writes nothing to stderr in steady state, so this\n * is silent on the happy path.\n */\n void forwardBridgeStderr(proc.stderr);\n\n const wsUrl =\n (await sandboxSession.getPortUrl({\n port: boundPort,\n protocol: 'ws',\n })) + `?agent_bridge_token=${encodeURIComponent(token)}`;\n\n const channel: CodexChannel = new SandboxChannel({\n connect: () => openWebSocket(wsUrl),\n outboundSchema: outboundMessageSchema,\n onDiagnostic,\n // In replay mode the respawned bridge reloaded the finished turn from\n // disk; seed the cursor and resume so it streams the tail (incl.\n // `finish`).\n ...(respawnStrategy === 'replay'\n ? { initialLastSeenEventId: coords?.lastSeenEventId ?? 0 }\n : {}),\n });\n await channel.open(\n respawnStrategy === 'replay' ? { resume: true } : undefined,\n );\n\n return createSession({\n sessionId: startOpts.sessionId,\n channel,\n cliShimPath,\n proc,\n model: settings.model ?? DEFAULT_CODEX_MODEL,\n reasoningEffort: settings.reasoningEffort,\n webSearch: settings.webSearch,\n resumeThreadId: resumeThreadIdString,\n isResume: respawnStrategy !== undefined,\n seedResumeThreadOnFirstPrompt: respawnStrategy !== undefined,\n rerunContinue: respawnStrategy === 'rerun',\n bridgePort: boundPort,\n bridgeToken: token,\n sandboxId,\n debug: startOpts.observability?.debug,\n permissionMode: startOpts.permissionMode,\n });\n },\n };\n}\n\nfunction resolveBridgePort(\n sandboxSession: HarnessV1NetworkSandboxSession,\n override: number | undefined,\n): number {\n if (override !== undefined) return override;\n if (sandboxSession.ports.length > 0) return sandboxSession.ports[0];\n throw new HarnessCapabilityUnsupportedError({\n harnessId: 'codex',\n message:\n 'The codex harness needs a TCP port exposed by the sandbox. ' +\n 'Create the sandbox with `ports: [<port>]` or pass `createCodex({ port })`.',\n });\n}\n\nasync function readBridgeAsset(name: string): Promise<string> {\n const candidates = [\n new URL(`./bridge/${name}`, import.meta.url),\n new URL(`../bridge/${name}`, import.meta.url),\n ];\n let lastErr: unknown;\n for (const url of candidates) {\n try {\n return await readFile(fileURLToPath(url), 'utf8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== 'ENOENT') throw err;\n lastErr = err;\n }\n }\n throw lastErr ?? new Error(`bridge asset not found: ${name}`);\n}\n\nasync function writeSkills({\n sandbox,\n skills,\n abortSignal,\n}: {\n sandbox: Experimental_SandboxSession;\n skills: ReadonlyArray<HarnessV1Skill>;\n abortSignal?: AbortSignal;\n}): Promise<WriteSkillsResult> {\n for (const skill of skills) {\n safeCodexSkillName(skill.name);\n for (const file of skill.files ?? []) {\n safeCodexSkillFilePath({\n skillName: skill.name,\n filePath: file.path,\n });\n }\n }\n\n const homeDir = await resolveSandboxHomeDir({ sandbox, abortSignal });\n const codexHomeDir = path.posix.join(homeDir, '.codex');\n await sandbox.run({\n command: `mkdir -p ${shellQuote(codexHomeDir)}`,\n abortSignal,\n });\n\n const rootDir = path.posix.join(homeDir, '.agents', 'skills');\n await sandbox.run({\n command: `mkdir -p ${shellQuote(rootDir)}`,\n abortSignal,\n });\n\n for (const skill of skills) {\n const name = safeCodexSkillName(skill.name);\n const skillDir = path.posix.join(rootDir, name);\n const content = `---\\nname: ${skill.name}\\ndescription: ${skill.description}\\n---\\n\\n${skill.content}`;\n\n await sandbox.writeTextFile({\n path: path.posix.join(skillDir, 'SKILL.md'),\n content,\n abortSignal,\n });\n\n for (const file of skill.files ?? []) {\n const filePath = safeCodexSkillFilePath({\n skillName: skill.name,\n filePath: file.path,\n });\n await sandbox.writeTextFile({\n path: path.posix.join(skillDir, filePath),\n content: file.content,\n abortSignal,\n });\n }\n }\n\n return {\n homeDir,\n codexHomeDir,\n };\n}\n\nasync function resolveSandboxHomeDir({\n sandbox,\n abortSignal,\n}: {\n sandbox: Experimental_SandboxSession;\n abortSignal?: AbortSignal;\n}): Promise<string> {\n const result = await sandbox.run({\n command: 'printf \"%s\" \"$HOME\"',\n abortSignal,\n });\n const homeDir = result.stdout.trim();\n if (result.exitCode !== 0 || !homeDir || !path.posix.isAbsolute(homeDir)) {\n throw new Error(\n `Unable to resolve sandbox HOME directory: ${result.stderr || result.stdout}`,\n );\n }\n return homeDir;\n}\n\nfunction safeCodexSkillName(name: string): string {\n if (!/^[A-Za-z0-9._-]+$/.test(name) || name === '.' || name === '..') {\n throw new Error(`Invalid Codex skill name: ${name}`);\n }\n return name;\n}\n\nfunction safeCodexSkillFilePath({\n skillName,\n filePath,\n}: {\n skillName: string;\n filePath: string;\n}): string {\n const normalized = path.posix.normalize(filePath);\n if (\n normalized === '.' ||\n normalized.startsWith('../') ||\n path.posix.isAbsolute(normalized)\n ) {\n throw new Error(\n `Invalid Codex skill file path for ${skillName}: ${filePath}`,\n );\n }\n return normalized;\n}\n\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`;\n}\n\nasync function forwardBridgeStderr(\n stream: ReadableStream<Uint8Array>,\n): Promise<void> {\n try {\n const reader = stream.pipeThrough(new TextDecoderStream()).getReader();\n while (true) {\n const { value, done } = await reader.read();\n if (done) return;\n if (value) {\n const trimmed = value.endsWith('\\n') ? value.slice(0, -1) : value;\n if (trimmed.length > 0) {\n // eslint-disable-next-line no-console\n console.log(`[bridge stderr] ${trimmed}`);\n }\n }\n }\n } catch {\n // Reader errors are non-fatal — best-effort diagnostic only.\n }\n}\n\nasync function drainRest(stream: ReadableStream<Uint8Array>): Promise<void> {\n try {\n const reader = stream.pipeThrough(new TextDecoderStream()).getReader();\n while (true) {\n const { done } = await reader.read();\n if (done) return;\n }\n } catch {}\n}\n\nfunction openWebSocket(url: string): Promise<WebSocket> {\n return new Promise((resolve, reject) => {\n const ws = new WebSocket(url);\n const onOpen = () => {\n ws.off('error', onError);\n resolve(ws);\n };\n const onError = (err: Error) => {\n ws.off('open', onOpen);\n reject(err);\n };\n ws.once('open', onOpen);\n ws.once('error', onError);\n });\n}\n\nfunction createSession({\n sessionId,\n channel,\n cliShimPath,\n proc,\n model,\n reasoningEffort,\n webSearch,\n resumeThreadId,\n isResume,\n seedResumeThreadOnFirstPrompt,\n rerunContinue,\n bridgePort,\n bridgeToken,\n sandboxId,\n debug,\n permissionMode,\n}: {\n sessionId: string;\n channel: CodexChannel;\n cliShimPath: string;\n /** Undefined on `attach` — the live bridge was spawned by another process. */\n proc: Experimental_SandboxProcess | undefined;\n model: string | undefined;\n reasoningEffort: 'low' | 'medium' | 'high' | undefined;\n webSearch: boolean | undefined;\n resumeThreadId: string | undefined;\n isResume: boolean;\n seedResumeThreadOnFirstPrompt: boolean;\n rerunContinue: boolean;\n bridgePort: number;\n bridgeToken: string;\n sandboxId: string;\n debug: HarnessV1DebugConfig | undefined;\n permissionMode: HarnessV1PermissionMode | undefined;\n}): HarnessV1Session {\n let stopped = false;\n let stopPromise: Promise<void> | undefined;\n /*\n * Send the persisted threadId on the first prompt only when the bridge was\n * respawned (rerun/replay) so it takes the `codex.resumeThread(...)` branch.\n * An `attach`ed bridge already holds its threadState in memory and continues\n * on its own, so it needs no seed.\n */\n let pendingResumeThreadId = seedResumeThreadOnFirstPrompt\n ? resumeThreadId\n : undefined;\n /*\n * Initial prompt guidance is prepended to the first user message of a fresh\n * session only. A resumed session (attach/replay/rerun) already carried it\n * in its original first message (preserved in the persisted thread), so it\n * starts \"applied\".\n */\n let instructionsApplied = isResume;\n\n /*\n * Latest codex thread id, cached from the bridge's `bridge-thread`\n * announcements. Seeded from lifecycle state so `doDetach()` and `doStop()`\n * can include a thread id even before this process has run a turn.\n */\n let latestThreadId = resumeThreadId;\n channel.on('bridge-thread', msg => {\n latestThreadId = msg.threadId;\n });\n\n /*\n * Wire the channel into one turn's worth of events and return the control\n * surface. Shared by `doPromptTurn` (which sends a `start` afterwards) and\n * `doContinueTurn` (which attaches to an already-running/replayed turn, or sends\n * a rerun `start`). The only difference between the two entry points is the\n * `start` message, not the listener/abort/settle plumbing.\n */\n const wireTurn = (turnOpts: {\n emit: (event: HarnessV1StreamPart) => void;\n abortSignal?: AbortSignal;\n }): {\n control: HarnessV1PromptControl;\n sendStart: (send: () => void) => void;\n } => {\n let pendingResolve: (() => void) | undefined;\n let pendingReject: ((err: unknown) => void) | undefined;\n const done = new Promise<void>((resolve, reject) => {\n pendingResolve = resolve;\n pendingReject = reject;\n });\n\n const unsubs: Array<() => void> = [];\n const forward = (event: HarnessV1StreamPart) => {\n try {\n turnOpts.emit(event);\n } catch {}\n };\n\n const eventTypes = [\n 'stream-start',\n 'text-start',\n 'text-delta',\n 'text-end',\n 'reasoning-start',\n 'reasoning-delta',\n 'reasoning-end',\n 'tool-call',\n 'tool-approval-request',\n 'tool-result',\n 'file-change',\n 'finish-step',\n 'raw',\n ] as const;\n let isSettled = false;\n const settleSuccess = () => {\n if (isSettled) return;\n isSettled = true;\n for (const u of unsubs) u();\n pendingResolve!();\n };\n const settleError = (err: unknown) => {\n if (isSettled) return;\n isSettled = true;\n for (const u of unsubs) u();\n pendingReject!(err);\n };\n\n for (const type of eventTypes) {\n unsubs.push(\n channel.on(type, msg => {\n forward(msg);\n }),\n );\n }\n unsubs.push(\n channel.on('finish', msg => {\n forward(msg);\n settleSuccess();\n }),\n );\n unsubs.push(\n channel.on('error', msg => {\n forward(msg);\n settleError(msg.error);\n }),\n );\n\n /*\n * A `'suspended'` close is a graceful slice-boundary freeze the host\n * initiated (`doSuspendTurn`): the turn keeps running in the bridge and its\n * tail is replayed to the next process, so wind this turn down cleanly\n * rather than failing it. Any other close mid-turn is an unexpected drop.\n */\n const onClose = (_code?: number, reason?: string) => {\n if (isSettled) return;\n if (reason === 'suspended') {\n settleSuccess();\n return;\n }\n settleError(new Error('codex bridge closed before the turn finished.'));\n };\n channel.onClose(onClose);\n\n const onAbort = () => {\n if (isSettled) return;\n try {\n channel.send({ type: 'abort' });\n } catch {}\n settleError(\n turnOpts.abortSignal?.reason ??\n new DOMException('Aborted', 'AbortError'),\n );\n };\n if (turnOpts.abortSignal) {\n if (turnOpts.abortSignal.aborted) {\n onAbort();\n } else {\n turnOpts.abortSignal.addEventListener('abort', onAbort, {\n once: true,\n });\n }\n }\n\n const control: HarnessV1PromptControl = {\n submitToolResult: async input => {\n channel.send({\n type: 'tool-result',\n toolCallId: input.toolCallId,\n output: input.output,\n isError: input.isError,\n });\n },\n submitToolApproval: async input => {\n channel.send({\n type: 'tool-approval-response',\n approvalId: input.approvalId,\n approved: input.approved,\n reason: input.reason,\n });\n },\n submitUserMessage: async text => {\n channel.send({ type: 'user-message', text });\n },\n done,\n };\n\n return {\n control,\n sendStart: send => {\n /*\n * Codex can complete short turns without using tools. Deferring the\n * start frame gives the harness runner one event-loop turn to finish\n * wiring the prompt control and stream output before Codex can settle.\n */\n const timer = setTimeout(() => {\n if (isSettled) return;\n try {\n send();\n } catch (err) {\n settleError(err);\n }\n }, 0);\n timer.unref?.();\n },\n };\n };\n\n return {\n sessionId,\n isResume,\n modelId: model,\n doPromptTurn: async promptOpts => {\n const turn = wireTurn({\n emit: promptOpts.emit,\n abortSignal: promptOpts.abortSignal,\n });\n\n const tools = (promptOpts.tools ?? []).map(t => ({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema,\n }));\n let promptText = extractUserText(promptOpts.prompt);\n if (!instructionsApplied) {\n const instructions =\n (promptOpts.instructions ? promptOpts.instructions + '\\n\\n' : '') +\n 'Only respond with your `final` message once you have fully addressed the user request.';\n promptText = frameInitialPromptGuidance({\n instructions,\n toolUsageBlock:\n tools.length > 0\n ? composeToolUsageInstructions({\n tools,\n cliShimPath,\n })\n : undefined,\n userText: promptText,\n });\n }\n instructionsApplied = true;\n\n const startMessage = {\n type: 'start' as const,\n prompt: promptText,\n tools,\n model,\n reasoningEffort,\n webSearch,\n ...(permissionMode ? { permissionMode } : {}),\n ...(pendingResumeThreadId\n ? { resumeThreadId: pendingResumeThreadId }\n : {}),\n ...(debug ? { debug } : {}),\n };\n pendingResumeThreadId = undefined;\n turn.sendStart(() => channel.send(startMessage));\n\n return turn.control;\n },\n doContinueTurn: async continueOpts => {\n const turn = wireTurn({\n emit: continueOpts.emit,\n abortSignal: continueOpts.abortSignal,\n });\n\n /*\n * attach / replay: the still-running (or disk-replayed) turn streams into\n * the wired listeners — `doStart` opened the channel with `{ resume: true }`\n * so the bridge replays everything past the persisted cursor (including a\n * `finish` if the turn ended during the gap). No `start` is sent: issuing\n * one would clear the bridge's replay log and begin a new turn. Lossless.\n *\n * rerun: the bridge was respawned with no in-flight turn to attach to, so\n * re-drive codex's own thread via `resumeThreadId`. Lossy — work in flight\n * at the interruption is recomputed. This is the rare bridge-died\n * fallback; the common slice path is `attach`.\n */\n if (rerunContinue) {\n const threadId = pendingResumeThreadId ?? latestThreadId;\n pendingResumeThreadId = undefined;\n turn.sendStart(() =>\n channel.send({\n type: 'start' as const,\n /*\n * A continuation nudge rather than an empty prompt: `resumeThreadId`\n * rehydrates the prior thread, and this is the new user turn that\n * drives it forward. Keeping it non-empty avoids handing the runtime\n * an empty user message (and mirrors the claude-code adapter, where an\n * empty text block trips the Anthropic API's `cache_control` rule).\n */\n prompt: 'Continue.',\n tools: (continueOpts.tools ?? []).map(t => ({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema,\n })),\n model,\n reasoningEffort,\n webSearch,\n ...(permissionMode ? { permissionMode } : {}),\n ...(threadId ? { resumeThreadId: threadId } : {}),\n ...(debug ? { debug } : {}),\n }),\n );\n }\n\n return turn.control;\n },\n doCompact: async () => {\n /*\n * Codex compacts its context automatically inside the core turn loop\n * (~90% of the model context window), but the `codex exec` transport this\n * adapter drives exposes no manual compaction trigger and emits no\n * compaction event. Manual `compact()` is therefore unsupported; Codex's\n * own auto-compaction continues to run regardless.\n */\n throw new HarnessCapabilityUnsupportedError({\n message:\n \"Harness 'codex' does not support manual compaction; Codex auto-compacts its context internally.\",\n harnessId: 'codex',\n });\n },\n doDetach: async () => {\n if (stopped) {\n throw new Error(\n `codex session ${sessionId} is already stopped; cannot detach.`,\n );\n }\n stopped = true;\n const lastSeenEventId = await channel.suspend();\n const payload: HarnessV1ResumeSessionState = {\n type: 'resume-session',\n harnessId: 'codex',\n specificationVersion: 'harness-v1',\n data: {\n ...(latestThreadId ? { threadId: latestThreadId } : {}),\n bridge: {\n port: bridgePort,\n token: bridgeToken,\n lastSeenEventId,\n sandboxId,\n },\n },\n };\n return payload;\n },\n doDestroy: async () => {\n if (stopped) return stopPromise;\n stopped = true;\n stopPromise = (async () => {\n // Tell the channel we are tearing down so the bridge's post-shutdown\n // socket close finalises instead of triggering a reconnect.\n channel.beginClose();\n try {\n if (!channel.isClosed()) {\n channel.send({ type: 'shutdown' });\n }\n } catch {}\n let stopTimer: ReturnType<typeof setTimeout> | undefined;\n try {\n if (proc) {\n await Promise.race([\n proc.wait(),\n new Promise<void>(resolve => {\n stopTimer = setTimeout(resolve, 5000);\n stopTimer.unref?.();\n }),\n ]);\n }\n } finally {\n if (stopTimer) clearTimeout(stopTimer);\n try {\n await proc?.kill();\n } catch {}\n channel.close();\n }\n })();\n return stopPromise;\n },\n doStop: async () => {\n if (stopped) {\n throw new Error(\n `codex session ${sessionId} is already stopped; cannot stop.`,\n );\n }\n stopped = true;\n /*\n * If the bridge's channel already closed (e.g. mid-turn WS drop)\n * there is no one to ack a `detach` message. Synthesize an empty\n * payload — the workdir is still captured by the sandbox snapshot\n * during the subsequent `sandboxSession.stop()`, so the next turn can\n * resume the filesystem state. The trade-off: we lose\n * `threadId`, so the codex CLI starts a fresh thread on the\n * preserved workdir rather than resuming the prior conversation\n * inside Codex's runtime. Ability to continue beats throwing.\n */\n // Tell the channel we are tearing down so the bridge's post-detach\n // socket close finalises instead of triggering a reconnect.\n channel.beginClose();\n const data: unknown = channel.isClosed()\n ? {}\n : await new Promise<unknown>((resolve, reject) => {\n const timer = setTimeout(() => {\n unsub();\n reject(\n new Error(\n `codex session ${sessionId} did not reply to detach within 5s.`,\n ),\n );\n }, 5000);\n timer.unref?.();\n const unsub = channel.on('bridge-detach', msg => {\n clearTimeout(timer);\n unsub();\n resolve(msg.data);\n });\n try {\n channel.send({ type: 'detach' });\n } catch (err) {\n clearTimeout(timer);\n unsub();\n reject(err);\n }\n });\n\n let stopTimer: ReturnType<typeof setTimeout> | undefined;\n try {\n if (proc) {\n await Promise.race([\n proc.wait(),\n new Promise<void>(resolve => {\n stopTimer = setTimeout(resolve, 5000);\n stopTimer.unref?.();\n }),\n ]);\n }\n } finally {\n if (stopTimer) clearTimeout(stopTimer);\n try {\n await proc?.kill();\n } catch {}\n channel.close();\n }\n\n const payload: HarnessV1ResumeSessionState = {\n type: 'resume-session',\n harnessId: 'codex',\n specificationVersion: 'harness-v1',\n data: (data ?? {}) as HarnessV1ResumeSessionState['data'],\n };\n return payload;\n },\n doSuspendTurn: async () => {\n if (stopped) {\n throw new Error(\n `codex session ${sessionId} is stopped; cannot suspend.`,\n );\n }\n stopped = true;\n /*\n * Gracefully freeze the active turn at a precise cursor. `channel.suspend`\n * stops processing inbound frames (the cursor stops advancing exactly at\n * the last delivered event), drains what was already dispatched, then\n * closes the host socket with reason `'suspended'` — which `wireTurn`'s\n * `onClose` treats as a clean turn end. The bridge keeps the turn running\n * and accumulates events past the cursor for the next slice to replay. The\n * sandbox process is deliberately left alive (no `shutdown`/`detach`).\n */\n const lastSeenEventId = await channel.suspend();\n const payload: HarnessV1ContinueTurnState = {\n type: 'continue-turn',\n harnessId: 'codex',\n specificationVersion: 'harness-v1',\n data: {\n ...(latestThreadId ? { threadId: latestThreadId } : {}),\n bridge: {\n port: bridgePort,\n token: bridgeToken,\n lastSeenEventId,\n sandboxId,\n },\n },\n };\n return payload;\n },\n };\n}\n\n/*\n * Frame session instructions, host-tool relay guidance, and the user's text so\n * Codex treats the prepended blocks as operating guidance rather than user\n * prose. Applied only to the first user message of a fresh session.\n */\nfunction frameInitialPromptGuidance({\n instructions,\n toolUsageBlock,\n userText,\n}: {\n instructions: string | undefined;\n toolUsageBlock: string | undefined;\n userText: string;\n}): string {\n const blocks: string[] = [];\n if (instructions) {\n blocks.push(\n '<session-instructions>\\n' +\n 'The block below is operating guidance from the system, not a message from the user — follow it, but do not mention it or attribute it to the user.\\n\\n' +\n `${instructions}\\n` +\n '</session-instructions>',\n );\n }\n if (toolUsageBlock) blocks.push(toolUsageBlock);\n if (blocks.length === 0) return userText;\n return `${blocks.join('\\n\\n')}\\n\\n<user-message>\\n${userText}\\n</user-message>`;\n}\n\nfunction composeToolUsageInstructions({\n tools,\n cliShimPath,\n}: {\n tools: ReadonlyArray<{\n name: string;\n description?: string;\n inputSchema?: unknown;\n }>;\n cliShimPath: string;\n}): string {\n const lines: string[] = [\n '<host-tool-instructions>',\n 'You have access to the following host-provided tools. To use one, run the following command via your built-in `bash` tool:',\n '',\n ` node ${cliShimPath} <toolName> '<jsonInput>'`,\n '',\n 'The script prints the JSON result to stdout. Do not invent another way to call these tools — only this CLI invocation will work. Pass the JSON input as a single-quoted argument.',\n 'For every user request that depends on a host-provided tool, run a separate CLI invocation for each needed tool call in the current turn before answering. Do not reuse previous tool results, and do not say you used a host tool unless the command has completed in the current turn.',\n '',\n ];\n for (const toolSpec of tools) {\n lines.push(\n `- **${toolSpec.name}**${toolSpec.description ? ': ' + toolSpec.description : ''}`,\n );\n lines.push(\n ` - Input schema: \\`${JSON.stringify(toolSpec.inputSchema ?? {})}\\``,\n );\n }\n lines.push('</host-tool-instructions>');\n return lines.join('\\n');\n}\n\n/*\n * Reduce a `HarnessV1Prompt` to the plain user text the bridge forwards\n * to the Codex SDK. File and image parts on the message are not yet\n * supported by the underlying runtime — throw rather than silently drop\n * them so callers learn about the gap instead of seeing mysteriously\n * truncated prompts.\n */\nfunction extractUserText(prompt: HarnessV1Prompt): string {\n if (typeof prompt === 'string') return prompt;\n const { content } = prompt;\n if (typeof content === 'string') return content;\n const parts: string[] = [];\n for (const part of content) {\n if (part.type !== 'text') {\n throw new HarnessCapabilityUnsupportedError({\n harnessId: 'codex',\n message: `The codex harness does not yet support user message parts of type '${part.type}'. Pass a string or a user message whose content contains only text parts.`,\n });\n }\n parts.push(part.text);\n }\n return parts.join('\\n\\n');\n}\n","import { getAiGatewayAuthFromEnv } from '@ai-sdk/harness/utils';\n\nexport type CodexAuthOptions = {\n readonly openaiCompatible?: {\n readonly apiKey?: string;\n readonly baseUrl?: string;\n readonly modelProviderName?: string;\n readonly queryParamsJson?: string;\n };\n readonly openai?: {\n readonly apiKey?: string;\n readonly baseUrl?: string;\n readonly organization?: string;\n readonly project?: string;\n };\n readonly gateway?: {\n readonly apiKey?: string;\n readonly baseUrl?: string;\n };\n};\n\n/**\n * Resolve the environment-variable blob the codex bridge needs. Precedence:\n *\n * 1. Explicit `auth.openaiCompatible` — pin to a custom OpenAI-compatible endpoint.\n * 2. Explicit `auth.openai` — pin to direct OpenAI auth.\n * 3. Explicit `auth.gateway` — pin to Vercel AI Gateway.\n * 4. Auto-detect from the host process env: gateway first\n * (`AI_GATEWAY_API_KEY` / `VERCEL_OIDC_TOKEN`), then `CODEX_API_KEY` /\n * `OPENAI_API_KEY`.\n */\nexport function resolveCodexEnv(\n auth: CodexAuthOptions | undefined,\n processEnv: Record<string, string | undefined> = process.env,\n): Record<string, string> {\n if (auth?.openaiCompatible) {\n return pickOpenAICompatible(auth.openaiCompatible, processEnv);\n }\n if (auth?.openai) {\n return pickOpenAI({ explicit: auth.openai, processEnv });\n }\n const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({\n env: processEnv,\n });\n if (auth?.gateway) {\n return pickGateway({\n explicit: auth.gateway,\n gatewayAuthFromEnv,\n });\n }\n if (gatewayAuthFromEnv.apiKey) {\n return pickGateway({\n explicit: {},\n gatewayAuthFromEnv,\n });\n }\n return pickOpenAI({ processEnv });\n}\n\nfunction pickOpenAICompatible(\n explicit: NonNullable<CodexAuthOptions['openaiCompatible']>,\n processEnv: Record<string, string | undefined>,\n): Record<string, string> {\n const env: Record<string, string> = {};\n const apiKey =\n explicit.apiKey ?? processEnv.OPENAI_API_KEY ?? processEnv.CODEX_API_KEY;\n if (apiKey) env.CODEX_API_KEY = apiKey;\n if (explicit.baseUrl) env.OPENAI_BASE_URL = explicit.baseUrl;\n if (explicit.modelProviderName)\n env.CODEX_MODEL_PROVIDER_NAME = explicit.modelProviderName;\n if (explicit.queryParamsJson)\n env.OPENAI_QUERY_PARAMS_JSON = explicit.queryParamsJson;\n return env;\n}\n\nfunction pickOpenAI({\n explicit,\n processEnv,\n}: {\n explicit?: NonNullable<CodexAuthOptions['openai']>;\n processEnv: Record<string, string | undefined>;\n}): Record<string, string> {\n const env: Record<string, string> = {};\n const apiKey =\n explicit?.apiKey ?? processEnv.OPENAI_API_KEY ?? processEnv.CODEX_API_KEY;\n if (apiKey) env.CODEX_API_KEY = apiKey;\n const baseUrl = explicit?.baseUrl ?? processEnv.OPENAI_BASE_URL;\n if (baseUrl) env.OPENAI_BASE_URL = baseUrl;\n const organization = explicit?.organization ?? processEnv.OPENAI_ORGANIZATION;\n if (organization) env.OPENAI_ORGANIZATION = organization;\n const project = explicit?.project ?? processEnv.OPENAI_PROJECT;\n if (project) env.OPENAI_PROJECT = project;\n return env;\n}\n\nfunction pickGateway({\n explicit,\n gatewayAuthFromEnv,\n}: {\n explicit: NonNullable<CodexAuthOptions['gateway']>;\n gatewayAuthFromEnv: ReturnType<typeof getAiGatewayAuthFromEnv>;\n}): Record<string, string> {\n const apiKey = explicit.apiKey ?? gatewayAuthFromEnv.apiKey;\n const baseUrl = toCodexGatewayBaseUrl(\n explicit.baseUrl ?? gatewayAuthFromEnv.baseUrl,\n );\n const env: Record<string, string> = {};\n if (apiKey) {\n env.AI_GATEWAY_API_KEY = apiKey;\n env.CODEX_API_KEY = apiKey;\n }\n env.AI_GATEWAY_BASE_URL = baseUrl;\n return env;\n}\n\nfunction toCodexGatewayBaseUrl(baseUrl: string): string {\n const trimmed = baseUrl.replace(/\\/+$/, '');\n return trimmed.endsWith('/v1') ? trimmed : `${trimmed}/v1`;\n}\n","import {\n harnessV1BridgeInboundCommandSchemas,\n harnessV1BridgeOutboundMessageSchema,\n harnessV1BridgeReadySchema,\n harnessV1BridgeStartBaseSchema,\n} from '@ai-sdk/harness';\nimport { z } from 'zod/v4';\n\n/*\n * Codex's bridge wire protocol. The outbound events (including `file-change`\n * and the `bridge-thread` resume coordinate), transport frames, shared inbound\n * commands, and `bridge-ready` line all come from the shared `@ai-sdk/harness`\n * protocol — the only Codex-specific piece is the `start` payload.\n */\n\nexport const outboundMessageSchema = harnessV1BridgeOutboundMessageSchema;\nexport type OutboundMessage = z.infer<typeof outboundMessageSchema>;\n\nexport const startMessageSchema = harnessV1BridgeStartBaseSchema.extend({\n reasoningEffort: z.enum(['low', 'medium', 'high']).optional(),\n webSearch: z.boolean().optional(),\n // Resume signal. When supplied, the bridge calls\n // `codex.resumeThread(resumeThreadId, …)` instead of starting a fresh thread.\n // The host sources the id from lifecycle state `data` cached from a prior\n // `agent.detach`.\n resumeThreadId: z.string().optional(),\n});\n\nexport type StartMessage = z.infer<typeof startMessageSchema>;\n\nexport const inboundMessageSchema = z.discriminatedUnion('type', [\n startMessageSchema,\n ...harnessV1BridgeInboundCommandSchemas,\n]);\nexport type InboundMessage = z.infer<typeof inboundMessageSchema>;\n\nexport const bridgeReadySchema = harnessV1BridgeReadySchema;\nexport type BridgeReady = z.infer<typeof bridgeReadySchema>;\n","/*\n * Temporary workaround for upstream codex CLI bug\n * https://github.com/openai/codex/issues/19425 — MCP tools registered via\n * `mcp_servers.*` are not exposed to the model in `codex exec --experimental-json`\n * mode (which is what `@openai/codex-sdk` uses). The MCP handshake completes\n * and `tools/list` succeeds, but codex never registers the tools as\n * model-callable functions.\n *\n * Until that's fixed upstream, this file implements a CLI-based relay:\n *\n * 1. A small Node script is written into harness-owned session state at turn\n * start (`buildCliShimScript`). It accepts `<toolName> <jsonInput>` as\n * argv, POSTs to the same HTTP relay the MCP shim uses, and prints the\n * result to stdout.\n *\n * 2. Tool descriptions and invocation instructions are injected into the\n * initial user prompt by the host adapter, telling the model to call host\n * tools by running `node <shim-path> <toolName> '<jsonInput>'` via its\n * built-in `bash` tool.\n *\n * 3. The bridge's event loop suppresses the matching `command_execution`\n * events (`isToolRelayCommand`) so callers receive clean `tool-call` /\n * `tool-result` events from the HTTP relay rather than seeing the\n * relay invocations as raw bash commands.\n *\n * Once #19425 is fixed and MCP tools are properly exposed in exec mode, the\n * three hookpoints in `bridge/index.ts` can be removed along with this file.\n */\nexport const CLI_SHIM_FILENAME = 'harness-tool.mjs';\n\nexport function buildCliShimScript({\n relayPort,\n}: {\n relayPort: number;\n}): string {\n return `#!/usr/bin/env node\nconst [toolName, inputJson = '{}'] = process.argv.slice(2);\nif (!toolName) {\n console.error('Usage: harness-tool <tool_name> <json_input>');\n process.exit(64);\n}\nlet input;\ntry {\n input = JSON.parse(inputJson);\n} catch (error) {\n console.error('Invalid JSON input: ' + (error instanceof Error ? error.message : String(error)));\n process.exit(64);\n}\nconst requestId = 'cli-' + Date.now() + '-' + Math.random().toString(16).slice(2);\nconst response = await fetch('http://127.0.0.1:${relayPort}', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ requestId, toolName, input }),\n});\nconst text = await response.text();\nlet payload;\ntry {\n payload = text ? JSON.parse(text) : {};\n} catch {\n payload = { error: text };\n}\nif (!response.ok || payload.error) {\n console.error(String(payload.error ?? ('tool relay failed with HTTP ' + response.status)));\n process.exit(1);\n}\nconsole.log(JSON.stringify(payload.result ?? payload, null, 2));\n`;\n}\n\nexport function isToolRelayCommand({\n command,\n cliShimPath,\n}: {\n command: string;\n cliShimPath: string;\n}): boolean {\n return parseToolRelayCommand({ command, cliShimPath }) !== undefined;\n}\n\nexport function parseToolRelayCommand({\n command,\n cliShimPath,\n}: {\n command: string;\n cliShimPath: string;\n}): { toolName: string; input: unknown } | undefined {\n return parseToolRelayCommandInternal({ command, cliShimPath, depth: 0 });\n}\n\nfunction parseToolRelayCommandInternal({\n command,\n cliShimPath,\n depth,\n}: {\n command: string;\n cliShimPath: string;\n depth: number;\n}): { toolName: string; input: unknown } | undefined {\n const argv = parseShellWords(command);\n if (!argv) return undefined;\n\n const relayCall = parseDirectToolRelayArgv({ argv, cliShimPath });\n if (relayCall) return relayCall;\n\n const innerCommand = extractShellEvalCommand(argv);\n if (!innerCommand || depth >= 2) return undefined;\n return parseToolRelayCommandInternal({\n command: innerCommand,\n cliShimPath,\n depth: depth + 1,\n });\n}\n\nfunction parseDirectToolRelayArgv({\n argv,\n cliShimPath,\n}: {\n argv: string[];\n cliShimPath: string;\n}): { toolName: string; input: unknown } | undefined {\n if (argv.length < 3 || argv.length > 4) return undefined;\n if (argv[0] !== 'node' || argv[1] !== cliShimPath) return undefined;\n const toolName = argv[2];\n if (!toolName) return undefined;\n try {\n return { toolName, input: JSON.parse(argv[3] ?? '{}') };\n } catch {\n return undefined;\n }\n}\n\nfunction extractShellEvalCommand(argv: string[]): string | undefined {\n if (argv.length !== 3) return undefined;\n const shellName = argv[0].split('/').at(-1);\n if (shellName !== 'bash' && shellName !== 'sh' && shellName !== 'zsh') {\n return undefined;\n }\n if (argv[1] !== '-c' && argv[1] !== '-lc') return undefined;\n return argv[2];\n}\n\nfunction parseShellWords(command: string): string[] | undefined {\n const words: string[] = [];\n let current = '';\n let quote: '\"' | \"'\" | undefined;\n let hasCurrent = false;\n\n const pushCurrent = () => {\n if (!hasCurrent) return;\n words.push(current);\n current = '';\n hasCurrent = false;\n };\n\n for (let i = 0; i < command.length; i++) {\n const char = command[i];\n if (quote === \"'\") {\n if (char === \"'\") {\n quote = undefined;\n } else {\n current += char;\n }\n hasCurrent = true;\n continue;\n }\n if (quote === '\"') {\n if (char === '\"') {\n quote = undefined;\n } else if (char === '\\\\' && i + 1 < command.length) {\n current += command[++i];\n } else {\n current += char;\n }\n hasCurrent = true;\n continue;\n }\n if (/\\s/.test(char)) {\n pushCurrent();\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n hasCurrent = true;\n continue;\n }\n if (char === '\\\\' && i + 1 < command.length) {\n current += command[++i];\n hasCurrent = true;\n continue;\n }\n if (/[;&|<>()`$]/.test(char)) return undefined;\n current += char;\n hasCurrent = true;\n }\n if (quote) return undefined;\n pushCurrent();\n return words;\n}\n","import { createCodex } from './codex-harness';\n\n/**\n * Default `codex` harness instance with no overrides — suitable for the\n * common case where the underlying `codex` CLI's defaults are fine.\n * Equivalent to `createCodex()`.\n */\nexport const codex = createCodex();\n\nexport { createCodex } from './codex-harness';\nexport type { CodexHarnessSettings } from './codex-harness';\nexport type { CodexAuthOptions } from './codex-auth';\n"],"mappings":"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","names":["z","z"]}
1
+ {"version":3,"sources":["../src/codex-harness.ts","../src/codex-auth.ts","../src/codex-bridge-protocol.ts","../src/bridge/cli-relay.ts","../src/index.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n commonTool,\n HarnessCapabilityUnsupportedError,\n harnessV1DiagnosticFromBridgeFrame,\n type HarnessV1,\n type HarnessV1Bootstrap,\n type HarnessV1DebugConfig,\n type HarnessV1BuiltinTool,\n type HarnessV1ContinueTurnState,\n type HarnessV1Prompt,\n type HarnessV1PromptControl,\n type HarnessV1ResumeSessionState,\n type HarnessV1NetworkSandboxSession,\n type HarnessV1PermissionMode,\n type HarnessV1Session,\n type HarnessV1Skill,\n type HarnessV1StreamPart,\n} from '@ai-sdk/harness';\nimport {\n classifyDiskLog,\n markBridgeStarting,\n resolveSandboxHomeDir,\n SandboxChannel,\n shellQuote,\n waitForBridgeReady,\n writeSkills as writeHarnessSkills,\n} from '@ai-sdk/harness/utils';\nimport {\n type Experimental_SandboxProcess,\n type Experimental_SandboxSession,\n} from '@ai-sdk/provider-utils';\nimport { WebSocket } from 'ws';\nimport { z } from 'zod/v4';\nimport { resolveCodexEnv, type CodexAuthOptions } from './codex-auth';\nimport {\n outboundMessageSchema,\n type InboundMessage,\n type OutboundMessage,\n} from './codex-bridge-protocol';\nimport { CLI_SHIM_FILENAME } from './bridge/cli-relay';\n\ntype CodexChannel = SandboxChannel<OutboundMessage, InboundMessage>;\ntype CodexRespawnStrategy = 'replay' | 'rerun';\n\ntype WriteSkillsResult = {\n readonly homeDir: string;\n readonly codexHomeDir: string;\n};\n\n/*\n * The model the adapter pins when the consumer configures none. The Codex SDK\n * does not report the model it resolves to at runtime (no model field on any\n * event), and exposes no default-model constant, so we pin the latest\n * codex-specialized model available for the bundled `@openai/codex@0.130.0`\n * (published 2026-05-08): `gpt-5.3-codex` (released 2026-02). Keep this in sync\n * when bumping the codex SDK/binary. Passing it explicitly makes the resolved\n * model deterministic and the telemetry (`gen_ai.request.model`) accurate.\n */\nconst DEFAULT_CODEX_MODEL = 'gpt-5.3-codex';\n\nexport type CodexHarnessSettings = {\n readonly auth?: CodexAuthOptions;\n /**\n * OpenAI model id the underlying `codex` CLI should use. Leaving this unset\n * pins the adapter default (`DEFAULT_CODEX_MODEL`).\n */\n readonly model?: string;\n /**\n * Reasoning effort for reasoning-capable models. Leaving this unset\n * defers to the CLI's default.\n */\n readonly reasoningEffort?: 'low' | 'medium' | 'high';\n /**\n * When `true`, allow the underlying runtime to use live web search.\n */\n readonly webSearch?: boolean;\n /**\n * Override the port the bridge binds inside the sandbox. By default the\n * adapter uses the first port the sandbox declares via `sandbox.ports`.\n * Only set this if the sandbox declares multiple ports and the first one\n * is reserved for something else.\n */\n readonly port?: number;\n /** Maximum milliseconds to wait for the bridge to advertise its port. Defaults to 120000. */\n readonly startupTimeoutMs?: number;\n};\n\n/*\n * Every native tool the Codex CLI can invoke as a model-callable tool,\n * declared as a `ToolSet` keyed by what the bridge emits as `toolName` on\n * the wire (`commonName ?? nativeName`). Schemas reflect the `ThreadItem`\n * union in `@openai/codex-sdk`'s `dist/index.d.ts`.\n *\n * Codex's other native operations (`apply_patch`, todo planning) surface\n * only as side-effect events (`file_change`, `todo_list`) and are not\n * model-callable tools — they don't appear here.\n */\nconst CODEX_BUILTIN_TOOLS = {\n bash: commonTool('bash', {\n nativeName: 'shell',\n toolUseKind: 'bash',\n description: 'Execute a shell command',\n inputSchema: z.object({ command: z.string() }),\n }),\n webSearch: commonTool('webSearch', {\n nativeName: 'web_search',\n toolUseKind: 'readonly',\n description: 'Search the web',\n inputSchema: z.object({ query: z.string() }),\n }),\n} as const satisfies Record<string, HarnessV1BuiltinTool<any, any>>;\n\n/*\n * Bootstrap lives in /tmp because it's pure derived state — the harness can\n * reinstall the CLI and bridge files on any fresh sandbox from the recipe.\n * Persistence comes from the sandbox provider's snapshot, not the path.\n *\n * The session work dir (`startOpts.sessionWorkDir`) lives under the sandbox's\n * default working directory — the provider's persistent mount — so any files\n * the agent edits survive both detach -> attach and stop -> snapshot -> resume\n * cycles. Harness infra derived from `sandboxSession.defaultWorkingDirectory`\n * lives under `.agent-runs`, outside the agent workdir.\n */\nconst BOOTSTRAP_DIR = '/tmp/harness/codex';\n\n/**\n * Live bridge coordinates returned by `doDetach()` and `doSuspendTurn()`. A\n * future process uses them to reopen a socket to the still-running bridge\n * (`attach`) instead of re-spawning it. Absent on a `doStop()` payload.\n */\nconst codexBridgeCoordsSchema = z.object({\n port: z.number(),\n token: z.string(),\n lastSeenEventId: z.number(),\n sandboxId: z.string().optional(),\n});\n\n/**\n * Schema for the adapter-specific lifecycle `data` payload Codex produces.\n * `threadId` is what `codex.resumeThread(...)` requires for the replay/rerun\n * rungs; the sandbox lookup is handled separately via\n * `provider.resumeSession({ sessionId })`. `bridge` carries live coordinates\n * for cross-process `attach` (present on `doDetach()` and `doSuspendTurn()`\n * payloads).\n */\nconst codexResumeStateSchema = z.object({\n threadId: z.string().optional(),\n bridge: codexBridgeCoordsSchema.optional(),\n});\n\ntype CodexBridgeCoords = z.infer<typeof codexBridgeCoordsSchema>;\n\nexport function createCodex(\n settings: CodexHarnessSettings = {},\n): HarnessV1<typeof CODEX_BUILTIN_TOOLS> {\n let cachedBootstrap: HarnessV1Bootstrap | undefined;\n\n return {\n specificationVersion: 'harness-v1',\n harnessId: 'codex',\n builtinTools: CODEX_BUILTIN_TOOLS,\n supportsBuiltinToolApprovals: false,\n lifecycleStateSchema: codexResumeStateSchema,\n getBootstrap: async () => {\n if (cachedBootstrap != null) return cachedBootstrap;\n const [pkg, lock, bridge, hostToolMcp] = await Promise.all([\n readBridgeAsset('package.json'),\n readBridgeAsset('pnpm-lock.yaml'),\n readBridgeAsset('index.mjs'),\n readBridgeAsset('host-tool-mcp.mjs'),\n ]);\n cachedBootstrap = {\n harnessId: 'codex',\n bootstrapDir: BOOTSTRAP_DIR,\n files: [\n { path: `${BOOTSTRAP_DIR}/package.json`, content: pkg },\n { path: `${BOOTSTRAP_DIR}/pnpm-lock.yaml`, content: lock },\n { path: `${BOOTSTRAP_DIR}/bridge.mjs`, content: bridge },\n {\n path: `${BOOTSTRAP_DIR}/host-tool-mcp.mjs`,\n content: hostToolMcp,\n },\n ],\n commands: [\n { command: `mkdir -p ${BOOTSTRAP_DIR}` },\n {\n command: `pnpm --dir ${BOOTSTRAP_DIR} install --frozen-lockfile --store-dir ${BOOTSTRAP_DIR}/.pnpm-store`,\n },\n ],\n };\n return cachedBootstrap;\n },\n doStart: async startOpts => {\n if (startOpts.builtinToolFiltering != null) {\n throw new HarnessCapabilityUnsupportedError({\n message:\n \"Harness 'codex' does not support built-in tool filtering controls.\",\n harnessId: 'codex',\n });\n }\n if (\n startOpts.permissionMode != null &&\n startOpts.permissionMode !== 'allow-all'\n ) {\n throw new HarnessCapabilityUnsupportedError({\n message:\n \"Harness 'codex' does not support built-in tool approval requests; use permissionMode: 'allow-all'.\",\n harnessId: 'codex',\n });\n }\n const sandboxSession = startOpts.sandboxSession;\n const session = sandboxSession.restricted();\n const sandboxId = sandboxSession.id;\n const lifecycleState = startOpts.continueFrom ?? startOpts.resumeFrom;\n const isResume = lifecycleState != null;\n const isContinue = startOpts.continueFrom != null;\n const resumeData =\n isResume && typeof lifecycleState?.data === 'object'\n ? (lifecycleState.data as {\n threadId?: unknown;\n bridge?: CodexBridgeCoords;\n })\n : undefined;\n const resumeThreadId = resumeData?.threadId;\n const resumeThreadIdString =\n typeof resumeThreadId === 'string' && resumeThreadId.length > 0\n ? resumeThreadId\n : undefined;\n const coords = resumeData?.bridge;\n\n const workDir = startOpts.sessionWorkDir;\n const sessionDataDir = `${sandboxSession.defaultWorkingDirectory}/.agent-runs/${startOpts.sessionId}`;\n const bridgeStateDir = `${sessionDataDir}/bridge`;\n const cliShimDir = `${sessionDataDir}/codex`;\n const cliShimPath = `${cliShimDir}/${CLI_SHIM_FILENAME}`;\n const timeoutMs = settings.startupTimeoutMs ?? 120_000;\n\n // Normalize each forwarded bridge diagnostics frame into the general\n // `HarnessV1Diagnostic` and report it. The adapter does no telemetry work\n // beyond this transport→emission mapping.\n const report = startOpts.observability?.report;\n const onDiagnostic = report\n ? (frame: Parameters<typeof harnessV1DiagnosticFromBridgeFrame>[0]) =>\n report(\n harnessV1DiagnosticFromBridgeFrame(frame, {\n sessionId: startOpts.sessionId,\n timestamp: Date.now(),\n }),\n )\n : undefined;\n\n /*\n * Rung 1 — ATTACH. With live coordinates, reopen a socket to the\n * still-running bridge. Parked between-turn sessions just attach and wait\n * for the next `start`; suspended in-flight turns request replay of\n * everything past the persisted cursor. No spawn, no fresh token. If the\n * bridge is gone the open throws and we fall through to a spawn-based\n * recovery.\n */\n if (coords) {\n try {\n const attachUrl =\n (await sandboxSession.getPortUrl({\n port: coords.port,\n protocol: 'ws',\n })) + `?agent_bridge_token=${encodeURIComponent(coords.token)}`;\n const attachChannel: CodexChannel = new SandboxChannel({\n connect: () => openWebSocket(attachUrl),\n outboundSchema: outboundMessageSchema,\n initialLastSeenEventId: coords.lastSeenEventId,\n onDiagnostic,\n });\n await attachChannel.open(isContinue ? { resume: true } : undefined);\n return createSession({\n sessionId: startOpts.sessionId,\n channel: attachChannel,\n cliShimPath,\n // The live bridge was spawned by another process; no process handle.\n proc: undefined,\n model: settings.model ?? DEFAULT_CODEX_MODEL,\n reasoningEffort: settings.reasoningEffort,\n webSearch: settings.webSearch,\n resumeThreadId: resumeThreadIdString,\n isResume: true,\n seedResumeThreadOnFirstPrompt: false,\n rerunContinue: false,\n bridgePort: coords.port,\n bridgeToken: coords.token,\n sandboxId,\n debug: startOpts.observability?.debug,\n permissionMode: startOpts.permissionMode,\n });\n } catch {\n // Bridge no longer reachable — recover by respawning below.\n }\n }\n\n /*\n * Rungs 2/3 — REPLAY vs RERUN. Respawn the bridge. `replay` is only sound\n * for `continueFrom`: those coordinates include the cursor the on-disk\n * log is replayed *from*. `resumeFrom` is a between-turn resume; even when\n * it carries bridge coordinates, replaying the previous turn would\n * re-deliver stale events into the next turn. Those resumes always `rerun`\n * via `codex.resumeThread(threadId)` when attach is unavailable.\n */\n let respawnStrategy: CodexRespawnStrategy | undefined = isResume\n ? 'rerun'\n : undefined;\n if (coords && isContinue) {\n const logRaw = await Promise.resolve(\n session.readTextFile({\n path: `${bridgeStateDir}/event-log.ndjson`,\n abortSignal: startOpts.abortSignal,\n }),\n ).catch(() => null);\n if ((await classifyDiskLog(logRaw)) === 'replay') {\n respawnStrategy = 'replay';\n }\n }\n\n const port = resolveBridgePort(sandboxSession, settings.port);\n const token = randomBytes(32).toString('hex');\n const codexSkillSetup =\n startOpts.skills && startOpts.skills.length > 0\n ? await writeCodexSkills({\n sandbox: session,\n skills: startOpts.skills,\n abortSignal: startOpts.abortSignal,\n })\n : undefined;\n const env = {\n ...resolveCodexEnv(settings.auth),\n BRIDGE_CHANNEL_TOKEN: token,\n BRIDGE_WS_PORT: String(port),\n ...(codexSkillSetup\n ? {\n HOME: codexSkillSetup.homeDir,\n CODEX_HOME: codexSkillSetup.codexHomeDir,\n }\n : {}),\n ...(respawnStrategy === 'replay'\n ? { BRIDGE_REPLAY_FROM_DISK: '1' }\n : {}),\n };\n\n if (respawnStrategy === undefined) {\n await session.run({\n command: `mkdir -p ${shellQuote(workDir)} ${shellQuote(bridgeStateDir)}`,\n abortSignal: startOpts.abortSignal,\n });\n }\n\n await markBridgeStarting({\n sandbox: session,\n bridgeStateDir,\n bridgeType: 'codex',\n abortSignal: startOpts.abortSignal,\n });\n\n const proc = await session.spawn({\n command: `node ${BOOTSTRAP_DIR}/bridge.mjs --workdir ${shellQuote(workDir)} --bridge-state-dir ${shellQuote(bridgeStateDir)} --bootstrap-dir ${shellQuote(BOOTSTRAP_DIR)} --cli-shim-dir ${shellQuote(cliShimDir)}`,\n env,\n abortSignal: startOpts.abortSignal,\n });\n\n const { port: boundPort } = await waitForBridgeReady({\n proc,\n sandbox: session,\n bridgeStateDir,\n bridgeType: 'codex',\n timeoutMs,\n abortSignal: startOpts.abortSignal,\n createTimeoutError: () =>\n new Error('codex bridge did not become ready in time.'),\n createExitError: () =>\n new Error('codex bridge exited before becoming ready.'),\n });\n void drainRest(proc.stdout);\n /*\n * Bridge stderr is the only diagnostic channel for what happens\n * inside the sandbox once the bridge is running (uncaught\n * exceptions, Codex SDK errors, network failures). Forward it\n * line-by-line to the host console so a mid-turn bridge crash can\n * be inspected from `pnpm dev` logs without redeploying. The\n * bridge itself writes nothing to stderr in steady state, so this\n * is silent on the happy path.\n */\n void forwardBridgeStderr(proc.stderr);\n\n const wsUrl =\n (await sandboxSession.getPortUrl({\n port: boundPort,\n protocol: 'ws',\n })) + `?agent_bridge_token=${encodeURIComponent(token)}`;\n\n const channel: CodexChannel = new SandboxChannel({\n connect: () => openWebSocket(wsUrl),\n outboundSchema: outboundMessageSchema,\n onDiagnostic,\n // In replay mode the respawned bridge reloaded the finished turn from\n // disk; seed the cursor and resume so it streams the tail (incl.\n // `finish`).\n ...(respawnStrategy === 'replay'\n ? { initialLastSeenEventId: coords?.lastSeenEventId ?? 0 }\n : {}),\n });\n await channel.open(\n respawnStrategy === 'replay' ? { resume: true } : undefined,\n );\n\n return createSession({\n sessionId: startOpts.sessionId,\n channel,\n cliShimPath,\n proc,\n model: settings.model ?? DEFAULT_CODEX_MODEL,\n reasoningEffort: settings.reasoningEffort,\n webSearch: settings.webSearch,\n resumeThreadId: resumeThreadIdString,\n isResume: respawnStrategy !== undefined,\n seedResumeThreadOnFirstPrompt: respawnStrategy !== undefined,\n rerunContinue: respawnStrategy === 'rerun',\n bridgePort: boundPort,\n bridgeToken: token,\n sandboxId,\n debug: startOpts.observability?.debug,\n permissionMode: startOpts.permissionMode,\n });\n },\n };\n}\n\nfunction resolveBridgePort(\n sandboxSession: HarnessV1NetworkSandboxSession,\n override: number | undefined,\n): number {\n if (override !== undefined) return override;\n if (sandboxSession.ports.length > 0) return sandboxSession.ports[0];\n throw new HarnessCapabilityUnsupportedError({\n harnessId: 'codex',\n message:\n 'The codex harness needs a TCP port exposed by the sandbox. ' +\n 'Create the sandbox with `ports: [<port>]` or pass `createCodex({ port })`.',\n });\n}\n\nasync function readBridgeAsset(name: string): Promise<string> {\n const candidates = [\n new URL(`./bridge/${name}`, import.meta.url),\n new URL(`../bridge/${name}`, import.meta.url),\n ];\n let lastErr: unknown;\n for (const url of candidates) {\n try {\n return await readFile(fileURLToPath(url), 'utf8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== 'ENOENT') throw err;\n lastErr = err;\n }\n }\n throw lastErr ?? new Error(`bridge asset not found: ${name}`);\n}\n\nasync function writeCodexSkills({\n sandbox,\n skills,\n abortSignal,\n}: {\n sandbox: Experimental_SandboxSession;\n skills: ReadonlyArray<HarnessV1Skill>;\n abortSignal?: AbortSignal;\n}): Promise<WriteSkillsResult> {\n const homeDir = await resolveSandboxHomeDir({ sandbox, abortSignal });\n const codexHomeDir = path.posix.join(homeDir, '.codex');\n await sandbox.run({\n command: `mkdir -p ${shellQuote(codexHomeDir)}`,\n abortSignal,\n });\n\n const rootDir = path.posix.join(homeDir, '.agents', 'skills');\n await writeHarnessSkills({\n sandbox,\n rootDir,\n skills,\n abortSignal,\n invalidSkillNameMessage: ({ name }) => `Invalid Codex skill name: ${name}`,\n invalidSkillFilePathMessage: ({ skillName, filePath }) =>\n `Invalid Codex skill file path for ${skillName}: ${filePath}`,\n });\n\n return {\n homeDir,\n codexHomeDir,\n };\n}\n\nasync function forwardBridgeStderr(\n stream: ReadableStream<Uint8Array>,\n): Promise<void> {\n try {\n const reader = stream.pipeThrough(new TextDecoderStream()).getReader();\n while (true) {\n const { value, done } = await reader.read();\n if (done) return;\n if (value) {\n const trimmed = value.endsWith('\\n') ? value.slice(0, -1) : value;\n if (trimmed.length > 0) {\n // eslint-disable-next-line no-console\n console.log(`[bridge stderr] ${trimmed}`);\n }\n }\n }\n } catch {\n // Reader errors are non-fatal — best-effort diagnostic only.\n }\n}\n\nasync function drainRest(stream: ReadableStream<Uint8Array>): Promise<void> {\n try {\n const reader = stream.pipeThrough(new TextDecoderStream()).getReader();\n while (true) {\n const { done } = await reader.read();\n if (done) return;\n }\n } catch {}\n}\n\nfunction openWebSocket(url: string): Promise<WebSocket> {\n return new Promise((resolve, reject) => {\n const ws = new WebSocket(url);\n const onOpen = () => {\n ws.off('error', onError);\n resolve(ws);\n };\n const onError = (err: Error) => {\n ws.off('open', onOpen);\n reject(err);\n };\n ws.once('open', onOpen);\n ws.once('error', onError);\n });\n}\n\nfunction createSession({\n sessionId,\n channel,\n cliShimPath,\n proc,\n model,\n reasoningEffort,\n webSearch,\n resumeThreadId,\n isResume,\n seedResumeThreadOnFirstPrompt,\n rerunContinue,\n bridgePort,\n bridgeToken,\n sandboxId,\n debug,\n permissionMode,\n}: {\n sessionId: string;\n channel: CodexChannel;\n cliShimPath: string;\n /** Undefined on `attach` — the live bridge was spawned by another process. */\n proc: Experimental_SandboxProcess | undefined;\n model: string | undefined;\n reasoningEffort: 'low' | 'medium' | 'high' | undefined;\n webSearch: boolean | undefined;\n resumeThreadId: string | undefined;\n isResume: boolean;\n seedResumeThreadOnFirstPrompt: boolean;\n rerunContinue: boolean;\n bridgePort: number;\n bridgeToken: string;\n sandboxId: string;\n debug: HarnessV1DebugConfig | undefined;\n permissionMode: HarnessV1PermissionMode | undefined;\n}): HarnessV1Session {\n let stopped = false;\n let stopPromise: Promise<void> | undefined;\n /*\n * Send the persisted threadId on the first prompt only when the bridge was\n * respawned (rerun/replay) so it takes the `codex.resumeThread(...)` branch.\n * An `attach`ed bridge already holds its threadState in memory and continues\n * on its own, so it needs no seed.\n */\n let pendingResumeThreadId = seedResumeThreadOnFirstPrompt\n ? resumeThreadId\n : undefined;\n /*\n * Initial prompt guidance is prepended to the first user message of a fresh\n * session only. A resumed session (attach/replay/rerun) already carried it\n * in its original first message (preserved in the persisted thread), so it\n * starts \"applied\".\n */\n let instructionsApplied = isResume;\n\n /*\n * Latest codex thread id, cached from the bridge's `bridge-thread`\n * announcements. Seeded from lifecycle state so `doDetach()` and `doStop()`\n * can include a thread id even before this process has run a turn.\n */\n let latestThreadId = resumeThreadId;\n channel.on('bridge-thread', msg => {\n latestThreadId = msg.threadId;\n });\n\n /*\n * Wire the channel into one turn's worth of events and return the control\n * surface. Shared by `doPromptTurn` (which sends a `start` afterwards) and\n * `doContinueTurn` (which attaches to an already-running/replayed turn, or sends\n * a rerun `start`). The only difference between the two entry points is the\n * `start` message, not the listener/abort/settle plumbing.\n */\n const wireTurn = (turnOpts: {\n emit: (event: HarnessV1StreamPart) => void;\n abortSignal?: AbortSignal;\n }): {\n control: HarnessV1PromptControl;\n sendStart: (send: () => void) => void;\n } => {\n let pendingResolve: (() => void) | undefined;\n let pendingReject: ((err: unknown) => void) | undefined;\n const done = new Promise<void>((resolve, reject) => {\n pendingResolve = resolve;\n pendingReject = reject;\n });\n\n const unsubs: Array<() => void> = [];\n const forward = (event: HarnessV1StreamPart) => {\n try {\n turnOpts.emit(event);\n } catch {}\n };\n\n const eventTypes = [\n 'stream-start',\n 'text-start',\n 'text-delta',\n 'text-end',\n 'reasoning-start',\n 'reasoning-delta',\n 'reasoning-end',\n 'tool-call',\n 'tool-approval-request',\n 'tool-result',\n 'file-change',\n 'finish-step',\n 'raw',\n ] as const;\n let isSettled = false;\n const settleSuccess = () => {\n if (isSettled) return;\n isSettled = true;\n for (const u of unsubs) u();\n pendingResolve!();\n };\n const settleError = (err: unknown) => {\n if (isSettled) return;\n isSettled = true;\n for (const u of unsubs) u();\n pendingReject!(err);\n };\n\n for (const type of eventTypes) {\n unsubs.push(\n channel.on(type, msg => {\n forward(msg);\n }),\n );\n }\n unsubs.push(\n channel.on('finish', msg => {\n forward(msg);\n settleSuccess();\n }),\n );\n unsubs.push(\n channel.on('error', msg => {\n forward(msg);\n settleError(msg.error);\n }),\n );\n\n /*\n * A `'suspended'` close is a graceful slice-boundary freeze the host\n * initiated (`doSuspendTurn`): the turn keeps running in the bridge and its\n * tail is replayed to the next process, so wind this turn down cleanly\n * rather than failing it. Any other close mid-turn is an unexpected drop.\n */\n const onClose = (_code?: number, reason?: string) => {\n if (isSettled) return;\n if (reason === 'suspended') {\n settleSuccess();\n return;\n }\n settleError(new Error('codex bridge closed before the turn finished.'));\n };\n channel.onClose(onClose);\n\n const onAbort = () => {\n if (isSettled) return;\n try {\n channel.send({ type: 'abort' });\n } catch {}\n settleError(\n turnOpts.abortSignal?.reason ??\n new DOMException('Aborted', 'AbortError'),\n );\n };\n if (turnOpts.abortSignal) {\n if (turnOpts.abortSignal.aborted) {\n onAbort();\n } else {\n turnOpts.abortSignal.addEventListener('abort', onAbort, {\n once: true,\n });\n }\n }\n\n const control: HarnessV1PromptControl = {\n submitToolResult: async input => {\n channel.send({\n type: 'tool-result',\n toolCallId: input.toolCallId,\n output: input.output,\n isError: input.isError,\n });\n },\n submitToolApproval: async input => {\n channel.send({\n type: 'tool-approval-response',\n approvalId: input.approvalId,\n approved: input.approved,\n reason: input.reason,\n });\n },\n submitUserMessage: async text => {\n channel.send({ type: 'user-message', text });\n },\n done,\n };\n\n return {\n control,\n sendStart: send => {\n /*\n * Codex can complete short turns without using tools. Deferring the\n * start frame gives the harness runner one event-loop turn to finish\n * wiring the prompt control and stream output before Codex can settle.\n */\n const timer = setTimeout(() => {\n if (isSettled) return;\n try {\n send();\n } catch (err) {\n settleError(err);\n }\n }, 0);\n timer.unref?.();\n },\n };\n };\n\n return {\n sessionId,\n isResume,\n modelId: model,\n doPromptTurn: async promptOpts => {\n const turn = wireTurn({\n emit: promptOpts.emit,\n abortSignal: promptOpts.abortSignal,\n });\n\n const tools = (promptOpts.tools ?? []).map(t => ({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema,\n }));\n let promptText = extractUserText(promptOpts.prompt);\n if (!instructionsApplied) {\n const instructions =\n (promptOpts.instructions ? promptOpts.instructions + '\\n\\n' : '') +\n 'Only respond with your `final` message once you have fully addressed the user request.';\n promptText = frameInitialPromptGuidance({\n instructions,\n toolUsageBlock:\n tools.length > 0\n ? composeToolUsageInstructions({\n tools,\n cliShimPath,\n })\n : undefined,\n userText: promptText,\n });\n }\n instructionsApplied = true;\n\n const startMessage = {\n type: 'start' as const,\n prompt: promptText,\n tools,\n model,\n reasoningEffort,\n webSearch,\n ...(permissionMode ? { permissionMode } : {}),\n ...(pendingResumeThreadId\n ? { resumeThreadId: pendingResumeThreadId }\n : {}),\n ...(debug ? { debug } : {}),\n };\n pendingResumeThreadId = undefined;\n turn.sendStart(() => channel.send(startMessage));\n\n return turn.control;\n },\n doContinueTurn: async continueOpts => {\n const turn = wireTurn({\n emit: continueOpts.emit,\n abortSignal: continueOpts.abortSignal,\n });\n\n /*\n * attach / replay: the still-running (or disk-replayed) turn streams into\n * the wired listeners — `doStart` opened the channel with `{ resume: true }`\n * so the bridge replays everything past the persisted cursor (including a\n * `finish` if the turn ended during the gap). No `start` is sent: issuing\n * one would clear the bridge's replay log and begin a new turn. Lossless.\n *\n * rerun: the bridge was respawned with no in-flight turn to attach to, so\n * re-drive codex's own thread via `resumeThreadId`. Lossy — work in flight\n * at the interruption is recomputed. This is the rare bridge-died\n * fallback; the common slice path is `attach`.\n */\n if (rerunContinue) {\n const threadId = pendingResumeThreadId ?? latestThreadId;\n pendingResumeThreadId = undefined;\n turn.sendStart(() =>\n channel.send({\n type: 'start' as const,\n /*\n * A continuation nudge rather than an empty prompt: `resumeThreadId`\n * rehydrates the prior thread, and this is the new user turn that\n * drives it forward. Keeping it non-empty avoids handing the runtime\n * an empty user message (and mirrors the claude-code adapter, where an\n * empty text block trips the Anthropic API's `cache_control` rule).\n */\n prompt: 'Continue.',\n tools: (continueOpts.tools ?? []).map(t => ({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema,\n })),\n model,\n reasoningEffort,\n webSearch,\n ...(permissionMode ? { permissionMode } : {}),\n ...(threadId ? { resumeThreadId: threadId } : {}),\n ...(debug ? { debug } : {}),\n }),\n );\n }\n\n return turn.control;\n },\n doCompact: async () => {\n /*\n * Codex compacts its context automatically inside the core turn loop\n * (~90% of the model context window), but the `codex exec` transport this\n * adapter drives exposes no manual compaction trigger and emits no\n * compaction event. Manual `compact()` is therefore unsupported; Codex's\n * own auto-compaction continues to run regardless.\n */\n throw new HarnessCapabilityUnsupportedError({\n message:\n \"Harness 'codex' does not support manual compaction; Codex auto-compacts its context internally.\",\n harnessId: 'codex',\n });\n },\n doDetach: async () => {\n if (stopped) {\n throw new Error(\n `codex session ${sessionId} is already stopped; cannot detach.`,\n );\n }\n stopped = true;\n const lastSeenEventId = await channel.suspend();\n const payload: HarnessV1ResumeSessionState = {\n type: 'resume-session',\n harnessId: 'codex',\n specificationVersion: 'harness-v1',\n data: {\n ...(latestThreadId ? { threadId: latestThreadId } : {}),\n bridge: {\n port: bridgePort,\n token: bridgeToken,\n lastSeenEventId,\n sandboxId,\n },\n },\n };\n return payload;\n },\n doDestroy: async () => {\n if (stopped) return stopPromise;\n stopped = true;\n stopPromise = (async () => {\n // Tell the channel we are tearing down so the bridge's post-shutdown\n // socket close finalises instead of triggering a reconnect.\n channel.beginClose();\n try {\n if (!channel.isClosed()) {\n channel.send({ type: 'shutdown' });\n }\n } catch {}\n let stopTimer: ReturnType<typeof setTimeout> | undefined;\n try {\n if (proc) {\n await Promise.race([\n proc.wait(),\n new Promise<void>(resolve => {\n stopTimer = setTimeout(resolve, 5000);\n stopTimer.unref?.();\n }),\n ]);\n }\n } finally {\n if (stopTimer) clearTimeout(stopTimer);\n try {\n await proc?.kill();\n } catch {}\n channel.close();\n }\n })();\n return stopPromise;\n },\n doStop: async () => {\n if (stopped) {\n throw new Error(\n `codex session ${sessionId} is already stopped; cannot stop.`,\n );\n }\n stopped = true;\n /*\n * If the bridge's channel already closed (e.g. mid-turn WS drop)\n * there is no one to ack a `detach` message. Synthesize an empty\n * payload — the workdir is still captured by the sandbox snapshot\n * during the subsequent `sandboxSession.stop()`, so the next turn can\n * resume the filesystem state. The trade-off: we lose\n * `threadId`, so the codex CLI starts a fresh thread on the\n * preserved workdir rather than resuming the prior conversation\n * inside Codex's runtime. Ability to continue beats throwing.\n */\n // Tell the channel we are tearing down so the bridge's post-detach\n // socket close finalises instead of triggering a reconnect.\n channel.beginClose();\n const data: unknown = channel.isClosed()\n ? {}\n : await new Promise<unknown>((resolve, reject) => {\n const timer = setTimeout(() => {\n unsub();\n reject(\n new Error(\n `codex session ${sessionId} did not reply to detach within 5s.`,\n ),\n );\n }, 5000);\n timer.unref?.();\n const unsub = channel.on('bridge-detach', msg => {\n clearTimeout(timer);\n unsub();\n resolve(msg.data);\n });\n try {\n channel.send({ type: 'detach' });\n } catch (err) {\n clearTimeout(timer);\n unsub();\n reject(err);\n }\n });\n\n let stopTimer: ReturnType<typeof setTimeout> | undefined;\n try {\n if (proc) {\n await Promise.race([\n proc.wait(),\n new Promise<void>(resolve => {\n stopTimer = setTimeout(resolve, 5000);\n stopTimer.unref?.();\n }),\n ]);\n }\n } finally {\n if (stopTimer) clearTimeout(stopTimer);\n try {\n await proc?.kill();\n } catch {}\n channel.close();\n }\n\n const payload: HarnessV1ResumeSessionState = {\n type: 'resume-session',\n harnessId: 'codex',\n specificationVersion: 'harness-v1',\n data: (data ?? {}) as HarnessV1ResumeSessionState['data'],\n };\n return payload;\n },\n doSuspendTurn: async () => {\n if (stopped) {\n throw new Error(\n `codex session ${sessionId} is stopped; cannot suspend.`,\n );\n }\n stopped = true;\n /*\n * Gracefully freeze the active turn at a precise cursor. `channel.suspend`\n * stops processing inbound frames (the cursor stops advancing exactly at\n * the last delivered event), drains what was already dispatched, then\n * closes the host socket with reason `'suspended'` — which `wireTurn`'s\n * `onClose` treats as a clean turn end. The bridge keeps the turn running\n * and accumulates events past the cursor for the next slice to replay. The\n * sandbox process is deliberately left alive (no `shutdown`/`detach`).\n */\n const lastSeenEventId = await channel.suspend();\n const payload: HarnessV1ContinueTurnState = {\n type: 'continue-turn',\n harnessId: 'codex',\n specificationVersion: 'harness-v1',\n data: {\n ...(latestThreadId ? { threadId: latestThreadId } : {}),\n bridge: {\n port: bridgePort,\n token: bridgeToken,\n lastSeenEventId,\n sandboxId,\n },\n },\n };\n return payload;\n },\n };\n}\n\n/*\n * Frame session instructions, host-tool relay guidance, and the user's text so\n * Codex treats the prepended blocks as operating guidance rather than user\n * prose. Applied only to the first user message of a fresh session.\n */\nfunction frameInitialPromptGuidance({\n instructions,\n toolUsageBlock,\n userText,\n}: {\n instructions: string | undefined;\n toolUsageBlock: string | undefined;\n userText: string;\n}): string {\n const blocks: string[] = [];\n if (instructions) {\n blocks.push(\n '<session-instructions>\\n' +\n 'The block below is operating guidance from the system, not a message from the user — follow it, but do not mention it or attribute it to the user.\\n\\n' +\n `${instructions}\\n` +\n '</session-instructions>',\n );\n }\n if (toolUsageBlock) blocks.push(toolUsageBlock);\n if (blocks.length === 0) return userText;\n return `${blocks.join('\\n\\n')}\\n\\n<user-message>\\n${userText}\\n</user-message>`;\n}\n\nfunction composeToolUsageInstructions({\n tools,\n cliShimPath,\n}: {\n tools: ReadonlyArray<{\n name: string;\n description?: string;\n inputSchema?: unknown;\n }>;\n cliShimPath: string;\n}): string {\n const lines: string[] = [\n '<host-tool-instructions>',\n 'You have access to the following host-provided tools. To use one, run the following command via your built-in `bash` tool:',\n '',\n ` node ${cliShimPath} <toolName> '<jsonInput>'`,\n '',\n 'The script prints the JSON result to stdout. Do not invent another way to call these tools — only this CLI invocation will work. Pass the JSON input as a single-quoted argument.',\n 'For every user request that depends on a host-provided tool, run a separate CLI invocation for each needed tool call in the current turn before answering. Do not reuse previous tool results, and do not say you used a host tool unless the command has completed in the current turn.',\n '',\n ];\n for (const toolSpec of tools) {\n lines.push(\n `- **${toolSpec.name}**${toolSpec.description ? ': ' + toolSpec.description : ''}`,\n );\n lines.push(\n ` - Input schema: \\`${JSON.stringify(toolSpec.inputSchema ?? {})}\\``,\n );\n }\n lines.push('</host-tool-instructions>');\n return lines.join('\\n');\n}\n\n/*\n * Reduce a `HarnessV1Prompt` to the plain user text the bridge forwards\n * to the Codex SDK. File and image parts on the message are not yet\n * supported by the underlying runtime — throw rather than silently drop\n * them so callers learn about the gap instead of seeing mysteriously\n * truncated prompts.\n */\nfunction extractUserText(prompt: HarnessV1Prompt): string {\n if (typeof prompt === 'string') return prompt;\n const { content } = prompt;\n if (typeof content === 'string') return content;\n const parts: string[] = [];\n for (const part of content) {\n if (part.type !== 'text') {\n throw new HarnessCapabilityUnsupportedError({\n harnessId: 'codex',\n message: `The codex harness does not yet support user message parts of type '${part.type}'. Pass a string or a user message whose content contains only text parts.`,\n });\n }\n parts.push(part.text);\n }\n return parts.join('\\n\\n');\n}\n","import { getAiGatewayAuthFromEnv } from '@ai-sdk/harness/utils';\n\nexport type CodexAuthOptions = {\n readonly openaiCompatible?: {\n readonly apiKey?: string;\n readonly baseUrl?: string;\n readonly modelProviderName?: string;\n readonly queryParamsJson?: string;\n };\n readonly openai?: {\n readonly apiKey?: string;\n readonly baseUrl?: string;\n readonly organization?: string;\n readonly project?: string;\n };\n readonly gateway?: {\n readonly apiKey?: string;\n readonly baseUrl?: string;\n };\n};\n\n/**\n * Resolve the environment-variable blob the codex bridge needs. Precedence:\n *\n * 1. Explicit `auth.openaiCompatible` — pin to a custom OpenAI-compatible endpoint.\n * 2. Explicit `auth.openai` — pin to direct OpenAI auth.\n * 3. Explicit `auth.gateway` — pin to Vercel AI Gateway.\n * 4. Auto-detect from the host process env: gateway first\n * (`AI_GATEWAY_API_KEY` / `VERCEL_OIDC_TOKEN`), then `CODEX_API_KEY` /\n * `OPENAI_API_KEY`.\n */\nexport function resolveCodexEnv(\n auth: CodexAuthOptions | undefined,\n processEnv: Record<string, string | undefined> = process.env,\n): Record<string, string> {\n if (auth?.openaiCompatible) {\n return pickOpenAICompatible(auth.openaiCompatible, processEnv);\n }\n if (auth?.openai) {\n return pickOpenAI({ explicit: auth.openai, processEnv });\n }\n const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({\n env: processEnv,\n });\n if (auth?.gateway) {\n return pickGateway({\n explicit: auth.gateway,\n gatewayAuthFromEnv,\n });\n }\n if (gatewayAuthFromEnv.apiKey) {\n return pickGateway({\n explicit: {},\n gatewayAuthFromEnv,\n });\n }\n return pickOpenAI({ processEnv });\n}\n\nfunction pickOpenAICompatible(\n explicit: NonNullable<CodexAuthOptions['openaiCompatible']>,\n processEnv: Record<string, string | undefined>,\n): Record<string, string> {\n const env: Record<string, string> = {};\n const apiKey =\n explicit.apiKey ?? processEnv.OPENAI_API_KEY ?? processEnv.CODEX_API_KEY;\n if (apiKey) env.CODEX_API_KEY = apiKey;\n if (explicit.baseUrl) env.OPENAI_BASE_URL = explicit.baseUrl;\n if (explicit.modelProviderName)\n env.CODEX_MODEL_PROVIDER_NAME = explicit.modelProviderName;\n if (explicit.queryParamsJson)\n env.OPENAI_QUERY_PARAMS_JSON = explicit.queryParamsJson;\n return env;\n}\n\nfunction pickOpenAI({\n explicit,\n processEnv,\n}: {\n explicit?: NonNullable<CodexAuthOptions['openai']>;\n processEnv: Record<string, string | undefined>;\n}): Record<string, string> {\n const env: Record<string, string> = {};\n const apiKey =\n explicit?.apiKey ?? processEnv.OPENAI_API_KEY ?? processEnv.CODEX_API_KEY;\n if (apiKey) env.CODEX_API_KEY = apiKey;\n const baseUrl = explicit?.baseUrl ?? processEnv.OPENAI_BASE_URL;\n if (baseUrl) env.OPENAI_BASE_URL = baseUrl;\n const organization = explicit?.organization ?? processEnv.OPENAI_ORGANIZATION;\n if (organization) env.OPENAI_ORGANIZATION = organization;\n const project = explicit?.project ?? processEnv.OPENAI_PROJECT;\n if (project) env.OPENAI_PROJECT = project;\n return env;\n}\n\nfunction pickGateway({\n explicit,\n gatewayAuthFromEnv,\n}: {\n explicit: NonNullable<CodexAuthOptions['gateway']>;\n gatewayAuthFromEnv: ReturnType<typeof getAiGatewayAuthFromEnv>;\n}): Record<string, string> {\n const apiKey = explicit.apiKey ?? gatewayAuthFromEnv.apiKey;\n const baseUrl = toCodexGatewayBaseUrl(\n explicit.baseUrl ?? gatewayAuthFromEnv.baseUrl,\n );\n const env: Record<string, string> = {};\n if (apiKey) {\n env.AI_GATEWAY_API_KEY = apiKey;\n env.CODEX_API_KEY = apiKey;\n }\n env.AI_GATEWAY_BASE_URL = baseUrl;\n return env;\n}\n\nfunction toCodexGatewayBaseUrl(baseUrl: string): string {\n const trimmed = baseUrl.replace(/\\/+$/, '');\n return trimmed.endsWith('/v1') ? trimmed : `${trimmed}/v1`;\n}\n","import {\n harnessV1BridgeInboundCommandSchemas,\n harnessV1BridgeOutboundMessageSchema,\n harnessV1BridgeReadySchema,\n harnessV1BridgeStartBaseSchema,\n} from '@ai-sdk/harness';\nimport { z } from 'zod/v4';\n\n/*\n * Codex's bridge wire protocol. The outbound events (including `file-change`\n * and the `bridge-thread` resume coordinate), transport frames, shared inbound\n * commands, and `bridge-ready` line all come from the shared `@ai-sdk/harness`\n * protocol — the only Codex-specific piece is the `start` payload.\n */\n\nexport const outboundMessageSchema = harnessV1BridgeOutboundMessageSchema;\nexport type OutboundMessage = z.infer<typeof outboundMessageSchema>;\n\nexport const startMessageSchema = harnessV1BridgeStartBaseSchema.extend({\n reasoningEffort: z.enum(['low', 'medium', 'high']).optional(),\n webSearch: z.boolean().optional(),\n // Resume signal. When supplied, the bridge calls\n // `codex.resumeThread(resumeThreadId, …)` instead of starting a fresh thread.\n // The host sources the id from lifecycle state `data` cached from a prior\n // `agent.detach`.\n resumeThreadId: z.string().optional(),\n});\n\nexport type StartMessage = z.infer<typeof startMessageSchema>;\n\nexport const inboundMessageSchema = z.discriminatedUnion('type', [\n startMessageSchema,\n ...harnessV1BridgeInboundCommandSchemas,\n]);\nexport type InboundMessage = z.infer<typeof inboundMessageSchema>;\n\nexport const bridgeReadySchema = harnessV1BridgeReadySchema;\nexport type BridgeReady = z.infer<typeof bridgeReadySchema>;\n","/*\n * Temporary workaround for upstream codex CLI bug\n * https://github.com/openai/codex/issues/19425 — MCP tools registered via\n * `mcp_servers.*` are not exposed to the model in `codex exec --experimental-json`\n * mode (which is what `@openai/codex-sdk` uses). The MCP handshake completes\n * and `tools/list` succeeds, but codex never registers the tools as\n * model-callable functions.\n *\n * Until that's fixed upstream, this file implements a CLI-based relay:\n *\n * 1. A small Node script is written into harness-owned session state at turn\n * start (`buildCliShimScript`). It accepts `<toolName> <jsonInput>` as\n * argv, POSTs to the same HTTP relay the MCP shim uses, and prints the\n * result to stdout.\n *\n * 2. Tool descriptions and invocation instructions are injected into the\n * initial user prompt by the host adapter, telling the model to call host\n * tools by running `node <shim-path> <toolName> '<jsonInput>'` via its\n * built-in `bash` tool.\n *\n * 3. The bridge's event loop suppresses the matching `command_execution`\n * events (`isToolRelayCommand`) so callers receive clean `tool-call` /\n * `tool-result` events from the HTTP relay rather than seeing the\n * relay invocations as raw bash commands.\n *\n * Once #19425 is fixed and MCP tools are properly exposed in exec mode, the\n * three hookpoints in `bridge/index.ts` can be removed along with this file.\n */\nexport const CLI_SHIM_FILENAME = 'harness-tool.mjs';\n\nexport function buildCliShimScript({\n relayPort,\n}: {\n relayPort: number;\n}): string {\n return `#!/usr/bin/env node\nconst [toolName, inputJson = '{}'] = process.argv.slice(2);\nif (!toolName) {\n console.error('Usage: harness-tool <tool_name> <json_input>');\n process.exit(64);\n}\nlet input;\ntry {\n input = JSON.parse(inputJson);\n} catch (error) {\n console.error('Invalid JSON input: ' + (error instanceof Error ? error.message : String(error)));\n process.exit(64);\n}\nconst requestId = 'cli-' + Date.now() + '-' + Math.random().toString(16).slice(2);\nconst response = await fetch('http://127.0.0.1:${relayPort}', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ requestId, toolName, input }),\n});\nconst text = await response.text();\nlet payload;\ntry {\n payload = text ? JSON.parse(text) : {};\n} catch {\n payload = { error: text };\n}\nif (!response.ok || payload.error) {\n console.error(String(payload.error ?? ('tool relay failed with HTTP ' + response.status)));\n process.exit(1);\n}\nconsole.log(JSON.stringify(payload.result ?? payload, null, 2));\n`;\n}\n\nexport function isToolRelayCommand({\n command,\n cliShimPath,\n}: {\n command: string;\n cliShimPath: string;\n}): boolean {\n return parseToolRelayCommand({ command, cliShimPath }) !== undefined;\n}\n\nexport function parseToolRelayCommand({\n command,\n cliShimPath,\n}: {\n command: string;\n cliShimPath: string;\n}): { toolName: string; input: unknown } | undefined {\n return parseToolRelayCommandInternal({ command, cliShimPath, depth: 0 });\n}\n\nfunction parseToolRelayCommandInternal({\n command,\n cliShimPath,\n depth,\n}: {\n command: string;\n cliShimPath: string;\n depth: number;\n}): { toolName: string; input: unknown } | undefined {\n const argv = parseShellWords(command);\n if (!argv) return undefined;\n\n const relayCall = parseDirectToolRelayArgv({ argv, cliShimPath });\n if (relayCall) return relayCall;\n\n const innerCommand = extractShellEvalCommand(argv);\n if (!innerCommand || depth >= 2) return undefined;\n return parseToolRelayCommandInternal({\n command: innerCommand,\n cliShimPath,\n depth: depth + 1,\n });\n}\n\nfunction parseDirectToolRelayArgv({\n argv,\n cliShimPath,\n}: {\n argv: string[];\n cliShimPath: string;\n}): { toolName: string; input: unknown } | undefined {\n if (argv.length < 3 || argv.length > 4) return undefined;\n if (argv[0] !== 'node' || argv[1] !== cliShimPath) return undefined;\n const toolName = argv[2];\n if (!toolName) return undefined;\n try {\n return { toolName, input: JSON.parse(argv[3] ?? '{}') };\n } catch {\n return undefined;\n }\n}\n\nfunction extractShellEvalCommand(argv: string[]): string | undefined {\n if (argv.length !== 3) return undefined;\n const shellName = argv[0].split('/').at(-1);\n if (shellName !== 'bash' && shellName !== 'sh' && shellName !== 'zsh') {\n return undefined;\n }\n if (argv[1] !== '-c' && argv[1] !== '-lc') return undefined;\n return argv[2];\n}\n\nfunction parseShellWords(command: string): string[] | undefined {\n const words: string[] = [];\n let current = '';\n let quote: '\"' | \"'\" | undefined;\n let hasCurrent = false;\n\n const pushCurrent = () => {\n if (!hasCurrent) return;\n words.push(current);\n current = '';\n hasCurrent = false;\n };\n\n for (let i = 0; i < command.length; i++) {\n const char = command[i];\n if (quote === \"'\") {\n if (char === \"'\") {\n quote = undefined;\n } else {\n current += char;\n }\n hasCurrent = true;\n continue;\n }\n if (quote === '\"') {\n if (char === '\"') {\n quote = undefined;\n } else if (char === '\\\\' && i + 1 < command.length) {\n current += command[++i];\n } else {\n current += char;\n }\n hasCurrent = true;\n continue;\n }\n if (/\\s/.test(char)) {\n pushCurrent();\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n hasCurrent = true;\n continue;\n }\n if (char === '\\\\' && i + 1 < command.length) {\n current += command[++i];\n hasCurrent = true;\n continue;\n }\n if (/[;&|<>()`$]/.test(char)) return undefined;\n current += char;\n hasCurrent = true;\n }\n if (quote) return undefined;\n pushCurrent();\n return words;\n}\n","import { createCodex } from './codex-harness';\n\n/**\n * Default `codex` harness instance with no overrides — suitable for the\n * common case where the underlying `codex` CLI's defaults are fine.\n * Equivalent to `createCodex()`.\n */\nexport const codex = createCodex();\n\nexport { createCodex } from './codex-harness';\nexport type { CodexHarnessSettings } from './codex-harness';\nexport type { CodexAuthOptions } from './codex-auth';\n"],"mappings":"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","names":["z","z"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/harness-codex",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -27,8 +27,8 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "ws": "^8.21.0",
30
- "@ai-sdk/harness": "1.0.11",
31
- "@ai-sdk/provider-utils": "5.0.2"
30
+ "@ai-sdk/harness": "1.0.13",
31
+ "@ai-sdk/provider-utils": "5.0.3"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "zod": "^3.25.76 || ^4.1.8"
@@ -107,12 +107,12 @@ for (const schema of schemas) {
107
107
  );
108
108
  }
109
109
 
110
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
111
- function toZodShape(schema: JsonSchemaObject | undefined): Record<string, any> {
110
+ function toZodShape(
111
+ schema: JsonSchemaObject | undefined,
112
+ ): Record<string, z.ZodTypeAny> {
112
113
  if (!schema?.properties) return {};
113
114
  const required = new Set(schema.required ?? []);
114
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
115
- const shape: Record<string, any> = {};
115
+ const shape: Record<string, z.ZodTypeAny> = {};
116
116
  for (const [key, propSchema] of Object.entries(schema.properties)) {
117
117
  const propType = toZodType(propSchema);
118
118
  shape[key] = required.has(key) ? propType : propType.optional();
@@ -120,13 +120,12 @@ function toZodShape(schema: JsonSchemaObject | undefined): Record<string, any> {
120
120
  return shape;
121
121
  }
122
122
 
123
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
- function toZodType(schema: JsonSchemaObject | undefined): any {
123
+ function toZodType(schema: JsonSchemaObject | undefined): z.ZodTypeAny {
125
124
  if (!schema) return z.any();
126
125
  const types = Array.isArray(schema.type)
127
126
  ? schema.type.filter((t): t is string => t !== 'null')
128
127
  : ([schema.type].filter(Boolean) as string[]);
129
- let zType;
128
+ let zType: z.ZodTypeAny;
130
129
  switch (types[0]) {
131
130
  case 'string':
132
131
  zType = z.string();
@@ -23,8 +23,11 @@ import {
23
23
  import {
24
24
  classifyDiskLog,
25
25
  markBridgeStarting,
26
+ resolveSandboxHomeDir,
26
27
  SandboxChannel,
28
+ shellQuote,
27
29
  waitForBridgeReady,
30
+ writeSkills as writeHarnessSkills,
28
31
  } from '@ai-sdk/harness/utils';
29
32
  import {
30
33
  type Experimental_SandboxProcess,
@@ -192,6 +195,13 @@ export function createCodex(
192
195
  return cachedBootstrap;
193
196
  },
194
197
  doStart: async startOpts => {
198
+ if (startOpts.builtinToolFiltering != null) {
199
+ throw new HarnessCapabilityUnsupportedError({
200
+ message:
201
+ "Harness 'codex' does not support built-in tool filtering controls.",
202
+ harnessId: 'codex',
203
+ });
204
+ }
195
205
  if (
196
206
  startOpts.permissionMode != null &&
197
207
  startOpts.permissionMode !== 'allow-all'
@@ -316,7 +326,7 @@ export function createCodex(
316
326
  const token = randomBytes(32).toString('hex');
317
327
  const codexSkillSetup =
318
328
  startOpts.skills && startOpts.skills.length > 0
319
- ? await writeSkills({
329
+ ? await writeCodexSkills({
320
330
  sandbox: session,
321
331
  skills: startOpts.skills,
322
332
  abortSignal: startOpts.abortSignal,
@@ -456,7 +466,7 @@ async function readBridgeAsset(name: string): Promise<string> {
456
466
  throw lastErr ?? new Error(`bridge asset not found: ${name}`);
457
467
  }
458
468
 
459
- async function writeSkills({
469
+ async function writeCodexSkills({
460
470
  sandbox,
461
471
  skills,
462
472
  abortSignal,
@@ -465,16 +475,6 @@ async function writeSkills({
465
475
  skills: ReadonlyArray<HarnessV1Skill>;
466
476
  abortSignal?: AbortSignal;
467
477
  }): Promise<WriteSkillsResult> {
468
- for (const skill of skills) {
469
- safeCodexSkillName(skill.name);
470
- for (const file of skill.files ?? []) {
471
- safeCodexSkillFilePath({
472
- skillName: skill.name,
473
- filePath: file.path,
474
- });
475
- }
476
- }
477
-
478
478
  const homeDir = await resolveSandboxHomeDir({ sandbox, abortSignal });
479
479
  const codexHomeDir = path.posix.join(homeDir, '.codex');
480
480
  await sandbox.run({
@@ -483,92 +483,22 @@ async function writeSkills({
483
483
  });
484
484
 
485
485
  const rootDir = path.posix.join(homeDir, '.agents', 'skills');
486
- await sandbox.run({
487
- command: `mkdir -p ${shellQuote(rootDir)}`,
486
+ await writeHarnessSkills({
487
+ sandbox,
488
+ rootDir,
489
+ skills,
488
490
  abortSignal,
491
+ invalidSkillNameMessage: ({ name }) => `Invalid Codex skill name: ${name}`,
492
+ invalidSkillFilePathMessage: ({ skillName, filePath }) =>
493
+ `Invalid Codex skill file path for ${skillName}: ${filePath}`,
489
494
  });
490
495
 
491
- for (const skill of skills) {
492
- const name = safeCodexSkillName(skill.name);
493
- const skillDir = path.posix.join(rootDir, name);
494
- const content = `---\nname: ${skill.name}\ndescription: ${skill.description}\n---\n\n${skill.content}`;
495
-
496
- await sandbox.writeTextFile({
497
- path: path.posix.join(skillDir, 'SKILL.md'),
498
- content,
499
- abortSignal,
500
- });
501
-
502
- for (const file of skill.files ?? []) {
503
- const filePath = safeCodexSkillFilePath({
504
- skillName: skill.name,
505
- filePath: file.path,
506
- });
507
- await sandbox.writeTextFile({
508
- path: path.posix.join(skillDir, filePath),
509
- content: file.content,
510
- abortSignal,
511
- });
512
- }
513
- }
514
-
515
496
  return {
516
497
  homeDir,
517
498
  codexHomeDir,
518
499
  };
519
500
  }
520
501
 
521
- async function resolveSandboxHomeDir({
522
- sandbox,
523
- abortSignal,
524
- }: {
525
- sandbox: Experimental_SandboxSession;
526
- abortSignal?: AbortSignal;
527
- }): Promise<string> {
528
- const result = await sandbox.run({
529
- command: 'printf "%s" "$HOME"',
530
- abortSignal,
531
- });
532
- const homeDir = result.stdout.trim();
533
- if (result.exitCode !== 0 || !homeDir || !path.posix.isAbsolute(homeDir)) {
534
- throw new Error(
535
- `Unable to resolve sandbox HOME directory: ${result.stderr || result.stdout}`,
536
- );
537
- }
538
- return homeDir;
539
- }
540
-
541
- function safeCodexSkillName(name: string): string {
542
- if (!/^[A-Za-z0-9._-]+$/.test(name) || name === '.' || name === '..') {
543
- throw new Error(`Invalid Codex skill name: ${name}`);
544
- }
545
- return name;
546
- }
547
-
548
- function safeCodexSkillFilePath({
549
- skillName,
550
- filePath,
551
- }: {
552
- skillName: string;
553
- filePath: string;
554
- }): string {
555
- const normalized = path.posix.normalize(filePath);
556
- if (
557
- normalized === '.' ||
558
- normalized.startsWith('../') ||
559
- path.posix.isAbsolute(normalized)
560
- ) {
561
- throw new Error(
562
- `Invalid Codex skill file path for ${skillName}: ${filePath}`,
563
- );
564
- }
565
- return normalized;
566
- }
567
-
568
- function shellQuote(value: string): string {
569
- return `'${value.replace(/'/g, `'\\''`)}'`;
570
- }
571
-
572
502
  async function forwardBridgeStderr(
573
503
  stream: ReadableStream<Uint8Array>,
574
504
  ): Promise<void> {