agentd/supervisor/
budget.rs1use crate::agentloop::stop::TerminalStatus;
12use crate::wire::intel::Usage;
13use std::time::Instant;
14
15#[derive(Debug)]
16pub struct Budget {
17 max_steps: u32,
18 max_tokens: u64,
19 deadline: Instant,
20 steps: u32,
21 tokens: u64,
22}
23
24impl Budget {
25 pub fn new(max_steps: u32, max_tokens: u64, deadline: Instant) -> Budget {
26 Budget {
27 max_steps,
28 max_tokens,
29 deadline,
30 steps: 0,
31 tokens: 0,
32 }
33 }
34
35 pub fn record_step(&mut self) {
37 self.steps += 1;
38 }
39
40 pub fn record_usage(&mut self, usage: Usage) {
42 self.tokens = self.tokens.saturating_add(usage.total());
43 }
44
45 pub fn tokens(&self) -> u64 {
46 self.tokens
47 }
48 pub fn steps(&self) -> u32 {
49 self.steps
50 }
51 pub fn max_steps(&self) -> u32 {
53 self.max_steps
54 }
55
56 pub fn exceeded(&self) -> Option<TerminalStatus> {
60 if Instant::now() >= self.deadline {
61 Some(TerminalStatus::Deadline)
62 } else if self.steps >= self.max_steps {
63 Some(TerminalStatus::ExhaustedSteps)
64 } else if self.tokens >= self.max_tokens {
65 Some(TerminalStatus::ExhaustedTokens)
66 } else {
67 None
68 }
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use std::time::Duration;
76
77 fn far() -> Instant {
78 Instant::now() + Duration::from_secs(3600)
79 }
80
81 #[test]
82 fn step_bound() {
83 let mut b = Budget::new(2, 1000, far());
84 assert!(b.exceeded().is_none());
85 b.record_step();
86 b.record_step();
87 assert_eq!(b.exceeded(), Some(TerminalStatus::ExhaustedSteps));
88 }
89
90 #[test]
91 fn token_bound() {
92 let mut b = Budget::new(100, 50, far());
93 b.record_usage(Usage {
94 input_tokens: 40,
95 output_tokens: 20,
96 });
97 assert_eq!(b.exceeded(), Some(TerminalStatus::ExhaustedTokens));
98 }
99
100 #[test]
101 fn deadline_bound() {
102 let b = Budget::new(100, 1000, Instant::now() - Duration::from_secs(1));
103 assert_eq!(b.exceeded(), Some(TerminalStatus::Deadline));
104 }
105}