KimiHermes:逆向 Kimi Claw 协议,把自托管 Hermes Agent 接进 Kimi

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.py644Hermes 平台适配器:入站消息注入 gateway,出站流式回传
terminal_bridge.py651agent-ws 远程终端桥(完整实现,默认关闭)
kimi_client.py587Kimi 协议客户端:Connect-RPC 订阅、WS 流式、文件上传
search_tools.py353五个搜索系工具的 API 封装
tools.py180kimi_upload_file 等 Hermes 工具注册
init.py77插件入口、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)、chatMessagetypingdisband

关键设计是 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": {}}

生产实测的 rejectionToolBlock.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/toolsget_data_source_desc
kimi_datasource_call/toolscall_data_source_tool,数据源含 yahoo_finance / arxiv / world_bank / imf / tianyancha / scholar 等

凭证按序解析 KIMI_PLUGIN_API_KEYHERMES_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)

  1. SUPPORTS_MESSAGE_EDITING = False 会让 gateway 跳过整个流式消费者——这本是为 QQ/微信准备的防重复守卫,副作用是流式输出全被吞掉。适配:上报 True,edit_message 优雅失败,draft 契约本就不会产生部分重复。
  2. 流式事件分发器(render_message_event/format_tool_event)存在但生产环境未接线,工具调用拦截改走 pre_tool_call 插件 hook,工具进度提示按平台关闭(display.platforms.kimi-claw.tool_progress: false)。
  3. 进度气泡与最终回答在通道里不可区分,约定:💬 前缀的思考转发到 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

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注