diff --git a/src/agents/cli-output.ts b/src/agents/cli-output.ts index 3486da3be2db..395622e7a35e 100644 --- a/src/agents/cli-output.ts +++ b/src/agents/cli-output.ts @@ -14,6 +14,7 @@ import type { MessagingToolSend, MessagingToolSourceReplyPayload, } from "./embedded-agent-messaging.types.js"; +import type { ToolSummaryTrace } from "./embedded-agent-runner/types.js"; export type CliUsage = { input?: number; @@ -51,6 +52,7 @@ export type CliOutput = { usage?: CliUsage; /** Terminal cumulative turn usage for diagnostics; reply accounting keeps using `usage`. */ diagnosticUsage?: CliUsage; + toolSummary?: ToolSummaryTrace; errorText?: string; terminalFailure?: CliTerminalFailure; diagnostics?: { @@ -154,6 +156,11 @@ function isGeminiCliProvider(providerId: string): boolean { return normalizeLowercaseStringOrEmpty(providerId) === "google-gemini-cli"; } +function isCodexExecJsonlProvider(providerId: string): boolean { + const normalized = normalizeLowercaseStringOrEmpty(providerId); + return normalized === "codex" || normalized === "codex-cli"; +} + function isGeminiStreamJsonDialect(params: { backend: CliBackendConfig; providerId: string; @@ -1167,6 +1174,128 @@ function dispatchGeminiCliStreamingToolEvent(params: { } } +type CodexToolEvent = { + toolCallId: string; + name: string; + kind: CliToolUseStartDelta["kind"]; + args: Record; + result?: unknown; + isError: boolean; +}; + +function readCodexToolEvent(item: Record): CodexToolEvent | null { + const toolCallId = typeof item.id === "string" ? item.id.trim() : ""; + if (!toolCallId) { + return null; + } + const type = normalizeLowercaseStringOrEmpty(item.type); + if (type === "command_execution") { + return { + toolCallId, + name: "bash", + kind: "tool_use", + args: typeof item.command === "string" ? { command: item.command } : {}, + result: item.aggregated_output, + isError: item.status === "failed" || item.status === "declined", + }; + } + if (type === "file_change") { + return { + toolCallId, + name: "apply_patch", + kind: "tool_use", + args: Array.isArray(item.changes) ? { changes: item.changes } : {}, + result: item.changes, + isError: item.status === "failed", + }; + } + if (type === "web_search") { + return { + toolCallId, + name: "web_search", + kind: "server_tool_use", + args: typeof item.query === "string" ? { query: item.query } : {}, + result: item.query, + isError: false, + }; + } + if (type !== "mcp_tool_call") { + if (type !== "collab_tool_call") { + return null; + } + const tool = typeof item.tool === "string" ? item.tool.trim() : ""; + if (!tool) { + return null; + } + return { + toolCallId, + name: `collab.${tool}`, + kind: "server_tool_use", + args: { + ...(typeof item.sender_thread_id === "string" + ? { sender_thread_id: item.sender_thread_id } + : {}), + ...(Array.isArray(item.receiver_thread_ids) + ? { receiver_thread_ids: item.receiver_thread_ids } + : {}), + ...(typeof item.prompt === "string" ? { prompt: item.prompt } : {}), + }, + result: item.agents_states, + isError: item.status === "failed", + }; + } + const server = typeof item.server === "string" ? item.server.trim() : ""; + const tool = typeof item.tool === "string" ? item.tool.trim() : ""; + if (!tool) { + return null; + } + return { + toolCallId, + name: server ? `${server}.${tool}` : tool, + kind: "mcp_tool_use", + args: isRecord(item.arguments) ? item.arguments : {}, + result: item.status === "failed" ? item.error : item.result, + isError: item.status === "failed", + }; +} + +function dispatchCodexCliStreamingToolEvent(params: { + providerId: string; + parsed: Record; + tracker: ToolUseTracker; + onToolUseStart?: (delta: CliToolUseStartDelta) => void; + onToolResult?: (delta: CliToolResultDelta) => void; +}): void { + if ( + !isCodexExecJsonlProvider(params.providerId) || + (params.parsed.type !== "item.started" && params.parsed.type !== "item.completed") || + !isRecord(params.parsed.item) + ) { + return; + } + const event = readCodexToolEvent(params.parsed.item); + if (!event) { + return; + } + emitToolStartOnce( + params.tracker, + event.toolCallId, + event.name, + event.kind, + event.args, + params.onToolUseStart, + ); + if (params.parsed.type === "item.completed") { + emitToolResultOnce( + params.tracker, + event.toolCallId, + event.isError, + event.result, + params.onToolResult, + ); + } +} + const GEMINI_CLI_ERROR_EVENT_FALLBACK = "Gemini CLI emitted an error event."; const GEMINI_CLI_RESULT_ERROR_FALLBACK = "Gemini CLI result status was error."; @@ -1398,6 +1527,13 @@ export function createCliJsonlStreamingParser(params: { } if (params.onToolUseStart || params.onToolResult) { + dispatchCodexCliStreamingToolEvent({ + providerId: params.providerId, + parsed, + tracker: toolTracker, + onToolUseStart: params.onToolUseStart, + onToolResult: params.onToolResult, + }); dispatchGeminiCliStreamingToolEvent({ backend: params.backend, providerId: params.providerId, diff --git a/src/agents/cli-runner.before-agent-reply-cron.test.ts b/src/agents/cli-runner.before-agent-reply-cron.test.ts index eb6f78c0c270..115098277651 100644 --- a/src/agents/cli-runner.before-agent-reply-cron.test.ts +++ b/src/agents/cli-runner.before-agent-reply-cron.test.ts @@ -217,6 +217,21 @@ describe("runCliAgent before_agent_reply seam", () => { expect(executePreparedCliRunMock).toHaveBeenCalledOnce(); }); + it("projects CLI tool summaries onto terminal run metadata", async () => { + executePreparedCliRunMock.mockResolvedValue({ + text: "done", + toolSummary: { calls: 1, tools: ["github.search"], failures: 0 }, + }); + + const result = await runCliAgent(baseRunParams); + + expect(result.meta.toolSummary).toEqual({ + calls: 1, + tools: ["github.search"], + failures: 0, + }); + }); + it("preserves the send phase when execution fails before successful cleanup", async () => { executePreparedCliRunMock.mockRejectedValueOnce(new Error("CLI process failed")); diff --git a/src/agents/cli-runner.codex-tool-summary.test.ts b/src/agents/cli-runner.codex-tool-summary.test.ts new file mode 100644 index 000000000000..1a89a5d5b0e5 --- /dev/null +++ b/src/agents/cli-runner.codex-tool-summary.test.ts @@ -0,0 +1,108 @@ +/** Tests Codex exec JSONL tool-summary projection through the CLI process boundary. */ +import { readFileSync } from "node:fs"; +import { beforeEach, describe, expect, it } from "vitest"; +import { + buildPreparedCliRunContext, + type PreparedCliRunContextOverrides, +} from "./cli-runner.test-helpers.js"; +import { createManagedRun, supervisorSpawnMock } from "./cli-runner.test-support.js"; +import { executePreparedCliRun } from "./cli-runner/execute.js"; + +const CODEX_BACKEND: PreparedCliRunContextOverrides["backend"] = { + output: "jsonl", + sessionIdFields: ["thread_id"], + systemPromptFileConfigArg: undefined, +}; + +function queueCodexFixture(name: string) { + supervisorSpawnMock.mockResolvedValueOnce( + createManagedRun({ + reason: "exit", + exitCode: 0, + exitSignal: null, + durationMs: 10, + stdout: readFileSync(`test/fixtures/cli/${name}.jsonl`, "utf8"), + stderr: "", + timedOut: false, + noOutputTimedOut: false, + }), + ); +} + +async function runCodexFixture(name: string) { + queueCodexFixture(name); + return await executePreparedCliRun( + buildPreparedCliRunContext({ + provider: "codex-cli", + model: "gpt-5.5", + backend: CODEX_BACKEND, + }), + ); +} + +beforeEach(() => { + supervisorSpawnMock.mockReset(); +}); + +describe("Codex CLI tool summaries", () => { + it("emits an explicit empty summary for a successful zero-tool turn", async () => { + const output = await runCodexFixture("codex-tool-summary-zero"); + + expect(output.toolSummary).toEqual({ calls: 0, tools: [], failures: 0 }); + }); + + it("counts paired MCP lifecycle events once", async () => { + const output = await runCodexFixture("codex-tool-summary-paired-mcp"); + + expect(output.toolSummary).toEqual({ calls: 1, tools: ["github.search"], failures: 0 }); + }); + + it("projects terminal-only MCP and native items in first-observed order", async () => { + const output = await runCodexFixture("codex-tool-summary-terminal-only"); + + expect(output.toolSummary).toEqual({ + calls: 4, + tools: ["lookup", "bash", "apply_patch", "web_search"], + failures: 0, + }); + }); + + it("counts a typed failed terminal MCP item", async () => { + const output = await runCodexFixture("codex-tool-summary-failed-mcp"); + + expect(output.toolSummary).toEqual({ calls: 1, tools: ["docs.read"], failures: 1 }); + }); + + it("counts a declined command terminal as a failure", async () => { + const output = await runCodexFixture("codex-tool-summary-declined-command"); + + expect(output.toolSummary).toEqual({ calls: 1, tools: ["bash"], failures: 1 }); + }); + + it.each([ + { + fixture: "codex-tool-summary-collab-paired", + tool: "collab.spawn_agent", + failures: 0, + }, + { + fixture: "codex-tool-summary-collab-terminal-only", + tool: "collab.wait", + failures: 0, + }, + { + fixture: "codex-tool-summary-collab-failed", + tool: "collab.send_input", + failures: 1, + }, + { + fixture: "codex-tool-summary-collab-close-agent", + tool: "collab.close_agent", + failures: 0, + }, + ])("projects $fixture lifecycle metadata", async ({ fixture, tool, failures }) => { + const output = await runCodexFixture(fixture); + + expect(output.toolSummary).toEqual({ calls: 1, tools: [tool], failures }); + }); +}); diff --git a/src/agents/cli-runner.ts b/src/agents/cli-runner.ts index 07cd868bbc2e..272ceb481f3b 100644 --- a/src/agents/cli-runner.ts +++ b/src/agents/cli-runner.ts @@ -1209,6 +1209,9 @@ export async function runPreparedCliAgent( stopReason, refusal: false, }, + ...(resultParams.output.toolSummary + ? { toolSummary: resultParams.output.toolSummary } + : {}), agentMeta: { sessionId: agentSessionId, provider: params.provider, diff --git a/src/agents/cli-runner/execute-events.ts b/src/agents/cli-runner/execute-events.ts index 1cfebf5f2575..ee8a5802af61 100644 --- a/src/agents/cli-runner/execute-events.ts +++ b/src/agents/cli-runner/execute-events.ts @@ -7,6 +7,7 @@ import type { CliThinkingProgress, CliToolUseStartDelta, } from "../cli-output.js"; +import type { ToolSummaryTrace } from "../embedded-agent-runner/types.js"; import { sanitizeToolArgs, sanitizeToolResult } from "../embedded-agent-subscribe.tools.js"; import { applyPluginTextReplacements } from "../plugin-text-transforms.js"; import { resolveCliToolTerminalReason } from "../run-termination.js"; @@ -20,6 +21,10 @@ type CliToolResult = { result?: unknown; }; +function resolveCliToolSource(name: string, kind?: CliToolUseStartDelta["kind"]): "core" | "mcp" { + return kind === "mcp_tool_use" || name.startsWith("mcp__") ? "mcp" : "core"; +} + export function createCliEventHandlers(params: { context: PreparedCliRunContext; toolTracking: CliToolTracking; @@ -32,12 +37,49 @@ export function createCliEventHandlers(params: { let signaledToolExecutionStarted = false; let signaledAssistantOutputStarted = false; let commentaryCounter = 0; + const toolSummaryById = new Map(); + const toolSummaryNames: string[] = []; + const toolSummaryNameSet = new Set(); const activeParsedTools = new Map< string, { startedAt: number; toolName: string; kind: CliToolUseStartDelta["kind"] } >(); + const rememberToolName = (name: string) => { + if (!name || toolSummaryNameSet.has(name)) { + return; + } + toolSummaryNameSet.add(name); + toolSummaryNames.push(name); + }; + const recordToolStart = (event: CliToolUseStartDelta) => { + const current = toolSummaryById.get(event.toolCallId); + if (!current) { + toolSummaryById.set(event.toolCallId, { name: event.name, failed: false }); + } else if (!current.name && event.name) { + current.name = event.name; + } + rememberToolName(event.name); + }; + const recordToolResult = (event: CliToolResult) => { + const current = toolSummaryById.get(event.toolCallId); + if (current) { + current.failed ||= event.isError; + if (!current.name && event.name) { + current.name = event.name; + } + } else { + toolSummaryById.set(event.toolCallId, { name: event.name, failed: event.isError }); + } + rememberToolName(event.name); + }; + const getToolSummary = (): ToolSummaryTrace => ({ + calls: toolSummaryById.size, + tools: toolSummaryNames.slice(), + failures: Array.from(toolSummaryById.values()).filter((entry) => entry.failed).length, + }); const emitCliToolUseStart = (event: CliToolUseStartDelta) => { observedCliActivity = true; + recordToolStart(event); if (!signaledToolExecutionStarted) { signaledToolExecutionStarted = true; runParams.onExecutionPhase?.({ @@ -63,6 +105,7 @@ export function createCliEventHandlers(params: { }; const emitCliToolResult = (event: CliToolResult) => { observedCliActivity = true; + recordToolResult(event); params.toolTracking.handleCliToolResult(event); if (emitLiveEvents) { emitAgentEvent({ @@ -92,7 +135,7 @@ export function createCliEventHandlers(params: { ...(runParams.sessionKey ? { sessionKey: runParams.sessionKey } : {}), ...(runParams.agentId ? { agentId: runParams.agentId } : {}), toolName: event.name, - toolSource: event.name.startsWith("mcp__") ? "mcp" : "core", + toolSource: resolveCliToolSource(event.name, event.kind), toolOwner: "cli-runner", toolCallId: event.toolCallId, }); @@ -136,7 +179,7 @@ export function createCliEventHandlers(params: { ...(runParams.sessionKey ? { sessionKey: runParams.sessionKey } : {}), ...(runParams.agentId ? { agentId: runParams.agentId } : {}), toolName, - toolSource: toolName.startsWith("mcp__") ? ("mcp" as const) : ("core" as const), + toolSource: resolveCliToolSource(toolName, activeTool?.kind), toolOwner: "cli-runner", toolCallId: event.toolCallId, durationMs: Math.max(0, now - (activeTool?.startedAt ?? now)), @@ -294,6 +337,7 @@ export function createCliEventHandlers(params: { emitCliPlanUpdate, hasObservedCliActivity: () => observedCliActivity, activeParsedToolCount: () => activeParsedTools.size, + getToolSummary, }; } diff --git a/src/agents/cli-runner/execute.ts b/src/agents/cli-runner/execute.ts index 6dc9e97e225c..79c01b2c4c65 100644 --- a/src/agents/cli-runner/execute.ts +++ b/src/agents/cli-runner/execute.ts @@ -515,7 +515,10 @@ export async function executePreparedCliRun( if (!runOutput) { throw new Error("CLI run completed without output"); } - return toolTracking.withExecutionEvidence(runOutput); + return toolTracking.withExecutionEvidence({ + ...runOutput, + toolSummary: events.getToolSummary(), + }); }; try { completedOutput = await enqueueCliRun(queueKey, async () => { diff --git a/test/fixtures/cli/codex-tool-summary-collab-close-agent.jsonl b/test/fixtures/cli/codex-tool-summary-collab-close-agent.jsonl new file mode 100644 index 000000000000..aad65c89756c --- /dev/null +++ b/test/fixtures/cli/codex-tool-summary-collab-close-agent.jsonl @@ -0,0 +1,6 @@ +{"type":"thread.started","thread_id":"thread-collab-close"} +{"type":"turn.started"} +{"type":"item.started","item":{"id":"collab-close","type":"collab_tool_call","tool":"close_agent","sender_thread_id":"thread-parent","receiver_thread_ids":["thread-child"],"prompt":null,"agents_states":{"thread-child":{"status":"running","message":null}},"status":"in_progress"}} +{"type":"item.completed","item":{"id":"collab-close","type":"collab_tool_call","tool":"close_agent","sender_thread_id":"thread-parent","receiver_thread_ids":["thread-child"],"prompt":null,"agents_states":{"thread-child":{"status":"shutdown","message":null}},"status":"completed"}} +{"type":"item.completed","item":{"id":"message-collab-close","type":"agent_message","text":"collab closed"}} +{"type":"turn.completed","usage":{"input_tokens":4,"cached_input_tokens":0,"cache_write_input_tokens":0,"output_tokens":2,"reasoning_output_tokens":0}} diff --git a/test/fixtures/cli/codex-tool-summary-collab-failed.jsonl b/test/fixtures/cli/codex-tool-summary-collab-failed.jsonl new file mode 100644 index 000000000000..a78eae339b23 --- /dev/null +++ b/test/fixtures/cli/codex-tool-summary-collab-failed.jsonl @@ -0,0 +1,5 @@ +{"type":"thread.started","thread_id":"thread-collab-failed"} +{"type":"turn.started"} +{"type":"item.completed","item":{"id":"collab-failed","type":"collab_tool_call","tool":"send_input","sender_thread_id":"thread-parent","receiver_thread_ids":["missing-thread"],"prompt":"continue","agents_states":{"missing-thread":{"status":"not_found","message":"agent not found"}},"status":"failed"}} +{"type":"item.completed","item":{"id":"message-collab-failed","type":"agent_message","text":"collab failed"}} +{"type":"turn.completed","usage":{"input_tokens":4,"cached_input_tokens":0,"cache_write_input_tokens":0,"output_tokens":2,"reasoning_output_tokens":0}} diff --git a/test/fixtures/cli/codex-tool-summary-collab-paired.jsonl b/test/fixtures/cli/codex-tool-summary-collab-paired.jsonl new file mode 100644 index 000000000000..5829ff4b672a --- /dev/null +++ b/test/fixtures/cli/codex-tool-summary-collab-paired.jsonl @@ -0,0 +1,6 @@ +{"type":"thread.started","thread_id":"thread-collab-paired"} +{"type":"turn.started"} +{"type":"item.started","item":{"id":"collab-paired","type":"collab_tool_call","tool":"spawn_agent","sender_thread_id":"thread-parent","receiver_thread_ids":[],"prompt":"draft a plan","agents_states":{},"status":"in_progress"}} +{"type":"item.completed","item":{"id":"collab-paired","type":"collab_tool_call","tool":"spawn_agent","sender_thread_id":"thread-parent","receiver_thread_ids":["thread-child"],"prompt":"draft a plan","agents_states":{"thread-child":{"status":"running","message":null}},"status":"completed"}} +{"type":"item.completed","item":{"id":"message-collab-paired","type":"agent_message","text":"collab paired"}} +{"type":"turn.completed","usage":{"input_tokens":5,"cached_input_tokens":0,"cache_write_input_tokens":0,"output_tokens":3,"reasoning_output_tokens":0}} diff --git a/test/fixtures/cli/codex-tool-summary-collab-terminal-only.jsonl b/test/fixtures/cli/codex-tool-summary-collab-terminal-only.jsonl new file mode 100644 index 000000000000..a4b6de5ad5f8 --- /dev/null +++ b/test/fixtures/cli/codex-tool-summary-collab-terminal-only.jsonl @@ -0,0 +1,5 @@ +{"type":"thread.started","thread_id":"thread-collab-terminal"} +{"type":"turn.started"} +{"type":"item.completed","item":{"id":"collab-terminal","type":"collab_tool_call","tool":"wait","sender_thread_id":"thread-parent","receiver_thread_ids":["thread-child"],"prompt":null,"agents_states":{"thread-child":{"status":"completed","message":"done"}},"status":"completed"}} +{"type":"item.completed","item":{"id":"message-collab-terminal","type":"agent_message","text":"collab terminal"}} +{"type":"turn.completed","usage":{"input_tokens":4,"cached_input_tokens":0,"cache_write_input_tokens":0,"output_tokens":2,"reasoning_output_tokens":0}} diff --git a/test/fixtures/cli/codex-tool-summary-declined-command.jsonl b/test/fixtures/cli/codex-tool-summary-declined-command.jsonl new file mode 100644 index 000000000000..d3e94f71aa65 --- /dev/null +++ b/test/fixtures/cli/codex-tool-summary-declined-command.jsonl @@ -0,0 +1,6 @@ +{"type":"thread.started","thread_id":"thread-declined"} +{"type":"turn.started"} +{"type":"item.started","item":{"id":"command-declined","type":"command_execution","command":"rm protected.txt","aggregated_output":"","status":"in_progress"}} +{"type":"item.completed","item":{"id":"command-declined","type":"command_execution","command":"rm protected.txt","aggregated_output":"","status":"declined"}} +{"type":"item.completed","item":{"id":"message-declined","type":"agent_message","text":"command declined"}} +{"type":"turn.completed","usage":{"input_tokens":4,"cached_input_tokens":0,"cache_write_input_tokens":0,"output_tokens":2,"reasoning_output_tokens":0}} diff --git a/test/fixtures/cli/codex-tool-summary-failed-mcp.jsonl b/test/fixtures/cli/codex-tool-summary-failed-mcp.jsonl new file mode 100644 index 000000000000..6a8017111c67 --- /dev/null +++ b/test/fixtures/cli/codex-tool-summary-failed-mcp.jsonl @@ -0,0 +1,5 @@ +{"type":"thread.started","thread_id":"thread-failed"} +{"type":"turn.started"} +{"type":"item.completed","item":{"id":"mcp-failed","type":"mcp_tool_call","server":"docs","tool":"read","arguments":{"path":"missing"},"error":{"message":"not found"},"status":"failed"}} +{"type":"item.completed","item":{"id":"message-failed","type":"agent_message","text":"handled failure"}} +{"type":"turn.completed","usage":{"input_tokens":6,"cached_input_tokens":0,"cache_write_input_tokens":0,"output_tokens":3,"reasoning_output_tokens":0}} diff --git a/test/fixtures/cli/codex-tool-summary-paired-mcp.jsonl b/test/fixtures/cli/codex-tool-summary-paired-mcp.jsonl new file mode 100644 index 000000000000..c24e42aaebe9 --- /dev/null +++ b/test/fixtures/cli/codex-tool-summary-paired-mcp.jsonl @@ -0,0 +1,6 @@ +{"type":"thread.started","thread_id":"thread-paired"} +{"type":"turn.started"} +{"type":"item.started","item":{"id":"mcp-paired","type":"mcp_tool_call","server":"github","tool":"search","arguments":{"query":"openclaw"},"status":"in_progress"}} +{"type":"item.completed","item":{"id":"mcp-paired","type":"mcp_tool_call","server":"github","tool":"search","arguments":{"query":"openclaw"},"result":{"content":[],"structured_content":{}},"status":"completed"}} +{"type":"item.completed","item":{"id":"message-paired","type":"agent_message","text":"paired mcp"}} +{"type":"turn.completed","usage":{"input_tokens":5,"cached_input_tokens":1,"cache_write_input_tokens":0,"output_tokens":3,"reasoning_output_tokens":0}} diff --git a/test/fixtures/cli/codex-tool-summary-terminal-only.jsonl b/test/fixtures/cli/codex-tool-summary-terminal-only.jsonl new file mode 100644 index 000000000000..656d7c807b2c --- /dev/null +++ b/test/fixtures/cli/codex-tool-summary-terminal-only.jsonl @@ -0,0 +1,8 @@ +{"type":"thread.started","thread_id":"thread-terminal-only"} +{"type":"turn.started"} +{"type":"item.completed","item":{"id":"mcp-terminal","type":"mcp_tool_call","server":"","tool":"lookup","arguments":{"id":"42"},"result":{"content":[],"structured_content":{}},"status":"completed"}} +{"type":"item.completed","item":{"id":"command-terminal","type":"command_execution","command":"pwd","aggregated_output":"/workspace\n","exit_code":0,"status":"completed"}} +{"type":"item.completed","item":{"id":"patch-terminal","type":"file_change","changes":[{"path":"README.md","kind":"update"}],"status":"completed"}} +{"type":"item.completed","item":{"id":"search-terminal","type":"web_search","query":"OpenClaw"}} +{"type":"item.completed","item":{"id":"message-terminal","type":"agent_message","text":"terminal only"}} +{"type":"turn.completed","usage":{"input_tokens":8,"cached_input_tokens":0,"cache_write_input_tokens":0,"output_tokens":4,"reasoning_output_tokens":1}} diff --git a/test/fixtures/cli/codex-tool-summary-zero.jsonl b/test/fixtures/cli/codex-tool-summary-zero.jsonl new file mode 100644 index 000000000000..11c1425e3fc3 --- /dev/null +++ b/test/fixtures/cli/codex-tool-summary-zero.jsonl @@ -0,0 +1,4 @@ +{"type":"thread.started","thread_id":"thread-zero"} +{"type":"turn.started"} +{"type":"item.completed","item":{"id":"message-zero","type":"agent_message","text":"zero tools"}} +{"type":"turn.completed","usage":{"input_tokens":3,"cached_input_tokens":0,"cache_write_input_tokens":0,"output_tokens":2,"reasoning_output_tokens":0}}