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 ba1e8cde2..1641398ae 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 d356a6a70..4a84ef749 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 e894b8f4e..3a7b5d0fc 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 f9ccbe40d..76a67d494 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 b8adcfb4b..3af78c9b5 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 7af8126e7..27fd1120e 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 3eb0552d3..c99c1a689 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, }, }, ]);