1use serde::{Deserialize, Serialize};
7use std::time::Duration;
8use thiserror::Error;
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
12pub struct ValidationError {
13 pub field: Option<String>,
15 pub message: String,
17}
18
19impl ValidationError {
20 #[must_use]
22 pub fn new(field: Option<String>, message: impl Into<String>) -> Self {
23 Self {
24 field,
25 message: message.into(),
26 }
27 }
28}
29
30#[derive(Debug, Error)]
32pub enum ToolError {
33 #[error("Tool execution failed: {message}")]
35 ExecutionFailed {
36 message: String,
38 retryable: bool,
40 },
41
42 #[error("Tool not found: {0}")]
44 NotFound(String),
45
46 #[error("Model retry requested: {0}")]
48 ModelRetry(String),
49
50 #[error("Approval required for tool '{tool_name}'")]
52 ApprovalRequired {
53 tool_name: String,
55 args: serde_json::Value,
57 },
58
59 #[error("Tool call '{tool_name}' deferred")]
61 CallDeferred {
62 tool_name: String,
64 args: serde_json::Value,
66 },
67
68 #[error("Tool execution timed out after {0:?}")]
70 Timeout(Duration),
71
72 #[error("Tool argument validation failed for '{tool_name}'")]
74 ValidationFailed {
75 tool_name: String,
77 errors: Vec<ValidationError>,
79 },
80
81 #[error("Tool execution cancelled")]
83 Cancelled,
84
85 #[error("Tool returned error: {0}")]
87 ToolReturnedError(String),
88
89 #[error("JSON error: {0}")]
91 Json(#[from] serde_json::Error),
92
93 #[error(transparent)]
95 Other(#[from] anyhow::Error),
96}
97
98impl ToolError {
99 #[must_use]
101 pub fn is_retryable(&self) -> bool {
102 match self {
103 Self::ExecutionFailed { retryable, .. } => *retryable,
104 Self::ModelRetry(_) => true,
105 Self::Timeout(_) => true,
106 Self::ValidationFailed { .. } => false,
107 Self::NotFound(_) => false,
108 Self::ApprovalRequired { .. } => false,
109 Self::CallDeferred { .. } => false,
110 Self::Cancelled => false,
111 Self::ToolReturnedError(_) => false,
112 Self::Json(_) => false,
113 Self::Other(_) => false,
114 }
115 }
116
117 #[must_use]
119 pub fn execution_failed(msg: impl Into<String>) -> Self {
120 Self::ExecutionFailed {
121 message: msg.into(),
122 retryable: false,
123 }
124 }
125
126 #[must_use]
128 pub fn retryable(msg: impl Into<String>) -> Self {
129 Self::ExecutionFailed {
130 message: msg.into(),
131 retryable: true,
132 }
133 }
134
135 #[must_use]
137 pub fn validation_failed(tool_name: impl Into<String>, errors: Vec<ValidationError>) -> Self {
138 Self::ValidationFailed {
139 tool_name: tool_name.into(),
140 errors,
141 }
142 }
143
144 #[must_use]
146 pub fn validation_error(
147 tool_name: impl Into<String>,
148 field: Option<String>,
149 message: impl Into<String>,
150 ) -> Self {
151 Self::validation_failed(tool_name, vec![ValidationError::new(field, message)])
152 }
153
154 #[must_use]
156 pub fn invalid_arguments(tool_name: impl Into<String>, message: impl Into<String>) -> Self {
157 Self::validation_failed(tool_name, vec![ValidationError::new(None, message)])
158 }
159
160 #[must_use]
162 pub fn not_found(name: impl Into<String>) -> Self {
163 Self::NotFound(name.into())
164 }
165
166 #[must_use]
168 pub fn model_retry(msg: impl Into<String>) -> Self {
169 Self::ModelRetry(msg.into())
170 }
171
172 #[must_use]
174 pub fn approval_required(tool_name: impl Into<String>, args: serde_json::Value) -> Self {
175 Self::ApprovalRequired {
176 tool_name: tool_name.into(),
177 args,
178 }
179 }
180
181 #[must_use]
183 pub fn call_deferred(tool_name: impl Into<String>, args: serde_json::Value) -> Self {
184 Self::CallDeferred {
185 tool_name: tool_name.into(),
186 args,
187 }
188 }
189
190 #[must_use]
192 pub fn timeout(duration: Duration) -> Self {
193 Self::Timeout(duration)
194 }
195
196 #[must_use]
198 pub fn message(&self) -> String {
199 self.to_string()
200 }
201
202 #[must_use]
204 pub fn is_approval_required(&self) -> bool {
205 matches!(self, Self::ApprovalRequired { .. })
206 }
207
208 #[must_use]
210 pub fn is_call_deferred(&self) -> bool {
211 matches!(self, Self::CallDeferred { .. })
212 }
213
214 #[must_use]
216 pub fn is_model_retry(&self) -> bool {
217 matches!(self, Self::ModelRetry(_))
218 }
219}
220
221impl From<String> for ToolError {
222 fn from(s: String) -> Self {
223 Self::execution_failed(s)
224 }
225}
226
227impl From<&str> for ToolError {
228 fn from(s: &str) -> Self {
229 Self::execution_failed(s)
230 }
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct ToolErrorInfo {
236 pub error_type: String,
238 pub message: String,
240 pub retryable: bool,
242 #[serde(skip_serializing_if = "Option::is_none")]
244 pub details: Option<serde_json::Value>,
245}
246
247impl ToolErrorInfo {
248 #[must_use]
250 pub fn new(error_type: impl Into<String>, message: impl Into<String>) -> Self {
251 Self {
252 error_type: error_type.into(),
253 message: message.into(),
254 retryable: false,
255 details: None,
256 }
257 }
258
259 #[must_use]
261 pub fn retryable(mut self, retryable: bool) -> Self {
262 self.retryable = retryable;
263 self
264 }
265
266 #[must_use]
268 pub fn with_details(mut self, details: serde_json::Value) -> Self {
269 self.details = Some(details);
270 self
271 }
272}
273
274impl From<&ToolError> for ToolErrorInfo {
275 fn from(err: &ToolError) -> Self {
276 let error_type = match err {
277 ToolError::ExecutionFailed { .. } => "execution_failed",
278 ToolError::NotFound(_) => "not_found",
279 ToolError::ModelRetry(_) => "model_retry",
280 ToolError::ApprovalRequired { .. } => "approval_required",
281 ToolError::CallDeferred { .. } => "call_deferred",
282 ToolError::Timeout(_) => "timeout",
283 ToolError::ValidationFailed { .. } => "validation_failed",
284 ToolError::Cancelled => "cancelled",
285 ToolError::ToolReturnedError(_) => "tool_error",
286 ToolError::Json(_) => "json_error",
287 ToolError::Other(_) => "other",
288 };
289
290 let details = match err {
291 ToolError::ValidationFailed { errors, .. } => serde_json::to_value(errors).ok(),
292 _ => None,
293 };
294
295 Self {
296 error_type: error_type.to_string(),
297 message: err.message(),
298 retryable: err.is_retryable(),
299 details,
300 }
301 }
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307
308 #[test]
309 fn test_execution_failed() {
310 let err = ToolError::execution_failed("Something went wrong");
311 assert!(!err.is_retryable());
312 assert!(err.message().contains("Something went wrong"));
313 }
314
315 #[test]
316 fn test_retryable_error() {
317 let err = ToolError::retryable("Temporary failure");
318 assert!(err.is_retryable());
319 }
320
321 #[test]
322 fn test_not_found() {
323 let err = ToolError::not_found("unknown_tool");
324 assert!(!err.is_retryable());
325 assert!(err.message().contains("unknown_tool"));
326 }
327
328 #[test]
329 fn test_approval_required() {
330 let err = ToolError::approval_required("dangerous_tool", serde_json::json!({"x": 1}));
331 assert!(err.is_approval_required());
332 assert!(!err.is_retryable());
333 }
334
335 #[test]
336 fn test_call_deferred() {
337 let err = ToolError::call_deferred("slow_tool", serde_json::json!({"a": "b"}));
338 assert!(err.is_call_deferred());
339 }
340
341 #[test]
342 fn test_timeout() {
343 let err = ToolError::timeout(Duration::from_secs(30));
344 assert!(err.is_retryable());
345 assert!(err.message().contains("30"));
346 }
347
348 #[test]
349 fn test_model_retry() {
350 let err = ToolError::model_retry("Invalid format");
351 assert!(err.is_model_retry());
352 assert!(err.is_retryable());
353 }
354
355 #[test]
356 fn test_validation_failed() {
357 let err =
358 ToolError::validation_error("test_tool", Some("field".to_string()), "Invalid value");
359 assert!(!err.is_retryable());
360 let info = ToolErrorInfo::from(&err);
361 assert_eq!(info.error_type, "validation_failed");
362 assert!(info.details.is_some());
363 }
364
365 #[test]
366 fn test_error_info_from_error() {
367 let err = ToolError::execution_failed("Test error");
368 let info = ToolErrorInfo::from(&err);
369 assert_eq!(info.error_type, "execution_failed");
370 assert!(!info.retryable);
371 }
372
373 #[test]
374 fn test_from_string() {
375 let err: ToolError = "error message".into();
376 assert!(matches!(err, ToolError::ExecutionFailed { .. }));
377 }
378}