Skip to content

Preserve Codex reasoning usage in Claude streaming and non-streaming responses #5587

Description

@Kooooojun

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions