codetether_agent/telemetry/cost.rs
1//! USD cost estimation for LLM requests.
2
3use serde::{Deserialize, Serialize};
4
5use super::tokens::TokenCounts;
6
7/// Estimated request cost, split into input/output components.
8///
9/// Prices are in **USD per million tokens** — the standard unit advertised by
10/// Anthropic, OpenAI, and Bedrock.
11///
12/// # Examples
13///
14/// ```rust
15/// use codetether_agent::telemetry::{CostEstimate, TokenCounts};
16///
17/// let cost = CostEstimate::from_tokens(
18/// &TokenCounts::new(2_000_000, 1_000_000),
19/// 3.00,
20/// 15.00,
21/// );
22/// assert!((cost.total_cost - 21.0).abs() < 1e-6);
23/// assert_eq!(cost.format_smart(), "$21.00");
24/// ```
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct CostEstimate {
27 /// Cost attributable to input tokens.
28 pub input_cost: f64,
29 /// Cost attributable to output tokens.
30 pub output_cost: f64,
31 /// `input_cost + output_cost`.
32 pub total_cost: f64,
33 /// ISO-4217 currency code (always `"USD"` today).
34 pub currency: String,
35}
36
37impl Default for CostEstimate {
38 fn default() -> Self {
39 Self {
40 input_cost: 0.0,
41 output_cost: 0.0,
42 total_cost: 0.0,
43 currency: "USD".to_string(),
44 }
45 }
46}
47
48impl CostEstimate {
49 /// Compute a cost estimate from token counts and per-million prices.
50 pub fn from_tokens(tokens: &TokenCounts, input_price: f64, output_price: f64) -> Self {
51 let input_cost = (tokens.input_tokens as f64 / 1_000_000.0) * input_price;
52 let output_cost = (tokens.output_tokens as f64 / 1_000_000.0) * output_price;
53 Self {
54 input_cost,
55 output_cost,
56 total_cost: input_cost + output_cost,
57 currency: "USD".to_string(),
58 }
59 }
60
61 /// Always 4 decimal places: `"$0.0042"`.
62 pub fn format_currency(&self) -> String {
63 format!("${:.4}", self.total_cost)
64 }
65
66 /// Sub-cent amounts use 4 decimals; `>= $0.01` uses 2.
67 pub fn format_smart(&self) -> String {
68 if self.total_cost < 0.01 {
69 format!("${:.4}", self.total_cost)
70 } else {
71 format!("${:.2}", self.total_cost)
72 }
73 }
74}