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 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<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
|
||||
pub async fn skills_toggle(
|
||||
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 =====================
|
||||
|
||||
#[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<String>,
|
||||
pub body: Option<String>,
|
||||
#[serde(default)]
|
||||
pub capability: Option<CapabilityPolicy>,
|
||||
}
|
||||
|
||||
#[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<Expert> for ExpertResponse {
|
||||
@ -515,6 +631,7 @@ impl From<Expert> 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| {
|
||||
|
||||
@ -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))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user