Featured image of post Kimi Code 对 kimi-k2.6 的专用处理:与 OpenCode 的对比

Kimi Code 对 kimi-k2.6 的专用处理:与 OpenCode 的对比

最近 kimi code 从 python 迁移到了 ts 版本。于是做了个简单的分析。

语速

最近 kimi code 从 python 迁移到了 ts 版本。于是做了个简单的分析。

根据对 kimi-code 源代码(特别是 packages/kosong/src/providers/kimi.tskimi-schema.tskimi-files.ts 等)以及 OpenCode 相关兼容性 Issue 的分析,以下是 kimi-code 针对 kimi-k2.6(及 Kimi 系列模型)做的专用处理,以及相对于 OpenCode 的关键差异。

1. 原生 Kimi Provider(非通用 OpenAI 兼容层)

kimi-code 没有直接把 Kimi 当"又一个 OpenAI 兼容接口"处理,而是实现了专门的 kimi provider 类型:

特性kimi-codeOpenCode
Provider 类型专门的 'kimi' 类型,独立适配通过 OpenAI/Anthropic 通用 bridge 访问
专有字段原生处理 reasoning_contentthinkinggenerationKwargsbridge 层常丢失 reasoning_content
认证头支持 kimiRequestHeadersX-Msh-Tool-Call-Id 等 Moonshot 专有 header通用 header 转发

2. reasoning_content 的全生命周期处理

kimi-k2.6 默认开启 thinking,且要求多轮对话历史中保留 reasoning_content,否则工具调用后会报 400 错误。

kimi-code 的处理:

  • convertMessage:将内部 think 类型的 content part 提取并序列化为 reasoning_content 字段,确保历史消息中 thinking 内容不丢失
  • 流式解析:在 _convertStreamResponse_convertNonStreamResponse 中都专门提取 delta.reasoning_content / message.reasoning_content
  • TUI 渲染:有专门的 ThinkingComponent 组件实时显示 thinking 内容,支持展开/收起和 spinner 动画

OpenCode 的问题:

OpenCode Go 的 bridge 在第二轮对话时丢失 reasoning_content,导致 Moonshot API 返回:

1
thinking is enabled but reasoning_content is missing in assistant tool call message

