kimi-code 深度掌握系列文章-通信协议设计Protocol RPC(十四)
1. Protocol 的定位
1.1 系统的"共同语言"
packages/protocol 是 kimi-code 系统中定义所有数据契约的核心包。它不做任何业务逻辑,不导入引擎、SDK 或 UI 框架——它的唯一职责是定义 引擎、SDK、UI 之间的"共同语言"。
这个包定义了系统的三大通信通道的数据形状:
- REST API 的请求/响应结构:定义 kap-server 暴露给 Web UI、VS Code 扩展等客户端的所有 API 端点参数和返回值
- WebSocket 的控制帧和事件帧:定义双向实时通道中所有消息的结构,从握手到订阅、终端输入到断线恢复
- 引擎内部事件类型:定义 Agent 在运行时发出的所有事件——从 turn 生命周期到 token 用量、tool 调用、subagent 生成
Protocol 包的唯一外部依赖是 Zod。它不导入引擎、不导入 Node.js、不导入任何 UI 框架。(和 Transcript 包一样的设计原则。)
1.2 包的结构
packages/protocol/src/index.ts 中导出了以下模块的完整结构:
export * from './envelope'; // 请求/响应信封
export * from './error-codes'; // 错误代码体系
export * from './pagination'; // 分页支持
export * from './time'; // 时间格式
export * from './request-id'; // 请求追踪
export * from './events'; // 引擎事件(60+ 种事件类型)
export * from './display'; // 工具展示数据
export * from './ws-control'; // WebSocket 控制帧
export * from './asyncapi'; // 异步 API// 业务领域模型
export * from './session'; // 会话
export * from './workspace'; // 工作空间
export * from './message'; // 消息
export * from './approval'; // 审批
export * from './question'; // 问题
export * from './tool'; // 工具
export * from './skill'; // 技能
export * from './task'; // 任务
export * from './fs'; // 文件系统
export * from './file'; // 文件
export * from './modelCatalog'; // 模型目录// REST API 端点
export * from './rest/meta'; // GET /meta
export * from './rest/auth'; // 认证
export * from './rest/session'; // 会话 CRUD
export * from './rest/message'; // 消息
export * from './rest/prompt'; // 提示词
export * from './rest/approval'; // 审批
export * from './rest/question'; // 问题
export * from './rest/tool'; // 工具
export * from './rest/skill'; // 技能
export * from './rest/task'; // 任务
export * from './rest/file'; // 文件
export * from './rest/config'; // 配置
export * from './rest/terminal'; // 终端
export * from './rest/connection';// 连接
export * from './rest/guiStore'; // GUI Store
总共覆盖了 session、workspace、message、approval、question、tool、skill、task、fs、file、modelCatalog、config、terminal 等 13 大领域模型和对应的 REST 接口契约。加上 WebSocket 协议和 60+ 种引擎事件类型,构成了整个系统的通信数据契约全集。
2. REST API 设计
2.1 kap-server:基于 Fastify 的 REST 服务
kimi-code 的 REST 层由 packages/kap-server 实现,基于 Fastify 框架构建。路由通过 Fastify 的插件机制注册,与 protocol 包中的 Zod schema 集成,实现了编译时类型安全 + 运行时输入验证。
kap-server 的路由注册分为两类:
- 标准 API 路由:覆盖业务领域的 REST 端点,由
registerApiV1Routes统一注册在/api/v1前缀下 - 调试路由:仅在
--debug-endpoints且绑定本地回环地址时加载,提供/api/v1/debug/*的运行时反射能力
所有路由都经过全局中间件链:Bearer 认证 → Origin 校验 → 安全头部注入 → Schema 验证 → 速率限制。
2.2 请求/响应信封(Envelope)模式
所有 REST 响应都包裹在统一信封中,不直接返回裸数据:
import { z } from 'zod';export const envelopeSchema = <T extends z.ZodTypeAny>(data: T) =>z.object({code: z.number().int(), // 0 = 成功,非 0 = 错误码msg: z.string(), // 人类可读的消息data: data.nullable(), // 业务数据,错误时为 nullrequest_id: z.string(), // 请求追踪 IDdetails: z.unknown().optional(), // 可选的错误详情stack: z.string().optional(), // 可选的错误堆栈});// 成功响应构造器
export function okEnvelope<T>(data: T, requestId: string): Envelope<T> {return { code: 0, msg: 'success', data, request_id: requestId };
}// 错误响应构造器
export function errEnvelope(code: number, msg: string, requestId: string, stack?: string
): Envelope<null> {return { code, msg, data: null, request_id: requestId, stack };
}
信封模式的核心价值:
- 统一的请求追踪:每个请求都有一个
request_id,贯穿日志系统和调试面板 - 一致的类型结构:客户端不需要区分成功和错误响应的顶层结构——总是
{ code, msg, data, request_id } - 渐进式错误信息:
details携带校验失败的具体字段路径,stack在调试模式下提供完整的调用栈
2.3 错误代码体系
kimi-code 采用 整数命名空间体系 的错误代码设计:
- 0:成功
- 4xxxx:客户端错误(HTTP-4xx 类比)——如
VALIDATION_FAILED: 40001、REQUEST_MALFORMED: 40002、SESSION_NOT_FOUND: 40401 - 5xxxx:daemon 内部错误——如
INTERNAL_ERROR: 50001、PERSISTENCE_FAILURE: 50003 - 6xxxx:工具运行时错误——如
TOOL_EXECUTION_FAILED: 60001、TOOL_NOT_AVAILABLE: 60002 - 7xxxx:LLM Provider 透传错误——
msg字段保留上游原始错误文本 - 8xxxx:MCP Server 透传错误——
msg字段保留上游原始错误文本
每个错误码都有对应的 ErrorCodeReason 字符串(如 "session.not_found"),用于结构化日志和客户端语义化判断。整个体系覆盖了 50+ 种细分错误场景,从认证、会话、文件系统到 permissions、MCP、goal、compaction 等各个子系统。
2.4 分页支持
列表类接口(消息列表、任务列表等)使用 基于游标的分页:
// 请求:基于游标查询
export const cursorQuerySchema = z.object({before_id: z.string().min(1).optional(), // 向前翻页after_id: z.string().min(1).optional(), // 向后翻页page_size: z.number().int().min(1).max(100).optional(),
}).superRefine((value, ctx) => {if (value.before_id !== undefined && value.after_id !== undefined) {ctx.addIssue({code: 'custom',message: 'before_id and after_id are mutually exclusive',});}
});// 响应:分页包装
export const pageResponseSchema = <T extends z.ZodTypeAny>(item: T) =>z.object({items: z.array(item), // 当前页数据has_more: z.boolean(), // 是否还有更多});export interface PageResponse<T> {items: T[];has_more: boolean;
}
和传统 page=1&size=20 的偏移分页不同,游标分页在会话这个不断有追加数据的场景中更可靠——不会因为插入新记录导致"翻页看到重复条目"的经典问题。
3. WebSocket 协议
3.1 实时事件通道
WebSocket 连接通过 /api/v1/ws 路径建立,是系统的实时事件通道。kap-server 使用 ws 库创建 WebSocketServer,采用 noServer 模式与 Fastify HTTP 服务器共享端口——不占用独立端口。
export function registerWsV1(core: Scope, opts: RegisterWsV1Options): WebSocketServer {const wss = new WebSocketServer({noServer: true,handleProtocols: selectWsBearerProtocol, // Bearer Token 协议选择});wss.on('connection', (socket, req) => {const conn = new WsConnectionV1({socket,broadcaster, // 会话事件广播器fsWatchBridge, // 文件系统监听桥connectionRegistry, // 连接注册表validateCredential, // 凭证验证器remoteAddress: req.socket.remoteAddress ?? null,userAgent: req.headers['user-agent'] ?? null,// 缓冲和批处理配置maxBufferSize, flushIntervalMs, maxBatchSize, highWaterMarkBytes,});socket.on('close', () => registry.remove(conn.id));});return wss;
}
3.2 协议版本与帧类型
当前 WebSocket 协议版本 v2:每条 WS 消息分为三大类:
- 控制帧(Control Frame):客户端发起,携带
id字段,服务端用ack帧回应——请求/响应模式 - 系统帧(System Frame):服务端单向推送——握手、心跳、错误通知
- 事件帧(Event Frame):服务端单向推送——会话级别的 Agent 事件流
三类帧的结构区分:
// 事件帧(session_event):推送实时数据
{ type, seq, epoch?, volatile?, offset?, session_id?, timestamp, payload }// 控制帧(subscribe / abort / terminal_*):客户端发起请求
{ type, id?, payload }// 确认帧(ack):服务端对控制帧的应答
{ type: 'ack', id, code, msg, payload }
3.3 握手与订阅流程
WebSocket 连接的完整生命周期:
客户端连接│▼
服务端 → server_hello{ type: "server_hello",payload: {ws_connection_id, protocol_version: 2,max_event_buffer_size, capabilities: { event_batching, compression }}}│▼
客户端 → client_hello{ type: "client_hello", id: "1",payload: { client_id, subscriptions?, cursors?, agent_filter? }}│▼
服务端 → ack{ type: "ack", id: "1", code: 0, msg: "ok",payload: { accepted_subscriptions, resync_required, cursors }}│▼
客户端 → subscribe(增量订阅更多 session){ type: "subscribe", id: "2",payload: { session_ids, cursors, watch_fs?, agent_filter? }}│▼
服务端 → ack + session_event 流式推送
v2 协议的关键改进:
- per-session cursor = { seq, epoch }:不仅用 seq 做增量游标,还用 epoch 标识日志的版本——journal 重建时 epoch 变化,客户端自动触发全量重同步
- volatile 事件:
assistant.delta、thinking.delta、tool.call.delta等流式增量事件标记为volatile: true——不写入持久化日志,不参与 seq 递增,断线重连后从 snapshot 恢复而不是重放 - event_batching + compression:服务端可以批量打包事件帧并在协议层面压缩
3.4 控制帧一览
客户端可以发送的控制帧(所有帧都有对应的 ack 响应):
| 帧类型 | 描述 |
|---|---|
client_hello |
握手:上报 client_id,订阅 session |
subscribe |
增量订阅 session 事件流 |
unsubscribe |
取消订阅 session 事件流 |
watch_fs_add |
添加文件系统监听路径 |
watch_fs_remove |
移除文件系统监听路径 |
abort |
中止正在运行的 prompt |
terminal_attach |
附加到终端输出流 |
terminal_detach |
从终端输出流分离 |
terminal_input |
向终端写入输入 |
terminal_resize |
调整终端尺寸 |
terminal_close |
关闭终端 |
pong |
心跳回复(回应 server ping) |
3.5 增量更新 vs 全量同步
同步策略通过 resync_required 帧触发分叉——服务端判断客户端游标是否有效:
- 增量路径:客户端 cursor 的 epoch 与服务端匹配 → 从
seq + 1开始重放 durable 事件 - 全量路径:epoch 不匹配(journal 已重建)、buffer 溢出、session 被重建 → 服务端发送
resync_required,客户端回退到 REST 获取完整session snapshot
// resync_required 帧
{type: "resync_required",timestamp: "2024-01-01T00:00:00.000Z",payload: {session_id: "ses_abc123",reason: "epoch_changed", // buffer_overflow | session_recreated | epoch_changedcurrent_seq: 42,epoch: "0d21b3f7" // 新的 epoch,客户端应采纳}
}
4. RPC 通道模式
4.1 RPC 在系统中的作用
kimi-code 的架构涉及多个进程/环境的桥接:CLI 进程、VS Code 扩展进程、Web UI 浏览器环境、kap-server daemon 进程。RPC 通道负责跨进程/环境的方法调用:让不同环境中的组件能像调用本地方法一样调用远程服务。
RPC 通道采用了类似 JSON-RPC 风格的请求/响应模式:
// RPC 请求格式
{jsonrpc: "2.0",method: "agent.skills.list",params: { workspaceId: "ws_xxx" },id: "req_123"
}// RPC 响应格式
{jsonrpc: "2.0",id: "req_123",result: { skills: [...] }
}// RPC 错误格式
{jsonrpc: "2.0",id: "req_123",error: { code: 40415, message: "skill.not_found", data: { skillName: "x" } }
}
4.2 SDK RpcClient:连接 CLI/VS Code 到引擎
SDK 层的 RpcClient 是前端(CLI/TUI、VS Code 扩展)与后端(kap-server daemon + agent-core)之间的桥梁:
- CLI/TUI → 通过 node-sdk 的 RPC 层连接到 agent-core,直接调用 Agent 的会话管理、prompt 提交、工具调用等接口
- VS Code 扩展 → 同样通过 node-sdk 的 RPC 层,复用与 CLI 完全相同的通信契约
- Web UI → 不直接使用 RPC,而是通过 REST + WebSocket 与 kap-server 通信,kap-server 内部再调用 agent-core-v2
4.3 事件推送(Push Events)
除了请求/响应模式,RPC 通道还支持事件推送——服务端主动向客户端发送事件通知。这在 Agent 运行时特别重要:
- agent.status.updated:Agent 状态变化(模型、token 用量、phase 切换)
- assistant.delta / thinking.delta:流式文本增量
- tool.call.started / tool.progress / tool.result:工具调用的完整生命周期
- subagent.spawned / subagent.completed:子 Agent 的生命周期
- turn.started / turn.step.started / turn.ended:Turn 生命周期
事件推送有两种传输路径:
- WebSocket 通道(Web UI):kap-server 通过 WS 的
session_event帧推送 - RPC 通道(CLI/VS Code):node-sdk 的 RPC 客户端直接接收引擎事件流
5. Zod Schema 驱动的类型安全
5.1 如何用 Zod 定义协议
Protocol 包中的每个数据结构都有对应的 Zod schema。以 Message 为例:
// 1. 定义内容类型的 discriminated union
export const messageContentSchema = z.discriminatedUnion('type', [textContentSchema, // { type: 'text', text: string }toolUseContentSchema, // { type: 'tool_use', tool_call_id, tool_name, input }toolResultContentSchema, // { type: 'tool_result', tool_call_id, output }imageContentSchema, // { type: 'image', source: { kind, ... } }videoContentSchema, // { type: 'video', source: { kind, ... } }fileContentSchema, // { type: 'file', file_id, name, media_type, size }thinkingContentSchema, // { type: 'thinking', thinking, signature? }
]);// 2. 定义 Message(组合内容数组 + 元数据)
export const messageSchema = z.object({id: z.string().min(1),session_id: z.string().min(1),role: messageRoleSchema, // 'user' | 'assistant' | 'tool' | 'system'content: z.array(messageContentSchema),created_at: isoDateTimeSchema,prompt_id: z.string().min(1).optional(),parent_message_id: z.string().min(1).optional(),metadata: z.record(z.string(), z.unknown()).optional(),
});// 3. 从 schema 提取 TypeScript 类型(编译时)
export type Message = z.infer<typeof messageSchema>;
这个模式在 protocol 包中反复应用:每个领域(session、tool、approval 等)都有自己的 Zod schema + 对应的 TypeScript 类型推导。
5.2 编译时类型 + 运行时验证
Zod 的 z.infer 从 schema 中提取编译时类型,保证了 单一真相源:
- 不需要手写 interface:schema 是唯一的规范,类型从 schema 自动推导
- 编译时类型安全:客户端代码使用
Message类型时,TypeScript 会检查所有访问路径 - 运行时数据验证:kap-server 的中间件在请求入口处使用 Zod schema 验证输入,拒绝不符合 schema 的请求
- 序列化安全:网络边界的序列化/反序列化经过 schema 验证,确保磁盘和网络上的数据始终符合契约
5.3 Schema 的组合与复用
Protocol 中的 schema 通过组合构建更复杂的类型:
// 会话 schema 组合了多个子 schema
export const sessionSchema = z.object({id: z.string().min(1),workspace_id: workspaceIdSchema, // 从 workspace 域引入title: z.string(),created_at: isoDateTimeSchema, // 从 time 域引入updated_at: isoDateTimeSchema,busy: z.boolean(),pending_interaction: sessionPendingInteractionSchema.optional(),metadata: sessionMetadataSchema, // cwd + 动态字段agent_config: sessionAgentConfigSchema, // model, system_prompt, tools, mcp_servers...usage: sessionUsageSchema, // input_tokens, output_tokens, ...permission_rules: z.array(permissionRuleSchema),message_count: z.number().int().nonnegative(),last_seq: z.number().int().nonnegative(), // WS 同步游标
});
5.4 Schema 的版本兼容性
协议版本通过以下机制管理:
- WS_PROTOCOL_VERSION = 2:协议版本号硬编码在 ws-control 中,客户端和服务端握手时协商
- optional 字段渐进演进:如
sessionSchema中pending_interaction标记为.optional()——老服务端不返回这个字段,新客户端将其视为undefined - deprecated 类型保留:如
SessionStatusChangedEvent标记为@deprecated但 schema 仍在 union 中——旧日志回放不会因类型不匹配而失败 - superRefine 交叉约束:如
cursorQuerySchema用.superRefine验证before_id和after_id互斥——这类约束无法用 Zod 基础方法表达,但 schema 统一封装了验证逻辑
6. 调试接口
6.1 /api/v1/debug/* 端点
kap-server 提供了一套调试接口,仅在 --debug-endpoints 且绑定本地回环地址时激活:
export function registerDebugRoutes(app: RouteHost, core: Scope): void {registerServiceDispatcherRoutes(app, core, '/debug', {lookup: resolveAnyScopedServiceId,describe: describeAllChannels,});
}
调试接口的核心能力:
- Service 面板:通过 DI 反射发现系统注册的所有 Service,展示每个 Service 的 data(运行时状态)和 trigger buttons(调用 Service 方法)
- DI 注册表反射:遍历作用域树,从 App 级别到 Session 级别,展示每个 Service 的注册信息
- 会话浏览器:查看和管理活跃会话
- 工作空间浏览器:查看和管理已注册的工作空间
6.2 Service Channel 查找
调试接口通过 DI 容器反射,实现了"任何 Service 都可以通过管道调用"的通用调度器:
// resolveAnyScopedServiceId — 在整个 DI 树中递归查找 Service ID
// describeAllChannels — 遍历所有作用域,收集可用通道
这意味着调试面板不需要为每个 Service 硬编码路由——它动态发现 DI 树中注册的所有 Service,并生成对应的交互界面。
7. 通信架构全景
7.1 完整通信图
下面这张图展示了 kimi-code 各组件之间的通信关系:
┌─────────────────────────────────────────────────────────────────┐
│ kimi-code 通信架构全景 │
│ │
│ CLI/TUI ──RPC(sdk)──> node-sdk ──RPC──> agent-core │
│ │ │
│ VS Code ──RPC(sdk)──> node-sdk ──RPC──> agent-core │
│ │ │
│ Web UI ──REST+WS──> kap-server ──> agent-core-v2 │
│ │ │ │
│ Inspect ──REST──> kap-server/debug │
│ │ │
│ [Fastify HTTP + ws] │
│ Port 统一的端口 │
│ │
│ Protocol Schema (Zod) ── 所有组件共享的数据契约 │
└─────────────────────────────────────────────────────────────────┘
7.2 三条通信路径的对比
| 路径 | 协议 | 客户端 | 特点 |
|---|---|---|---|
| CLI/TUI → agent-core | RPC (JSON-RPC) | node-sdk | 同进程或本地进程,低延迟,直接方法调用 |
| VS Code → agent-core | RPC (JSON-RPC) | node-sdk | 与 CLI 路径复用完全相同的通信契约 |
| Web UI → kap-server | REST + WebSocket | 浏览器 Fetch/WebSocket API | HTTP 请求走 REST,实时事件走 WS;kap-server 转发给 agent-core-v2 |
| Inspect → kap-server | REST | 浏览器 | 调试专用,仅在本地回环启用 |
7.3 设计亮点总结
- 单一协议包:所有通信契约集中在一个
packages/protocol包中,引擎、SDK、UI 导入同一套 schema——没有跨包的契约漂移 - Zod 驱动:编译时类型 + 运行时验证,消除了手写 interface 和手写 validator 之间的不一致
- 统一信封:REST 的
{ code, msg, data, request_id }和 WebSocket 的ack帧共享相同的错误代码体系 - 事件分类:durable 事件持久化 + replayable,volatile 事件(delta/progress 等)仅在线传输——平衡了可靠性和带宽
- 双通道同步:WebSocket 实时推送 + REST 全量补全,混合使用 seq-based 增量游标和 epoch-based 版本检查
- 调试反射:DI 驱动的 Service 面板让调试不依赖硬编码路由——任何新 Service 自动可见
Protocol 层的设计做到了"一个 schema 定义,到处使用"。无论是 CLI 的 TUI 渲染、Web Dashboard 的 React 组件、VS Code 的 WebView,还是 kap-server 的中间件——它们共享的是同一套 Zod schema 编译出的类型。这不是约定,是编译时保证。
总结
Protocol 包是 kimi-code 通信架构的基石。它定义了系统的完整数据契约——从 REST API 的请求/响应信封、WebSocket 的控制帧和事件帧、到引擎内部 60+ 种事件类型——全部通过 Zod schema 实现编译时类型安全和运行时验证。
关键设计决策回顾:
- 统一信封模式:所有 REST 响应共享
{ code, msg, data, request_id },所有 WS 控制帧共享{ type, id, payload }→{ type: 'ack', id, code, msg, payload } - 整数错误码命名空间:客户端 4xxxx、服务端 5xxxx、工具 6xxxx、Provider 7xxxx、MCP 8xxxx——一眼能看出错误来源
- WebSocket v2 协议:
{ seq, epoch }游标 + volatile 事件 + event_batching + compression——在可靠性和实时性之间取得平衡 - Zod schema 驱动:schema 是唯一的真相源,TypeScript 类型从 schema 自动推导
- DI 反射调试:运行时动态发现所有注册的 Service,不依赖硬编码
通信协议设计的核心原则:契约先行,实现验证。Protocol 包定义"数据长什么样",kap-server 和 SDK 负责"怎么传输和验证",引擎负责"怎么产生和处理"。三层各司其职,通过 Zod schema 在编译时和运行时双重保证契约的一致性。
在下一篇,我们将探讨 CLI/TUI 架构——kimi-code 的终端用户界面层,了解命令解析、交互式 TUI 渲染和终端事件处理。