Conversation
42aed13 to
dd66fed
Compare
Codex reports how much of a ChatGPT subscription is spent twice: as `x-codex-*` headers on an HTTP response, and as a `codex.rate_limits` event on the WebSocket transport. The proxy answers in Anthropic's format, which has no equivalent field, so neither reaches the client and a tool in front of the proxy — a rotating multi-account gateway, a quota readout — cannot see the limit it is about to hit. Keep the newest snapshot and stamp it onto every Codex response under the header names Codex itself uses, so a client that already reads them needs no second format. The event precedes the first generated output on both transports and the response head is built from that output, so a response normally carries the numbers of the request it answers. Only quota headers are forwarded: `x-codex-turn-state` is continuation state, and a large opaque blob to put on every response.
dd66fed to
d340f74
Compare
|
Rebased onto current Flagging an overlap so it isn't discovered at merge time: #149 also surfaces the Codex rate-limit snapshot to the client, as a typed The reasoning/thinking tokens half of #149 is orthogonal to this and useful on its own; nothing here competes with it. On the rate-limit half, the one property headers have that the usage field doesn't: they arrive before the body, so a caller can read the meter without parsing the stream to completion. That matters if you're pooling subscriptions and want to route on quota before committing a turn, or if you're forwarding responses without buffering them. The usage field is the better fit if the consumer is already reading usage anyway and wants it in one place. They can also coexist — the header path doesn't preclude the field. Happy to defer if you'd rather have one mechanism: I can close this, or rework it to read from whatever #149 lands as. Just say which you prefer rather than carrying both by accident. |
parse_codex_usage already reads output_tokens_details.reasoning_tokens
into CodexUsage, but map_codex_usage_to_anthropic dropped it, so Claude
Code recorded thinking_tokens as 0 for every Codex turn. Emit it as
output_tokens_details.{reasoning_tokens, thinking_tokens}; the second
name is what Claude Code records for Anthropic models, so per-turn
accounting in transcripts stays comparable across providers.
Ported from the reasoning-tokens half of upstream PR raine#149
(raine/claude-code-proxy, author sommaht, branch usage-visibility).
The other half of that PR, the codex_rate_limits usage field, is left
out on purpose: this fork already surfaces the same data as x-codex-*
response headers via rate_limits.rs (upstream PR raine#127), and two sources
of truth for the same meter would fight at the next rebase. Drop this
commit at whichever rebase sees raine#149 land upstream.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Problem
Codex reports how much of a ChatGPT subscription is spent in two places:
x-codex-*headers on an HTTP response, and acodex.rate_limitsevent on the WebSocket transport. Neither reaches the client. The proxy answers in Anthropic's format, which has no equivalent field, so the event is classified as control and becomes aping, and the response headers are dropped.That leaves anything sitting in front of the proxy — a rotating multi-account gateway, a quota readout — unable to see the limit it is about to hit. The first sign of a spent window is a 429.
Change
A new
providers/codex/rate_limits.rskeeps the newest snapshot and stamps it onto every Codex/v1/messagesresponse, under the header names Codex itself uses, so a client that already parses them needs no second format. Both transports feed it: the HTTP response head, and thecodex.rate_limitsevent.Only quota headers are forwarded.
x-codex-turn-stateis continuation state, and a large opaque blob to put on every response.Codex sends the event ahead of the first generated output and the response head is built from that output, so a response normally carries the numbers of the request it answers. One that arrives before any telemetry does carries the previous snapshot, or none.
Which window is which is left to the client, because it varies: a ChatGPT Pro subscription reports its weekly limit as
primaryand meters no secondary window at all, so the-window-minutesheader is what identifies a bucket, not the position.Testing
cargo test— 1033 pass.cargo clippy --all-targetsandcargo fmt --checkclean.The HTTP API reference gains a paragraph describing the headers.
🤖 Generated with Claude Code
https://claude.ai/code/session_01S3HCmE8ZPT1o59UCGaoEeV