你运行的 Kimi Code 版本是?
2.0.0(回归起点为 0.42.0,0.41.0 行为正常)
你使用的是哪个开放平台/订阅?
第三方 OpenAI 兼容网关(new-api 中转,Chat Completions 协议,provider type 为 openai)
你使用的是哪个模型?
deepseek-v4.1-flash(网关侧映射为 deepseek/deepseek-v4.1-flash)
你的电脑平台是?
Microsoft Windows NT 10.0.26100.0 x64
你遇到了什么问题?
开启 Thinking([thinking] enabled = true,effort=max)后,思考块在 TUI 中完全不显示——既没有流式思考文本,回合结束后也没有任何折叠的思考组件。检查会话的 wire.jsonl 发现整个会话 0 条 think 记录,即思考内容在进入渲染层之前就被丢弃了。
同一个模型、同一份配置,在 0.41.0 下思考内容显示正常,因此这是 0.42 引入的回归。
根因(已定位到代码):上游网关以 OpenRouter 方言返回思考内容,SSE delta 中同时携带:
{
"choices": [
{
"delta": {
"reasoning": "We",
"reasoning_details": [
{ "type": "reasoning.text", "text": "We", "format": "unknown", "index": "0" }
]
}
}
]
}
即:字符串字段 reasoning + 数组字段 reasoning_details(元素类型为 reasoning.text),没有 reasoning_content。
在 2.0.0 的 packages/agent-core-v2/src/human/llm/requester/bases/openai/format.ts 流式解析中(约 L300 起):
- 只要 delta 里存在数组形态的
reasoning_details,就进入 details 分支;
reasoning-key.ts 的 toReasoningDetailsElement 只接受 summary/encrypted 两种元素类型,reasoning.text 元素被跳过,extractReasoningDetails 返回空数组(而非 undefined),分支已被占用;
- 于是读取字符串字段的
else 分支(extractReasoning(delta),能扫到 reasoning 字符串)永远不会执行;
- 结果:思考内容被完整丢弃,不进 wire、不上屏。
0.41.0 的 openai-legacy.ts 只做 reasoningKeyDialect.observe(delta) 字符串扫描,会跳过数组、读到 reasoning 字符串,因此显示正常。
另外注意到 PR #3492 的描述中写的是「跳过非 summary/encrypted 元素,OpenRouter 的 reasoning.* 数组方言保持和以前一样被忽略」——但实际上以前并没有被完全忽略(字符串 reasoning 字段当时能被读到并显示),所以这是一次意外回归。PR #3735 的显示优先级修复只作用于 kimi trait,generic openai provider 的这个问题在最新 main 上依然存在。
复现步骤?
- 准备一个会返回上述方言的 OpenAI 兼容端点。没有现成网关的话,可用下面的最小 mock(Python 3,监听 8787):
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
class H(BaseHTTPRequestHandler):
def do_POST(self):
self.send_response(200)
self.send_header('Content-Type', 'text/event-stream')
self.end_headers()
def chunk(delta, finish=None):
c = {"id":"x","object":"chat.completion.chunk","choices":[{"index":0,"delta":delta,"finish_reason":finish}]}
self.wfile.write(f"data: {json.dumps(c)}\n\n".encode())
for w in ["We", " need", " think"]:
chunk({"role":"assistant","reasoning": w,
"reasoning_details":[{"type":"reasoning.text","text":w,"format":"unknown","index":"0"}]})
chunk({"content": "3"})
chunk({}, "stop")
self.wfile.write(b"data: [DONE]\n\n")
HTTPServer(("127.0.0.1", 8787), H).serve_forever()
config.toml 配置指向 mock 的模型并开启 Thinking:
[providers.mock]
type = "openai"
base_url = "http://127.0.0.1:8787/v1"
api_key = "sk-any"
[models."mock/t"]
provider = "mock"
model = "t"
max_context_size = 128000
capabilities = ["thinking", "tool_use"]
[thinking]
enabled = true
- 用该模型发起任意对话 → 思考内容完全不显示(wire.jsonl 中无 think 记录)。
- 对照:把 mock 中的字段名换成
reasoning_content(字符串、去掉 reasoning_details 数组)→ 思考正常显示。
真实环境补充:我的网关上游对 deepseek/deepseek-v4.1-flash 在 reasoning_effort 的 low/high/max 各档位均稳定返回上述方言(直连上游多轮实测一致);经 new-api 透传后格式不变。返回标准 reasoning_content 的模型(如 kimi-k3 官方渠道)不受影响。
期望的行为是什么?
OpenRouter 方言的思考内容应正常显示。建议任选其一:
extractReasoningDetails 在数组存在但所有元素都被过滤时返回 undefined,让流程回退到字符串字段扫描(改动最小);
- details 分支内同时读取字符串
reasoning 字段;
toReasoningDetailsElement 识别 reasoning.text 元素,取其 text 作为可见思考。
至少不应在「数组存在但无可用元素」时把字符串字段里的思考内容静默吞掉。
补充信息
- OpenRouter 的
reasoning_details(reasoning.text 元素)是相当常见的方言,任何前置/中转 OpenRouter 风格端点的网关都会踩到这个问题。
- 当前会话(2.0.0 下 kimi-k3 官方渠道)wire.jsonl 中 think 记录完整,可证明渲染层与配置无问题,问题确在解析层。
Contribution
你运行的 Kimi Code 版本是?
2.0.0(回归起点为 0.42.0,0.41.0 行为正常)
你使用的是哪个开放平台/订阅?
第三方 OpenAI 兼容网关(new-api 中转,Chat Completions 协议,provider type 为
openai)你使用的是哪个模型?
deepseek-v4.1-flash(网关侧映射为
deepseek/deepseek-v4.1-flash)你的电脑平台是?
Microsoft Windows NT 10.0.26100.0 x64
你遇到了什么问题?
开启 Thinking(
[thinking] enabled = true,effort=max)后,思考块在 TUI 中完全不显示——既没有流式思考文本,回合结束后也没有任何折叠的思考组件。检查会话的wire.jsonl发现整个会话 0 条think记录,即思考内容在进入渲染层之前就被丢弃了。同一个模型、同一份配置,在 0.41.0 下思考内容显示正常,因此这是 0.42 引入的回归。
根因(已定位到代码):上游网关以 OpenRouter 方言返回思考内容,SSE delta 中同时携带:
{ "choices": [ { "delta": { "reasoning": "We", "reasoning_details": [ { "type": "reasoning.text", "text": "We", "format": "unknown", "index": "0" } ] } } ] }即:字符串字段
reasoning+ 数组字段reasoning_details(元素类型为reasoning.text),没有reasoning_content。在 2.0.0 的
packages/agent-core-v2/src/human/llm/requester/bases/openai/format.ts流式解析中(约 L300 起):reasoning_details,就进入 details 分支;reasoning-key.ts的toReasoningDetailsElement只接受summary/encrypted两种元素类型,reasoning.text元素被跳过,extractReasoningDetails返回空数组(而非undefined),分支已被占用;else分支(extractReasoning(delta),能扫到reasoning字符串)永远不会执行;0.41.0 的
openai-legacy.ts只做reasoningKeyDialect.observe(delta)字符串扫描,会跳过数组、读到reasoning字符串,因此显示正常。另外注意到 PR #3492 的描述中写的是「跳过非 summary/encrypted 元素,OpenRouter 的 reasoning.* 数组方言保持和以前一样被忽略」——但实际上以前并没有被完全忽略(字符串
reasoning字段当时能被读到并显示),所以这是一次意外回归。PR #3735 的显示优先级修复只作用于 kimi trait,genericopenaiprovider 的这个问题在最新 main 上依然存在。复现步骤?
config.toml配置指向 mock 的模型并开启 Thinking:reasoning_content(字符串、去掉reasoning_details数组)→ 思考正常显示。真实环境补充:我的网关上游对
deepseek/deepseek-v4.1-flash在reasoning_effort的 low/high/max 各档位均稳定返回上述方言(直连上游多轮实测一致);经 new-api 透传后格式不变。返回标准reasoning_content的模型(如 kimi-k3 官方渠道)不受影响。期望的行为是什么?
OpenRouter 方言的思考内容应正常显示。建议任选其一:
extractReasoningDetails在数组存在但所有元素都被过滤时返回undefined,让流程回退到字符串字段扫描(改动最小);reasoning字段;toReasoningDetailsElement识别reasoning.text元素,取其text作为可见思考。至少不应在「数组存在但无可用元素」时把字符串字段里的思考内容静默吞掉。
补充信息
reasoning_details(reasoning.text元素)是相当常见的方言,任何前置/中转 OpenRouter 风格端点的网关都会踩到这个问题。Contribution