use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Provider {
Anthropic,
OpenAI,
OpenRouter,
Other(String),
}
impl Provider {
pub fn label(&self) -> &str {
match self {
Provider::Anthropic => "anthropic",
Provider::OpenAI => "openai",
Provider::OpenRouter => "openrouter",
Provider::Other(s) => s.as_str(),
}
}
}
impl serde::Serialize for Provider {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.label())
}
}
#[derive(Debug, Clone, Default, PartialEq, Serialize)]
pub struct TokenBuckets {
pub input: u64, pub output: u64,
pub cache_read: u64,
pub cache_write: u64, }
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct ModelCost {
pub model: String,
pub provider: Provider,
pub tokens: TokenBuckets,
pub subtotal_usd: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[serde(tag = "kind", content = "detail")]
pub enum Approximation {
UnpricedModel(String),
AssumedStandardTier,
UnknownModelForTurn,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct CostEstimate {
pub total_usd: f64,
pub per_model: Vec<ModelCost>,
pub tokens: TokenBuckets,
pub unpriced_models: Vec<String>,
pub approximations: Vec<Approximation>,
pub pricing_as_of: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct MessageUsage {
pub model: String, pub provider: Provider,
pub namespace: String, pub input_uncached: u64,
pub cache_read: u64,
pub cache_write_5m: u64,
pub cache_write_1h: u64,
pub output: u64,
pub request_input_tokens: u64, pub service_tier: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn provider_serializes_as_lowercase_string() {
assert_eq!(
serde_json::to_value(Provider::OpenRouter).unwrap(),
serde_json::json!("openrouter")
);
assert_eq!(
serde_json::to_value(Provider::Other("bedrock".into())).unwrap(),
serde_json::json!("bedrock")
);
assert_eq!(
serde_json::to_value(Provider::OpenAI).unwrap(),
serde_json::json!("openai")
);
}
#[test]
fn cost_estimate_serializes_with_expected_fields() {
let est = CostEstimate {
total_usd: 1.5,
per_model: vec![],
tokens: TokenBuckets::default(),
unpriced_models: vec![],
approximations: vec![Approximation::AssumedStandardTier],
pricing_as_of: "2026-06-04".into(),
};
let v: serde_json::Value = serde_json::to_value(&est).unwrap();
assert_eq!(v["total_usd"], 1.5);
assert_eq!(v["pricing_as_of"], "2026-06-04");
assert_eq!(v["approximations"][0]["kind"], "AssumedStandardTier");
}
}