gproxy_protocol/usage/
types.rs1use std::collections::BTreeMap;
9
10use rust_decimal::Decimal;
11
12use crate::connection::StreamFraming;
13
14#[derive(Debug, Clone, Default, PartialEq, Eq)]
16pub struct TokenUsage {
17 pub input_tokens: Option<u64>,
19 pub output_tokens: Option<u64>,
21 pub cached_input_tokens: Option<u64>,
22 pub cache_creation: Vec<CacheCreation>,
24 pub reasoning_tokens: Option<u64>,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
29pub struct CacheCreation {
30 pub tokens: u64,
31 pub ttl_seconds: Option<u64>,
32}
33
34impl TokenUsage {
35 pub fn cache_creation_tokens(&self) -> u64 {
36 self.cache_creation
37 .iter()
38 .fold(0, |sum, entry| sum.saturating_add(entry.tokens))
39 }
40
41 pub fn add_cache_creation(&mut self, tokens: Option<u64>, ttl_seconds: Option<u64>) {
42 let Some(tokens) = tokens else { return };
43 if let Some(entry) = self
44 .cache_creation
45 .iter_mut()
46 .find(|entry| entry.ttl_seconds == ttl_seconds)
47 {
48 entry.tokens = entry.tokens.saturating_add(tokens);
49 } else {
50 self.cache_creation.push(CacheCreation {
51 tokens,
52 ttl_seconds,
53 });
54 }
55 }
56}
57
58#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
59pub enum UsageCompleteness {
60 Complete,
61 Partial,
62 #[default]
63 Unknown,
64}
65
66#[derive(Debug, Clone, Default, PartialEq, Eq)]
67pub struct NormalizedUsage {
68 pub tokens: TokenUsage,
69 pub metrics: BTreeMap<String, Decimal>,
72 pub dimensions: BTreeMap<String, String>,
74 pub actual_service_tier: Option<String>,
76 pub completeness: UsageCompleteness,
77 pub attempts: Vec<UsageAttempt>,
79 pub responses: Vec<ResponseUsage>,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct ResponseUsage {
87 pub id: String,
88 pub usage: Box<NormalizedUsage>,
89}
90
91impl NormalizedUsage {
92 pub fn aggregate<'a>(values: impl IntoIterator<Item = &'a NormalizedUsage>) -> Self {
95 let mut total = Self {
96 completeness: UsageCompleteness::Complete,
97 ..Default::default()
98 };
99 let mut first = true;
100 fn add(target: &mut Option<u64>, value: Option<u64>) {
101 if let Some(v) = value {
102 *target = Some(target.unwrap_or(0).saturating_add(v));
103 }
104 }
105 for value in values {
106 add(&mut total.tokens.input_tokens, value.tokens.input_tokens);
107 add(&mut total.tokens.output_tokens, value.tokens.output_tokens);
108 add(
109 &mut total.tokens.cached_input_tokens,
110 value.tokens.cached_input_tokens,
111 );
112 add(
113 &mut total.tokens.reasoning_tokens,
114 value.tokens.reasoning_tokens,
115 );
116 for entry in &value.tokens.cache_creation {
117 total
118 .tokens
119 .add_cache_creation(Some(entry.tokens), entry.ttl_seconds);
120 }
121 for (key, count) in &value.metrics {
122 let sum = total.metrics.entry(key.clone()).or_default();
125 *sum = sum.saturating_add(*count);
126 }
127 if first {
128 total.dimensions = value.dimensions.clone();
129 total.actual_service_tier = value.actual_service_tier.clone();
130 first = false;
131 } else {
132 total
133 .dimensions
134 .retain(|key, v| value.dimensions.get(key) == Some(v));
135 if total.actual_service_tier != value.actual_service_tier {
136 total.actual_service_tier = None;
137 }
138 }
139 if value.completeness != UsageCompleteness::Complete {
140 total.completeness = UsageCompleteness::Partial;
141 }
142 }
143 total
144 }
145}
146
147#[derive(Debug, Clone, PartialEq, Eq)]
148pub struct UsageAttempt {
149 pub operation: Option<crate::Operation>,
151 pub model: String,
152 pub usage: Box<NormalizedUsage>,
153 pub billable: Option<bool>,
155 pub started_at_ms: Option<i64>,
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub enum UsageTransport {
161 Http { framing: Option<StreamFraming> },
162 WebSocket,
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub enum UsageStreamEnd {
169 Complete,
170 Interrupted,
171}