zeph-llm 0.21.2

LLM provider abstraction with Ollama, Claude, OpenAI, and Candle backends
Documentation
// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
// SPDX-License-Identifier: MIT OR Apache-2.0

/// Tracks token usage and cache hit statistics.
///
/// Note: `last_cache` is only populated by Claude and `OpenAI` providers.
/// Ollama and Gemini only use `last_usage`.
///
/// `last_reasoning` stores reasoning tokens, which are a **subset** of completion tokens
/// (`OpenAI` o-series only). Never add reasoning tokens to cost separately.
#[derive(Debug, Default)]
#[allow(clippy::struct_field_names)] // all fields are `last_*` by design — they track the last seen value
pub(crate) struct UsageTracker {
    last_usage: std::sync::Mutex<Option<(u64, u64)>>,
    last_cache: std::sync::Mutex<Option<(u64, u64)>>,
    last_reasoning: std::sync::Mutex<Option<u64>>,
}

impl UsageTracker {
    pub(crate) fn record_usage(&self, input: u64, output: u64) {
        if let Ok(mut g) = self.last_usage.lock() {
            *g = Some((input, output));
        }
    }

    pub(crate) fn record_cache(&self, creation: u64, read: u64) {
        if let Ok(mut g) = self.last_cache.lock() {
            *g = Some((creation, read));
        }
    }

    /// Record reasoning tokens from the last response.
    ///
    /// Reasoning tokens are a **subset** of completion tokens; callers must not add them
    /// to cost calculations.
    pub(crate) fn record_reasoning(&self, tokens: u64) {
        if let Ok(mut g) = self.last_reasoning.lock() {
            *g = Some(tokens);
        }
    }

    pub(crate) fn last_usage(&self) -> Option<(u64, u64)> {
        self.last_usage.lock().ok().and_then(|g| *g)
    }

    pub(crate) fn last_cache_usage(&self) -> Option<(u64, u64)> {
        self.last_cache.lock().ok().and_then(|g| *g)
    }

    /// Returns reasoning tokens from the last response, or `None` if the provider
    /// does not report them.
    pub(crate) fn last_reasoning(&self) -> Option<u64> {
        self.last_reasoning.lock().ok().and_then(|g| *g)
    }
}

impl Clone for UsageTracker {
    fn clone(&self) -> Self {
        Self::default()
    }
}