use serde::{Deserialize, Serialize};
use crate::budget::Tokens;
use crate::model::wire::DialectKind;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(into = "UsageRow", try_from = "UsageRow")]
pub struct ModelUsage {
pub input_tokens: Tokens,
pub output_tokens: Tokens,
pub cache_read_tokens: Tokens,
pub cache_write_tokens: Tokens,
pub dialect: Option<DialectKind>,
}
const USAGE_MEANING: u8 = 1;
#[derive(Serialize, Deserialize)]
struct UsageRow {
input_tokens: Tokens,
output_tokens: Tokens,
cache_read_tokens: Tokens,
cache_write_tokens: Tokens,
#[serde(default, skip_serializing_if = "Option::is_none")]
v: Option<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
dialect: Option<DialectKind>,
}
impl From<ModelUsage> for UsageRow {
fn from(usage: ModelUsage) -> UsageRow {
UsageRow {
input_tokens: usage.input_tokens,
output_tokens: usage.output_tokens,
cache_read_tokens: usage.cache_read_tokens,
cache_write_tokens: usage.cache_write_tokens,
v: Some(USAGE_MEANING),
dialect: usage.dialect,
}
}
}
impl TryFrom<UsageRow> for ModelUsage {
type Error = String;
fn try_from(row: UsageRow) -> Result<ModelUsage, String> {
let input_tokens = match row.v {
Some(USAGE_MEANING) => row.input_tokens,
Some(other) => {
return Err(format!(
"usage row version {other} is newer than this build reads \
(it reads {USAGE_MEANING}); read the ledger with a newer sprawling"
));
}
None => {
let cached = row
.cache_read_tokens
.checked_add(row.cache_write_tokens)
.ok_or("usage row cache counts overflow a token count")?;
if cached > row.input_tokens {
row.input_tokens
.checked_add(cached)
.ok_or("usage row counts overflow a token count")?
} else {
row.input_tokens
}
}
};
Ok(ModelUsage {
input_tokens,
output_tokens: row.output_tokens,
cache_read_tokens: row.cache_read_tokens,
cache_write_tokens: row.cache_write_tokens,
dialect: row.dialect,
})
}
}