bamboo_domain/
token_usage.rs1use serde::{Deserialize, Serialize};
4
5pub const MAX_DURABLE_TOKEN_COUNT: u64 = i64::MAX as u64;
12
13#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
19pub struct TokenUsage {
20 pub prompt_tokens: u64,
22 pub completion_tokens: u64,
24 pub total_tokens: u64,
26}
27
28impl TokenUsage {
29 pub fn add_assign(&mut self, other: TokenUsage) {
31 self.prompt_tokens = self.prompt_tokens.saturating_add(other.prompt_tokens);
32 self.completion_tokens = self
33 .completion_tokens
34 .saturating_add(other.completion_tokens);
35 self.total_tokens = self.total_tokens.saturating_add(other.total_tokens);
36 }
37
38 pub fn recompute_total(&mut self) {
40 self.total_tokens = self.prompt_tokens.saturating_add(self.completion_tokens);
41 }
42
43 pub fn clamped_for_durable_metrics(mut self) -> Self {
50 self.prompt_tokens = self.prompt_tokens.min(MAX_DURABLE_TOKEN_COUNT);
51 self.completion_tokens = self.completion_tokens.min(MAX_DURABLE_TOKEN_COUNT);
52 self.total_tokens = self
53 .prompt_tokens
54 .saturating_add(self.completion_tokens)
55 .min(MAX_DURABLE_TOKEN_COUNT);
56 self
57 }
58
59 pub fn add_assign_durable(&mut self, other: TokenUsage) {
61 self.prompt_tokens = self
62 .prompt_tokens
63 .saturating_add(other.prompt_tokens)
64 .min(MAX_DURABLE_TOKEN_COUNT);
65 self.completion_tokens = self
66 .completion_tokens
67 .saturating_add(other.completion_tokens)
68 .min(MAX_DURABLE_TOKEN_COUNT);
69 self.total_tokens = self
70 .prompt_tokens
71 .saturating_add(self.completion_tokens)
72 .min(MAX_DURABLE_TOKEN_COUNT);
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn default_is_zero() {
82 let usage = TokenUsage::default();
83 assert_eq!(usage.prompt_tokens, 0);
84 assert_eq!(usage.completion_tokens, 0);
85 assert_eq!(usage.total_tokens, 0);
86 }
87
88 #[test]
89 fn add_assign_accumulates() {
90 let mut usage1 = TokenUsage {
91 prompt_tokens: 100,
92 completion_tokens: 50,
93 total_tokens: 150,
94 };
95 let usage2 = TokenUsage {
96 prompt_tokens: 200,
97 completion_tokens: 100,
98 total_tokens: 300,
99 };
100 usage1.add_assign(usage2);
101 assert_eq!(usage1.prompt_tokens, 300);
102 assert_eq!(usage1.completion_tokens, 150);
103 assert_eq!(usage1.total_tokens, 450);
104 }
105
106 #[test]
107 fn recompute_total_uses_saturating_add() {
108 let mut usage = TokenUsage {
109 prompt_tokens: u64::MAX - 5,
110 completion_tokens: u64::MAX - 9,
111 total_tokens: 0,
112 };
113 usage.recompute_total();
114 assert_eq!(usage.total_tokens, u64::MAX);
115 }
116
117 #[test]
118 fn durable_metrics_policy_clamps_components_and_recomputes_total() {
119 let usage = TokenUsage {
120 prompt_tokens: u64::MAX,
121 completion_tokens: u64::MAX,
122 total_tokens: 1,
123 }
124 .clamped_for_durable_metrics();
125
126 assert_eq!(usage.prompt_tokens, MAX_DURABLE_TOKEN_COUNT);
127 assert_eq!(usage.completion_tokens, MAX_DURABLE_TOKEN_COUNT);
128 assert_eq!(usage.total_tokens, MAX_DURABLE_TOKEN_COUNT);
129 }
130
131 #[test]
132 fn durable_accumulation_saturates_without_wraparound() {
133 let mut usage = TokenUsage {
134 prompt_tokens: MAX_DURABLE_TOKEN_COUNT - 2,
135 completion_tokens: MAX_DURABLE_TOKEN_COUNT - 3,
136 total_tokens: MAX_DURABLE_TOKEN_COUNT,
137 };
138 usage.add_assign_durable(TokenUsage {
139 prompt_tokens: 10,
140 completion_tokens: 20,
141 total_tokens: 30,
142 });
143
144 assert_eq!(usage.prompt_tokens, MAX_DURABLE_TOKEN_COUNT);
145 assert_eq!(usage.completion_tokens, MAX_DURABLE_TOKEN_COUNT);
146 assert_eq!(usage.total_tokens, MAX_DURABLE_TOKEN_COUNT);
147 }
148}