1use thiserror::Error;
16
17#[derive(Debug, Error)]
18#[non_exhaustive]
19pub enum ToolCallError {
20 #[error("invalid arguments: {0}")]
23 InvalidArgs(String),
24
25 #[error("execution failed ({code}): {message}")]
28 ExecutionFailed {
29 code: String,
30 message: String,
31 retryable: bool,
32 },
33
34 #[error("internal error: {0}")]
36 InternalError(String),
37
38 #[error("rate limited ({tool_id}): max_concurrent={limit} in-flight")]
42 RateLimited {
43 tool_id: String,
44 limit: u32,
45 retry_after_ms: Option<u64>,
46 },
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52
53 #[test]
54 fn invalid_args_display_format() {
55 let e = ToolCallError::InvalidArgs("missing field `path`".into());
56 assert_eq!(format!("{e}"), "invalid arguments: missing field `path`");
57 }
58
59 #[test]
60 fn execution_failed_display_includes_code_and_message() {
61 let e = ToolCallError::ExecutionFailed {
62 code: "EPERM".into(),
63 message: "denied".into(),
64 retryable: false,
65 };
66 let s = format!("{e}");
67 assert!(s.contains("EPERM"));
68 assert!(s.contains("denied"));
69 }
70
71 #[test]
72 fn internal_error_display_format() {
73 let e = ToolCallError::InternalError("logic bug".into());
74 assert_eq!(format!("{e}"), "internal error: logic bug");
75 }
76
77 #[test]
78 fn enum_is_non_exhaustive_at_api_boundary() {
79 let e = ToolCallError::InvalidArgs("x".into());
80 match e {
81 ToolCallError::InvalidArgs(_) => {}
82 ToolCallError::ExecutionFailed { .. } => {}
83 ToolCallError::InternalError(_) => {}
84 ToolCallError::RateLimited { .. } => {}
85 }
86 }
87}