use crate::llm::Usage;
pub trait CostEstimator: Send + Sync {
fn estimate_cost_usd(&self, provider: &str, model: &str, usage: &Usage) -> Option<f64>;
fn estimate_aggregate_cost_usd(
&self,
provider: &str,
model: &str,
usage: &Usage,
) -> Option<f64> {
self.estimate_cost_usd(provider, model, usage)
}
fn estimate_override_cost_usd(
&self,
_provider: &str,
_model: &str,
_usage: &Usage,
) -> Option<f64> {
None
}
fn estimate_override_aggregate_cost_usd(
&self,
provider: &str,
model: &str,
usage: &Usage,
) -> Option<f64> {
self.estimate_override_cost_usd(provider, model, usage)
}
fn estimate_feed_cost_usd(&self, provider: &str, model: &str, usage: &Usage) -> Option<f64> {
self.estimate_cost_usd(provider, model, usage)
}
fn estimate_feed_aggregate_cost_usd(
&self,
provider: &str,
model: &str,
usage: &Usage,
) -> Option<f64> {
self.estimate_aggregate_cost_usd(provider, model, usage)
}
}
#[cfg(feature = "model-discovery")]
impl CostEstimator for agent_sdk_providers::ModelRegistry {
fn estimate_cost_usd(&self, provider: &str, model: &str, usage: &Usage) -> Option<f64> {
Self::estimate_dynamic_cost_usd(self, provider, model, usage)
}
fn estimate_aggregate_cost_usd(
&self,
provider: &str,
model: &str,
usage: &Usage,
) -> Option<f64> {
Self::estimate_dynamic_base_cost_usd(self, provider, model, usage)
}
fn estimate_override_cost_usd(
&self,
provider: &str,
model: &str,
usage: &Usage,
) -> Option<f64> {
Self::estimate_override_cost_usd(self, provider, model, usage)
}
fn estimate_override_aggregate_cost_usd(
&self,
provider: &str,
model: &str,
usage: &Usage,
) -> Option<f64> {
Self::estimate_override_base_cost_usd(self, provider, model, usage)
}
fn estimate_feed_cost_usd(&self, provider: &str, model: &str, usage: &Usage) -> Option<f64> {
Self::estimate_feed_cost_usd(self, provider, model, usage)
}
fn estimate_feed_aggregate_cost_usd(
&self,
provider: &str,
model: &str,
usage: &Usage,
) -> Option<f64> {
Self::estimate_feed_base_cost_usd(self, provider, model, usage)
}
}
#[cfg(all(test, feature = "model-discovery"))]
mod tests {
use super::*;
use agent_sdk_providers::{CatalogEntry, ModelRegistry};
use anyhow::Context;
#[test]
fn model_registry_prices_through_the_trait() -> anyhow::Result<()> {
let registry = ModelRegistry::new().with_override(
"openai",
"feed-only-model",
CatalogEntry {
provider: "openai".to_owned(),
model_id: "feed-only-model".to_owned(),
context_window: None,
max_output_tokens: None,
pricing: Some(crate::model_capabilities::Pricing::flat(10.0, 20.0)),
pricing_tiers: Vec::new(),
supports_thinking: None,
},
);
let usage = Usage {
input_tokens: 1_000_000,
output_tokens: 1_000_000,
cached_input_tokens: 0,
cache_creation_input_tokens: 0,
};
let estimator: &dyn CostEstimator = ®istry;
let cost = estimator
.estimate_cost_usd("openai", "feed-only-model", &usage)
.context("registry must price a model it carries")?;
assert!((cost - 30.0).abs() < 1e-9, "unexpected cost: {cost}");
assert!(
estimator
.estimate_cost_usd("openai", "no-such-model", &usage)
.is_none()
);
Ok(())
}
}