3. JSON Schema 专用规范化(kimi-schema.ts

Moonshot 的工具参数验证器对 JSON Schema 有严格且特殊的要求,这是 OpenCode 与 kimi-k2.6 不兼容的主要来源之一。

kimi-code 的 normalizeKimiToolSchema 做了:

  • 解引用 $ref:将 $defs / definitions 中的定义内联展开,消除外部引用
  • 补全缺失的 type:Kimi 验证器会拒绝省略 type 的嵌套属性(比如 MCP 产生的 enum-only schema),kimi-code 会推断并补全 type: string/object/array
  • 循环引用检测:遇到循环引用时保留原始 $ref,避免无限递归

OpenCode 的问题:

生成 schema 使用 #/definitions/ 而非 Moonshot 要求的 #/$defs/,且没有为 Kimi 做 schema 类型推断和补全,导致复杂工具调用直接 400。

4. Thinking 模式的原生配置体系

kimi-code 从配置层到 UI 层都内置了对 Kimi thinking 模式的支持:

  • 配置解析ThinkingConfigSchema 支持 mode: auto/on/offeffort: low/medium/high/xhigh/max

  • 模型能力标注ModelAlias 支持 capabilities: ['thinking', 'always_thinking']

  • 模型选择器 UI:按 ←→ 键可切换 thinking on/off,always-on 模型不可关闭

  • Provider 方法withThinking(effort) 会正确生成:

    1
    2
    3
    4
    
    {
      "reasoning_effort": "high",
      "extra_body": { "thinking": { "type": "enabled" } }
    }
    
  • token 预算:自动将 legacy max_tokens 规范化为 Kimi 偏好的 max_completion_tokens

OpenCode 的问题:

通过 Anthropic bridge 时硬编码发送 thinking content blocks,但 Kimi API 只支持 text/image_url/video_url/video,导致:

1
Invalid value: thinking. Supported values are: 'text','image_url','video_url' and 'video'.

5. Moonshot 原生服务集成

kimi-code 内置了 Moonshot 专属服务,而非使用通用本地实现:

  • MoonshotFetchURLProvider:优先调用 Moonshot 的 coding-fetch 服务(已做页面文本提取),失败才 fallback 到本地
  • MoonshotWebSearchProvider:直接调用 Moonshot 搜索 API,支持 enable_page_crawling
  • KimiFiles:视频上传到 Moonshot 文件服务,生成 ms://<file-id> 格式的 video_url

6. 工具调用层细节

  • 内置函数:以 $ 开头的工具名会被识别为 Kimi builtin function,序列化为 type: 'builtin_function'
  • Usage 提取:支持 Moonshot 特有的 choices[0].usage 位置,以及 cached_tokens 等字段
  • Finish Reason 映射:将 OpenAI 格式的 stop/tool_calls/length 等映射到内部统一枚举

7. CLI 核心与 LLM SDK 的架构隔离

这是一个容易被忽视但很重要的架构差异。

kimi-code 的核心 CLI(apps/kimi-code)本身不直接依赖任何 OpenAI 或 Anthropic 的 TypeScript SDK。 查看其 package.json,核心依赖只有 TUI 渲染(pi-tui)、命令行解析(commander)、语法高亮(cli-highlight)等通用库。所有与 LLM provider 的交互都被隔离在自研的 kosong 包中。

packages/kosong 虽然内部使用了 openai@anthropic-ai/sdk 作为实现细节(因为 Kimi API 是 OpenAI-compatible 的),但对外提供的是统一的 LLM 抽象接口。CLI 核心只依赖 kosong,不感知底层 vendor SDK。

OpenCode 则不同。packages/opencode 核心包直接依赖了大量 vendor SDK:

  • @ai-sdk/openai
  • @ai-sdk/anthropic
  • @ai-sdk/google
  • @ai-sdk/azure
  • @openrouter/ai-sdk-provider
  • ……(共计十余个 provider-specific 包)

这意味着 OpenCode 的核心代码与各个 vendor 的 SDK 深度耦合,而 kimi-code 的核心 CLI 保持干净,模型交互完全通过自研抽象层隔离。

8. Commit 历史揭示的演进路径

以上的代码结构差异只是静态结果。更有意思的是看两边的 commit 历史——动态演进方向完全不同

kimi-code:原生设计,持续消除配置负担

842e699 — “Kimi For Coding”(初始提交)

这是整个项目的起点。初始代码就包含了:

  • packages/kosong/src/providers/kimi.ts:专用 Kimi provider
  • packages/kosong/src/providers/kimi-schema.ts:专用 JSON Schema 规范化器
  • packages/kosong/src/providers/kimi-files.ts:专用文件上传服务

结论:kimi-code 从一开始就把 Kimi API 当作一等公民设计,不是后期打补丁适配。


d95b013 fix(catalog): preserve reasoning fields in custom model (#70)

这个 commit 修复了一个很隐蔽的问题。models.dev 用 interleaved 字段标记 reasoning 支持,但早期代码把 interleaved=true 当作 undefined 处理,导致通过 /connect 选择的模型静默丢失 reasoning 能力。

修复内容:

  • interleaved=true 映射为默认的 reasoning_content
  • update-catalog.mjs 的 allowlist 加入 interleaved,否则 release build 的离线 catalog 会再次丢失该字段

61f7d0e fix(kosong): make openai-compatible thinking work without reasoning_key (#78)

这是 reasoning 处理的核心 commit,展示了 kimi-code 对兼容性的深层思考。diff 显示了三层关键设计:

  1. Inbound 自动扫描(响应解析)

    1
    2
    
    const KNOWN_REASONING_KEYS = ['reasoning_content', 'reasoning_details', 'reasoning'] as const;
    // 自动扫描三个字段,第一个字符串值获胜
    
  2. Outbound 默认回写(请求序列化)

    1
    2
    
    const DEFAULT_OUTBOUND_REASONING_KEY = KNOWN_REASONING_KEYS[0]; // 'reasoning_content'
    // 默认以 reasoning_content 回传,无需用户配置
    
  3. 自动注入 reasoning_effort(历史连续性)

    1
    2
    
    // 当历史中有 ThinkPart 但 caller 未显式设置 reasoning_effort 时,
    // 自动注入 'medium',避免 One API / DeepSeek 等严格网关返回 400
    

边界情况也处理得很细:blank reasoning_key"")被 normalize 为 undefined;caller 通过 withGenerationKwargs 显式设置的值不会被自动注入覆盖

验证目标明确写的是:

Manually verified end-to-end against the real DeepSeek API with a hand-written config.toml that does not set reasoning_key: thinking content renders, no 400, multi-turn conversations work.


OpenCode:通用层设计,OpenAI-centric

eb84f46 fix(llm): split OpenAI reasoning summary blocks (#29000)

这个 commit 展示了 OpenCode 处理 reasoning 的完全不同思路——围绕 OpenAI Responses API 设计:

  • 维护 encrypted_contentitem_reference 的状态机
  • item_id + summary_index 折叠多个 summary parts
  • store:false 时过滤缺少 encrypted_content 的 reasoning items

这与 Kimi 的 reasoning_content 机制完全不同。 Kimi 不需要 encrypted_contentitem_reference,而是直接在 message 上附加 reasoning_content 字段。


一个残酷的事实

  • OpenCode Issue #26331 “Bug: OpenCode Go bridge layer incompatible with kimi-k2.6 tool calls” — 状态:至今仍是 open
  • OpenCode Issue #27054 “KIMI K2.6 showing error in Opencode GO” — 状态:closed,但关闭方式是禁用 MCP(workaround)

#27054 的最后一个 comment:

The workaround is to disable your MCP and then initiate the session

这不是修复,这是回避问题。


Commit 历史对比总结

维度kimi-codeOpenCode
初始设计初始提交即包含完整 Kimi provider + schema 规范化 + 文件服务通用多模型架构,后期通过 bridge 适配
reasoning 机制围绕 reasoning_content 字段设计,自动扫描/回写/注入 effort围绕 OpenAI Responses 的 encrypted_content + item_reference 设计
schema 处理专用 normalizeKimiToolSchema,解引用 $ref + 补全 type通用 schema 验证,友好错误提示为主
配置哲学让 OpenAI-compatible 网关"零配置工作",自动推断所有字段依赖用户通过 bridge/config 手动适配
issue 状态持续发布 reasoning 相关 patch(#70, #78kimi-k2.6 兼容性问题 #26331 至今 open

总结:核心差异

维度kimi-codeOpenCode
架构定位原生为 Kimi/Moonshot 设计,专用 provider通用多模型代理,通过 bridge 适配
Thinking/Reasoning原生支持,全链路保留 reasoning_contentbridge 层易丢失,导致 400
JSON Schema专用 normalizeKimiToolSchema 做解引用和类型补全通用 schema 生成,不符合 Kimi 验证器要求
API 格式直接生成 Moonshot 原生格式(含 thinking 配置、$defs 规范等)经 OpenAI/Anthropic 协议转换,产生格式不匹配
服务集成内置 Moonshot fetch/search/file 服务使用通用本地工具
核心依赖CLI 核心不直接依赖 vendor SDK,通过自研 kosong 包隔离核心包直接耦合 @ai-sdk/openai 等十余个 vendor SDK

从 commit 历史看,kimi-code 的演进方向是持续消除用户配置负担reasoning_key 从必填 → 可选覆盖 → 自动推断;interleaved 从被过滤 → 正确映射),而 OpenCode 的演进方向是持续深化 OpenAI 生态集成(Responses API、encrypted reasoning、item reference),对 Kimi 的适配停留在通用 bridge 层。

这就是 commit 层面的真相:一个是原生演进,一个是 bridge 间隙。