Skip to content

Commit e455f34

Browse files
authored
fix(pricing): resolve Codex log model aliases (#1301)
* fix(pricing): price codex auto review logs Codex session logs can record codex-auto-review as the model name for review runs, but LiteLLM and the embedded pricing table do not publish that identifier directly. Resolve the internal review model to the latest embedded Codex pricing so reports keep the raw model label while still calculating cost and avoiding missing-pricing warnings. * fix(pricing): resolve Codex model aliases Codex logs can contain internal or shortened model identifiers that are not published as pricing keys. Treat these as model-name aliases instead of adding duplicated pricing rows. The fallback now preserves the raw model label in reports while resolving codex-auto-review to gpt-5.5 and gpt-5.3-spark to the existing gpt-5.3-codex-spark pricing path. * fix(pricing): alias Codex context limits CodeRabbit pointed out that model aliases resolved pricing but not context limits, which made alias handling asymmetric for callers that ask for context-window metadata. Route context limit lookup through the same model alias path as pricing, and make the alias tests cover the context limit behavior with clearer failure messages.
1 parent 49d5979 commit e455f34

1 file changed

Lines changed: 65 additions & 6 deletions

File tree

‎rust/crates/ccusage/src/pricing.rs‎

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -361,22 +361,29 @@ impl PricingMap {
361361
}
362362

363363
pub(crate) fn find(&self, model: &str) -> Option<Pricing> {
364-
self.find_entry(model)
364+
self.find_entry_or_alias(model)
365365
.or_else(|| {
366366
self.enable_models_dev_fallback
367-
.then(|| models_dev_pricing().and_then(|pricing| pricing.find_entry(model)))
367+
.then(|| {
368+
models_dev_pricing().and_then(|pricing| pricing.find_entry_or_alias(model))
369+
})
368370
.flatten()
369371
})
370372
// The embedded models.dev snapshot is a separate map, so it only
371373
// resolves models the primary table misses and never perturbs its
372374
// fuzzy alias matching. It works offline, unlike the network source.
373375
.or_else(|| {
374376
self.enable_embedded_models_dev_fallback
375-
.then(|| embedded_models_dev_pricing().find_entry(model))
377+
.then(|| embedded_models_dev_pricing().find_entry_or_alias(model))
376378
.flatten()
377379
})
378380
}
379381

382+
fn find_entry_or_alias(&self, model: &str) -> Option<Pricing> {
383+
self.find_entry(model)
384+
.or_else(|| pricing_alias(model).and_then(|alias| self.find_entry(alias)))
385+
}
386+
380387
fn find_entry(&self, model: &str) -> Option<Pricing> {
381388
self.entries.get(model).copied().or_else(|| {
382389
let normalized_model = normalized_pricing_key(model);
@@ -393,21 +400,27 @@ impl PricingMap {
393400
}
394401

395402
pub(crate) fn context_limit(&self, model: &str) -> Option<u64> {
396-
self.context_limit_entry(model)
403+
self.context_limit_entry_or_alias(model)
397404
.or_else(|| {
398405
self.enable_models_dev_fallback
399406
.then(|| {
400-
models_dev_pricing().and_then(|pricing| pricing.context_limit_entry(model))
407+
models_dev_pricing()
408+
.and_then(|pricing| pricing.context_limit_entry_or_alias(model))
401409
})
402410
.flatten()
403411
})
404412
.or_else(|| {
405413
self.enable_embedded_models_dev_fallback
406-
.then(|| embedded_models_dev_pricing().context_limit_entry(model))
414+
.then(|| embedded_models_dev_pricing().context_limit_entry_or_alias(model))
407415
.flatten()
408416
})
409417
}
410418

419+
fn context_limit_entry_or_alias(&self, model: &str) -> Option<u64> {
420+
self.context_limit_entry(model)
421+
.or_else(|| pricing_alias(model).and_then(|alias| self.context_limit_entry(alias)))
422+
}
423+
411424
fn context_limit_entry(&self, model: &str) -> Option<u64> {
412425
self.context_limits.get(model).copied().or_else(|| {
413426
let normalized_model = normalized_pricing_key(model);
@@ -1118,6 +1131,16 @@ fn normalized_pricing_key(value: &str) -> Cow<'_, str> {
11181131
}
11191132
}
11201133

1134+
/// Maps Codex log labels that upstream pricing sources do not publish to
1135+
/// canonical pricing keys.
1136+
fn pricing_alias(model: &str) -> Option<&'static str> {
1137+
match model {
1138+
"codex-auto-review" => Some("gpt-5.5"),
1139+
"gpt-5.3-spark" => Some("gpt-5.3-codex-spark"),
1140+
_ => None,
1141+
}
1142+
}
1143+
11211144
fn matches_model_suffix(part: &str, base: &str) -> bool {
11221145
let Some(index) = part.rfind(base) else {
11231146
return false;
@@ -1772,6 +1795,42 @@ mod tests {
17721795
assert_eq!(pricing.find("gpt-5.3-codex").unwrap().fast_multiplier, 2.0);
17731796
}
17741797

1798+
#[test]
1799+
fn embedded_pricing_resolves_codex_auto_review_model() {
1800+
let pricing = PricingMap::load_embedded();
1801+
let auto_review = pricing
1802+
.find("codex-auto-review")
1803+
.expect("codex-auto-review should resolve via model alias");
1804+
let latest_codex = pricing
1805+
.find("gpt-5.5")
1806+
.expect("canonical latest Codex pricing should exist");
1807+
1808+
assert_eq!(auto_review.input, latest_codex.input);
1809+
assert_eq!(auto_review.output, latest_codex.output);
1810+
assert_eq!(auto_review.cache_read, latest_codex.cache_read);
1811+
assert_eq!(auto_review.fast_multiplier, latest_codex.fast_multiplier);
1812+
assert_eq!(
1813+
pricing.context_limit("codex-auto-review"),
1814+
pricing.context_limit("gpt-5.5")
1815+
);
1816+
}
1817+
1818+
#[test]
1819+
fn embedded_pricing_resolves_codex_spark_short_model_alias() {
1820+
let pricing = PricingMap::load_embedded();
1821+
let short_spark = pricing
1822+
.find("gpt-5.3-spark")
1823+
.expect("gpt-5.3-spark should resolve via model alias");
1824+
let codex_spark = pricing
1825+
.find("gpt-5.3-codex-spark")
1826+
.expect("canonical Codex Spark pricing should exist");
1827+
1828+
assert_eq!(short_spark.input, codex_spark.input);
1829+
assert_eq!(short_spark.output, codex_spark.output);
1830+
assert_eq!(short_spark.cache_read, codex_spark.cache_read);
1831+
assert_eq!(short_spark.fast_multiplier, codex_spark.fast_multiplier);
1832+
}
1833+
17751834
#[test]
17761835
fn embedded_pricing_includes_claude_fast_multiplier_for_provider_models() {
17771836
let pricing = PricingMap::load_embedded();

0 commit comments

Comments
 (0)