From 89c444ad3fb16d19b3f2d03e33b054f91c5a528f Mon Sep 17 00:00:00 2001 From: oudecheng <13802883547@139.com> Date: Thu, 30 Jul 2026 16:54:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(gateway):=20=E6=96=B0=E5=A2=9E=20/api/tool?= =?UTF-8?q?s=20=E4=B8=8E=20/api/subagents/update=20=E7=AB=AF=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /api/tools: 只读访问 ToolRegistry,返回 builtin + MCP 工具的 name/description/source - /api/subagents/update: PUT 接口,调用 SubagentRuntime::update_subagent 写回 SUBAGENT.md frontmatter - 路由表两处同步注册新端点 --- src/gateway/http.rs | 120 +++++++++++++++++++++++++++++++++++++++++++- src/gateway/mod.rs | 4 ++ 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/src/gateway/http.rs b/src/gateway/http.rs index 56a4215..c557a33 100644 --- a/src/gateway/http.rs +++ b/src/gateway/http.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use super::GatewayState; use crate::config::{Config, get_default_config_path}; +use crate::domain::CapabilityPolicy; use crate::experts::{Expert, ExpertScope, ExpertWithStatus}; use crate::skills::SkillWithStatus; use crate::tools::task::runtime::{SubagentScope, SubagentWithStatus}; @@ -248,6 +249,52 @@ pub async fn skills_list( }) } +#[derive(Serialize)] +pub struct ToolInfo { + pub name: String, + pub description: String, + /// "builtin" 或 "mcp:{server_key}" + pub source: String, +} + +#[derive(Serialize)] +pub struct ToolsListResponse { + pub total: usize, + pub tools: Vec, +} + +/// GET /api/tools — Return all registered tools (builtin + MCP) with name/description/source. +/// 通过 SessionManager::tools() 只读访问 ToolRegistry,不修改状态。 +pub async fn tools_list( + State(state): State>, +) -> Json { + let registry = state.session_manager.tools(); + let tools: Vec = registry + .get_definitions() + .into_iter() + .map(|t| { + let source = if t.function.name.starts_with("mcp_") { + // mcp_{server}_{tool} → mcp:{server} + let parts: Vec<&str> = t.function.name.splitn(3, '_').collect(); + if parts.len() == 3 { + format!("mcp:{}", parts[1]) + } else { + "mcp".to_string() + } + } else { + "builtin".to_string() + }; + ToolInfo { + name: t.function.name, + description: t.function.description, + source, + } + }) + .collect(); + let total = tools.len(); + Json(ToolsListResponse { total, tools }) +} + /// POST /api/skills/toggle — Enable or disable a specific skill pub async fn skills_toggle( State(state): State>, @@ -423,6 +470,69 @@ pub async fn subagents_toggle( } } +#[derive(Deserialize)] +pub struct SubagentUpdateRequest { + pub name: String, + pub description: Option, + pub body: Option, + #[serde(default)] + pub capability: Option, +} + +#[derive(Serialize)] +pub struct SubagentUpdateResponse { + pub success: bool, + pub subagent: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +/// PUT /api/subagents/update — Update subagent capability/description/body (writes back SUBAGENT.md) +pub async fn subagents_update( + State(state): State>, + Json(req): Json, +) -> Result, (StatusCode, String)> { + let updated = state + .subagent_runtime + .update_subagent( + &req.name, + req.description.as_deref(), + req.body.as_deref(), + req.capability.as_ref(), + true, + ) + .map_err(|err| { + let status = if err.contains("not found") { + StatusCode::NOT_FOUND + } else if err.contains("builtin") { + StatusCode::BAD_REQUEST + } else { + StatusCode::INTERNAL_SERVER_ERROR + }; + (status, err) + })?; + + // 返回更新后的状态(含 disabled_in_scopes) + let status = state + .subagent_runtime + .list_with_status() + .into_iter() + .find(|s| s.name == updated.name) + .unwrap_or_else(|| SubagentWithStatus { + name: updated.name.clone(), + description: updated.description.clone(), + source: updated.source.as_str().to_string(), + disabled_in_scopes: vec![], + capability: updated.capability.clone(), + }); + + Ok(Json(SubagentUpdateResponse { + success: true, + subagent: Some(status), + error: None, + })) +} + // ===================== Experts ===================== #[derive(Deserialize)] @@ -458,6 +568,8 @@ pub struct ExpertCreateRequest { pub description: String, pub body: String, pub scope: String, + #[serde(default)] + pub capability: CapabilityPolicy, } #[derive(Deserialize)] @@ -466,6 +578,8 @@ pub struct ExpertUpdateRequest { pub scope: String, pub description: Option, pub body: Option, + #[serde(default)] + pub capability: Option, } #[derive(Deserialize)] @@ -505,6 +619,8 @@ pub struct ExpertResponse { pub body: String, pub source: String, pub path: String, + #[serde(default)] + pub capability: CapabilityPolicy, } impl From for ExpertResponse { @@ -515,6 +631,7 @@ impl From for ExpertResponse { body: expert.body, source: expert.source.as_str().to_string(), path: expert.path.display().to_string(), + capability: expert.capability, } } } @@ -625,7 +742,7 @@ pub async fn experts_create( let expert = state .experts - .create_expert(scope, &req.name, &req.description, &req.body, true) + .create_expert(scope, &req.name, &req.description, &req.body, &req.capability, true) .map_err(|err| { let status = if err.contains("already exists") { StatusCode::CONFLICT @@ -653,6 +770,7 @@ pub async fn experts_update( &req.name, req.description.as_deref(), req.body.as_deref(), + req.capability.as_ref(), true, ) .map_err(|err| { diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 4512253..bd9c6fe 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -241,8 +241,10 @@ pub async fn run( .route("/api/mcp/status", routing::get(http::mcp_status)) .route("/api/skills", routing::get(http::skills_list)) .route("/api/skills/toggle", routing::post(http::skills_toggle)) + .route("/api/tools", routing::get(http::tools_list)) .route("/api/subagents", routing::get(http::subagents_list)) .route("/api/subagents/toggle", routing::post(http::subagents_toggle)) + .route("/api/subagents/update", routing::put(http::subagents_update)) .route("/api/experts", routing::get(http::experts_list)) .route("/api/experts/toggle", routing::post(http::experts_toggle)) .route("/api/experts/create", routing::post(http::experts_create)) @@ -262,8 +264,10 @@ pub async fn run( .route("/api/mcp/status", routing::get(http::mcp_status)) .route("/api/skills", routing::get(http::skills_list)) .route("/api/skills/toggle", routing::post(http::skills_toggle)) + .route("/api/tools", routing::get(http::tools_list)) .route("/api/subagents", routing::get(http::subagents_list)) .route("/api/subagents/toggle", routing::post(http::subagents_toggle)) + .route("/api/subagents/update", routing::put(http::subagents_update)) .route("/api/experts", routing::get(http::experts_list)) .route("/api/experts/toggle", routing::post(http::experts_toggle)) .route("/api/experts/create", routing::post(http::experts_create))