1#[inline]
27fn model_prices(model: &str) -> (f64, f64) {
28 match model {
29 m if m.contains("fable") => (10.0, 50.0),
30 m if m.contains("opus") => (5.0, 25.0),
31 m if m.contains("sonnet") => (3.0, 15.0),
32 m if m.contains("haiku") => (1.0, 5.0),
33 _ => (3.0, 15.0), }
35}
36
37pub fn calculate_cost(
49 model: &str,
50 input_tokens: u64,
51 output_tokens: u64,
52 cache_read: u64,
53 cache_creation: u64,
54) -> f64 {
55 let (input_price, output_price) = model_prices(model);
56 (input_tokens as f64 / 1_000_000.0) * input_price
57 + (cache_read as f64 / 1_000_000.0) * input_price * 0.1
58 + (cache_creation as f64 / 1_000_000.0) * input_price * 1.25
59 + (output_tokens as f64 / 1_000_000.0) * output_price
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65
66 #[test]
67 fn fable_pricing() {
68 let cost = calculate_cost("claude-fable-5", 1_000_000, 1_000_000, 0, 0);
70 assert!((cost - 60.0).abs() < 1e-9, "expected $60, got ${cost}");
71 }
72
73 #[test]
74 fn opus_pricing() {
75 let cost = calculate_cost("claude-opus-4-5", 1_000_000, 1_000_000, 0, 0);
77 assert!((cost - 30.0).abs() < 1e-9, "expected $30, got ${cost}");
78 }
79
80 #[test]
81 fn sonnet_pricing() {
82 let cost = calculate_cost("claude-sonnet-4-5", 1_000_000, 1_000_000, 0, 0);
84 assert!((cost - 18.0).abs() < 1e-9, "expected $18, got ${cost}");
85 }
86
87 #[test]
88 fn haiku_pricing() {
89 let cost = calculate_cost("claude-haiku-4-5", 1_000_000, 1_000_000, 0, 0);
91 assert!((cost - 6.0).abs() < 1e-9, "expected $6, got ${cost}");
92 }
93
94 #[test]
95 fn cache_read_bills_at_tenth_input_rate() {
96 let cost = calculate_cost("claude-sonnet-4-5", 0, 0, 1_000_000, 0);
98 assert!((cost - 0.30).abs() < 1e-9, "expected $0.30, got ${cost}");
99 }
100
101 #[test]
102 fn cache_creation_bills_at_125_percent_input_rate() {
103 let cost = calculate_cost("claude-sonnet-4-5", 0, 0, 0, 1_000_000);
105 assert!((cost - 3.75).abs() < 1e-9, "expected $3.75, got ${cost}");
106 }
107
108 #[test]
109 fn unknown_model_falls_back_to_sonnet() {
110 let cost_unknown = calculate_cost("gpt-99-turbo", 1_000_000, 0, 0, 0);
111 let cost_sonnet = calculate_cost("claude-sonnet-4-5", 1_000_000, 0, 0, 0);
112 assert!((cost_unknown - cost_sonnet).abs() < 1e-9);
113 }
114
115 #[test]
116 fn zero_usage_is_zero_cost() {
117 assert_eq!(calculate_cost("claude-opus-4-5", 0, 0, 0, 0), 0.0);
118 }
119}