//! 工具系统错误类型。 use std::sync::Arc; /// 工具调用过程中可能发生的所有错误。 #[derive(thiserror::Error, Debug, Clone)] #[non_exhaustive] pub enum ToolError { /// 工具未注册。不可恢复——需调用方先 `registry.register(...)`。 #[error( "工具 '{0}' 未注册。请先用 ToolRegistry::register(...) 注册该工具,或检查 LLM 输出的工具名拼写" )] NotFound(String), /// 工具执行失败(可恢复——文本回传 LLM 由其决定重试或放弃)。 #[error("工具 '{0}' 执行失败: {1}。可让 LLM 调整参数后重试,或切换到备选工具")] ExecutionFailed(String, String), /// 工具参数无效(可恢复——文本回传 LLM)。 #[error( "工具 '{0}' 参数无效: {1}。请检查 LLM 输出的参数是否符合 BaseTool::parameters() 声明的 JSON Schema" )] InvalidArguments(String, String), /// 权限被拒绝(不可恢复——终止循环)。 #[error( "权限被拒绝: 工具 '{0}' 需要 {1} 权限。请在 PermissionConfig 中显式允许,或人工确认后绕过" )] PermissionDenied(String, String), /// MCP 协议错误(不可恢复)。 #[error("MCP 协议错误: {0}。请检查 MCP server 版本与本库兼容,或查看 server 日志")] McpError(String), /// MCP 未初始化(不可恢复)。 #[error("MCP 未初始化: {0}。请先调用 McpClient::initialize() 完成握手")] McpNotInitialized(String), /// MCP 超时(不可恢复)。 #[error("MCP 超时: {0}。请检查 MCP server 是否存活,或调大超时配置")] McpTimeout(String), /// IO 错误(不可恢复)。 #[error("IO 错误: {0}。请检查文件路径、权限或磁盘空间")] Io(Arc), /// 工具执行被取消。 #[error("工具执行已取消: {0}")] Cancelled(String), /// 其他未分类错误。 #[error("工具调用错误: {0}")] Other(String), } impl From for ToolError { fn from(e: std::io::Error) -> Self { ToolError::Io(Arc::new(e)) } } impl ToolError { /// 判断错误是否可恢复——可恢复的错误回传 LLM 由其自行重试, /// 不可恢复的错误终止自动 tool 循环并返回给调用方。 pub fn is_recoverable(&self) -> bool { matches!( self, Self::ExecutionFailed(..) | Self::InvalidArguments(..) | Self::Other(_) ) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_execution_failed_is_recoverable() { let err = ToolError::ExecutionFailed("foo".into(), "boom".into()); assert!(err.is_recoverable()); } #[test] fn test_invalid_arguments_is_recoverable() { let err = ToolError::InvalidArguments("foo".into(), "missing x".into()); assert!(err.is_recoverable()); } #[test] fn test_not_found_is_not_recoverable() { let err = ToolError::NotFound("foo".into()); assert!(!err.is_recoverable()); } #[test] fn test_permission_denied_is_not_recoverable() { let err = ToolError::PermissionDenied("foo".into(), "Shell".into()); assert!(!err.is_recoverable()); } #[test] fn test_mcp_error_is_not_recoverable() { let err = ToolError::McpError("protocol".into()); assert!(!err.is_recoverable()); } #[test] fn test_mcp_timeout_is_not_recoverable() { let err = ToolError::McpTimeout("foo".into()); assert!(!err.is_recoverable()); } #[test] fn test_io_is_not_recoverable() { let io_err = std::io::Error::other("disk"); let err = ToolError::from(io_err); assert!(!err.is_recoverable()); } #[test] fn test_other_is_recoverable() { let err = ToolError::Other("something".into()); assert!(err.is_recoverable()); } }