use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Cost {
pub input: f64,
pub output: f64,
pub cache_read: f64,
pub cache_write: f64,
pub total: f64,
}
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Usage {
pub input_tokens: u32,
pub output_tokens: u32,
pub cache_read_tokens: u32,
pub cache_write_tokens: u32,
pub reasoning_tokens: Option<u32>,
pub total_tokens: u32,
pub cost: Option<Cost>,
}
impl Usage {
pub fn visible_output_tokens(&self) -> u32 {
self.output_tokens
.saturating_sub(self.reasoning_tokens.unwrap_or(0))
}
pub fn non_cached_input_tokens(&self) -> u32 {
self.input_tokens
.saturating_sub(self.cache_read_tokens)
.saturating_sub(self.cache_write_tokens)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_usage_default_is_zero() {
let u = Usage::default();
assert_eq!(u.input_tokens, 0);
assert_eq!(u.output_tokens, 0);
assert_eq!(u.total_tokens, 0);
assert!(u.cost.is_none());
}
#[test]
fn test_visible_output_tokens() {
let u = Usage {
output_tokens: 100,
reasoning_tokens: Some(40),
..Default::default()
};
assert_eq!(u.visible_output_tokens(), 60);
}
#[test]
fn test_visible_output_tokens_no_reasoning() {
let u = Usage {
output_tokens: 100,
reasoning_tokens: None,
..Default::default()
};
assert_eq!(u.visible_output_tokens(), 100);
}
#[test]
fn test_visible_output_tokens_saturating() {
let u = Usage {
output_tokens: 10,
reasoning_tokens: Some(999),
..Default::default()
};
assert_eq!(u.visible_output_tokens(), 0);
}
#[test]
fn test_non_cached_input_tokens() {
let u = Usage {
input_tokens: 1000,
cache_read_tokens: 600,
cache_write_tokens: 200,
..Default::default()
};
assert_eq!(u.non_cached_input_tokens(), 200);
}
#[test]
fn test_non_cached_input_tokens_saturating() {
let u = Usage {
input_tokens: 100,
cache_read_tokens: 80,
cache_write_tokens: 80,
..Default::default()
};
assert_eq!(u.non_cached_input_tokens(), 0);
}
#[test]
fn test_usage_serde_roundtrip() {
let u = Usage {
input_tokens: 500,
output_tokens: 200,
cache_read_tokens: 100,
cache_write_tokens: 50,
reasoning_tokens: Some(30),
total_tokens: 700,
cost: Some(Cost {
input: 0.005,
output: 0.010,
cache_read: 0.001,
cache_write: 0.002,
total: 0.018,
}),
};
let json = serde_json::to_string(&u).unwrap();
let restored: Usage = serde_json::from_str(&json).unwrap();
assert_eq!(u, restored);
}
#[test]
fn test_cost_serde_roundtrip() {
let c = Cost {
input: 0.01,
output: 0.03,
cache_read: 0.001,
cache_write: 0.002,
total: 0.043,
};
let json = serde_json::to_string(&c).unwrap();
let restored: Cost = serde_json::from_str(&json).unwrap();
assert_eq!(c, restored);
}
}