import path from "node:path"; import type { AgentMeta, AgentParseResult, AgentSpec, BuildArgsContext } from "./types.js"; function parseCodexJson(raw: string): AgentParseResult { const lines = raw.split(/\n+/).filter((l) => l.trim().startsWith("{")); let text: string | undefined; let meta: AgentMeta | undefined; for (const line of lines) { try { const ev = JSON.parse(line) as { type?: string; item?: { type?: string; text?: string }; usage?: unknown }; if (ev.type === "item.completed" && ev.item?.type === "agent_message" && typeof ev.item.text === "string") { text = ev.item.text; } if (ev.type === "turn.completed" && ev.usage && typeof ev.usage === "object") { const u = ev.usage as { input_tokens?: number; cached_input_tokens?: number; output_tokens?: number; }; meta = { usage: { input: u.input_tokens, output: u.output_tokens, cacheRead: u.cached_input_tokens, total: (u.input_tokens ?? 0) + (u.output_tokens ?? 0) + (u.cached_input_tokens ?? 0), }, }; } } catch { // ignore } } return { text: text?.trim(), meta }; } export const codexSpec: AgentSpec = { kind: "codex", isInvocation: (argv) => argv.length > 0 && path.basename(argv[0]) === "codex", buildArgs: (ctx) => { const argv = [...ctx.argv]; const hasExec = argv.length > 0 && argv[1] === "exec"; if (!hasExec) { argv.splice(1, 0, "exec"); } // Ensure JSON output if (!argv.includes("--json")) { argv.splice(argv.length - 1, 0, "--json"); } // Safety defaults if (!argv.includes("--skip-git-repo-check")) { argv.splice(argv.length - 1, 0, "--skip-git-repo-check"); } if (!argv.some((p) => p === "--sandbox" || p.startsWith("--sandbox="))) { argv.splice(argv.length - 1, 0, "--sandbox", "read-only"); } return argv; }, parseOutput: parseCodexJson, };