- config.go: LLMConfig.FallbackMaxTokens(LLM_FALLBACK_MAX_TOKENS, 默认8192) - runtimeconfig/models.go: Group.MaxTokens(models.json每模型独立预算, 负值拒绝) - model/fallback.go: Chain.WithTokenBudget + applyBudget; runtime.go buildSnapshot接入(active.MaxTokens, env.FallbackMaxTokens) - 优先级: 显式Request.MaxTokens > 组maxTokens > 仅多步链末端的LLM_FALLBACK_MAX_TOKENS > 适配器默认8192; 单步链不动env兜底(避免OpenAI主模型行为回归) - gateway admin: AdminLlmProvider.MaxTokens 透传/回显/负值400 - .env.example + .env(LLM_FALLBACK_MAX_TOKENS=8192); Docs/api-client v0.27 + api-reference + design/04 - 新增测试(config/runtimeconfig/budget_test/admin_write), build/vet/test绿+3平台交叉编译
79 KiB
Cyrene v2 后端 API 完整参考(api-reference.md)
用途:本文档是 Cyrene v2 全部后端 API(REST + WebSocket)的唯一详细参考,供 Web 前端 / 移动端 / 原生客户端 / IM 桥接等跨平台客户端开发者使用。所有字段名、取值、鉴权规则、状态码均以代码为准(
internal/services/gateway/*.go)。 主文档关系:Docs/api-client.md为面向使用者的摘要;Docs/api-reference.md为超详细主文档。 维护铁律:每实现/变更一个端点或 WS 事件必须同步更新本文档;契约变更版本化(REST 走/api/v1,未来破坏性变更升 v2),不得破坏既有客户端。 当前版本:以代码为准(gateway 已在 M0–M16 若干里程碑落地;详见各端点「里程碑」注记)。
0. 基础约定(全局)
- Base URL:
http://<host>:8080。网关是唯一对外端口(单二进制、单端口:8080、零跨进程 IPC 红线);生产一般经反向代理 + HTTPS。 - 版本:REST 全部挂在
/api/v1/{resource};WS 挂/ws/chat;管理端点挂/api/v1/admin/*。 - 响应统一信封:
- 成功:
{"data": <payload>}(HTTP 200 / 201 / 202 / 204)。 - 失败:
{"error": {"code": <string>, "message": <string>}}(HTTP 400/401/403/404/409/500/501 等)。
- 成功:
- 错误码:业务错误带稳定字符串
code(如unauthorized/not_found/bad_request/chat_busy/not_implemented),便于客户端分支;message面向人读。 - CORS:
Access-Control-Allow-Origin为配置的ALLOWED_ORIGINS白名单(*时放开);Allow-Methods: GET, POST, PUT, DELETE, OPTIONS;Allow-Headers: Authorization, Content-Type, X-API-Version;Access-Control-Expose-Headers: X-Voice-Provider(跨域浏览器可读语音降级信号头);Max-Age: 600。OPTIONS预检直接回204。同源/无 Origin 请求放行。注意:X-API-Version仅作advisory惯例头(见 §0.2)——CORS 已放行但它不参与服务端版本协商**,服务端当前不校验、不据此改变行为。** - 请求日志:每个请求由
logging中间件记录一行(method/path/status/duration_ms),并计入可观测性指标。panic 由recover中间件兜底为500 {error:{code:internal,...}},不崩溃进程。 - 超时:WS 连接 ping 周期 54s、pong 等待 60s、写超时 10s;读上限 4096 字节/帧。
0.1 鉴权模型(RBAC)
认证基于 JWT Bearer(internal/auth),角色层级 owner > admin > viewer(owner 满足 admin/viewer,admin 满足 viewer,viewer 仅满足 viewer)。
- 获取 token:
POST /api/v1/auth/login(见 §2.2)。token 有效期 24 小时(tokenTTL),签发角色恒为owner。 - REST 鉴权:请求头
Authorization: Bearer <JWT>。s.auth.Authorize(...)中间件:- 缺失/无效/过期 token →
401 {error:{code:"unauthorized",...}}。 - 有效 token 但角色不足 →
403 {error:{code:"forbidden",...}}。 Authorize()(无参)→ 任意已认证角色通过(only read metadata, e.g. GET /sessions、GET /personas)。Authorize(owner)→ 仅 owner;Authorize(owner, admin)→ owner 或 admin。
- 缺失/无效/过期 token →
- WS 鉴权:
GET /ws/chat通过?token=<JWT>或Authorization: Bearer取 token(wsToken),Verify通过即可升级;不做角色门槛(任何有效 token 均可连接),但history回放与proactive推送等按角色处理(见 §6)。 - API Key(
X-API-Key):⚠️ 当前实现未接入。认证/授权中间件Authorize只校验Authorization: Bearer <JWT>。POST /api/v1/admin/users/apikey会签发一个明文 API key(仅返回一次),GET /api/v1/admin/users会列出其掩码,但网关的鉴权中间件目前不接受X-API-Key头(auth.APIKey.Verify/Lookup仅在单元测试中调用)。本文档按代码如实标注:所有端点实际鉴权 = Bearer JWT。若未来把 API key 接入中间件,需同步更新本档。
0.2 X-API-Version 头
Docs/api-client.md 建议客户端可带 X-API-Version: 1。该头是 advisory 惯例头——服务端不校验、不据此协商版本,带不带行为完全不变(CORS Allow-Headers 已放行它,跨域客户端发送不会预检失败)。若未来要真正版本化(/api/v1 → 语义化协商),需另行实现服务端校验;在那之前客户端不应依赖它做任何行为分支。
0.3 上传 / 媒体(重要确认)
POST /api/v1/upload(B1)上传图片 → 返回同源 URL(/api/v1/files/<name>),可直接作chat.image;也可继续用 base64 data: URI 或 http(s) URL 走POST /api/v1/chat/ WSchat的image字段(入视觉真推理,见 §6 流式与直连)。- REST 语音端点已存在:
POST /api/v1/audio/transcribe(STT)、POST /api/v1/audio/speech(TTS)、POST /api/v1/audio/voice-clone(声音复刻),均 owner-only + 审计,绕开 WS 4KB 帧限。语音也可走 WSvoice_input(上行)/voice_output(下行)。 - 管理端导入知识文档(RAG)走 JSON body 文本(
POST /api/v1/admin/rag),非文件上传。
1. REST 端点总览
下表为全量登记;下文 §2–§5 每端点给出字段级说明。
鉴权列:无/JWT(任意已认证) /owner|admin/owner。里程碑为代码/设计归属的大致里程碑(M8 网关、M9 前端、M10 补齐写、M16 会话持久化等)。
1.1 公开(无鉴权)
| # | 方法 | 路径 | 状态 | 说明 | 里程碑 |
|---|---|---|---|---|---|
| 1 | GET | /api/v1/health |
200 | 存活探针 {status:"ok"} |
M8 |
| 2 | POST | /api/v1/auth/login |
200 | 换取 JWT(恒 owner) | M8 |
1.2 用户侧(已认证)
| # | 方法 | 路径 | 鉴权 | 状态 | 说明 | 里程碑 |
|---|---|---|---|---|---|---|
| 3 | POST | /api/v1/chat |
owner|admin | 200/202/409 | 发消息(同步回复或触发 WS 流式) | M8/M15 |
| 4 | GET | /api/v1/sessions |
JWT | 200 | 会话列表(持久化会话 or 渠道会话回退) | M16 |
| 5 | GET | /api/v1/personas |
JWT | 200 | 人格列表 | M8 |
| 6 | GET | /api/v1/sessions/{id}/messages |
owner|admin | 200 | 会话消息热分页 | M16 |
| 7 | POST | /api/v1/sessions |
owner|admin | 201 | 新建次级会话 | M16 |
| 8 | DELETE | /api/v1/sessions/{id} |
owner|admin | 200 | 删除次级会话 | M16 |
1.3 管理后台(/api/v1/admin/*)
读(owner|admin)+ 写(owner,全程审计)。下表按注册顺序,标注鉴权与状态。
| # | 方法 | 路径 | 鉴权 | 状态 | 说明 | 里程碑 |
|---|---|---|---|---|---|---|
| 9 | GET | /api/v1/admin/personas |
owner|admin | 200 | 人格列表 | M8 |
| 10 | GET | /api/v1/admin/sessions |
owner|admin | 200 | 会话列表 | M16 |
| 11 | GET | /api/v1/admin/memories |
owner|admin | 200 | 记忆列表 | M8 |
| 12 | GET | /api/v1/admin/plugins |
owner|admin | 200 | 插件/能力列表 | M8 |
| 13 | GET | /api/v1/admin/channels |
owner|admin | 200 | 渠道列表 + 已配账号(掩码+bot 身份+capabilities) | M8/M17 |
| 14 | GET | /api/v1/admin/channels/messages |
owner|admin | 200 | IM 适配器日志流(全量/平台/适配器三粒度) | M16 |
| 15 | GET | /api/v1/admin/channels/{platform}/messages |
owner|admin | 200 | 某平台 IM 适配器日志流 | M16 |
| 16 | GET | /api/v1/admin/iot |
owner|admin | 200 | IoT 设备列表 | M8 |
| 17 | GET | /api/v1/admin/llm |
owner|admin | 200 | LLM 多厂商多模型组(APIKey 不回显) | M8/M15 |
| 18 | GET | /api/v1/admin/automations |
owner|admin | 200 | 自动化/提醒列表 | M8 |
| 19 | GET | /api/v1/admin/users |
owner|admin | 200 | 账号/API-key 列表 | M8 |
| 20 | GET | /api/v1/admin/backthink |
owner|admin | 200 | 后台思考记录 | M9/M10 |
| 21 | GET | /api/v1/admin/rag |
owner|admin | 200 | 知识库文档列表(可带 ?query= 并入命中) |
M10 |
| 22 | POST | /api/v1/admin/rag/query |
owner|admin | 200 | 知识库检索 | M10 |
| 23 | POST | /api/v1/admin/rag/delete |
owner|admin | 501 | 删除文档(写为 M10,当前 501) | M10 |
| 24 | GET | /api/v1/admin/observability/metrics |
owner|admin | 200 | 网关请求指标 | M10 |
| 25 | GET | /api/v1/admin/observability/trace |
owner|admin | 200 | 最近 trace span | M10 |
| 26 | GET | /api/v1/admin/observability/audit |
owner|admin | 200 | 审计记录 | M10 |
| 27 | POST | /api/v1/admin/plugins/policy |
owner | 200 | 写工具策略(审计) | M8 |
| 28 | GET | /api/v1/admin/sessions/{id}/export |
owner|admin | 200 | 导出会话元数据 | M8 |
| 29 | POST | /api/v1/admin/personas |
owner | 201 | 新建人格 | M8 |
| 30 | PUT | /api/v1/admin/personas/{id} |
owner | 200 | 编辑人格 | M8 |
| 31 | POST | /api/v1/admin/users |
owner | 501 | 新建用户(M10) | M10 |
| 32 | POST | /api/v1/admin/users/apikey |
owner | 201 | 签发 API key | M8 |
| 33 | DELETE | /api/v1/admin/users/apikey/{id} |
owner | 200 | 吊销 API key | M8 |
| 34 | POST | /api/v1/admin/iot/{id}/control |
owner | 200 | 设备控制 | M8 |
| 35 | POST | /api/v1/admin/iot/scenarios |
owner | 501 | IoT 场景(M10) | M10 |
| 36 | POST | /api/v1/admin/iot/rules |
owner | 501 | IoT 规则(M10) | M10 |
| 37 | POST | /api/v1/admin/automations |
owner | 201 | 新建自动化/提醒 | M8 |
| 38 | POST | /api/v1/admin/automations/{id}/run |
owner | 200 | 手动触发规则 | M8 |
| 39 | DELETE | /api/v1/admin/automations/{id} |
owner | 200 | 删除规则 | M8 |
| 40 | POST | /api/v1/admin/llm |
owner | 201 | 保存 LLM provider 组(落盘 + 热更新) | M8/M15 |
| 41 | POST | /api/v1/admin/llm/test |
owner | 200 | 真实模型调用测试 | M8/M15 |
| 42 | POST | /api/v1/admin/channels/open |
owner | 200 | 连接渠道 | M8 |
| 43 | POST | /api/v1/admin/channels/close |
owner | 200 | 断开渠道 | M8 |
| 44 | POST | /api/v1/admin/channels/test |
owner | 200 | 结构性身份识别测试 | M8/M16 |
| 45 | GET | /api/v1/admin/channels/{platform}/login-qr |
owner | 200 | 微信 iLink 扫码绑定 QR | M16 |
| 46 | GET | /api/v1/admin/channels/{platform}/login-status |
owner | 200 | 微信 iLink 扫码状态 | M16 |
| 47 | GET | /api/v1/admin/channels/{platform}/view |
owner | 200 | KOOK 账号聚合视图 | M17 |
| 48 | GET | /api/v1/admin/channels/{platform}/friends |
owner | 200 | KOOK 好友/申请/屏蔽 | M17 |
| 49 | POST | /api/v1/admin/channels/{platform}/friend/handle |
owner | 200 | KOOK 处理好友申请 | M17 |
| 50 | POST | /api/v1/admin/channels/{platform}/message/test |
owner | 200 | KOOK 发送测试消息 | M17 |
| 51 | PUT | /api/v1/admin/channels/{platform}/config |
owner | 201 | 保存渠道账号配置(落盘 + 热更新) | M16 |
| 52 | DELETE | /api/v1/admin/channels/{platform}/config |
owner | 200 | 删除渠道账号 | M16 |
| 53 | POST | /api/v1/admin/plugins/install |
owner | 501 | 安装插件(需源码清单) | M10 |
| 54 | POST | /api/v1/admin/plugins/uninstall |
owner | 200 | 移除插件 | M8 |
| 55 | POST | /api/v1/admin/memories |
owner | 201 | 手动添加记忆 | M8 |
| 56 | POST | /api/v1/admin/memories/search |
owner | 200 | 记忆检索 | M8 |
| 57 | POST | /api/v1/admin/rag |
owner | 201 | 导入知识文档 | M10 |
| 58 | POST | /api/v1/admin/system/revoke-keys |
owner | 200 | 吊销全部 API key | M10/M11 |
| 59 | POST | /api/v1/admin/system/rotate-jwt |
owner | 200 | 轮换 JWT 密钥 | M10/M11 |
| 60 | POST | /api/v1/admin/system/backup |
owner | 200 | 备份数据库 | M10/M11 |
| 61 | POST | /api/v1/admin/system/restore |
owner | 200 | 恢复数据库 | M10/M11 |
另:外部 webhook 回调(如 webhook 渠道的 HMAC 回调)由 composition root 通过 Deps.Webhooks 动态挂载,不走网关鉴权(handler 自校验签名/授权);路径由调用方决定,不在上表枚举。
WS:GET /ws/chat(握手,JWT,见 §6)。
2. 公开端点
2.1 GET /api/v1/health
- 鉴权:无(探针)。
- 响应:
200{"data":{"status":"ok"}}。 - 备注:仅存活探针;不含依赖健康状态。示例:
curl -s http://<host>:8080/api/v1/health
# => {"data":{"status":"ok"}}
2.2 POST /api/v1/auth/login
- 鉴权:无(换取 JWT)。
- 请求 Body(
loginRequest):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
username |
string | 是 | 管理员用户名(对照 cfg.Auth.AdminUser) |
password |
string | 是 | 管理员密码(对照 cfg.Auth.AdminPass) |
{"username":"admin","password":"s3cret"}
- 响应:
200{"data":{"token":"<JWT>","expiresAt":<unix秒>,"role":"owner","refreshToken":"<opaque>"}}(loginResponse)。
| 字段 | 类型 | 说明 |
|---|---|---|
token |
string | 已签名 JWT(HS256),下文请求头 Authorization: Bearer <token> |
expiresAt |
int64 | 过期时间,Unix 秒 = now + 24h(tokenTTL=24h) |
role |
string | 恒为 "owner"(代码硬编码 auth.RoleOwner) |
refreshToken |
string | 不透明 256-bit 随机串(B2)——服务端仅存其 SHA-256 哈希,明文只在响应出现一次;用于 POST /api/v1/auth/refresh 静默续期,AUTH_REFRESH_TTL 默认 720h |
- 错误:
400{"error":{"code":"bad_request","message":"invalid request body"}}(JSON 解析失败);401{"error":{"code":"unauthorized","message":"invalid username or password"}};500{"error":{"code":"login_failed",...}}(签名失败)。 - 备注:登录响应以代码为准 =
{token, expiresAt, role}(不是{token,jwt})。旧api-client.md§1 表内一行写{token,jwt}为历史笔误,以本档为准。curl:
curl -s -X POST http://<host>:8080/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"s3cret"}'
# => {"data":{"token":"eyJ...","expiresAt":1799...,"role":"owner","refreshToken":"...base64url..."}}
2.3 POST /api/v1/auth/refresh(B2)
- 鉴权:公开,body 带
refreshToken。 - 请求 Body:
{refreshToken:"<opaque>"}。 - 响应:
200{"data":{"token":"<新JWT>","expiresAt":<unix秒>,"refreshToken":"<新opaque?>"}}——默认单次使用旋转:旧 refresh 立即作废并签发新的;AUTH_REFRESH_ROTATE_DISABLED=true时旧 token 保持有效、不返回新 refresh(refreshToken省略)。 - 错误:
400 bad_request(JSON 解析失败 / 空 token);401{"error":{"code":"invalid_grant",...}}(未知/已撤销/已过期,统一口径,防枚举);仅成功才审计。 - curl:
curl -s -X POST http://<host>:8080/api/v1/auth/refresh -H 'Content-Type: application/json' -d '{"refreshToken":"<opaque>"}'
2.4 POST /api/v1/auth/logout(B2)
- 鉴权:公开,两种方式二选一——body
{refreshToken}撤单个 grant(幂等);或无 body 但带Authorization: Bearer <jwt>撤该账号全部 grant(全设备登出)。 - 响应:
200{"data":{"ok":true,"revokedCount":<n>}}(单撤revokedCount为 0/1;全撤为该账号 refresh 行数)。 - 错误:不存在的 token 为幂等 no-op(仍
200);非法 body400。
3. 用户侧端点(已认证)
3.1 POST /api/v1/chat
- 鉴权:
Authorization: Bearer <JWT>,Authorize(owner, admin)(owner 或 admin)。viewer 不能写。 - 请求 Body(
chatRequest):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
sessionId |
string | 否 | 目标会话 id;缺省 "web"(主会话)。子会话用 web:<uuid> |
text |
string | 是* | 用户文本。text 与 image 至少一项非空 |
image |
string | 否 | 视觉输入:base64 data: URI 或 http(s) URL;入 vision/OCR 或主模型多模态(design/06)。图文并存则为带图 + 文本的 turn |
deviceId |
string | 否 | 客户端生成的端标识(design/16 §4.5,如 web:chrome:a1b2c3),落库到消息,可选 |
stream |
bool | 是 | true = 经 WS 触发流式(publishChatStream 发 ws.chat),立即返回;false = 同步返回完整回复 |
{"sessionId":"web","text":"你好","deviceId":"web:chrome:a1b2c3","stream":false}
- 响应:
- 同步(
stream=false,会话空闲)200{"data":{"reply":"<助手文本>","steps":<int>,"stopReason":"<string>"}}(chatResponse)。steps为工具循环步数,stopReason为停止原因(如stop/idle/error)。 - 同步但会话正忙(
SubmitSync返回Pending)202{"data":{"queued":true,"queueDepth":<int>}}(非阻塞 ack,稍后按上下文续接)。 - 流式(
stream=true)200{"data":{"stream":true}}—— 实际文本经 WS 推送(见 §6)。
- 同步(
- 错误:
400{"error":{"code":"bad_request","message":"text or image is required"}}(空消息)。409{"error":{"code":"chat_busy","message":"会话正忙,待处理消息已满"}}(pending 队列满,PendingFull)。500{"error":{"code":"chat_unavailable",...}}(runner未注入)/{"error":{"code":"chat_failed",...}}(回合运行出错)。
- 备注:
- 会话编排(design/15):同会话正忙时新消息不阻塞、入 pending(默认上限 5);满则
409 chat_busy。状态经 WS 状态事件通知(§6.4)。 - 主会话
web绑定全量访问 env(loop.OwnerEnv():记忆读写 + 行为不变)。 - 敏感字段(
image可能为 base64)不走日志明文。 - curl:
- 会话编排(design/15):同会话正忙时新消息不阻塞、入 pending(默认上限 5);满则
TOKEN=$(curl -s -X POST http://<host>:8080/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"s3cret"}' | jq -r .data.token)
curl -s -X POST http://<host>:8080/api/v1/chat \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-H 'X-API-Version: 1' \
-d '{"sessionId":"web","text":"讲个笑话","stream":false}'
# => {"data":{"reply":"...","steps":2,"stopReason":"stop"}}
3.2 GET /api/v1/sessions
- 鉴权:
Authorize()(任意已认证角色可读元数据)。 - 响应:注入
HistoryStore时返回持久化会话(historySessionSummary);否则回退渠道会话列表(SessionSummary,或[])。
A) 持久化会话(history != nil,主 + 子):
{"data":[
{"id":"web","type":"main","title":"","createdAt":1753...},
{"id":"web:<uuid>","type":"sub","title":"临时小事","createdAt":1753...}
]}
id:主会话恒"web";次级为web:<uuid>。type:"main"|"sub"。title:可选(omitempty)。createdAt:int64(Unix 毫秒)。
B) 渠道会话回退(history == nil):
{"data":[{"platform":"kook","channelId":"...","sessionId":"...","actorRole":"owner","owner":true,"isGroup":false,"memWritable":true}]}
SessionSummary 字段:platform / channelId / sessionId / actorRole(owner|admin|viewer|...)/ owner(bool) / isGroup(bool) / memWritable(bool)。
- 错误:
500{"error":{"code":"sessions_failed",...}}(列表失败)。 - 备注:
HistoryStore未注入时后端不报错,退化为渠道视图(向后兼容)。curl:
curl -s http://<host>:8080/api/v1/sessions -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
3.3 GET /api/v1/personas
- 鉴权:
Authorize()(任意已认证角色)。 - 响应:
200{"data":[<PersonaSummary>...]}(无 store 时[])。
PersonaSummary 字段:
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 人格 id(PersonaID,回退 ID) |
name |
string | 显示名(c.Name) |
character |
string | 角色名(当前恒 = name) |
isDefault |
bool | 是否默认人格 |
mode |
string | 首个行为模式(modes[0],未设置为 "") |
memoryNamespace |
string | 记忆命名空间(该人格自己的) |
avatar |
string | 视觉头像 URL(omitempty;该人格自己的,缺失则为空) |
chatBackground |
object | omitempty;{landscape, portrait} 双朝向壁纸,仅该人格自己的 |
{"data":[{"id":"cyrene","name":"Cyrene","character":"Cyrene","isDefault":true,"mode":"normal","memoryNamespace":"cyrene","avatar":"/assets/cyrene.png","chatBackground":{"landscape":"/bg/b1.png","portrait":"/bg/b1p.png"}}]}
- 备注:视觉资产按角色作用域读取,绝不返回别的角色的;缺失时前端回退中性默认。curl:
curl -s http://<host>:8080/api/v1/personas -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
3.4 GET /api/v1/sessions/{id}/messages
- 鉴权:
Authorize(owner, admin)(会话消息/thinking 属主人私密历史,viewer 不可读)。 - Path:
{id}= 会话 id(web或web:<uuid>;IM 隔离会话sess-xxx亦可用)。 - Query:
参数 类型 必填 默认 说明 limitint 否 50 页大小,需 >=1,钳制上限 200(maxPageLimit);非法回 400beforeint64 否 0 游标:上一页最旧消息的 createdAt(Unix 毫秒),向更旧翻页afterint64 否 0 正向增量(A5):返回 created_at > after,升序;与before互斥(同传 400);负数/非数字回 400platformstring 否 - 来源平台过滤(design/16 §4.4)。Web 端不传 = 全量共享主会话;IM 传本平台(如 telegram)只拉本渠道消息 - 响应:
200{"data":[<historyMessage>...]};before时按时间倒序(newest first,需reverse()渲染);after时按时间升序(最旧在前,可直接顺序渲染/追加)。
historyMessage 全字段:
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 后端生成的消息 id(客户端据此去重) |
sessionId |
string | 会话 id |
role |
string | user / assistant |
content |
string | 正文(纯文本/图像消息文本;图像为 image 字段) |
mode |
string | 该条的模式(omitempty,供渲染气泡不用猜) |
mood |
string | 情绪标记(omitempty) |
image |
string | 图像引用(omitempty) |
thinking |
string | 思维链(omitempty;本端点 owner/admin-only,故可达;公开回放/持久化广播中已剥离) |
platform |
string | 来源平台(web / telegram / weixin-ilink / qq-obv11...,omitempty) |
deviceId |
string | 客户端端标识(omitempty,如 web:chrome:a1b2c3) |
senderName |
string | 第三方发送者昵称(omitempty;群聊/陌生人消息带,主人私聊为空) |
adapterId |
string | 产生该消息的适配器注册名(omitempty,如 kook:3bef...;Web 消息为空) |
createdAt |
int64 | Unix 毫秒(兼作分页游标) |
{"data":[{"id":"m1","sessionId":"web","role":"assistant","content":"你好","mode":"normal","mood":"calm","platform":"web","deviceId":"web:chrome:a1b2c3","createdAt":1753...}]}
- 错误:
400(limit/before/after非法、before+after同传、id缺失);501{"error":{"code":"not_implemented","message":"会话历史服务未配置(未注入 HistoryStore)"}};500{"error":{"code":"messages_failed",...}}。 - 备注:热分页(design/16 §2.2,§5.1)。
thinking只在本 owner/admin 端点可见;WS 回放/广播已剥离。curl:
curl -s 'http://<host>:8080/api/v1/sessions/web/messages?limit=50&before=1753000000000&platform=web' \
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
3.5 POST /api/v1/sessions
- 鉴权:
Authorize(owner, admin)(写操作,变更持久态)。 - 请求:无 body(空)。可从 JWT claims 取
ownerID作为子会话归属。 - 响应:
201{"data":{"sessionId":"web:<uuid>"}}。 - 错误:
501{"error":{"code":"not_implemented","message":"会话历史服务未配置(未注入 HistoryStore)"}};500{"error":{"code":"session_failed",...}}。 - 备注:新建次级会话(
NewSubSession),独立上下文回溯。curl:
curl -s -X POST http://<host>:8080/api/v1/sessions \
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1' -d '{}'
# => {"data":{"sessionId":"web:0123abcd..."}}
3.6 DELETE /api/v1/sessions/{id}
- 鉴权:
Authorize(owner, admin)。 - Path:
{id}= 会话 id。 - 响应:
200{"data":{"deleted":"<id>"}}。 - 错误:
403{"error":{"code":"forbidden","message":"the main session cannot be deleted"}}(history.ErrImmutableMain,主会话web不可删)。400(id 缺失);501(无 HistoryStore);500{"error":{"code":"session_failed",...}}。
- 备注:删除次级会话及其全部消息。curl:
curl -s -X DELETE http://<host>:8080/api/v1/sessions/web:0123abcd... \
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
# => {"data":{"deleted":"web:0123abcd..."}}
4. 管理后台 · 读端点(owner|admin)
以下端点
鉴权 = Authorize(owner, admin)(除特别标注 owner)。所有管理读不审计;管理写(§5)全程审计(who/what/when/result,见 §4.15)。
4.1 GET /api/v1/admin/personas
- 同
GET /api/v1/personas(§3.3),返回PersonaSummary[]。adminListPersonas直接复用collectPersonas()。 200 {"data":[<PersonaSummary>...]}。curl:
curl -s http://<host>:8080/api/v1/admin/personas -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
4.2 GET /api/v1/admin/sessions
- 同
GET /api/v1/sessions(§3.2):注入 HistoryStore 返回持久化会话(主 + 子);否则回退渠道会话视图(collectSessions)。 200 {"data":[...]}。curl:
curl -s http://<host>:8080/api/v1/admin/sessions -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
4.3 GET /api/v1/admin/memories
- 响应:
200 {"data":[<MemorySummary>...]}(无 store 时[])。MemorySummary={id, scope, text}。scope∈user|persona|project。curl:
curl -s http://<host>:8080/api/v1/admin/memories -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
4.4 GET /api/v1/admin/plugins
- 响应:
200 {"data":[<PluginSummary>...]}(PluginSummary={name, kind, description, allowInGroup, allowInStranger})。kind为能力类型(如plugin|channel|tool|mcp);allowInGroup/allowInStranger为可达性策略。curl:
curl -s http://<host>:8080/api/v1/admin/plugins -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
4.5 GET /api/v1/admin/channels
- 响应:
200 {"data":[<ChannelSummary>...]}。
ChannelSummary = {name, connected, accounts?}。accounts 为 AdminChannelAccount[](掩码无 raw token),仅当 s.channels 注入且平台有已配账号时出现。
-
name:平台名(如kook、telegram、weixin-ilink)。 -
connected:bool,该平台是否已连接。 -
accounts[](AdminChannelAccount)字段:id/config(掩码后的配置 map)/allowGroups/denyGroups/allowUsers/denyUsers/groupMsgPerMin/msgPerMin/maxMessages/botId?/botUsername?/botNickname?/capabilities?(如 kook 的["friends","user_chats","channels","guilds","message_test"])。botId/botUsername/botNickname为已连接 bot 的真实身份(如 KOOK Connect 时调/api/v3/user/me解析),非敏感来自 live adapter。config值已脱敏(token 掩码****+ 尾 4);bot_token等密钥绝不回显。
-
聚合逻辑:
channels.json是账号存在性的真源;一个裸平台 + 多账号合并为一行;无注册 adapter 但已配账号的平台仍出现(供查看配置)。curl:
curl -s http://<host>:8080/api/v1/admin/channels -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
4.6 GET /api/v1/admin/channels/messages
- 鉴权:
owner|admin。 - Query:
limit(默认50,上限200) /before(游标,Unix 毫秒) /adapter(适配器注册名,如kook:3bef...) /platform。三粒度(互斥,adapter优先,其次platform,都不传=全量 IM 总览)。 - 响应:
200 {"data":[<historyMessage>...]}(newest first)。本视图不过滤主会话(因为它带adapter_id);按adapter_id/platform过滤。historyMessage见 §3.4。 - 错误:
501(无 HistoryStore)。 - 备注:IM 适配器日志页全量数据源 + 轮询兜底;实时增量走 WS
channel_persisted(§6.3)。curl:
curl -s 'http://<host>:8080/api/v1/admin/channels/messages?adapter=kook:3bef...&limit=50' \
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
4.7 GET /api/v1/admin/channels/{platform}/messages
- 鉴权:
owner|admin。 - Path:
{platform}= IM 平台(如telegram)。 - Query:
limit(默认50,上限200) /before(Unix 毫秒)。 - 响应:
200 {"data":[<historyMessage>...]}。排除主会话web,只返回该平台非主人消息流(隔离sess-xxx)。senderName标发送者昵称;adapterId标来源适配器。 - 错误:
400(platform 缺失);501(无 HistoryStore);500。 - curl:
curl -s 'http://<host>:8080/api/v1/admin/channels/telegram/messages?limit=50' \
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
4.8 GET /api/v1/admin/iot
- 响应:
200 {"data":[<iotDeviceSummary>...]}(iotDeviceSummary={id, name, type, state, online};state为map[string]any)。无 store 时[]。 - 错误:
500{"error":{"code":"iot_failed",...}}。curl:
curl -s http://<host>:8080/api/v1/admin/iot -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
4.9 GET /api/v1/admin/llm
- 响应:
200 {"data":{"providers":[<AdminLlmProvider>...]}}(AdminLlmConfig)。 AdminLlmProvider:{id, name, provider, baseUrl, model, multimodal, enabled, fallback?, maxTokens?}。provider协议 ∈openai|anthropic;name为友好标签(OpenAI/Anthropic 或组 id)。enabled:index 0 的组为生效组 =true。maxTokens:该组的 token 预算(omitempty,缺省/0 = 未设);缺省时链末端回退步回落LLM_FALLBACK_MAX_TOKENS(.env,默认 8192),再回落适配器默认(anthropic 8192)。- API key 永不返回(wire 无 key 字段)。
- 数据源:
models.json各组(index 0 = 生效组);无models.json时按.env合成单组。curl:
curl -s http://<host>:8080/api/v1/admin/llm -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
4.10 GET /api/v1/admin/automations
- 响应:
200 {"data":[<AdminAutomationRecord>...]}。 AdminAutomationRecord:{id, name, kind, cron?, enabled, lastRun?, nextRun?}。kind∈cron|event;lastRun/nextRun为 RFC3339(未知省略)。curl:
curl -s http://<host>:8080/api/v1/admin/automations -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
4.11 GET /api/v1/admin/users
- 响应:
200 {"data":[<AdminUserRecord>...]}。 AdminUserRecord:{id, username, role, createdAt?}(createdAtRFC3339)。无 UserStore 时仅有配置的 admin 账号(id:"admin"、role:"owner");注入 UserStore 后追加 API key 摘要(username:"apikey"、id为掩码)。curl:
curl -s http://<host>:8080/api/v1/admin/users -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
4.12 GET /api/v1/admin/backthink
- 响应:
200 {"data":[<AdminBackthinkItem>...]}(无 store 时[])。AdminBackthinkItem:{id, trigger, conclusion?, shouldNotify, pushed, at}(at为 Unix 毫秒)。默认取最近 50 条。curl:
curl -s http://<host>:8080/api/v1/admin/backthink -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
4.13 GET /api/v1/admin/rag
- Query:
?query=<string>(可选;带则并入检索命中)。 - 响应:
200 {"data":{"documents":[<AdminRagDoc>...], "hits"?:[<AdminRagHit>...]}}(AdminRagResponse)。AdminRagDoc:{id, name, desc?, chunks, embedded}(embedded反映是否配置向量增强)。AdminRagHit:{docId, docName, chapter?, text, score, semantic}(semantic布尔,语义/向量命中为 true)。
- 错误:
500。无 store 时返回空文档列表(不 500)。curl:
curl -s 'http://<host>:8080/api/v1/admin/rag?query=部署' -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
4.14 POST /api/v1/admin/rag/query
- 鉴权:
owner|admin。 - Body:
{"query":"<string>"}(必填,非空)。 - 响应:
200 {"data":{"query":"<string>","hits":[<AdminRagHit>...]}}。审计:rag.query(成功ok/ 失败error)。 - 错误:
400(query 缺失/空);501(未注入 RagStore);500。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/rag/query \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"query":"部署要求是什么"}'
4.15 GET /api/v1/admin/observability/audit
- Query:
?limit=N(默认 50,上限 500)。 - 响应:
200 {"data":[<AuditRecord>...]}(newest first)。AuditRecord={id(int64递增), who, what, when(RFC3339), result}。who形如admin:owner;what为审计动作(如system.backup);result∈ok|error。 - 来源:SQLite
audit_logs(SQLiteAuditStore);未注入时退化为进程内存。 - 示例:
{"data":[{"id":1,"who":"admin:owner","what":"system.backup","when":"2026-09-08T00:00:00Z","result":"ok"}]}。curl:
curl -s 'http://<host>:8080/api/v1/admin/observability/audit?limit=100' -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
4.16 GET /api/v1/admin/observability/metrics
- Query:无(不接收 limit)。
- 响应:
200 {"data":<AdminMetrics>}。AdminMetrics={totalRequests, qps, avgLatencyMs, maxLatencyMs, perMethod{}, perPath{}, statusCounts{}, llmCalls, subagentEvents}。qps:近 1 秒窗口计数;avgLatencyMs/maxLatencyMs:均值/最大耗时(毫秒)。perMethod/perPath/statusCounts:分布计数。llmCalls/subagentEvents:由实际观测的 trace span 按kind累计(Tracer 未注入为 0)。- Token/成本后端不统计、不返回(不伪造指标)。curl:
curl -s http://<host>:8080/api/v1/admin/observability/metrics -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
4.17 GET /api/v1/admin/observability/trace
- Query:
?limit=N(默认 50,上限 500)。 - 响应:
200 {"data":[<AdminTraceSpan>...]}(newest first)。AdminTraceSpan={id, parentId?, name, kind, status, startedAt, endedAt?, durationNs, props?, events?}。kind∈llm_call|tool_call|orchestration|state_transition|subagent|memory|guardrail;events为[{time,type,props?}];时间 RFC3339。 - 来源:环形缓冲(默认 512 条),由
ObserveSpan/TraceSink注入。curl:
curl -s 'http://<host>:8080/api/v1/admin/observability/trace?limit=100' -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
4.18 GET /api/v1/admin/sessions/{id}/export
- 鉴权:
owner|admin(注册为adminAuth)。 - Path:
{id}= 会话 id。 - 响应:
200 {"data":{"session":<SessionSummary>}}(summaryOfSession:{platform, channelId, sessionId, actorRole, owner, isGroup, memWritable})。 - 错误:
404(会话未找到);500。 - 备注:导出会话身份元数据;完整消息转录由 M10 提供。curl:
curl -s http://<host>:8080/api/v1/admin/sessions/web:0123.../export -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
5. 管理后台 · 写端点(owner,全程审计)
以下端点鉴权 =
Authorize(owner)(仅主人)。每个写操作都会调用auditClaims(r, "<action>", "ok"|"error")记录 who/what/when/result(落audit_logs+ 网关日志)。敏感值(token/密钥/新 JWT 密钥/DB 路径)不在响应/日志回显。
5.1 POST /api/v1/admin/plugins/policy
- Body(
policyRequest):{"name":"<能力名>","allowInGroup":bool,"allowInStranger":bool}。 - 响应:
200 {"data":{"name":"<能力名>","allowInGroup":bool,"allowInStranger":bool}}。 - 错误:
400(name 缺失);500(cap service 未配置);404(能力不存在);500。 - 审计:
policy.set。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/plugins/policy \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"name":"web_search","allowInGroup":true,"allowInStranger":false}'
5.2 POST /api/v1/admin/personas
- Body(
personaUpsertRequest):{"id","name","characterRef?","role","systemPrompt?","personality?","speechStyle?","catchphrase?","background?","values?[]","modes?[]"/"mode?","memoryNamespace?"}。id/name/role必填;systemPrompt空时派生默认;characterRef空时 = id。 - 响应:
201 {"data":<PersonaSummary>}。注册进 persona registry(真实运行效果)+ 持久化到configs/personas/<id>.yaml(重启存活)。 - 错误:
400(id/name/role 缺失);409{"error":{"code":"duplicate","message":"人格已存在,请使用编辑"}};500(registry 未配置 / 持久化失败 / 注册失败)。 - 审计:
persona.create。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/personas \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"id":"luna","name":"Luna","role":"女仆","modes":["normal"],"memoryNamespace":"luna"}'
# => {"data":{"id":"luna","name":"Luna","character":"Luna","isDefault":false,"mode":"normal","memoryNamespace":"luna"}}
5.3 PUT /api/v1/admin/personas/{id}
- Path:
{id}= 人格 id。Body 同 5.2(不含 id;服务端以 path 覆盖req.ID)。name/role必填。 - 响应:
200 {"data":<PersonaSummary>}。 - 错误:
400(name/role 缺失);404(不存在);500(registry 未配置 / 持久化 / 更新失败)。 - 审计:
persona.update。curl:
curl -s -X PUT http://<host>:8080/api/v1/admin/personas/luna \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"name":"Luna","role":"女仆","modes":["gentle"]}'
5.4 POST /api/v1/admin/users
- 响应:
501 {"error":{"code":"not_implemented","message":"新增用户/角色存储为 M10 提供(当前为单 admin + APIKey 现状)"}}。审计:user.create(error)。 - 备注:诚实不伪造(无持久用户表)。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/users \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' -d '{}'
5.5 POST /api/v1/admin/users/apikey
- Body(
apiKeyIssueRequest):{"role":"owner|admin|viewer"}。 - 响应:
201 {"data":{"id":"<掩码>","key":"<明文·仅此一次>","role":"<role>"}}。key为 base64 raw URL 编码的 32 字节随机串,仅此响应可见一次;id为掩码(xxxx....xxxx),用于后续吊销。 - 错误:
400(invalid role/role 缺失);500(user service 未配置 / 生成失败)。 - 审计:
apikey.issue。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/users/apikey \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"role":"admin"}'
# => {"data":{"id":"abcd****wxyz","key":"LQV...","role":"admin"}}
5.6 DELETE /api/v1/admin/users/apikey/{id}
- Path:
{id}= 列表返回的掩码 id。 - 响应:
200 {"data":{"id":"<掩码>","revoked":true}}。 - 错误:
404(掩码未命中);500(user service 未配置)。 - 审计:
apikey.revoke。curl:
curl -s -X DELETE http://<host>:8080/api/v1/admin/users/apikey/abcd****wxyz \
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
5.7 POST /api/v1/admin/iot/{id}/control
- Path:
{id}= 设备 id。Body(iotControlRequest):{"action":"<string>","params":{...}?}。 - 响应:
200 {"data":{<iotDeviceSummary>}}({id,name,type,state,online})——真实下发到 IoT provider,并发布device_update事件。 - 错误:
400(action 缺失 / 动作不支持 / 参数错);404(设备不存在);409(设备离线);500(IoT 服务未配置 / 控制失败)。 - 审计:
iot.control。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/iot/d1/control \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"action":"toggle","params":{"brightness":50}}'
5.8 POST /api/v1/admin/iot/scenarios
- 响应:
501 {"error":{"code":"not_implemented","message":"IoT 场景存储为 M10 提供(当前 IoT 仅有设备列表/控制/状态)"}}。审计:iot.scenario(error)。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/iot/scenarios -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{}'
5.9 POST /api/v1/admin/iot/rules
- 响应:
501(同 5.8,iot.rule)。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/iot/rules -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{}'
5.10 POST /api/v1/admin/automations
- Body(
automationCreateRequest):{"name","kind":"cron|event","expression"}(均必填)。 - 响应:
201 {"data":{"id":"<id>","name":"<name>","kind":"<kind>"}}——真实注册规则。 - 错误:
400(字段缺失 /invalid_cron/invalid_rule);500(自动化服务未配置 / 添加失败)。 - 审计:
automation.create。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/automations \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"name":"晨间问候","kind":"cron","expression":"0 8 * * *"}'
5.11 POST /api/v1/admin/automations/{id}/run
- Path:
{id}。Body:无。 - 响应:
200 {"data":{"id":"<id>","output":"<string>"}}(手动触发一次)。 - 错误:
404;500。 - 审计:
automation.run。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/automations/<id>/run -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
5.12 DELETE /api/v1/admin/automations/{id}
- Path:
{id}。响应:200 {"data":{"id":"<id>","deleted":true}}。 - 错误:
404;500。审计:automation.delete。curl:
curl -s -X DELETE http://<host>:8080/api/v1/admin/automations/<id> -H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
5.13 POST /api/v1/admin/llm
- Body(
llmProviderRequest):{"id?","name?","provider?","baseUrl","apiKey?","model","multimodal?","fallback?","maxTokens?"}。baseUrl+model必填;provider空默认openai(仅允许openai|anthropic);maxTokens缺省/0= 未设,负数回400。 - 响应:
201 {"data":{"provider":<AdminLlmProvider>,"persisted":true,"path":"<models.json 绝对路径>"}}。真实持久化:读-改-写models.json,保存组提升为生效组(index 0),并热重载运行中的 LLM stack(无需重启)。 - 错误:
400(缺 baseUrl/model / invalid provider);500(LLM 服务未注入 / 写盘失败 / 热重载失败)。API key 写入文件但永不回显、不落日志。 - 审计:
llm.addProvider。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/llm \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"provider":"openai","baseUrl":"https://api.openai.com/v1","apiKey":"sk-...","model":"gpt-4o","multimodal":true}'
5.14 POST /api/v1/admin/llm/test
- Body(
llmTestRequest):{"provider?","baseUrl","model","apiKey?","prompt?"}。model+baseUrl必填;provider空默认openai;prompt空用探针"Reply with the single word: OK"。 - 响应:
200 {"data":{"ok":true,"provider":"openai","model":"gpt-4o","reply":"<模型回复>","tokens":<int>}}——真实模型调用(超时 30s)。 - 错误:
400(缺 model/baseUrl / invalid provider);500(调用失败)。 - 审计:
llm.test。API key 仅本次请求内使用,不落库、不进 wire。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/llm/test \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"provider":"openai","baseUrl":"https://api.openai.com/v1","model":"gpt-4o","apiKey":"sk-...","prompt":"你好"}'
5.15 POST /api/v1/admin/channels/open
- Body(
channelActionRequest):{"name":"<渠道名>"}(如"kook"/"kook:conf0"/ 裸平台名解析到该平台首个账号)。 - 响应:
200 {"data":{"name":"<name>","connected":true}}——真实 Connect。 - 错误:
400(name 缺失);404(渠道未注册);409{"error":{"code":"needs_login","message":"该渠道尚未登录:请先用「扫码登录」绑定,再连接"}}(weixin-iLink 无 token);500(连接失败)。 - 审计:
channel.open。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/channels/open \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"name":"kook"}'
5.16 POST /api/v1/admin/channels/close
- Body:
{"name":"<渠道名>"}。响应:200 {"data":{"name":"<name>","connected":false}}——真实 Disconnect。 - 错误:
400/404/500。审计:channel.close。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/channels/close \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' -d '{"name":"kook"}'
5.17 POST /api/v1/admin/channels/test
- Body(
channelTestRequest):{"platform?","accountId","isGroup"}(accountId必填)。 - 响应:
200 {"data":{"role":"owner|member|...","owner":bool,"isGroup":bool,"env":"owner|...|","verdict":"<人读结论>"}}。结构性身份识别:channel 层 actor 元数据 ×configs/users/owner.yaml白名单,绝不靠提示词。verdict说明:群聊→隔离+记忆只读+仅允许工具;命中白名单→owner(全量访问);未命中→按陌生人(default deny)。 - 错误:
400(accountId 缺失)。审计:channel.identity-test。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/channels/test \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"platform":"weixin-ilink","accountId":"wx_uid_123","isGroup":false}'
# => {"data":{"role":"owner","owner":true,"isGroup":false,"env":"owner","verdict":"匹配主人白名单 → owner(全量访问,记忆可读写)"}}
5.18 GET /api/v1/admin/channels/{platform}/login-qr
- 鉴权:
owner。Path:{platform}=weixin-ilink(其它平台 501)。Query:?accountId=(可选)。 - 响应:
200 {"data":{"qrUrl?","qrCode?","status":"confirmed|<状态>"}}。qrUrl为微信二维码图 URL(前端据此渲染可扫 QR),qrCode为原始扫码标识;已登录时status:"confirmed"。 - 错误:
501(非 weixin-ilink);404(账号未找到,提示先保存账号);500(取码失败)。 - 审计:
channel.weixin.login.qr。curl:
curl -s 'http://<host>:8080/api/v1/admin/channels/weixin-ilink/login-qr?accountId=conf0' \
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
5.19 GET /api/v1/admin/channels/{platform}/login-status
- 鉴权:
owner。Path:{platform}=weixin-ilink。Query:?accountId=(可选)。 - 响应:
200 {"data":{"status":"confirmed|failed|...","botId?","botToken?","err?"}}。botToken为掩码(****+尾4);status==confirmed时后端已把刚取到的bot_token/bot_id/owner_id写入 channels.json 并热加载该平台(bridge 连上已配置的 adapter,无需重启),且(owner_id 存在时)把扫码者绑定 id 登记进 owner 白名单(owner.yaml,热生效)。status==failed时err带原因。 - 错误:
501/404/500。 - 审计:
channel.weixin.login.status(+channel.weixin.login.persist(确认时))。curl:
curl -s 'http://<host>:8080/api/v1/admin/channels/weixin-ilink/login-status?accountId=conf0' \
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
# 确认后 => {"data":{"status":"confirmed","botId":"wxid_xx","botToken":"****abcd"}}
5.20 GET /api/v1/admin/channels/{platform}/view
- 鉴权:
owner。Path:{platform}=kook(其它 501)。Query:?accountId=(可选)。 - 响应:
200 {"data":{"platform":"kook","accountId?","friends?","userChats?","guilds?","channels?","members?","errors?"}}。聚合该账号真实数据(好友/申请/屏蔽、私信会话、服务器 + 首个服务器频道与成员)。errors为{子项:原因}记录失败子项(不伪造)。token/密钥不打印不落日志。 - 错误:
501(非 kook);404(账号未找到);500。 - 审计:
channel.platform.view。curl:
curl -s 'http://<host>:8080/api/v1/admin/channels/kook/view?accountId=3bef...' \
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
5.21 GET /api/v1/admin/channels/{platform}/friends
- 鉴权:
owner。Path:kook。Query:?type=friend|request|block&accountId=(type空=全部)。 - 响应:
200 {"data":<KOOK FriendList>}(含 friends/requests/blocked)。 - 错误:
501/404/500。审计:channel.friends。curl:
curl -s 'http://<host>:8080/api/v1/admin/channels/kook/friends?type=request&accountId=3bef...' \
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
5.22 POST /api/v1/admin/channels/{platform}/friend/handle
- 鉴权:
owner。Path:kook。Query:?accountId=。Body(kookFriendHandleRequest):{"id":<int64>,"accept":bool}(id 必填)。 - 响应:
200 {"data":{"handled":true,"id":<int64>,"accept":bool}}。 - 错误:
400(id 缺失);501/404/500。审计:channel.friend.handle。curl:
curl -s -X POST 'http://<host>:8080/api/v1/admin/channels/kook/friend/handle?accountId=3bef...' \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"id":12345,"accept":true}'
5.23 POST /api/v1/admin/channels/{platform}/message/test
- 鉴权:
owner。Path:kook。Query:?accountId=。Body(kookMessageTestRequest):{"targetId":"<频道id>","content":"<文本>"}(均必填)。 - 响应:
200 {"data":{<KOOK TestMessageResult>}}(如{msg_id,msg_timestamp})——真实向目标发一条 kmarkdown 测试消息。 - 错误:
400(字段缺失);501/404/500。审计:channel.message.test。curl:
curl -s -X POST 'http://<host>:8080/api/v1/admin/channels/kook/message/test?accountId=3bef...' \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"targetId":"123456","content":"ping"}'
5.24 PUT /api/v1/admin/channels/{platform}/config
- 鉴权:
owner。Path:{platform}。Body(channelConfigRequest):字段 类型 说明 idstring 账号 id(可选;空则由服务端生成) tokenstring 快捷 token,并入 config["bot_token"](若 config 未显式给)baseURLstring → config["base_url"]remoteURLstring → config["remote_url"]listenAddrstring → config["listen_addr"]modestring → config["mode"]accessTokenstring → config["access_token"]selfIDstring → config["self_id"]configobject 平台专属键值(如 bot_token/base_url/remote_url/endpointPath/receive_mode/webhook_verify_token...)allowGroups/denyGroupsstring[] 黑白名单(群) allowUsers/denyUsersstring[] 黑白名单(用户) groupMsgPerMinint 群消息限流 msgPerMinint 用户消息限流 maxMessagesint 单账号最大消息数 空配置时:
weixin-ilink豁免(先建账号再扫码),其它平台400。 - 响应:
201 {"data":{"persisted":true,"path":"<channels.json 路径>","platform":"<platform>","accountId":"<accountId>"}}。真实持久化 + 热更新(读 channels.json→UpsertChannel→落盘→Reload(platform)热替换 adapter,无需重启)。token 写入文件但不回显(GET 列表掩码)。 - 错误:
400(platform 缺失 / 空配置);500(渠道配置服务未注入 / 写盘失败 / 热重载失败)。 - 审计:
channel.save。curl:
curl -s -X PUT http://<host>:8080/api/v1/admin/channels/kook/config \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"id":"conf0","token":"<bot_token>","allowUsers":["user_i_like"],"groupMsgPerMin":20}'
5.25 DELETE /api/v1/admin/channels/{platform}/config
- 鉴权:
owner。Path:{platform}。Query:?id=<accountId>(必填)。 - 响应:
200 {"data":{"persisted":true,"path":"<channels.json>","platform":"","accountId":"<id>"}}。删除账号 → 落盘 →Reload(platform)热更新(对应 adapter 优雅关停)。 - 错误:
400(platform/id 缺失);500(未注入 / 写盘 / 重载失败)。 - 审计:
channel.delete。curl:
curl -s -X DELETE 'http://<host>:8080/api/v1/admin/channels/kook/config?id=conf0' \
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1'
5.26 POST /api/v1/admin/plugins/install
- 响应:
501 {"error":{"code":"not_implemented","message":"插件安装需插件源码清单(编译二进制/YAML)为 M10 提供;卸载可直接调用 registry"}}。审计:plugin.install(error)。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/plugins/install \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -d '{"name":"x"}'
5.27 POST /api/v1/admin/plugins/uninstall
- Body(
pluginActionRequest):{"name":"<能力名>"}(必填)。 - 响应:
200 {"data":{"name":"<name>","uninstalled":true}}——真实从 registry 移除。 - 错误:
400(name 缺失);404(不存在);500。 - 审计:
plugin.uninstall。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/plugins/uninstall \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' -d '{"name":"web_search"}'
5.28 POST /api/v1/admin/memories
- Body(
memoryAddRequest):{"personaId?","content","scope?"}。content必填;personaId缺省"cyrene";scope∈user|persona|project(缺省persona;无效非空回 400)。 - 响应:
201 {"data":{"id":"<id>","scope":"<scope>","text":"<内容>"}}——真实写入 memory store。 - 错误:
400(content 缺失 / scope 非法);501(memory store 未配置 / 未注入memory.Store);500。 - 审计:
memory.add。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/memories \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"personaId":"cyrene","content":"主人喜欢喝美式咖啡","scope":"user"}'
5.29 POST /api/v1/admin/memories/search
- Body(
memorySearchRequest):{"query","personaId?","topK?"}。query必填;personaId缺省"cyrene";topK≤0 取 10。 - 响应:
200 {"data":{"query":"<query>","personaId":"<personaId>","semantic":false,"hits":[{"id","scope","text","score"}]}}。真实检索(keyword×衰减);语义/向量路径需注入 vector index(semantic当前恒false,诚实反映未接向量器)。 - 错误:
400(query 缺失);500(memory store 未配置 / 检索失败)。 - 审计:
memory.search。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/memories/search \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"query":"咖啡","personaId":"cyrene","topK":5}'
5.30 POST /api/v1/admin/rag
- Body(
ragIndexRequest):{"name","desc?","content"}。name+content必填。 - 响应:
201 {"data":{"name":"<name>"}}——真实导入:分块并落项目索引,立即可被关键词/行上下文检索。 - 错误:
400(name/content 缺失);501(未注入 RagStore);500。 - 审计:
rag.index。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/rag \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"name":"部署说明","desc":"v2 部署要求","content":"单二进制...\n单端口 8080..."}'
5.31 POST /api/v1/admin/system/revoke-keys
- Body:无。
- 响应:
200 {"data":{"revoked":<int>}}——清空 API key 表,先前签发的所有 key 立即失效。 - 错误:
500(user service 未配置,users_unavailable)。 - 审计:
system.revoke_keys。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/system/revoke-keys \
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1' -d '{}'
5.32 POST /api/v1/admin/system/rotate-jwt
- Body(
rotateJWTRequest):{"newSecret":"<≥32 位>"}(必填)。 - 响应:
200 {"data":{"rotated":true,"note":"现有 token 已全部失效,需重新登录"}}。 - 错误:
400(newSecret 缺失 / 长度 <32,"新密钥长度不足 32 位");500(认证服务未配置 / 轮换失败)。 - 审计:
system.rotate_jwt。新密钥永不回显、不落日志。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/system/rotate-jwt \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"newSecret":"a-32-char-long-random-secret-12345678"}'
5.33 POST /api/v1/admin/system/backup
- Body:无。
- 响应:
200 {"data":{"path":"<dataDir>/backup-<ts>.db","size":<字节数>}}。经 SQLiteVACUUM INTO(含已提交 WAL)快照到数据目录。 - 错误:
500(DB 路径未配置backup_unavailable/ 备份失败)。 - 审计:
system.backup。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/system/backup \
-H "Authorization: Bearer $TOKEN" -H 'X-API-Version: 1' -d '{}'
5.34 POST /api/v1/admin/system/restore
- Body(
restoreRequest):{"path":"<dataDir 内的备份文件>"}(必填)。 - 响应:
200 {"data":{"restored":true,"backup":"<恢复前自动快照路径>","note":"已安排恢复,需重启服务后生效"}}。流程:校验 path 在 dataDir 内(filepath.Rel防路径穿越,..逃逸回 400)→ 文件存在校验 → 先自动备份当前 DB → 复制替换 DB 文件 → 返回(替换后需重启服务生效,因运行进程持有 DB 句柄)。 - 错误:
400(path 缺失 / 逃逸数据目录);404(备份文件不存在);500(DB 路径未配置 / 恢复前备份失败 / 替换失败)。 - 审计:
system.restore。curl:
curl -s -X POST http://<host>:8080/api/v1/admin/system/restore \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' -H 'X-API-Version: 1' \
-d '{"path":"/abs/path/data/backup-20260908T000000.000000000Z.db"}'
6. WebSocket 实时协议(GET /ws/chat)
唯一实时通道。单二进制、单端口:同一
:8080上,REST/WS/admin 共用。生产者(loop/IoT/task/automation/reminder/subagent)经进程内bus发布,hub 订阅ws.>及桥接模式并规范化成 WS 帧广播给客户端。WS 上行不鉴权角色门槛,但特定下行按角色(history回放对任何已认证连接,proactive仅 owner/admin)。
6.1 握手与连接
- URL:
ws://<host>:8080/ws/chat?token=<JWT>(或Authorization: Bearer <JWT>头)。 - 方法:必须
GET(否则握手前回400 {"error":{"code":"bad_request","message":"websocket requires GET"}})。 - 鉴权:
s.auth.Verify(wsToken(r))。wsToken先取?token=,再取Authorization: Bearer。失败 → 握手前回401 {"error":{"code":"unauthorized",...}}(浏览器/IM 客户端收到干净拒绝)。不校验角色(任意有效 JWT 可连)。 - Origin 校验:
CheckOrigin允许「同一 origin」+「同一 host」+ 白名单;*全放行。同 host 放宽:production 内嵌前端页面 origin 即网关 Host,故PORT改变无需改.env。跨源客户端(外部 IM / 另一 origin)需在ALLOWED_ORIGINS白名单。 - 升级后流程:注册连接 →
replayHistory(c, "web")(首帧推history回放)→ 启动writePump(唯一写者,含 ping/pong)与readPump(读客户端帧)。 - 心跳/限帧:读上限 4096 字节/帧;写超时 10s;pong 等待 60s;ping 周期 54s。发送缓冲 32(慢客户端满则丢帧,生产者不阻塞)。
history回放(首帧):{type:"history", sessionId:"web", data:{messages:[historyMessage...]}}——主会话最近 50 条(wsReplayLimit=50),thinking已剥离;回放仅在history != nil时发生,失败静默跳过。
6.2 帧壳(Frame Shell)
-
下行(服务端→客户端)
wsEvent:字段 类型 必填 说明 typestring 是 事件类型(见 §6.3 表) turnIdstring 否 一轮交换的关联 id(流式; turn-<hex>);OnStream 结构化流路径不带seqint 否 hub 级单调递增序号,供客户端排序关联流式突发 sessionIdstring 否 能确定会话的事件带上,客户端按激活会话过滤 dataany 否 类型化负载(每个事件见 §6.3) -
上行(客户端→服务端)
wsInbound:字段 类型 必填 说明 typestring 是 事件类型(见 §6.3 上行表) reqIdstring 否 请求关联 id(当前服务端不回填,预留) dataobject 否 每类型负载(raw JSON)
6.3 上行事件(客户端 → 服务端)
由
wsConn.route(in)按in.Type分派。未知类型回{type:"error", data:{message:"unknown event type"}}。
| type | 数据负载 | 服务端行为 | 结果 |
|---|---|---|---|
mode_switch |
任意(如 {"mode":"chat"}) |
发布 ws.mode_switch → hub 规范化广播 |
下行 mode_switch(广播) |
chat |
{sessionId?, text, image?, deviceId?} |
发布 ws.chat → streamChat(跑 loop 并流式) |
下行 stream_chunk/stream_end 或 message.*/thinking.*/tool_call.*(广播) |
interrupt |
{sessionId} |
会话编排器中断:取消在跑 turn + 清空 pending | 下行 session_interrupted |
voice_input |
{audio?, format?, text?, sessionId?} |
STT→chat→TTS(缺省 sessionId=web) |
下行 voice_output(定向回请求连接) |
subagent_ask |
{id, mode?(""/full/tail/keyword), n, query?} |
审计子代理上下文(缺省 mode=full) |
下行 subagent_event(或 error) |
task_output_query |
{taskId} |
查询后台任务输出 | 下行 task_output(或 error) |
上行各负载字段
chat(chatStreamRequest):{sessionId(缺省"web"),text,image?(base64 data: URI 或 URL),deviceId?}。text+image 至少一项;runner未注入或两者皆空 → 直接广播stream_end{stopReason:"idle"}。主会话web绑定全量 env(loop.OwnerEnv())。chat进入时若该会话正忙 → 由编排器入队(见 §6.4),非阻塞。interrupt:{sessionId}。命中空闲会话为 no-op。voice_input:{audio(base64 或 data: URI),format(wav|mp3|ogg|webm|mpeg,空由 mime 猜,默认wav),text(已转写则跳过 STT),sessionId?}。无voice服务 →{type:"error",data:{message:"voice unavailable"}}。subagent_ask:{id(必填),mode(空/full/tail/keyword),n(tail 查询窗长),query(keyword 查询词)}。无 auditor →error;id 缺失 →error。task_output_query:{taskId}。无 task store →error;运行中任务返回截至目前输出;输出拼接stdout + "\n" + stderr。
6.4 下行事件全表
来源标记:
[direct]网关直接 emit;[relay]hub 从 busws.>规范化(任意ws.X主题 → 类型X);[bridge]桥接 producer 主题;[target]仅定向特定连接。
| type | 来源 | 数据(data) |
用途 |
|---|---|---|---|
history |
direct | {messages:[historyMessage]} |
连接首帧回放主会话最近 50 条(thinking 剥离) |
message.start |
direct | {index} |
正文段开始 |
message.delta |
direct | {delta,index} |
正文 token 增量(累加到当前正文) |
message.end |
direct | {index} |
正文段结束(不结束整轮,由 stream_end 收尾) |
thinking.start |
direct | {index} |
思维链段开始(前端折叠块) |
thinking.delta |
direct | {delta,index} |
思维链 token 增量 |
thinking.end |
direct | {index} |
思维链段结束 |
tool_call.start |
direct | {id,index} |
工具调用中间态开始 |
tool_call.delta |
direct | {id,index,delta} |
工具调用参数增量(如 input_json_delta) |
tool_call.end |
direct | {id,index} |
工具调用中间态结束 |
tool_execution.update |
direct | {id,index,delta} |
工具执行进度/局部输出 |
stream_chunk |
direct | {delta} |
向后兼容整段文本(仅非流式回退路径;流式路径不 emit) |
stream_end |
direct | {stopReason, steps?} / {stopReason:"idle"} / {stopReason:"error", error} |
一轮结束 |
session_busy |
direct(编排) | {sessionId, mode} |
会话进入忙碌态(mode∈idle/thinking/executing/subagent) |
pending_ack |
direct(编排) | {sessionId, mode, queueDepth} |
忙时新消息已入队「收到,稍后回」 |
pending_drain |
direct(编排) | {sessionId, mode, queueDepth} |
忙完开始消费下一待处理消息 |
session_idle |
direct(编排) | {sessionId, mode} |
会话回到空闲、队列清空 |
session_interrupted |
direct(编排) | {sessionId, mode, dropped} |
用户 interrupt,当前任务取消、pending 清空 |
pending_full |
direct(编排) | {sessionId, mode, max} |
单会话 pending 达上限(默认 5)拒绝入队 |
message_persisted |
direct | {message:historyMessage}(sessionId 设) |
每条 user/assistant 消息落库后广播(主会话 web/web:<uuid>),thinking 剥离 |
channel_persisted |
direct | {message:historyMessage}(sessionId=sess-xxx) |
群聊/陌生人(非主人)消息落库后广播,供 IM 适配器日志页 |
voice_output |
direct/target | {audio, format, text, reply} |
语音合成回复(定向回请求连接) |
subagent_event |
direct+relay | 审计结果 或 {id,name,status,role,mode} |
子代理上下文查询应答 / 子代理进度 |
task_output |
direct+bridge | {taskId, output, status} |
后台任务输出查询应答 / task.output 桥接 |
capacity_awaken |
direct | {fromState, toState, tool?} |
能力状态迁移(design/14 §5,sessionId 设) |
proactive_message |
direct | {platform?, channelId?, channelType?, content, contentType?, sender?, timestamp?, messageId?} |
自主主动推送(仅 owner/admin 连接收到) |
device_update |
bridge | producer payload | IoT 设备状态更新(iot.device_update) |
task_done |
bridge | producer payload | 后台任务完成(task.done) |
automation_result |
bridge | producer payload | 自动化规则结果(automation.result) |
reminder |
bridge | producer payload | 提醒触发(reminder.trigger) |
mode_switch |
direct | 上行原样(任意) | 行为模式切换回显(ws.mode_switch) |
mood/tool_used/graph_update/… |
relay | ws.X 主题负载(ws.mood→mood,ws.tool_used→tool_used,ws.graph_update→graph_update,…) |
生产者在 bus 上发 ws.X 即规范化成类型 X 广播 |
error |
direct | {message} |
非法 JSON / 未知类型 / 语音·子代理·任务错误等 |
auth(旧 design/07 列表):当前不由网关直接 emit;若未来有ws.auth生产者主题,则按[relay]规则广播。本文档以代码为准。
message_persisted / channel_persisted / history 的 historyMessage 全字段(含 thinking 剥离规则):
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 后端生成消息 id(客户端去重键) |
sessionId |
string | 会话 id |
role |
string | user / assistant |
content |
string | 正文 |
mode |
string | omitempty |
mood |
string | omitempty |
image |
string | omitempty |
thinking |
string | omitempty,但在这些实时帧中恒为空(BroadcastPersisted/replayHistory 均置空;/messages owner/admin 端点才带) |
platform |
string | omitempty(web/telegram/weixin-ilink/qq-obv11...) |
deviceId |
string | omitempty |
senderName |
string | omitempty(第三方昵称,主人私聊为空) |
adapterId |
string | omitempty(仅 channel_persisted 常见,如 kook:3bef...) |
createdAt |
int64 | Unix 毫秒 |
事件类型分流(BroadcastPersisted):sessionId == "web" 或以 web: 前缀 → message_persisted(Web 聊天实时同步);其它(隔离 sess-xxx)→ channel_persisted(IM 适配器日志专用)。Web 聊天无 channel_persisted handler,天然忽略。
6.5 会话编排状态(design/15,data.sessionId 区分)
- 语义:同一会话
chat到达时若「她忙」(跑 loop/工具/子代理/长回复)→ 新消息不阻塞、入该会话 pending(上限默认 5);状态经上述session_busy/pending_ack/pending_drain/session_idle/session_interrupted/pending_full推给前端,供「正在做 xx… 有新消息稍后回」轻状态展示。 - 续接:忙完重开时新回合带上上一轮上下文(
tail默认 20 条),非清空重来。 - mode 取值:
idle/thinking/executing/subagent。 - REST 同步路径(§3.1)在忙时返回
202 queued/ 满409 chat_busy,与这些 WS 状态事件一致。
6.6 流式关联(turnId / seq)
turnId:runTurn每次生成turn-<hex>(crypto/rand),用于关联一轮流式突发;stream_end/stream_chunk(回退路径)带turnId。OnStream 结构化流路径(message.*等)由broadcastStream只填seq、不带turnId(OnStream bus 负载未携带 turn 身份)。seq:hub 级单调递增atomic.Uint64,保证一次突发有序,即便 loop 多 goroutine 触发。
6.7 WS 示例
握手 + 收 history + 发 chat 收流式(伪代码/curl-like):
# 1) 拿 token
TOKEN=$(curl -s -X POST http://<host>:8080/api/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"s3cret"}' | jq -r .data.token)
# 2) 连接(浏览器用 ws = new WebSocket('ws://host:8080/ws/chat?token='+TOKEN))
# 首帧:{"type":"history","sessionId":"web","data":{"messages":[...]}}
# 3) 上行 chat
# {"type":"chat","data":{"sessionId":"web","text":"你好","deviceId":"web:chrome:a1b2c3"}}
# 下行(流式回退):{"type":"stream_chunk","turnId":"turn-abc123","seq":1,"sessionId":"web","data":{"delta":"你"}}
# ... {"type":"stream_chunk",...} ...
# {"type":"stream_end","turnId":"turn-abc123","seq":8,"sessionId":"web","data":{"stopReason":"stop","steps":2}}
# 4) 上行 interrupt
# {"type":"interrupt","data":{"sessionId":"web"}}
# 下行:{"type":"session_interrupted","data":{"sessionId":"web","mode":"idle","dropped":2}}
# 5) 上行 voice_input
# {"type":"voice_input","data":{"audio":"<base64>","format":"wav","sessionId":"web"}}
# 下行(定向):{"type":"voice_output","data":{"audio":"<base64>","format":"wav","text":"你好","reply":"你好呀"}}
# 6) 上行 subagent_ask
# {"type":"subagent_ask","data":{"id":"sub-1","mode":"full"}}
# 下行:{"type":"subagent_event","data":{...审计结果...}}
# 7) 上行 task_output_query
# {"type":"task_output_query","data":{"taskId":"task-1"}}
# 下行:{"type":"task_output","data":{"taskId":"task-1","output":"...","status":"done"}}
7. 上传 / 媒体说明(重要检查结论)
- 无
/upload端点:网关未注册任何文件上传路由。图片通过POST /api/v1/chat或 WSchat的image字段以 base64 data: URI 或 http(s) URL 传入,入视觉真推理路径(vision/OCR 或主模型多模态,design/06)。文档导入走POST /api/v1/admin/rag的 JSONcontent文本。 - 无 REST 语音端点:语音只走 WS —— 上行
voice_input(base64 音频或已转写text),下行voice_output(合成音频 base64 + 识别文本 + 回复文本,定向回请求连接)。REST 侧没有语音流。 - 图片 + 文本并存:
image非空时 turn 变为「带图 + 可选文本」;text空 +image有 → 纯图像 turn。 - 敏感面:
image(base64)与bot_token/apiKey/newSecret等概不作为日志明文输出;wire 响应对 token/key 做掩码或不返回。
8. 附录
8.1 通用错误码 / 状态码
| 状态码 | code(示例) | 说明 |
|---|---|---|
| 200 | - | 成功 |
| 201 | - | 创建成功(personas/子会话/apikey/automations/llm/channels config/memories/rag) |
| 202 | - | 已接受(chat 排队 queued:true,queueDepth) |
| 204 | - | CORS 预检 OPTIONS 成功无内容 |
| 400 | bad_request / invalid_cron / invalid_rule / invalid_role / unsupported_action / invalid_param / invalid_provider / invalid_scope |
请求体/参数非法 |
| 401 | unauthorized |
认证缺失/失效/过期(REST Bearer 与 WS) |
| 403 | forbidden |
角色不足(viewer 读管理/主会话删除/写操作);主会话不可删 |
| 404 | not_found |
资源不存在 |
| 409 | chat_busy / duplicate / device_offline / needs_login |
冲突:队列满 / 人格已存在 / 设备离线 / 渠道未登录 |
| 500 | internal / chat_failed / sessions_failed / … |
服务端错误(含未注入依赖) |
| 501 | not_implemented |
后端确定未实现(新增用户/IoT 场景·规则/插件安装/RAG 删除)——诚实返回,不伪造成功 |
8.2 依赖注入与「未配置」行为
网关依赖(Deps)大多为 nil 容忍,未注入时端点要么回退、要么如实报错,绝不伪造成功:
History未注入:GET /sessions回退渠道会话视图;/messages、POST /sessions、DELETE /sessions/{id}、/channels/messages回501 not_implemented。RAG未注入:GET /admin/rag返回空文档列表;POST /admin/rag、/admin/rag/delete回501。Runner(Loop)未注入:/chat回500 chat_unavailable;WSchat直接广播stream_end{stopReason:"idle"}。Channels未注入:/admin/channels/config回500 channels_unavailable。LLM未注入:/admin/llm按.env合成单组;POST /admin/llm回500 llm_unavailable。Voice未注入:WSvoice_input回error {message:"voice unavailable"}。Scheduler(auditor)未注入:WSsubagent_ask回error {message:"subagent audit unavailable"}。Tasks未注入:WStask_output_query回error {message:"task output unavailable"}。AuditStore未注入:审计退化为进程内存(端点仍返回数据)。- 管理员只读端点若依赖缺失,大多返回
[],不 500。
8.3 审计动作清单(what 字段,落 audit_logs)
policy.set / persona.create / persona.update / user.create / apikey.issue / apikey.revoke / iot.control / iot.scenario / iot.rule / automation.create / automation.run / automation.delete / llm.addProvider / llm.test / channel.open / channel.close / channel.identity-test / channel.weixin.login.qr / channel.weixin.login.status / channel.weixin.login.persist / channel.platform.view / channel.friends / channel.friend.handle / channel.message.test / channel.save / channel.delete / plugin.install / plugin.uninstall / memory.add / memory.search / rag.index / rag.query / rag.delete / session.export / system.revoke_keys / system.rotate_jwt / system.backup / system.restore。
(what 后缀 _failed/_persist_failed/_reload_failed 等计入 error 分支。)
8.4 里程碑与未实现集合(诚实标注)
- 已真实实现:登录/健康检查/聊天(同步+流式)/会话(列表/新建/删除/消息分页)/人格/管理读(personas/sessions/memories/plugins/channels/iot/llm/users/automations/backthink/rag/observability×3)/管理写(人格/API key/设备控制/自动化/LLM 保存·测试/渠道连接·断开·身份测试/渠道配置·删除/KOOK 面板/微信扫码/记忆添加·检索/插件卸载/RAG 导入/系统设置 4 项)。
- 仍为后置 M10(返回
501 not_implemented,不伪造):新增用户、IoT 场景与规则、插件安装(需源码清单)、RAG 删除、主人档案保存、编辑环境变量/重启服务/检查更新、完整会话转录导出(当前导出身份元数据)。 - 语义/向量增强:
memory.search的semantic恒为false(未接向量器时诚实反映);RAGembedded反映是否配置向量增强;配置了增强必须真实走语义/向量路径(红线,非空壳)。 - API key 认证:
X-API-Key当前未被Authorize中间件校验(见 §0.1 注);key 的签发/吊销/列表已真实生效。
8.5 客户端接入快速索引
POST /api/v1/auth/login→{token, expiresAt, role}(恒owner)。- 之后每个 REST 请求带
Authorization: Bearer <token>;管理写需owner;管理读需owner|admin;GET /sessions、GET /personas任意已认证。 - 实时:
ws://<host>:8080/ws/chat?token=<token>;首帧history回放;上行chat/interrupt/voice_input/subagent_ask/task_output_query/mode_switch;下行见 §6.4。 - 身份判定永远来自后端 channel 层 actor 元数据 + 白名单(
configs/users/owner.yaml),客户端不要用提示词/文本判身份;thinking在公开回放/实时帧中已剥离,客户端不应依赖。 - 发送消息带
deviceId(推荐)以便端标识落库;platform用于 IM 渠道过滤。