use serde::Serialize;
#[derive(Debug, Clone, Default, PartialEq, Serialize)]
#[expect(
clippy::struct_field_names,
reason = "provider payloads use token counter names with explicit token suffixes"
)]
pub struct TokenUsage {
pub input_tokens: i64,
pub output_tokens: i64,
pub cache_read_tokens: i64,
pub cache_write_tokens: i64,
pub reasoning_tokens: i64,
pub total_tokens: i64,
}
impl TokenUsage {
#[must_use]
pub const fn is_empty(&self) -> bool {
self.input_tokens == 0
&& self.output_tokens == 0
&& self.cache_read_tokens == 0
&& self.cache_write_tokens == 0
&& self.reasoning_tokens == 0
&& self.total_tokens == 0
}
#[must_use]
pub const fn request_side(&self) -> Self {
Self {
input_tokens: self.input_tokens,
output_tokens: 0,
cache_read_tokens: self.cache_read_tokens,
cache_write_tokens: self.cache_write_tokens,
reasoning_tokens: 0,
total_tokens: self.input_tokens,
}
}
#[must_use]
pub const fn response_side(&self) -> Self {
Self {
input_tokens: 0,
output_tokens: self.output_tokens,
cache_read_tokens: 0,
cache_write_tokens: 0,
reasoning_tokens: self.reasoning_tokens,
total_tokens: self.output_tokens,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum TokenUsageSource {
ProviderReported,
EstimatedVisibleContent,
Absent,
}
impl TokenUsageSource {
#[must_use]
pub const fn provider_or_absent(usage: &TokenUsage) -> Self {
if usage.is_empty() {
Self::Absent
} else {
Self::ProviderReported
}
}
#[must_use]
pub const fn is_estimated(self) -> bool {
matches!(self, Self::EstimatedVisibleContent)
}
}