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!(
108 self,
109 Self::Llm(_)
110 | Self::LlmApi { .. }
111 | Self::LlmStream(_)
112 | Self::ServiceUnavailable(_)
113 | Self::RateLimitExceeded
114 )
115 }
116
117 pub fn is_rate_limited(&self) -> bool {
118 matches!(self, Self::RateLimitExceeded)
119 }
120
121 pub fn is_resource_unavailable(&self) -> bool {
122 matches!(self, Self::ResourceUnavailable(_))
123 }
124
125 pub fn kind(&self) -> ErrorKind {
127 match self {
128 Self::ToolExecution { name, .. } => ErrorKind::ToolCallFailed {
129 tool_name: name.clone(),
130 },
131 Self::ToolNotFound { .. } => ErrorKind::ToolNotFound,
132 Self::ToolArgsInvalid { .. } => ErrorKind::ToolArgsInvalid,
133 Self::ToolTimeout => ErrorKind::ToolTimeout,
134 Self::ServiceUnavailable(_) => ErrorKind::ModelOverloaded,
135 Self::RateLimitExceeded => ErrorKind::RateLimited,
136 Self::Llm(_) | Self::LlmApi { .. } | Self::LlmStream(_) => ErrorKind::ModelOverloaded,
138 _ => ErrorKind::Internal,
140 }
141 }
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
150pub enum ErrorKind {
151 ToolCallFailed { tool_name: String },
153 ToolNotFound,
155 ToolArgsInvalid,
157 ToolTimeout,
159 ModelOverloaded,
161 RateLimited,
163 Internal,
165}
166
167impl std::fmt::Display for ErrorKind {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 match self {
170 Self::ToolCallFailed { tool_name } => write!(f, "tool call failed: {tool_name}"),
171 Self::ToolNotFound => write!(f, "tool not found"),
172 Self::ToolArgsInvalid => write!(f, "tool args invalid"),
173 Self::ToolTimeout => write!(f, "tool timeout"),
174 Self::ModelOverloaded => write!(f, "model overloaded"),
175 Self::RateLimited => write!(f, "rate limited"),
176 Self::Internal => write!(f, "internal error"),
177 }
178 }
179}
180
181pub type AgentResult<T> = Result<T, AgentError>;
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn kind_tool_execution() {
189 let err = AgentError::ToolExecution {
190 name: "my_tool".to_string(),
191 source: Box::new(AgentError::internal("boom")),
192 };
193 assert_eq!(
194 err.kind(),
195 ErrorKind::ToolCallFailed {
196 tool_name: "my_tool".to_string()
197 }
198 );
199 }
200
201 #[test]
202 fn kind_tool_not_found() {
203 let err = AgentError::tool_not_found("missing");
204 assert_eq!(err.kind(), ErrorKind::ToolNotFound);
205 }
206
207 #[test]
208 fn kind_tool_args_invalid() {
209 let err = AgentError::ToolArgsInvalid {
210 name: "t".to_string(),
211 raw: "bad".to_string(),
212 };
213 assert_eq!(err.kind(), ErrorKind::ToolArgsInvalid);
214 }
215
216 #[test]
217 fn kind_tool_timeout() {
218 let err = AgentError::tool_timeout();
219 assert_eq!(err.kind(), ErrorKind::ToolTimeout);
220 }
221
222 #[test]
223 fn kind_service_unavailable() {
224 let err = AgentError::service_unavailable("overloaded");
225 assert_eq!(err.kind(), ErrorKind::ModelOverloaded);
226 }
227
228 #[test]
229 fn kind_rate_limit() {
230 let err = AgentError::rate_limit_exceeded();
231 assert_eq!(err.kind(), ErrorKind::RateLimited);
232 }
233
234 #[test]
235 fn kind_llm_maps_to_overloaded() {
236 let err = AgentError::llm("connection refused");
237 assert_eq!(err.kind(), ErrorKind::ModelOverloaded);
238 }
239
240 #[test]
241 fn kind_llm_api_maps_to_overloaded() {
242 let err = AgentError::LlmApi {
243 message: "529".to_string(),
244 };
245 assert_eq!(err.kind(), ErrorKind::ModelOverloaded);
246 }
247
248 #[test]
249 fn kind_llm_stream_maps_to_overloaded() {
250 let err = AgentError::LlmStream("stream broken".to_string());
251 assert_eq!(err.kind(), ErrorKind::ModelOverloaded);
252 }
253
254 #[test]
255 fn kind_internal_fallback() {
256 let err = AgentError::internal("something");
257 assert_eq!(err.kind(), ErrorKind::Internal);
258
259 let err = AgentError::Cancelled;
260 assert_eq!(err.kind(), ErrorKind::Internal);
261
262 let err = AgentError::config_error("bad config");
263 assert_eq!(err.kind(), ErrorKind::Internal);
264 }
265
266 #[test]
267 fn error_kind_display() {
268 assert_eq!(
269 ErrorKind::ToolCallFailed {
270 tool_name: "t".to_string()
271 }
272 .to_string(),
273 "tool call failed: t"
274 );
275 assert_eq!(ErrorKind::ToolNotFound.to_string(), "tool not found");
276 assert_eq!(ErrorKind::ModelOverloaded.to_string(), "model overloaded");
277 assert_eq!(ErrorKind::RateLimited.to_string(), "rate limited");
278 }
279}