1#[derive(Clone, Debug, Eq, PartialEq)]
5pub enum ToolBudgetDecision {
6 Continue,
8 ContinueAfterBudgetMessage,
11 Exhausted {
13 segment_iterations: usize,
15 total_batches: usize,
17 continuations_used: usize,
19 },
20}
21
22#[derive(Clone, Debug, Eq, PartialEq)]
24pub struct ToolIterationBudget {
25 segment_limit: usize,
26 continuation_limit: usize,
27 segment_iterations: usize,
28 total_batches: usize,
29 continuations_used: usize,
30}
31
32impl ToolIterationBudget {
33 pub fn new(segment_limit: usize, continuation_limit: usize) -> Self {
35 Self { segment_limit, continuation_limit, segment_iterations: 0, total_batches: 0, continuations_used: 0 }
36 }
37
38 pub fn record_tool_batch(&mut self) {
40 self.segment_iterations = self.segment_iterations.saturating_add(1);
41 self.total_batches = self.total_batches.saturating_add(1);
42 }
43
44 pub fn before_provider_request(&mut self) -> ToolBudgetDecision {
46 if self.segment_iterations < self.segment_limit {
47 return ToolBudgetDecision::Continue;
48 }
49
50 if self.continuations_used < self.continuation_limit {
51 self.segment_iterations = 0;
52 self.continuations_used += 1;
53 return ToolBudgetDecision::ContinueAfterBudgetMessage;
54 }
55
56 ToolBudgetDecision::Exhausted {
57 segment_iterations: self.segment_iterations,
58 total_batches: self.total_batches,
59 continuations_used: self.continuations_used,
60 }
61 }
62
63 pub const fn total_batches(&self) -> usize {
65 self.total_batches
66 }
67
68 pub const fn continuations_used(&self) -> usize {
70 self.continuations_used
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn budget_allows_bounded_continuations_before_exhausting() {
80 let mut budget = ToolIterationBudget::new(2, 1);
81 assert_eq!(budget.before_provider_request(), ToolBudgetDecision::Continue);
82 budget.record_tool_batch();
83 budget.record_tool_batch();
84 assert_eq!(
85 budget.before_provider_request(),
86 ToolBudgetDecision::ContinueAfterBudgetMessage
87 );
88 budget.record_tool_batch();
89 budget.record_tool_batch();
90 assert_eq!(
91 budget.before_provider_request(),
92 ToolBudgetDecision::Exhausted { segment_iterations: 2, total_batches: 4, continuations_used: 1 }
93 );
94 }
95}