use super::{aliases, litellm::ModelPricing};
use crate::{provider_identity, strip_parenthesized_reasoning_tier, TokenBreakdown};
use std::collections::HashMap;
use std::sync::RwLock;
const PROVIDER_PREFIXES: &[&str] = &[
"openai/",
"anthropic/",
"google/",
"meta-llama/",
"mistralai/",
"minimax/",
"deepseek/",
"qwen/",
"cohere/",
"perplexity/",
"x-ai/",
];
const ORIGINAL_PROVIDER_PREFIXES: &[&str] = &[
"x-ai/",
"xai/",
"anthropic/",
"openai/",
"google/",
"meta-llama/",
"mistralai/",
"minimax/",
"deepseek/",
"z-ai/",
"qwen/",
"cohere/",
"perplexity/",
"moonshotai/",
];
const RESELLER_PROVIDER_PREFIXES: &[&str] = &[
"azure/",
"azure_ai/",
"bedrock/",
"vertex_ai/",
"together/",
"together_ai/",
"fireworks_ai/",
"groq/",
"openrouter/",
];
const FUZZY_BLOCKLIST: &[&str] = &[
"auto",
"mini",
"chat",
"base",
"claude",
"anthropic",
"model",
"router",
];
const MAX_LOOKUP_CACHE_ENTRIES: usize = 512;
const TIERED_PRICING_THRESHOLD_128K_TOKENS: f64 = 128_000.0;
const TIERED_PRICING_THRESHOLD_200K_TOKENS: f64 = 200_000.0;
const TIERED_PRICING_THRESHOLD_256K_TOKENS: f64 = 256_000.0;
const TIERED_PRICING_THRESHOLD_272K_TOKENS: f64 = 272_000.0;
const MIN_FUZZY_MATCH_LEN: usize = 5;
const MIN_MODEL_NAME_LEN: usize = 2;
const MAX_PREFIX_STRIP_SEGMENTS: usize = 2;
const MAX_SUFFIX_STRIP_SEGMENTS: usize = 4;
#[derive(Clone)]
struct CachedResult {
pricing: ModelPricing,
source: String,
matched_key: String,
}
struct KeyModelPart {
key: String,
lower_model_part: String,
}
struct ProviderScopedModelPath<'a> {
provider: &'a str,
terminal_model_id: &'a str,
}
pub struct PricingLookup {
litellm: HashMap<String, ModelPricing>,
openrouter: HashMap<String, ModelPricing>,
cursor: HashMap<String, ModelPricing>,
sakana: HashMap<String, ModelPricing>,
models_dev: HashMap<String, ModelPricing>,
litellm_keys: Vec<String>,
openrouter_keys: Vec<String>,
litellm_key_parts: Vec<KeyModelPart>,
openrouter_key_parts: Vec<KeyModelPart>,
models_dev_key_parts: Vec<KeyModelPart>,
litellm_lower: HashMap<String, String>,
openrouter_lower: HashMap<String, String>,
models_dev_lower: HashMap<String, String>,
openrouter_model_part: HashMap<String, String>,
models_dev_model_part: HashMap<String, String>,
cursor_lower: HashMap<String, String>,
sakana_lower: HashMap<String, String>,
lookup_cache: RwLock<HashMap<String, Option<CachedResult>>>,
}
pub struct LookupResult {
pub pricing: ModelPricing,
pub source: String,
pub matched_key: String,
}
impl PricingLookup {
pub fn new(
litellm: HashMap<String, ModelPricing>,
openrouter: HashMap<String, ModelPricing>,
cursor: HashMap<String, ModelPricing>,
) -> Self {
Self::new_with_models_dev(litellm, openrouter, cursor, HashMap::new(), HashMap::new())
}
pub fn new_with_models_dev(
litellm: HashMap<String, ModelPricing>,
openrouter: HashMap<String, ModelPricing>,
cursor: HashMap<String, ModelPricing>,
sakana: HashMap<String, ModelPricing>,
models_dev: HashMap<String, ModelPricing>,
) -> Self {
let mut litellm_keys: Vec<String> = litellm.keys().cloned().collect();
litellm_keys.sort_by_key(|k| std::cmp::Reverse(k.len()));
let mut openrouter_keys: Vec<String> = openrouter.keys().cloned().collect();
openrouter_keys.sort_by_key(|k| std::cmp::Reverse(k.len()));
let mut models_dev_keys: Vec<String> = models_dev.keys().cloned().collect();
models_dev_keys.sort_by_key(|k| std::cmp::Reverse(k.len()));
let mut litellm_lower = HashMap::with_capacity(litellm.len());
for key in &litellm_keys {
litellm_lower.insert(key.to_lowercase(), key.clone());
}
let mut openrouter_lower = HashMap::with_capacity(openrouter.len());
let mut openrouter_model_part = HashMap::with_capacity(openrouter.len());
for key in &openrouter_keys {
let lower = key.to_lowercase();
openrouter_lower.insert(lower.clone(), key.clone());
if let Some(model_part) = lower.split('/').next_back() {
if model_part != lower {
openrouter_model_part.insert(model_part.to_string(), key.clone());
}
}
}
let mut models_dev_lower = HashMap::with_capacity(models_dev.len());
let mut models_dev_model_part: HashMap<String, String> =
HashMap::with_capacity(models_dev.len());
for key in &models_dev_keys {
let lower = key.to_lowercase();
models_dev_lower.insert(lower.clone(), key.clone());
if !models_dev.get(key).is_some_and(has_any_usable_pricing) {
continue;
}
if let Some(model_part) = lower.split('/').next_back() {
if model_part != lower {
match models_dev_model_part.entry(model_part.to_string()) {
std::collections::hash_map::Entry::Occupied(mut entry) => {
if prefers_model_part_key(key, entry.get()) {
entry.insert(key.clone());
}
}
std::collections::hash_map::Entry::Vacant(entry) => {
entry.insert(key.clone());
}
}
}
}
}
let mut cursor_lower = HashMap::with_capacity(cursor.len());
for key in cursor.keys() {
cursor_lower.insert(key.to_lowercase(), key.clone());
}
let mut sakana_lower = HashMap::with_capacity(sakana.len());
for key in sakana.keys() {
sakana_lower.insert(key.to_lowercase(), key.clone());
}
let build_key_parts = |keys: &[String]| -> Vec<KeyModelPart> {
keys.iter()
.map(|key| {
let lower = key.to_lowercase();
let model_part = lower.split('/').next_back().unwrap_or(&lower).to_string();
KeyModelPart {
key: key.clone(),
lower_model_part: model_part,
}
})
.collect()
};
let litellm_key_parts = build_key_parts(&litellm_keys);
let openrouter_key_parts = build_key_parts(&openrouter_keys);
let models_dev_key_parts = build_key_parts(&models_dev_keys);
Self {
litellm,
openrouter,
cursor,
sakana,
models_dev,
litellm_keys,
openrouter_keys,
litellm_key_parts,
openrouter_key_parts,
models_dev_key_parts,
litellm_lower,
openrouter_lower,
models_dev_lower,
openrouter_model_part,
models_dev_model_part,
cursor_lower,
sakana_lower,
lookup_cache: RwLock::new(HashMap::with_capacity(64)),
}
}
pub fn lookup(&self, model_id: &str) -> Option<LookupResult> {
self.lookup_with_provider(model_id, None)
}
pub fn lookup_with_provider(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
let provider_id = normalize_provider_hint(provider_id);
let cache_key = build_lookup_cache_key(model_id, provider_id);
if let Some(cached) = self
.lookup_cache
.read()
.ok()
.and_then(|c| c.get(&cache_key).cloned())
{
return cached.map(|c| LookupResult {
pricing: c.pricing,
source: c.source,
matched_key: c.matched_key,
});
}
let result = self.lookup_with_source_and_provider(model_id, None, provider_id);
if let Ok(mut cache) = self.lookup_cache.write() {
if cache.len() >= MAX_LOOKUP_CACHE_ENTRIES {
let evict_count = cache.len() / 4;
let keys_to_remove: Vec<String> = cache.keys().take(evict_count).cloned().collect();
for key in keys_to_remove {
cache.remove(&key);
}
}
cache.insert(
cache_key,
result.as_ref().map(|r| CachedResult {
pricing: r.pricing.clone(),
source: r.source.clone(),
matched_key: r.matched_key.clone(),
}),
);
}
result
}
pub fn lookup_with_source(
&self,
model_id: &str,
force_source: Option<&str>,
) -> Option<LookupResult> {
self.lookup_with_source_and_provider(model_id, force_source, None)
}
pub fn lookup_with_source_and_provider(
&self,
model_id: &str,
force_source: Option<&str>,
provider_id: Option<&str>,
) -> Option<LookupResult> {
let provider_id = normalize_provider_hint(provider_id);
let canonical = aliases::resolve_alias(model_id).unwrap_or(model_id);
let lower = canonical.to_lowercase();
let normalized_owned = strip_parenthesized_reasoning_tier(&lower).map(str::to_owned);
if normalized_owned.is_none()
&& lower
.strip_suffix(')')
.and_then(|inner| inner.rsplit_once('('))
.is_some()
{
return None;
}
let lower_ref: &str = normalized_owned.as_deref().unwrap_or(&lower);
let do_lookup = |id: &str| match force_source {
Some("litellm") => self.lookup_litellm_only(id, provider_id),
Some("openrouter") => self.lookup_openrouter_only(id, provider_id),
Some("models.dev") | Some("modelsdev") | Some("models_dev") => {
self.lookup_models_dev_only(id, provider_id)
}
_ => self.lookup_auto(id, provider_id),
};
let requested_family = claude_family(lower_ref);
let requested_version = requested_claude_version(lower_ref);
let unparsed_modern_version = requested_family.is_some()
&& requested_version.is_none()
&& contains_delimited_modern_major_minor(lower_ref);
let unsafe_claude_resolution = |result: &LookupResult| {
resolves_unsafe_claude_version(
requested_family,
requested_version.as_deref(),
unparsed_modern_version,
result,
)
};
if let Some(result) = do_lookup(lower_ref) {
if unsafe_claude_resolution(&result) {
return None;
}
return Some(result);
}
if parse_provider_scoped_model_path(lower_ref).is_some() {
return None;
}
let guarded_lookup = |candidate: &str| {
do_lookup(candidate).filter(|result| !unsafe_claude_resolution(result))
};
if let Some(terminal) = strip_generic_provider_prefix(lower_ref) {
if let Some(result) = guarded_lookup(terminal) {
return Some(result);
}
}
if let Some(result) = try_strip_unknown_suffix(lower_ref, guarded_lookup) {
return Some(result);
}
if let Some(result) = try_strip_unknown_prefix(lower_ref, guarded_lookup) {
return Some(result);
}
None
}
fn lookup_auto(&self, model_id: &str, provider_id: Option<&str>) -> Option<LookupResult> {
if let Some(result) = self.lookup_provider_scoped_path(model_id, provider_id) {
return Some(result);
}
if parse_provider_scoped_model_path(model_id).is_some() {
return None;
}
if let Some(stripped) = strip_known_provider_prefix(model_id) {
let prefix_matches_hint =
provider_id.is_none() || model_prefix_matches_provider(model_id, provider_id);
if prefix_matches_hint {
if let Some(exact_litellm) = self.exact_match_litellm(model_id) {
return Some(exact_litellm);
}
let exact_openrouter = self.exact_match_openrouter(model_id);
let stripped_litellm = self.exact_or_normalized_litellm(stripped, provider_id);
if let (Some(litellm), Some(openrouter)) = (&stripped_litellm, &exact_openrouter) {
if has_meaningful_tier_support(&litellm.pricing)
&& !has_any_valid_above_tier_value(&openrouter.pricing)
{
return stripped_litellm;
}
}
if let Some(result) = exact_openrouter {
return Some(result);
}
if let Some(result) = stripped_litellm {
return Some(result);
}
if let Some(result) = self.exact_match_models_dev(model_id) {
return Some(result);
}
if let Some(result) =
self.exact_match_models_dev_with_provider(stripped, provider_id)
{
return Some(result);
}
} else {
if let Some(result) = choose_best_source_result(
self.exact_match_litellm_for_provider(stripped, provider_id),
self.exact_match_openrouter_for_provider(stripped, provider_id),
provider_id,
) {
return Some(result);
}
if let Some(result) = self.exact_or_normalized_litellm(stripped, provider_id) {
return Some(result);
}
if let Some(result) =
self.exact_match_models_dev_with_provider(stripped, provider_id)
{
return Some(result);
}
}
}
let exact_litellm = self.exact_match_litellm(model_id);
if should_prefer_openai_tiered_litellm(model_id, provider_id, exact_litellm.as_ref()) {
return exact_litellm;
}
if let Some(result) = choose_best_source_result(
self.exact_match_litellm_for_provider(model_id, provider_id),
self.exact_match_openrouter_for_provider(model_id, provider_id),
provider_id,
) {
return Some(result);
}
if let Some(result) = exact_litellm {
return Some(result);
}
if let Some(result) = self.exact_match_openrouter_full_key(model_id) {
return Some(result);
}
if provider_id.is_some() {
if let Some(result) = self.exact_match_models_dev_for_provider(model_id, provider_id) {
return Some(result);
}
}
if let Some(result) = self.exact_match_openrouter_model_part(model_id) {
return Some(result);
}
if let Some(version_normalized) = normalize_version_separator(model_id) {
if let Some(result) = choose_best_source_result(
self.exact_match_litellm_for_provider(&version_normalized, provider_id),
self.exact_match_openrouter_for_provider(&version_normalized, provider_id),
provider_id,
) {
return Some(result);
}
if provider_id.is_some() {
if let Some(result) =
self.exact_match_models_dev_for_provider(&version_normalized, provider_id)
{
return Some(result);
}
}
if let Some(result) = self.exact_match_litellm(&version_normalized) {
return Some(result);
}
if let Some(result) = self.exact_match_openrouter(&version_normalized) {
return Some(result);
}
}
if let Some(result) = self.exact_match_models_dev_with_provider(model_id, provider_id) {
return Some(result);
}
if let Some(version_normalized) = normalize_version_separator(model_id) {
if let Some(result) =
self.exact_match_models_dev_with_provider(&version_normalized, provider_id)
{
return Some(result);
}
}
if let Some(normalized) = normalize_model_name(model_id) {
if let Some(result) = choose_best_source_result(
self.exact_match_litellm_for_provider(&normalized, provider_id),
self.exact_match_openrouter_for_provider(&normalized, provider_id),
provider_id,
) {
return Some(result);
}
if let Some(result) = self.exact_match_litellm(&normalized) {
return Some(result);
}
if let Some(result) = self.exact_match_openrouter(&normalized) {
return Some(result);
}
if let Some(result) =
self.exact_match_models_dev_with_provider(&normalized, provider_id)
{
return Some(result);
}
}
if let Some(result) = self.prefix_match_litellm(model_id, provider_id) {
return Some(result);
}
if let Some(result) = self.prefix_match_openrouter(model_id, provider_id) {
return Some(result);
}
if let Some(result) = self.prefix_match_models_dev(model_id, provider_id) {
return Some(result);
}
if let Some(version_normalized) = normalize_version_separator(model_id) {
if let Some(result) = self.prefix_match_litellm(&version_normalized, provider_id) {
return Some(result);
}
if let Some(result) = self.prefix_match_openrouter(&version_normalized, provider_id) {
return Some(result);
}
if let Some(result) = self.prefix_match_models_dev(&version_normalized, provider_id) {
return Some(result);
}
}
if let Some(result) = self.exact_match_cursor(model_id) {
return Some(result);
}
if let Some(version_normalized) = normalize_version_separator(model_id) {
if let Some(result) = self.exact_match_cursor(&version_normalized) {
return Some(result);
}
}
if let Some(result) = self.exact_match_sakana(model_id) {
return Some(result);
}
if let Some(version_normalized) = normalize_version_separator(model_id) {
if let Some(result) = self.exact_match_sakana(&version_normalized) {
return Some(result);
}
}
if !is_fuzzy_eligible(model_id) {
return None;
}
let litellm_result = self.fuzzy_match_litellm(model_id, provider_id);
let openrouter_result = self.fuzzy_match_openrouter(model_id, provider_id);
choose_best_source_result(litellm_result, openrouter_result, provider_id)
}
fn exact_or_normalized_litellm(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
if let Some(result) = self.exact_match_litellm_for_provider(model_id, provider_id) {
return Some(result);
}
if let Some(result) = self.exact_match_litellm(model_id) {
return Some(result);
}
if let Some(version_normalized) = normalize_version_separator(model_id) {
if let Some(result) =
self.exact_match_litellm_for_provider(&version_normalized, provider_id)
{
return Some(result);
}
if let Some(result) = self.exact_match_litellm(&version_normalized) {
return Some(result);
}
}
if let Some(normalized) = normalize_model_name(model_id) {
if let Some(result) = self.exact_match_litellm_for_provider(&normalized, provider_id) {
return Some(result);
}
if let Some(result) = self.exact_match_litellm(&normalized) {
return Some(result);
}
}
None
}
fn lookup_models_dev_only(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
if parse_provider_scoped_model_path(model_id).is_some() {
return None;
}
if let Some(result) = self.exact_match_models_dev_with_provider(model_id, provider_id) {
return Some(result);
}
if let Some(version_normalized) = normalize_version_separator(model_id) {
if let Some(result) =
self.exact_match_models_dev_with_provider(&version_normalized, provider_id)
{
return Some(result);
}
}
if let Some(normalized) = normalize_model_name(model_id) {
if let Some(result) =
self.exact_match_models_dev_with_provider(&normalized, provider_id)
{
return Some(result);
}
}
if let Some(result) = self.prefix_match_models_dev(model_id, provider_id) {
return Some(result);
}
if let Some(version_normalized) = normalize_version_separator(model_id) {
if let Some(result) = self.prefix_match_models_dev(&version_normalized, provider_id) {
return Some(result);
}
}
None
}
fn lookup_litellm_only(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
if let Some(result) = self.lookup_provider_scoped_path_litellm(model_id, provider_id) {
return Some(result);
}
if parse_provider_scoped_model_path(model_id).is_some() {
return None;
}
if let Some(result) = self.exact_or_normalized_litellm(model_id, provider_id) {
return Some(result);
}
if let Some(stripped) = strip_known_provider_prefix(model_id) {
if let Some(result) = self.exact_or_normalized_litellm(stripped, provider_id) {
return Some(result);
}
}
if let Some(result) = self.prefix_match_litellm(model_id, provider_id) {
return Some(result);
}
if let Some(version_normalized) = normalize_version_separator(model_id) {
if let Some(result) = self.prefix_match_litellm(&version_normalized, provider_id) {
return Some(result);
}
}
if is_fuzzy_eligible(model_id) {
if let Some(result) = self.fuzzy_match_litellm(model_id, provider_id) {
return Some(result);
}
}
None
}
fn lookup_openrouter_only(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
if let Some(result) = self.lookup_provider_scoped_path_openrouter(model_id, provider_id) {
return Some(result);
}
if parse_provider_scoped_model_path(model_id).is_some() {
return None;
}
if let Some(result) = self.exact_match_openrouter_with_provider(model_id, provider_id) {
return Some(result);
}
if let Some(version_normalized) = normalize_version_separator(model_id) {
if let Some(result) =
self.exact_match_openrouter_with_provider(&version_normalized, provider_id)
{
return Some(result);
}
}
if let Some(normalized) = normalize_model_name(model_id) {
if let Some(result) =
self.exact_match_openrouter_with_provider(&normalized, provider_id)
{
return Some(result);
}
}
if let Some(result) = self.prefix_match_openrouter(model_id, provider_id) {
return Some(result);
}
if let Some(version_normalized) = normalize_version_separator(model_id) {
if let Some(result) = self.prefix_match_openrouter(&version_normalized, provider_id) {
return Some(result);
}
}
if is_fuzzy_eligible(model_id) {
if let Some(result) = self.fuzzy_match_openrouter(model_id, provider_id) {
return Some(result);
}
}
None
}
fn lookup_provider_scoped_path(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
let scoped = parse_provider_scoped_model_path(model_id)?;
if !provider_hint_matches_scoped_provider(provider_id, scoped.provider) {
return None;
}
choose_best_source_result(
self.lookup_provider_scoped_path_litellm(model_id, provider_id),
self.lookup_provider_scoped_path_openrouter(model_id, provider_id),
Some(scoped.provider),
)
}
fn lookup_provider_scoped_path_litellm(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
let scoped = parse_provider_scoped_model_path(model_id)?;
if !provider_hint_matches_scoped_provider(provider_id, scoped.provider) {
return None;
}
if let Some(result) = self.exact_match_litellm(model_id) {
return Some(result);
}
let scoped_tags = provider_identity::provider_tags(scoped.provider);
for prefix in RESELLER_PROVIDER_PREFIXES {
if !provider_prefix_matches_scoped_provider(prefix, &scoped_tags) {
continue;
}
let key = format!("{}{}", prefix, model_id);
if let Some(litellm_key) = self.litellm_lower.get(&key) {
if let Some(pricing) = self.litellm.get(litellm_key) {
if let Some(result) = lookup_result_if_usable(pricing, "LiteLLM", litellm_key) {
return Some(result);
}
}
}
}
self.exact_match_litellm_for_provider(scoped.terminal_model_id, Some(scoped.provider))
}
fn lookup_provider_scoped_path_openrouter(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
let scoped = parse_provider_scoped_model_path(model_id)?;
if !provider_hint_matches_scoped_provider(provider_id, scoped.provider) {
return None;
}
self.exact_match_openrouter(model_id).or_else(|| {
self.exact_match_openrouter_for_provider(
scoped.terminal_model_id,
Some(scoped.provider),
)
})
}
fn exact_match_litellm_for_provider(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
exact_match_with_provider_prefixes(
model_id,
provider_id,
&self.litellm_key_parts,
&self.litellm,
"LiteLLM",
)
}
fn exact_match_openrouter_for_provider(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
exact_match_with_provider_prefixes(
model_id,
provider_id,
&self.openrouter_key_parts,
&self.openrouter,
"OpenRouter",
)
}
fn exact_match_openrouter_with_provider(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
self.exact_match_openrouter_for_provider(model_id, provider_id)
.or_else(|| self.exact_match_openrouter(model_id))
}
fn exact_match_models_dev_for_provider(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
exact_match_with_provider_prefixes(
model_id,
provider_id,
&self.models_dev_key_parts,
&self.models_dev,
"Models.dev",
)
}
fn exact_match_models_dev_with_provider(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
self.exact_match_models_dev_for_provider(model_id, provider_id)
.or_else(|| self.exact_match_models_dev(model_id))
}
fn exact_match_litellm(&self, model_id: &str) -> Option<LookupResult> {
let key = self.litellm_lower.get(model_id)?;
let pricing = self.litellm.get(key)?;
lookup_result_if_usable(pricing, "LiteLLM", key)
}
fn exact_match_openrouter(&self, model_id: &str) -> Option<LookupResult> {
self.exact_match_openrouter_full_key(model_id)
.or_else(|| self.exact_match_openrouter_model_part(model_id))
}
fn exact_match_openrouter_full_key(&self, model_id: &str) -> Option<LookupResult> {
let key = self.openrouter_lower.get(model_id)?;
let pricing = self.openrouter.get(key)?;
lookup_result_if_usable(pricing, "OpenRouter", key)
}
fn exact_match_openrouter_model_part(&self, model_id: &str) -> Option<LookupResult> {
let key = self.openrouter_model_part.get(model_id)?;
let pricing = self.openrouter.get(key)?;
lookup_result_if_usable(pricing, "OpenRouter", key)
}
fn exact_match_models_dev(&self, model_id: &str) -> Option<LookupResult> {
if let Some(key) = self.models_dev_lower.get(model_id) {
if let Some(pricing) = self.models_dev.get(key) {
return Some(LookupResult {
pricing: pricing.clone(),
source: "Models.dev".into(),
matched_key: key.clone(),
});
}
}
if let Some(key) = self.models_dev_model_part.get(model_id) {
if let Some(pricing) = self.models_dev.get(key) {
return Some(LookupResult {
pricing: pricing.clone(),
source: "Models.dev".into(),
matched_key: key.clone(),
});
}
}
None
}
fn exact_match_cursor(&self, model_id: &str) -> Option<LookupResult> {
if let Some(key) = self.cursor_lower.get(model_id) {
return lookup_result_if_usable(self.cursor.get(key).unwrap(), "Cursor", key);
}
if let Some(model_part) = model_id.split('/').next_back() {
if model_part != model_id {
if let Some(key) = self.cursor_lower.get(model_part) {
return lookup_result_if_usable(self.cursor.get(key).unwrap(), "Cursor", key);
}
}
}
None
}
fn exact_match_sakana(&self, model_id: &str) -> Option<LookupResult> {
if let Some(key) = self.sakana_lower.get(model_id) {
return lookup_result_if_usable(self.sakana.get(key).unwrap(), "Sakana", key);
}
if let Some(model_part) = model_id.split('/').next_back() {
if model_part != model_id {
if let Some(key) = self.sakana_lower.get(model_part) {
return lookup_result_if_usable(self.sakana.get(key).unwrap(), "Sakana", key);
}
}
}
None
}
fn prefix_match_litellm(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
if let Some(result) = self.exact_match_litellm_for_provider(model_id, provider_id) {
return Some(result);
}
for prefix in PROVIDER_PREFIXES {
let key = format!("{}{}", prefix, model_id);
if let Some(litellm_key) = self.litellm_lower.get(&key) {
if let Some(pricing) = self.litellm.get(litellm_key) {
if let Some(result) = lookup_result_if_usable(pricing, "LiteLLM", litellm_key) {
return Some(result);
}
}
}
}
None
}
fn prefix_match_openrouter(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
if let Some(result) = self.exact_match_openrouter_for_provider(model_id, provider_id) {
return Some(result);
}
for prefix in PROVIDER_PREFIXES {
let key = format!("{}{}", prefix, model_id);
if let Some(or_key) = self.openrouter_lower.get(&key) {
if let Some(pricing) = self.openrouter.get(or_key) {
if let Some(result) = lookup_result_if_usable(pricing, "OpenRouter", or_key) {
return Some(result);
}
}
}
}
None
}
fn prefix_match_models_dev(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
if let Some(result) = self.exact_match_models_dev_for_provider(model_id, provider_id) {
return Some(result);
}
for prefix in PROVIDER_PREFIXES {
let key = format!("{}{}", prefix, model_id);
if let Some(models_dev_key) = self.models_dev_lower.get(&key) {
if let Some(pricing) = self.models_dev.get(models_dev_key) {
return Some(LookupResult {
pricing: pricing.clone(),
source: "Models.dev".into(),
matched_key: models_dev_key.clone(),
});
}
}
}
None
}
fn fuzzy_match_litellm(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
let family = extract_model_family(model_id);
let mut family_matches_list: Vec<&String> = Vec::new();
for key in &self.litellm_keys {
let lower_key = key.to_lowercase();
if family_matches(&lower_key, &family) && contains_model_id(&lower_key, model_id) {
family_matches_list.push(key);
}
}
if let Some(result) =
select_best_match(&family_matches_list, &self.litellm, "LiteLLM", provider_id)
{
return Some(result);
}
let mut all_matches: Vec<&String> = Vec::new();
for key in &self.litellm_keys {
let lower_key = key.to_lowercase();
if contains_model_id(&lower_key, model_id) {
all_matches.push(key);
}
}
select_best_match(&all_matches, &self.litellm, "LiteLLM", provider_id)
}
fn fuzzy_match_openrouter(
&self,
model_id: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
let family = extract_model_family(model_id);
let mut family_matches_list: Vec<&String> = Vec::new();
for key in &self.openrouter_keys {
let lower_key = key.to_lowercase();
let model_part = lower_key.split('/').next_back().unwrap_or(&lower_key);
if family_matches(model_part, &family) && contains_model_id(model_part, model_id) {
family_matches_list.push(key);
}
}
if let Some(result) = select_best_match(
&family_matches_list,
&self.openrouter,
"OpenRouter",
provider_id,
) {
return Some(result);
}
let mut all_matches: Vec<&String> = Vec::new();
for key in &self.openrouter_keys {
let lower_key = key.to_lowercase();
let model_part = lower_key.split('/').next_back().unwrap_or(&lower_key);
if contains_model_id(model_part, model_id) {
all_matches.push(key);
}
}
select_best_match(&all_matches, &self.openrouter, "OpenRouter", provider_id)
}
pub fn calculate_cost(
&self,
model_id: &str,
input: i64,
output: i64,
cache_read: i64,
cache_write: i64,
reasoning: i64,
) -> f64 {
let usage = TokenBreakdown {
input,
output,
cache_read,
cache_write,
reasoning,
};
self.calculate_cost_with_provider(model_id, None, &usage)
}
pub fn calculate_cost_with_provider(
&self,
model_id: &str,
provider_id: Option<&str>,
usage: &TokenBreakdown,
) -> f64 {
let provider_id = normalize_provider_hint(provider_id);
let result = match self.lookup_with_provider(model_id, provider_id) {
Some(r) => r,
None => return 0.0,
};
compute_cost_for_lookup(&result, provider_id, usage)
}
}
fn matches_model_or_snapshot(model_id: &str, base: &str) -> bool {
model_id == base
|| model_id
.strip_prefix(base)
.is_some_and(|suffix| suffix.starts_with("-20"))
}
fn is_openai_full_request_272k_model(model_id: &str) -> bool {
let key = model_id.to_ascii_lowercase();
let model_id = key.split('/').next_back().unwrap_or(&key);
[
"gpt-5.4",
"gpt-5.4-pro",
"gpt-5.5",
"gpt-5.5-pro",
"gpt-5.6",
"gpt-5.6-sol",
"gpt-5.6-terra",
"gpt-5.6-luna",
]
.into_iter()
.any(|base| matches_model_or_snapshot(model_id, base))
}
fn should_prefer_openai_tiered_litellm(
model_id: &str,
provider_id: Option<&str>,
litellm: Option<&LookupResult>,
) -> bool {
provider_id.is_some_and(|provider| {
provider_identity::canonical_provider(provider).as_deref() == Some("openai")
}) && is_openai_full_request_272k_model(model_id)
&& litellm.is_some_and(|result| has_complete_openai_272k_pricing(&result.pricing))
}
fn has_complete_openai_272k_pricing(pricing: &ModelPricing) -> bool {
let valid_pair = |base: Option<f64>, above: Option<f64>| {
base.is_some_and(is_valid_price_value) && above.is_some_and(is_valid_price_value)
};
valid_pair(
pricing.input_cost_per_token,
pricing.input_cost_per_token_above_272k_tokens,
) && valid_pair(
pricing.output_cost_per_token,
pricing.output_cost_per_token_above_272k_tokens,
) && valid_pair(
pricing.cache_read_input_token_cost,
pricing.cache_read_input_token_cost_above_272k_tokens,
)
}
fn uses_openai_full_request_272k_pricing(result: &LookupResult, provider_id: Option<&str>) -> bool {
if result.source != "LiteLLM"
|| is_reseller_provider(&result.matched_key)
|| provider_id.is_some_and(|provider| {
provider_identity::canonical_provider(provider).as_deref() != Some("openai")
})
{
return false;
}
let key = result.matched_key.to_ascii_lowercase();
if key.contains('/') && !key.starts_with("openai/") {
return false;
}
is_openai_full_request_272k_model(&key)
}
fn compute_cost_for_lookup(
result: &LookupResult,
provider_id: Option<&str>,
usage: &TokenBreakdown,
) -> f64 {
let calculate = |pricing| {
compute_cost(
pricing,
usage.input,
usage.output,
usage.cache_read,
usage.cache_write,
usage.reasoning,
)
};
let total_input = usage
.input
.max(0)
.saturating_add(usage.cache_read.max(0))
.saturating_add(usage.cache_write.max(0));
if !uses_openai_full_request_272k_pricing(result, provider_id) {
return calculate(&result.pricing);
}
let mut pricing = result.pricing.clone();
if total_input <= TIERED_PRICING_THRESHOLD_272K_TOKENS as i64 {
pricing.input_cost_per_token_above_272k_tokens = None;
pricing.output_cost_per_token_above_272k_tokens = None;
pricing.cache_read_input_token_cost_above_272k_tokens = None;
return calculate(&pricing);
}
if let Some(high) = pricing
.input_cost_per_token_above_272k_tokens
.filter(|price| is_valid_price_value(*price))
{
let input_multiplier = pricing
.input_cost_per_token
.filter(|base| is_valid_price_value(*base) && *base > 0.0)
.map(|base| high / base);
for rate in [
&mut pricing.input_cost_per_token,
&mut pricing.input_cost_per_token_above_128k_tokens,
&mut pricing.input_cost_per_token_above_200k_tokens,
&mut pricing.input_cost_per_token_above_256k_tokens,
&mut pricing.input_cost_per_token_above_272k_tokens,
] {
*rate = Some(high);
}
if let (Some(multiplier), Some(cache_write_price)) = (
input_multiplier,
pricing
.cache_creation_input_token_cost
.filter(|price| is_valid_price_value(*price)),
) {
let high = Some(cache_write_price * multiplier);
pricing.cache_creation_input_token_cost = high;
pricing.cache_creation_input_token_cost_above_200k_tokens = high;
}
}
if let Some(high) = pricing
.output_cost_per_token_above_272k_tokens
.filter(|price| is_valid_price_value(*price))
{
for rate in [
&mut pricing.output_cost_per_token,
&mut pricing.output_cost_per_token_above_128k_tokens,
&mut pricing.output_cost_per_token_above_200k_tokens,
&mut pricing.output_cost_per_token_above_256k_tokens,
&mut pricing.output_cost_per_token_above_272k_tokens,
] {
*rate = Some(high);
}
}
if let Some(high) = pricing
.cache_read_input_token_cost_above_272k_tokens
.filter(|price| is_valid_price_value(*price))
{
for rate in [
&mut pricing.cache_read_input_token_cost,
&mut pricing.cache_read_input_token_cost_above_200k_tokens,
&mut pricing.cache_read_input_token_cost_above_272k_tokens,
] {
*rate = Some(high);
}
}
calculate(&pricing)
}
pub fn compute_cost(
pricing: &ModelPricing,
input: i64,
output: i64,
cache_read: i64,
cache_write: i64,
reasoning: i64,
) -> f64 {
let safe_price = |opt: Option<f64>| opt.filter(|v| is_valid_price_value(*v)).unwrap_or(0.0);
let tiered_cost = |tokens: f64, base: Option<f64>, tiers: &[(f64, Option<f64>)]| {
let base_price = safe_price(base);
let mut cost = 0.0;
let mut lower_bound = 0.0;
let mut active_price = base_price;
for (threshold, tier_price) in tiers {
let Some(tier_price) = tier_price.filter(|v| is_valid_price_value(*v)) else {
continue;
};
if !threshold.is_finite() || *threshold <= lower_bound {
continue;
}
if tokens <= *threshold {
return cost + (tokens - lower_bound).max(0.0) * active_price;
}
cost += (*threshold - lower_bound) * active_price;
lower_bound = *threshold;
active_price = tier_price;
}
cost + (tokens - lower_bound).max(0.0) * active_price
};
let input_clamped = input.max(0) as f64;
let output_clamped = output.max(0).saturating_add(reasoning.max(0)) as f64;
let cache_read_clamped = cache_read.max(0) as f64;
let cache_write_clamped = cache_write.max(0) as f64;
let input_cost = tiered_cost(
input_clamped,
pricing.input_cost_per_token,
&[
(
TIERED_PRICING_THRESHOLD_128K_TOKENS,
pricing.input_cost_per_token_above_128k_tokens,
),
(
TIERED_PRICING_THRESHOLD_200K_TOKENS,
pricing.input_cost_per_token_above_200k_tokens,
),
(
TIERED_PRICING_THRESHOLD_256K_TOKENS,
pricing.input_cost_per_token_above_256k_tokens,
),
(
TIERED_PRICING_THRESHOLD_272K_TOKENS,
pricing.input_cost_per_token_above_272k_tokens,
),
],
);
let output_cost = tiered_cost(
output_clamped,
pricing.output_cost_per_token,
&[
(
TIERED_PRICING_THRESHOLD_128K_TOKENS,
pricing.output_cost_per_token_above_128k_tokens,
),
(
TIERED_PRICING_THRESHOLD_200K_TOKENS,
pricing.output_cost_per_token_above_200k_tokens,
),
(
TIERED_PRICING_THRESHOLD_256K_TOKENS,
pricing.output_cost_per_token_above_256k_tokens,
),
(
TIERED_PRICING_THRESHOLD_272K_TOKENS,
pricing.output_cost_per_token_above_272k_tokens,
),
],
);
let cache_read_cost = tiered_cost(
cache_read_clamped,
pricing.cache_read_input_token_cost,
&[
(
TIERED_PRICING_THRESHOLD_200K_TOKENS,
pricing.cache_read_input_token_cost_above_200k_tokens,
),
(
TIERED_PRICING_THRESHOLD_272K_TOKENS,
pricing.cache_read_input_token_cost_above_272k_tokens,
),
],
);
let cache_write_cost = tiered_cost(
cache_write_clamped,
pricing.cache_creation_input_token_cost,
&[(
TIERED_PRICING_THRESHOLD_200K_TOKENS,
pricing.cache_creation_input_token_cost_above_200k_tokens,
)],
);
input_cost + output_cost + cache_read_cost + cache_write_cost
}
fn extract_model_family(model_id: &str) -> String {
let lower = model_id.to_lowercase();
if lower.contains("gpt-5") {
return "gpt-5".into();
}
if lower.contains("gpt-4.1") {
return "gpt-4.1".into();
}
if lower.contains("gpt-4o") {
return "gpt-4o".into();
}
if lower.contains("gpt-4") {
return "gpt-4".into();
}
if lower.contains("o3") {
return "o3".into();
}
if lower.contains("o4") {
return "o4".into();
}
if lower.contains("opus") {
return "opus".into();
}
if lower.contains("sonnet") {
return "sonnet".into();
}
if lower.contains("haiku") {
return "haiku".into();
}
if lower.contains("claude") {
return "claude".into();
}
if lower.contains("gemini-3") {
return "gemini-3".into();
}
if lower.contains("gemini-2.5") {
return "gemini-2.5".into();
}
if lower.contains("gemini-2") {
return "gemini-2".into();
}
if lower.contains("gemini") {
return "gemini".into();
}
if lower.contains("llama") {
return "llama".into();
}
if lower.contains("mistral") {
return "mistral".into();
}
if lower.contains("deepseek") {
return "deepseek".into();
}
if lower.contains("qwen") {
return "qwen".into();
}
lower
.split(['-', '_', '.'])
.next()
.unwrap_or(&lower)
.to_string()
}
fn family_matches(key: &str, family: &str) -> bool {
if family.is_empty() {
return true;
}
key.contains(family)
}
fn contains_model_id(key: &str, model_id: &str) -> bool {
if let Some(pos) = key.find(model_id) {
let before_ok = pos == 0 || !key[..pos].chars().last().unwrap().is_alphanumeric();
let after_pos = pos + model_id.len();
let after_ok =
after_pos == key.len() || !key[after_pos..].chars().next().unwrap().is_alphanumeric();
before_ok && after_ok
} else {
false
}
}
fn normalize_model_name(model_id: &str) -> Option<String> {
let lower = model_id.to_lowercase();
let family = claude_family(&lower)?;
if let Some(model) = normalize_claude_family_minor(&lower) {
return Some(model);
}
if contains_delimited_modern_major_minor(&lower) {
return None;
}
if let Some(model) = normalize_claude_family_bare_major(&lower) {
return Some(model);
}
match family {
"opus" if contains_delimited_fragment(&lower, "4") => Some("claude-opus-4".into()),
"sonnet" => {
if contains_delimited_fragment(&lower, "4") {
Some("claude-sonnet-4".into())
} else if contains_delimited_fragment(&lower, "3.7")
|| contains_delimited_fragment(&lower, "3-7")
{
Some("claude-3-7-sonnet".into())
} else if contains_delimited_fragment(&lower, "3.5")
|| contains_delimited_fragment(&lower, "3-5")
{
Some("claude-3.5-sonnet".into())
} else {
None
}
}
"haiku" => {
if contains_delimited_fragment(&lower, "3.5")
|| contains_delimited_fragment(&lower, "3-5")
{
Some("claude-3.5-haiku".into())
} else {
None
}
}
_ => None,
}
}
const CLAUDE_FAMILY_TOKENS: &[&str] = &["opus", "sonnet", "haiku", "fable"];
fn claude_family(lower: &str) -> Option<&'static str> {
CLAUDE_FAMILY_TOKENS
.iter()
.copied()
.find(|family| lower.contains(family))
}
fn is_modern_claude_major(value: &str) -> bool {
value.len() == 1 && value.as_bytes()[0].is_ascii_digit() && value.as_bytes()[0] >= b'4'
}
fn normalize_claude_family_minor(lower: &str) -> Option<String> {
let parts: Vec<&str> = lower
.split(|ch: char| !ch.is_ascii_alphanumeric())
.filter(|part| !part.is_empty())
.collect();
for window in parts.windows(3) {
if CLAUDE_FAMILY_TOKENS.contains(&window[0])
&& is_modern_claude_major(window[1])
&& is_single_digit_minor(window[2])
{
return Some(format!("claude-{}-{}-{}", window[0], window[1], window[2]));
}
if is_modern_claude_major(window[0])
&& is_single_digit_minor(window[1])
&& CLAUDE_FAMILY_TOKENS.contains(&window[2])
{
return Some(format!("claude-{}-{}-{}", window[2], window[0], window[1]));
}
}
None
}
fn normalize_claude_family_bare_major(lower: &str) -> Option<String> {
let parts: Vec<&str> = lower
.split(|ch: char| !ch.is_ascii_alphanumeric())
.filter(|part| !part.is_empty())
.collect();
let all_digits = |part: &str| part.bytes().all(|b| b.is_ascii_digit());
for (idx, part) in parts.iter().enumerate() {
if !CLAUDE_FAMILY_TOKENS.contains(part) {
continue;
}
if let Some(major) = parts
.get(idx + 1)
.copied()
.filter(|p| is_modern_claude_major(p))
{
if parts.get(idx + 2).is_none_or(|next| !all_digits(next)) {
return Some(format!("claude-{part}-{major}"));
}
}
if idx >= 1
&& is_modern_claude_major(parts[idx - 1])
&& (idx < 2 || !all_digits(parts[idx - 2]))
{
return Some(format!("claude-{part}-{}", parts[idx - 1]));
}
}
None
}
fn contains_delimited_modern_major_minor(haystack: &str) -> bool {
('4'..='9').any(|major| contains_delimited_major_minor(haystack, major))
}
fn requested_claude_version(lower: &str) -> Option<String> {
if let Some(model) = normalize_claude_family_minor(lower) {
return Some(model);
}
normalize_claude_family_bare_major(lower).filter(|model| !model.ends_with("-4"))
}
fn resolves_unsafe_claude_version(
requested_family: Option<&'static str>,
requested_version: Option<&str>,
unparsed_modern_version: bool,
result: &LookupResult,
) -> bool {
let Some(requested_family) = requested_family else {
return false;
};
let matched_lower = result.matched_key.to_lowercase();
if claude_family(&matched_lower).is_some_and(|family| family != requested_family) {
return true;
}
let resolved = normalize_model_name(&matched_lower);
if let Some(requested_version) = requested_version {
return resolved.is_some_and(|resolved| resolved != requested_version);
}
unparsed_modern_version && resolved.is_some()
}
fn is_single_digit_minor(value: &str) -> bool {
value.len() == 1 && value.as_bytes()[0].is_ascii_digit() && value.as_bytes()[0] != b'0'
}
fn normalize_version_separator(model_id: &str) -> Option<String> {
let mut result = String::with_capacity(model_id.len());
let chars: Vec<char> = model_id.chars().collect();
let mut changed = false;
for i in 0..chars.len() {
if chars[i] == '-'
&& i > 0
&& i < chars.len() - 1
&& chars[i - 1].is_ascii_digit()
&& chars[i + 1].is_ascii_digit()
{
let is_multi_digit_before = i >= 2 && chars[i - 2].is_ascii_digit();
let is_multi_digit_after = i + 2 < chars.len() && chars[i + 2].is_ascii_digit();
let looks_like_date = is_multi_digit_before || is_multi_digit_after;
if looks_like_date {
result.push(chars[i]);
} else {
result.push('.');
changed = true;
}
} else {
result.push(chars[i]);
}
}
if changed {
Some(result)
} else {
None
}
}
fn strip_known_provider_prefix(model_id: &str) -> Option<&str> {
for prefix in PROVIDER_PREFIXES {
if let Some(stripped) = model_id.strip_prefix(prefix) {
if !stripped.is_empty() {
return Some(stripped);
}
}
}
None
}
fn strip_generic_provider_prefix(model_id: &str) -> Option<&str> {
let terminal = model_id.rsplit('/').next()?;
if terminal.is_empty() || terminal == model_id {
return None;
}
Some(terminal)
}
fn is_valid_price_value(value: f64) -> bool {
value.is_finite() && value >= 0.0
}
fn has_any_usable_pricing(pricing: &ModelPricing) -> bool {
[
pricing.input_cost_per_token,
pricing.output_cost_per_token,
pricing.cache_read_input_token_cost,
pricing.cache_creation_input_token_cost,
pricing.input_cost_per_token_above_128k_tokens,
pricing.input_cost_per_token_above_200k_tokens,
pricing.input_cost_per_token_above_256k_tokens,
pricing.input_cost_per_token_above_272k_tokens,
pricing.output_cost_per_token_above_128k_tokens,
pricing.output_cost_per_token_above_200k_tokens,
pricing.output_cost_per_token_above_256k_tokens,
pricing.output_cost_per_token_above_272k_tokens,
pricing.cache_read_input_token_cost_above_200k_tokens,
pricing.cache_read_input_token_cost_above_272k_tokens,
pricing.cache_creation_input_token_cost_above_200k_tokens,
]
.into_iter()
.any(|opt| opt.is_some_and(is_valid_price_value))
}
fn lookup_result_if_usable(
pricing: &ModelPricing,
source: &str,
matched_key: &str,
) -> Option<LookupResult> {
has_any_usable_pricing(pricing).then(|| LookupResult {
pricing: pricing.clone(),
source: source.into(),
matched_key: matched_key.into(),
})
}
fn has_any_valid_above_tier_value(pricing: &ModelPricing) -> bool {
[
pricing.input_cost_per_token_above_128k_tokens,
pricing.input_cost_per_token_above_200k_tokens,
pricing.input_cost_per_token_above_256k_tokens,
pricing.input_cost_per_token_above_272k_tokens,
pricing.output_cost_per_token_above_128k_tokens,
pricing.output_cost_per_token_above_200k_tokens,
pricing.output_cost_per_token_above_256k_tokens,
pricing.output_cost_per_token_above_272k_tokens,
pricing.cache_read_input_token_cost_above_200k_tokens,
pricing.cache_read_input_token_cost_above_272k_tokens,
pricing.cache_creation_input_token_cost_above_200k_tokens,
]
.into_iter()
.flatten()
.any(is_valid_price_value)
}
fn has_meaningful_tier_support(pricing: &ModelPricing) -> bool {
[
(
pricing.input_cost_per_token,
pricing.input_cost_per_token_above_128k_tokens,
),
(
pricing.input_cost_per_token,
pricing.input_cost_per_token_above_200k_tokens,
),
(
pricing.input_cost_per_token,
pricing.input_cost_per_token_above_256k_tokens,
),
(
pricing.input_cost_per_token,
pricing.input_cost_per_token_above_272k_tokens,
),
(
pricing.output_cost_per_token,
pricing.output_cost_per_token_above_128k_tokens,
),
(
pricing.output_cost_per_token,
pricing.output_cost_per_token_above_200k_tokens,
),
(
pricing.output_cost_per_token,
pricing.output_cost_per_token_above_256k_tokens,
),
(
pricing.output_cost_per_token,
pricing.output_cost_per_token_above_272k_tokens,
),
]
.into_iter()
.any(|(base, above)| match (base, above) {
(Some(base), Some(above)) => base.is_finite() && base >= 0.0 && is_valid_price_value(above),
_ => false,
})
}
fn contains_delimited_fragment(haystack: &str, fragment: &str) -> bool {
if fragment.is_empty() {
return false;
}
for (pos, _) in haystack.match_indices(fragment) {
let before_ok = pos == 0 || !haystack[..pos].chars().last().unwrap().is_alphanumeric();
let after_pos = pos + fragment.len();
let after_ok = after_pos == haystack.len()
|| !haystack[after_pos..]
.chars()
.next()
.unwrap()
.is_alphanumeric();
if before_ok && after_ok {
return true;
}
}
false
}
fn contains_delimited_major_minor(haystack: &str, major: char) -> bool {
for (pos, _) in haystack.match_indices(major) {
let before_ok = pos == 0 || !haystack[..pos].chars().last().unwrap().is_alphanumeric();
let after_pos = pos + major.len_utf8();
let mut after = haystack[after_pos..].chars();
let Some(separator) = after.next() else {
continue;
};
let Some(minor_start) = after.next() else {
continue;
};
if before_ok && matches!(separator, '.' | '-') && minor_start.is_ascii_digit() {
return true;
}
}
false
}
fn is_fuzzy_eligible(model_id: &str) -> bool {
if model_id.len() < MIN_FUZZY_MATCH_LEN {
return false;
}
!FUZZY_BLOCKLIST.contains(&model_id)
}
fn try_strip_unknown_suffix<F>(model_id: &str, do_lookup: F) -> Option<LookupResult>
where
F: Fn(&str) -> Option<LookupResult>,
{
if has_unrecognized_claude_four_minor(model_id) {
return None;
}
let parts: Vec<&str> = model_id.split('-').collect();
if parts.len() < 2 {
return None;
}
let max_strip = std::cmp::min(parts.len() - 1, MAX_SUFFIX_STRIP_SEGMENTS);
for strip in 1..=max_strip {
let candidate: String = parts[..parts.len() - strip].join("-");
if candidate.len() >= MIN_MODEL_NAME_LEN {
if strips_claude_numeric_minor(&candidate, parts[parts.len() - strip]) {
continue;
}
if let Some(result) = do_lookup(&candidate) {
return Some(result);
}
}
}
None
}
fn strips_claude_numeric_minor(candidate: &str, first_stripped_segment: &str) -> bool {
if !is_version_segment(first_stripped_segment) {
return false;
}
let claude_branded = candidate.contains("claude")
|| candidate.contains("opus")
|| candidate.contains("sonnet")
|| candidate.contains("haiku");
if !claude_branded {
return false;
}
contains_delimited_fragment(candidate, "4") || !candidate.bytes().any(|b| b.is_ascii_digit())
}
fn is_version_segment(segment: &str) -> bool {
let bytes = segment.as_bytes();
if bytes.is_empty() || !bytes[0].is_ascii_digit() || !bytes[bytes.len() - 1].is_ascii_digit() {
return false;
}
let mut seen_dot = false;
for &byte in bytes {
match byte {
b'0'..=b'9' => {}
b'.' if !seen_dot => seen_dot = true,
_ => return false,
}
}
true
}
fn has_unrecognized_claude_four_minor(model_id: &str) -> bool {
(model_id.contains("claude")
|| model_id.contains("opus")
|| model_id.contains("sonnet")
|| model_id.contains("haiku"))
&& contains_delimited_major_minor(model_id, '4')
&& !contains_delimited_fragment(model_id, "4.5")
&& !contains_delimited_fragment(model_id, "4-5")
&& !contains_delimited_fragment(model_id, "4.6")
&& !contains_delimited_fragment(model_id, "4-6")
&& !contains_delimited_fragment(model_id, "4.7")
&& !contains_delimited_fragment(model_id, "4-7")
}
fn try_strip_unknown_prefix<F>(model_id: &str, do_lookup: F) -> Option<LookupResult>
where
F: Fn(&str) -> Option<LookupResult>,
{
let parts: Vec<&str> = model_id.split('-').collect();
if parts.len() < 2 {
return None;
}
let max_skip = std::cmp::min(parts.len() - 1, MAX_PREFIX_STRIP_SEGMENTS);
for skip in 1..=max_skip {
let candidate: String = parts[skip..].join("-");
if candidate.len() >= MIN_MODEL_NAME_LEN {
if let Some(result) = do_lookup(&candidate) {
return Some(result);
}
if let Some(result) = try_strip_unknown_suffix(&candidate, &do_lookup) {
return Some(result);
}
}
}
None
}
fn prefers_model_part_key(candidate: &str, existing: &str) -> bool {
let candidate_lower = candidate.to_lowercase();
let existing_lower = existing.to_lowercase();
let is_anthropic = |key: &str| key.split('/').next() == Some("anthropic");
match (
is_anthropic(&candidate_lower),
is_anthropic(&existing_lower),
) {
(true, false) => true,
(false, true) => false,
_ => (candidate_lower.len(), candidate_lower) < (existing_lower.len(), existing_lower),
}
}
fn is_original_provider(key: &str) -> bool {
let lower = key.to_lowercase();
ORIGINAL_PROVIDER_PREFIXES
.iter()
.any(|prefix| lower.starts_with(prefix))
}
fn is_reseller_provider(key: &str) -> bool {
let lower = key.to_lowercase();
RESELLER_PROVIDER_PREFIXES
.iter()
.any(|prefix| lower.starts_with(prefix))
}
fn select_best_match(
matches: &[&String],
dataset: &HashMap<String, ModelPricing>,
source: &str,
provider_id: Option<&str>,
) -> Option<LookupResult> {
if matches.is_empty() {
return None;
}
let hint_tags: Vec<String> = provider_id
.map(provider_identity::provider_tags)
.unwrap_or_default();
let provider_matches: Vec<&String> = matches
.iter()
.copied()
.filter(|key| provider_identity::matches_provider_hint_with_tags(key, &hint_tags))
.collect();
let preferred_matches = if provider_matches.is_empty() {
matches
} else {
provider_matches.as_slice()
};
let preferred_with_pricing: Vec<&String> = preferred_matches
.iter()
.copied()
.filter(|k| dataset.get(k.as_str()).is_some_and(has_any_usable_pricing))
.collect();
let effective_matches: Vec<&String> =
if preferred_with_pricing.is_empty() && !provider_matches.is_empty() {
matches
.iter()
.copied()
.filter(|k| dataset.get(k.as_str()).is_some_and(has_any_usable_pricing))
.collect()
} else {
preferred_with_pricing
};
if effective_matches.is_empty() {
return None;
}
let effective_matches = effective_matches.as_slice();
let hint_is_reseller = provider_id.is_some_and(is_reseller_provider);
let pick = |candidates: &[&String], prefer_reseller: bool| -> Option<LookupResult> {
let key = if prefer_reseller {
candidates
.iter()
.find(|k| is_reseller_provider(k))
.or_else(|| candidates.first())
} else {
candidates
.iter()
.find(|k| is_original_provider(k))
.or_else(|| candidates.iter().find(|k| !is_reseller_provider(k)))
.or_else(|| candidates.first())
};
key.and_then(|k| {
dataset.get(k.as_str()).map(|pricing| LookupResult {
pricing: pricing.clone(),
source: source.into(),
matched_key: (*k).clone(),
})
})
};
pick(effective_matches, hint_is_reseller)
}
fn model_prefix_matches_provider(model_id: &str, provider_id: Option<&str>) -> bool {
let Some(hint) = provider_id else {
return true;
};
let Some(prefix) = model_id.split('/').next() else {
return false;
};
let prefix_tag = provider_identity::canonical_provider(prefix);
let hint_primary = provider_identity::canonical_provider(hint);
match (prefix_tag, hint_primary) {
(Some(p), Some(h)) => p == h,
_ => false,
}
}
fn parse_provider_scoped_model_path(model_id: &str) -> Option<ProviderScopedModelPath<'_>> {
let rest = model_id.strip_prefix("accounts/")?;
let (provider, rest) = rest.split_once('/')?;
let (scope, terminal_model_id) = rest.split_once('/')?;
if provider.is_empty() || terminal_model_id.is_empty() {
return None;
}
match scope {
"models" | "routers" => Some(ProviderScopedModelPath {
provider,
terminal_model_id,
}),
_ => None,
}
}
fn provider_hint_matches_scoped_provider(provider_id: Option<&str>, scoped_provider: &str) -> bool {
let Some(provider_id) = provider_id else {
return true;
};
let scoped_tags = provider_identity::provider_tags(scoped_provider);
let hint_tags = provider_identity::provider_tags(provider_id);
!scoped_tags.is_empty()
&& scoped_tags
.iter()
.any(|scoped| hint_tags.iter().any(|hint| hint == scoped))
}
fn provider_prefix_matches_scoped_provider(prefix: &str, scoped_tags: &[String]) -> bool {
if scoped_tags.is_empty() {
return false;
}
provider_identity::provider_tags(prefix.trim_end_matches('/'))
.iter()
.any(|prefix_tag| scoped_tags.iter().any(|scoped| scoped == prefix_tag))
}
fn normalize_provider_hint(provider_id: Option<&str>) -> Option<&str> {
provider_id
.map(str::trim)
.filter(|s| !s.is_empty() && !s.eq_ignore_ascii_case("unknown"))
}
fn build_lookup_cache_key(model_id: &str, provider_id: Option<&str>) -> String {
match provider_id {
Some(provider) if !provider.trim().is_empty() => {
format!("{}|{}", provider.to_lowercase(), model_id.to_lowercase())
}
_ => model_id.to_lowercase(),
}
}
fn model_part_matches_exact(model_part: &str, model_id: &str) -> bool {
if model_part == model_id {
return true;
}
let mut suffix = model_part;
while let Some((_, rest)) = suffix.split_once('.') {
if rest == model_id {
return true;
}
suffix = rest;
}
false
}
fn choose_best_source_result(
litellm_result: Option<LookupResult>,
openrouter_result: Option<LookupResult>,
provider_id: Option<&str>,
) -> Option<LookupResult> {
match (&litellm_result, &openrouter_result) {
(Some(l), Some(o)) => {
let l_matches_provider =
provider_identity::matches_provider_hint(&l.matched_key, provider_id);
let o_matches_provider =
provider_identity::matches_provider_hint(&o.matched_key, provider_id);
if l_matches_provider && !o_matches_provider {
return litellm_result;
}
if o_matches_provider && !l_matches_provider {
return openrouter_result;
}
let l_is_original = is_original_provider(&l.matched_key);
let o_is_original = is_original_provider(&o.matched_key);
let l_is_reseller = is_reseller_provider(&l.matched_key);
let o_is_reseller = is_reseller_provider(&o.matched_key);
if o_is_original && !l_is_original {
return openrouter_result;
}
if l_is_original && !o_is_original {
return litellm_result;
}
if !l_is_reseller && o_is_reseller {
return litellm_result;
}
if !o_is_reseller && l_is_reseller {
return openrouter_result;
}
litellm_result
}
(Some(_), None) => litellm_result,
(None, Some(_)) => openrouter_result,
(None, None) => None,
}
}
fn exact_match_with_provider_prefixes(
model_id: &str,
provider_id: Option<&str>,
key_parts: &[KeyModelPart],
dataset: &HashMap<String, ModelPricing>,
source: &str,
) -> Option<LookupResult> {
let provider_id = provider_id?;
let hint_tags = provider_identity::provider_tags(provider_id);
let matches: Vec<&String> = key_parts
.iter()
.filter(|kp| {
model_part_matches_exact(&kp.lower_model_part, model_id)
&& provider_identity::matches_provider_hint_with_tags(&kp.key, &hint_tags)
})
.map(|kp| &kp.key)
.collect();
if matches.is_empty() {
return None;
}
select_best_match(&matches, dataset, source, Some(provider_id))
}
#[cfg(test)]
mod tests {
use super::*;
fn mock_litellm() -> HashMap<String, ModelPricing> {
let mut m = HashMap::new();
m.insert(
"gpt-4o".into(),
ModelPricing {
input_cost_per_token: Some(0.0000025),
output_cost_per_token: Some(0.00001),
cache_read_input_token_cost: Some(0.00000125),
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"gpt-4o-mini".into(),
ModelPricing {
input_cost_per_token: Some(0.00000015),
output_cost_per_token: Some(0.0000006),
cache_read_input_token_cost: Some(0.000000075),
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"gpt-4-turbo".into(),
ModelPricing {
input_cost_per_token: Some(0.00001),
output_cost_per_token: Some(0.00003),
cache_read_input_token_cost: None,
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"gpt-5.2".into(),
ModelPricing {
input_cost_per_token: Some(0.00000175),
output_cost_per_token: Some(0.000014),
cache_read_input_token_cost: Some(1.75e-7),
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"gpt-5.5".into(),
ModelPricing {
input_cost_per_token: Some(0.000005),
input_cost_per_token_above_272k_tokens: Some(0.000010),
output_cost_per_token: Some(0.000030),
output_cost_per_token_above_272k_tokens: Some(0.000045),
cache_read_input_token_cost: Some(0.0000005),
cache_read_input_token_cost_above_272k_tokens: Some(0.000001),
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"gpt-5.1".into(),
ModelPricing {
input_cost_per_token: Some(0.00000125),
output_cost_per_token: Some(0.00001),
cache_read_input_token_cost: Some(1.25e-7),
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"gpt-5.1-codex".into(),
ModelPricing {
input_cost_per_token: Some(0.00000125),
output_cost_per_token: Some(0.00001),
cache_read_input_token_cost: Some(1.25e-7),
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"gpt-5.1-codex-max".into(),
ModelPricing {
input_cost_per_token: Some(0.00000125),
output_cost_per_token: Some(0.00001),
cache_read_input_token_cost: Some(1.25e-7),
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"gpt-5".into(),
ModelPricing {
input_cost_per_token: Some(0.00000125),
output_cost_per_token: Some(0.00001),
cache_read_input_token_cost: Some(1.25e-7),
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"gpt-5-codex".into(),
ModelPricing {
input_cost_per_token: Some(0.00000125),
output_cost_per_token: Some(0.00001),
cache_read_input_token_cost: Some(1.25e-7),
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"gpt-5-nano".into(),
ModelPricing {
input_cost_per_token: Some(5e-8),
output_cost_per_token: Some(4e-7),
cache_read_input_token_cost: Some(5e-9),
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"claude-3-5-sonnet-20241022".into(),
ModelPricing {
input_cost_per_token: Some(0.000003),
output_cost_per_token: Some(0.000015),
cache_read_input_token_cost: Some(0.0000003),
cache_creation_input_token_cost: Some(0.00000375),
..Default::default()
},
);
m.insert(
"claude-sonnet-4-5".into(),
ModelPricing {
input_cost_per_token: Some(0.000003),
output_cost_per_token: Some(0.000015),
cache_read_input_token_cost: Some(3e-7),
cache_creation_input_token_cost: Some(0.00000375),
..Default::default()
},
);
m.insert(
"claude-haiku-4-5".into(),
ModelPricing {
input_cost_per_token: Some(0.000001),
output_cost_per_token: Some(0.000005),
cache_read_input_token_cost: Some(1e-7),
cache_creation_input_token_cost: Some(0.00000125),
..Default::default()
},
);
m.insert(
"bedrock/us.anthropic.claude-3-5-haiku-20241022-v1:0".into(),
ModelPricing {
input_cost_per_token: Some(8e-7),
output_cost_per_token: Some(0.000004),
cache_read_input_token_cost: Some(8e-8),
cache_creation_input_token_cost: Some(0.000001),
..Default::default()
},
);
m.insert(
"claude-opus-4-5".into(),
ModelPricing {
input_cost_per_token: Some(0.000005),
output_cost_per_token: Some(0.000025),
cache_read_input_token_cost: Some(5e-7),
cache_creation_input_token_cost: Some(0.00000625),
..Default::default()
},
);
m.insert(
"claude-opus-4-1".into(),
ModelPricing {
input_cost_per_token: Some(0.000015),
output_cost_per_token: Some(0.000075),
cache_read_input_token_cost: Some(0.0000015),
cache_creation_input_token_cost: Some(0.00001875),
..Default::default()
},
);
m.insert(
"openrouter/google/gemini-3-pro-preview".into(),
ModelPricing {
input_cost_per_token: Some(0.000002),
output_cost_per_token: Some(0.000012),
cache_read_input_token_cost: Some(2e-7),
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"vertex_ai/gemini-3-flash-preview".into(),
ModelPricing {
input_cost_per_token: Some(5e-7),
output_cost_per_token: Some(0.000003),
cache_read_input_token_cost: Some(5e-8),
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"xai/grok-code-fast-1-0825".into(),
ModelPricing {
input_cost_per_token: Some(2e-7),
output_cost_per_token: Some(0.0000015),
cache_read_input_token_cost: Some(2e-8),
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"azure_ai/grok-code-fast-1".into(),
ModelPricing {
input_cost_per_token: Some(0.0000035),
output_cost_per_token: Some(0.0000175),
cache_read_input_token_cost: None,
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"bedrock/anthropic.claude-sonnet-4".into(),
ModelPricing {
input_cost_per_token: Some(0.000003),
output_cost_per_token: Some(0.000015),
cache_read_input_token_cost: Some(3e-7),
cache_creation_input_token_cost: Some(0.00000375),
..Default::default()
},
);
m.insert(
"vertex_ai/gemini-2.5-pro".into(),
ModelPricing {
input_cost_per_token: Some(0.00000125),
output_cost_per_token: Some(0.000005),
cache_read_input_token_cost: None,
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"google/gemini-2.5-pro".into(),
ModelPricing {
input_cost_per_token: Some(0.00000125),
output_cost_per_token: Some(0.000005),
cache_read_input_token_cost: None,
cache_creation_input_token_cost: None,
..Default::default()
},
);
m
}
fn mock_openrouter() -> HashMap<String, ModelPricing> {
let mut m = HashMap::new();
m.insert(
"openai/gpt-4o".into(),
ModelPricing {
input_cost_per_token: Some(0.0000025),
output_cost_per_token: Some(0.00001),
cache_read_input_token_cost: Some(0.00000125),
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"anthropic/claude-sonnet-4".into(),
ModelPricing {
input_cost_per_token: Some(0.000003),
output_cost_per_token: Some(0.000015),
cache_read_input_token_cost: Some(3e-7),
cache_creation_input_token_cost: Some(0.00000375),
..Default::default()
},
);
m.insert(
"anthropic/claude-opus-4-5".into(),
ModelPricing {
input_cost_per_token: Some(0.000005),
output_cost_per_token: Some(0.000025),
cache_read_input_token_cost: Some(0.0000005),
cache_creation_input_token_cost: Some(0.00000625),
..Default::default()
},
);
m.insert(
"anthropic/claude-3.5-haiku".into(),
ModelPricing {
input_cost_per_token: Some(8e-7),
output_cost_per_token: Some(0.000004),
cache_read_input_token_cost: Some(8e-8),
cache_creation_input_token_cost: Some(0.000001),
..Default::default()
},
);
m.insert(
"z-ai/glm-4.7".into(),
ModelPricing {
input_cost_per_token: Some(4e-7),
output_cost_per_token: Some(0.0000015),
cache_read_input_token_cost: None,
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"z-ai/glm-4.6".into(),
ModelPricing {
input_cost_per_token: Some(3.9e-7),
output_cost_per_token: Some(0.0000019),
cache_read_input_token_cost: None,
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"moonshotai/kimi-k2".into(),
ModelPricing {
input_cost_per_token: Some(4.56e-7),
output_cost_per_token: Some(0.00000184),
cache_read_input_token_cost: None,
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"moonshotai/kimi-k2.5".into(),
ModelPricing {
input_cost_per_token: Some(4.5e-7),
output_cost_per_token: Some(0.0000025),
cache_read_input_token_cost: None,
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"moonshotai/kimi-k2.6".into(),
ModelPricing {
input_cost_per_token: Some(9.5e-7),
output_cost_per_token: Some(0.000004),
cache_read_input_token_cost: None,
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"moonshotai/kimi-k2-thinking".into(),
ModelPricing {
input_cost_per_token: Some(4e-7),
output_cost_per_token: Some(0.00000175),
cache_read_input_token_cost: None,
cache_creation_input_token_cost: None,
..Default::default()
},
);
m.insert(
"qwen/qwen3-coder".into(),
ModelPricing {
input_cost_per_token: Some(2.2e-7),
output_cost_per_token: Some(9.5e-7),
cache_read_input_token_cost: None,
cache_creation_input_token_cost: None,
..Default::default()
},
);
m
}
fn create_lookup() -> PricingLookup {
PricingLookup::new(mock_litellm(), mock_openrouter(), HashMap::new())
}
#[test]
fn test_opencode_zen_gpt_5_2() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-5.2").unwrap();
assert_eq!(result.matched_key, "gpt-5.2");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_opencode_zen_gpt_5_1() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-5.1").unwrap();
assert_eq!(result.matched_key, "gpt-5.1");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_opencode_zen_gpt_5_1_codex() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-5.1-codex").unwrap();
assert_eq!(result.matched_key, "gpt-5.1-codex");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_opencode_zen_gpt_5_1_codex_max() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-5.1-codex-max").unwrap();
assert_eq!(result.matched_key, "gpt-5.1-codex-max");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_opencode_zen_gpt_5() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-5").unwrap();
assert_eq!(result.matched_key, "gpt-5");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_opencode_zen_gpt_5_codex() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-5-codex").unwrap();
assert_eq!(result.matched_key, "gpt-5-codex");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_opencode_zen_gpt_5_nano() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-5-nano").unwrap();
assert_eq!(result.matched_key, "gpt-5-nano");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_opencode_zen_claude_sonnet_4_5() {
let lookup = create_lookup();
let result = lookup.lookup("claude-sonnet-4-5").unwrap();
assert_eq!(result.matched_key, "claude-sonnet-4-5");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_opencode_zen_claude_sonnet_4() {
let lookup = create_lookup();
let result = lookup.lookup("claude-sonnet-4").unwrap();
assert_eq!(result.matched_key, "anthropic/claude-sonnet-4");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_opencode_zen_claude_haiku_4_5() {
let lookup = create_lookup();
let result = lookup.lookup("claude-haiku-4-5").unwrap();
assert_eq!(result.matched_key, "claude-haiku-4-5");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_opencode_zen_claude_3_5_haiku() {
let lookup = create_lookup();
let result = lookup.lookup("claude-3-5-haiku").unwrap();
assert_eq!(result.matched_key, "anthropic/claude-3.5-haiku");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_opencode_zen_claude_3_5_haiku_with_dot() {
let lookup = create_lookup();
let result = lookup.lookup("claude-3.5-haiku").unwrap();
assert_eq!(result.matched_key, "anthropic/claude-3.5-haiku");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_opencode_zen_claude_opus_4_5() {
let lookup = create_lookup();
let result = lookup.lookup("claude-opus-4-5").unwrap();
assert_eq!(result.matched_key, "claude-opus-4-5");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_opencode_zen_claude_opus_4_1() {
let lookup = create_lookup();
let result = lookup.lookup("claude-opus-4-1").unwrap();
assert_eq!(result.matched_key, "claude-opus-4-1");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_opencode_zen_glm_4_7_free() {
let lookup = create_lookup();
let result = lookup.lookup("glm-4.7-free").unwrap();
assert_eq!(result.matched_key, "z-ai/glm-4.7");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_opencode_zen_glm_4_6() {
let lookup = create_lookup();
let result = lookup.lookup("glm-4.6").unwrap();
assert_eq!(result.matched_key, "z-ai/glm-4.6");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_opencode_zen_glm_4_7_with_hyphen() {
let lookup = create_lookup();
let result = lookup.lookup("glm-4-7").unwrap();
assert_eq!(result.matched_key, "z-ai/glm-4.7");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_opencode_zen_glm_4_6_with_hyphen() {
let lookup = create_lookup();
let result = lookup.lookup("glm-4-6").unwrap();
assert_eq!(result.matched_key, "z-ai/glm-4.6");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_opencode_zen_big_pickle() {
let lookup = create_lookup();
let result = lookup.lookup("big-pickle").unwrap();
assert_eq!(result.matched_key, "z-ai/glm-4.7");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_opencode_zen_gemini_3_pro() {
let lookup = create_lookup();
let result = lookup.lookup("gemini-3-pro").unwrap();
assert_eq!(result.matched_key, "openrouter/google/gemini-3-pro-preview");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_opencode_zen_gemini_3_flash() {
let lookup = create_lookup();
let result = lookup.lookup("gemini-3-flash").unwrap();
assert_eq!(result.matched_key, "vertex_ai/gemini-3-flash-preview");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn antigravity_model_aliases_reach_priced_catalog_entries() {
let mut litellm = mock_litellm();
litellm.insert(
"gemini-3.1-pro".into(),
ModelPricing {
input_cost_per_token: Some(0.000002),
output_cost_per_token: Some(0.000012),
..Default::default()
},
);
let mut models_dev = HashMap::new();
models_dev.insert(
"google/gemini-3.5-flash".into(),
ModelPricing {
input_cost_per_token: Some(0.0000015),
output_cost_per_token: Some(0.000009),
cache_read_input_token_cost: Some(0.00000015),
..Default::default()
},
);
let lookup = PricingLookup::new_with_models_dev(
litellm,
mock_openrouter(),
HashMap::new(),
HashMap::new(),
models_dev,
);
let cases = [
("MODEL_PLACEHOLDER_M16", "gemini-3.1-pro", "LiteLLM"),
(
"MODEL_PLACEHOLDER_M84",
"vertex_ai/gemini-3-flash-preview",
"LiteLLM",
),
(
"MODEL_PLACEHOLDER_M133",
"google/gemini-3.5-flash",
"Models.dev",
),
(
"gemini-3-flash-agent",
"google/gemini-3.5-flash",
"Models.dev",
),
("gemini-3-flash-b", "google/gemini-3.5-flash", "Models.dev"),
(
"gemini-3-flash-a",
"google/gemini-3.5-flash",
"Models.dev",
),
(
"MODEL_PLACEHOLDER_M187",
"google/gemini-3.5-flash",
"Models.dev",
),
(
"MODEL_PLACEHOLDER_M20",
"google/gemini-3.5-flash",
"Models.dev",
),
];
for (raw, expected_key, expected_source) in cases {
let result = lookup
.lookup(raw)
.unwrap_or_else(|| panic!("unpriced alias: {raw}"));
assert_eq!(result.matched_key, expected_key, "raw model: {raw}");
assert_eq!(result.source, expected_source, "raw model: {raw}");
}
let cost = lookup.calculate_cost("gemini-3-flash-agent", 1_000_000, 100_000, 50_000, 0, 0);
assert!((cost - 2.4075).abs() < 1e-10);
}
#[test]
fn test_opencode_zen_kimi_k2() {
let lookup = create_lookup();
let result = lookup.lookup("kimi-k2").unwrap();
assert_eq!(result.matched_key, "moonshotai/kimi-k2");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_opencode_zen_kimi_k2_thinking() {
let lookup = create_lookup();
let result = lookup.lookup("kimi-k2-thinking").unwrap();
assert_eq!(result.matched_key, "moonshotai/kimi-k2-thinking");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_opencode_zen_kimi_k2_5() {
let lookup = create_lookup();
let result = lookup.lookup("kimi-k2.5").unwrap();
assert_eq!(result.matched_key, "moonshotai/kimi-k2.5");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_opencode_zen_kimi_k2_5_free() {
let lookup = create_lookup();
let result = lookup.lookup("kimi-k2.5-free").unwrap();
assert_eq!(result.matched_key, "moonshotai/kimi-k2.5");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_opencode_zen_kimi_k2_6_aliases() {
let lookup = create_lookup();
for model_id in ["k2p6", "k2-p6", "kimi-k2p6", "Kimi-K2.6"] {
let result = lookup.lookup(model_id).unwrap();
assert_eq!(result.matched_key, "moonshotai/kimi-k2.6");
assert_eq!(result.source, "OpenRouter");
assert_eq!(result.pricing.input_cost_per_token, Some(9.5e-7));
assert_eq!(result.pricing.output_cost_per_token, Some(0.000004));
}
}
#[test]
fn test_opencode_zen_kimi_k2_6_provider_hint_from_kimi_for_coding() {
let lookup = create_lookup();
let result = lookup
.lookup_with_provider("k2p6", Some("kimi-for-coding"))
.unwrap();
assert_eq!(result.matched_key, "moonshotai/kimi-k2.6");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_opencode_zen_kimi_k2_5_aliases_unchanged() {
let lookup = create_lookup();
let raw_k2p5 = lookup.lookup("k2p5").unwrap();
assert_eq!(raw_k2p5.matched_key, "moonshotai/kimi-k2-thinking");
let dotted = lookup.lookup("kimi-k2.5").unwrap();
assert_eq!(dotted.matched_key, "moonshotai/kimi-k2.5");
}
#[test]
fn test_opencode_zen_qwen3_coder() {
let lookup = create_lookup();
let result = lookup.lookup("qwen3-coder").unwrap();
assert_eq!(result.matched_key, "qwen/qwen3-coder");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_opencode_zen_grok_code() {
let lookup = create_lookup();
let result = lookup.lookup("grok-code").unwrap();
assert_eq!(result.matched_key, "xai/grok-code-fast-1-0825");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_provider_hint_prefers_matching_pricing_source() {
let lookup = create_lookup();
let result = lookup
.lookup_with_provider("grok-code", Some("azure"))
.unwrap();
assert_eq!(result.matched_key, "azure_ai/grok-code-fast-1");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_provider_hint_matches_nested_reseller_exact_key() {
let mut litellm = HashMap::new();
litellm.insert(
"gpt-4".into(),
ModelPricing {
input_cost_per_token: Some(0.001),
output_cost_per_token: Some(0.002),
..Default::default()
},
);
litellm.insert(
"azure/openai/gpt-4".into(),
ModelPricing {
input_cost_per_token: Some(0.01),
output_cost_per_token: Some(0.02),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup.lookup_with_provider("gpt-4", Some("azure")).unwrap();
assert_eq!(result.matched_key, "azure/openai/gpt-4");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn fuzzy_match_does_not_resolve_generic_model_token() {
let mut litellm = HashMap::new();
litellm.insert(
"azure_ai/model_router".into(),
ModelPricing {
input_cost_per_token: Some(1.4e-7),
output_cost_per_token: Some(0.0),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
assert!(lookup.lookup("model").is_none());
assert!(lookup.lookup("model-zero-usage-v1").is_none());
assert!(lookup.lookup("model-nonzero-usage-v1").is_none());
assert!(lookup.lookup("test-model").is_none());
let mut litellm2 = HashMap::new();
litellm2.insert(
"azure/model-router".into(),
ModelPricing {
input_cost_per_token: Some(1.4e-7),
output_cost_per_token: Some(0.0),
..Default::default()
},
);
let lookup2 = PricingLookup::new(litellm2, HashMap::new(), HashMap::new());
assert_eq!(
lookup2.lookup("model-router").unwrap().matched_key,
"azure/model-router"
);
}
#[test]
fn test_provider_hint_normalizes_openai_codex_alias() {
let mut litellm = HashMap::new();
litellm.insert(
"openai/gpt-5.2-preview".into(),
ModelPricing {
input_cost_per_token: Some(1.0),
..Default::default()
},
);
litellm.insert(
"google/gpt-5.2-preview-max".into(),
ModelPricing {
input_cost_per_token: Some(2.0),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup
.lookup_with_provider("gpt-5.2", Some("openai-codex"))
.unwrap();
assert_eq!(result.matched_key, "openai/gpt-5.2-preview");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_provider_hint_matches_nested_google_segment_during_fuzzy_lookup() {
let mut litellm = HashMap::new();
litellm.insert(
"openrouter/google/gemini-3-pro-preview".into(),
ModelPricing {
input_cost_per_token: Some(1.0),
..Default::default()
},
);
litellm.insert(
"vertex_ai/gemini-3-pro-preview-max".into(),
ModelPricing {
input_cost_per_token: Some(2.0),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup
.lookup_with_provider("gemini-3-pro", Some("google"))
.unwrap();
assert_eq!(result.matched_key, "openrouter/google/gemini-3-pro-preview");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_cross_source_fuzzy_provider_hint_wins_over_original_provider_fallback() {
let mut litellm = HashMap::new();
litellm.insert(
"fireworks_ai/deepseek-v3-0324".into(),
ModelPricing {
input_cost_per_token: Some(0.001),
..Default::default()
},
);
let mut openrouter = HashMap::new();
openrouter.insert(
"deepseek/deepseek-v3-0324".into(),
ModelPricing {
input_cost_per_token: Some(0.002),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, openrouter, HashMap::new());
let result = lookup
.lookup_with_provider("deepseek-v3", Some("fireworks"))
.unwrap();
assert_eq!(result.matched_key, "fireworks_ai/deepseek-v3-0324");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_provider_scoped_path_does_not_strip_into_wrong_fireworks_model() {
let mut litellm = HashMap::new();
litellm.insert(
"fireworks_ai/accounts/fireworks/models/deepseek-r1-0528-distill-qwen3-8b".into(),
ModelPricing {
input_cost_per_token: Some(0.0000002),
output_cost_per_token: Some(0.0000002),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
assert!(
lookup
.lookup("accounts/fireworks/models/deepseek-v4-pro")
.is_none(),
"provider-scoped model paths should not be shortened into unrelated fuzzy matches"
);
}
#[test]
fn test_provider_scoped_path_matches_exact_litellm_reseller_key() {
let mut litellm = HashMap::new();
litellm.insert(
"fireworks_ai/accounts/fireworks/models/deepseek-v4-pro".into(),
ModelPricing {
input_cost_per_token: Some(0.0000003),
output_cost_per_token: Some(0.0000004),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup
.lookup("accounts/fireworks/models/deepseek-v4-pro")
.unwrap();
assert_eq!(
result.matched_key,
"fireworks_ai/accounts/fireworks/models/deepseek-v4-pro"
);
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_provider_scoped_path_matches_exact_terminal_provider_key() {
let mut litellm = HashMap::new();
litellm.insert(
"fireworks_ai/deepseek-v4-pro".into(),
ModelPricing {
input_cost_per_token: Some(0.0000003),
output_cost_per_token: Some(0.0000004),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup
.lookup("accounts/fireworks/models/deepseek-v4-pro")
.unwrap();
assert_eq!(result.matched_key, "fireworks_ai/deepseek-v4-pro");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_provider_scoped_path_does_not_use_upstream_openrouter_exact() {
let mut openrouter = HashMap::new();
openrouter.insert(
"deepseek/deepseek-v4-pro".into(),
ModelPricing {
input_cost_per_token: Some(0.000001),
output_cost_per_token: Some(0.000002),
..Default::default()
},
);
let lookup = PricingLookup::new(HashMap::new(), openrouter, HashMap::new());
assert!(
lookup
.lookup("accounts/fireworks/models/deepseek-v4-pro")
.is_none(),
"Fireworks-scoped usage should not be priced with upstream DeepSeek rates"
);
}
#[test]
fn test_exact_match_litellm() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-4o").unwrap();
assert_eq!(result.matched_key, "gpt-4o");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_exact_match_gpt_5_5_litellm() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-5.5").unwrap();
assert_eq!(result.matched_key, "gpt-5.5");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_exact_match_openrouter() {
let lookup = create_lookup();
let result = lookup.lookup("z-ai/glm-4.7").unwrap();
assert_eq!(result.matched_key, "z-ai/glm-4.7");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_openrouter_model_part_match() {
let lookup = create_lookup();
let result = lookup.lookup("glm-4.7").unwrap();
assert_eq!(result.matched_key, "z-ai/glm-4.7");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_tier_suffix_low() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-5.1-codex-low").unwrap();
assert_eq!(result.matched_key, "gpt-5.1-codex");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_tier_suffix_high() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-4o-high").unwrap();
assert_eq!(result.matched_key, "gpt-4o");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_tier_suffix_free() {
let lookup = create_lookup();
let result = lookup.lookup("glm-4.7-free").unwrap();
assert_eq!(result.matched_key, "z-ai/glm-4.7");
assert_eq!(result.source, "OpenRouter");
}
#[test]
fn test_tier_suffix_xhigh() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-5.2-xhigh").unwrap();
assert_eq!(result.matched_key, "gpt-5.2");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_tier_suffix_xhigh_gpt_5_5() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-5.5-xhigh").unwrap();
assert_eq!(result.matched_key, "gpt-5.5");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_tier_suffix_xhigh_codex_max() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-5.1-codex-max-xhigh").unwrap();
assert_eq!(result.matched_key, "gpt-5.1-codex-max");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_parenthesized_reasoning_tier_gpt_levels() {
let lookup = create_lookup();
for tier in ["minimal", "low", "medium", "high", "xhigh", "auto", "none"] {
let id = format!("gpt-5.2({tier})");
let result = lookup.lookup(&id).unwrap_or_else(|| panic!("{id} miss"));
assert_eq!(result.matched_key, "gpt-5.2", "{id}");
assert_eq!(result.source, "LiteLLM", "{id}");
}
}
#[test]
fn test_parenthesized_reasoning_tier_claude_and_gemini() {
let lookup = create_lookup();
let claude = lookup.lookup("claude-sonnet-4-5(high)").unwrap();
assert_eq!(claude.matched_key, "claude-sonnet-4-5");
assert_eq!(claude.source, "LiteLLM");
let claude_dot = lookup.lookup("claude-sonnet-4.5(none)").unwrap();
assert_eq!(claude_dot.matched_key, "claude-sonnet-4-5");
let gemini = lookup.lookup("gemini-3-pro(auto)").unwrap();
assert_eq!(gemini.matched_key, "openrouter/google/gemini-3-pro-preview");
}
#[test]
fn test_parenthesized_reasoning_tier_with_routing_prefix() {
let lookup = create_lookup();
let prefixed = lookup.lookup("myproxy-gpt-5.2(xhigh)").unwrap();
assert_eq!(prefixed.matched_key, "gpt-5.2");
let antigravity = lookup
.lookup("antigravity-claude-sonnet-4-5(high)")
.unwrap();
assert_eq!(antigravity.matched_key, "claude-sonnet-4-5");
}
#[test]
fn test_parenthesized_reasoning_tier_unknown_value_does_not_strip() {
let lookup = create_lookup();
assert!(lookup.lookup("gpt-5.2(weirdgarbage)").is_none());
assert!(lookup.lookup("gpt-5.2(1024)").is_none());
assert!(lookup.lookup("gpt-5.2()").is_none());
assert!(lookup.lookup("gpt-5.2-codex(invalid)").is_none());
assert!(lookup.lookup("myproxy-gpt-5.2(invalid)").is_none());
assert!(lookup
.lookup("antigravity-claude-sonnet-4-5(invalid)")
.is_none());
assert!(lookup.lookup("claude-sonnet-4-5(garbage)").is_none());
assert!(lookup.lookup("gemini-3-pro(weird)").is_none());
}
#[test]
fn test_parenthesized_reasoning_tier_cost_matches_base_model() {
let lookup = create_lookup();
let base = lookup.calculate_cost("gpt-5.2", 1_000_000, 500_000, 0, 0, 0);
let tiered = lookup.calculate_cost("gpt-5.2(xhigh)", 1_000_000, 500_000, 0, 0, 0);
assert!((tiered - base).abs() < f64::EPSILON);
assert!((tiered - 8.75).abs() < 0.001);
}
#[test]
fn test_normalize_opus_4_5() {
let lookup = create_lookup();
let result = lookup.lookup("opus-4-5").unwrap();
assert_eq!(result.matched_key, "claude-opus-4-5");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_free_variant_normalizes_to_market_priced_claude_model() {
let lookup = create_lookup();
let result = lookup.lookup("claude-sonnet-4-5-free").unwrap();
assert_eq!(result.matched_key, "claude-sonnet-4-5");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_free_variant_with_extra_suffix_falls_back_to_market_priced_model() {
let lookup = create_lookup();
let result = lookup.lookup("claude-sonnet-4-5-free-high").unwrap();
assert_eq!(result.matched_key, "claude-sonnet-4-5");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_normalize_opus_4_6_prefers_4_6_over_4() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-opus-4".into(),
ModelPricing {
input_cost_per_token: Some(0.00002),
output_cost_per_token: Some(0.0001),
..Default::default()
},
);
litellm.insert(
"claude-opus-4-6".into(),
ModelPricing {
input_cost_per_token: Some(0.00001),
output_cost_per_token: Some(0.00005),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup.lookup("opus-4-6").unwrap();
assert_eq!(result.matched_key, "claude-opus-4-6");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_normalize_opus_4_6_dot_prefers_4_6_over_4() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-opus-4".into(),
ModelPricing {
input_cost_per_token: Some(0.00002),
output_cost_per_token: Some(0.0001),
..Default::default()
},
);
litellm.insert(
"claude-opus-4-6".into(),
ModelPricing {
input_cost_per_token: Some(0.00001),
output_cost_per_token: Some(0.00005),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup.lookup("opus-4.6").unwrap();
assert_eq!(result.matched_key, "claude-opus-4-6");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_normalize_opus_4_60_does_not_degrade_to_opus_4() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-opus-4".into(),
ModelPricing {
input_cost_per_token: Some(0.00002),
output_cost_per_token: Some(0.0001),
..Default::default()
},
);
litellm.insert(
"claude-opus-4-6".into(),
ModelPricing {
input_cost_per_token: Some(0.00001),
output_cost_per_token: Some(0.00005),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
assert!(lookup.lookup("opus-4-60").is_none());
}
#[test]
fn test_normalize_opus_4_7_prefers_4_7_over_4() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-opus-4".into(),
ModelPricing {
input_cost_per_token: Some(0.000015),
output_cost_per_token: Some(0.000075),
..Default::default()
},
);
litellm.insert(
"claude-opus-4-7".into(),
ModelPricing {
input_cost_per_token: Some(0.000005),
output_cost_per_token: Some(0.000025),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup.lookup("opus-4-7").unwrap();
assert_eq!(result.matched_key, "claude-opus-4-7");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_normalize_opus_4_7_dot_prefers_4_7_over_4() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-opus-4".into(),
ModelPricing {
input_cost_per_token: Some(0.000015),
output_cost_per_token: Some(0.000075),
..Default::default()
},
);
litellm.insert(
"claude-opus-4-7".into(),
ModelPricing {
input_cost_per_token: Some(0.000005),
output_cost_per_token: Some(0.000025),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup.lookup("opus-4.7").unwrap();
assert_eq!(result.matched_key, "claude-opus-4-7");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_aws_opus_4_7_does_not_degrade_to_opus_4() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-opus-4-7".into(),
ModelPricing {
input_cost_per_token: Some(0.000005),
output_cost_per_token: Some(0.000025),
cache_read_input_token_cost: Some(5e-7),
cache_creation_input_token_cost: Some(0.00000625),
..Default::default()
},
);
let mut openrouter = HashMap::new();
openrouter.insert(
"anthropic/claude-opus-4".into(),
ModelPricing {
input_cost_per_token: Some(0.000015),
output_cost_per_token: Some(0.000075),
cache_read_input_token_cost: Some(0.0000015),
cache_creation_input_token_cost: Some(0.00001875),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, openrouter, HashMap::new());
let result = lookup.lookup("aws.claude-opus-4-7").unwrap();
assert_eq!(result.matched_key, "claude-opus-4-7");
assert_ne!(result.matched_key, "anthropic/claude-opus-4");
let cost = lookup.calculate_cost(
"aws.claude-opus-4-7",
8_400_000,
873_000,
41_300_000,
12_100_000,
0,
);
assert!(
(140.0..=180.0).contains(&cost),
"expected opus-4-7 priced cost around $160, got ${cost:.2}"
);
}
#[test]
fn test_unknown_future_opus_minor_does_not_degrade_to_opus_4() {
let mut openrouter = HashMap::new();
openrouter.insert(
"anthropic/claude-opus-4".into(),
ModelPricing {
input_cost_per_token: Some(0.000015),
output_cost_per_token: Some(0.000075),
..Default::default()
},
);
let lookup = PricingLookup::new(HashMap::new(), openrouter, HashMap::new());
assert!(lookup.lookup("claude-opus-4-8").is_none());
assert!(lookup.lookup("aws.claude-opus-4-8").is_none());
}
#[test]
fn test_normalize_opus_14_6_does_not_map_to_4_6() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-opus-4-6".into(),
ModelPricing {
input_cost_per_token: Some(0.00001),
output_cost_per_token: Some(0.00005),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
assert!(lookup.lookup("opus-14-6").is_none());
}
#[test]
fn test_normalize_sonnet_14_5_does_not_map_to_4_5() {
assert_eq!(normalize_model_name("sonnet-14-5"), None);
}
#[test]
fn test_normalize_haiku_14_5_does_not_map_to_4_5() {
assert_eq!(normalize_model_name("haiku-14-5"), None);
}
fn claude_family_fixture() -> PricingLookup {
fn p(input: f64, output: f64) -> ModelPricing {
ModelPricing {
input_cost_per_token: Some(input),
output_cost_per_token: Some(output),
..Default::default()
}
}
let mut litellm = HashMap::new();
litellm.insert("claude-opus-4".to_string(), p(15e-6, 75e-6));
litellm.insert("claude-opus-4-1".to_string(), p(15e-6, 75e-6));
litellm.insert("claude-opus-4-5".to_string(), p(5e-6, 25e-6));
litellm.insert("claude-opus-4-6".to_string(), p(5e-6, 25e-6));
litellm.insert("claude-opus-4-7".to_string(), p(5e-6, 25e-6));
litellm.insert("claude-opus-4-8".to_string(), p(5e-6, 25e-6));
litellm.insert("claude-sonnet-4".to_string(), p(3e-6, 15e-6));
litellm.insert("claude-sonnet-4-5".to_string(), p(3e-6, 15e-6));
litellm.insert("claude-sonnet-4-6".to_string(), p(3e-6, 15e-6));
litellm.insert("claude-haiku-4-5".to_string(), p(1e-6, 5e-6));
litellm.insert("us.anthropic.claude-opus-4-8".to_string(), p(5e-6, 25e-6));
litellm.insert("vertex_ai/claude-sonnet-4-6".to_string(), p(3e-6, 15e-6));
let mut openrouter = HashMap::new();
openrouter.insert("anthropic/claude-opus-4".to_string(), p(15e-6, 75e-6));
openrouter.insert("anthropic/claude-opus-4.8".to_string(), p(5e-6, 25e-6));
openrouter.insert("anthropic/claude-opus-4.8-fast".to_string(), p(7e-6, 30e-6));
openrouter.insert("anthropic/claude-sonnet-4.6".to_string(), p(3e-6, 15e-6));
openrouter.insert("anthropic/claude-haiku-4.5".to_string(), p(1e-6, 5e-6));
openrouter.insert("anthropic/claude-fable-5".to_string(), p(5e-6, 25e-6));
PricingLookup::new(litellm, openrouter, HashMap::new())
}
#[test]
fn test_normalize_minor_generalizes_across_families() {
assert_eq!(
normalize_model_name("claude-sonnet-4-7"),
Some("claude-sonnet-4-7".into())
);
assert_eq!(
normalize_model_name("sonnet-4.7"),
Some("claude-sonnet-4-7".into())
);
assert_eq!(
normalize_model_name("claude-haiku-4-6"),
Some("claude-haiku-4-6".into())
);
assert_eq!(
normalize_model_name("haiku-4.6"),
Some("claude-haiku-4-6".into())
);
assert_eq!(
normalize_model_name("claude-opus-4-9"),
Some("claude-opus-4-9".into())
);
assert_eq!(
normalize_model_name("opus-4.9"),
Some("claude-opus-4-9".into())
);
assert_eq!(
normalize_model_name("opus-5-2"),
Some("claude-opus-5-2".into())
);
}
#[test]
fn test_normalize_reversed_order_all_families() {
assert_eq!(
normalize_model_name("claude-4-8-opus"),
Some("claude-opus-4-8".into())
);
assert_eq!(
normalize_model_name("4-8-opus"),
Some("claude-opus-4-8".into())
);
assert_eq!(
normalize_model_name("claude-4-6-sonnet"),
Some("claude-sonnet-4-6".into())
);
assert_eq!(
normalize_model_name("claude-4-5-haiku"),
Some("claude-haiku-4-5".into())
);
}
#[test]
fn test_normalize_bare_modern_major() {
assert_eq!(
normalize_model_name("claude-sonnet-5"),
Some("claude-sonnet-5".into())
);
assert_eq!(
normalize_model_name("claude-opus-5"),
Some("claude-opus-5".into())
);
assert_eq!(
normalize_model_name("fable-5"),
Some("claude-fable-5".into())
);
assert_eq!(
normalize_model_name("claude-fable-5[1m]"),
Some("claude-fable-5".into())
);
}
#[test]
fn test_normalize_modern_claude_boundaries() {
assert_eq!(normalize_model_name("opus-4-60"), None);
assert_eq!(normalize_model_name("sonnet-4-60"), None);
assert_eq!(normalize_model_name("opus-14-6"), None);
assert_eq!(normalize_model_name("opus4"), None);
assert_eq!(normalize_model_name("opus-4x"), None);
assert_eq!(normalize_model_name("opus-3"), None);
assert_eq!(normalize_model_name("claude-sonnet-5-0"), None);
assert_eq!(normalize_model_name("claude-opus-4-20250514"), None);
}
#[test]
fn test_normalize_legacy_line_not_hijacked_by_modern_parser() {
assert_eq!(
normalize_model_name("claude-3-5-sonnet"),
Some("claude-3.5-sonnet".into())
);
assert_eq!(
normalize_model_name("claude-3-7-sonnet-20250219"),
Some("claude-3-7-sonnet".into())
);
assert_eq!(
normalize_model_name("claude-3-5-haiku-20241022"),
Some("claude-3.5-haiku".into())
);
}
#[test]
fn test_bedrock_sonnet_never_billed_as_opus() {
let lookup = claude_family_fixture();
let result = lookup
.lookup("us.anthropic.claude-sonnet-4-6-v1:0")
.unwrap();
assert_eq!(result.matched_key, "claude-sonnet-4-6");
assert_eq!(result.pricing.input_cost_per_token, Some(3e-6));
}
#[test]
fn test_reversed_sonnet_resolves_canonical_not_cross_family() {
let lookup = claude_family_fixture();
for id in ["claude-4-6-sonnet", "4-6-sonnet"] {
let result = lookup.lookup(id).unwrap();
assert_eq!(result.matched_key, "claude-sonnet-4-6", "id: {id}");
}
let result = lookup.lookup("claude-4-5-haiku").unwrap();
assert_eq!(result.matched_key, "claude-haiku-4-5");
}
#[test]
fn test_unknown_sonnet_haiku_minor_does_not_degrade() {
let lookup = claude_family_fixture();
for id in [
"sonnet-4-7",
"claude-sonnet-4-7",
"sonnet-4-60",
"haiku-4-6",
"claude-haiku-4-6",
] {
assert!(lookup.lookup(id).is_none(), "id {id} must not degrade");
}
}
#[test]
fn test_major_five_never_resolves_to_different_major() {
let lookup = claude_family_fixture();
for id in [
"claude-opus-5",
"opus-5",
"opus-5-2",
"sonnet-5",
"claude-sonnet-5-0",
] {
assert!(
lookup.lookup(id).is_none(),
"id {id} must not resolve to a 4.x key"
);
}
for id in [
"claude-fable-5",
"fable-5",
"claude-fable-5[1m]",
"anthropic/claude-fable-5",
] {
let result = lookup.lookup(id).unwrap();
assert_eq!(result.matched_key, "anthropic/claude-fable-5", "id: {id}");
}
}
#[test]
fn test_unknown_prefixed_model_id_strips_to_underlying_model() {
let lookup = create_lookup();
let direct = lookup.lookup("gpt-5.5").unwrap();
let prefixed = lookup.lookup("cx/gpt-5.5").unwrap();
assert_eq!(prefixed.matched_key, direct.matched_key);
assert_eq!(prefixed.source, direct.source);
assert_eq!(
prefixed.pricing.input_cost_per_token,
direct.pricing.input_cost_per_token
);
assert_eq!(
prefixed.pricing.output_cost_per_token,
direct.pricing.output_cost_per_token
);
}
#[test]
fn test_known_prefixed_dataset_key_still_resolves_exactly() {
let lookup = claude_family_fixture();
let result = lookup.lookup("anthropic/claude-fable-5").unwrap();
assert_eq!(result.matched_key, "anthropic/claude-fable-5");
}
#[test]
fn test_unknown_prefixed_unknown_model_stays_none() {
let lookup = create_lookup();
assert!(lookup.lookup("unknown/nonexistent").is_none());
}
#[test]
fn test_major_five_resolves_once_dataset_knows_it() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-opus-5".to_string(),
ModelPricing {
input_cost_per_token: Some(0.00001),
output_cost_per_token: Some(0.00005),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
for id in ["claude-opus-5", "opus-5", "aws.claude-opus-5-thinking"] {
let result = lookup.lookup(id).unwrap();
assert_eq!(result.matched_key, "claude-opus-5", "id: {id}");
}
}
#[test]
fn test_known_minor_shapes_resolve_per_family() {
let lookup = claude_family_fixture();
let cases = [
("opus-4-8", "claude-opus-4-8"),
("opus-4.8", "claude-opus-4-8"),
("aws.claude-opus-4-8", "claude-opus-4-8"),
("claude-opus-4-8-thinking", "claude-opus-4-8"),
("claude-sonnet-4-6", "claude-sonnet-4-6"),
("claude-sonnet-4.6", "claude-sonnet-4-6"),
("sonnet-4-6", "claude-sonnet-4-6"),
("sonnet-4.6", "claude-sonnet-4-6"),
("aws.claude-sonnet-4-6-v1", "claude-sonnet-4-6"),
("claude-sonnet-4-6-thinking", "claude-sonnet-4-6"),
("haiku-4-5", "claude-haiku-4-5"),
("haiku-4.5", "claude-haiku-4-5"),
("vertex_ai/claude-sonnet-4-6", "vertex_ai/claude-sonnet-4-6"),
];
for (id, expected) in cases {
let result = lookup.lookup(id).unwrap();
assert_eq!(result.matched_key, expected, "id: {id}");
}
}
#[test]
fn test_normalize_opus_4_8_prefers_4_8_over_4() {
let lookup = claude_family_fixture();
for id in ["opus-4-8", "opus-4.8"] {
let result = lookup.lookup(id).unwrap();
assert_eq!(result.matched_key, "claude-opus-4-8", "id: {id}");
assert_eq!(result.source, "LiteLLM");
}
}
#[test]
fn test_aws_opus_4_8_does_not_degrade_to_opus_4() {
let lookup = claude_family_fixture();
let result = lookup.lookup("aws.claude-opus-4-8").unwrap();
assert_eq!(result.matched_key, "claude-opus-4-8");
let cost = lookup.calculate_cost("aws.claude-opus-4-8", 8_400_000, 873_000, 0, 0, 0);
assert!(
(60.0..=70.0).contains(&cost),
"expected opus-4-8 priced cost around $64, got ${cost:.2}"
);
}
#[test]
fn claude_2x_never_fuzzy_matches_modern_models() {
let mut openrouter = HashMap::new();
openrouter.insert(
"anthropic/claude-opus-4.7-fast".to_string(),
ModelPricing {
input_cost_per_token: Some(30e-6),
output_cost_per_token: Some(150e-6),
..Default::default()
},
);
let lookup = PricingLookup::new(HashMap::new(), openrouter, HashMap::new());
for id in ["claude-2.1", "claude-2.0", "claude", "anthropic"] {
assert!(
lookup.lookup(id).is_none(),
"id {id} must resolve unpriced, never to another model's price"
);
}
}
#[test]
fn claude_2x_still_resolves_when_dataset_prices_it() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-2.1".to_string(),
ModelPricing {
input_cost_per_token: Some(8e-6),
output_cost_per_token: Some(24e-6),
..Default::default()
},
);
let mut openrouter = HashMap::new();
openrouter.insert(
"anthropic/claude-opus-4.7-fast".to_string(),
ModelPricing {
input_cost_per_token: Some(30e-6),
output_cost_per_token: Some(150e-6),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, openrouter, HashMap::new());
let result = lookup.lookup("claude-2.1").unwrap();
assert_eq!(result.matched_key, "claude-2.1");
assert_eq!(result.pricing.input_cost_per_token, Some(8e-6));
}
#[test]
fn canonical_fast_price_beats_reseller_markup() {
let mut openrouter = HashMap::new();
openrouter.insert(
"anthropic/claude-opus-4.6-fast".to_string(),
ModelPricing {
input_cost_per_token: Some(30e-6),
output_cost_per_token: Some(150e-6),
..Default::default()
},
);
let mut models_dev = HashMap::new();
models_dev.insert(
"venice/claude-opus-4-6-fast".to_string(),
ModelPricing {
input_cost_per_token: Some(36e-6),
output_cost_per_token: Some(180e-6),
..Default::default()
},
);
let lookup = PricingLookup::new_with_models_dev(
HashMap::new(),
openrouter,
HashMap::new(),
HashMap::new(),
models_dev,
);
let result = lookup.lookup("claude-opus-4-6-fast").unwrap();
assert_eq!(result.matched_key, "anthropic/claude-opus-4.6-fast");
assert_eq!(result.pricing.input_cost_per_token, Some(30e-6));
}
#[test]
fn provider_hint_keeps_models_dev_provider_key_over_unscoped_canonical() {
let mut openrouter = HashMap::new();
openrouter.insert(
"anthropic/claude-opus-4.6-fast".to_string(),
ModelPricing {
input_cost_per_token: Some(30e-6),
output_cost_per_token: Some(150e-6),
..Default::default()
},
);
let mut models_dev = HashMap::new();
models_dev.insert(
"venice/claude-opus-4-6-fast".to_string(),
ModelPricing {
input_cost_per_token: Some(36e-6),
output_cost_per_token: Some(180e-6),
..Default::default()
},
);
let lookup = PricingLookup::new_with_models_dev(
HashMap::new(),
openrouter,
HashMap::new(),
HashMap::new(),
models_dev,
);
let hinted = lookup
.lookup_with_provider("claude-opus-4-6-fast", Some("venice"))
.unwrap();
assert_eq!(hinted.matched_key, "venice/claude-opus-4-6-fast");
assert_eq!(hinted.pricing.input_cost_per_token, Some(36e-6));
let unhinted = lookup.lookup("claude-opus-4-6-fast").unwrap();
assert_eq!(unhinted.matched_key, "anthropic/claude-opus-4.6-fast");
assert_eq!(unhinted.pricing.input_cost_per_token, Some(30e-6));
}
#[test]
fn provider_hint_beats_unscoped_openrouter_model_part_for_dotted_id() {
let mut openrouter = HashMap::new();
openrouter.insert(
"anthropic/claude-opus-4.6-fast".to_string(),
ModelPricing {
input_cost_per_token: Some(30e-6),
output_cost_per_token: Some(150e-6),
..Default::default()
},
);
let mut models_dev = HashMap::new();
models_dev.insert(
"venice/claude-opus-4.6-fast".to_string(),
ModelPricing {
input_cost_per_token: Some(36e-6),
output_cost_per_token: Some(180e-6),
..Default::default()
},
);
let lookup = PricingLookup::new_with_models_dev(
HashMap::new(),
openrouter,
HashMap::new(),
HashMap::new(),
models_dev,
);
let hinted = lookup
.lookup_with_provider("claude-opus-4.6-fast", Some("venice"))
.unwrap();
assert_eq!(hinted.matched_key, "venice/claude-opus-4.6-fast");
assert_eq!(hinted.pricing.input_cost_per_token, Some(36e-6));
let unhinted = lookup.lookup("claude-opus-4.6-fast").unwrap();
assert_eq!(unhinted.matched_key, "anthropic/claude-opus-4.6-fast");
assert_eq!(unhinted.pricing.input_cost_per_token, Some(30e-6));
let no_match = lookup
.lookup_with_provider("claude-opus-4.6-fast", Some("groq"))
.unwrap();
assert_eq!(no_match.matched_key, "anthropic/claude-opus-4.6-fast");
assert_eq!(no_match.pricing.input_cost_per_token, Some(30e-6));
}
#[test]
fn unpriced_anthropic_models_dev_key_does_not_shadow_priced_reseller() {
let mut models_dev = HashMap::new();
models_dev.insert("anthropic/model-x".to_string(), ModelPricing::default());
models_dev.insert(
"reseller/model-x".to_string(),
ModelPricing {
input_cost_per_token: Some(36e-6),
output_cost_per_token: Some(180e-6),
..Default::default()
},
);
let lookup = PricingLookup::new_with_models_dev(
HashMap::new(),
HashMap::new(),
HashMap::new(),
HashMap::new(),
models_dev,
);
let result = lookup.lookup("model-x").unwrap();
assert_eq!(result.matched_key, "reseller/model-x");
assert_eq!(result.pricing.input_cost_per_token, Some(36e-6));
}
#[test]
fn models_dev_still_covers_long_tail_after_reorder() {
let mut models_dev = HashMap::new();
models_dev.insert(
"someprovider/exotic-model-9".to_string(),
ModelPricing {
input_cost_per_token: Some(2e-6),
output_cost_per_token: Some(6e-6),
..Default::default()
},
);
let lookup = PricingLookup::new_with_models_dev(
HashMap::new(),
HashMap::new(),
HashMap::new(),
HashMap::new(),
models_dev,
);
let result = lookup.lookup("exotic-model-9").unwrap();
assert_eq!(result.matched_key, "someprovider/exotic-model-9");
assert_eq!(result.pricing.input_cost_per_token, Some(2e-6));
}
#[test]
fn models_dev_provider_choice_is_deterministic_and_prefers_anthropic() {
let price = ModelPricing {
input_cost_per_token: Some(0.8e-6),
output_cost_per_token: Some(4e-6),
..Default::default()
};
let mut models_dev = HashMap::new();
models_dev.insert("302ai/claude-3-5-haiku-20241022".to_string(), price.clone());
models_dev.insert(
"anthropic/claude-3-5-haiku-20241022".to_string(),
price.clone(),
);
let lookup = PricingLookup::new_with_models_dev(
HashMap::new(),
HashMap::new(),
HashMap::new(),
HashMap::new(),
models_dev,
);
let result = lookup.lookup("claude-3-5-haiku-20241022").unwrap();
assert_eq!(result.matched_key, "anthropic/claude-3-5-haiku-20241022");
assert_eq!(result.pricing.input_cost_per_token, Some(0.8e-6));
}
#[test]
fn test_blocklist_auto() {
let lookup = create_lookup();
assert!(lookup.lookup("auto").is_none());
}
#[test]
fn test_blocklist_mini() {
let lookup = create_lookup();
assert!(lookup.lookup("mini").is_none());
}
#[test]
fn test_force_source_litellm() {
let lookup = create_lookup();
let result = lookup
.lookup_with_source("gpt-4o", Some("litellm"))
.unwrap();
assert_eq!(result.source, "LiteLLM");
assert_eq!(result.matched_key, "gpt-4o");
}
#[test]
fn test_force_source_openrouter() {
let lookup = create_lookup();
let result = lookup
.lookup_with_source("gpt-4o", Some("openrouter"))
.unwrap();
assert_eq!(result.source, "OpenRouter");
assert_eq!(result.matched_key, "openai/gpt-4o");
}
#[test]
fn test_case_insensitive() {
let lookup = create_lookup();
let result = lookup.lookup("GPT-4O").unwrap();
assert_eq!(result.matched_key, "gpt-4o");
}
#[test]
fn test_fuzzy_match_gemini() {
let lookup = create_lookup();
let result = lookup.lookup("gemini-3-pro").unwrap();
assert_eq!(result.matched_key, "openrouter/google/gemini-3-pro-preview");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_tier_suffix_with_fuzzy() {
let lookup = create_lookup();
let result = lookup.lookup("gemini-3-pro-high").unwrap();
assert_eq!(result.matched_key, "openrouter/google/gemini-3-pro-preview");
}
#[test]
fn test_nonexistent_model() {
let lookup = create_lookup();
assert!(lookup.lookup("nonexistent-model-xyz").is_none());
}
#[test]
fn test_fallback_suffix_lookup() {
let mut litellm = HashMap::new();
litellm.insert(
"gpt-5".into(),
ModelPricing {
input_cost_per_token: Some(0.00000125),
output_cost_per_token: Some(0.00001),
cache_read_input_token_cost: Some(1.25e-7),
cache_creation_input_token_cost: None,
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup.lookup("gpt-5-codex").unwrap();
assert_eq!(result.matched_key, "gpt-5");
assert_eq!(result.source, "LiteLLM");
let result = lookup.lookup("gpt-5-codex-max").unwrap();
assert_eq!(result.matched_key, "gpt-5");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_fallback_suffix_with_tier_suffix() {
let mut litellm = HashMap::new();
litellm.insert(
"gpt-5".into(),
ModelPricing {
input_cost_per_token: Some(0.00000125),
output_cost_per_token: Some(0.00001),
cache_read_input_token_cost: Some(1.25e-7),
cache_creation_input_token_cost: None,
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup.lookup("gpt-5-codex-high").unwrap();
assert_eq!(result.matched_key, "gpt-5");
assert_eq!(result.source, "LiteLLM");
let result = lookup.lookup("gpt-5-codex-max-xhigh").unwrap();
assert_eq!(result.matched_key, "gpt-5");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_fallback_suffix_prefers_exact_match() {
let mut litellm = HashMap::new();
litellm.insert(
"gpt-5".into(),
ModelPricing {
input_cost_per_token: Some(0.00000125),
output_cost_per_token: Some(0.00001),
cache_read_input_token_cost: None,
cache_creation_input_token_cost: None,
..Default::default()
},
);
litellm.insert(
"gpt-5-codex".into(),
ModelPricing {
input_cost_per_token: Some(0.000002), output_cost_per_token: Some(0.000015),
cache_read_input_token_cost: None,
cache_creation_input_token_cost: None,
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup.lookup("gpt-5-codex").unwrap();
assert_eq!(result.matched_key, "gpt-5-codex");
assert_eq!(result.pricing.input_cost_per_token, Some(0.000002));
}
#[test]
fn test_normalize_version_separator() {
assert_eq!(
normalize_version_separator("glm-4-7"),
Some("glm-4.7".into())
);
assert_eq!(
normalize_version_separator("glm-4-6"),
Some("glm-4.6".into())
);
assert_eq!(
normalize_version_separator("claude-3-5-haiku"),
Some("claude-3.5-haiku".into())
);
assert_eq!(
normalize_version_separator("gpt-5-1-codex"),
Some("gpt-5.1-codex".into())
);
assert_eq!(normalize_version_separator("gpt-4o"), None);
assert_eq!(normalize_version_separator("claude-sonnet"), None);
assert_eq!(normalize_version_separator("big-pickle"), None);
}
#[test]
fn test_normalize_version_separator_preserves_dates() {
assert_eq!(normalize_version_separator("2024-11-20"), None);
assert_eq!(normalize_version_separator("model-2024-11-20"), None);
assert_eq!(
normalize_version_separator("claude-3-5-sonnet-20241022"),
Some("claude-3.5-sonnet-20241022".into())
);
assert_eq!(normalize_version_separator("sonnet-20241022"), None);
assert_eq!(normalize_version_separator("model-20241022-v1"), None);
}
#[test]
fn test_is_fuzzy_eligible() {
assert!(!is_fuzzy_eligible("auto"));
assert!(!is_fuzzy_eligible("mini"));
assert!(!is_fuzzy_eligible("chat"));
assert!(!is_fuzzy_eligible("base"));
assert!(!is_fuzzy_eligible("abc"));
assert!(is_fuzzy_eligible("gpt-4o"));
assert!(!is_fuzzy_eligible("claude"));
assert!(!is_fuzzy_eligible("anthropic"));
}
#[test]
fn test_provider_preference_grok_prefers_xai_over_azure() {
let lookup = create_lookup();
let result = lookup.lookup("grok-code").unwrap();
assert_eq!(result.matched_key, "xai/grok-code-fast-1-0825");
assert_eq!(result.source, "LiteLLM");
assert!(!result.matched_key.starts_with("azure"));
}
#[test]
fn test_grok_code_prefers_xai_over_azure() {
let mut litellm = HashMap::new();
litellm.insert(
"azure_ai/grok-code-fast-1".to_string(),
ModelPricing {
input_cost_per_token: Some(0.0000035), output_cost_per_token: Some(0.0000175), cache_read_input_token_cost: None,
cache_creation_input_token_cost: None,
..Default::default()
},
);
litellm.insert(
"xai/grok-code-fast-1-0825".to_string(),
ModelPricing {
input_cost_per_token: Some(0.0000002), output_cost_per_token: Some(0.0000015), cache_read_input_token_cost: Some(0.00000002),
cache_creation_input_token_cost: None,
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup.lookup("grok-code").unwrap();
assert!(
result.matched_key.starts_with("xai/"),
"Expected xai/ prefix (original provider) but got: {}. \
The lookup should prefer original providers over resellers.",
result.matched_key
);
assert_eq!(
result.matched_key, "xai/grok-code-fast-1-0825",
"Should match the xai/grok-code-fast-1-0825 entry, not azure_ai/grok-code-fast-1"
);
let pricing = &result.pricing;
assert!(
pricing.input_cost_per_token.unwrap() < 0.000001,
"Input cost should be ~$0.20/1M (0.0000002), not ~$3.50/1M (reseller price)"
);
assert!(
pricing.output_cost_per_token.unwrap() < 0.000005,
"Output cost should be ~$1.50/1M (0.0000015), not ~$17.50/1M (reseller price)"
);
}
#[test]
fn test_provider_preference_gemini_prefers_google_over_vertex() {
let lookup = create_lookup();
let result = lookup.lookup("gemini-2.5-pro").unwrap();
assert_eq!(result.matched_key, "google/gemini-2.5-pro");
assert_eq!(result.source, "LiteLLM");
assert!(!result.matched_key.starts_with("vertex_ai"));
}
#[test]
fn test_is_original_provider() {
assert!(is_original_provider("xai/grok-code"));
assert!(is_original_provider("anthropic/claude-3"));
assert!(is_original_provider("openai/gpt-4"));
assert!(is_original_provider("google/gemini"));
assert!(is_original_provider("x-ai/grok"));
assert!(!is_original_provider("azure_ai/grok"));
assert!(!is_original_provider("bedrock/anthropic"));
assert!(!is_original_provider("vertex_ai/gemini"));
assert!(!is_original_provider("unknown-provider/model"));
}
#[test]
fn test_is_reseller_provider() {
assert!(is_reseller_provider("azure_ai/grok-code"));
assert!(is_reseller_provider("azure/openai/gpt-4"));
assert!(is_reseller_provider("bedrock/anthropic.claude"));
assert!(is_reseller_provider("vertex_ai/gemini"));
assert!(is_reseller_provider("together_ai/llama"));
assert!(is_reseller_provider("groq/llama"));
assert!(!is_reseller_provider("xai/grok"));
assert!(!is_reseller_provider("anthropic/claude"));
assert!(!is_reseller_provider("openai/gpt-4"));
}
#[test]
fn test_calculate_cost_gpt_5_2() {
let lookup = create_lookup();
let cost = lookup.calculate_cost("gpt-5.2", 1_000_000, 500_000, 0, 0, 0);
assert!((cost - 8.75).abs() < 0.001);
}
#[test]
fn test_calculate_cost_claude_sonnet_4_5() {
let lookup = create_lookup();
let cost = lookup.calculate_cost("claude-sonnet-4-5", 100_000, 50_000, 200_000, 0, 0);
assert!((cost - 1.11).abs() < 0.001);
}
#[test]
fn test_compute_cost_tiered_boundary_at_200k_uses_base_rates() {
let pricing: ModelPricing = serde_json::from_str(
r#"{
"input_cost_per_token": 0.000001,
"input_cost_per_token_above_200k_tokens": 0.000002,
"output_cost_per_token": 0.000003,
"output_cost_per_token_above_200k_tokens": 0.000004
}"#,
)
.unwrap();
let cost = compute_cost(&pricing, 200_000, 200_000, 0, 0, 0);
let expected = 200_000.0 * 0.000001 + 200_000.0 * 0.000003;
assert!((cost - expected).abs() < 1e-12);
}
#[test]
fn test_compute_cost_tiered_above_200k_splits_input_and_output() {
let pricing: ModelPricing = serde_json::from_str(
r#"{
"input_cost_per_token": 0.000001,
"input_cost_per_token_above_200k_tokens": 0.000002,
"output_cost_per_token": 0.000003,
"output_cost_per_token_above_200k_tokens": 0.000004
}"#,
)
.unwrap();
let cost = compute_cost(&pricing, 200_001, 200_001, 0, 0, 0);
let expected =
(200_000.0 * 0.000001 + 1.0 * 0.000002) + (200_000.0 * 0.000003 + 1.0 * 0.000004);
assert!((cost - expected).abs() < 1e-12);
}
#[test]
fn test_compute_cost_tiered_above_272k_splits_gpt_5_5_tokens() {
let pricing: ModelPricing = serde_json::from_str(
r#"{
"input_cost_per_token": 0.000005,
"input_cost_per_token_above_272k_tokens": 0.000010,
"output_cost_per_token": 0.000030,
"output_cost_per_token_above_272k_tokens": 0.000045,
"cache_read_input_token_cost": 0.0000005,
"cache_read_input_token_cost_above_272k_tokens": 0.000001
}"#,
)
.unwrap();
let cost = compute_cost(&pricing, 272_001, 272_001, 272_001, 0, 0);
let expected = (272_000.0 * 0.000005 + 1.0 * 0.000010)
+ (272_000.0 * 0.000030 + 1.0 * 0.000045)
+ (272_000.0 * 0.0000005 + 1.0 * 0.000001);
assert!((cost - expected).abs() < 1e-12);
}
#[test]
fn test_compute_cost_tiered_uses_multiple_thresholds_in_order() {
let pricing: ModelPricing = serde_json::from_str(
r#"{
"input_cost_per_token": 0.000001,
"input_cost_per_token_above_128k_tokens": 0.000002,
"input_cost_per_token_above_256k_tokens": 0.000003,
"input_cost_per_token_above_272k_tokens": 0.000004
}"#,
)
.unwrap();
let cost = compute_cost(&pricing, 300_000, 0, 0, 0, 0);
let expected = (128_000.0 * 0.000001)
+ (128_000.0 * 0.000002)
+ (16_000.0 * 0.000003)
+ (28_000.0 * 0.000004);
assert!((cost - expected).abs() < 1e-12);
}
fn openai_272k_result(key: &str, source: &str) -> LookupResult {
LookupResult {
matched_key: key.into(),
source: source.into(),
pricing: ModelPricing {
input_cost_per_token: Some(0.000005),
input_cost_per_token_above_272k_tokens: Some(0.000010),
output_cost_per_token: Some(0.000030),
output_cost_per_token_above_272k_tokens: Some(0.000045),
cache_read_input_token_cost: Some(0.0000005),
cache_read_input_token_cost_above_272k_tokens: Some(0.000001),
cache_creation_input_token_cost: Some(0.00000625),
..Default::default()
},
}
}
#[test]
fn test_openai_272k_full_request_pricing_uses_combined_input() {
let result = openai_272k_result("openai/gpt-5.5", "LiteLLM");
let usage = |input, output, cache_read, cache_write| TokenBreakdown {
input,
output,
cache_read,
cache_write,
reasoning: 0,
};
let cost =
compute_cost_for_lookup(&result, Some("openai"), &usage(200_000, 10_000, 72_000, 1));
let expected = 200_000.0 * 0.000010 + 10_000.0 * 0.000045 + 72_000.0 * 0.000001 + 0.0000125;
assert!((cost - expected).abs() < 1e-12);
let boundary = compute_cost_for_lookup(&result, None, &usage(200_000, 10_000, 72_000, 0));
let boundary_expected = 200_000.0 * 0.000005 + 10_000.0 * 0.000030 + 72_000.0 * 0.0000005;
assert!((boundary - boundary_expected).abs() < 1e-12);
let output_only = compute_cost_for_lookup(&result, None, &usage(1, 300_000, 0, 0));
assert!((output_only - (0.000005 + 300_000.0 * 0.000030)).abs() < 1e-12);
}
#[test]
fn test_provider_aware_openai_prefers_complete_litellm_tiers() {
let litellm_pricing = openai_272k_result("gpt-5.6-sol", "LiteLLM").pricing;
let openrouter_pricing = ModelPricing {
input_cost_per_token: litellm_pricing.input_cost_per_token,
output_cost_per_token: litellm_pricing.output_cost_per_token,
cache_read_input_token_cost: litellm_pricing.cache_read_input_token_cost,
..Default::default()
};
let lookup = PricingLookup::new(
HashMap::from([("gpt-5.6-sol".into(), litellm_pricing.clone())]),
HashMap::from([("openai/gpt-5.6-sol".into(), openrouter_pricing)]),
HashMap::new(),
);
let result = lookup
.lookup_with_provider("gpt-5.6-sol", Some("openai"))
.unwrap();
assert_eq!(result.source, "LiteLLM");
assert_eq!(result.matched_key, "gpt-5.6-sol");
let usage = TokenBreakdown {
input: 200_000,
output: 10_000,
cache_read: 72_001,
..Default::default()
};
let expected = 200_000.0 * 0.000010 + 10_000.0 * 0.000045 + 72_001.0 * 0.000001;
for provider in [Some("openai"), Some("unknown"), Some(""), None] {
let cost = lookup.calculate_cost_with_provider("gpt-5.6-sol", provider, &usage);
assert!((cost - expected).abs() < 1e-12);
}
let lookup = PricingLookup::new(
HashMap::from([("gpt-5.6-sol".into(), litellm_pricing.clone())]),
HashMap::from([("openai/gpt-5.6-sol".into(), litellm_pricing)]),
HashMap::new(),
);
let result = lookup
.lookup_with_provider("gpt-5.6-sol", Some("openai"))
.unwrap();
assert_eq!(result.source, "LiteLLM");
assert!(!should_prefer_openai_tiered_litellm(
"gpt-5.6-sol",
Some("openrouter"),
Some(&result)
));
}
#[test]
fn test_openai_tiered_litellm_preference_requires_complete_272k_pricing() {
let pricing = openai_272k_result("gpt-5.6-sol", "LiteLLM").pricing;
assert!(has_complete_openai_272k_pricing(&pricing));
let clear_required: [fn(&mut ModelPricing); 5] = [
|pricing| pricing.input_cost_per_token = None,
|pricing| pricing.input_cost_per_token_above_272k_tokens = None,
|pricing| pricing.output_cost_per_token = None,
|pricing| pricing.output_cost_per_token_above_272k_tokens = None,
|pricing| pricing.cache_read_input_token_cost_above_272k_tokens = None,
];
for clear in clear_required {
let mut incomplete = pricing.clone();
clear(&mut incomplete);
assert!(!has_complete_openai_272k_pricing(&incomplete));
}
let mut without_cache_read = pricing;
without_cache_read.cache_read_input_token_cost = None;
without_cache_read.cache_read_input_token_cost_above_272k_tokens = None;
assert!(!has_complete_openai_272k_pricing(&without_cache_read));
}
#[test]
fn openai_272k_preference_prefers_openrouter_cache_read_pricing_over_incomplete_litellm() {
let mut litellm_pricing = openai_272k_result("gpt-5.6-sol", "LiteLLM").pricing;
litellm_pricing.cache_read_input_token_cost = None;
litellm_pricing.cache_read_input_token_cost_above_272k_tokens = None;
let openrouter_pricing = openai_272k_result("openai/gpt-5.6-sol", "OpenRouter").pricing;
let lookup = PricingLookup::new(
HashMap::from([("gpt-5.6-sol".into(), litellm_pricing)]),
HashMap::from([("openai/gpt-5.6-sol".into(), openrouter_pricing)]),
HashMap::new(),
);
let result = lookup
.lookup_with_provider("gpt-5.6-sol", Some("openai"))
.unwrap();
assert_eq!(result.source, "OpenRouter");
assert_eq!(result.matched_key, "openai/gpt-5.6-sol");
assert!(result.pricing.cache_read_input_token_cost.is_some());
}
#[test]
fn openai_272k_preference_still_prefers_complete_litellm_pricing() {
let litellm_pricing = openai_272k_result("gpt-5.6-sol", "LiteLLM").pricing;
let openrouter_pricing = openai_272k_result("openai/gpt-5.6-sol", "OpenRouter").pricing;
let lookup = PricingLookup::new(
HashMap::from([("gpt-5.6-sol".into(), litellm_pricing)]),
HashMap::from([("openai/gpt-5.6-sol".into(), openrouter_pricing)]),
HashMap::new(),
);
let result = lookup
.lookup_with_provider("gpt-5.6-sol", Some("openai"))
.unwrap();
assert_eq!(result.source, "LiteLLM");
assert_eq!(result.matched_key, "gpt-5.6-sol");
}
#[test]
fn test_openai_272k_full_request_pricing_scope() {
for key in [
"gpt-5.4",
"openai/gpt-5.4-pro-2026-03-05",
"gpt-5.5-2026-04-23",
"gpt-5.5-pro",
"gpt-5.5-pro-2026-04-23",
"gpt-5.6",
"gpt-5.6-sol",
"gpt-5.6-terra-2026-07-01",
"gpt-5.6-luna",
] {
assert!(
uses_openai_full_request_272k_pricing(
&openai_272k_result(key, "LiteLLM"),
Some("openai")
),
"expected full-request pricing for {key}"
);
}
for key in [
"gpt-5.4-mini",
"gpt-5.4-nano",
"gpt-5.5-promax",
"gpt-5.2",
"fugu-ultra",
"custom/gpt-5.5-pro",
] {
assert!(
!uses_openai_full_request_272k_pricing(
&openai_272k_result(key, "LiteLLM"),
Some("openai")
),
"expected progressive pricing for {key}"
);
}
for (result, provider) in [
(openai_272k_result("fugu-ultra", "LiteLLM"), None),
(openai_272k_result("openai/gpt-5.5", "OpenRouter"), None),
(
openai_272k_result("azure/openai/gpt-5.5", "LiteLLM"),
Some("azure"),
),
] {
assert!(!uses_openai_full_request_272k_pricing(&result, provider));
}
}
#[test]
fn test_compute_cost_tiered_is_applied_per_bucket() {
let pricing: ModelPricing = serde_json::from_str(
r#"{
"input_cost_per_token": 0.000001,
"input_cost_per_token_above_200k_tokens": 0.000002,
"output_cost_per_token": 0.000003,
"output_cost_per_token_above_200k_tokens": 0.000004
}"#,
)
.unwrap();
let cost = compute_cost(&pricing, 200_001, 200_000, 0, 0, 0);
let expected = (200_000.0 * 0.000001 + 1.0 * 0.000002) + (200_000.0 * 0.000003);
assert!((cost - expected).abs() < 1e-12);
}
#[test]
fn test_compute_cost_tiered_missing_base_input_only_charges_above_threshold() {
let pricing: ModelPricing = serde_json::from_str(
r#"{
"input_cost_per_token_above_200k_tokens": 0.000002
}"#,
)
.unwrap();
let at_threshold = compute_cost(&pricing, 200_000, 0, 0, 0, 0);
let above_threshold = compute_cost(&pricing, 200_001, 0, 0, 0, 0);
assert_eq!(at_threshold, 0.0);
assert!((above_threshold - 0.000002).abs() < 1e-12);
}
#[test]
fn test_compute_cost_tiered_cache_read_applies_split() {
let pricing: ModelPricing = serde_json::from_str(
r#"{
"cache_read_input_token_cost": 0.0000001,
"cache_read_input_token_cost_above_200k_tokens": 0.0000002
}"#,
)
.unwrap();
let at_threshold = compute_cost(&pricing, 0, 0, 200_000, 0, 0);
let above_threshold = compute_cost(&pricing, 0, 0, 200_001, 0, 0);
assert!((at_threshold - (200_000.0 * 0.0000001)).abs() < 1e-12);
assert!((above_threshold - (200_000.0 * 0.0000001 + 0.0000002)).abs() < 1e-12);
}
#[test]
fn test_compute_cost_tiered_cache_write_applies_split() {
let pricing: ModelPricing = serde_json::from_str(
r#"{
"cache_creation_input_token_cost": 0.0000003,
"cache_creation_input_token_cost_above_200k_tokens": 0.0000004
}"#,
)
.unwrap();
let at_threshold = compute_cost(&pricing, 0, 0, 0, 200_000, 0);
let above_threshold = compute_cost(&pricing, 0, 0, 0, 200_001, 0);
assert!((at_threshold - (200_000.0 * 0.0000003)).abs() < 1e-12);
assert!((above_threshold - (200_000.0 * 0.0000003 + 0.0000004)).abs() < 1e-12);
}
#[test]
fn test_compute_cost_tiered_without_above_rate_uses_base_for_all_tokens() {
let pricing = ModelPricing {
input_cost_per_token: Some(0.000001),
..Default::default()
};
let cost = compute_cost(&pricing, 250_000, 0, 0, 0, 0);
assert!((cost - (250_000.0 * 0.000001)).abs() < 1e-12);
}
#[test]
fn test_compute_cost_tiered_invalid_above_rate_falls_back_to_base() {
let pricing_negative = ModelPricing {
input_cost_per_token: Some(0.000001),
input_cost_per_token_above_200k_tokens: Some(-0.000002),
..Default::default()
};
let pricing_infinite = ModelPricing {
input_cost_per_token: Some(0.000001),
input_cost_per_token_above_200k_tokens: Some(f64::INFINITY),
..Default::default()
};
let pricing_nan = ModelPricing {
input_cost_per_token: Some(0.000001),
input_cost_per_token_above_200k_tokens: Some(f64::NAN),
..Default::default()
};
let expected = 200_001.0 * 0.000001;
assert!((compute_cost(&pricing_negative, 200_001, 0, 0, 0, 0) - expected).abs() < 1e-12);
assert!((compute_cost(&pricing_infinite, 200_001, 0, 0, 0, 0) - expected).abs() < 1e-12);
assert!((compute_cost(&pricing_nan, 200_001, 0, 0, 0, 0) - expected).abs() < 1e-12);
}
#[test]
fn test_compute_cost_tiered_reasoning_boundary_at_200k_uses_base_output_rate() {
let pricing = ModelPricing {
output_cost_per_token: Some(0.000003),
output_cost_per_token_above_200k_tokens: Some(0.000004),
..Default::default()
};
let cost = compute_cost(&pricing, 0, 199_999, 0, 0, 1);
let expected = 200_000.0 * 0.000003;
assert!((cost - expected).abs() < 1e-12);
}
#[test]
fn test_compute_cost_tiered_invalid_above_rate_falls_back_to_base_output_reasoning() {
let pricing_negative = ModelPricing {
output_cost_per_token: Some(0.000003),
output_cost_per_token_above_200k_tokens: Some(-0.000004),
..Default::default()
};
let pricing_infinite = ModelPricing {
output_cost_per_token: Some(0.000003),
output_cost_per_token_above_200k_tokens: Some(f64::INFINITY),
..Default::default()
};
let pricing_nan = ModelPricing {
output_cost_per_token: Some(0.000003),
output_cost_per_token_above_200k_tokens: Some(f64::NAN),
..Default::default()
};
let expected = 200_001.0 * 0.000003;
assert!((compute_cost(&pricing_negative, 0, 199_999, 0, 0, 2) - expected).abs() < 1e-12);
assert!((compute_cost(&pricing_infinite, 0, 199_999, 0, 0, 2) - expected).abs() < 1e-12);
assert!((compute_cost(&pricing_nan, 0, 199_999, 0, 0, 2) - expected).abs() < 1e-12);
}
#[test]
fn test_compute_cost_tiered_invalid_above_rate_falls_back_to_base_cache_read() {
let pricing_negative = ModelPricing {
cache_read_input_token_cost: Some(0.0000001),
cache_read_input_token_cost_above_200k_tokens: Some(-0.0000002),
..Default::default()
};
let pricing_infinite = ModelPricing {
cache_read_input_token_cost: Some(0.0000001),
cache_read_input_token_cost_above_200k_tokens: Some(f64::INFINITY),
..Default::default()
};
let pricing_nan = ModelPricing {
cache_read_input_token_cost: Some(0.0000001),
cache_read_input_token_cost_above_200k_tokens: Some(f64::NAN),
..Default::default()
};
let expected = 200_001.0 * 0.0000001;
assert!((compute_cost(&pricing_negative, 0, 0, 200_001, 0, 0) - expected).abs() < 1e-12);
assert!((compute_cost(&pricing_infinite, 0, 0, 200_001, 0, 0) - expected).abs() < 1e-12);
assert!((compute_cost(&pricing_nan, 0, 0, 200_001, 0, 0) - expected).abs() < 1e-12);
}
#[test]
fn test_compute_cost_tiered_invalid_above_rate_falls_back_to_base_cache_write() {
let pricing_negative = ModelPricing {
cache_creation_input_token_cost: Some(0.0000003),
cache_creation_input_token_cost_above_200k_tokens: Some(-0.0000004),
..Default::default()
};
let pricing_infinite = ModelPricing {
cache_creation_input_token_cost: Some(0.0000003),
cache_creation_input_token_cost_above_200k_tokens: Some(f64::INFINITY),
..Default::default()
};
let pricing_nan = ModelPricing {
cache_creation_input_token_cost: Some(0.0000003),
cache_creation_input_token_cost_above_200k_tokens: Some(f64::NAN),
..Default::default()
};
let expected = 200_001.0 * 0.0000003;
assert!((compute_cost(&pricing_negative, 0, 0, 0, 200_001, 0) - expected).abs() < 1e-12);
assert!((compute_cost(&pricing_infinite, 0, 0, 0, 200_001, 0) - expected).abs() < 1e-12);
assert!((compute_cost(&pricing_nan, 0, 0, 0, 200_001, 0) - expected).abs() < 1e-12);
}
#[test]
fn test_provider_prefixed_non_opus_prefers_exact_openrouter_without_tier_advantage() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-sonnet-4".into(),
ModelPricing {
input_cost_per_token: Some(0.000003),
output_cost_per_token: Some(0.000015),
..Default::default()
},
);
let mut openrouter = HashMap::new();
openrouter.insert(
"anthropic/claude-sonnet-4".into(),
ModelPricing {
input_cost_per_token: Some(0.0000123),
output_cost_per_token: Some(0.0000456),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, openrouter, HashMap::new());
let resolved = lookup.lookup("anthropic/claude-sonnet-4").unwrap();
assert_eq!(resolved.source, "OpenRouter");
assert_eq!(resolved.matched_key, "anthropic/claude-sonnet-4");
}
#[test]
fn test_provider_prefixed_exact_litellm_beats_stripped_generic_match() {
let mut litellm = HashMap::new();
litellm.insert(
"gpt-4".into(),
ModelPricing {
input_cost_per_token: Some(0.001),
..Default::default()
},
);
litellm.insert(
"openai/gpt-4".into(),
ModelPricing {
input_cost_per_token: Some(0.01),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let resolved = lookup.lookup("openai/gpt-4").unwrap();
assert_eq!(resolved.source, "LiteLLM");
assert_eq!(resolved.matched_key, "openai/gpt-4");
}
#[test]
fn test_provider_prefixed_override_requires_valid_base_and_above_pair() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-sonnet-4".into(),
ModelPricing {
input_cost_per_token: None,
input_cost_per_token_above_200k_tokens: Some(0.00002),
..Default::default()
},
);
let mut openrouter = HashMap::new();
openrouter.insert(
"anthropic/claude-sonnet-4".into(),
ModelPricing {
input_cost_per_token: Some(0.0000123),
output_cost_per_token: Some(0.0000456),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, openrouter, HashMap::new());
let resolved = lookup.lookup("anthropic/claude-sonnet-4").unwrap();
assert_eq!(resolved.source, "OpenRouter");
assert_eq!(resolved.matched_key, "anthropic/claude-sonnet-4");
}
#[test]
fn test_provider_prefixed_override_rejects_invalid_base_even_with_above() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-sonnet-4".into(),
ModelPricing {
input_cost_per_token: Some(f64::NAN),
input_cost_per_token_above_200k_tokens: Some(0.00002),
..Default::default()
},
);
let mut openrouter = HashMap::new();
openrouter.insert(
"anthropic/claude-sonnet-4".into(),
ModelPricing {
input_cost_per_token: Some(0.0000123),
output_cost_per_token: Some(0.0000456),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, openrouter, HashMap::new());
let resolved = lookup.lookup("anthropic/claude-sonnet-4").unwrap();
assert_eq!(resolved.source, "OpenRouter");
assert_eq!(resolved.matched_key, "anthropic/claude-sonnet-4");
}
#[test]
fn test_provider_prefixed_override_allows_zero_base_with_valid_above() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-sonnet-4".into(),
ModelPricing {
input_cost_per_token: Some(0.0),
input_cost_per_token_above_200k_tokens: Some(0.00002),
..Default::default()
},
);
let mut openrouter = HashMap::new();
openrouter.insert(
"anthropic/claude-sonnet-4".into(),
ModelPricing {
input_cost_per_token: Some(0.0000123),
output_cost_per_token: Some(0.0000456),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, openrouter, HashMap::new());
let resolved = lookup.lookup("anthropic/claude-sonnet-4").unwrap();
assert_eq!(resolved.source, "LiteLLM");
assert_eq!(resolved.matched_key, "claude-sonnet-4");
}
#[test]
fn test_provider_prefixed_cache_only_tier_keeps_exact_openrouter() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-sonnet-4".into(),
ModelPricing {
cache_read_input_token_cost: Some(0.0000001),
cache_read_input_token_cost_above_200k_tokens: Some(0.0000002),
cache_creation_input_token_cost: Some(0.0000003),
cache_creation_input_token_cost_above_200k_tokens: Some(0.0000004),
..Default::default()
},
);
let mut openrouter = HashMap::new();
openrouter.insert(
"anthropic/claude-sonnet-4".into(),
ModelPricing {
input_cost_per_token: Some(0.0000123),
output_cost_per_token: Some(0.0000456),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, openrouter, HashMap::new());
let resolved = lookup.lookup("anthropic/claude-sonnet-4").unwrap();
assert_eq!(resolved.source, "OpenRouter");
assert_eq!(resolved.matched_key, "anthropic/claude-sonnet-4");
}
#[test]
fn test_provider_prefixed_opus_4_6_prefers_litellm_tiered_pricing() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-opus-4-6".into(),
ModelPricing {
input_cost_per_token: Some(0.00001),
input_cost_per_token_above_200k_tokens: Some(0.00002),
output_cost_per_token: Some(0.00005),
output_cost_per_token_above_200k_tokens: Some(0.00006),
cache_read_input_token_cost: Some(0.000001),
cache_read_input_token_cost_above_200k_tokens: Some(0.000002),
cache_creation_input_token_cost: Some(0.000003),
cache_creation_input_token_cost_above_200k_tokens: Some(0.000004),
..Default::default()
},
);
let mut openrouter = HashMap::new();
openrouter.insert(
"anthropic/claude-opus-4-6".into(),
ModelPricing {
input_cost_per_token: Some(0.123),
output_cost_per_token: Some(0.456),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, openrouter, HashMap::new());
let resolved = lookup.lookup("anthropic/claude-opus-4-6").unwrap();
assert_eq!(resolved.source, "LiteLLM");
assert_eq!(resolved.matched_key, "claude-opus-4-6");
let cost = lookup.calculate_cost("anthropic/claude-opus-4-6", 200_001, 0, 0, 0, 0);
let expected = 200_000.0 * 0.00001 + 0.00002;
assert!((cost - expected).abs() < 1e-12);
}
#[test]
fn test_anthropic_prefixed_sonnet_variant_uses_canonical_pricing() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-sonnet-4-6".into(),
ModelPricing {
input_cost_per_token: Some(0.000003),
output_cost_per_token: Some(0.000015),
cache_read_input_token_cost: Some(0.0000003),
cache_creation_input_token_cost: Some(0.00000375),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let resolved = lookup.lookup("anthropic/claude-4-6-sonnet").unwrap();
assert_eq!(resolved.source, "LiteLLM");
assert_eq!(resolved.matched_key, "claude-sonnet-4-6");
let cost = lookup.calculate_cost("anthropic/claude-4-6-sonnet", 100, 20, 10, 5, 0);
let expected = 100.0 * 0.000003 + 20.0 * 0.000015 + 10.0 * 0.0000003 + 5.0 * 0.00000375;
assert!((cost - expected).abs() < 1e-12);
}
#[test]
fn test_anthropic_prefixed_haiku_variant_uses_canonical_pricing() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-haiku-4-5".into(),
ModelPricing {
input_cost_per_token: Some(0.0000008),
output_cost_per_token: Some(0.000004),
cache_read_input_token_cost: Some(0.00000008),
cache_creation_input_token_cost: Some(0.000001),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let resolved = lookup.lookup("anthropic/claude-4-5-haiku").unwrap();
assert_eq!(resolved.source, "LiteLLM");
assert_eq!(resolved.matched_key, "claude-haiku-4-5");
let cost = lookup.calculate_cost("anthropic/claude-4-5-haiku", 100, 20, 10, 5, 0);
let expected = 100.0 * 0.0000008 + 20.0 * 0.000004 + 10.0 * 0.00000008 + 5.0 * 0.000001;
assert!((cost - expected).abs() < 1e-12);
}
#[test]
fn test_none_pricing_reseller_does_not_shadow_real_entry() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-opus-4-6".into(),
ModelPricing {
input_cost_per_token: Some(0.000005),
output_cost_per_token: Some(0.000025),
cache_read_input_token_cost: Some(0.0000005),
cache_creation_input_token_cost: Some(0.00000625),
..Default::default()
},
);
litellm.insert(
"perplexity/anthropic/claude-opus-4-6".into(),
ModelPricing::default(),
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup.lookup_with_provider("claude-opus-4-6", Some("anthropic"));
assert!(result.is_some(), "lookup should succeed");
let result = result.unwrap();
assert_eq!(result.matched_key, "claude-opus-4-6");
assert!(result.pricing.input_cost_per_token.is_some());
let cost = lookup.calculate_cost("claude-opus-4-6", 100_000, 50_000, 0, 0, 0);
assert!(cost > 0.0, "cost should be positive, got {}", cost);
}
#[test]
fn test_none_pricing_provider_match_falls_back_to_priced_fuzzy_candidate() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-opus-4-6-20250301".into(),
ModelPricing {
input_cost_per_token: Some(0.000005),
output_cost_per_token: Some(0.000025),
..Default::default()
},
);
litellm.insert(
"perplexity/anthropic/claude-opus-4-6-20250301".into(),
ModelPricing::default(),
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup.lookup_with_provider("claude-opus-4-6-latest", Some("anthropic"));
assert!(result.is_some(), "lookup should succeed via fuzzy fallback");
let result = result.unwrap();
assert_eq!(result.matched_key, "claude-opus-4-6-20250301");
assert_eq!(result.source, "LiteLLM");
assert!(result.pricing.input_cost_per_token.is_some());
}
#[test]
fn test_none_pricing_exact_litellm_does_not_shadow_openrouter_model_part() {
let mut litellm = HashMap::new();
litellm.insert("claude-opus-4-6".into(), ModelPricing::default());
let mut openrouter = HashMap::new();
openrouter.insert(
"anthropic/claude-opus-4-6".into(),
ModelPricing {
input_cost_per_token: Some(0.000005),
output_cost_per_token: Some(0.000025),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, openrouter, HashMap::new());
let result = lookup.lookup("claude-opus-4-6").unwrap();
assert_eq!(result.source, "OpenRouter");
assert_eq!(result.matched_key, "anthropic/claude-opus-4-6");
let cost = lookup.calculate_cost("claude-opus-4-6", 100, 20, 0, 0, 0);
assert!(cost > 0.0, "cost should use priced fallback, got {cost}");
}
#[test]
fn test_none_pricing_provider_exact_does_not_shadow_stripped_priced_entry() {
let mut litellm = HashMap::new();
litellm.insert(
"anthropic/claude-sonnet-4-5".into(),
ModelPricing::default(),
);
litellm.insert(
"claude-sonnet-4-5".into(),
ModelPricing {
input_cost_per_token: Some(0.000003),
output_cost_per_token: Some(0.000015),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup.lookup("anthropic/claude-sonnet-4-5").unwrap();
assert_eq!(result.source, "LiteLLM");
assert_eq!(result.matched_key, "claude-sonnet-4-5");
let cost = lookup.calculate_cost("anthropic/claude-sonnet-4-5", 100, 20, 0, 0, 0);
assert!(
cost > 0.0,
"cost should use stripped priced entry, got {cost}"
);
}
#[test]
fn test_zero_pricing_exact_entry_is_usable() {
let mut litellm = HashMap::new();
litellm.insert(
"free-model".into(),
ModelPricing {
input_cost_per_token: Some(0.0),
output_cost_per_token: Some(0.0),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup.lookup("free-model").unwrap();
assert_eq!(result.matched_key, "free-model");
assert_eq!(lookup.calculate_cost("free-model", 100, 20, 0, 0, 0), 0.0);
}
#[test]
fn test_calculate_cost_tiered_all_buckets_with_reasoning_threshold_crossing() {
let mut litellm = HashMap::new();
litellm.insert(
"claude-opus-4-6".into(),
ModelPricing {
input_cost_per_token: Some(0.000001),
input_cost_per_token_above_200k_tokens: Some(0.000002),
output_cost_per_token: Some(0.000003),
output_cost_per_token_above_200k_tokens: Some(0.000004),
cache_read_input_token_cost: Some(0.0000001),
cache_read_input_token_cost_above_200k_tokens: Some(0.0000002),
cache_creation_input_token_cost: Some(0.0000003),
cache_creation_input_token_cost_above_200k_tokens: Some(0.0000004),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let cost = lookup.calculate_cost("claude-opus-4-6", 200_001, 199_999, 200_001, 200_001, 2);
let expected_input = 200_000.0 * 0.000001 + 0.000002;
let expected_output = 200_000.0 * 0.000003 + 0.000004; let expected_cache_read = 200_000.0 * 0.0000001 + 0.0000002;
let expected_cache_write = 200_000.0 * 0.0000003 + 0.0000004;
let expected =
expected_input + expected_output + expected_cache_read + expected_cache_write;
assert!((cost - expected).abs() < 1e-12);
}
#[test]
fn test_calculate_cost_unknown_model() {
let lookup = create_lookup();
let cost = lookup.calculate_cost("nonexistent-model", 1_000_000, 500_000, 0, 0, 0);
assert_eq!(cost, 0.0);
}
#[test]
fn test_antigravity_prefix_gemini_3_flash() {
let lookup = create_lookup();
let result = lookup.lookup("antigravity-gemini-3-flash").unwrap();
assert_eq!(result.matched_key, "vertex_ai/gemini-3-flash-preview");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_antigravity_prefix_gemini_3_pro() {
let lookup = create_lookup();
let result = lookup.lookup("antigravity-gemini-3-pro").unwrap();
assert_eq!(result.matched_key, "openrouter/google/gemini-3-pro-preview");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_antigravity_prefix_with_tier_suffix() {
let lookup = create_lookup();
let result = lookup.lookup("antigravity-gemini-3-pro-high").unwrap();
assert_eq!(result.matched_key, "openrouter/google/gemini-3-pro-preview");
}
#[test]
fn test_antigravity_prefix_claude() {
let lookup = create_lookup();
let result = lookup.lookup("antigravity-claude-sonnet-4-5").unwrap();
assert_eq!(result.matched_key, "claude-sonnet-4-5");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_antigravity_prefix_gpt() {
let lookup = create_lookup();
let result = lookup.lookup("antigravity-gpt-4o").unwrap();
assert_eq!(result.matched_key, "gpt-4o");
assert_eq!(result.source, "LiteLLM");
}
#[test]
fn test_antigravity_prefix_case_insensitive() {
let lookup = create_lookup();
let result = lookup.lookup("Antigravity-gpt-4o").unwrap();
assert_eq!(result.matched_key, "gpt-4o");
}
#[test]
fn test_antigravity_cost_calculation() {
let lookup = create_lookup();
let cost_with_prefix =
lookup.calculate_cost("antigravity-gpt-5.2", 1_000_000, 500_000, 0, 0, 0);
let cost_without_prefix = lookup.calculate_cost("gpt-5.2", 1_000_000, 500_000, 0, 0, 0);
assert!((cost_with_prefix - cost_without_prefix).abs() < 0.001);
assert!(cost_with_prefix > 0.0);
}
#[test]
fn test_unknown_prefix_generic() {
let lookup = create_lookup();
let result = lookup.lookup("myplugin-gpt-4o").unwrap();
assert_eq!(result.matched_key, "gpt-4o");
}
#[test]
fn test_unknown_prefix_two_segments() {
let lookup = create_lookup();
let result = lookup.lookup("router-v2-claude-sonnet-4-5").unwrap();
assert_eq!(result.matched_key, "claude-sonnet-4-5");
}
#[test]
fn test_unknown_suffix_thinking() {
let lookup = create_lookup();
let result = lookup.lookup("claude-sonnet-4-5-thinking").unwrap();
assert_eq!(result.matched_key, "claude-sonnet-4-5");
}
#[test]
fn test_unknown_suffix_two_segments() {
let lookup = create_lookup();
let result = lookup.lookup("claude-opus-4-5-thinking-pro").unwrap();
assert_eq!(result.matched_key, "claude-opus-4-5");
}
#[test]
fn test_prefix_and_suffix_combined() {
let lookup = create_lookup();
let result = lookup
.lookup("antigravity-claude-opus-4-5-thinking")
.unwrap();
assert_eq!(result.matched_key, "claude-opus-4-5");
}
#[test]
fn test_prefix_and_suffix_with_tier() {
let lookup = create_lookup();
let result = lookup
.lookup("antigravity-claude-opus-4-5-thinking-high")
.unwrap();
assert_eq!(result.matched_key, "claude-opus-4-5");
}
#[test]
fn test_no_false_positive_valid_model() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-4o-mini").unwrap();
assert_eq!(result.matched_key, "gpt-4o-mini");
}
#[test]
fn test_suffix_strip_high() {
let lookup = create_lookup();
let result = lookup.lookup("claude-sonnet-4-5-high").unwrap();
assert_eq!(result.matched_key, "claude-sonnet-4-5");
}
#[test]
fn test_suffix_strip_xhigh() {
let lookup = create_lookup();
let result = lookup.lookup("claude-sonnet-4-5-xhigh").unwrap();
assert_eq!(result.matched_key, "claude-sonnet-4-5");
}
#[test]
fn test_suffix_strip_low() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-4o-low").unwrap();
assert_eq!(result.matched_key, "gpt-4o");
}
#[test]
fn test_suffix_strip_codex() {
let lookup = create_lookup();
let result = lookup.lookup("gpt-5.2-codex").unwrap();
assert_eq!(result.matched_key, "gpt-5.2");
}
#[test]
fn test_provider_hint_empty_and_unknown_treated_as_none() {
let mut litellm = HashMap::new();
litellm.insert(
"gpt-4".into(),
ModelPricing {
input_cost_per_token: Some(0.001),
..Default::default()
},
);
litellm.insert(
"azure_ai/gpt-4".into(),
ModelPricing {
input_cost_per_token: Some(0.01),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let r_none = lookup.lookup_with_provider("gpt-4", None).unwrap();
let r_empty = lookup.lookup_with_provider("gpt-4", Some("")).unwrap();
let r_unknown = lookup
.lookup_with_provider("gpt-4", Some("unknown"))
.unwrap();
assert_eq!(r_none.matched_key, r_empty.matched_key);
assert_eq!(r_none.matched_key, r_unknown.matched_key);
}
#[test]
fn test_provider_hint_mistralai_matches_mistral_keys() {
let mut litellm = HashMap::new();
litellm.insert(
"mistralai/mistral-large".into(),
ModelPricing {
input_cost_per_token: Some(0.002),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup
.lookup_with_provider("mistral-large", Some("mistral"))
.unwrap();
assert_eq!(result.matched_key, "mistralai/mistral-large");
}
#[test]
fn test_provider_hint_minimax_matches_minimax_keys() {
let mut litellm = HashMap::new();
litellm.insert(
"minimax/minimax-m2.1".into(),
ModelPricing {
input_cost_per_token: Some(0.002),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let result = lookup
.lookup_with_provider("MiniMax-M2.1", Some("minimax"))
.unwrap();
assert_eq!(result.matched_key, "minimax/minimax-m2.1");
}
#[test]
fn test_prefixed_model_with_conflicting_provider_uses_provider_aware_path() {
let mut litellm = HashMap::new();
litellm.insert(
"openai/gpt-4".into(),
ModelPricing {
input_cost_per_token: Some(0.01),
..Default::default()
},
);
litellm.insert(
"azure/openai/gpt-4".into(),
ModelPricing {
input_cost_per_token: Some(0.02),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let r_azure = lookup
.lookup_with_provider("openai/gpt-4", Some("azure"))
.unwrap();
assert_eq!(
r_azure.matched_key, "azure/openai/gpt-4",
"should prefer azure key when provider_id=azure"
);
let r_openai = lookup
.lookup_with_provider("openai/gpt-4", Some("openai"))
.unwrap();
assert_eq!(
r_openai.matched_key, "openai/gpt-4",
"should use exact prefixed key when provider_id matches prefix"
);
let r_none = lookup.lookup_with_provider("openai/gpt-4", None).unwrap();
assert_eq!(
r_none.matched_key, "openai/gpt-4",
"should use exact prefixed key when no provider hint"
);
}
#[test]
fn test_prefixed_model_conflicting_provider_falls_back_to_stripped() {
let mut litellm = HashMap::new();
litellm.insert(
"openai/gpt-4".into(),
ModelPricing {
input_cost_per_token: Some(0.01),
..Default::default()
},
);
litellm.insert(
"gpt-4".into(),
ModelPricing {
input_cost_per_token: Some(0.001),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let r = lookup
.lookup_with_provider("openai/gpt-4", Some("azure"))
.unwrap();
assert_eq!(
r.matched_key, "gpt-4",
"with no azure-specific key, should fall back to stripped generic"
);
}
#[test]
fn test_compound_provider_hint_prefers_reseller_over_prefix() {
let mut litellm = HashMap::new();
litellm.insert(
"openai/gpt-4".into(),
ModelPricing {
input_cost_per_token: Some(0.01),
..Default::default()
},
);
litellm.insert(
"azure/openai/gpt-4".into(),
ModelPricing {
input_cost_per_token: Some(0.02),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let r = lookup
.lookup_with_provider("openai/gpt-4", Some("azure/openai"))
.unwrap();
assert_eq!(
r.matched_key, "azure/openai/gpt-4",
"compound hint azure/openai should prefer azure-specific key over openai/ prefix"
);
}
#[test]
fn test_source_and_provider_normalizes_unknown_hint() {
let mut litellm = HashMap::new();
litellm.insert(
"openai/gpt-4".into(),
ModelPricing {
input_cost_per_token: Some(0.01),
..Default::default()
},
);
let lookup = PricingLookup::new(litellm, HashMap::new(), HashMap::new());
let r_unknown = lookup
.lookup_with_source_and_provider("openai/gpt-4", None, Some("unknown"))
.unwrap();
let r_none = lookup
.lookup_with_source_and_provider("openai/gpt-4", None, None)
.unwrap();
assert_eq!(
r_unknown.matched_key, r_none.matched_key,
"unknown hint via source_and_provider should behave like None"
);
}
}