style(tools, llm): 统一导入顺序与代码格式
This commit is contained in:
+9
-3
@@ -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
@@ -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
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(_, _))));
|
||||
|
||||
Reference in New Issue
Block a user