244 lines
5.5 KiB
Markdown
244 lines
5.5 KiB
Markdown
# 7-5 [done] Hermes client proxy 合同 v1
|
||
|
||
> 更新时间:2026-05-14
|
||
>
|
||
> 上位依据:`design/07-ai/done/7-3-page-ai-hermes-panel-and-mnote-plugin-v1.md`、`design/07-ai/done/7-4-page-ai-hermes-panel-execution-checklist-v1.md`
|
||
>
|
||
> Hermes Web UI 参考:`packages/client/src/api/hermes/chat.ts`、`packages/client/src/api/hermes/sessions.ts`、`packages/server/src/routes/hermes/proxy-handler.ts`、`packages/server/src/services/hermes/chat-run-socket.ts`
|
||
|
||
## 1. 边界
|
||
|
||
- [x] 浏览器只访问 mnote-web 同源 `/api/hermes/client/*`。
|
||
- [x] Hermes API key / gateway token 只存在 mnote-web 服务端环境变量。
|
||
- [x] mnote-web proxy 只做 auth、同源安全、页面上下文注入、trace 注入和错误码标准化。
|
||
- [x] mnote-web proxy 不保存 Hermes session、message、tool event、usage、model 真相。
|
||
- [x] `pageContext` 是 run 输入上下文,不是 Hermes session 的长期事实源。
|
||
- [x] 旧 `/api/ai-agent/run` 在并存期只作为 legacy endpoint,不是新 Hermes 面板主代理。
|
||
|
||
## 2. 路由
|
||
|
||
### `GET /api/hermes/client/sessions`
|
||
|
||
请求:
|
||
|
||
```http
|
||
GET /api/hermes/client/sessions?workspaceId=ws_1&documentId=doc_1&limit=20
|
||
```
|
||
|
||
响应:
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"traceId": "trace_1",
|
||
"sessions": [
|
||
{
|
||
"sessionId": "mnote_doc_1_20260514",
|
||
"title": "当前页问答",
|
||
"preview": "请总结当前页面",
|
||
"messageCount": 2,
|
||
"toolCallCount": 1,
|
||
"updatedAt": 1778712000,
|
||
"model": "hermes-agent"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### `POST /api/hermes/client/sessions`
|
||
|
||
请求:
|
||
|
||
```json
|
||
{
|
||
"workspaceId": "ws_1",
|
||
"documentId": "doc_1",
|
||
"traceId": "trace_1",
|
||
"title": "当前页问答"
|
||
}
|
||
```
|
||
|
||
响应:
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"sessionId": "mnote_doc_1_trace_1",
|
||
"traceId": "trace_1",
|
||
"persistence": "hermes_on_first_run"
|
||
}
|
||
```
|
||
|
||
说明:Hermes Web UI 参考实现没有独立 session create HTTP route,客户端生成 session id,首次 run 时由 Hermes 持久化。mnote 保留本 route 是为了同源客户端合同稳定,但不得在 mnote 保存聊天历史。
|
||
|
||
### `GET /api/hermes/client/sessions/{session_id}`
|
||
|
||
请求:
|
||
|
||
```http
|
||
GET /api/hermes/client/sessions/mnote_doc_1_trace_1?workspaceId=ws_1&documentId=doc_1
|
||
```
|
||
|
||
响应:
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"sessionId": "mnote_doc_1_trace_1",
|
||
"traceId": "trace_1",
|
||
"messages": [
|
||
{
|
||
"messageId": "42",
|
||
"role": "assistant",
|
||
"content": "当前页标题是...",
|
||
"toolCallId": null,
|
||
"toolName": null,
|
||
"timestamp": 1778712000,
|
||
"reasoning": null
|
||
}
|
||
],
|
||
"usage": {
|
||
"inputTokens": 120,
|
||
"outputTokens": 30,
|
||
"totalTokens": 150
|
||
}
|
||
}
|
||
```
|
||
|
||
### `POST /api/hermes/client/runs`
|
||
|
||
请求:
|
||
|
||
```json
|
||
{
|
||
"workspaceId": "ws_1",
|
||
"documentId": "doc_1",
|
||
"sessionId": "mnote_doc_1_trace_1",
|
||
"message": "概括当前页面标题和第一段",
|
||
"model": "hermes-agent",
|
||
"pageContext": {
|
||
"title": "项目计划",
|
||
"outline": [],
|
||
"pageOptions": { "wideLayout": true }
|
||
},
|
||
"selectedBlockId": null,
|
||
"selectedText": null,
|
||
"traceId": "trace_1"
|
||
}
|
||
```
|
||
|
||
响应:
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"sessionId": "mnote_doc_1_trace_1",
|
||
"runId": "run_123",
|
||
"messageId": null,
|
||
"events": [],
|
||
"traceId": "trace_1"
|
||
}
|
||
```
|
||
|
||
### `GET /api/hermes/client/events/{run_id}`
|
||
|
||
请求:
|
||
|
||
```http
|
||
GET /api/hermes/client/events/run_123?sessionId=mnote_doc_1_trace_1
|
||
```
|
||
|
||
响应:`text/event-stream`
|
||
|
||
```text
|
||
data: {"event":"message.delta","run_id":"run_123","session_id":"mnote_doc_1_trace_1","delta":"当前页"}
|
||
|
||
data: {"event":"run.completed","run_id":"run_123","session_id":"mnote_doc_1_trace_1","output":"当前页...","usage":{"input_tokens":120,"output_tokens":30,"total_tokens":150}}
|
||
```
|
||
|
||
### `POST /api/hermes/client/runs/{run_id}/abort`
|
||
|
||
请求:
|
||
|
||
```json
|
||
{
|
||
"workspaceId": "ws_1",
|
||
"documentId": "doc_1",
|
||
"sessionId": "mnote_doc_1_trace_1",
|
||
"traceId": "trace_1"
|
||
}
|
||
```
|
||
|
||
响应:
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"runId": "run_123",
|
||
"sessionId": "mnote_doc_1_trace_1",
|
||
"traceId": "trace_1",
|
||
"events": [
|
||
{ "event": "abort.started", "run_id": "run_123" }
|
||
]
|
||
}
|
||
```
|
||
|
||
### `GET /api/hermes/client/models`
|
||
|
||
响应:
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"traceId": "trace_1",
|
||
"defaultModel": "hermes-agent",
|
||
"models": [
|
||
{ "id": "hermes-agent", "label": "Hermes Agent", "provider": "hermes" }
|
||
]
|
||
}
|
||
```
|
||
|
||
### `GET /api/hermes/client/tools`
|
||
|
||
响应:
|
||
|
||
```json
|
||
{
|
||
"ok": true,
|
||
"traceId": "trace_1",
|
||
"tools": [
|
||
{ "name": "mnote.page.get", "scope": "page.read", "schemaVersion": "mnote.hermes_tool.v1" }
|
||
]
|
||
}
|
||
```
|
||
|
||
## 3. 错误响应
|
||
|
||
统一错误体:
|
||
|
||
```json
|
||
{
|
||
"ok": false,
|
||
"code": "hermes_client_unconfigured",
|
||
"message": "Hermes client proxy 未配置 upstream",
|
||
"traceId": "trace_1",
|
||
"requestId": "req_1"
|
||
}
|
||
```
|
||
|
||
稳定错误码:
|
||
|
||
- [x] `hermes_client_unauthorized`:未登录或缺少有效 mnote 会话。
|
||
- [x] `hermes_client_unconfigured`:未配置 `MNOTE_WEB_HERMES_UPSTREAM_URL`。
|
||
- [x] `hermes_client_bad_request`:请求 JSON 或必要字段错误。
|
||
- [x] `hermes_client_upstream_unauthorized`:Hermes upstream 拒绝服务端 token。
|
||
- [x] `hermes_client_upstream_rate_limited`:Hermes upstream 429。
|
||
- [x] `hermes_client_upstream_unavailable`:Hermes upstream 连接失败或 5xx。
|
||
|
||
## 4. 并存期规则
|
||
|
||
- [x] 新页面 AI 面板只允许请求 `/api/hermes/client/*`。
|
||
- [x] `/api/ai-agent/run` 保留为 legacy guard,不再承载 Hermes 页面 AI 主链。
|
||
- [x] `provider=hermes` 不再通过旧 `/api/ai-agent/run` 表达。
|
||
- [x] 所有新 smoke 应断言页面 AI 主链没有 `/api/ai-agent/run` 请求。
|