feat(gateway): 新增 /api/tools 与 /api/subagents/update 端点
- /api/tools: 只读访问 ToolRegistry,返回 builtin + MCP 工具的 name/description/source - /api/subagents/update: PUT 接口,调用 SubagentRuntime::update_subagent 写回 SUBAGENT.md frontmatter - 路由表两处同步注册新端点
This commit is contained in:
parent
12f1094426
commit
89c444ad3f
@ -5,6 +5,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use super::GatewayState;
|
use super::GatewayState;
|
||||||
use crate::config::{Config, get_default_config_path};
|
use crate::config::{Config, get_default_config_path};
|
||||||
|
use crate::domain::CapabilityPolicy;
|
||||||
use crate::experts::{Expert, ExpertScope, ExpertWithStatus};
|
use crate::experts::{Expert, ExpertScope, ExpertWithStatus};
|
||||||
use crate::skills::SkillWithStatus;
|
use crate::skills::SkillWithStatus;
|
||||||
use crate::tools::task::runtime::{SubagentScope, SubagentWithStatus};
|
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<ToolInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// GET /api/tools — Return all registered tools (builtin + MCP) with name/description/source.
|
||||||
|
/// 通过 SessionManager::tools() 只读访问 ToolRegistry,不修改状态。
|
||||||
|
pub async fn tools_list(
|
||||||
|
State(state): State<Arc<GatewayState>>,
|
||||||
|
) -> Json<ToolsListResponse> {
|
||||||
|
let registry = state.session_manager.tools();
|
||||||
|
let tools: Vec<ToolInfo> = 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
|
/// POST /api/skills/toggle — Enable or disable a specific skill
|
||||||
pub async fn skills_toggle(
|
pub async fn skills_toggle(
|
||||||
State(state): State<Arc<GatewayState>>,
|
State(state): State<Arc<GatewayState>>,
|
||||||
@ -423,6 +470,69 @@ pub async fn subagents_toggle(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct SubagentUpdateRequest {
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub body: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub capability: Option<CapabilityPolicy>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct SubagentUpdateResponse {
|
||||||
|
pub success: bool,
|
||||||
|
pub subagent: Option<SubagentWithStatus>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PUT /api/subagents/update — Update subagent capability/description/body (writes back SUBAGENT.md)
|
||||||
|
pub async fn subagents_update(
|
||||||
|
State(state): State<Arc<GatewayState>>,
|
||||||
|
Json(req): Json<SubagentUpdateRequest>,
|
||||||
|
) -> Result<Json<SubagentUpdateResponse>, (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 =====================
|
// ===================== Experts =====================
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -458,6 +568,8 @@ pub struct ExpertCreateRequest {
|
|||||||
pub description: String,
|
pub description: String,
|
||||||
pub body: String,
|
pub body: String,
|
||||||
pub scope: String,
|
pub scope: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub capability: CapabilityPolicy,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -466,6 +578,8 @@ pub struct ExpertUpdateRequest {
|
|||||||
pub scope: String,
|
pub scope: String,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub body: Option<String>,
|
pub body: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub capability: Option<CapabilityPolicy>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -505,6 +619,8 @@ pub struct ExpertResponse {
|
|||||||
pub body: String,
|
pub body: String,
|
||||||
pub source: String,
|
pub source: String,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub capability: CapabilityPolicy,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Expert> for ExpertResponse {
|
impl From<Expert> for ExpertResponse {
|
||||||
@ -515,6 +631,7 @@ impl From<Expert> for ExpertResponse {
|
|||||||
body: expert.body,
|
body: expert.body,
|
||||||
source: expert.source.as_str().to_string(),
|
source: expert.source.as_str().to_string(),
|
||||||
path: expert.path.display().to_string(),
|
path: expert.path.display().to_string(),
|
||||||
|
capability: expert.capability,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -625,7 +742,7 @@ pub async fn experts_create(
|
|||||||
|
|
||||||
let expert = state
|
let expert = state
|
||||||
.experts
|
.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| {
|
.map_err(|err| {
|
||||||
let status = if err.contains("already exists") {
|
let status = if err.contains("already exists") {
|
||||||
StatusCode::CONFLICT
|
StatusCode::CONFLICT
|
||||||
@ -653,6 +770,7 @@ pub async fn experts_update(
|
|||||||
&req.name,
|
&req.name,
|
||||||
req.description.as_deref(),
|
req.description.as_deref(),
|
||||||
req.body.as_deref(),
|
req.body.as_deref(),
|
||||||
|
req.capability.as_ref(),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
|
|||||||
@ -241,8 +241,10 @@ pub async fn run(
|
|||||||
.route("/api/mcp/status", routing::get(http::mcp_status))
|
.route("/api/mcp/status", routing::get(http::mcp_status))
|
||||||
.route("/api/skills", routing::get(http::skills_list))
|
.route("/api/skills", routing::get(http::skills_list))
|
||||||
.route("/api/skills/toggle", routing::post(http::skills_toggle))
|
.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", routing::get(http::subagents_list))
|
||||||
.route("/api/subagents/toggle", routing::post(http::subagents_toggle))
|
.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", routing::get(http::experts_list))
|
||||||
.route("/api/experts/toggle", routing::post(http::experts_toggle))
|
.route("/api/experts/toggle", routing::post(http::experts_toggle))
|
||||||
.route("/api/experts/create", routing::post(http::experts_create))
|
.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/mcp/status", routing::get(http::mcp_status))
|
||||||
.route("/api/skills", routing::get(http::skills_list))
|
.route("/api/skills", routing::get(http::skills_list))
|
||||||
.route("/api/skills/toggle", routing::post(http::skills_toggle))
|
.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", routing::get(http::subagents_list))
|
||||||
.route("/api/subagents/toggle", routing::post(http::subagents_toggle))
|
.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", routing::get(http::experts_list))
|
||||||
.route("/api/experts/toggle", routing::post(http::experts_toggle))
|
.route("/api/experts/toggle", routing::post(http::experts_toggle))
|
||||||
.route("/api/experts/create", routing::post(http::experts_create))
|
.route("/api/experts/create", routing::post(http::experts_create))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user