use std::fmt;
use std::str::FromStr;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use thiserror::Error;
pub const MAX_SAFE_INTEGER: u64 = 9_007_199_254_740_991;
const MAX_DECIMAL_DIGITS: usize = 36;
const MAX_DECIMAL_SCALE: usize = 18;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
#[serde(transparent)]
pub struct TokenCount(u64);
impl TokenCount {
pub const fn new(value: u64) -> Result<Self, UsageError> {
if value <= MAX_SAFE_INTEGER {
Ok(Self(value))
} else {
Err(UsageError::UnsafeInteger)
}
}
#[must_use]
pub const fn get(self) -> u64 {
self.0
}
}
impl<'de> Deserialize<'de> for TokenCount {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value = u64::deserialize(deserializer)?;
Self::new(value).map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
#[allow(clippy::struct_field_names)] pub struct Usage {
input_tokens: TokenCount,
output_tokens: TokenCount,
#[serde(skip_serializing_if = "Option::is_none")]
cache_read_tokens: Option<TokenCount>,
#[serde(skip_serializing_if = "Option::is_none")]
cache_write_tokens: Option<TokenCount>,
#[serde(skip_serializing_if = "Option::is_none")]
reasoning_tokens: Option<TokenCount>,
}
impl Usage {
#[must_use]
pub const fn new(input_tokens: TokenCount, output_tokens: TokenCount) -> Self {
Self {
input_tokens,
output_tokens,
cache_read_tokens: None,
cache_write_tokens: None,
reasoning_tokens: None,
}
}
#[must_use]
pub const fn input_tokens(&self) -> TokenCount {
self.input_tokens
}
#[must_use]
pub const fn output_tokens(&self) -> TokenCount {
self.output_tokens
}
#[must_use]
pub const fn cache_read_tokens(&self) -> Option<TokenCount> {
self.cache_read_tokens
}
#[must_use]
pub const fn cache_write_tokens(&self) -> Option<TokenCount> {
self.cache_write_tokens
}
#[must_use]
pub const fn reasoning_tokens(&self) -> Option<TokenCount> {
self.reasoning_tokens
}
#[must_use]
pub const fn with_cache_read(mut self, value: TokenCount) -> Self {
self.cache_read_tokens = Some(value);
self
}
#[must_use]
pub const fn with_cache_write(mut self, value: TokenCount) -> Self {
self.cache_write_tokens = Some(value);
self
}
pub fn with_reasoning(mut self, value: TokenCount) -> Result<Self, UsageError> {
if value > self.output_tokens {
return Err(UsageError::ReasoningExceedsOutput);
}
self.reasoning_tokens = Some(value);
Ok(self)
}
pub fn total_tokens(&self) -> Result<TokenCount, UsageError> {
let total = [
Some(self.input_tokens),
Some(self.output_tokens),
self.cache_read_tokens,
self.cache_write_tokens,
]
.into_iter()
.flatten()
.try_fold(0_u64, |total, value| total.checked_add(value.get()))
.ok_or(UsageError::TotalOverflow)?;
TokenCount::new(total)
}
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct RawUsage {
#[serde(rename = "inputTokens")]
input: TokenCount,
#[serde(rename = "outputTokens")]
output: TokenCount,
#[serde(default, rename = "cacheReadTokens")]
cache_read: Option<TokenCount>,
#[serde(default, rename = "cacheWriteTokens")]
cache_write: Option<TokenCount>,
#[serde(default, rename = "reasoningTokens")]
reasoning: Option<TokenCount>,
}
impl<'de> Deserialize<'de> for Usage {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let raw = RawUsage::deserialize(deserializer)?;
let mut usage = Self::new(raw.input, raw.output);
usage.cache_read_tokens = raw.cache_read;
usage.cache_write_tokens = raw.cache_write;
if let Some(reasoning) = raw.reasoning {
usage = usage
.with_reasoning(reasoning)
.map_err(serde::de::Error::custom)?;
}
usage.total_tokens().map_err(serde::de::Error::custom)?;
Ok(usage)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
pub enum UsageError {
#[error("token count exceeds the JSON safe-integer range")]
UnsafeInteger,
#[error("reasoning tokens must be a subset of output tokens")]
ReasoningExceedsOutput,
#[error("total token count exceeds the supported range")]
TotalOverflow,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DecimalAmount(String);
impl DecimalAmount {
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
pub enum DecimalAmountParseError {
#[error("amount must use canonical non-negative decimal text")]
InvalidFormat,
#[error("amount exceeds the supported precision or scale")]
TooPrecise,
}
impl FromStr for DecimalAmount {
type Err = DecimalAmountParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let (integer, fraction) = value
.split_once('.')
.map_or((value, None), |(left, right)| (left, Some(right)));
if integer.is_empty()
|| !integer.bytes().all(|byte| byte.is_ascii_digit())
|| (integer.len() > 1 && integer.starts_with('0'))
{
return Err(DecimalAmountParseError::InvalidFormat);
}
if let Some(fraction) = fraction {
if fraction.is_empty()
|| !fraction.bytes().all(|byte| byte.is_ascii_digit())
|| fraction.ends_with('0')
{
return Err(DecimalAmountParseError::InvalidFormat);
}
if fraction.len() > MAX_DECIMAL_SCALE {
return Err(DecimalAmountParseError::TooPrecise);
}
}
let digits = integer.len() + fraction.map_or(0, str::len);
if digits > MAX_DECIMAL_DIGITS {
return Err(DecimalAmountParseError::TooPrecise);
}
Ok(Self(value.to_owned()))
}
}
impl fmt::Display for DecimalAmount {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.0)
}
}
impl Serialize for DecimalAmount {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.0)
}
}
impl<'de> Deserialize<'de> for DecimalAmount {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct CurrencyCode(String);
impl CurrencyCode {
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
#[error("currency must contain exactly three uppercase ASCII letters")]
pub struct CurrencyCodeParseError;
impl FromStr for CurrencyCode {
type Err = CurrencyCodeParseError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
if value.len() == 3 && value.bytes().all(|byte| byte.is_ascii_uppercase()) {
Ok(Self(value.to_owned()))
} else {
Err(CurrencyCodeParseError)
}
}
}
impl fmt::Display for CurrencyCode {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.0)
}
}
impl Serialize for CurrencyCode {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.0)
}
}
impl<'de> Deserialize<'de> for CurrencyCode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer)?
.parse()
.map_err(serde::de::Error::custom)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CostUnit {
MajorCurrency,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExactCost {
amount: DecimalAmount,
currency: CurrencyCode,
unit: CostUnit,
}
impl ExactCost {
#[must_use]
pub const fn new(amount: DecimalAmount, currency: CurrencyCode) -> Self {
Self {
amount,
currency,
unit: CostUnit::MajorCurrency,
}
}
#[must_use]
pub const fn amount(&self) -> &DecimalAmount {
&self.amount
}
#[must_use]
pub const fn currency(&self) -> &CurrencyCode {
&self.currency
}
#[must_use]
pub const fn unit(&self) -> CostUnit {
self.unit
}
}