use serde::{Deserialize, Serialize};
pub const MAX_DURABLE_TOKEN_COUNT: u64 = i64::MAX as u64;
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct TokenUsage {
pub prompt_tokens: u64,
pub completion_tokens: u64,
pub total_tokens: u64,
}
impl TokenUsage {
pub fn add_assign(&mut self, other: TokenUsage) {
self.prompt_tokens = self.prompt_tokens.saturating_add(other.prompt_tokens);
self.completion_tokens = self
.completion_tokens
.saturating_add(other.completion_tokens);
self.total_tokens = self.total_tokens.saturating_add(other.total_tokens);
}
pub fn recompute_total(&mut self) {
self.total_tokens = self.prompt_tokens.saturating_add(self.completion_tokens);
}
pub fn clamped_for_durable_metrics(mut self) -> Self {
self.prompt_tokens = self.prompt_tokens.min(MAX_DURABLE_TOKEN_COUNT);
self.completion_tokens = self.completion_tokens.min(MAX_DURABLE_TOKEN_COUNT);
self.total_tokens = self
.prompt_tokens
.saturating_add(self.completion_tokens)
.min(MAX_DURABLE_TOKEN_COUNT);
self
}
pub fn add_assign_durable(&mut self, other: TokenUsage) {
self.prompt_tokens = self
.prompt_tokens
.saturating_add(other.prompt_tokens)
.min(MAX_DURABLE_TOKEN_COUNT);
self.completion_tokens = self
.completion_tokens
.saturating_add(other.completion_tokens)
.min(MAX_DURABLE_TOKEN_COUNT);
self.total_tokens = self
.prompt_tokens
.saturating_add(self.completion_tokens)
.min(MAX_DURABLE_TOKEN_COUNT);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_zero() {
let usage = TokenUsage::default();
assert_eq!(usage.prompt_tokens, 0);
assert_eq!(usage.completion_tokens, 0);
assert_eq!(usage.total_tokens, 0);
}
#[test]
fn add_assign_accumulates() {
let mut usage1 = TokenUsage {
prompt_tokens: 100,
completion_tokens: 50,
total_tokens: 150,
};
let usage2 = TokenUsage {
prompt_tokens: 200,
completion_tokens: 100,
total_tokens: 300,
};
usage1.add_assign(usage2);
assert_eq!(usage1.prompt_tokens, 300);
assert_eq!(usage1.completion_tokens, 150);
assert_eq!(usage1.total_tokens, 450);
}
#[test]
fn recompute_total_uses_saturating_add() {
let mut usage = TokenUsage {
prompt_tokens: u64::MAX - 5,
completion_tokens: u64::MAX - 9,
total_tokens: 0,
};
usage.recompute_total();
assert_eq!(usage.total_tokens, u64::MAX);
}
#[test]
fn durable_metrics_policy_clamps_components_and_recomputes_total() {
let usage = TokenUsage {
prompt_tokens: u64::MAX,
completion_tokens: u64::MAX,
total_tokens: 1,
}
.clamped_for_durable_metrics();
assert_eq!(usage.prompt_tokens, MAX_DURABLE_TOKEN_COUNT);
assert_eq!(usage.completion_tokens, MAX_DURABLE_TOKEN_COUNT);
assert_eq!(usage.total_tokens, MAX_DURABLE_TOKEN_COUNT);
}
#[test]
fn durable_accumulation_saturates_without_wraparound() {
let mut usage = TokenUsage {
prompt_tokens: MAX_DURABLE_TOKEN_COUNT - 2,
completion_tokens: MAX_DURABLE_TOKEN_COUNT - 3,
total_tokens: MAX_DURABLE_TOKEN_COUNT,
};
usage.add_assign_durable(TokenUsage {
prompt_tokens: 10,
completion_tokens: 20,
total_tokens: 30,
});
assert_eq!(usage.prompt_tokens, MAX_DURABLE_TOKEN_COUNT);
assert_eq!(usage.completion_tokens, MAX_DURABLE_TOKEN_COUNT);
assert_eq!(usage.total_tokens, MAX_DURABLE_TOKEN_COUNT);
}
}