stack 2/5: price long-context requests at the published long rate (#908) - #952
stack 2/5: price long-context requests at the published long rate (#908)#952lidge-jun wants to merge 1 commit into
Conversation
) Several vendors reprice the entire request once the prompt crosses a token threshold, and a flat Cost4 could not express it — so every request billed at the short rate, including the long ones, which are the expensive ones. The threshold reads raw usage.inputTokens, not normalized billable input: a 280k prompt with a 200k cache read has 80k billable input and still crosses OpenAI's 272k boundary. Deciding after normalization would have under-billed exactly the cache-heavy long requests. Long context and Fast are mutually exclusive, not composable. OpenAI does not serve long context in Fast mode, so exclusivity keys on the response-confirmed tier: a >272k request merely tagged priority was necessarily downgraded and bills long. That needed tier provenance at all four estimator call sites instead of the collapsed scalar. Also adds base prices for the three -pro virtual aliases, which resolved to null and rendered no cost estimate at all. Fixes #908
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (2)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
Stack navigation
Review and merge bottom-up. Each PR targets the preceding stack branch, so its Files changed view contains only that layer. The layers touch disjoint files — Carried in #953, with authorship preserved: #939, #942, #943, #944, #945, #948. |
There was a problem hiding this comment.
💡 Codex Review
Line 334 in c72dc99
In a mixed-provider combo, the request-level tier context is passed to every attempt even though attempts carry no service-tier provenance. If the final OpenAI response confirms priority, this unconditional return also suppresses long-context pricing for earlier xAI or MiniMax attempts; for example, a preceding 600K MiniMax-M3 attempt is charged at the short rate despite OpenAI Fast restrictions being irrelevant to it. Gate suppression on the current provider/model being eligible for OpenAI Fast, or persist and use service-tier provenance per attempt.
Lines 381 to 382 in c72dc99
The usage passed here can represent multiple upstream requests rather than one pricing decision: runWithImageBridge() explicitly sums every hidden and final iteration into one OcxUsage, then stores that aggregate on the active attempt. Applying the threshold to that sum overcharges media-loop requests when, for example, two xAI calls each use fewer than 200K input tokens but together exceed 200K; neither individual call qualifies for the long rate, yet the estimator doubles both. Preserve per-send usage and apply the context tier to each upstream call before summing its cost.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const OPENAI_GPT56_CONTEXT_MODELS = [ | ||
| "gpt-5.6-sol", | ||
| "gpt-5.6-terra", | ||
| "gpt-5.6-luna", |
There was a problem hiding this comment.
Include the generic GPT-5.6 alias in context tiers
The OpenAI API catalog exposes gpt-5.6, and the repository's API contract records that this upstream alias routes to Sol, but this exact-match list begins at gpt-5.6-sol. Consequently, findContextTier() returns no rule for API-key requests selecting the generic alias, so prompts above 272K still use the short rate—for example, 300K input plus 20K output remains $2.10 instead of $3.90. Include gpt-5.6, preferably by deriving this list from the canonical provider registry rather than maintaining another independent model list.
AGENTS.md reference: src/AGENTS.md:L18-L18
Useful? React with 👍 / 👎.
| export const CONTEXT_TIERS: readonly ContextTier[] = [ | ||
| ...["openai", "openai-apikey"].flatMap(provider => | ||
| OPENAI_GPT56_CONTEXT_MODELS.map((modelId): ContextTier => ({ |
There was a problem hiding this comment.
Document the user-visible long-context pricing rules
This registry changes the estimated amounts displayed by the Logs and Usage surfaces whenever OpenAI, xAI, or MiniMax prompts cross the new thresholds, including the non-obvious Fast-mode exclusivity rule, but the commit contains no docs-site/ update explaining those calculations. Add the thresholds and estimation behavior to the English documentation and keep the translated versions consistent so users can interpret the changed dashboard totals.
AGENTS.md reference: AGENTS.md:L212-L213
Useful? React with 👍 / 👎.
| { provider: "openai-apikey", modelId: "gpt-5.6-sol-pro", cost4: GPT56_SOL, source: `collapsed base ID ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-03", status: "verified-derived" }, | ||
| { provider: "openai-apikey", modelId: "gpt-5.6-terra-pro", cost4: GPT56_TERRA, source: `collapsed base ID ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-03", status: "verified-derived" }, | ||
| { provider: "openai-apikey", modelId: "gpt-5.6-luna-pro", cost4: GPT56_LUNA, source: `collapsed base ID ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-03", status: "verified-derived" }, |
There was a problem hiding this comment.
Apply Fast multipliers to newly priced Pro aliases
These rows make the *-pro selections priceable while their selected suffix remains in the usage log, but applyPriorityMultiplier() consequently looks up names such as gpt-5.6-sol-pro in PRIORITY_MULTIPLIERS, which only contains the base slugs. The API-key provider still sends service_tier=priority after rewriting the virtual selection to its base wire model, so a response-confirmed Fast Sol Pro request with 200K input and 20K output is reported as $1.60 instead of the $3.20 Fast rate. Resolve the multiplier through the virtual model's base ID, or derive corresponding alias entries and cover them with a Fast regression test.
AGENTS.md reference: src/AGENTS.md:L18-L18
Useful? React with 👍 / 👎.
Stack navigation
Review and merge bottom-up. Each PR targets the preceding stack branch, so its Files changed view contains only that layer. The layers touch disjoint files — #954 needs human security review per Carried in #953, with authorship preserved: #939, #942, #943, #944, #945, #948. |
Stack
2/3 — long-context pricing tiers
Base:
codex/bug-stack-plan(#951)Next: carried contributor bug fixes (#952)
Summary
Several vendors reprice the entire request once the prompt crosses a token threshold.
Cost4is flat andresolveMatchedPrice()never saw a token count, so there was nowhere to express "this rate depends on how big the prompt is" — every request billed at the short rate, including the long ones, which are the expensive ones.ContextTierregistry with exact provider+model rules, each carrying its source URL andverifiedAtcalculateCost();resolveMatchedPrice()stays token-independent so its provider/model memoization is untouchedcontextTiersurfaced onAttemptCostEstimateandCostEstimate, propagated to combo results-provirtual aliasesgpt-5.6-sol/-terra/-luna(+-pro)>grok-4.5>=MiniMax-M3>Verified 2026-08-03 against the published tables — OpenAI's
>272Kis exclusive, xAI's≥ 200kis inclusive.Three things worth reviewing closely
The threshold reads raw
usage.inputTokens, not normalized input.normalizeCostTokens()subtracts cache read/write to produce billable input, so a 280k prompt with a 200k cache read has 80k billable input and still crosses OpenAI's boundary. Deciding after normalization would have silently under-billed exactly the cache-heavy long requests. Covered by L3.Long context and Fast are mutually exclusive, not composable. An earlier draft multiplied both. OpenAI's Fast guide states plainly that "Long context, fine-tuned models, and embeddings are not supported", so that product cannot exist. Exclusivity keys on the response-confirmed tier: a >272k request merely tagged
prioritywas necessarily downgraded and must still bill long — suppressing the tier there would under-bill the downgraded request. That required passing tier provenance rather than the collapsedeffectiveServiceTier()scalar to all four estimator call sites. Covered by L8.MiniMax casing is exact on purpose. The bundle carries both
minimax-m3(0.6/2.4/0.12/0) andMiniMax-M3(0.3/1.2/0.06/0); case-folding would select the wrong base row. Covered by L5.A separate bug this surfaced
gpt-5.6-sol-pro,-terra-pro,-luna-prohad no base price at all — the virtual resolver keeps the selected id in the usage log while cost resolution deliberately does not fall back throughresolvedModel. A probe against the real resolver returnednullfor all three, meaning-prousage rendered no cost estimate whatsoever, and a context-tier row alone could never have been reached. Base rows added; L10 covers both halves.Not fixed here
Terra and Luna still carry pre-price-cut base rates — that is #907, and it cannot be fixed in this repository (canonical
models.jsonlives inlidge-jun/jawcode). Note for whoever lands it:PRIORITY_MULTIPLIERSstores Fast pricing as ratios calibrated against the stale bases, so correcting them without recomputing those ratios would fix an overcharge and introduce an undercharge.Verification
bun x tsc --noEmit— exit 0bun test tests/usage-cost.test.ts— 51 pass, 0 failapplyContextTier()fails 8 of the new testsbun run test— 7691 pass, 8 skip, 0 fail, 507 filesbun run privacy:scan— passedThe existing Fast fixture used a 1M-token prompt that now crosses the threshold; it moved to 200k so those cases still isolate the Fast multiplier, with totals recomputed.
Fixes #908