agent_top_core/
pricing.rs1use crate::model::TokenUsage;
11
12#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct Price {
14 pub input: f64,
15 pub output: f64,
16 pub cache_write_5m: f64,
17 pub cache_write_1h: f64,
18 pub cache_read: f64,
19}
20
21impl Price {
22 const fn anthropic(input: f64, output: f64, cache_read: f64) -> Price {
23 Price { input, output, cache_write_5m: input * 1.25, cache_write_1h: input * 2.0, cache_read }
24 }
25
26 pub fn cost(&self, usage: &TokenUsage) -> f64 {
28 const M: f64 = 1_000_000.0;
29 usage.input as f64 * self.input / M
30 + usage.cache_write_5m as f64 * self.cache_write_5m / M
31 + usage.cache_write_1h as f64 * self.cache_write_1h / M
32 + usage.cache_read as f64 * self.cache_read / M
33 + usage.output as f64 * self.output / M
34 }
35}
36
37const TABLE: &[(&str, Price)] = &[
39 ("claude-fable-5-1", Price::anthropic(10.0, 50.0, 0.25)),
40 ("claude-mythos-5-1", Price::anthropic(10.0, 50.0, 0.25)),
41 ("claude-fable-5", Price::anthropic(10.0, 50.0, 1.0)),
42 ("claude-mythos-5", Price::anthropic(10.0, 50.0, 1.0)),
43 ("claude-opus-5", Price::anthropic(5.0, 25.0, 0.5)),
44 ("claude-opus-4-8", Price::anthropic(5.0, 25.0, 0.5)),
45 ("claude-opus-4-7", Price::anthropic(5.0, 25.0, 0.5)),
46 ("claude-opus-4-6", Price::anthropic(5.0, 25.0, 0.5)),
47 ("claude-sonnet-5", Price::anthropic(2.0, 10.0, 0.2)),
48 ("claude-sonnet-4-6", Price::anthropic(3.0, 15.0, 0.3)),
49 ("claude-haiku-4-5", Price::anthropic(1.0, 5.0, 0.1)),
50];
51
52pub fn price_for(model: &str) -> Option<Price> {
55 let m = model.trim().to_ascii_lowercase();
56 let m = m.strip_prefix("anthropic.").unwrap_or(&m);
57 let m = m.strip_prefix("us.anthropic.").unwrap_or(m);
58 TABLE.iter().filter(|(prefix, _)| m.starts_with(prefix)).max_by_key(|(prefix, _)| prefix.len()).map(|(_, p)| *p)
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn longest_prefix_wins() {
67 assert_eq!(price_for("claude-fable-5-1").unwrap().cache_read, 0.25);
68 assert_eq!(price_for("claude-fable-5").unwrap().cache_read, 1.0);
69 assert_eq!(price_for("claude-sonnet-4-6-20251114").unwrap().input, 3.0);
70 assert!(price_for("gpt-5-codex").is_none());
71 assert!(price_for("<synthetic>").is_none());
72 }
73
74 #[test]
75 fn cost_arithmetic() {
76 let p = price_for("claude-sonnet-5").unwrap();
77 let u = TokenUsage { input: 1_000_000, output: 1_000_000, ..Default::default() };
78 assert!((p.cost(&u) - 12.0).abs() < 1e-9);
79 let u = TokenUsage { cache_write_1h: 1_000_000, ..Default::default() };
80 assert!((p.cost(&u) - 4.0).abs() < 1e-9);
81 }
82}