Skip to main content

orch/store/
pricing.rs

1/// Token usage for an agent run.
2#[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/// Per-1M token pricing in USD.
15#[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    #[expect(
23        clippy::cast_precision_loss,
24        reason = "token counts stay far below 2^52"
25    )]
26    pub fn estimate_cost_usd(self, usage: TokenUsage) -> CostEstimate {
27        let input_cost = (usage.input_tokens as f64 / 1_000_000.0) * self.input_per_million_usd;
28        let output_cost = (usage.output_tokens as f64 / 1_000_000.0) * self.output_per_million_usd;
29        CostEstimate {
30            input_cost_usd: input_cost,
31            output_cost_usd: output_cost,
32            total_cost_usd: input_cost + output_cost,
33        }
34    }
35}
36
37/// Cost estimate in USD.
38#[derive(Debug, Clone, Copy, PartialEq, Default)]
39pub struct CostEstimate {
40    pub input_cost_usd: f64,
41    pub output_cost_usd: f64,
42    pub total_cost_usd: f64,
43}
44/// Zero-cost pricing (subscription-based or free-tier models).
45const FREE: ModelPricing = ModelPricing {
46    input_per_million_usd: 0.0,
47    output_per_million_usd: 0.0,
48};
49
50/// Resolve model pricing using a built-in table and normalized model aliases.
51pub fn pricing_for_model(model: &str) -> ModelPricing {
52    let normalized = model.trim().to_lowercase();
53
54    // GitHub Copilot subscription models — billed to Copilot account, not per-token.
55    if normalized.starts_with("github-copilot/") {
56        return FREE;
57    }
58
59    // Free-tier models (e.g. minimax-m2.5-free, nemotron-3-super-free).
60    if normalized.contains("-free") || normalized.ends_with("free") {
61        return FREE;
62    }
63
64    // DeepSeek models (e.g. deepseek-r1, deepseek-v3).
65    if normalized.starts_with("deepseek") {
66        return ModelPricing {
67            input_per_million_usd: 0.55,
68            output_per_million_usd: 2.19,
69        };
70    }
71
72    // OpenAI models
73    if normalized == "o3" {
74        return ModelPricing {
75            input_per_million_usd: 2.0,
76            output_per_million_usd: 8.0,
77        };
78    }
79    if normalized == "o4-mini" || normalized.contains("gpt-4.1-mini") {
80        return ModelPricing {
81            input_per_million_usd: 0.15,
82            output_per_million_usd: 0.6,
83        };
84    }
85    if normalized.contains("gpt-4.1") && !normalized.contains("mini") {
86        return ModelPricing {
87            input_per_million_usd: 2.0,
88            output_per_million_usd: 8.0,
89        };
90    }
91    // Codex v5 mini variants (gpt-5.1-codex-mini, gpt-5.4-mini, etc.).
92    if normalized.starts_with("gpt-5") && normalized.contains("mini") {
93        return ModelPricing {
94            input_per_million_usd: 0.15,
95            output_per_million_usd: 0.6,
96        };
97    }
98    // Codex v5 full variants (gpt-5.2-codex, gpt-5.3-codex, etc.).
99    if normalized.starts_with("gpt-5") {
100        return ModelPricing {
101            input_per_million_usd: 2.0,
102            output_per_million_usd: 8.0,
103        };
104    }
105
106    // Claude models matched by family substring.
107    if normalized.contains("opus") {
108        return ModelPricing {
109            input_per_million_usd: 15.0,
110            output_per_million_usd: 75.0,
111        };
112    }
113    if normalized.contains("sonnet") {
114        return ModelPricing {
115            input_per_million_usd: 3.0,
116            output_per_million_usd: 15.0,
117        };
118    }
119    if normalized.contains("haiku") {
120        return ModelPricing {
121            input_per_million_usd: 0.8,
122            output_per_million_usd: 4.0,
123        };
124    }
125
126    // Fallback baseline when model is unknown.
127    ModelPricing {
128        input_per_million_usd: 1.0,
129        output_per_million_usd: 4.0,
130    }
131}