# Cyrene v2 客户端 API 文档(用户侧) > **面向**: Web 前端 / 移动端原生客户端 / IM 桥接等**跨平台客户端开发者**。 > **维护铁律**: 每实现/变更一个 API(REST 端点、WS 事件、admin API)**立即更新本文件**;契约变更必须版本化,不破坏既有客户端。 > **状态**: gateway 已在 M8 落地核心端点;M9 前端按此开发。**`✅` = 已实现**,`🕐` = 规划。 --- ## 0. 基础约定 - **Base URL**: `http://:8080`(网关唯一对外端口;生产一般经反代/HTTPS)。 - **版本**: REST 全部挂在 `/api/v1/{resource}`;未来破坏性变更升 v2。 - **认证**: `Authorization: Bearer `(RBAC:`owner`/`admin`/`viewer`);管理端另支持 `X-API-Key`。 - **响应统一**: `{"data": ...}` / `{"error": {"code": , "message": }}`;状态码 200/201/204/400/401/403/404/429/500。 - **错误码**:业务错误带 `code` 字符串(如 `unauthorized` / `not_found`),便于客户端分支。 --- ## 1. REST 端点(公开) | # | 端点 | 方法 | 认证 | 状态 | 说明 | |---|------|------|------|:---:|------| | 1 | `/api/v1/auth/login` | POST | 否 | ✅ | `{username,password}` → `{data:{token,jwt}}`(24h,owner) | | 2 | `/api/v1/health` | GET | 否 | ✅ | 健康检查 `{data:{status:"ok"}}` | | 3 | `/api/v1/chat` | POST | JWT | ✅ | `{text,image?,stream?}` → `{data:{reply}}`;`stream:true` 时经 WS 触发流式。`image` 为 base64 data: URI 或 http(s) URL,入视觉真推理(design/06)。**会话编排(design/15)**:非流式且该会话「正忙」时**不阻塞**,返回 `202` `{data:{queued:true,queueDepth}}`(稍后按上下文续接);待处理队列满时返回 `409` `{error:{code:"chat_busy"}}` | | 4 | `/api/v1/sessions` | GET | JWT | ✅ | `{data:[session...]}`。**会话持久化(design/16)**:注入 HistoryStore 后返回持久化会话元数据 `{data:[{id,type("main"|"sub"),title?,createdAt}]}`(主会话 `id="web"` 恒在首位,次级 `web:`);未注入时退化回渠道会话列表 | | 5 | `/api/v1/sessions` | POST | JWT(owner/admin) | ✅ | **新建次级会话** → `201 {data:{sessionId}}`(`sessionId="web:"`);未注入 HistoryStore 回 `501 not_implemented` | | 6 | `/api/v1/sessions/{id}/messages` | GET | JWT(owner/admin) | ✅ | **热分页拉历史**:`?limit=&before=` 默认 `limit=50`(上限 200)、`before`=上一页最旧 `createdAt`(毫秒),按时间倒序 `{data:[{id,sessionId,role,content,mode?,mood?,image?,thinking?,createdAt}]}`;非法 `limit`/`before` 回 `400`;未注入 HistoryStore 回 `501` | | 7 | `/api/v1/sessions/{id}` | DELETE | JWT(owner/admin) | ✅ | **删除次级会话及其消息** → `{data:{deleted:}}`;主会话不可删回 `403 forbidden`;未注入 HistoryStore 回 `501` | | 8 | `/api/v1/personas` | GET | JWT | ✅ | `{data:[persona...]}` | ## 2. REST 端点(管理,`/api/v1/admin/*`) > 仅供管理面板与授权客户端;RBAC:`owner`/`admin`/`viewer`(viewer 只读、写 403);**写操作全程审计**。 | # | 端点 | 方法 | 状态 | 说明 | |---|------|------|:---:|------| | 1 | `/api/v1/admin/personas` | GET | ✅ | 人格列表 | | 2 | `/api/v1/admin/sessions` | GET | ✅ | 会话列表 | | 3 | `/api/v1/admin/memories` | GET | ✅ | 记忆列表(检索/编辑随 M10 扩展) | | 4 | `/api/v1/admin/plugins` | GET | ✅ | 插件/能力列表(含 MCP/渠道) | | 5 | `/api/v1/admin/plugins/policy` | POST | ✅ | 写工具策略(owner-only,审计) | | 6 | `/api/v1/admin/channels` | GET | ✅ | 渠道列表/健康 | | 7 | `/api/v1/admin/iot` | GET | ✅ | IoT 设备列表 | | 8 | `/api/v1/admin/users` | GET/POST | ✅(GET) | 用户/角色/API key(GET 已实现;POST 创建用户/签发 key 随 M10 落地) | | 9 | `/api/v1/admin/llm` | GET/PUT | ✅(GET) | LLM 模型/路由/降级(GET 已实现,APIKey 脱敏;PUT 热重载 M10) | | 10 | `/api/v1/admin/automations` | GET/POST | ✅(GET) | 自动化与提醒(GET 已实现;POST 创建 M10) | | 11 | `/api/v1/admin/backthink` | GET | ✅ | 后台思考记录 | | 12 | `/api/v1/admin/rag` | GET | ✅ | 知识库文档列表(分块/嵌入状态);带 `?query=` 时并入检索结果 | | 13 | `/api/v1/admin/rag/query` | POST | ✅ | 知识库检索(真实检索;配置向量增强走语义路径) | | 14 | `/api/v1/admin/rag/delete` | POST | 🕐(写) | 删除知识库文档;写操作 M10,当前返回 `501 not_implemented` | | 15 | `/api/v1/admin/observability/metrics` | GET | ✅ | 网关请求指标(总数/QPS/延迟/方法·路径·状态计数/LLM·子代理计数) | | 16 | `/api/v1/admin/observability/trace` | GET | ✅ | 最近 trace span(环形缓冲,默认 512 条) | | 17 | `/api/v1/admin/observability/audit` | GET | ✅ | 审计记录(who/what/when/result,SQLite 持久化) | > **M10 前端契约补充(管理面板)**:`GET /api/v1/admin/users` · `GET /api/v1/admin/llm` · `GET /api/v1/admin/automations` · `GET /api/v1/admin/backthink` · `GET /api/v1/admin/rag` · `POST /api/v1/admin/rag/query` · `GET /api/v1/admin/observability/metrics` · `GET /api/v1/admin/observability/trace` · `GET /api/v1/admin/observability/audit` 已实现,返回真实数据;`GET /api/v1/admin/logs` · `GET /api/v1/admin/system` 仍🕐未实现(前端按契约封装、后端 404 时显示"待实现"壳,不报错)。**写操作已补齐**(见下方「管理面板写操作契约」):人格/API key/设备控制/自动化/LLM 测试/渠道连接与识别测试/记忆检索/会话导出/插件卸载**真实持久化或真实生效**;仍为后置 M10:**新增用户**、**IoT 场景与规则**、**插件安装(需源码清单)**、**主人档案保存**、**系统设置**、**知识库上传/删除**——后端如实返回 `501 not_implemented`,不伪造成功。 **管理面板写操作契约**(`POST/PUT/DELETE /api/v1/admin/*`,均 **owner-only**,读为 owner/admin;写操作经 audit 落 `audit_logs`;成功 `{data}` / 失败 `{error:{code,message}}`): - `POST /admin/personas` body `{id,name,role,systemPrompt?,characterRef?,modes?/mode?,memoryNamespace?}` → `201 {data:{id,name,character,isDefault,mode,memoryNamespace}}`(注册进 persona registry + 持久化到 `configs/personas/.yaml`);id 已存在回 `409 duplicate`。 - `PUT /admin/personas/{id}` body 同上(不含 id)→ `200 {data:PersonaSummary}`;不存在回 `404`。 - `POST /admin/users` → `501 not_implemented`(无持久用户表,当前为单 admin + APIKey 现状)。 - `POST /admin/users/apikey` body `{role:"owner|admin|viewer"}` → `201 {data:{id:<掩码>, key:<明文·仅此一次>, role}}`。`key` 仅此响应出现一次。 - `DELETE /admin/users/apikey/{id}`(`id`=列表掩码)→ `200 {data:{id,revoked:true}}`;未命中 `404`。 - `POST /admin/iot/{id}/control` body `{action,params?}` → `200 {data:{id,name,type,state,online}}`(真实下发到 IoT provider);`404` 设备不存在 / `400` 动作不支持或参数错 / `409` 设备离线。 - `POST /admin/iot/scenarios`、`POST /admin/iot/rules` → `501 not_implemented`(IoT 无场景/规则存储)。 - `POST /admin/automations` body `{name,kind:"cron|event",expression}` → `201 {data:{id,name,kind}}`(真实注册规则);无效 cron/规则回 `400 invalid_cron|invalid_rule`。 - `POST /admin/automations/{id}/run` → `200 {data:{id,output}}`(手动触发);`404`。 - `DELETE /admin/automations/{id}` → `200 {data:{id,deleted:true}}`;`404`。 - `POST /admin/llm` body `{name,model,baseUrl?,multimodal?,fallback?}` → `201 {data:{provider,temporary:true}}`。**临时**,进程生命周期,不入 config、不绑定模型路由。 - `POST /admin/llm/test` body `{provider?,baseUrl,model,apiKey?,prompt?}` → `200 {data:{ok,provider,model,reply,tokens}}`(**真实模型调用**);无效/失败回 `400 invalid_provider` / `500 llm_test_failed`。API key 仅本次请求内使用,不落库、不进 wire。 - `POST /admin/channels/open`、`POST /admin/channels/close` body `{name}` → `200 {data:{name,connected}}`(真实 Connect/Disconnect);`404`(未注册渠道)/`500`(凭据缺失等连接失败)。 - `POST /admin/channels/test` body `{platform?,accountId,isGroup}` → `200 {data:{role,owner,isGroup,env,verdict}}`。**结构性判定**:channel 层 actor 元数据 × `configs/users/owner.yaml` 白名单,绝不靠提示词。 - `POST /admin/plugins/install` → `501 not_implemented`(需插件源码清单)。 - `POST /admin/plugins/uninstall` body `{name}` → `200 {data:{name,uninstalled:true}}`(真实从 registry 移除);`404`。 - `POST /admin/memories/search` body `{query,personaId?,topK?}` → `200 {data:{query,personaId,semantic,hits:[{id,scope,text,score}]}}`。真实检索(keyword×衰减);**语义/向量路径**需主会话注入 vector index(`semantic` 当前恒 `false`,诚实反映未接向量器)。 - `GET /admin/sessions/{id}/export` → `200 {data:{session:{platform,channelId,sessionId,actorRole,owner,isGroup,memWritable}}}`;`404`。当前导出会话身份元数据(完整消息转录由 M10 提供)。 **知识库 RAG 契约**: - `GET /admin/rag` → `{"data":{"documents":[{id,name,desc?,chunks,embedded}],"hits"?:[{docId,docName,chapter?,text,score,semantic}]}}`。`hits` 仅在携带 `?query=` 时出现;`embedded` 反映部署是否配置向量增强(未配置为待嵌入)。 - `POST /admin/rag/query` body `{"query":"..."}` → `{"data":{"query":"...","hits":[{docId,docName,chapter?,text,score,semantic}]}}`。检索走项目索引关键词+行上下文;配置 pgvector/向量增强时并入语义/向量命中(`semantic:true`)。 - `POST /admin/rag/delete` body `{"id":"..."}` → 写操作 M10,返回 `501` `{"error":{"code":"not_implemented",...}}`。 **可观测性契约**(`GET /api/v1/admin/observability/*`,均需 owner/admin,`?limit=N` 缺省 50、上限 500): - `GET /metrics` → `{"data":{"totalRequests","qps","avgLatencyMs","maxLatencyMs","perMethod{}","perPath{}","statusCounts{}","llmCalls","subagentEvents"}}`。`qps` 为近 1 秒窗口计数;`llmCalls`/`subagentEvents` 由实际观测到的 trace span 按 `kind` 累计(Tracer 未注入时为 0)。**Token/成本后端不统计、不返回**(不伪造指标)。 - `GET /trace?limit=100` → `{"data":[{id,parentId?,name,kind,status,startedAt,endedAt?,durationNs,props?,events?]}`。`kind` ∈ `llm_call|tool_call|orchestration|state_transition|subagent|memory|guardrail`;`durationNs` 为纳秒;`events` 为 `[{time,type,props?}]`。 - `GET /audit?limit=100` → `{"data":[{id,who,what,when,result}]}`。`id` 为整数自增;`when` 为 RFC3339;来源为 SQLite `audit_logs` 表(未注入 AuditStore 时退化为进程内存)。 ## 3. WebSocket `/ws/chat`(实时) - **握手**: `ws://:8080/ws/chat?token=` 或 `Authorization: Bearer`;未认证在握手前回 `401` JSON。 - **下行(服务端→客户端)**: 经 bus 广播 `{type:"", data:..., turnId?, seq?, sessionId?}`(`turnId`/`seq` 为流式关联字段,见 §3.1;`sessionId` 为**会话持久化(design/16)新增**,能确定会话的事件带上,供客户端按激活会话过滤)——`message.start/delta/end` / `thinking.start/delta/end` / `tool_call.start/delta/end` / `tool_execution.update`(结构化流,design/04 §1.1),以及向后兼容的 `stream_chunk` / `stream_end` / `thinking`(思维链) / `mood` / `reminder` / `device_update` / `tool_used`(工具回显) / `subagent_event` / `graph_update` / **`message_persisted`**(会话持久化增量,见 §3.3)。**会话编排状态(design/15,均为广播,`data.sessionId` 区分会话)**:`session_busy` / `pending_ack` / `pending_drain` / `session_idle` / `session_interrupted` / `pending_full`(见 §3.2)。语音回复走 `voice_output`(定向回请求连接,非广播)。 > **`history` 回放事件(design/16,新增)**:WS 握手升级成功后、该连接下一帧即收到 `{type:"history", sessionId:"web", data:{messages:[{id,sessionId,role,content,mode?,mood?,image?,createdAt}]}}`——回放主会话最新 50 条(`thinking` 已剥离,不泄给非 owner;默认主会话,次级会话由客户端按激活会话走 `GET /sessions/{id}/messages` 拉取)。客户端据此在重连/新开连接后补全上下文。 ### 3.1 结构化流事件(`message.*` / `thinking.*` / `tool_call.*` / `tool_execution.*`) - **事件壳**:`{type, turnId?, seq?, data}`, `data` 为**类型化结构**(非 `any`)。`seq` 为 hub 级单调递增序号,供客户端将一次突发的流式事件排序关联。 - **`message.start`**(`{index}`) / **`message.delta`**(`{delta, index}`) / **`message.end`**(`{index}`):正文段。`delta` 为正文 token 增量;客户端对 `message.delta` 累加到当前消息正文;`message.start/end` 划定正文段边界,**不**结束整轮(结束由 `stream_end` 负责)。 - **`thinking.start`**(`{index}`) / **`thinking.delta`**(`{delta, index}`) / **`thinking.end`**(`{index}`):思维链段(前端折叠块)。`thinking.delta` 累加增量。 - **`tool_call.start`**(`{id, index}`) / **`tool_call.delta`**(`{id, index, delta}`) / **`tool_call.end`**(`{id, index}`):工具调用中间态;`tool_call.delta` 携带参数增量(如 Anthropic `input_json_delta`)。 - **`tool_execution.update`**(`{id, index, delta}`):工具执行进度/局部输出(保留,工具循环 §1.2 后置实现)。 - **向后兼容**:未映射/无法确定的事件仍走 `stream_chunk` + `stream_end`(非流式回退路径)。 - **上行(客户端→服务端)**: `mode_switch` / `chat`(跑 loop 并广播流式) / `voice_input` / `subagent_ask` / `task_output_query` / `interrupt`。 - **`interrupt`**(上行,客户端→服务端):`{sessionId}` → 会话编排器中断(design/15 §3)——取消该会话当前在跑的任务(context cancel)、清空其待处理队列、回 `session_interrupted`;用户说「停下/算了/换个话题」时发。命中空闲会话为 no-op。 - **`voice_input`**(上行,客户端→服务端):`_{audio?, format?, text?, sessionId?}`。`audio` 为 base64(或 data: URI)音频,`format` 为其编码(`wav`/`mp3`/`ogg`);`text` 为已转写文本时跳过 STT。服务端走 STT→agent 聊天→TTS(design/06 §1),回复经 `voice_output` 定向回该连接。无注入语音服务时回 `error` 帧(`voice unavailable`)。 - **`voice_output`**(下行,服务端→客户端):`{audio, format, text, reply}`。`audio` 为 base64 合成音频,`format` 为其编码,`text` 为识别文本,`reply` 为助手回复文本。 - **向后兼容**:未映射/无法确定的事件仍走 `stream_chunk` + `stream_end`(非流式回退路径)。 --- ### 3.2 会话编排状态事件(design/15) - **触发**:同一会话内,`chat` 到达时若「她忙」(正在跑 loop/工具循环/子代理/长回复)→ 新消息**不阻塞**、入该会话 pending;这一层的状态经以下事件推给前端,供「正在做 xx… 有新消息稍后回」轻状态展示。 - **`session_busy`**:会话进入忙碌态(`{sessionId, mode}`,`mode` ∈ `idle|thinking|executing|subagent`)。 - **`pending_ack`**:忙时新消息已入队(`{sessionId, mode, queueDepth}`)——「收到,稍后回」。 - **`pending_drain`**:忙完开始消费下一待处理消息(`{sessionId, mode, queueDepth}`)。 - **`session_idle`**:会话回到空闲、队列清空(`{sessionId, mode}`)。 - **`session_interrupted`**:用户 `interrupt` 中断,当前任务取消、pending 清空(`{sessionId, mode, dropped}`)。 - **`pending_full`**:单会话 pending 达上限(默认 5)拒绝入队(`{sessionId, mode, max}`)——防无限积压(design/15 §4)。 - **上下文续接**:忙完重开时新回合**带上一轮上下文**(`{mode, queueDepth}` 之外的续接由服务端完成),非清空重来(design/15 §2.2)。 ### 3.3 消息持久化增量事件(`message_persisted`,design/16) - **触发**:每一条 user / assistant 消息**落库后**由编排层广播(`persistUserMessages` 在提交时写用户消息、`persistAssistantReply` 在回合落定时写助手消息)。 - **载荷**:`{type:"message_persisted", sessionId, data:{message:{id, sessionId, role, content, mode?, mood?, image?, createdAt}}}`——`role` ∈ `user|assistant`;`id` 为**后端生成的消息 id**(客户端据此去重);`thinking` 已剥离(design/16 §6)。 - **客户端处理**:仅当 `data.message.sessionId === activeSessionId`(激活会话)时处理;按 `id` upsert——本端已预建的**同 role 乐观气泡**(本地 `uid` 无后端 `id`)被**归一**(`uid` → 后端 `id`),异端无本地气泡则直接追加,两者最终收敛为**同一条 `id` 相同的消息**,「A 发消息、A 与 B 都无需刷新且各出现一次」。 ## 4. 客户端接入指引 - **认证**: 先 `POST /api/v1/auth/login` 取 JWT → WS 握手(`?token=`)注入。 - **版本**: 客户端请求头声明 `X-API-Version: 1`。 - **离线**: PWA / 原生端建议缓存 `sessions`/`personas` 列表。 ## 5. 变更记录 - `v0.11`(跨端实时同步,design/16 §4.2):WS 下行新增 **`message_persisted`** 事件——每条 user/assistant 消息落库后广播 `{type, sessionId, data:{message:{id,sessionId,role,content,mode?,mood?,image?,createdAt}}}`(`id` 为后端生成,`thinking` 已剥离)。客户端按 `sessionId === activeSessionId` 过滤 + 按 `id` upsert(含本端乐观气泡 `uid`→后端 `id` 归一)去重,实现「A 发消息、A 与 B 都无需刷新且各出现一次」。后端 `history.Store` 新增 `AppendFull` 供编排层取含 `id` 的落库消息。 - `v0.10`(会话消息持久化 — WS 契约,design/16,后端-2):WS 下行帧新增可选 `sessionId` 字段(能确定会话的事件带上);新增 **`history` 回放事件**——WS 握手成功即回放主会话最新 50 条(`{type:"history", sessionId:"web", data:{messages:[...]}}`,`thinking` 已剥离)。`stream_end`/`stream_chunk`/会话编排状态帧(`session_busy` 等)在能确定 session 时带 `sessionId`;`message.delta` 等结构化流帧因 `model.StreamEvent` 无 session 上下文不带(即"若知道就带上"边界)。 - `v0.9`(会话消息持久化 + 主/次级会话,design/16,后端-1):新增 `POST /api/v1/sessions`(新建次级会话)、`GET /api/v1/sessions/{id}/messages?limit=&before=`(游标热分页拉历史,时间倒序)、`DELETE /api/v1/sessions/{id}`(删次级会话及其消息);`GET /api/v1/sessions` 在注入 `HistoryStore` 后改返回持久化会话元数据数组(主 + 次级,`type` 区分),未注入时保持原渠道会话列表向后兼容。读消息/写会话为 owner/admin(用户侧私有历史,thinking 不达 viewer);会话元数据列表保持任意认证角色可读。 - `v0.8`(管理面板写操作 CRUD):补齐管理面板**写端点**(见 §2「管理面板写操作契约」,均 owner-only + 审计)——`POST/PUT /admin/personas`(真实注册 + 持久化 `configs/personas/.yaml`)、`POST /admin/users/apikey` + `DELETE /admin/users/apikey/{id}`(真实签发/撤销)、`POST /admin/iot/{id}/control`(真实设备控制)、`POST /admin/automations` + `/run` + `DELETE`(真实注册/触发/移除规则)、`POST /admin/llm`(临时记录)+ `/admin/llm/test`(真实模型调用)、`POST /admin/channels/{open,close,test}`(真实连接/断开 + 结构性身份识别)、`POST /admin/plugins/uninstall`(真实 registry 移除)、`POST /admin/memories/search`(真实检索)、`GET /admin/sessions/{id}/export`。无存储的写(新增用户 / IoT 场景·规则 / 插件安装)如实返回 `501 not_implemented`。 - `v0.7`(会话消息编排 design/15):`POST /api/v1/chat` 非流式且会话「正忙」时改回 `202 {data:{queued:true,queueDepth}}`(原同步 `{data:{reply}}` 仍为空闲时返回);pending 满回 `409 chat_busy`。WS 上行新增 `interrupt`(`{sessionId}`,中断当前任务+清 pending);WS 下行新增会话编排状态事件 `session_busy` / `pending_ack` / `pending_drain` / `session_idle` / `session_interrupted` / `pending_full`(见 §3.2)。既有 `stream_chunk`/`stream_end` 与结构化流事件不变,契约向后兼容。 - `v0.6`(可观测性):新增 `GET /api/v1/admin/observability/{metrics,trace,audit}`(owner/admin)——metrics 返回网关实时请求计数/QPS/延迟/方法·路径·状态分布 + LLM/子代理 span 计数;trace 返回 gateway 环形缓冲里的最近 span(经 TraceSink 对接 runtime tracer,映射为 `AdminTraceSpan`);audit 返回持久化审计(SQLite `audit_logs`,未注入时退化为内存)。`llmCalls`/`subagentEvents` 由实际观测 span 累计;Token/成本后端不统计、不返回(不伪造指标)。 - `v0.5`(批次B·视觉真推理 + 语音端):`POST /api/v1/chat` 增 `image` 字段(base64 data: URI 或 URL),入 loop 视觉路由(vision/OCR 工具或主模型多模态,design/06 §0「先看图再对话」);WS 上行 `voice_input` 接 STT→聊天→TTS 管道,下行新增 `voice_output`(定向回请求连接,含合成音频 + 识别文本 + 回复文本)。 - `v0.4`(结构化流):WS 下行新增 `message.*` / `thinking.*` / `tool_call.*` / `tool_execution.update` 结构化流事件(event shell 补 `turnId`/`seq`,`data` 类型化),对齐 OpenClaw AssistantMessageEvent;保留 `stream_chunk`/`stream_end` 向后兼容。 - `v0.3`(M10):管理面板 `/admin` 落地——按 §2 契约封装 admin 只读端点(personas/sessions/memories/plugins/channels/iot)+ owner-only `plugins/policy` 写;其余 M10 端点以契约路径封装、后端未实现时前端显式"待实现"壳。 - `v0.2`(M8):gateway 落地——登录/health/chat/sessions/personas + admin 只读端点 + `plugins/policy` 写 + WS `/ws/chat`(JWT + bus 广播)。 - `v0.1`(契约先行):骨架 + 端点登记表。