1use serde::{Deserialize, Serialize};
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
28pub struct Cost {
29 pub input: f64,
31 pub output: f64,
33 pub cache_read: f64,
35 pub cache_write: f64,
37 pub total: f64,
39}
40
41#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
60pub struct Usage {
61 pub input_tokens: u32,
63 pub output_tokens: u32,
65 pub cache_read_tokens: u32,
67 pub cache_write_tokens: u32,
69 pub reasoning_tokens: Option<u32>,
71 pub total_tokens: u32,
73 pub cost: Option<Cost>,
75}
76
77impl Usage {
78 pub fn visible_output_tokens(&self) -> u32 {
80 self.output_tokens
81 .saturating_sub(self.reasoning_tokens.unwrap_or(0))
82 }
83
84 pub fn non_cached_input_tokens(&self) -> u32 {
86 self.input_tokens
87 .saturating_sub(self.cache_read_tokens)
88 .saturating_sub(self.cache_write_tokens)
89 }
90}
91
92
93#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn test_usage_default_is_zero() {
103 let u = Usage::default();
104 assert_eq!(u.input_tokens, 0);
105 assert_eq!(u.output_tokens, 0);
106 assert_eq!(u.total_tokens, 0);
107 assert!(u.cost.is_none());
108 }
109
110 #[test]
111 fn test_visible_output_tokens() {
112 let u = Usage {
113 output_tokens: 100,
114 reasoning_tokens: Some(40),
115 ..Default::default()
116 };
117 assert_eq!(u.visible_output_tokens(), 60);
118 }
119
120 #[test]
121 fn test_visible_output_tokens_no_reasoning() {
122 let u = Usage {
123 output_tokens: 100,
124 reasoning_tokens: None,
125 ..Default::default()
126 };
127 assert_eq!(u.visible_output_tokens(), 100);
128 }
129
130 #[test]
131 fn test_visible_output_tokens_saturating() {
132 let u = Usage {
133 output_tokens: 10,
134 reasoning_tokens: Some(999),
135 ..Default::default()
136 };
137 assert_eq!(u.visible_output_tokens(), 0);
138 }
139
140 #[test]
141 fn test_non_cached_input_tokens() {
142 let u = Usage {
143 input_tokens: 1000,
144 cache_read_tokens: 600,
145 cache_write_tokens: 200,
146 ..Default::default()
147 };
148 assert_eq!(u.non_cached_input_tokens(), 200);
149 }
150
151 #[test]
152 fn test_non_cached_input_tokens_saturating() {
153 let u = Usage {
154 input_tokens: 100,
155 cache_read_tokens: 80,
156 cache_write_tokens: 80,
157 ..Default::default()
158 };
159 assert_eq!(u.non_cached_input_tokens(), 0);
160 }
161
162 #[test]
163 fn test_usage_serde_roundtrip() {
164 let u = Usage {
165 input_tokens: 500,
166 output_tokens: 200,
167 cache_read_tokens: 100,
168 cache_write_tokens: 50,
169 reasoning_tokens: Some(30),
170 total_tokens: 700,
171 cost: Some(Cost {
172 input: 0.005,
173 output: 0.010,
174 cache_read: 0.001,
175 cache_write: 0.002,
176 total: 0.018,
177 }),
178 };
179 let json = serde_json::to_string(&u).unwrap();
180 let restored: Usage = serde_json::from_str(&json).unwrap();
181 assert_eq!(u, restored);
182 }
183
184 #[test]
185 fn test_cost_serde_roundtrip() {
186 let c = Cost {
187 input: 0.01,
188 output: 0.03,
189 cache_read: 0.001,
190 cache_write: 0.002,
191 total: 0.043,
192 };
193 let json = serde_json::to_string(&c).unwrap();
194 let restored: Cost = serde_json::from_str(&json).unwrap();
195 assert_eq!(c, restored);
196 }
197}