Port upstream 0.60.4: price OpenCodex rows by billing route, unpriced instead of zero (stacked on #682) - #731
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…route, unpriced instead of zero (stacked on #682)
…r rates, Daybreak aliases (stacked on #731)
|
Adversarial validation passed at faa48b521d05e88f37d08c40a4e8e5e0f72c8e2b Scope: price OpenCodex rows by billing route, unpriced-not-zero for missing tokens or prices (#731, upstream 0.60.4 steipete#3676), validated as merged into release/v0.70.0 (merge faa48b5). Attacks (highest-risk semantics, from the merged tree):
No defects found. READY for the un-draft rule. |
Stacked on #682 (
port/micro-0.68.0-nous-opencodex-ledger). Review and merge #682 first.Summary
With OpenCodex import on, each OpenCodex ledger row is now priced by its recorded billing route, as
in upstream 0.60.4. A row the price sources cannot cover stays unpriced: its tokens are kept, it adds
no dollars, and it counts in the unpriced coverage instead of as $0. This applies to Usage & Spend,
get_spend_contractandcodexbar cost --format json.Which catalog prices a row
provideris the billing provider. A legacy OpenAI-transport row(
provider: "openai") uses its model prefix instead when the prefix is a known route:deepseek,kimi-coding,kimi-for-coding,openai,opencode,opencode-freeoropencode-go.core::models_dev_pricing_targets(upstreamModelsDevPricingTargetResolver) turns thatinto models.dev identities:
x-aitoxaiand strips one model prefix that repeats the provider.kimi-codingalso checkskimi-for-coding, andopencode-freealso checksopencode.opencode-go+openai/gpt-5is lookedup as
openai/gpt-5in theopencode-gocatalog and never borrows OpenAI's rates.historical rates, then the catalog. Every other identity needs an exact models.dev entry and bills
each token lane on its own. If a row used a cache lane that the catalog has no rate for, the row
stays unpriced instead of borrowing the input rate.
rates now include upstream's cache-write rates (3.125e-6 and 1.25e-6 per token; 6.25e-6 and
2.5e-6 above 272K).
Custom pricing (
custom-pricing.json)Overrides resolve in upstream order:
Within each step, the bare model key wins over
provider/model, as in upstream. This replaces thekey-order divergence noted in Port upstream 0.68.0: attribute Nous OpenCodex ledger rows to Nous Portal spend #682.
The first matching override wins whole. A rate it lacks leaves the row unpriced; it's never filled
from a later override or from the catalog. A model priced by an override keeps its custom-pricing
flag even when its cost is unknown.
Refreshing prices
refreshPricingIfNeeded) intwo cases: it's stale (older than 24 h), or a priceable imported row's exact identity is missing
from it.
implausible fetch keeps the previous cache.
get_spend_contractwith OpenCodex import,codexbar cost --format jsonwhen OpenCodex import is on.Upstream reference
cached rates, honor custom-price overrides, and leave missing token counts or required prices
unpriced instead of zero (fix(opencodex): price usage by its recorded provider steipete/CodexBar#3676). The audit tracks it as Gap 2; Port OpenCodex recorded-provider pricing from upstream 0.60.4 #516 ported the routing part.
v0.60.4, underSources/CodexBarCore/Vendored/unless noted:OpenCodexUsage/:OpenCodexUsagePricing.swift(providerID(for:),targets(for:),OpenCodexUsageStore.refreshPricingIfNeeded),OpenCodexUsageAggregator.swift(
listPriceUSD).CostUsage/:ModelsDevPricingTargetResolver.swift,ModelsDevPricing.swift(
refreshForUnknownModelsIfNeeded(exactModelIDs:)),CostUsagePricing+Provider.swift(
providerCostUSD),CostUsageCustomPricing.swift,CostUsagePricing+Overlay.swift,CostUsagePricing.swift(codexHistoricalPricing).Sources/CodexBar/SpendDashboardSource+OpenCodex.swiftandSources/CodexBarCLI/CLICostCommand.swift(loadOpenCodexCostPayload): refresh before themerge and before the JSON payload.
OpenCodexProviderPricingTests.swift(all 18 cases ported),ModelsDevPricingTargetResolverTests.swift(all 6 cases ported) andSpendDashboardOpenCodexPricingRefreshTests.swift.Ported / Deferred
Ported:
rust/src/core/models_dev_targets.rs(new): the target resolver.rust/src/core/models_dev_pricing.rs:pricing_snapshot_at.refresh_exact_pricing_targets_if_needed: a stale-catalog refresh first, then an exactunknown-model refresh. It shares the existing 15-minute attempt window and in-process
coordinator.
rust/src/spend_contract/opencodex.rs:RowPricingresolves the override and the targets onceper row, and that result feeds both the cost and the custom-pricing flag.
nous.rskeeps onlythe Nous subscription id, because Nous rows now go through the shared resolver.
rust/src/spend_contract.rs:CustomPricing::overlay_rates(bare key first) andCustomRates::lane_cost(independentlanes; a lane with tokens but no rate leaves the cost unknown).
CustomPricing::parsereads each entry on its own, like upstreamCostUsageCustomPricing.parse.A negative, non-finite or non-numeric rate is unknown, and the camelCase key wins over the
snake_case one. An entry with no usable rate (
{}included) is dropped, so it never blocks thecatalog. One bad entry no longer empties the whole file. Native local spend reads the same file,
so this applies there too.
rust/src/core/cost_pricing.rs: the cache-write-aware historical Codex cost, andhas_bundled_codex_pricing.rust/src/cli/cost.rs, andcommands/spend_contract.rsandcommands/usage_spend.rsin the desktop shell.Differs from upstream on Windows:
custom-pricing.json, which plays the role ofupstream's application overlay. Upstream's caller-supplied
customPricingargument has no Windowsequivalent. Overlay rows subtract cache reads and writes from input before billing, as upstream's
overlay does. Tests that expect caller-pricing values in upstream (
0.00142,0.000488,0.00157)assert the overlay values instead (
0.00122, routed0.000306,0.00107).Port upstream 0.68.0: attribute Nous OpenCodex ledger rows to Nous Portal spend #682's fixture values.
bundled rates and unattributed Codex models, because they never read the catalog.
refresh.
behavior predates this PR.
usageStatus(reportedis vendor-metered),as introduced by b0f0e60. Upstream labels every OpenCodex snapshot as a list-price estimate. This
PR doesn't change that.
Deferred:
daybreak aliases). These are in the stacked follow-up
port/micro-0.70.0-codex-pricing.Validation
Head
fffc7006(the last commit only moves themodels_dev_pricingandopencodextest modules intotheir own files, so both stay under 1000 lines).
cargo +1.98.0 fmt --all --check: ok at the head.cargo +1.98.0 clippy --workspace --all-targets -- -D warnings: ok at the head.opencodex models_dev spend_contract cost_pricing): 132 passed, 0 failed at 53f39a6;models_dev opencodex spend_contractat the head: 92 passed, 0 failed.cargo +1.98.0 test -p codexbarat the head: 2210 passed, 0 failed, 1 ignored.cargo +1.98.0 test -p codexbar-desktop-tauriat 5636ca1: 462 passed, 1 failed. The failureis
bootstrap_payload_exposes_every_provider_variant, which reads the host's settings and alsofails on the base branch on this machine; Make the bootstrap catalog test hermetic (#684) #711 makes it hermetic. The later commit only moves
test modules in the
codexbarcrate.spend_contract/opencodex/pricing_tests.rs: 20 tests (the 18 upstream cases, refresh-targetscoping, and override entries without a usable rate).
spend_contract/opencodex/tests.rs:opencodex_historical_gpt56_bills_cache_writes_at_their_own_rate.spend_contract/tests.rs:custom_pricing_reads_each_entry_on_its_own_like_upstream.core/models_dev_targets.rs: 5.models_dev_pricing::exact_refresh_tests: 6 (attempt window, dated alias, no targets, stalefirst, exact miss, failure keeps the cache).
usage_spend: 1 (only a rebuild may refresh).Affected areas
the CLI cost JSON. No frontend files changed.
https://models.dev/api.json(at most once per 15 minutes percache path, 20 s timeout) before it builds. Upstream does the same.
UI proof
Not required. Backend only, with no frontend change; the audit classes Gap 2 as UI: no (Usage &
Spend numbers only). Unit tests cover the pricing behavior.