use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TokenUsage {
pub input_tokens: u64,
pub output_tokens: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_read_input_tokens: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_creation_input_tokens: Option<u64>,
}
impl TokenUsage {
pub fn new() -> Self {
Self::default()
}
pub fn from_sdk_usage(usage: &serde_json::Value) -> Self {
Self {
input_tokens: usage["input_tokens"].as_u64().unwrap_or(0),
output_tokens: usage["output_tokens"].as_u64().unwrap_or(0),
cache_read_input_tokens: usage["cache_read_input_tokens"].as_u64(),
cache_creation_input_tokens: usage["cache_creation_input_tokens"].as_u64(),
}
}
pub fn add(&mut self, other: &TokenUsage) {
self.input_tokens += other.input_tokens;
self.output_tokens += other.output_tokens;
if let Some(v) = other.cache_read_input_tokens {
*self.cache_read_input_tokens.get_or_insert(0) += v;
}
if let Some(v) = other.cache_creation_input_tokens {
*self.cache_creation_input_tokens.get_or_insert(0) += v;
}
}
pub fn total(&self) -> u64 {
self.input_tokens + self.output_tokens
}
pub fn is_empty(&self) -> bool {
self.input_tokens == 0 && self.output_tokens == 0
}
}
#[derive(Debug, Clone, Default)]
pub struct SessionStats {
pub active_sessions: usize,
pub total_usage: TokenUsage,
pub total_cost_usd: f64,
}
impl SessionStats {
pub fn new() -> Self {
Self::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_token_usage_default() {
let usage = TokenUsage::default();
assert_eq!(usage.input_tokens, 0);
assert_eq!(usage.output_tokens, 0);
assert!(usage.cache_read_input_tokens.is_none());
assert!(usage.is_empty());
assert_eq!(usage.total(), 0);
}
#[test]
fn test_token_usage_from_sdk() {
let sdk_usage = json!({
"input_tokens": 1000,
"output_tokens": 500,
"cache_read_input_tokens": 200,
"cache_creation_input_tokens": 100
});
let usage = TokenUsage::from_sdk_usage(&sdk_usage);
assert_eq!(usage.input_tokens, 1000);
assert_eq!(usage.output_tokens, 500);
assert_eq!(usage.cache_read_input_tokens, Some(200));
assert_eq!(usage.cache_creation_input_tokens, Some(100));
assert_eq!(usage.total(), 1500);
assert!(!usage.is_empty());
}
#[test]
fn test_token_usage_add() {
let mut usage1 = TokenUsage {
input_tokens: 100,
output_tokens: 50,
cache_read_input_tokens: Some(10),
cache_creation_input_tokens: None,
};
let usage2 = TokenUsage {
input_tokens: 200,
output_tokens: 100,
cache_read_input_tokens: Some(20),
cache_creation_input_tokens: Some(5),
};
usage1.add(&usage2);
assert_eq!(usage1.input_tokens, 300);
assert_eq!(usage1.output_tokens, 150);
assert_eq!(usage1.cache_read_input_tokens, Some(30));
assert_eq!(usage1.cache_creation_input_tokens, Some(5));
}
#[test]
fn test_token_usage_serialization() {
let usage = TokenUsage {
input_tokens: 100,
output_tokens: 50,
cache_read_input_tokens: None,
cache_creation_input_tokens: None,
};
let json = serde_json::to_string(&usage).unwrap();
let parsed: TokenUsage = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.input_tokens, 100);
assert_eq!(parsed.output_tokens, 50);
}
}