#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct RunUsage {
pub input_tokens: u64,
pub output_tokens: u64,
pub cache_read_tokens: u64,
pub cache_creation_tokens: u64,
}
impl RunUsage {
pub const fn total_tokens(&self) -> u64 {
self.input_tokens + self.output_tokens
}
#[must_use]
pub const fn plus(self, other: Self) -> Self {
Self {
input_tokens: self.input_tokens + other.input_tokens,
output_tokens: self.output_tokens + other.output_tokens,
cache_read_tokens: self.cache_read_tokens + other.cache_read_tokens,
cache_creation_tokens: self.cache_creation_tokens + other.cache_creation_tokens,
}
}
pub(crate) fn recording(self, event: &mentra::SessionEvent) -> Self {
let mentra::SessionEvent::UsageReport {
input_tokens,
output_tokens,
cache_read_tokens,
cache_creation_tokens,
..
} = event
else {
return self;
};
Self {
input_tokens: self.input_tokens + *input_tokens,
output_tokens: self.output_tokens + *output_tokens,
cache_read_tokens: self.cache_read_tokens + *cache_read_tokens,
cache_creation_tokens: self.cache_creation_tokens + *cache_creation_tokens,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn usage_report(input: u64, output: u64) -> mentra::SessionEvent {
mentra::SessionEvent::UsageReport {
agent_id: "a1".to_string(),
input_tokens: input,
output_tokens: output,
cache_read_tokens: 1,
cache_creation_tokens: 2,
}
}
#[test]
fn usage_is_summed_over_the_rounds_of_a_turn() {
let usage = RunUsage::default()
.recording(&usage_report(100, 20))
.recording(&usage_report(150, 30));
assert_eq!(usage.input_tokens, 250);
assert_eq!(usage.output_tokens, 50);
assert_eq!(usage.cache_read_tokens, 2);
assert_eq!(usage.cache_creation_tokens, 4);
assert_eq!(usage.total_tokens(), 300, "the budget counts these two");
}
#[test]
fn an_event_that_reports_no_usage_changes_nothing() {
let counted = RunUsage::default().recording(&usage_report(10, 5));
let after = counted.recording(&mentra::SessionEvent::UserMessage {
text: "hello".to_string(),
});
assert_eq!(after, counted);
}
#[test]
fn a_turn_that_reported_nothing_reports_zero() {
let usage = RunUsage::default();
assert_eq!(usage.total_tokens(), 0);
}
#[test]
fn usage_adds_up_across_runs() {
let one = RunUsage::default().recording(&usage_report(100, 10));
let two = RunUsage::default().recording(&usage_report(200, 20));
assert_eq!(one.plus(two).total_tokens(), 330);
assert_eq!(one.total_tokens(), 110, "the originals are untouched");
}
}