From 1d6843c4fbedb3cbe2ca0be7f6a7392b21098c76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E5=A4=A9?= Date: Thu, 14 May 2026 02:31:35 +0800 Subject: [PATCH] feat(hermes): expose '/v1/capabilities' as the 'hermes_capabilities' Tauri command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hermes 已在 v2026.5.x 暴露 GET /v1/capabilities 给外部 UI 用作机器可读的能力描述,让前端能动态适配可用 endpoint / feature 而无需用版本号硬匹配。ClawPanel 之前完全没利用这层抽象,本 commit 加一条 Tauri 命令 + Web handler + 前端 wrapper,为后续 chat/runs/approval 等动态降级(老版 Gateway 没有的能力优雅隐藏 UI)打底。 --- scripts/dev-api.js | 7 +++++++ src-tauri/src/commands/hermes.rs | 28 ++++++++++++++++++++++++++++ src-tauri/src/lib.rs | 1 + src/lib/tauri-api.js | 1 + 4 files changed, 37 insertions(+) diff --git a/scripts/dev-api.js b/scripts/dev-api.js index 8f6056c..cd2377a 100644 --- a/scripts/dev-api.js +++ b/scripts/dev-api.js @@ -7063,6 +7063,13 @@ const handlers = { return await resp.json() }, + async hermes_capabilities() { + const url = `${hermesGatewayUrl()}/v1/capabilities` + const resp = await globalThis.fetch(url, { signal: AbortSignal.timeout(5000), headers: { 'User-Agent': 'ClawPanel-Web' } }) + if (!resp.ok) throw new Error(`Gateway 返回 HTTP ${resp.status}`) + return await resp.json() + }, + async hermes_api_proxy({ method, path: reqPath, body, headers: customHeaders } = {}) { const url = `${hermesGatewayUrl()}${reqPath}` const opts = { method: method || 'GET', headers: { 'User-Agent': 'ClawPanel-Web' } } diff --git a/src-tauri/src/commands/hermes.rs b/src-tauri/src/commands/hermes.rs index 9980ace..2ecda04 100644 --- a/src-tauri/src/commands/hermes.rs +++ b/src-tauri/src/commands/hermes.rs @@ -2648,6 +2648,34 @@ pub async fn hermes_health_check() -> Result { } } +// --------------------------------------------------------------------------- +// hermes_capabilities — 探测 Gateway 暴露的 API 能力描述(GET /v1/capabilities) +// +// Hermes 内核 v2026.5.x 起暴露的「机器可读 capability 描述」,给外部 UI 用来 +// 动态适配可用功能,避免在前端写死哪些 endpoint/feature 存在。例: +// 老版本的 Gateway 没有 `/v1/runs/{id}/approval`,新版有 → 用 capabilities 判 +// 断而不是用版本号匹配。 +// +// 不可达 / 老版 Gateway 没有该 endpoint → 返回 Err,调用方应优雅降级。 +// --------------------------------------------------------------------------- + +#[tauri::command] +pub async fn hermes_capabilities() -> Result { + let url = format!("{}/v1/capabilities", hermes_gateway_url()); + + let client = hermes_gateway_http_client(std::time::Duration::from_secs(5)) + .map_err(|e| format!("HTTP 客户端创建失败: {e}"))?; + + match client.get(&url).send().await { + Ok(resp) if resp.status().is_success() => { + let body: Value = resp.json().await.unwrap_or(Value::Null); + Ok(body) + } + Ok(resp) => Err(format!("Gateway 返回 HTTP {}", resp.status())), + Err(e) => Err(format!("Gateway 不可达: {e}")), + } +} + // --------------------------------------------------------------------------- // hermes_detect_environments — 检测 WSL2 / Docker 中的 Hermes Agent // --------------------------------------------------------------------------- diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f48e678..b0b6cc4 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -236,6 +236,7 @@ pub fn run() { hermes::configure_hermes, hermes::hermes_gateway_action, hermes::hermes_health_check, + hermes::hermes_capabilities, hermes::hermes_api_proxy, hermes::hermes_agent_run, hermes::hermes_read_config, diff --git a/src/lib/tauri-api.js b/src/lib/tauri-api.js index ac833d6..fd8298c 100644 --- a/src/lib/tauri-api.js +++ b/src/lib/tauri-api.js @@ -471,6 +471,7 @@ export const api = { configureHermes: (provider, apiKey, model, baseUrl) => invoke('configure_hermes', { provider, apiKey, model: model || null, baseUrl: baseUrl || null }), hermesGatewayAction: (action) => invoke('hermes_gateway_action', { action }), hermesHealthCheck: () => invoke('hermes_health_check'), + hermesCapabilities: () => invoke('hermes_capabilities'), hermesApiProxy: (method, path, body, headers) => invoke('hermes_api_proxy', { method, path, body: body || null, headers: headers || null }), hermesAgentRun: (input, sessionId, conversationHistory, instructions) => invoke('hermes_agent_run', { input, sessionId: sessionId || null, conversationHistory: conversationHistory || null, instructions: instructions || null }), hermesAgentRunStream: (input, sessionId, conversationHistory, instructions, onEvent, options) => webStreamInvoke('hermes_agent_run_stream', { input, sessionId: sessionId || null, conversationHistory: conversationHistory || null, instructions: instructions || null }, onEvent, options),