From ddfa6917520494a29934a89287ee105d516a870b Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Fri, 24 Apr 2026 01:54:11 -0700 Subject: [PATCH] Surface reasoning tokens in exec JSON usage (#19308) ## Summary Fixes #19022. `codex exec --json` currently emits `turn.completed.usage` with input, cached input, and output token counts, but drops the reasoning-token split that Codex already receives through thread token usage updates. Programmatic consumers that rely on the JSON stream, especially ephemeral runs that do not write rollout files, need this field to accurately display reasoning-model usage. This PR adds `reasoning_output_tokens` to the public exec JSON `Usage` payload and maps it from the existing `ThreadTokenUsageUpdated` total token usage data. ## Verification - Added coverage to `event_processor_with_json_output::token_usage_update_is_emitted_on_turn_completion` so `turn.completed.usage.reasoning_output_tokens` is asserted. - Updated SDK expectations for `run()` and `runStreamed()` so TypeScript consumers see the new usage field. - Ran `cargo test -p codex-exec`. - Ran `pnpm --filter ./sdk/typescript run build`. - Ran `pnpm --filter ./sdk/typescript run lint`. - Ran `pnpm --filter ./sdk/typescript exec jest --runInBand --testTimeout=30000`. --- codex-rs/exec/src/event_processor_with_jsonl_output.rs | 1 + codex-rs/exec/src/exec_events.rs | 2 ++ codex-rs/exec/tests/event_processor_with_json_output.rs | 1 + sdk/typescript/samples/basic_streaming.ts | 2 +- sdk/typescript/src/events.ts | 2 ++ sdk/typescript/tests/run.test.ts | 1 + sdk/typescript/tests/runStreamed.test.ts | 1 + 7 files changed, 9 insertions(+), 1 deletion(-) diff --git a/codex-rs/exec/src/event_processor_with_jsonl_output.rs b/codex-rs/exec/src/event_processor_with_jsonl_output.rs index ba1e8cde25..1641398ae6 100644 --- a/codex-rs/exec/src/event_processor_with_jsonl_output.rs +++ b/codex-rs/exec/src/event_processor_with_jsonl_output.rs @@ -122,6 +122,7 @@ impl EventProcessorWithJsonOutput { input_tokens: usage.total.input_tokens, cached_input_tokens: usage.total.cached_input_tokens, output_tokens: usage.total.output_tokens, + reasoning_output_tokens: usage.total.reasoning_output_tokens, } } diff --git a/codex-rs/exec/src/exec_events.rs b/codex-rs/exec/src/exec_events.rs index d356a6a70b..4a84ef7494 100644 --- a/codex-rs/exec/src/exec_events.rs +++ b/codex-rs/exec/src/exec_events.rs @@ -65,6 +65,8 @@ pub struct Usage { pub cached_input_tokens: i64, /// The number of output tokens used during the turn. pub output_tokens: i64, + /// The number of reasoning output tokens used during the turn. + pub reasoning_output_tokens: i64, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, TS)] diff --git a/codex-rs/exec/tests/event_processor_with_json_output.rs b/codex-rs/exec/tests/event_processor_with_json_output.rs index e894b8f4eb..3a7b5d0fcc 100644 --- a/codex-rs/exec/tests/event_processor_with_json_output.rs +++ b/codex-rs/exec/tests/event_processor_with_json_output.rs @@ -1232,6 +1232,7 @@ fn token_usage_update_is_emitted_on_turn_completion() { input_tokens: 10, cached_input_tokens: 3, output_tokens: 29, + reasoning_output_tokens: 7, }, })], status: CodexStatus::InitiateShutdown, diff --git a/sdk/typescript/samples/basic_streaming.ts b/sdk/typescript/samples/basic_streaming.ts index f9ccbe40d1..76a67d494b 100755 --- a/sdk/typescript/samples/basic_streaming.ts +++ b/sdk/typescript/samples/basic_streaming.ts @@ -56,7 +56,7 @@ const handleEvent = (event: ThreadEvent): void => { break; case "turn.completed": console.log( - `Used ${event.usage.input_tokens} input tokens, ${event.usage.cached_input_tokens} cached input tokens, ${event.usage.output_tokens} output tokens.`, + `Used ${event.usage.input_tokens} input tokens, ${event.usage.cached_input_tokens} cached input tokens, ${event.usage.output_tokens} output tokens, ${event.usage.reasoning_output_tokens} reasoning output tokens.`, ); break; case "turn.failed": diff --git a/sdk/typescript/src/events.ts b/sdk/typescript/src/events.ts index b8adcfb4b0..3af78c9b56 100644 --- a/sdk/typescript/src/events.ts +++ b/sdk/typescript/src/events.ts @@ -25,6 +25,8 @@ export type Usage = { cached_input_tokens: number; /** The number of output tokens used during the turn. */ output_tokens: number; + /** The number of reasoning output tokens used during the turn. */ + reasoning_output_tokens: number; }; /** Emitted when a turn is completed. Typically right after the assistant's response. */ diff --git a/sdk/typescript/tests/run.test.ts b/sdk/typescript/tests/run.test.ts index 7af8126e7d..27fd1120e4 100644 --- a/sdk/typescript/tests/run.test.ts +++ b/sdk/typescript/tests/run.test.ts @@ -40,6 +40,7 @@ describe("Codex", () => { cached_input_tokens: 12, input_tokens: 42, output_tokens: 5, + reasoning_output_tokens: 0, }); expect(thread.id).toEqual(expect.any(String)); } finally { diff --git a/sdk/typescript/tests/runStreamed.test.ts b/sdk/typescript/tests/runStreamed.test.ts index 3eb0552d38..c99c1a689e 100644 --- a/sdk/typescript/tests/runStreamed.test.ts +++ b/sdk/typescript/tests/runStreamed.test.ts @@ -50,6 +50,7 @@ describe("Codex", () => { cached_input_tokens: 12, input_tokens: 42, output_tokens: 5, + reasoning_output_tokens: 0, }, }, ]);