1use crate::identity::{CorrelationIds, UsageEventId};
7use crate::pricing::{ModelRef, ProviderRef};
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct UsageObservation {
18 pub event_id: UsageEventId,
20
21 pub subject: crate::identity::BillingSubject,
23
24 pub meter_set: MeterSet,
26
27 pub model_ref: ModelRef,
29
30 pub provider_ref: Option<ProviderRef>,
32
33 pub source: UsageSource,
35
36 pub outcome: UsageOutcome,
38
39 pub timing: UsageTiming,
41
42 pub correlation: CorrelationIds,
44
45 pub attributes: Attributes,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct MeterSet {
56 pub meters: HashMap<MeterKind, u64>,
57}
58
59impl MeterSet {
60 pub fn new() -> Self {
62 Self {
63 meters: HashMap::new(),
64 }
65 }
66
67 pub fn accumulate(&mut self, kind: MeterKind, quantity: u64) -> Result<(), MeterSetError> {
72 use std::collections::hash_map::Entry;
73 match self.meters.entry(kind) {
74 Entry::Occupied(mut e) => {
75 let new_val = e
76 .get()
77 .checked_add(quantity)
78 .ok_or_else(|| MeterSetError::Overflow(e.key().clone()))?;
79 e.insert(new_val);
80 }
81 Entry::Vacant(e) => {
82 e.insert(quantity);
83 }
84 }
85 Ok(())
86 }
87
88 pub fn get(&self, kind: &MeterKind) -> u64 {
91 self.meters.get(kind).copied().unwrap_or(0)
92 }
93}
94
95impl Default for MeterSet {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101#[derive(Debug, Clone)]
103pub enum MeterSetError {
104 Overflow(MeterKind),
106}
107
108#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
113pub enum MeterKind {
114 InputTokens,
115 OutputTokens,
116 CachedInputTokens,
117 CachedWriteTokens,
118 ReasoningTokens,
119 AudioInputTokens,
120 AudioOutputTokens,
121 ImageCount,
122 Custom(String),
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
130pub enum UsageSource {
131 ProviderReported,
133 StreamAccumulated,
135 Estimated,
137 Corrected { correction_of: UsageEventId },
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
149pub enum UsageOutcome {
150 Success,
151 Error { code: String },
152 Timeout,
153 Unknown,
154}
155
156#[derive(Debug, Clone, Default, Serialize, Deserialize)]
163#[serde(transparent)]
164pub struct Attributes {
165 inner: HashMap<String, String>,
166}
167
168impl Attributes {
169 pub const MAX_KEY_LEN: usize = 64;
170 pub const MAX_VALUE_LEN: usize = 256;
171
172 pub fn new() -> Self {
174 Self {
175 inner: HashMap::new(),
176 }
177 }
178
179 pub fn insert(&mut self, key: impl Into<String>, value: impl Into<String>) -> Result<(), AttributeError> {
181 let key = key.into();
182 let value = value.into();
183
184 if key.starts_with("sys.") {
185 return Err(AttributeError::ReservedPrefix(key));
186 }
187
188 if key.len() > Self::MAX_KEY_LEN {
189 let len = key.len();
190 return Err(AttributeError::KeyTooLong { key, len });
191 }
192
193 if value.len() > Self::MAX_VALUE_LEN {
194 let len = value.len();
195 return Err(AttributeError::ValueTooLong { key, len });
196 }
197
198 self.inner.insert(key, value);
199 Ok(())
200 }
201
202 pub fn get(&self, key: &str) -> Option<&str> {
204 self.inner.get(key).map(|s| s.as_str())
205 }
206
207 pub fn iter(&self) -> impl Iterator<Item = (&String, &String)> {
209 self.inner.iter()
210 }
211}
212
213#[derive(Debug, Clone)]
215pub enum AttributeError {
216 ReservedPrefix(String),
217 KeyTooLong { key: String, len: usize },
218 ValueTooLong { key: String, len: usize },
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct UsageTiming {
224 pub observed_at: DateTime<Utc>,
226
227 pub completed_at: Option<DateTime<Utc>>,
229}
230
231#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
233pub struct CurrencyCode(pub String);
234
235impl CurrencyCode {
236 pub fn usd() -> Self {
238 CurrencyCode("USD".to_string())
239 }
240 pub fn cny() -> Self {
241 CurrencyCode("CNY".to_string())
242 }
243 pub fn eur() -> Self {
244 CurrencyCode("EUR".to_string())
245 }
246}
247
248impl std::str::FromStr for CurrencyCode {
249 type Err = CurrencyCodeError;
250
251 fn from_str(s: &str) -> Result<Self, Self::Err> {
254 if s.len() == 3 && s.chars().all(|c| c.is_ascii_uppercase()) {
255 Ok(CurrencyCode(s.to_string()))
256 } else {
257 Err(CurrencyCodeError::Invalid(s.to_string()))
258 }
259 }
260}
261
262#[derive(Debug, Clone)]
264pub enum CurrencyCodeError {
265 Invalid(String),
266}
267
268impl std::fmt::Display for CurrencyCodeError {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 match self {
271 CurrencyCodeError::Invalid(s) => write!(f, "Invalid currency code: {s}"),
272 }
273 }
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[test]
281 fn meter_set_accumulate_accumulates() {
282 let mut ms = MeterSet::new();
283 ms.accumulate(MeterKind::InputTokens, 100).unwrap();
284 ms.accumulate(MeterKind::InputTokens, 50).unwrap();
285 assert_eq!(ms.get(&MeterKind::InputTokens), 150);
286 }
287
288 #[test]
289 fn meter_set_overflow_returns_error() {
290 let mut ms = MeterSet::new();
291 ms.accumulate(MeterKind::InputTokens, u64::MAX).unwrap();
292 let result = ms.accumulate(MeterKind::InputTokens, 1);
293 assert!(matches!(result, Err(MeterSetError::Overflow(_))));
294 }
295
296 #[test]
297 fn meter_set_get_missing_returns_zero() {
298 let ms = MeterSet::new();
299 assert_eq!(ms.get(&MeterKind::OutputTokens), 0);
300 }
301
302 #[test]
303 fn meter_kind_custom_hash() {
304 let mut map = HashMap::new();
305 map.insert(MeterKind::Custom("test".to_string()), 1);
306 assert_eq!(map.get(&MeterKind::Custom("test".to_string())), Some(&1));
307 }
308
309 #[test]
310 fn meter_kind_enum_variant_not_confused_with_custom() {
311 let mut map = HashMap::new();
312 map.insert(MeterKind::InputTokens, 1);
313 map.insert(MeterKind::Custom("InputTokens".to_string()), 2);
314 assert_eq!(map.len(), 2);
315 }
316
317 #[test]
318 fn attributes_insert_valid() {
319 let mut attrs = Attributes::new();
320 assert!(attrs.insert("key1", "value1").is_ok());
321 assert_eq!(attrs.get("key1"), Some("value1"));
322 }
323
324 #[test]
325 fn attributes_rejects_reserved_prefix() {
326 let mut attrs = Attributes::new();
327 let result = attrs.insert("sys.test", "value");
328 assert!(matches!(result, Err(AttributeError::ReservedPrefix(_))));
329 }
330
331 #[test]
332 fn attributes_rejects_too_long_key() {
333 let mut attrs = Attributes::new();
334 let long_key = "a".repeat(65);
335 let result = attrs.insert(long_key, "value");
336 assert!(matches!(result, Err(AttributeError::KeyTooLong { .. })));
337 }
338}