KimiHermes 是一个 Hermes Agent 平台插件:把自托管的 Hermes Agent 桥接到 Kimi Claw(kimi.com),在 Kimi App/网页里直接指挥自己的 Agent,支持打字机流式、工具卡片、思考折叠块和文件互传。
Kimi 官方只发布了面向 OpenClaw 的连接插件,线协议没有公开文档。这个项目是对官方插件包(v0.27.1)的互操作重实现:协议从公开发布的插件包逆向整理,然后开着 verbose tracing 抓取 Kimi 官方云端实例的真实流量,逐帧对比校准,目前在生产环境稳定运行。本文记录实现要点,完整协议细节在仓库的 docs/PROTOCOL.md(219 行)。
代码结构
共约 2500 行 Python,无第三方依赖(终端桥只用 stdlib pty):
| 模块 | 行数 | 职责 |
|---|---|---|
adapter.py | 644 | Hermes 平台适配器:入站消息注入 gateway,出站流式回传 |
terminal_bridge.py | 651 | agent-ws 远程终端桥(完整实现,默认关闭) |
kimi_client.py | 587 | Kimi 协议客户端:Connect-RPC 订阅、WS 流式、文件上传 |
search_tools.py | 353 | 五个搜索系工具的 API 封装 |
tools.py | 180 | kimi_upload_file 等 Hermes 工具注册 |
init.py | 77 | 插件入口、pre_tool_call hook |
拓扑与鉴权
Kimi App/网页 ── Kimi 云端 ──(1) IM RPC:Connect-RPC over HTTPS──▶ 插件 ──▶ Hermes gateway
──(2) WS 流式回复 ──▶
──(3) agent-ws 终端通道 ──▶(服务端未开放)
三条通道共用一个鉴权 header:X-Kimi-Bot-Token: km_b_prod_...。消息体全部是 protojson——lowerCamelCase 字段名、枚举序列化为名字字符串、int64 序列化为字符串、FieldMask 是单个字符串。另有四个自报身份 header(X-Kimi-Claw-Version 等),实测服务端从不校验。
入站:Connect-RPC 订阅 + 两段式回取
入站是 Connect-RPC 的 server-streaming:
POST {im_base}/kimi.gateway.im.v1.IMService/Subscribe
content-type: application/connect+json; charset=utf-8
connect-protocol-version: 1
请求体是单个 Connect envelope:0x00 + uint32BE(len) + protojson(SubscribeRequest),响应是同构 envelope 流(flag bit1 = EndStream)。SubscribeRequest 只有一个可选字段 sinceId——持久化每个事件的 ID,断线重连时带上,实现续传。重连策略:backoff 1s 起步 ×2,封顶 300s,不限次数;401/403 直接放弃(鉴权失败,重试无意义)。
事件类型有六种:ping(10 秒一次,30 秒无消息判定死连接)、reconnect(立即重连)、botReport(回 UpdateBotMeta)、chatMessage、typing、disband。
关键设计是 chatMessage 事件只带 ID 不带正文,需要回取:
POST .../IMService/ListMessages
{"chatId", "startMessageId": msgId, "endMessageId": msgId,
"includeStartMessage": true, "includeEndMessage": true,
"direction": "DIRECTION_BACKWARD", "pageSize": 20}
正文在 messages[*].message.blocks[]:text.content 是文本,file 是附件(内联 kimi.file.v1.File,含 signUrl,过期则走 GET /api-claw/files/{fileId} 换签名 URL),resourceLink.uri 是链接。
防自回复循环靠 role 过滤:只 dispatch role == "user" 且 status == "STATUS_COMPLETED" 的事件。
出站:WS 流式的三车道帧编舞
流式回复走 WebSocket:wss://www.kimi.com/api-ws/im/send-message/ws,握手只带 X-Kimi-Bot-Token。每帧一个 protojson SendMessageStreamRequest。以下编舞是从官方云端实例 verbose trace 抓到的真实序列:
frame 1 {"chatId": ...} ← 首帧绑定会话
frames 2..N think 车道:blockId "0",op=append,mask=block.think.content,
内容加 "Reasoning:\n" 前缀
tool 车道: blockId "1",op=set,不带 mask,
block.tool = {toolCallId, name, args}
answer 车道:下一个空闲 blockId(抓包样本里是 "3"),
op=append,mask=block.text.content,增量 delta
last {"end": {}} ← 正常关闭即成功,无 message_id 返回
思考、工具卡片、正文三条车道各占一个 blockId,append 增量堆叠。对账逻辑:如果新快照不是已发送文本的延展(发生段重写),发一帧 op=set 全量快照修正。keepalive 每 10 秒 {"ping": {}}。
生产实测的 rejection:ToolBlock.contents[].status 会杀死连接,"running"/"done" 和 "STATUS_RUNNING"/"STATUS_DONE" 两种编码都试过都被断。只有裸 {toolCallId, name, args} 被接受——工具状态流转因此不表达。这类坑文档没有,只能抓包试。
非流式场景有 unary SendMessage 可用(一发一收,返回 messageId);文件卡片是 resourceLink block,URI 形式 kimi-file://。文件上传走 POST {origin}/api-claw/files:upload(multipart,一次 1–5 个),注意只上传不会显示任何东西,必须再把 file id 包成 resourceLink 发出去。
搜索系工具
五个工具复用同一组 REST 端点,base 是 https://api.kimi.com/coding/v1(官方云插件默认的 agent-gw.kimi.com 需要 Kimi provisioning 的 key,普通 Kimi Code key 只在 api.kimi.com 有效):
| 工具 | 端点 | 说明 |
|---|---|---|
kimi_search | /search | {text_query, limit 1-20, enable_page_crawling, timeout_seconds} |
kimi_fetch | /fetch | {url},Accept: text/markdown 返回 markdown |
kimi_finance | /tools | 包装 stock_finance_data 数据源的实时行情 |
kimi_datasource_get_desc | /tools | get_data_source_desc |
kimi_datasource_call | /tools | call_data_source_tool,数据源含 yahoo_finance / arxiv / world_bank / imf / tianyancha / scholar 等 |
凭证按序解析 KIMI_PLUGIN_API_KEY → HERMES_CUSTOM_API_KIMI_COM_API_KEY(现有 Kimi Code key 直接可用),运行时实时读 .env,换 key 免重启 gateway。
agent-ws 终端:实现了,但服务端没接线
第三条通道是远程终端(web-ssh):JSON-RPC 2.0 initialize 能力协商 + 带外终端 envelope(open / input / resize / close / heartbeat,stdout 流 base64 回传,错误码 -32010~-32602 全套)。651 行完整实现,纯 stdlib pty。
实测结论:云端接受这条 WebSocket 连接,然后什么都不发——自建实例的终端功能在服务端根本没接线(官方云实例的终端是 Kimi 内网直连 SSH 到他们 provisioning 的 VM,不走这个协议)。代码留着默认关闭,服务端哪天开放,改配置即用。
Hermes 侧的三个适配坑(v0.20.1)
SUPPORTS_MESSAGE_EDITING = False会让 gateway 跳过整个流式消费者——这本是为 QQ/微信准备的防重复守卫,副作用是流式输出全被吞掉。适配:上报 True,edit_message优雅失败,draft 契约本就不会产生部分重复。- 流式事件分发器(
render_message_event/format_tool_event)存在但生产环境未接线,工具调用拦截改走pre_tool_call插件 hook,工具进度提示按平台关闭(display.platforms.kimi-claw.tool_progress: false)。 - 进度气泡与最终回答在通道里不可区分,约定:
💬前缀的思考转发到 think 块,其余内容结束当前流——”一段 = 一个气泡”模型。多工具任务在 Kimi 侧是多个依次打出的气泡而非官方的单消息多块,内容无损。
安装与安全
# 1. kimi-claw/ 复制到 ~/.hermes/plugins/
# 2. kimi.com → Kimi Claw →「关联已有 OpenClaw」取 --bot-token
# 3. .env 写入 KIMI_CLAW_BOT_TOKEN=km_b_prod_...
# 4. config.yaml:platforms.kimi-claw.enabled: true + streaming.enabled: true
# 5. hermes plugins enable kimi-claw && 重启 gateway
# 6. 首条消息会收到配对码:hermes pairing approve kimi-claw <配对码>
两个安全点:bot-token 等于这个 bot 的完整控制权,泄露即轮换;terminal_enabled: true 会把本机 shell 暴露给 Kimi 会话持有者,非必要不开。
项目为非官方社区实现,与月之暗面无关;上游协议变更可能导致失效——但 PROTOCOL.md 里那份带实测结论的协议记录,本身就是给社区留的公开文档。MIT 协议,欢迎试用:github.com/SHAWNTRIBBIANI/KimiHermes。