Skip to main content

codetether_agent/telemetry/tokens/
snapshot.rs

1//! Read-only snapshots of token usage.
2//!
3//! Snapshots are plain data — they don't hold locks and can be freely cloned
4//! across threads or serialized for the TUI / API.
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9use super::totals::TokenTotals;
10
11/// A point-in-time view of the global [`super::AtomicTokenCounter`].
12///
13/// # Examples
14///
15/// ```rust
16/// use codetether_agent::telemetry::GlobalTokenSnapshot;
17///
18/// let s = GlobalTokenSnapshot::new(1_000, 500, 1_500);
19/// assert_eq!(s.totals.total(), 1_500);
20/// assert!(s.summary().contains("1500"));
21/// ```
22#[derive(Debug, Clone, Default)]
23pub struct GlobalTokenSnapshot {
24    /// Cumulative prompt tokens.
25    pub input: u64,
26    /// Cumulative completion tokens.
27    pub output: u64,
28    /// Deprecated duplicate of [`Self::totals`] kept for API compatibility.
29    pub total: TokenTotals,
30    /// Preferred aggregate view.
31    pub totals: TokenTotals,
32    /// Number of completions recorded since startup.
33    pub request_count: u64,
34}
35
36impl GlobalTokenSnapshot {
37    /// Build a snapshot from raw counts (the third arg is ignored and kept
38    /// for API compatibility).
39    pub fn new(input: u64, output: u64, _total: u64) -> Self {
40        Self {
41            input,
42            output,
43            total: TokenTotals::new(input, output),
44            totals: TokenTotals::new(input, output),
45            request_count: 0,
46        }
47    }
48
49    /// Human-readable one-liner, e.g. `"1500 total tokens (1000 input, 500 output)"`.
50    pub fn summary(&self) -> String {
51        format!(
52            "{} total tokens ({} input, {} output)",
53            self.totals.total(),
54            self.input,
55            self.output
56        )
57    }
58}
59
60/// Per-model token snapshot. Instances are stamped with [`Utc::now`] when
61/// produced by [`super::AtomicTokenCounter::model_snapshots`].
62///
63/// # Examples
64///
65/// ```rust
66/// use codetether_agent::telemetry::TokenUsageSnapshot;
67///
68/// // `current()` pulls from the global counter and never panics.
69/// let s = TokenUsageSnapshot::current();
70/// assert_eq!(s.name, "global");
71/// ```
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct TokenUsageSnapshot {
74    /// Scope name (model id, or `"global"` for the whole process).
75    pub name: String,
76    /// Prompt tokens observed in this scope.
77    pub prompt_tokens: u64,
78    /// Completion tokens observed in this scope.
79    pub completion_tokens: u64,
80    /// `prompt_tokens + completion_tokens`.
81    pub total_tokens: u64,
82    /// Convenience aggregate.
83    pub totals: TokenTotals,
84    /// When this snapshot was produced.
85    pub timestamp: DateTime<Utc>,
86    /// Number of requests rolled into this snapshot (0 if unknown).
87    pub request_count: u64,
88}
89
90impl TokenUsageSnapshot {
91    /// Sample the global [`super::super::TOKEN_USAGE`] counter right now.
92    pub fn current() -> Self {
93        let (prompt, comp, total) = super::super::TOKEN_USAGE.get();
94        Self {
95            name: "global".to_string(),
96            prompt_tokens: prompt,
97            completion_tokens: comp,
98            total_tokens: total,
99            totals: TokenTotals::new(prompt, comp),
100            timestamp: Utc::now(),
101            request_count: 0,
102        }
103    }
104
105    /// Human-readable one-liner.
106    pub fn summary(&self) -> String {
107        format!(
108            "{} total tokens ({} input, {} output)",
109            self.totals.total(),
110            self.prompt_tokens,
111            self.completion_tokens
112        )
113    }
114}