kimi-code/docs/zh/reference/server-api.md
Haozhe 15da84606a
Some checks are pending
CI / build (push) Waiting to run
CI / test (1) (push) Waiting to run
CI / test (2) (push) Waiting to run
CI / test (3) (push) Waiting to run
CI / test (4) (push) Waiting to run
CI / test (5) (push) Waiting to run
CI / test-pi-tui (push) Waiting to run
CI / test-vscode-legacy (push) Waiting to run
CI / test-windows (push) Waiting to run
CI / lint (push) Waiting to run
CI / typecheck (push) Waiting to run
Nix Build / nix build .#kimi-code (push) Blocked by required conditions
Release / Release (push) Waiting to run
Release / Deploy docs (push) Blocked by required conditions
Release / Native release artifact (push) Blocked by required conditions
Release / Publish native release assets (push) Blocked by required conditions
Nix Build / Check flake.nix workspace sync (push) Waiting to run
feat(kap-server): add workspace-grouped sessions view and lifecycle events (#3114)
GET /api/v2/sessions gains view=by_workspace: one request returns every
workspace with a matching session, each carrying its first group.page_size
sessions under the requested sort plus the workspace's full matching total,
with group-level page_token pagination (40922 on condition drift). Groups
key on the alias-canonical workspace id, so legacy split buckets of one
physical directory merge into a single group, matching the v1 alias
semantics. meta.has_prompt filters sessions by prompt presence (the v1
exclude_empty equivalent) in both views. The flat view and v1 routes stay
byte-compatible.

The global WS stream now fans out event.session.archived (live and cold
paths; payload carries the session id and workspace_id) and
event.workspace.created/updated/deleted, published by the core
IWorkspaceService on every mutation path including the implicit
createOrTouch on session creation.

kimi-inspect consumes the grouped projection as a single-column
workspace/session tree in the chat view; the session pane merges into the
right dock as the Session tab. The server API reference (en + zh) documents
the new parameters, the grouped response, and the new events.
2026-08-20 13:45:38 +08:00

400 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 服务 API
`kimi web` 启动的本地服务暴露两组程序化接口REST API`/api/v1`,另有 `/api/v2/sessions`)和 WebSocket 事件流(`/api/v1/ws`)。本页是这两组接口的协议参考;服务的启动方式与命令行选项见 [kimi 命令](./kimi-command.md#kimi-web),端到端的上手流程见[本地服务与 API](../guides/server.md)。
每个端点的完整请求 / 响应 schema 以服务自描述的规范文档为准:`GET /openapi.json`OpenAPI`GET /asyncapi.json`AsyncAPI两者都需要鉴权。
::: warning 注意
本页描述的 REST 与 WebSocket API 为实验性特性:不保证接口稳定性,端点、字段与事件类型可能随版本随时更改。集成时请以当前版本服务的 `/openapi.json``/asyncapi.json` 为准。
:::
## 基础约定
### 地址
默认地址 `http://127.0.0.1:58627`;端口被占用时自动 +1 重试(至多 100 次),可用 `--port` / `--host` 修改。同一 home 目录可并存多个实例,运行中的实例登记在 `~/.kimi-code/server/instances/`
### 鉴权
除以下例外,所有 `/api/*` 路径(含 `/openapi.json``/asyncapi.json`)都要求 bearer token
- `OPTIONS` 预检请求
- `GET /api/v1/healthz`(探活)
- 静态 web 资源(非 `/api/` 路径)
携带方式REST 用 `Authorization: Bearer <token>` 请求头WebSocket 升级请求可用同一请求头,或子协议 `kimi-code.bearer.<token>`。token 的生成与轮换见[本地服务与 API鉴权](../guides/server.md#鉴权)。
鉴权失败返回 HTTP 401信封 `code``40101`。在非 loopback 绑定上,同一来源 60 秒内鉴权失败 10 次会被封禁 60 秒,期间一律返回 HTTP 429`code``42901`)。
### 响应信封
所有 JSON 响应统一包在信封里:
```json
{
"code": 0,
"msg": "success",
"data": {},
"request_id": "01JZX4A6E7M8V0R3Q0N2K2M5Q9"
}
```
- `code`:业务结果,`0` 表示成功;错误码分段见下文。
- `data`:成功时的业务数据。注意部分「错误」信封也携带非空 `data`——例如重复解决审批返回 `40902``data.resolved``false`——客户端应先判 `code` 再看 `data`
- `request_id`:本次请求的 ULID客户端可用 `X-Request-Id` 请求头指定,非法值会被服务端重新生成。
HTTP 状态码几乎总是 200业务结果以 `code` 为准。例外情况:
| 场景 | HTTP 状态 |
| --- | --- |
| 鉴权失败 / 触发限流 | 401 / 429 |
| 创建供应商、导入供应商目录成功 | 201 |
| 删除供应商成功 | 204 |
| 二进制与流式端点 | 支持时返回 206Range 分段)/ 304ETag 未变),各端点能力不同,详见「[二进制与流式端点](#二进制与流式端点)」 |
| `GET /api/v1/files/{file_id}` 下载错误 | 真实 404 / 500响应体仍为信封 |
其中 201 的响应体仍是标准信封(`code``0`),只是状态行遵循 REST 的资源创建习惯204 按 HTTP 语义没有响应体,删除成功以状态码本身为准。
### 错误码
错误码按段位分组:
| 段位 | 含义 | 示例 |
| --- | --- | --- |
| `0` | 成功 | |
| `400xx` | 请求参数错误 | `40001` 校验失败(`details` 逐字段说明)、`40003` 供应商由 OAuth 托管 |
| `401xx` | 鉴权与就绪状态 | `40101` 未授权、`40110` 未配置供应商、`40113` 模型未解析 |
| `404xx` | 资源不存在 | `40401` 会话、`40408` MCP 服务、`40409` 文件路径 |
| `409xx` | 状态冲突 | `40901` 会话忙、`40902` 审批已解决、`40922` 分页条件与 `page_token` 不符 |
| `410xx` | 资源已过期 | `41001` 审批超时、`41002` 提问超时、`41003` 临时文件过期 |
| `413xx` | 体积或边界超限 | `41302` 读取文件超 10 MB、`41304` 路径越出会话目录 |
| `429xx` | 限流 | `42901` 鉴权失败封禁、`42902` 文件监听数超限 |
| `500xx` | 服务端内部错误 | `50001` 未捕获异常、`50003` 持久化失败 |
| `6xxxx` / `7xxxx` / `8xxxx` | 工具运行时 / LLM 供应商 / MCP 透传错误,`msg` 保留上游原文 | |
### 分页
列表端点有两种分页风格:
- **游标式**`before_id` / `after_id`(互斥)加 `page_size`1100响应为 `{ items, has_more }`。用于会话列表、消息列表、转录等。
- **`page_token`**:不透明令牌(内部绑定了查询条件指纹),用于 `POST /api/v1/search``GET /api/v2/sessions`。翻页途中改变任何查询条件会使令牌失效v2 返回 `40922`search 返回 `40001``GET /api/v2/sessions` 另提供无状态的 `page` 页码模式作为替代。
## REST 端点
按资源分组列出端点。路径里的 `:{action}` 是动作后缀约定——对单个资源 POST 到 `路径:动作` 执行非 CRUD 操作(如会话的 `:fork``:archive`)。
### 服务与元信息
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/healthz` | 探活,免鉴权 |
| `GET /api/v1/meta` | 服务版本、能力集、`server_id`、实验开关等 |
| `POST /api/v1/shutdown` | 优雅退出(先回 200 再关闭);仅 loopback 绑定时挂载 |
### 登录与用量
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/auth` | 登录就绪状态快照 |
| `POST /api/v1/oauth/login` | 发起 OAuth device-code 登录流程 |
| `GET /api/v1/oauth/login` | 轮询登录流程状态 |
| `DELETE /api/v1/oauth/login` | 取消进行中的登录流程 |
| `POST /api/v1/oauth/logout` | 登出托管供应商 |
| `GET /api/v1/oauth/usage` | 查询套餐用量与限额 |
| `GET /api/v1/oauth/userinfo` | 查询账号资料 |
### 配置
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/config` | 读取全局配置(密钥字段脱敏) |
| `POST /api/v1/config` | 合并式更新配置,并广播 `event.config.changed` |
### 模型与供应商
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/models` | 列出已配置的模型别名 |
| `POST /api/v1/models/{model_id}:set_default` | 设置全局默认模型 |
| `GET /api/v1/providers` | 列出供应商 |
| `POST /api/v1/providers` | 创建供应商201 |
| `GET /api/v1/providers/{provider_id}` | 读取供应商(含已存密钥) |
| `PUT /api/v1/providers/{provider_id}` | 整体替换供应商配置 |
| `DELETE /api/v1/providers/{provider_id}` | 删除供应商204 |
| `POST /api/v1/providers/{provider_id}:refresh` | 刷新该供应商的模型元数据 |
| `POST /api/v1/providers:{action}` | 集合级动作:`refresh` / `refresh_oauth` / `import_catalog` / `import_registry` |
| `GET /api/v1/catalog/providers` | 浏览 models.dev 目录(服务端代理) |
| `GET /api/v1/catalog/providers/{catalog_id}` | 读取目录中单个条目 |
### 会话
| 方法与路径 | 说明 |
| --- | --- |
| `POST /api/v1/sessions` | 创建会话(需 `workspace_id``metadata.cwd` |
| `GET /api/v1/sessions` | 列出会话,游标分页,支持 `busy` / `archived_only` 等过滤 |
| `GET /api/v1/sessions/{session_id}` | 读取单个会话 |
| `GET /api/v1/sessions/{session_id}/profile` | 读取会话档案 |
| `POST /api/v1/sessions/{session_id}/profile` | 更新标题、元数据、agent 配置 |
| `POST /api/v1/sessions/{session_id}:{action}` | 会话动作:`fork` / `compact` / `undo` / `abort` / `btw` / `archive` / `restore` |
| `GET /api/v1/sessions/{session_id}/children` | 列出子会话 |
| `POST /api/v1/sessions/{session_id}/children` | 创建子会话fork 并打标) |
| `GET /api/v1/sessions/{session_id}/status` | 实时状态汇总 |
| `GET /api/v1/sessions/{session_id}/goal` | 当前目标快照(无则 `null` |
| `GET /api/v1/sessions/{session_id}/warnings` | 会话级告警 |
| `POST /api/v1/sessions/{session_id}/export` | 导出会话与诊断信息zip 流,不走信封) |
| `GET /api/v1/sessions/{session_id}/snapshot` | 客户端重建用全量快照(含 `as_of_seq``epoch` |
### 消息与转录
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/sessions/{session_id}/messages` | 消息分页(`before_id` / `after_id` / `role` |
| `GET /api/v1/sessions/{session_id}/messages/{message_id}` | 读取单条消息 |
| `GET /api/v1/sessions/{session_id}/transcript` | 转录按轮次分页(需 `agent_id`),全局状态不分页随响应返回 |
| `GET /api/v1/sessions/{session_id}/transcript/ops` | 转录批次补漏(`since_seq``complete: false` 时需全量刷新 |
| `GET /api/v1/sessions/{session_id}/transcript/user-messages` | 各轮次的用户输入,不分页 |
| `GET /api/v1/sessions/{session_id}/transcript/plan` | ExitPlanMode 计划内容、路径与审阅结果 |
### 提示词
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/sessions/{session_id}/prompts` | 进行中与排队中的提示词 |
| `POST /api/v1/sessions/{session_id}/prompts` | 提交提示词(内容块数组,可带模型 / 权限模式等覆盖) |
| `POST /api/v1/sessions/{session_id}/prompts:steer` | 把排队的提示词插入当前轮次 |
| `POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:abort` | 中止进行中的提示词 |
| `POST /api/v1/sessions/{session_id}/prompts/{prompt_id}:steer` | 插入单个排队提示词 |
### 审批与提问
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/sessions/{session_id}/approvals` | 列出审批请求(可按 `status=pending` 过滤) |
| `POST /api/v1/sessions/{session_id}/approvals/{approval_id}` | 答复审批 |
| `GET /api/v1/sessions/{session_id}/questions` | 列出提问 |
| `POST /api/v1/sessions/{session_id}/questions/{question_id}` | 回答提问 |
| `POST /api/v1/sessions/{session_id}/questions/{question_id}:dismiss` | 忽略提问 |
### 后台任务
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/sessions/{session_id}/tasks` | 列出后台任务 |
| `GET /api/v1/sessions/{session_id}/tasks/{task_id}` | 读取任务(可选输出预览) |
| `POST /api/v1/sessions/{session_id}/tasks/{task_id}:cancel` | 取消任务 |
### 技能、工具与 MCP
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/sessions/{session_id}/skills` | 会话级技能目录 |
| `GET /api/v1/workspaces/{workspace_id}/skills` | 无会话的工作区技能目录 |
| `POST /api/v1/sessions/{session_id}/skills/{skill_name}:activate` | 激活技能(开启一个轮次) |
| `GET /api/v1/tools` | 列出当前生效 agent 的工具 |
| `GET /api/v1/mcp/servers` | 列出 MCP 服务 |
| `POST /api/v1/mcp/servers/{mcp_server_id}:restart` | 重启 MCP 服务 |
### 终端
PTY 终端接口,仅 loopback 绑定时挂载。
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/sessions/{session_id}/terminals` | 列出终端 |
| `POST /api/v1/sessions/{session_id}/terminals` | 创建终端 |
| `GET /api/v1/sessions/{session_id}/terminals/{terminal_id}` | 读取终端(含回滚缓冲) |
| `POST /api/v1/sessions/{session_id}/terminals/{terminal_id}:close` | 关闭终端 |
### 工作区
| 方法与路径 | 说明 |
| --- | --- |
| `GET /api/v1/workspaces` | 列出已注册工作区 |
| `POST /api/v1/workspaces` | 注册工作区(按根路径幂等) |
| `PATCH /api/v1/workspaces/{workspace_id}` | 重命名 |
| `DELETE /api/v1/workspaces/{workspace_id}` | 注销(保留磁盘内容) |
| `GET /api/v1/workspaces/{workspace_id}/trust` | 读取信任状态 |
| `POST /api/v1/workspaces/{workspace_id}/trust` | 授予信任 |
| `POST /api/v1/workspaces/{workspace_id}/untrust` | 撤销信任 |
### 文件系统
会话内文件操作为 `POST /api/v1/sessions/{session_id}/fs:{action}`,动作包括 `list` / `read` / `list_many` / `stat` / `stat_many` / `mkdir` / `search` / `grep` / `git_status` / `diff` / `open` / `open-in` / `reveal`,请求体为 JSON。另有
| 方法与路径 | 说明 |
| --- | --- |
| `POST /api/v1/workspace/fs:search` | 无会话的工作区搜索body 携带工作区引用) |
| `GET /api/v1/sessions/{session_id}/fs/{path}:download` | 下载会话文件(二进制,见下文) |
| `GET /api/v1/fs:browse` | 列出本机目录(文件夹选择器用) |
| `GET /api/v1/fs:home` | 用户主目录与最近工作区 |
| `GET /api/v1/fs:content` | 读取本机任意文件原始字节(仅受 token 保护,谨慎暴露端口) |
| `POST /api/v1/fs:mkdir` | 按绝对路径创建目录 |
### 文件上传
| 方法与路径 | 说明 |
| --- | --- |
| `POST /api/v1/files` | multipart 上传(字段 `file`,可选 `name``expires_in_sec`),返回文件元信息 |
| `GET /api/v1/files/{file_id}` | 下载(二进制,错误用真实 HTTP 状态码) |
| `DELETE /api/v1/files/{file_id}` | 删除 |
### 全局搜索与其他
| 方法与路径 | 说明 |
| --- | --- |
| `POST /api/v1/search` | 跨会话全文搜索,`mode``terms`(默认)或 `literal`(精确子串),`page_token` 分页 |
| `GET /api/v1/connections` | 列出当前在线的 WebSocket 连接 |
| `GET /api/v2/sessions` | 新一代会话列表,见下节 |
| `POST /api/v2/sessions:archive` | 批量归档会话,见下节 |
| `POST /api/v2/sessions:restore` | 批量恢复已归档会话,见下节 |
| `/api/v1/debug/*` | 反射式调试 RPC`--debug-endpoints` 且 loopback 时挂载,不属于稳定协议 |
### `GET /api/v2/sessions`
面向列表页的新一代会话查询,筛选、排序、字段组都在查询参数里:
| 参数 | 说明 |
| --- | --- |
| `workspace.id` | 按工作区过滤,可重复 |
| `activity.status` | 按活动状态过滤:`running` / `approval` / `question` / `failed` / `idle`,可重复 |
| `meta.updated_after` | 只看该时间epoch 毫秒)之后更新过的会话 |
| `meta.updated_before` | 只看该时间epoch 毫秒)之前更新过的会话 |
| `meta.archived` | `true` / `false`(默认)/ `all` |
| `meta.has_prompt` | `true` 只保留有用户 prompt 的会话,`false` 只保留空会话(等价 `GET /api/v1/sessions``exclude_empty` |
| `view` | `flat`(默认)/ `by_workspace`,见下文 |
| `group.page_size` | `view=by_workspace` 时每个工作区返回的会话数1100默认 5使用 `id,archived` 投影时上限 10000未开分组视图时传入返回 `40001` |
| `sort` | `meta.updated_at_desc`(默认)/ `meta.updated_at_asc` / `meta.created_at_desc` |
| `include` | 逗号分隔的附加字段组;目前支持 `git`(分支与 PR 信息,按目录去重并缓存 60 秒) |
| `fields` | 逗号分隔的字段投影;目前仅支持 `id,archived`,每项裁剪为 `{ id, archived }`(用于全选匹配场景)。不可与 `include=git` 同传(`40001` |
| `page_size` | 1100默认 50使用 `id,archived` 投影时上限放宽至 10000。`view=by_workspace` 时按组计数 |
| `page_token` | 上一页返回的翻页令牌 |
| `page` | 无状态的 1 起始页码;与 `page_token` 互斥(同传返回 `40001` |
响应每项固定包含 `workspace``meta``activity` 三组,`include=git` 时附加 `git` 组;`fields=id,archived` 时仅返回 `{ id, archived }`。每页额外携带 `total`,即过滤后的集合大小。翻页令牌绑定首页查询条件(含投影),中途改条件返回 `40922``page` 模式是跳页用的无状态替代:每次请求都是独立快照,不签发令牌,`next_page_token` 恒为 `null`
`view=by_workspace` 时,同一份过滤、排序后的集合会重新投影为按工作区分组的形态,概览页因此可以用一次请求替代「每个工作区各一轮询」:
```json
{
"code": 0,
"msg": "success",
"data": {
"groups": [
{
"workspace": { "id": "wd_my-app_a1b2c3d4e5f6", "cwd": "/Users/dev/my-app" },
"sessions": [ { "id": "session_...", "workspace": { "id": "wd_my-app_a1b2c3d4e5f6", "cwd": "/Users/dev/my-app" }, "meta": { "title": "修复登录页", "last_prompt": "调整按钮间距", "created_at": 1787000000000, "updated_at": 1787000100000, "archived": false, "archived_at": null }, "activity": { "status": "idle" } } ],
"total": 42
}
],
"total": 7,
"has_more": true,
"next_page_token": "eyJ2IjoxLCJmIjoi..."
},
"request_id": "req_..."
}
```
每组携带该工作区按请求 `sort` 排序的前 `group.page_size` 条会话,以及该工作区匹配过滤条件的会话总数 `total`(用作「查看全部」入口)。只有至少有一条匹配会话的工作区才会出现;组间按组内首条会话的 sort key 排序,相同则按工作区 id。`page``page_token` 按组翻页(外层 `total` 为组数),指纹绑定规则相同:令牌同时覆盖 `view` 与分组参数,翻页途中变更同样返回 `40922`
### `POST /api/v2/sessions:archive` 与 `POST /api/v2/sessions:restore`
面向会话管理页的批量归档/恢复。请求体为 `{ "ids": ["session_..."] }`——非空、去重后不超过 5000 条。仍在线的会话走完整生命周期;未加载的冷会话直接改写磁盘上的元数据,不会被加载。
只有请求体校验失败才会让整个请求失败(`40001`);其余情况按条返回:`data.results` 保持输入顺序,每项为 `{ id, ok }``{ id, ok: false, error }`(不存在的 id 在自身条目里报 `40401`),并附 `succeeded` / `failed` 计数。
```json
{
"code": 0,
"msg": "success",
"data": {
"results": [
{ "id": "session_a", "ok": true },
{ "id": "session_b", "ok": false, "error": { "code": 40401, "message": "session session_b does not exist" } }
],
"succeeded": 1,
"failed": 1
},
"request_id": "req_..."
}
```
## WebSocket 协议
### 建立连接
唯一端点是 `ws://<host>:<port>/api/v1/ws`,升级请求即完成鉴权(方式见上文「鉴权」)。连接建立后服务端立即发送 `server_hello`
```json
{
"type": "server_hello",
"timestamp": "2026-01-01T00:00:00.000Z",
"payload": {
"ws_connection_id": "conn_01JZX4...",
"protocol_version": 2,
"max_event_buffer_size": 1000,
"capabilities": { "event_batching": false, "compression": false }
}
}
```
注意服务端不发送心跳,也不会主动断开空闲连接——保活与重连由客户端自己负责。
### 控制帧
客户端发送 JSON 帧 `{ "type", "id"?, "payload" }`;每个请求帧都会收到应答 `{ "type": "ack", "id", "code", "msg", "payload" }``code``0` 表示成功。
| 帧 | payload | 说明 |
| --- | --- | --- |
| `subscribe` | `{ session_ids, cursors?, agent_filter? }` | 订阅会话事件;带 `cursors`(每会话 `{seq, epoch}`)时回放错过的持久事件 |
| `unsubscribe` | `{ session_ids }` | 取消会话订阅 |
| `subscribe_v2` | `{ session_id, transcript, transcript_since? }` | 订阅转录流(唯一的转录订阅通道),`transcript` 按 agent 指定粒度 |
| `unsubscribe_v2` | `{ session_id, agent_ids? }` | 退订转录流;省略 `agent_ids` 表示整个会话 |
| `watch_fs_add` / `watch_fs_remove` | `{ session_id, paths, recursive? }` | 订阅 / 取消文件变更通知(`event.fs.changed` |
| `client_hello` | `{ client_id }` | 握手帧,其余字段为遗留兼容 |
### 事件
事件帧形状为 `{ "type", "seq", "epoch"?, "volatile"?, "offset"?, "session_id"?, "timestamp", "payload" }``type` 即事件类型。按投递范围分两类:
- **全局事件**:发送到每个已建立连接,无需订阅——`session.meta.updated``event.session.created``event.session.archived``event.session.work_changed``event.session.status_changed``event.workspace.*``event.config.*`
- **会话事件**:只发给订阅了该会话的连接,受 `agent_filter` 过滤。主要事件族:
| 事件族 | 主要事件 |
| --- | --- |
| 轮次 | `turn.started``turn.ended``turn.step.started` / `completed` / `interrupted` / `retrying` |
| 流式文本 | `assistant.delta``thinking.delta`(带 `offset` 用于对齐) |
| 工具调用 | `tool.call.started``tool.call.delta``tool.progress``tool.result` |
| 交互 | `event.approval.requested` / `resolved``event.question.requested` / `answered` / `dismissed` |
| subagent | `subagent.spawned` / `started` / `suspended` / `completed` / `failed` |
| 后台 | `task.started` / `terminated``shell.started` / `output` / `completed` |
| 其他 | `compaction.*``skill.activated``goal.updated``prompt.*``error``warning` |
有三个全局生命周期事件可以让跨工作区概览免掉逐工作区轮询。`event.session.archived` 在在线归档与冷归档两条路径上都会发出;其事件帧 `session_id` 是全局水位 `__global__`,真实会话 id 在 payload 里:`{ "type": "event.session.archived", "workspace_id": "wd_...", "sessionId": "session_..." }`payload 字段为 `workspace_id` / `sessionId`)。`event.workspace.created` / `updated` 携带完整工作区对象(`{ id, root, name, created_at, last_opened_at, session_count }`——会话创建触碰工作区时也会发 `updated``event.workspace.deleted` 携带 `{ "workspace_id", "root" }`。这些事件只覆盖本服务进程内的变更;其他进程(例如写同一 home 目录的 CLI的变更要等索引 reconcile约一分钟才可见因此概览客户端应保留低频兜底轮询。目前没有会话删除事件。
事件另分持久与易失两种:持久事件带严格递增的 `seq`,落盘并可回放;易失事件(各 `*.delta``tool.progress``shell.*` 等)标 `volatile: true`,不回放。消费易失文本流时用 `offset`(该轮次内的累计字符偏移)与本地已累积文本比对:小于本地长度说明是重复帧,大于说明有缺漏、需走快照恢复。
### 断线恢复
重连后在 `subscribe``cursors` 里带上每个会话最后应用事件的 `{seq, epoch}`服务端会回放缺口落后超过缓冲1000 条)或游标失效时改为收到 `resync_required`。此时调用 `GET /api/v1/sessions/{session_id}/snapshot` 拿全量快照(含 `as_of_seq``epoch`),再以新游标重新订阅。
### 转录协议
`subscribe_v2``transcript` 按 agent 指定粒度:`off` / `turn` / `block` / `delta`(键 `"*"` 表示默认粒度),粒度越高推送越细。粒度非 `off` 的 agent 走两帧推送:`transcript.reset`(基线快照,历史经 REST 分页回读)和 `transcript.ops`(增量批次,带每个 agent 连续递增的 `seq`);该 agent 的旧式事件在同一连接上被抑制,改由转录帧承载。断线时用 `transcript_since` 续传服务端批次日志无法覆盖缺口时REST 补漏返回 `complete: false`需全量刷新。REST 侧对应 `GET .../transcript`(按轮次分页)与 `GET .../transcript/ops?since_seq=`(批次补漏)。
## 二进制与流式端点
以下端点返回二进制流而非 JSON 载荷,各端点的 HTTP 能力并不相同:
| 方法与路径 | 说明 | Range 分段206 | ETag / 304 |
| --- | --- | --- | --- |
| `GET /api/v1/files/{file_id}` | 下载已上传文件 | 支持 | 不支持(会发送 `etag` 头,但不处理 `If-None-Match` |
| `GET /api/v1/sessions/{session_id}/fs/{path}:download` | 下载会话工作区文件 | 支持 | 支持 |
| `GET /api/v1/fs:content` | 读取本机任意文件(仅受 token 保护,谨慎暴露端口) | 支持 | 支持 |
| `POST /api/v1/sessions/{session_id}/export` | 导出会话与诊断信息zip 流) | 不支持 | 不支持 |
错误语义也不相同:`GET /api/v1/files/{file_id}` 对查找和存储失败返回真实 404 / 500 状态码(参数校验失败仍走 HTTP 200 信封),其余三个端点的所有失败都走标准[响应信封](#响应信封)——客户端在这三个端点上仍需检查信封中的 `code`
## 下一步
- [本地服务与 API](../guides/server.md) — 启动、鉴权与端到端调用流程
- [kimi 命令](./kimi-command.md#kimi-web) — `kimi web` 的全部命令行选项