use crate::pricing::CostEstimator;
use crate::types::{BudgetLimitKind, TokenUsage, UsageLimits};
use agent_sdk_foundation::audit::AuditProvenance;
use agent_sdk_foundation::llm::Usage;
use std::borrow::Cow;
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum UsageScope {
Call,
Aggregate,
}
#[must_use]
pub(super) fn estimate_cost_usd(
pricing: Option<&dyn CostEstimator>,
provenance: &AuditProvenance,
usage: &TokenUsage,
) -> Option<f64> {
estimate_scoped(pricing, provenance, usage, UsageScope::Call)
}
#[must_use]
pub(super) fn estimate_aggregate_cost_usd(
pricing: Option<&dyn CostEstimator>,
provenance: &AuditProvenance,
usage: &TokenUsage,
) -> Option<f64> {
estimate_scoped(pricing, provenance, usage, UsageScope::Aggregate)
}
fn estimate_scoped(
pricing: Option<&dyn CostEstimator>,
provenance: &AuditProvenance,
usage: &TokenUsage,
scope: UsageScope,
) -> Option<f64> {
let usage = Usage {
input_tokens: usage.input_tokens,
output_tokens: usage.output_tokens,
cached_input_tokens: usage.cached_input_tokens,
cache_creation_input_tokens: usage.cache_creation_input_tokens,
};
let provider = provenance.provider.as_str();
let model = provenance.model.as_str();
if let Some(estimator) = pricing {
let candidates: Vec<(Cow<'_, str>, Cow<'_, str>)> =
catalog_candidates(provider, model).collect();
let override_estimate = |provider: &str, model: &str| match scope {
UsageScope::Call => estimator.estimate_override_cost_usd(provider, model, &usage),
UsageScope::Aggregate => {
estimator.estimate_override_aggregate_cost_usd(provider, model, &usage)
}
};
let feed_estimate = |provider: &str, model: &str| match scope {
UsageScope::Call => estimator.estimate_feed_cost_usd(provider, model, &usage),
UsageScope::Aggregate => {
estimator.estimate_feed_aggregate_cost_usd(provider, model, &usage)
}
};
let first_override =
candidates
.iter()
.enumerate()
.find_map(|(index, (provider, model))| {
override_estimate(provider, model)
.filter(|cost| is_usable_estimate(*cost))
.map(|cost| (index, cost))
});
if let Some((index, override_cost)) = first_override {
let more_specific_feed = candidates[..index].iter().any(|(provider, model)| {
feed_estimate(provider, model).is_some_and(is_usable_estimate)
});
if !more_specific_feed {
return Some(override_cost);
}
}
let best = candidates
.iter()
.filter_map(|(provider, model)| feed_estimate(provider, model))
.filter(|cost| is_usable_estimate(*cost))
.max_by(f64::total_cmp);
if let Some(cost) = best {
return Some(cost);
}
}
static_candidates(provider, model).find_map(|(provider, model)| {
crate::model_capabilities::get_model_capabilities(provider, model)?
.estimate_cost_usd(&usage)
})
}
fn is_usable_estimate(cost: f64) -> bool {
!cost.is_nan() && cost != f64::NEG_INFINITY
}
pub(super) const fn durable_cost(cost: f64) -> f64 {
cost.min(f64::MAX)
}
fn catalog_candidates<'a>(
provider: &'a str,
model: &'a str,
) -> impl Iterator<Item = (Cow<'a, str>, Cow<'a, str>)> {
std::iter::once((Cow::Borrowed(provider), Cow::Borrowed(model)))
.chain(
gateway_model_candidates(provider, model)
.map(|(service, prefixed)| (Cow::Borrowed(service), Cow::Owned(prefixed))),
)
.chain(
feed_service_keys(provider, model)
.map(move |service| (Cow::Borrowed(service), Cow::Borrowed(model))),
)
.chain(
backend_aliases(provider, model)
.map(move |backend| (Cow::Borrowed(backend), Cow::Borrowed(model))),
)
.chain(feed_slug_candidates(model).map(|(p, m)| (Cow::Borrowed(p), Cow::Borrowed(m))))
}
fn gateway_model_candidates<'a>(
provider: &'a str,
model: &str,
) -> impl Iterator<Item = (&'a str, String)> {
let backends: &'static [&'static str] = match provider {
"cloudflare-ai-gateway" => &["anthropic", "openai"],
_ => &[],
};
backends
.iter()
.map(move |backend| (provider, format!("{backend}/{model}")))
}
fn feed_service_keys(provider: &str, model: &str) -> impl Iterator<Item = &'static str> {
let services: &'static [&'static str] = match provider {
"vertex" if model.starts_with("claude-") => &["google-vertex-anthropic", "google-vertex"],
"vertex" => &["google-vertex"],
_ => &[],
};
services.iter().copied()
}
fn feed_slug_candidates(model: &str) -> impl Iterator<Item = (&str, &str)> {
let route_key = model.contains('/').then_some(("openrouter", model));
route_key.into_iter().chain(vendor_slug_key(model))
}
fn static_candidates<'a>(
provider: &'a str,
model: &'a str,
) -> impl Iterator<Item = (&'a str, &'a str)> {
std::iter::once((provider, model))
.chain(backend_aliases(provider, model).map(move |backend| (backend, model)))
}
fn backend_aliases(provider: &str, model: &str) -> impl Iterator<Item = &'static str> {
let backends: &'static [&'static str] = match provider {
"openai-responses" | "openai-codex" => &["openai"],
"vertex" if model.starts_with("claude-") => &["anthropic"],
"vertex" => &["gemini"],
"cloudflare-ai-gateway" | "record-replay" => &["anthropic", "openai", "gemini"],
_ => &[],
};
backends.iter().copied()
}
fn vendor_slug_key(model: &str) -> Option<(&str, &str)> {
let (vendor, model_id) = model.split_once('/')?;
Some((normalize_slug_vendor(vendor), model_id))
}
fn normalize_slug_vendor(vendor: &str) -> &str {
match vendor {
"google" => "gemini",
"z-ai" => "zai",
"x-ai" => "xai",
"mistralai" => "mistral",
other => other,
}
}
#[must_use]
pub(super) const fn usage_is_zero(usage: &TokenUsage) -> bool {
usage.input_tokens == 0
&& usage.output_tokens == 0
&& usage.cached_input_tokens == 0
&& usage.cache_creation_input_tokens == 0
}
pub(super) fn accumulate_cost(
state: &mut crate::types::AgentState,
pricing: Option<&dyn CostEstimator>,
provenance: &AuditProvenance,
pre_delta_total: &TokenUsage,
delta: &TokenUsage,
delta_scope: UsageScope,
) {
if state.accumulated_cost_usd.is_none() && !usage_is_zero(pre_delta_total) {
state.accumulated_cost_usd =
estimate_aggregate_cost_usd(pricing, provenance, pre_delta_total).map(durable_cost);
}
let delta_cost = match delta_scope {
UsageScope::Call => estimate_cost_usd(pricing, provenance, delta),
UsageScope::Aggregate => estimate_aggregate_cost_usd(pricing, provenance, delta),
};
if let Some(delta_cost) = delta_cost {
state.accumulated_cost_usd = Some(durable_cost(
state.accumulated_cost_usd.unwrap_or(0.0) + delta_cost,
));
}
}
#[must_use]
pub(super) fn run_cost_usd(
accumulated: Option<f64>,
pricing: Option<&dyn CostEstimator>,
provenance: &AuditProvenance,
usage: &TokenUsage,
) -> Option<f64> {
accumulated
.or_else(|| estimate_aggregate_cost_usd(pricing, provenance, usage))
.map(durable_cost)
}
#[must_use]
pub(super) fn status(
usage_limits: Option<&UsageLimits>,
pricing: Option<&dyn CostEstimator>,
provenance: &AuditProvenance,
usage: &TokenUsage,
accumulated_cost_usd: Option<f64>,
) -> Option<(BudgetLimitKind, Option<f64>)> {
let limits = usage_limits?;
let cost = run_cost_usd(accumulated_cost_usd, pricing, provenance, usage);
let limit = check_budget(limits, usage, cost)?;
Some((limit, cost))
}
#[must_use]
fn total_tokens(usage: &TokenUsage) -> u64 {
u64::from(usage.input_tokens).saturating_add(u64::from(usage.output_tokens))
}
#[must_use]
pub(super) fn check_budget(
limits: &UsageLimits,
usage: &TokenUsage,
cost: Option<f64>,
) -> Option<BudgetLimitKind> {
if let Some(max_total_tokens) = limits.max_total_tokens
&& total_tokens(usage) > max_total_tokens
{
return Some(BudgetLimitKind::TotalTokens);
}
if let (Some(max_cost_usd), Some(cost)) = (limits.max_cost_usd, cost)
&& cost > max_cost_usd
{
return Some(BudgetLimitKind::CostUsd);
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{AgentState, ThreadId};
const FEED_MODEL: &str = "feed-only-model";
struct FeedPricing;
impl CostEstimator for FeedPricing {
fn estimate_cost_usd(&self, provider: &str, model: &str, usage: &Usage) -> Option<f64> {
(provider == "openai" && model == FEED_MODEL).then(|| {
(f64::from(usage.input_tokens) / 1_000_000.0)
.mul_add(10.0, f64::from(usage.output_tokens) / 1_000_000.0 * 20.0)
})
}
}
struct EmptyPricing;
impl CostEstimator for EmptyPricing {
fn estimate_cost_usd(&self, _provider: &str, _model: &str, _usage: &Usage) -> Option<f64> {
None
}
}
struct NanThenFinitePricing;
impl CostEstimator for NanThenFinitePricing {
fn estimate_cost_usd(&self, provider: &str, _model: &str, _usage: &Usage) -> Option<f64> {
match provider {
"openrouter" => Some(f64::NAN),
"vendor" => Some(2.5),
_ => None,
}
}
}
struct NanThenPosInfPricing;
impl CostEstimator for NanThenPosInfPricing {
fn estimate_cost_usd(&self, provider: &str, _model: &str, _usage: &Usage) -> Option<f64> {
match provider {
"openrouter" => Some(f64::NAN),
"vendor" => Some(f64::INFINITY),
_ => None,
}
}
}
struct NegInfOnlyPricing;
impl CostEstimator for NegInfOnlyPricing {
fn estimate_cost_usd(&self, provider: &str, _model: &str, _usage: &Usage) -> Option<f64> {
(provider == "vendor").then_some(f64::NEG_INFINITY)
}
}
fn one_million_each() -> TokenUsage {
TokenUsage {
input_tokens: 1_000_000,
output_tokens: 1_000_000,
..Default::default()
}
}
#[test]
fn non_finite_estimate_does_not_poison_the_max() -> anyhow::Result<()> {
use anyhow::Context;
let provenance = AuditProvenance::new("openai", "vendor/model");
let usage = one_million_each();
let cost = estimate_cost_usd(Some(&NanThenFinitePricing), &provenance, &usage)
.context("a finite candidate must price the call")?;
assert!(cost.is_finite(), "NaN leaked into the estimate: {cost}");
assert!((cost - 2.5).abs() < 1e-9, "unexpected cost: {cost}");
let limits = UsageLimits {
max_cost_usd: Some(1.0),
..Default::default()
};
let (limit, _) = status(
Some(&limits),
Some(&NanThenFinitePricing),
&provenance,
&usage,
None,
)
.context("the finite estimate must trip the cost limit")?;
assert_eq!(limit, BudgetLimitKind::CostUsd);
Ok(())
}
#[test]
fn positive_infinity_estimate_survives_and_trips_the_cap() -> anyhow::Result<()> {
use anyhow::Context;
let provenance = AuditProvenance::new("openai", "vendor/model");
let usage = one_million_each();
let cost = estimate_cost_usd(Some(&NanThenPosInfPricing), &provenance, &usage)
.context("+inf must survive as the estimate")?;
assert!(
cost.is_infinite() && cost > 0.0,
"expected +inf, got {cost}"
);
let limits = UsageLimits {
max_cost_usd: Some(1_000_000.0),
..Default::default()
};
let (limit, _) = status(
Some(&limits),
Some(&NanThenPosInfPricing),
&provenance,
&usage,
None,
)
.context("+inf must trip a finite cost cap")?;
assert_eq!(limit, BudgetLimitKind::CostUsd);
let mut state = AgentState::new(ThreadId::new());
accumulate_cost(
&mut state,
Some(&NanThenPosInfPricing),
&provenance,
&TokenUsage::default(),
&usage,
UsageScope::Call,
);
let accumulated = state
.accumulated_cost_usd
.context("the +inf delta must accumulate")?;
assert!(
accumulated.is_finite(),
"accumulator not clamped: {accumulated}"
);
assert!((accumulated - f64::MAX).abs() < f64::EPSILON);
let json = serde_json::to_string(&state).context("serialize state")?;
let restored: crate::types::AgentState =
serde_json::from_str(&json).context("deserialize state")?;
assert_eq!(restored.accumulated_cost_usd, Some(f64::MAX));
let (limit, _) = status(
Some(&limits),
Some(&NanThenPosInfPricing),
&provenance,
&usage,
restored.accumulated_cost_usd,
)
.context("the restored accumulator must keep tripping")?;
assert_eq!(limit, BudgetLimitKind::CostUsd);
Ok(())
}
#[test]
fn negative_infinity_estimate_is_dropped() {
let provenance = AuditProvenance::new("openai", "vendor/model");
let usage = one_million_each();
assert!(estimate_cost_usd(Some(&NegInfOnlyPricing), &provenance, &usage).is_none());
}
#[test]
fn feed_only_model_is_unpriced_without_a_cost_estimator() {
let provenance = AuditProvenance::new("openai", FEED_MODEL);
assert!(estimate_cost_usd(None, &provenance, &one_million_each()).is_none());
}
#[test]
fn feed_only_model_accrues_cost_through_the_estimator() -> anyhow::Result<()> {
use anyhow::Context;
let provenance = AuditProvenance::new("openai", FEED_MODEL);
let cost = estimate_cost_usd(Some(&FeedPricing), &provenance, &one_million_each())
.context("the estimator prices this model")?;
assert!((cost - 30.0).abs() < 1e-9, "unexpected cost: {cost}");
Ok(())
}
#[test]
fn feed_only_model_trips_the_cost_limit() -> anyhow::Result<()> {
use anyhow::Context;
let limits = UsageLimits {
max_cost_usd: Some(1.0),
..Default::default()
};
let provenance = AuditProvenance::new("openai", FEED_MODEL);
let usage = one_million_each();
assert!(status(Some(&limits), None, &provenance, &usage, None).is_none());
let (limit, cost) = status(Some(&limits), Some(&FeedPricing), &provenance, &usage, None)
.context("feed pricing must trip the cost limit")?;
assert_eq!(limit, BudgetLimitKind::CostUsd);
let cost = cost.context("the tripped limit carries the estimate")?;
assert!((cost - 30.0).abs() < 1e-9, "unexpected cost: {cost}");
Ok(())
}
struct SlugPricing;
impl CostEstimator for SlugPricing {
fn estimate_cost_usd(&self, provider: &str, model: &str, usage: &Usage) -> Option<f64> {
(provider == "moonshotai" && model == "kimi-9-turbo")
.then(|| f64::from(usage.input_tokens) + f64::from(usage.output_tokens))
}
}
#[test]
fn estimator_prices_a_vendor_slug_model_under_its_feed_key() -> anyhow::Result<()> {
use anyhow::Context;
let provenance = AuditProvenance::new("openai", "moonshotai/kimi-9-turbo");
let usage = TokenUsage {
input_tokens: 20,
output_tokens: 10,
..Default::default()
};
let cost = estimate_cost_usd(Some(&SlugPricing), &provenance, &usage)
.context("the slug must resolve to the catalog's vendor/model key")?;
assert!((cost - 30.0).abs() < 1e-9, "unexpected cost: {cost}");
Ok(())
}
#[test]
fn estimator_prices_an_aliased_provenance() -> anyhow::Result<()> {
use anyhow::Context;
let provenance = AuditProvenance::new("openai-codex", FEED_MODEL);
let cost = estimate_cost_usd(Some(&FeedPricing), &provenance, &one_million_each())
.context("the aliased provenance must resolve to the openai entry")?;
assert!((cost - 30.0).abs() < 1e-9, "unexpected cost: {cost}");
Ok(())
}
#[test]
fn static_table_still_prices_what_the_estimator_misses() -> anyhow::Result<()> {
use anyhow::Context;
let provenance = AuditProvenance::new("openai", "gpt-4o");
let usage = TokenUsage {
input_tokens: 2_000,
output_tokens: 1_000,
..Default::default()
};
let with_empty_catalog = estimate_cost_usd(Some(&EmptyPricing), &provenance, &usage)
.context("an estimator miss must fall back to the static table")?;
let statically_priced = estimate_cost_usd(None, &provenance, &usage)
.context("gpt-4o is priced in the static table")?;
assert!((with_empty_catalog - statically_priced).abs() < 1e-12);
Ok(())
}
#[test]
fn accumulate_cost_uses_the_estimator_for_feed_priced_calls() -> anyhow::Result<()> {
use anyhow::Context;
let provenance = AuditProvenance::new("openai", FEED_MODEL);
let mut state = AgentState::new(ThreadId::new());
accumulate_cost(
&mut state,
Some(&FeedPricing),
&provenance,
&TokenUsage::default(),
&one_million_each(),
UsageScope::Call,
);
let accumulated = state
.accumulated_cost_usd
.context("a feed-priced call must accumulate cost")?;
assert!(
(accumulated - 30.0).abs() < 1e-9,
"unexpected accumulated cost: {accumulated}"
);
Ok(())
}
#[test]
fn estimate_cost_for_gpt_4o_matches_pricing() -> anyhow::Result<()> {
use anyhow::Context;
let provenance = AuditProvenance::new("openai", "gpt-4o");
let usage = TokenUsage {
input_tokens: 2_000,
output_tokens: 1_000,
..Default::default()
};
let cost = estimate_cost_usd(None, &provenance, &usage).context("gpt-4o has pricing")?;
assert!((cost - 0.0075).abs() < 1e-9, "unexpected cost: {cost}");
Ok(())
}
#[test]
fn estimate_cost_resolves_openai_responses_alias() -> anyhow::Result<()> {
use anyhow::Context;
let usage = TokenUsage {
input_tokens: 2_000,
output_tokens: 1_000,
..Default::default()
};
let canonical = estimate_cost_usd(None, &AuditProvenance::new("openai", "gpt-4o"), &usage)
.context("gpt-4o has pricing")?;
let aliased = estimate_cost_usd(
None,
&AuditProvenance::new("openai-responses", "gpt-4o"),
&usage,
)
.context("openai-responses must resolve to the openai catalog entry")?;
assert!((aliased - canonical).abs() < 1e-12);
Ok(())
}
#[test]
fn estimate_cost_resolves_openai_codex_alias() -> anyhow::Result<()> {
use anyhow::Context;
let usage = TokenUsage {
input_tokens: 2_000,
output_tokens: 1_000,
..Default::default()
};
let canonical = estimate_cost_usd(None, &AuditProvenance::new("openai", "gpt-4o"), &usage)
.context("gpt-4o has pricing")?;
let aliased = estimate_cost_usd(
None,
&AuditProvenance::new("openai-codex", "gpt-4o"),
&usage,
)
.context("openai-codex must resolve to the openai catalog entry")?;
assert!((aliased - canonical).abs() < 1e-12);
Ok(())
}
#[test]
fn estimate_cost_resolves_vertex_claude_alias() -> anyhow::Result<()> {
use anyhow::Context;
let usage = TokenUsage {
input_tokens: 2_000,
output_tokens: 1_000,
..Default::default()
};
let canonical = estimate_cost_usd(
None,
&AuditProvenance::new("anthropic", "claude-fable-5"),
&usage,
)
.context("claude-fable-5 has pricing")?;
let aliased = estimate_cost_usd(
None,
&AuditProvenance::new("vertex", "claude-fable-5"),
&usage,
)
.context("vertex claude-* must resolve to the anthropic catalog entry")?;
assert!((aliased - canonical).abs() < 1e-12);
Ok(())
}
#[test]
fn estimate_cost_resolves_vertex_gemini_alias() -> anyhow::Result<()> {
use anyhow::Context;
let usage = TokenUsage {
input_tokens: 2_000,
output_tokens: 1_000,
..Default::default()
};
let canonical = estimate_cost_usd(
None,
&AuditProvenance::new("gemini", "gemini-3.1-pro"),
&usage,
)
.context("gemini-3.1-pro has pricing")?;
let aliased = estimate_cost_usd(
None,
&AuditProvenance::new("vertex", "gemini-3.1-pro"),
&usage,
)
.context("vertex non-claude must resolve to the gemini catalog entry")?;
assert!((aliased - canonical).abs() < 1e-12);
Ok(())
}
#[test]
fn estimate_cost_resolves_cloudflare_gateway_claude_alias() -> anyhow::Result<()> {
use anyhow::Context;
let usage = TokenUsage {
input_tokens: 2_000,
output_tokens: 1_000,
..Default::default()
};
let canonical = estimate_cost_usd(
None,
&AuditProvenance::new("anthropic", "claude-fable-5"),
&usage,
)
.context("claude-fable-5 has pricing")?;
let aliased = estimate_cost_usd(
None,
&AuditProvenance::new("cloudflare-ai-gateway", "claude-fable-5"),
&usage,
)
.context("gateway claude-* must resolve to the anthropic catalog entry")?;
assert!((aliased - canonical).abs() < 1e-12);
Ok(())
}
#[test]
fn estimate_cost_resolves_cloudflare_gateway_openai_alias() -> anyhow::Result<()> {
use anyhow::Context;
let usage = TokenUsage {
input_tokens: 2_000,
output_tokens: 1_000,
..Default::default()
};
let canonical = estimate_cost_usd(None, &AuditProvenance::new("openai", "gpt-4o"), &usage)
.context("gpt-4o has pricing")?;
let aliased = estimate_cost_usd(
None,
&AuditProvenance::new("cloudflare-ai-gateway", "gpt-4o"),
&usage,
)
.context("gateway gpt-* must resolve to the openai catalog entry")?;
assert!((aliased - canonical).abs() < 1e-12);
Ok(())
}
#[test]
fn estimate_cost_resolves_record_replay_anthropic_alias() -> anyhow::Result<()> {
use anyhow::Context;
let usage = TokenUsage {
input_tokens: 2_000,
output_tokens: 1_000,
..Default::default()
};
let canonical = estimate_cost_usd(
None,
&AuditProvenance::new("anthropic", "claude-fable-5"),
&usage,
)
.context("claude-fable-5 has pricing")?;
let aliased = estimate_cost_usd(
None,
&AuditProvenance::new("record-replay", "claude-fable-5"),
&usage,
)
.context("record-replay claude-* must resolve to the anthropic catalog entry")?;
assert!((aliased - canonical).abs() < 1e-12);
Ok(())
}
#[test]
fn estimate_cost_resolves_record_replay_openai_alias() -> anyhow::Result<()> {
use anyhow::Context;
let usage = TokenUsage {
input_tokens: 2_000,
output_tokens: 1_000,
..Default::default()
};
let canonical = estimate_cost_usd(None, &AuditProvenance::new("openai", "gpt-4o"), &usage)
.context("gpt-4o has pricing")?;
let aliased = estimate_cost_usd(
None,
&AuditProvenance::new("record-replay", "gpt-4o"),
&usage,
)
.context("record-replay gpt-* must resolve to the openai catalog entry")?;
assert!((aliased - canonical).abs() < 1e-12);
Ok(())
}
#[test]
fn estimate_cost_resolves_record_replay_gemini_alias() -> anyhow::Result<()> {
use anyhow::Context;
let usage = TokenUsage {
input_tokens: 2_000,
output_tokens: 1_000,
..Default::default()
};
let canonical = estimate_cost_usd(
None,
&AuditProvenance::new("gemini", "gemini-3.1-pro"),
&usage,
)
.context("gemini-3.1-pro has pricing")?;
let aliased = estimate_cost_usd(
None,
&AuditProvenance::new("record-replay", "gemini-3.1-pro"),
&usage,
)
.context("record-replay gemini-* must resolve to the gemini catalog entry")?;
assert!((aliased - canonical).abs() < 1e-12);
Ok(())
}
#[test]
fn estimate_cost_is_none_for_unknown_model() {
let provenance = AuditProvenance::new("mock", "mock-model");
let usage = TokenUsage {
input_tokens: 100,
output_tokens: 100,
..Default::default()
};
assert!(estimate_cost_usd(None, &provenance, &usage).is_none());
}
#[test]
fn token_budget_trips_when_exceeded() {
let limits = UsageLimits {
max_total_tokens: Some(100),
..Default::default()
};
let under = TokenUsage {
input_tokens: 50,
output_tokens: 50,
..Default::default()
};
assert!(check_budget(&limits, &under, None).is_none());
let over = TokenUsage {
input_tokens: 60,
output_tokens: 60,
..Default::default()
};
assert_eq!(
check_budget(&limits, &over, None),
Some(BudgetLimitKind::TotalTokens)
);
}
#[test]
fn cost_budget_trips_when_exceeded() {
let limits = UsageLimits {
max_cost_usd: Some(1.0),
..Default::default()
};
let usage = TokenUsage::default();
assert!(check_budget(&limits, &usage, Some(0.5)).is_none());
assert_eq!(
check_budget(&limits, &usage, Some(1.5)),
Some(BudgetLimitKind::CostUsd)
);
assert!(check_budget(&limits, &usage, None).is_none());
}
}
#[cfg(all(test, feature = "model-discovery"))]
mod catalog_tests {
use super::*;
use crate::types::{AgentState, ThreadId};
use agent_sdk_providers::model_catalog::{parse_modelsdev, parse_openrouter};
use agent_sdk_providers::{CatalogEntry, ModelCatalogSource, ModelRegistry};
use anyhow::{Context, Result};
use async_trait::async_trait;
const OPENROUTER_SLUG_FIXTURE: &str = r#"{
"data": [
{
"id": "z-ai/glm-9-turbo",
"context_length": 200000,
"pricing": { "prompt": "0.000001", "completion": "0.000002" }
}
]
}"#;
const MODELSDEV_PARTIAL_FIXTURE: &str = r#"{
"openai": {
"id": "openai",
"models": {
"gpt-4o": {
"id": "gpt-4o",
"cost": { "input": 1000 }
}
}
}
}"#;
const OPENROUTER_DIRECT_MODELS_FIXTURE: &str = r#"{
"data": [
{
"id": "openai/gpt-4o",
"context_length": 128000,
"pricing": { "prompt": "0.00001", "completion": "0.000015" }
}
]
}"#;
const OPENROUTER_REPRICED_FIXTURE: &str = r#"{
"data": [
{
"id": "z-ai/glm-5.1",
"context_length": 202752,
"pricing": { "prompt": "0.0000098", "completion": "0.0000308" }
}
]
}"#;
const MODELSDEV_ROUTE_FIXTURE: &str = r#"{
"openrouter": {
"id": "openrouter",
"models": {
"moonshotai/kimi-k2.6": {
"id": "moonshotai/kimi-k2.6",
"cost": { "input": 0.95, "output": 4.0 }
}
}
},
"moonshotai": {
"id": "moonshotai",
"models": {
"kimi-k2.6": {
"id": "kimi-k2.6",
"cost": { "input": 0.66, "output": 3.41 }
}
}
}
}"#;
struct FixtureFeed(Vec<CatalogEntry>);
#[async_trait]
impl ModelCatalogSource for FixtureFeed {
async fn fetch(&self) -> Result<Vec<CatalogEntry>> {
Ok(self.0.clone())
}
}
async fn registry_from_entries(entries: Vec<CatalogEntry>) -> Result<ModelRegistry> {
let registry = ModelRegistry::new();
registry.refresh(&FixtureFeed(entries)).await?;
Ok(registry)
}
async fn registry_from(fixture: &str) -> Result<ModelRegistry> {
let registry = ModelRegistry::new();
registry
.refresh(&FixtureFeed(parse_openrouter(fixture)?))
.await?;
Ok(registry)
}
#[tokio::test]
async fn feed_only_slug_model_accrues_cost_and_trips_the_budget() -> Result<()> {
let registry = registry_from(OPENROUTER_SLUG_FIXTURE).await?;
let usage = TokenUsage {
input_tokens: 1_000_000,
output_tokens: 1_000_000,
..Default::default()
};
let provenance = AuditProvenance::new("openai", "z-ai/glm-9-turbo");
assert!(estimate_cost_usd(None, &provenance, &usage).is_none());
let cost = estimate_cost_usd(Some(®istry), &provenance, &usage)
.context("the feed prices this slug model")?;
assert!((cost - 3.0).abs() < 1e-9, "unexpected cost: {cost}");
let limits = UsageLimits {
max_cost_usd: Some(1.0),
..Default::default()
};
let (limit, _) = status(Some(&limits), Some(®istry), &provenance, &usage, None)
.context("feed pricing must trip the cost limit")?;
assert_eq!(limit, BudgetLimitKind::CostUsd);
Ok(())
}
#[tokio::test]
async fn partially_priced_feed_row_yields_to_the_static_table() -> Result<()> {
let registry = registry_from_entries(parse_modelsdev(MODELSDEV_PARTIAL_FIXTURE)?).await?;
let usage = TokenUsage {
input_tokens: 2_000,
output_tokens: 1_000,
..Default::default()
};
let provenance = AuditProvenance::new("openai", "gpt-4o");
let cost = estimate_cost_usd(Some(®istry), &provenance, &usage)
.context("the static table prices gpt-4o")?;
let statically_priced = estimate_cost_usd(None, &provenance, &usage)
.context("gpt-4o is priced in the static table")?;
assert!((cost - statically_priced).abs() < 1e-12);
assert!((cost - 0.0075).abs() < 1e-9, "unexpected cost: {cost}");
Ok(())
}
#[tokio::test]
async fn override_authority_beats_a_higher_feed_row() -> Result<()> {
use crate::model_capabilities::Pricing;
let registry = ModelRegistry::new().with_override(
"openrouter",
"vendor/model",
CatalogEntry {
provider: "openrouter".to_owned(),
model_id: "vendor/model".to_owned(),
context_window: None,
max_output_tokens: None,
pricing: Some(Pricing::flat(0.05, 0.05)),
pricing_tiers: Vec::new(),
supports_thinking: None,
},
);
registry
.refresh(&FixtureFeed(vec![CatalogEntry {
provider: "vendor".to_owned(),
model_id: "model".to_owned(),
context_window: None,
max_output_tokens: None,
pricing: Some(Pricing::flat(2.5, 2.5)),
pricing_tiers: Vec::new(),
supports_thinking: None,
}]))
.await?;
let provenance = AuditProvenance::new("openai", "vendor/model");
let usage = TokenUsage {
input_tokens: 1_000_000,
output_tokens: 1_000_000,
..Default::default()
};
let cost = estimate_cost_usd(Some(®istry), &provenance, &usage)
.context("the override must price this call")?;
assert!((cost - 0.10).abs() < 1e-9, "unexpected cost: {cost}");
let limits = UsageLimits {
max_cost_usd: Some(1.0),
..Default::default()
};
assert!(
status(Some(&limits), Some(®istry), &provenance, &usage, None).is_none(),
"an overridden price must not trip a cap it is under",
);
Ok(())
}
#[tokio::test]
async fn a_later_override_does_not_displace_an_earlier_feed_row() -> Result<()> {
use crate::model_capabilities::Pricing;
let registry = ModelRegistry::new().with_override(
"anthropic",
"claude-sonnet-4",
CatalogEntry {
provider: "anthropic".to_owned(),
model_id: "claude-sonnet-4".to_owned(),
context_window: None,
max_output_tokens: None,
pricing: Some(Pricing::flat(0.0, 0.0)),
pricing_tiers: Vec::new(),
supports_thinking: None,
},
);
registry
.refresh(&FixtureFeed(vec![CatalogEntry {
provider: "cloudflare-ai-gateway".to_owned(),
model_id: "anthropic/claude-sonnet-4".to_owned(),
context_window: None,
max_output_tokens: None,
pricing: Some(Pricing::flat(4.0, 20.0)),
pricing_tiers: Vec::new(),
supports_thinking: None,
}]))
.await?;
let provenance = AuditProvenance::new("cloudflare-ai-gateway", "claude-sonnet-4");
let usage = TokenUsage {
input_tokens: 1_000_000,
output_tokens: 1_000_000,
..Default::default()
};
let cost = estimate_cost_usd(Some(®istry), &provenance, &usage)
.context("the gateway feed row must price the call")?;
assert!((cost - 24.0).abs() < 1e-9, "unexpected cost: {cost}");
let limits = UsageLimits {
max_cost_usd: Some(1.0),
..Default::default()
};
let (limit, _) = status(Some(&limits), Some(®istry), &provenance, &usage, None)
.context("the gateway price must trip the cap")?;
assert_eq!(limit, BudgetLimitKind::CostUsd);
Ok(())
}
#[tokio::test]
async fn a_direct_override_wins_on_a_direct_call() -> Result<()> {
use crate::model_capabilities::Pricing;
let registry = ModelRegistry::new().with_override(
"anthropic",
"claude-sonnet-4",
CatalogEntry {
provider: "anthropic".to_owned(),
model_id: "claude-sonnet-4".to_owned(),
context_window: None,
max_output_tokens: None,
pricing: Some(Pricing::flat(0.0, 0.0)),
pricing_tiers: Vec::new(),
supports_thinking: None,
},
);
registry
.refresh(&FixtureFeed(vec![CatalogEntry {
provider: "anthropic".to_owned(),
model_id: "claude-sonnet-4".to_owned(),
context_window: None,
max_output_tokens: None,
pricing: Some(Pricing::flat(3.0, 15.0)),
pricing_tiers: Vec::new(),
supports_thinking: None,
}]))
.await?;
let provenance = AuditProvenance::new("anthropic", "claude-sonnet-4");
let usage = TokenUsage {
input_tokens: 1_000_000,
output_tokens: 1_000_000,
..Default::default()
};
let cost = estimate_cost_usd(Some(®istry), &provenance, &usage)
.context("the override must price the direct call")?;
assert!(cost.abs() < 1e-9, "unexpected cost: {cost}");
let limits = UsageLimits {
max_cost_usd: Some(1.0),
..Default::default()
};
assert!(status(Some(&limits), Some(®istry), &provenance, &usage, None).is_none());
Ok(())
}
#[tokio::test]
async fn tiered_sibling_is_not_shadowed_by_a_flat_route_row() -> Result<()> {
const MODELSDEV_ROUTE_AND_NATIVE_FIXTURE: &str = r#"{
"openrouter": {
"id": "openrouter",
"models": {
"google/gemini-2.5-pro": {
"id": "google/gemini-2.5-pro",
"cost": { "input": 1.25, "output": 10 }
}
}
},
"google": {
"id": "google",
"models": {
"gemini-2.5-pro": {
"id": "gemini-2.5-pro",
"cost": {
"input": 1.25,
"output": 10,
"tiers": [
{
"input": 2.5,
"output": 15,
"tier": { "type": "context", "size": 200000 }
}
]
}
}
}
}
}"#;
let registry =
registry_from_entries(parse_modelsdev(MODELSDEV_ROUTE_AND_NATIVE_FIXTURE)?).await?;
let provenance = AuditProvenance::new("openai", "google/gemini-2.5-pro");
let short = TokenUsage {
input_tokens: 100_000,
output_tokens: 100_000,
..Default::default()
};
let short_cost = estimate_cost_usd(Some(®istry), &provenance, &short)
.context("the route row prices a short call")?;
assert!(
(short_cost - 1.125).abs() < 1e-9,
"unexpected short cost: {short_cost}"
);
let long = TokenUsage {
input_tokens: 300_000,
output_tokens: 100_000,
..Default::default()
};
let long_cost = estimate_cost_usd(Some(®istry), &provenance, &long)
.context("the tiered native row must price a long call")?;
assert!(
(long_cost - 2.25).abs() < 1e-9,
"unexpected long cost: {long_cost}"
);
Ok(())
}
#[tokio::test]
async fn direct_call_is_not_priced_at_the_routers_rate() -> Result<()> {
let registry = registry_from(OPENROUTER_DIRECT_MODELS_FIXTURE).await?;
let usage = TokenUsage {
input_tokens: 1_000_000,
output_tokens: 1_000_000,
..Default::default()
};
let direct = AuditProvenance::new("openai", "gpt-4o");
let cost = estimate_cost_usd(Some(®istry), &direct, &usage)
.context("gpt-4o is priced in the static table")?;
assert!((cost - 6.25).abs() < 1e-9, "unexpected direct cost: {cost}");
let uncatalogued = estimate_cost_usd(None, &direct, &usage)
.context("gpt-4o is priced in the static table")?;
assert!((cost - uncatalogued).abs() < 1e-12);
let routed = AuditProvenance::new("openai", "openai/gpt-4o");
let routed_cost = estimate_cost_usd(Some(®istry), &routed, &usage)
.context("the route key must price a routed call")?;
assert!(
(routed_cost - 25.0).abs() < 1e-9,
"unexpected routed cost: {routed_cost}"
);
Ok(())
}
#[tokio::test]
async fn feed_price_beats_a_stale_static_price_for_a_known_slug_model() -> Result<()> {
let registry = registry_from(OPENROUTER_REPRICED_FIXTURE).await?;
let usage = TokenUsage {
input_tokens: 1_000_000,
output_tokens: 1_000_000,
..Default::default()
};
let provenance = AuditProvenance::new("openai", "z-ai/glm-5.1");
let stale = estimate_cost_usd(None, &provenance, &usage)
.context("the static table prices this slug model")?;
assert!(
(stale - 4.06).abs() < 1e-9,
"unexpected static cost: {stale}"
);
let fresh = estimate_cost_usd(Some(®istry), &provenance, &usage)
.context("the feed prices this slug model")?;
assert!(
(fresh - 40.60).abs() < 1e-9,
"unexpected feed cost: {fresh}"
);
let limits = UsageLimits {
max_cost_usd: Some(5.0),
..Default::default()
};
assert!(status(Some(&limits), None, &provenance, &usage, None).is_none());
let (limit, _) = status(Some(&limits), Some(®istry), &provenance, &usage, None)
.context("the fresh feed price must trip the cost limit")?;
assert_eq!(limit, BudgetLimitKind::CostUsd);
Ok(())
}
#[tokio::test]
async fn openrouter_routed_model_prices_at_the_route_key() -> Result<()> {
let registry = registry_from_entries(parse_modelsdev(MODELSDEV_ROUTE_FIXTURE)?).await?;
let usage = TokenUsage {
input_tokens: 1_000_000,
output_tokens: 1_000_000,
..Default::default()
};
let provenance = AuditProvenance::new("openai", "moonshotai/kimi-k2.6");
let cost = estimate_cost_usd(Some(®istry), &provenance, &usage)
.context("the models.dev route key must price this call")?;
assert!((cost - 4.95).abs() < 1e-9, "unexpected cost: {cost}");
Ok(())
}
#[tokio::test]
async fn slug_vendor_spelling_reaches_the_native_section() -> Result<()> {
const MODELSDEV_NATIVE_ZAI_FIXTURE: &str = r#"{
"zai": {
"id": "zai",
"models": {
"glm-5.1": {
"id": "glm-5.1",
"cost": { "input": 0.6, "output": 2.2 }
}
}
}
}"#;
let registry =
registry_from_entries(parse_modelsdev(MODELSDEV_NATIVE_ZAI_FIXTURE)?).await?;
let provenance = AuditProvenance::new("openai", "z-ai/glm-5.1");
let usage = TokenUsage {
input_tokens: 1_000_000,
output_tokens: 1_000_000,
..Default::default()
};
let cost = estimate_cost_usd(Some(®istry), &provenance, &usage)
.context("the zai native section must price this slug model")?;
assert!((cost - 2.8).abs() < 1e-9, "unexpected cost: {cost}");
Ok(())
}
#[tokio::test]
async fn vertex_sku_prices_from_the_google_vertex_feed_key() -> Result<()> {
const MODELSDEV_VERTEX_FIXTURE: &str = r#"{
"google-vertex-anthropic": {
"id": "google-vertex-anthropic",
"models": {
"claude-haiku-4-5@20251001": {
"id": "claude-haiku-4-5@20251001",
"cost": { "input": 1, "output": 5 }
}
}
}
}"#;
let registry = registry_from_entries(parse_modelsdev(MODELSDEV_VERTEX_FIXTURE)?).await?;
let usage = TokenUsage {
input_tokens: 1_000_000,
output_tokens: 1_000_000,
..Default::default()
};
let provenance = AuditProvenance::new("vertex", "claude-haiku-4-5@20251001");
assert!(estimate_cost_usd(None, &provenance, &usage).is_none());
let cost = estimate_cost_usd(Some(®istry), &provenance, &usage)
.context("the google-vertex-anthropic key must price this SKU")?;
assert!((cost - 6.0).abs() < 1e-9, "unexpected cost: {cost}");
let limits = UsageLimits {
max_cost_usd: Some(1.0),
..Default::default()
};
let (limit, _) = status(Some(&limits), Some(®istry), &provenance, &usage, None)
.context("a Vertex SKU must be able to trip the cost limit")?;
assert_eq!(limit, BudgetLimitKind::CostUsd);
Ok(())
}
#[tokio::test]
async fn gateway_row_prices_via_the_prefixed_model_key() -> Result<()> {
const MODELSDEV_GATEWAY_FIXTURE: &str = r#"{
"cloudflare-ai-gateway": {
"id": "cloudflare-ai-gateway",
"models": {
"anthropic/claude-sonnet-4": {
"id": "anthropic/claude-sonnet-4",
"cost": { "input": 4, "output": 20 }
}
}
},
"anthropic": {
"id": "anthropic",
"models": {
"claude-sonnet-4": {
"id": "claude-sonnet-4",
"cost": { "input": 3, "output": 15 }
}
}
}
}"#;
let registry = registry_from_entries(parse_modelsdev(MODELSDEV_GATEWAY_FIXTURE)?).await?;
let usage = TokenUsage {
input_tokens: 1_000_000,
output_tokens: 1_000_000,
..Default::default()
};
let provenance = AuditProvenance::new("cloudflare-ai-gateway", "claude-sonnet-4");
let cost = estimate_cost_usd(Some(®istry), &provenance, &usage)
.context("the gateway service row must price this call")?;
assert!((cost - 24.0).abs() < 1e-9, "unexpected cost: {cost}");
let limits = UsageLimits {
max_cost_usd: Some(1.0),
..Default::default()
};
let (limit, _) = status(Some(&limits), Some(®istry), &provenance, &usage, None)
.context("a gateway row must be able to trip the cost limit")?;
assert_eq!(limit, BudgetLimitKind::CostUsd);
Ok(())
}
#[tokio::test]
async fn gateway_falls_back_to_the_direct_vendor_row() -> Result<()> {
const MODELSDEV_DIRECT_ONLY_FIXTURE: &str = r#"{
"anthropic": {
"id": "anthropic",
"models": {
"claude-sonnet-4": {
"id": "claude-sonnet-4",
"cost": { "input": 3, "output": 15 }
}
}
}
}"#;
let registry =
registry_from_entries(parse_modelsdev(MODELSDEV_DIRECT_ONLY_FIXTURE)?).await?;
let usage = TokenUsage {
input_tokens: 1_000_000,
output_tokens: 1_000_000,
..Default::default()
};
let provenance = AuditProvenance::new("cloudflare-ai-gateway", "claude-sonnet-4");
let cost = estimate_cost_usd(Some(®istry), &provenance, &usage)
.context("the direct-vendor alias must price this call")?;
assert!((cost - 18.0).abs() < 1e-9, "unexpected cost: {cost}");
Ok(())
}
#[tokio::test]
async fn long_context_call_trips_the_budget_at_its_tier_rate() -> Result<()> {
const MODELSDEV_TIERED_FIXTURE: &str = r#"{
"openai": {
"id": "openai",
"models": {
"gpt-5.4": {
"id": "gpt-5.4",
"cost": {
"input": 2.5,
"output": 15,
"tiers": [
{
"input": 5,
"output": 22.5,
"tier": { "type": "context", "size": 272000 }
}
]
}
}
}
}
}"#;
let registry = registry_from_entries(parse_modelsdev(MODELSDEV_TIERED_FIXTURE)?).await?;
let provenance = AuditProvenance::new("openai", "gpt-5.4");
let usage = TokenUsage {
input_tokens: 400_000,
output_tokens: 100_000,
..Default::default()
};
let cost = estimate_cost_usd(Some(®istry), &provenance, &usage)
.context("the tiered feed row must price this call")?;
assert!((cost - 4.25).abs() < 1e-9, "unexpected cost: {cost}");
let mut state = AgentState::new(ThreadId::new());
accumulate_cost(
&mut state,
Some(®istry),
&provenance,
&TokenUsage::default(),
&usage,
UsageScope::Call,
);
let accumulated = state
.accumulated_cost_usd
.context("the call must accumulate cost")?;
assert!(
(accumulated - 4.25).abs() < 1e-9,
"unexpected accumulated cost: {accumulated}"
);
let limits = UsageLimits {
max_cost_usd: Some(3.0),
..Default::default()
};
let (limit, _) = status(
Some(&limits),
Some(®istry),
&provenance,
&usage,
state.accumulated_cost_usd,
)
.context("the tier rate must trip the cost limit")?;
assert_eq!(limit, BudgetLimitKind::CostUsd);
Ok(())
}
#[tokio::test]
async fn openrouter_long_context_override_trips_the_budget() -> Result<()> {
const OPENROUTER_OVERRIDE_FIXTURE: &str = r#"{
"data": [
{
"id": "google/gemini-2.5-pro",
"context_length": 1048576,
"pricing": {
"prompt": "0.00000125",
"completion": "0.00001",
"overrides": [
{
"min_prompt_tokens": 200000,
"prompt": "0.0000025",
"completion": "0.000015"
}
]
}
}
]
}"#;
let registry = registry_from(OPENROUTER_OVERRIDE_FIXTURE).await?;
let provenance = AuditProvenance::new("openai", "google/gemini-2.5-pro");
let usage = TokenUsage {
input_tokens: 400_000,
output_tokens: 100_000,
..Default::default()
};
let cost = estimate_cost_usd(Some(®istry), &provenance, &usage)
.context("the override must price this call")?;
assert!((cost - 2.5).abs() < 1e-9, "unexpected cost: {cost}");
let mut state = AgentState::new(ThreadId::new());
accumulate_cost(
&mut state,
Some(®istry),
&provenance,
&TokenUsage::default(),
&usage,
UsageScope::Call,
);
let limits = UsageLimits {
max_cost_usd: Some(2.0),
..Default::default()
};
let (limit, _) = status(
Some(&limits),
Some(®istry),
&provenance,
&usage,
state.accumulated_cost_usd,
)
.context("the override rate must trip the cost limit")?;
assert_eq!(limit, BudgetLimitKind::CostUsd);
Ok(())
}
#[tokio::test]
async fn aggregate_repricing_stays_on_the_base_band() -> Result<()> {
const MODELSDEV_TIERED_FIXTURE: &str = r#"{
"openai": {
"id": "openai",
"models": {
"gpt-5.4": {
"id": "gpt-5.4",
"cost": {
"input": 2.5,
"output": 15,
"tiers": [
{
"input": 5,
"output": 22.5,
"tier": { "type": "context", "size": 272000 }
}
]
}
}
}
}
}"#;
let registry = registry_from_entries(parse_modelsdev(MODELSDEV_TIERED_FIXTURE)?).await?;
let provenance = AuditProvenance::new("openai", "gpt-5.4");
let aggregate = TokenUsage {
input_tokens: 300_000,
output_tokens: 0,
..Default::default()
};
let cost = run_cost_usd(None, Some(®istry), &provenance, &aggregate)
.context("the feed prices this model")?;
assert!(
(cost - 0.75).abs() < 1e-9,
"unexpected aggregate cost: {cost}"
);
let limits = UsageLimits {
max_cost_usd: Some(1.0),
..Default::default()
};
assert!(
status(
Some(&limits),
Some(®istry),
&provenance,
&aggregate,
None
)
.is_none(),
"a healthy thread must not be killed by a phantom long-context bill",
);
Ok(())
}
#[tokio::test]
async fn compaction_delta_is_priced_at_base_rates() -> Result<()> {
const MODELSDEV_TIERED_FIXTURE: &str = r#"{
"openai": {
"id": "openai",
"models": {
"gpt-5.4": {
"id": "gpt-5.4",
"cost": {
"input": 2.5,
"output": 15,
"tiers": [
{
"input": 5,
"output": 22.5,
"tier": { "type": "context", "size": 272000 }
}
]
}
}
}
}
}"#;
let registry = registry_from_entries(parse_modelsdev(MODELSDEV_TIERED_FIXTURE)?).await?;
let provenance = AuditProvenance::new("openai", "gpt-5.4");
let summed = TokenUsage {
input_tokens: 300_000,
output_tokens: 0,
..Default::default()
};
let mut state = AgentState::new(ThreadId::new());
accumulate_cost(
&mut state,
Some(®istry),
&provenance,
&TokenUsage::default(),
&summed,
UsageScope::Aggregate,
);
let accumulated = state
.accumulated_cost_usd
.context("the compaction spend must accumulate")?;
assert!(
(accumulated - 0.75).abs() < 1e-9,
"compaction priced at the tier rate: {accumulated}"
);
Ok(())
}
#[test]
fn no_catalog_leaves_a_routed_model_unpriced() {
let usage = TokenUsage {
input_tokens: 1_000_000,
output_tokens: 1_000_000,
..Default::default()
};
let routed = AuditProvenance::new("openai", "anthropic/claude-fable-5");
assert!(
estimate_cost_usd(None, &routed, &usage).is_none(),
"the static table must not answer for a vendor-split key",
);
let direct = AuditProvenance::new("anthropic", "claude-fable-5");
assert!(estimate_cost_usd(None, &direct, &usage).is_some());
}
}