feat(tools): 添加工具系统框架与 MCP 协议客户端

This commit is contained in:
徐涛
2026-06-07 10:57:15 +08:00
parent e598f6d3ee
commit b6e7acfb0f
9 changed files with 2034 additions and 1 deletions
+118
View File
@@ -0,0 +1,118 @@
//! 工具系统错误类型。
use std::sync::Arc;
/// 工具调用过程中可能发生的所有错误。
#[derive(thiserror::Error, Debug, Clone)]
pub enum ToolError {
/// 工具未注册。
#[error("工具 '{0}' 未注册")]
NotFound(String),
/// 工具执行失败(可恢复——文本回传 LLM)。
#[error("工具 '{0}' 执行失败: {1}")]
ExecutionFailed(String, String),
/// 工具参数无效(可恢复——文本回传 LLM)。
#[error("工具 '{0}' 参数无效: {1}")]
InvalidArguments(String, String),
/// 权限被拒绝(不可恢复——终止循环)。
#[error("权限被拒绝: 工具 '{0}' 需要 {1} 权限")]
PermissionDenied(String, String),
/// MCP 协议错误(不可恢复)。
#[error("MCP 协议错误: {0}")]
McpError(String),
/// MCP 未初始化(不可恢复)。
#[error("MCP 未初始化: {0}")]
McpNotInitialized(String),
/// MCP 超时(不可恢复)。
#[error("MCP 超时: {0}")]
McpTimeout(String),
/// IO 错误(不可恢复)。
#[error("IO 错误: {0}")]
Io(Arc<std::io::Error>),
/// 取消。
#[error("工具执行已取消: {0}")]
Cancelled(String),
/// 其他未分类错误。
#[error("其他错误: {0}")]
Other(String),
}
impl From<std::io::Error> 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::new(std::io::ErrorKind::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());
}
}