use lellm_core::TokenUsage;
#[derive(Debug)]
pub enum UsageDelta {
InputTokens(u32),
OutputTokens(u32),
Full(TokenUsage),
}
pub struct UsageAccumulator {
prompt_tokens: Option<u32>,
completion_tokens: Option<u32>,
}
impl UsageAccumulator {
pub fn new() -> Self {
Self {
prompt_tokens: None,
completion_tokens: None,
}
}
pub fn push(&mut self, delta: &UsageDelta) {
match delta {
UsageDelta::InputTokens(n) => {
self.prompt_tokens = Some(std::cmp::max(*n, self.prompt_tokens.unwrap_or(0)));
}
UsageDelta::OutputTokens(n) => {
self.completion_tokens =
Some(std::cmp::max(*n, self.completion_tokens.unwrap_or(0)));
}
UsageDelta::Full(u) => {
self.prompt_tokens = Some(u.prompt_tokens);
self.completion_tokens = Some(u.completion_tokens);
}
}
}
pub fn finalize(self) -> Option<TokenUsage> {
match (self.prompt_tokens, self.completion_tokens) {
(Some(p), Some(c)) => Some(TokenUsage {
prompt_tokens: p,
completion_tokens: c,
total_tokens: p + c,
}),
(Some(p), None) => Some(TokenUsage {
prompt_tokens: p,
completion_tokens: 0,
total_tokens: p,
}),
(None, Some(c)) => Some(TokenUsage {
prompt_tokens: 0,
completion_tokens: c,
total_tokens: c,
}),
(None, None) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accumulates_anthropic_style() {
let mut acc = UsageAccumulator::new();
acc.push(&UsageDelta::InputTokens(100));
acc.push(&UsageDelta::OutputTokens(50));
let usage = acc.finalize().unwrap();
assert_eq!(usage.prompt_tokens, 100);
assert_eq!(usage.completion_tokens, 50);
assert_eq!(usage.total_tokens, 150);
}
#[test]
fn openai_full_overrides() {
let mut acc = UsageAccumulator::new();
acc.push(&UsageDelta::InputTokens(100));
acc.push(&UsageDelta::Full(TokenUsage {
prompt_tokens: 200,
completion_tokens: 80,
total_tokens: 280,
}));
let usage = acc.finalize().unwrap();
assert_eq!(usage.prompt_tokens, 200);
assert_eq!(usage.completion_tokens, 80);
}
#[test]
fn empty_returns_none() {
let acc = UsageAccumulator::new();
assert!(acc.finalize().is_none());
}
#[test]
fn partial_input_only() {
let mut acc = UsageAccumulator::new();
acc.push(&UsageDelta::InputTokens(42));
let usage = acc.finalize().unwrap();
assert_eq!(usage.prompt_tokens, 42);
assert_eq!(usage.completion_tokens, 0);
}
}