diff --git a/rust/src/core/cost_pricing.rs b/rust/src/core/cost_pricing.rs index d8b4ccc70c..2cf6be0f0e 100755 --- a/rust/src/core/cost_pricing.rs +++ b/rust/src/core/cost_pricing.rs @@ -756,6 +756,24 @@ impl CostUsagePricing { cached_input_tokens: u64, output_tokens: u64, pricing_date: NaiveDate, + ) -> Option { + Self::codex_cost_usd_at_date_with_pricing_snapshot( + model, + input_tokens, + cached_input_tokens, + output_tokens, + pricing_date, + None, + ) + } + + pub fn codex_cost_usd_at_date_with_pricing_snapshot( + model: &str, + input_tokens: u64, + cached_input_tokens: u64, + output_tokens: u64, + pricing_date: NaiveDate, + pricing_snapshot: Option<&models_dev_pricing::ModelsDevPricingSnapshot>, ) -> Option { let key = Self::normalize_codex_model(model); let cutoff = NaiveDate::from_ymd_opt(2026, 7, 30).expect("valid pricing cutoff"); @@ -779,7 +797,13 @@ impl CostUsagePricing { )); } } - Self::codex_cost_usd(model, input_tokens, cached_input_tokens, output_tokens) + Self::codex_cost_usd_with_pricing_snapshot( + model, + input_tokens, + cached_input_tokens, + output_tokens, + pricing_snapshot, + ) } pub fn codex_fast_cost_usd_at_date( @@ -810,6 +834,22 @@ impl CostUsagePricing { input_tokens: u64, cached_input_tokens: u64, output_tokens: u64, + ) -> Option { + Self::codex_cost_usd_with_pricing_snapshot( + model, + input_tokens, + cached_input_tokens, + output_tokens, + None, + ) + } + + pub fn codex_cost_usd_with_pricing_snapshot( + model: &str, + input_tokens: u64, + cached_input_tokens: u64, + output_tokens: u64, + pricing_snapshot: Option<&models_dev_pricing::ModelsDevPricingSnapshot>, ) -> Option { let key = Self::normalize_codex_model(model); // Model-less / deliberately unattributed usage stays unpriced even if a @@ -861,7 +901,10 @@ impl CostUsagePricing { } None => ("openai", model), }; - let pricing = models_dev_pricing::lookup(provider_id, lookup_model)?; + let pricing = match pricing_snapshot { + Some(snapshot) => snapshot.lookup(provider_id, lookup_model), + None => models_dev_pricing::lookup(provider_id, lookup_model), + }?; let use_tier = pricing .threshold_tokens .is_some_and(|threshold| input_tokens > threshold); diff --git a/rust/src/core/models_dev_pricing.rs b/rust/src/core/models_dev_pricing.rs index 2bba9d05fc..672de73646 100644 --- a/rust/src/core/models_dev_pricing.rs +++ b/rust/src/core/models_dev_pricing.rs @@ -209,6 +209,21 @@ struct ModelsDevCatalog { providers: HashMap, } +/// Immutable models.dev view for callers that price many rows in one pass. +/// Loading this once avoids repeating cache metadata checks for every row. +#[derive(Debug, Clone)] +pub struct ModelsDevPricingSnapshot { + artifact: Option>, +} + +impl ModelsDevPricingSnapshot { + pub fn lookup(&self, provider_id: &str, model_id: &str) -> Option { + self.artifact + .as_ref() + .and_then(|artifact| artifact.catalog.lookup(provider_id, model_id)) + } +} + #[derive(Debug, Deserialize)] #[serde(untagged)] enum ModelsDevCatalogWire { @@ -714,6 +729,13 @@ async fn wait_for_refresh(mut receiver: watch::Receiver>) -> bool { static REFRESH_COORDINATOR: LazyLock = LazyLock::new(ModelsDevRefreshCoordinator::default); +/// Loads the cached models.dev catalog once for bulk-pricing callers. +pub fn pricing_snapshot() -> ModelsDevPricingSnapshot { + let load = ModelsDevCache::load(SystemTime::now(), None); + let artifact = (!load.is_stale).then_some(load.artifact).flatten(); + ModelsDevPricingSnapshot { artifact } +} + /// Looks up a cached models.dev price for a provider/model pair. pub fn lookup(provider_id: &str, model_id: &str) -> Option { let load = ModelsDevCache::load(SystemTime::now(), None); diff --git a/rust/src/spend_contract/opencodex.rs b/rust/src/spend_contract/opencodex.rs index 980b7740dc..93256a366e 100644 --- a/rust/src/spend_contract/opencodex.rs +++ b/rust/src/spend_contract/opencodex.rs @@ -149,6 +149,9 @@ fn aggregate( let mut daily: BTreeMap = BTreeMap::new(); let mut known_cost = 0.0; let mut saw_known_cost = false; + // Upstream 0.55.0 #3136: resolve the dynamic pricing catalog once per + // aggregate instead of re-checking its cache metadata for every usage row. + let pricing_snapshot = crate::core::pricing_snapshot(); for entry in &entries { if let Some(conversation) = entry.conversation_id.as_ref() { @@ -163,7 +166,7 @@ fn aggregate( token_mix.reasoning_tokens = add_optional(token_mix.reasoning_tokens, entry.reasoning_tokens); - let cost = entry_cost(entry, custom); + let cost = entry_cost(entry, custom, &pricing_snapshot); match entry.usage_status.as_str() { "reported" if cost.is_some() => coverage.priced = coverage.priced.saturating_add(1), "estimated" if cost.is_some() => { @@ -273,7 +276,11 @@ fn aggregate( }) } -fn entry_cost(entry: &OpenCodexEntry, custom: &CustomPricing) -> Option { +fn entry_cost( + entry: &OpenCodexEntry, + custom: &CustomPricing, + pricing_snapshot: &crate::core::ModelsDevPricingSnapshot, +) -> Option { if !matches!(entry.usage_status.as_str(), "reported" | "estimated") { return None; } @@ -293,12 +300,13 @@ fn entry_cost(entry: &OpenCodexEntry, custom: &CustomPricing) -> Option { return rates.cost_parts(input, output, cache_read, cache_write); } let pricing_model = pricing_model(entry)?; - CostUsagePricing::codex_cost_usd_at_date( + CostUsagePricing::codex_cost_usd_at_date_with_pricing_snapshot( &pricing_model, input, cache_read, output, entry.timestamp.date_naive(), + Some(pricing_snapshot), ) } @@ -625,7 +633,8 @@ mod tests { #[test] fn opencodex_uses_request_day_for_historical_gpt56_pricing() { let entry = entry("openai", "gpt-5.6-terra"); - let cost = entry_cost(&entry, &CustomPricing::default()).unwrap(); + let pricing_snapshot = crate::core::pricing_snapshot(); + let cost = entry_cost(&entry, &CustomPricing::default(), &pricing_snapshot).unwrap(); let expected = 90.0 * 2.5e-6 + 10.0 * 2.5e-7 + 5.0 * 1.5e-5; assert!((cost - expected).abs() < 1e-12); }