systemprompt_api/services/gateway/
pricing.rs1use systemprompt_models::services::{GatewayConfig, ModelPricing, ProviderRegistry};
22
23pub fn resolve(
24 provider: &str,
25 candidates: &[&str],
26 gateway: Option<&GatewayConfig>,
27 registry: &ProviderRegistry,
28) -> ModelPricing {
29 for model in candidates.iter().filter(|m| !m.is_empty()) {
30 if let Some(p) = lookup(model, gateway, registry) {
31 return p;
32 }
33 }
34
35 tracing::warn!(
36 provider = provider,
37 candidates = ?candidates,
38 "Gateway pricing lookup: no override and no registry entry — cost_microdollars will be 0"
39 );
40 ModelPricing::default()
41}
42
43fn lookup(
44 model: &str,
45 gateway: Option<&GatewayConfig>,
46 registry: &ProviderRegistry,
47) -> Option<ModelPricing> {
48 if let Some(gw) = gateway
49 && let Some(route) = gw.find_route(model)
50 && let Some(p) = route.pricing
51 {
52 return Some(p);
53 }
54 registry_pricing(registry, gateway, model)
55}
56
57fn registry_pricing(
58 registry: &ProviderRegistry,
59 gateway: Option<&GatewayConfig>,
60 model: &str,
61) -> Option<ModelPricing> {
62 if let Some(route) = gateway.and_then(|gw| gw.find_route(model))
63 && let Some(m) = route
64 .resolve(registry)
65 .and_then(|entry| entry.find_model(model))
66 {
67 return Some(m.pricing);
68 }
69 registry
70 .providers
71 .iter()
72 .find_map(|entry| entry.find_model(model))
73 .map(|m| m.pricing)
74}
75
76#[must_use]
77pub fn cost_microdollars(pricing: ModelPricing, tokens: CostTokens) -> i64 {
78 let rate = |count: u32, per_million: f64| (f64::from(count) / 1_000_000.0) * per_million;
79 let total = rate(tokens.input, pricing.input_per_million)
80 + rate(tokens.output, pricing.output_per_million)
81 + rate(tokens.cache_read, pricing.cache_read_per_million)
82 + rate(tokens.cache_creation, pricing.cache_write_per_million);
83 (total * 1_000_000.0).round() as i64
84}
85
86#[derive(Debug, Clone, Copy, Default)]
89pub struct CostTokens {
90 pub input: u32,
91 pub output: u32,
92 pub cache_read: u32,
93 pub cache_creation: u32,
94}