1#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
3pub struct TokenUsage {
4 pub input_tokens: u64,
5 pub output_tokens: u64,
6}
7
8impl TokenUsage {
9 pub fn total_tokens(self) -> u64 {
10 self.input_tokens.saturating_add(self.output_tokens)
11 }
12}
13
14#[derive(Debug, Clone, Copy, PartialEq)]
16pub struct ModelPricing {
17 pub input_per_million_usd: f64,
18 pub output_per_million_usd: f64,
19}
20
21impl ModelPricing {
22 pub fn estimate_cost_usd(self, usage: TokenUsage) -> CostEstimate {
23 let input_cost = (usage.input_tokens as f64 / 1_000_000.0) * self.input_per_million_usd;
24 let output_cost = (usage.output_tokens as f64 / 1_000_000.0) * self.output_per_million_usd;
25 CostEstimate {
26 input_cost_usd: input_cost,
27 output_cost_usd: output_cost,
28 total_cost_usd: input_cost + output_cost,
29 }
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Default)]
35pub struct CostEstimate {
36 pub input_cost_usd: f64,
37 pub output_cost_usd: f64,
38 pub total_cost_usd: f64,
39}
40const FREE: ModelPricing = ModelPricing {
42 input_per_million_usd: 0.0,
43 output_per_million_usd: 0.0,
44};
45
46pub fn pricing_for_model(model: &str) -> ModelPricing {
48 let normalized = model.trim().to_lowercase();
49
50 if normalized.starts_with("github-copilot/") {
52 return FREE;
53 }
54
55 if normalized.contains("-free") || normalized.ends_with("free") {
57 return FREE;
58 }
59
60 if normalized.starts_with("deepseek") {
62 return ModelPricing {
63 input_per_million_usd: 0.55,
64 output_per_million_usd: 2.19,
65 };
66 }
67
68 if normalized == "o3" {
70 return ModelPricing {
71 input_per_million_usd: 2.0,
72 output_per_million_usd: 8.0,
73 };
74 }
75 if normalized == "o4-mini" || normalized.contains("gpt-4.1-mini") {
76 return ModelPricing {
77 input_per_million_usd: 0.15,
78 output_per_million_usd: 0.6,
79 };
80 }
81 if normalized.contains("gpt-4.1") && !normalized.contains("mini") {
82 return ModelPricing {
83 input_per_million_usd: 2.0,
84 output_per_million_usd: 8.0,
85 };
86 }
87 if normalized.starts_with("gpt-5") && normalized.contains("mini") {
89 return ModelPricing {
90 input_per_million_usd: 0.15,
91 output_per_million_usd: 0.6,
92 };
93 }
94 if normalized.starts_with("gpt-5") {
96 return ModelPricing {
97 input_per_million_usd: 2.0,
98 output_per_million_usd: 8.0,
99 };
100 }
101
102 if normalized.contains("opus") {
104 return ModelPricing {
105 input_per_million_usd: 15.0,
106 output_per_million_usd: 75.0,
107 };
108 }
109 if normalized.contains("sonnet") {
110 return ModelPricing {
111 input_per_million_usd: 3.0,
112 output_per_million_usd: 15.0,
113 };
114 }
115 if normalized.contains("haiku") {
116 return ModelPricing {
117 input_per_million_usd: 0.8,
118 output_per_million_usd: 4.0,
119 };
120 }
121
122 ModelPricing {
124 input_per_million_usd: 1.0,
125 output_per_million_usd: 4.0,
126 }
127}