rskit_agent/types/
limit.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5#[non_exhaustive]
6pub enum AgentLimitError {
7 Cancelled,
9 WallClockExceeded,
11 MaxToolCallsExceeded,
13 MaxTokensExceeded,
15 MaxTurnsExceeded,
17}
18
19impl AgentLimitError {
20 pub const fn precedence(self) -> u8 {
22 match self {
23 Self::Cancelled => 5,
24 Self::WallClockExceeded => 4,
25 Self::MaxToolCallsExceeded => 3,
26 Self::MaxTokensExceeded => 2,
27 Self::MaxTurnsExceeded => 1,
28 }
29 }
30}
31
32#[cfg(test)]
33mod tests {
34 use super::*;
35
36 #[test]
37 fn cancellation_outranks_every_budget_failure() {
38 let budgets = [
39 AgentLimitError::WallClockExceeded,
40 AgentLimitError::MaxToolCallsExceeded,
41 AgentLimitError::MaxTokensExceeded,
42 AgentLimitError::MaxTurnsExceeded,
43 ];
44 for budget in budgets {
45 assert!(
46 AgentLimitError::Cancelled.precedence() > budget.precedence(),
47 "cancellation must win over {budget:?}",
48 );
49 }
50 }
51
52 #[test]
53 fn precedence_is_strict_total_order_with_locked_ranking() {
54 let ranked = [
56 AgentLimitError::Cancelled,
57 AgentLimitError::WallClockExceeded,
58 AgentLimitError::MaxToolCallsExceeded,
59 AgentLimitError::MaxTokensExceeded,
60 AgentLimitError::MaxTurnsExceeded,
61 ];
62 for pair in ranked.windows(2) {
63 assert!(
64 pair[0].precedence() > pair[1].precedence(),
65 "{:?} must outrank {:?}",
66 pair[0],
67 pair[1],
68 );
69 }
70
71 let mut shuffled = ranked;
73 shuffled.reverse();
74 let winner = shuffled
75 .into_iter()
76 .max_by_key(|error| error.precedence())
77 .expect("non-empty");
78 assert_eq!(winner, AgentLimitError::Cancelled);
79 }
80}