use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct Usage {
pub input_tokens: u32,
#[serde(default)]
pub cache_read_tokens: u32,
#[serde(default)]
pub cache_write_tokens: u32,
pub output_tokens: u32,
#[serde(default)]
pub reasoning_tokens: u32,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub server_tool_calls: BTreeMap<String, u32>,
}
impl Usage {
pub fn prompt_tokens(&self) -> u32 {
self.input_tokens + self.cache_read_tokens + self.cache_write_tokens
}
pub fn completion_tokens(&self) -> u32 {
self.output_tokens + self.reasoning_tokens
}
pub fn total_tokens(&self) -> u32 {
self.prompt_tokens() + self.completion_tokens()
}
}
mod peek {
use crate::types::{AnthropicUsage, GeminiUsage, OpenAiUsage};
use serde::Deserialize;
#[derive(Deserialize)]
pub struct OpenAi {
pub usage: Option<OpenAiUsage>,
}
#[derive(Deserialize)]
pub struct Anthropic {
pub usage: Option<AnthropicUsage>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Gemini {
pub usage_metadata: Option<GeminiUsage>,
}
}
impl From<&[u8]> for Usage {
fn from(raw: &[u8]) -> Self {
if let Ok(peek::OpenAi { usage: Some(u) }) = crate::json::from_slice(raw)
&& (u.prompt_tokens > 0 || u.completion_tokens > 0)
{
return Usage::from(&u);
}
if let Ok(peek::Anthropic { usage: Some(u) }) = crate::json::from_slice(raw)
&& (u.input_tokens > 0 || u.output_tokens > 0)
{
return Usage::from(&u);
}
if let Ok(peek::Gemini {
usage_metadata: Some(u),
}) = crate::json::from_slice(raw)
&& u.total_token_count > 0
{
return Usage::from(&u);
}
Usage::default()
}
}