Skip to content

fix(gateway): meter OpenAI usage from the shapes the wire actually sends - #262

Open
ophiocus wants to merge 2 commits into
theam:mainfrom
ophiocus:fix/gateway-openai-usage-shapes
Open

fix(gateway): meter OpenAI usage from the shapes the wire actually sends#262
ophiocus wants to merge 2 commits into
theam:mainfrom
ophiocus:fix/gateway-openai-usage-shapes

Conversation

@ophiocus

@ophiocus ophiocus commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Closes #232 — and full credit to @Julian-Genuario, whose diagnosis was exact down to the sentence "the tests encode the wrong shapes." I claimed this in the issue thread with right of way offered; the courtesy window has passed, so here is the fix, built the way #181 was.

Stubs first, so the tests bite (the #181 pattern)

Both OpenAI stubs now emit what the wire actually sends — Chat Completions speaks prompt_tokens/completion_tokens (with prompt_tokens_details.cached_tokens on the final include_usage chunk), and the /responses stub streams the real envelope: usage nested inside {"type":"response.completed","response":{...}}, never at the frame's top level. With the corrected stubs alone, tests 3 and 3b fail against the old parser exactly as the issue predicted:

AssertionError: expected +0 to be 600000   // Chat streaming metered to zero
AssertionError: expected +0 to be 750000   // Codex/Responses streaming metered to zero

Then the parser

  • usageFromJson accepts both naming schemes; the stream path also reads the response.completed envelope (the wire Codex speaks) alongside top-level usage.
  • The double-charge is fixed at its root: OpenAI reports input INCLUSIVE of cached tokens while costCents sums buckets additively in the Anthropic convention — so cached tokens are subtracted from input, or they get billed at both the input and cache-read rates.

Pinned money math

The assertions now check the full buckets, not just presence: 1M prompt with 400k cached + 1M completion on gpt-5.5-mini = 216¢ (was silently 225 with cached double-billed and names lucky-matched); the streamed Codex path (1M/250k cached/200k out on gpt-5.6-sol) = 987.5¢ — previously zero, which is the hard-budget bypass in the issue title.

Gateway suite: 57 tests, 0 failures, tsc clean. Adjacent, not overlapping: #248/#251/#255 handle incomplete usage reconciliation; this handles usage that arrives complete but was unparseable.

ophiocus and others added 2 commits August 31, 2026 13:19
The gateway parsed OpenAI usage with Responses-only, top-level names, so
Chat Completions (prompt_tokens/completion_tokens) and streamed Responses
(usage nested in the response.completed envelope - the wire Codex speaks)
both metered to zero: hard budgets never accumulated for Codex runs. The
stubs encoded the same wrong shapes, so the assertions could not catch it
(theam#232). Both stubs now emit the wire truth - with those stubs alone, tests
3 and 3b fail against the old parser at "expected +0 to be 600000/750000".
usageFromJson accepts both naming schemes, the stream path also reads the
response envelope, and cached tokens are subtracted from input: OpenAI
reports input INCLUSIVE of cached while costCents sums buckets additively
in the Anthropic convention, so the old accounting billed cached tokens
at both the input and cache-read rates. Metering assertions now pin the
full bucket math (600k/400k cached at 216 cents; 750k/250k/200k at 987.5).

Closes theam#232

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Gateway meters OpenAI traffic wrong: streamed Responses and Chat usage parse to zero, and cached tokens are charged on top of input

1 participant