agent_base/types/
error.rs1use thiserror::Error;
2
3#[derive(Debug, Error)]
4pub enum AgentError {
5 #[error("LLM call failed: {0}")]
6 Llm(String),
7
8 #[error("LLM API error: {message}")]
9 LlmApi { message: String },
10
11 #[error("LLM rate limit exceeded")]
12 RateLimitExceeded,
13
14 #[error("LLM service unavailable: {0}")]
15 ServiceUnavailable(String),
16
17 #[error("SSE stream error: {0}")]
18 LlmStream(String),
19
20 #[error("JSON parse error: {0}")]
21 Json(String),
22
23 #[error("Tool '{name}' not registered")]
24 ToolNotFound { name: String },
25
26 #[error("Tool '{name}' argument parsing failed: {raw}")]
27 ToolArgsInvalid { name: String, raw: String },
28
29 #[error("Tool '{name}' execution failed: {source}")]
30 ToolExecution {
31 name: String,
32 #[source]
33 source: Box<AgentError>,
34 },
35
36 #[error("Tool timeout exceeded")]
37 ToolTimeout,
38
39 #[error("Tool call rejected by approval: {tool_name}")]
40 ApprovalDenied { tool_name: String },
41
42 #[error("Session {0} not found")]
43 SessionNotFound(u64),
44
45 #[error("Max turns ({limit}) reached, stopping forcibly")]
46 MaxTurnsExceeded { limit: u32 },
47
48 #[error("Operation cancelled")]
49 Cancelled,
50
51 #[error("Resource unavailable: {0}")]
52 ResourceUnavailable(String),
53
54 #[error("Configuration error: {0}")]
55 ConfigError(String),
56
57 #[error("Internal error: {0}")]
58 Internal(String),
59}
60
61impl AgentError {
62 pub fn llm(message: impl Into<String>) -> Self {
63 Self::Llm(message.into())
64 }
65
66 pub fn json(message: impl Into<String>) -> Self {
67 Self::Json(message.into())
68 }
69
70 pub fn internal(message: impl Into<String>) -> Self {
71 Self::Internal(message.into())
72 }
73
74 pub fn tool_not_found(name: impl Into<String>) -> Self {
75 Self::ToolNotFound { name: name.into() }
76 }
77
78 pub fn session_not_found(id: u64) -> Self {
79 Self::SessionNotFound(id)
80 }
81
82 pub fn tool_timeout() -> Self {
83 Self::ToolTimeout
84 }
85
86 pub fn rate_limit_exceeded() -> Self {
87 Self::RateLimitExceeded
88 }
89
90 pub fn service_unavailable(message: impl Into<String>) -> Self {
91 Self::ServiceUnavailable(message.into())
92 }
93
94 pub fn resource_unavailable(message: impl Into<String>) -> Self {
95 Self::ResourceUnavailable(message.into())
96 }
97
98 pub fn config_error(message: impl Into<String>) -> Self {
99 Self::ConfigError(message.into())
100 }
101
102 pub fn is_cancelled(&self) -> bool {
103 matches!(self, Self::Cancelled)
104 }
105
106 pub fn is_retryable(&self) -> bool {
107 matches!(self, Self::Llm(_) | Self::LlmApi { .. } | Self::LlmStream(_) | Self::ServiceUnavailable(_) | Self::RateLimitExceeded)
108 }
109
110 pub fn is_rate_limited(&self) -> bool {
111 matches!(self, Self::RateLimitExceeded)
112 }
113
114 pub fn is_resource_unavailable(&self) -> bool {
115 matches!(self, Self::ResourceUnavailable(_))
116 }
117
118 pub fn kind(&self) -> ErrorKind {
120 match self {
121 Self::ToolExecution { name, .. } => ErrorKind::ToolCallFailed {
122 tool_name: name.clone(),
123 },
124 Self::ToolNotFound { .. } => ErrorKind::ToolNotFound,
125 Self::ToolArgsInvalid { .. } => ErrorKind::ToolArgsInvalid,
126 Self::ToolTimeout => ErrorKind::ToolTimeout,
127 Self::ServiceUnavailable(_) => ErrorKind::ModelOverloaded,
128 Self::RateLimitExceeded => ErrorKind::RateLimited,
129 Self::Llm(_) | Self::LlmApi { .. } | Self::LlmStream(_) => ErrorKind::ModelOverloaded,
131 _ => ErrorKind::Internal,
133 }
134 }
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
143pub enum ErrorKind {
144 ToolCallFailed { tool_name: String },
146 ToolNotFound,
148 ToolArgsInvalid,
150 ToolTimeout,
152 ModelOverloaded,
154 RateLimited,
156 Internal,
158}
159
160impl std::fmt::Display for ErrorKind {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 match self {
163 Self::ToolCallFailed { tool_name } => write!(f, "tool call failed: {tool_name}"),
164 Self::ToolNotFound => write!(f, "tool not found"),
165 Self::ToolArgsInvalid => write!(f, "tool args invalid"),
166 Self::ToolTimeout => write!(f, "tool timeout"),
167 Self::ModelOverloaded => write!(f, "model overloaded"),
168 Self::RateLimited => write!(f, "rate limited"),
169 Self::Internal => write!(f, "internal error"),
170 }
171 }
172}
173
174pub type AgentResult<T> = Result<T, AgentError>;
175
176#[cfg(test)]
177mod tests {
178 use super::*;
179
180 #[test]
181 fn kind_tool_execution() {
182 let err = AgentError::ToolExecution {
183 name: "my_tool".to_string(),
184 source: Box::new(AgentError::internal("boom")),
185 };
186 assert_eq!(
187 err.kind(),
188 ErrorKind::ToolCallFailed {
189 tool_name: "my_tool".to_string()
190 }
191 );
192 }
193
194 #[test]
195 fn kind_tool_not_found() {
196 let err = AgentError::tool_not_found("missing");
197 assert_eq!(err.kind(), ErrorKind::ToolNotFound);
198 }
199
200 #[test]
201 fn kind_tool_args_invalid() {
202 let err = AgentError::ToolArgsInvalid {
203 name: "t".to_string(),
204 raw: "bad".to_string(),
205 };
206 assert_eq!(err.kind(), ErrorKind::ToolArgsInvalid);
207 }
208
209 #[test]
210 fn kind_tool_timeout() {
211 let err = AgentError::tool_timeout();
212 assert_eq!(err.kind(), ErrorKind::ToolTimeout);
213 }
214
215 #[test]
216 fn kind_service_unavailable() {
217 let err = AgentError::service_unavailable("overloaded");
218 assert_eq!(err.kind(), ErrorKind::ModelOverloaded);
219 }
220
221 #[test]
222 fn kind_rate_limit() {
223 let err = AgentError::rate_limit_exceeded();
224 assert_eq!(err.kind(), ErrorKind::RateLimited);
225 }
226
227 #[test]
228 fn kind_llm_maps_to_overloaded() {
229 let err = AgentError::llm("connection refused");
230 assert_eq!(err.kind(), ErrorKind::ModelOverloaded);
231 }
232
233 #[test]
234 fn kind_llm_api_maps_to_overloaded() {
235 let err = AgentError::LlmApi {
236 message: "529".to_string(),
237 };
238 assert_eq!(err.kind(), ErrorKind::ModelOverloaded);
239 }
240
241 #[test]
242 fn kind_llm_stream_maps_to_overloaded() {
243 let err = AgentError::LlmStream("stream broken".to_string());
244 assert_eq!(err.kind(), ErrorKind::ModelOverloaded);
245 }
246
247 #[test]
248 fn kind_internal_fallback() {
249 let err = AgentError::internal("something");
250 assert_eq!(err.kind(), ErrorKind::Internal);
251
252 let err = AgentError::Cancelled;
253 assert_eq!(err.kind(), ErrorKind::Internal);
254
255 let err = AgentError::config_error("bad config");
256 assert_eq!(err.kind(), ErrorKind::Internal);
257 }
258
259 #[test]
260 fn error_kind_display() {
261 assert_eq!(
262 ErrorKind::ToolCallFailed {
263 tool_name: "t".to_string()
264 }
265 .to_string(),
266 "tool call failed: t"
267 );
268 assert_eq!(ErrorKind::ToolNotFound.to_string(), "tool not found");
269 assert_eq!(ErrorKind::ModelOverloaded.to_string(), "model overloaded");
270 assert_eq!(ErrorKind::RateLimited.to_string(), "rate limited");
271 }
272}