style(tools, llm): 统一导入顺序与代码格式

This commit is contained in:
徐涛
2026-07-05 08:19:13 +08:00
parent 98dfe6c1ed
commit 5648b1d217
39 changed files with 390 additions and 397 deletions
+9 -3
View File
@@ -6,7 +6,9 @@ use std::sync::Arc;
#[derive(thiserror::Error, Debug, Clone)]
pub enum ToolError {
/// 工具未注册。不可恢复——需调用方先 `registry.register(...)`。
#[error("工具 '{0}' 未注册。请先用 ToolRegistry::register(...) 注册该工具,或检查 LLM 输出的工具名拼写")]
#[error(
"工具 '{0}' 未注册。请先用 ToolRegistry::register(...) 注册该工具,或检查 LLM 输出的工具名拼写"
)]
NotFound(String),
/// 工具执行失败(可恢复——文本回传 LLM 由其决定重试或放弃)。
@@ -14,11 +16,15 @@ pub enum ToolError {
ExecutionFailed(String, String),
/// 工具参数无效(可恢复——文本回传 LLM)。
#[error("工具 '{0}' 参数无效: {1}。请检查 LLM 输出的参数是否符合 BaseTool::parameters() 声明的 JSON Schema")]
#[error(
"工具 '{0}' 参数无效: {1}。请检查 LLM 输出的参数是否符合 BaseTool::parameters() 声明的 JSON Schema"
)]
InvalidArguments(String, String),
/// 权限被拒绝(不可恢复——终止循环)。
#[error("权限被拒绝: 工具 '{0}' 需要 {1} 权限。请在 PermissionConfig 中显式允许,或人工确认后绕过")]
#[error(
"权限被拒绝: 工具 '{0}' 需要 {1} 权限。请在 PermissionConfig 中显式允许,或人工确认后绕过"
)]
PermissionDenied(String, String),
/// MCP 协议错误(不可恢复)。
+14 -30
View File
@@ -9,16 +9,16 @@
use std::collections::HashMap;
use std::process::Stdio;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use serde_json::{Value, json};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::process::{Child, ChildStdin, ChildStdout, Command};
use tokio::sync::{oneshot, Mutex};
use tokio::sync::{Mutex, oneshot};
#[allow(deprecated)]
use crate::llm::types::ToolDefinition;
@@ -226,9 +226,7 @@ impl McpClient {
"version": env!("CARGO_PKG_VERSION")
}
});
let _response = self
.send_request("initialize", Some(init_params))
.await?;
let _response = self.send_request("initialize", Some(init_params)).await?;
// 发送 initialized 通知(无 id
self.send_notification("notifications/initialized", Some(json!({})))
@@ -337,11 +335,7 @@ impl McpClient {
if let Some(state) = self.process.take() {
let mut state = state.lock().await;
// 优雅等待 5 秒
let graceful = tokio::time::timeout(
Duration::from_secs(5),
state.child.wait(),
)
.await;
let graceful = tokio::time::timeout(Duration::from_secs(5), state.child.wait()).await;
if graceful.is_err() {
// 超时则强杀
let _ = state.child.kill().await;
@@ -372,11 +366,7 @@ impl McpClient {
tools
}
async fn send_request(
&self,
method: &str,
params: Option<Value>,
) -> Result<Value, ToolError> {
async fn send_request(&self, method: &str, params: Option<Value>) -> Result<Value, ToolError> {
let state_arc = self
.process
.as_ref()
@@ -412,9 +402,11 @@ impl McpClient {
.write_all(b"\n")
.await
.map_err(|e| ToolError::McpError(format!("写入换行失败: {e}")))?;
state.stdin.flush().await.map_err(|e| {
ToolError::McpError(format!("flush stdin 失败: {e}"))
})?;
state
.stdin
.flush()
.await
.map_err(|e| ToolError::McpError(format!("flush stdin 失败: {e}")))?;
}
// 等待响应(带超时)
@@ -471,10 +463,7 @@ impl McpClient {
}
/// 持续读取 stdout,将响应分发到对应的 oneshot sender。
async fn read_loop(
mut reader: BufReader<ChildStdout>,
state: Arc<Mutex<ChildProcessState>>,
) {
async fn read_loop(mut reader: BufReader<ChildStdout>, state: Arc<Mutex<ChildProcessState>>) {
let mut line = String::new();
loop {
line.clear();
@@ -556,11 +545,7 @@ impl BaseTool for McpToolAdapter {
self.parameters.clone()
}
async fn execute(
&self,
_args: Value,
_ctx: &ToolContext<'_>,
) -> Result<Value, ToolError> {
async fn execute(&self, _args: Value, _ctx: &ToolContext<'_>) -> Result<Value, ToolError> {
// 当前 Phase 2 实现的简化:McpToolAdapter 不持有活跃 MCP 连接。
// 实际生产中应持有 Arc<McpClient> 并通过 mcp.call_tool() 执行。
// 这里返回错误,提示需要通过其他方式调用 MCP 工具。
@@ -617,8 +602,7 @@ mod tests {
#[test]
fn test_jsonrpc_response_parse_error() {
let s =
r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"Method not found"}}"#;
let s = r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"Method not found"}}"#;
let resp: JsonRpcResponse = serde_json::from_str(s).unwrap();
assert_eq!(resp.id, 1);
assert!(resp.result.is_none());
+21 -18
View File
@@ -148,9 +148,7 @@ mod tests {
#[test]
fn test_default_config_denies_delete() {
let checker = PermissionChecker::new(PermissionConfig::default());
assert!(checker
.check("rm_file", &p(Permission::Delete))
.is_err());
assert!(checker.check("rm_file", &p(Permission::Delete)).is_err());
}
#[test]
@@ -246,12 +244,16 @@ mod tests {
allow_unspecified: false,
};
let checker = PermissionChecker::new(cfg);
assert!(checker
.check("t", &[Permission::Custom("db:read".into())])
.is_ok());
assert!(checker
.check("t", &[Permission::Custom("db:write".into())])
.is_err());
assert!(
checker
.check("t", &[Permission::Custom("db:read".into())])
.is_ok()
);
assert!(
checker
.check("t", &[Permission::Custom("db:write".into())])
.is_err()
);
}
#[test]
@@ -262,12 +264,11 @@ mod tests {
allow_unspecified: false,
};
let checker = PermissionChecker::new(cfg);
assert!(checker
.check(
"t",
&[Permission::Read, Permission::Network]
)
.is_ok());
assert!(
checker
.check("t", &[Permission::Read, Permission::Network])
.is_ok()
);
}
#[test]
@@ -279,8 +280,10 @@ mod tests {
};
let checker = PermissionChecker::new(cfg);
// 任一权限不在白名单则拒绝
assert!(checker
.check("t", &[Permission::Read, Permission::Write])
.is_err());
assert!(
checker
.check("t", &[Permission::Read, Permission::Write])
.is_err()
);
}
}
+6 -3
View File
@@ -348,7 +348,10 @@ mod tests {
async fn test_invoke_success() {
let mut reg = ToolRegistry::new();
reg.register(Arc::new(AddTool { base: 100 })).unwrap();
let result = reg.invoke("call_1", "add", json!({ "n": 5 })).await.unwrap();
let result = reg
.invoke("call_1", "add", json!({ "n": 5 }))
.await
.unwrap();
let value = result.output.unwrap();
assert_eq!(value["result"], 105);
assert_eq!(result.tool_call_id, "call_1");
@@ -372,8 +375,8 @@ mod tests {
#[tokio::test]
async fn test_invoke_with_permission_denied() {
let mut reg = ToolRegistry::new()
.with_permission_checker(PermissionChecker::new(Default::default()));
let mut reg =
ToolRegistry::new().with_permission_checker(PermissionChecker::new(Default::default()));
reg.register(Arc::new(ShellTool)).unwrap();
let result = reg.invoke("call_z", "shell", json!({})).await;
assert!(matches!(result, Err(ToolError::PermissionDenied(_, _))));