agentusage 1.3.0

Local-first agent usage tracking
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
pub enum Status {
    #[serde(rename = "OK")]
    Ok,
    #[serde(rename = "NEAR_LIMIT")]
    NearLimit,
    #[serde(rename = "LIMITED")]
    Limited,
    #[serde(rename = "AUTH_REQUIRED")]
    AuthRequired,
    #[serde(rename = "UNSUPPORTED")]
    Unsupported,
    #[serde(rename = "ERROR")]
    Error,
    #[default]
    #[serde(rename = "UNKNOWN")]
    Unknown,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct Metric {
    pub limit: Option<f64>,
    pub remaining: Option<f64>,
    pub used: Option<f64>,
    pub unit: String,
    pub window: String,
}

impl Metric {
    pub fn percent(&self) -> f64 {
        match (self.limit, self.remaining, self.used) {
            (Some(limit), Some(remaining), _) if limit > 0.0 => remaining / limit * 100.0,
            (Some(limit), _, Some(used)) if limit > 0.0 => (limit - used) / limit * 100.0,
            _ => -1.0,
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct TokenUsage {
    pub input_tokens: Option<i64>,
    pub output_tokens: Option<i64>,
    pub reasoning_tokens: Option<i64>,
    pub cache_read_tokens: Option<i64>,
    pub cache_write_tokens: Option<i64>,
    pub total_tokens: Option<i64>,
    pub cost_usd: Option<f64>,
    pub requests: Option<i64>,
}

impl TokenUsage {
    pub fn sum_total_tokens(&mut self) {
        if self.total_tokens.is_some() {
            return;
        }
        let values = [
            self.input_tokens,
            self.output_tokens,
            self.reasoning_tokens,
            self.cache_read_tokens,
            self.cache_write_tokens,
        ];
        if values.iter().any(Option::is_some) {
            self.total_tokens = Some(values.into_iter().flatten().sum());
        }
    }
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct UsageSnapshot {
    pub provider_id: String,
    pub account_id: String,
    pub timestamp: DateTime<Utc>,
    pub status: Status,
    pub metrics: std::collections::BTreeMap<String, Metric>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct UsageEvent {
    pub time: DateTime<Utc>,
    pub provider_id: String,
    pub model: String,
    pub project: String,
    pub session: String,
    pub input_tokens: i64,
    pub output_tokens: i64,
    pub cache_read_tokens: i64,
    pub cache_creation_tokens: i64,
    pub reasoning_tokens: i64,
    pub cost_usd: f64,
    pub has_cost: bool,
}

pub trait UsageProvider {
    fn id(&self) -> &str;
}

pub trait ItemizedUsageProvider {
    fn itemized_usage(&self) -> anyhow::Result<Vec<UsageEvent>>;
}