Goal
Preserve upstream usage.output_tokens_details.reasoning_tokens when translating Codex Responses to Claude /v1/messages, for both streaming and non-streaming clients.
Filed as an issue rather than a translator-only PR in accordance with AGENTS.md (external contributor with READ permission).
Evidence
Installed 7.2.147 (17a65ee); current main still extracts input/output/cache counts without reasoning details in internal/translator/codex/claude/codex_claude_response.go.
A small synthetic coding request through the Responses passthrough returned HTTP 200 and completed with these numeric usage fields:
{"input_tokens":420,"output_tokens":518,"output_tokens_details":{"reasoning_tokens":163},"total_tokens":938}
The Claude translator calls extractResponsesUsage then sets only input/output/cache usage fields. The reasoning detail cannot reach the client through that path. Do not infer zero reasoning from its absence. No credentials, prompts, response content, or account identifiers are included here.
Intended implementation
Keep output_tokens inclusive; reasoning is a subset, not an additional charge. In both terminal stream message_delta usage and non-stream message usage, forward the existing upstream detail (including explicit zero), and omit it if absent. Claude Code records the thinking_tokens spelling; the OpenAI-compatible reasoning_tokens spelling can be retained too if desired for compatibility.
Proposed shared helper used by both paths:
func setClaudeReasoningUsage(out []byte, usage gjson.Result) []byte {
detail := usage.Get("output_tokens_details.reasoning_tokens")
if !detail.Exists() || detail.Type != gjson.Number {
return out
}
tokens := detail.Int()
if tokens < 0 {
return out
}
tokens = min(tokens, max(int64(0), usage.Get("output_tokens").Int()))
updated, err := sjson.SetBytes(out, "usage.output_tokens_details.thinking_tokens", tokens)
if err != nil {
return out
}
return updated
}
Call after existing usage normalization in both response converters. This is an implementation proposal, not a compiled or deployed patch.
Acceptance tests
- Streaming and non-streaming: output 518 / reasoning 163 preserves both values.
- Explicit zero is forwarded; missing detail stays absent.
- Negative/non-numeric detail is rejected; oversized detail cannot exceed output.
- Input/cache normalization and total output remain unchanged.
Historical client logs where the detail was omitted cannot reconstruct the original count.
Goal
Preserve upstream
usage.output_tokens_details.reasoning_tokenswhen translating Codex Responses to Claude/v1/messages, for both streaming and non-streaming clients.Filed as an issue rather than a translator-only PR in accordance with AGENTS.md (external contributor with READ permission).
Evidence
Installed 7.2.147 (17a65ee); current main still extracts input/output/cache counts without reasoning details in
internal/translator/codex/claude/codex_claude_response.go.A small synthetic coding request through the Responses passthrough returned HTTP 200 and completed with these numeric usage fields:
{"input_tokens":420,"output_tokens":518,"output_tokens_details":{"reasoning_tokens":163},"total_tokens":938}The Claude translator calls
extractResponsesUsagethen sets only input/output/cache usage fields. The reasoning detail cannot reach the client through that path. Do not infer zero reasoning from its absence. No credentials, prompts, response content, or account identifiers are included here.Intended implementation
Keep output_tokens inclusive; reasoning is a subset, not an additional charge. In both terminal stream message_delta usage and non-stream message usage, forward the existing upstream detail (including explicit zero), and omit it if absent. Claude Code records the
thinking_tokensspelling; the OpenAI-compatiblereasoning_tokensspelling can be retained too if desired for compatibility.Proposed shared helper used by both paths:
Call after existing usage normalization in both response converters. This is an implementation proposal, not a compiled or deployed patch.
Acceptance tests
Historical client logs where the detail was omitted cannot reconstruct the original count.