[Bug] /v1/chat/completions 的 reasoning_effort 被静默丢弃;thinking passthrough 的 effort 钳位使 Kiro 通道 xhigh/max 不可达

Author: Bruce-anleCreated Sep 24, 2026Updated Sep 24, 2026

环境

  • 版本:Docker 镜像 3.5.1;源码逐行核对于 main 0324439(2026-09-23 “refactor: 重构上游错误处理,更新依赖与模型,隐藏赞助”)
  • 通道:claude-kiro-oauth;部署 Docker

问题一:顶层 reasoning_effort 在 OpenAI→Claude 转换中被静默丢弃

src/converters/strategies/OpenAIConverter.js 的 toClaudeRequest 构造 claudeRequest 时只映射 model / messages / system / tools / thinking(thinking 仅来自 extra_body.anthropic.thinking 旁路)。顶层 reasoning_effort 没有映射进任何字段(全文件 claudeRequest.reasoning_effort = 与 claudeRequest.output_config = 均为 0 次赋值)。

后果链:src/providers/claude/claude-kiro.js callApi 把 body.reasoning_effort 递给 buildCodewhispererRequest → kiro-effort.js 的 explicitEffort 为空 → 不生成 additionalModelRequestFields。客户端设置 reasoning_effort: "high" 与不设置行为完全一致,无报错、无日志提示。

影响范围:所有 Claude 型后端(claude-kiro-oauth、claude-custom)经 /v1/chat/completions 的请求。claude-custom 的 callApi 是 data: body 原样转发(claude-core.js),因此任何会新增字段的通用修复需要考虑该转发路径。

问题二:extra_body.anthropic.thinking 的 adaptive effort 钳位窄于 provider 层

OpenAIConverter.js 约 367 行:

javascript
const normalizedEffort = (effort === 'low' || effort === 'medium' || effort === 'high') ? effort : 'high';

而 provider 层 src/providers/claude/kiro-effort.js 的 CLAUDE_EFFORT_LEVELS 支持更宽集合:

javascript
['claude-opus-5',    new Set(['low', 'medium', 'high', 'xhigh', 'max'])],
['claude-sonnet-5',  new Set(['low', 'medium', 'high', 'xhigh', 'max'])],
['claude-opus-4.8',  new Set(['low', 'medium', 'high', 'xhigh', 'max'])],
['claude-opus-4.6',  new Set(['low', 'medium', 'high', 'max'])],

转换层先于 provider 层钳位,xhigh/max 在这里被折叠为 high。provider 层的 per-model 校验(不支持的档位回退 medium、旧模型完全省略字段以规避 400)已经足够安全,转换层这行可以放宽或下沉。

/v1/messages 不受影响:格式与后端匹配时免转换(日志 Request format matches backend provider. No conversion needed.),output_config.effort 原样到达 provider,xhigh/max 正常生效(已实测 200)。

复现

bash
# 问题一:带不带 reasoning_effort 行为完全一致(additionalModelRequestFields 均未生成)
curl http://127.0.0.1:3000/claude-kiro-oauth/v1/chat/completions \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"model":"claude-sonnet-5","reasoning_effort":"high","max_tokens":100,"messages":[{"role":"user","content":"hi"}]}'

# 问题二:xhigh 在转换层被折叠为 high
-d '{"model":"claude-sonnet-5","extra_body":{"anthropic":{"thinking":{"type":"adaptive","effort":"xhigh"}}},"messages":[...]}'

建议修复

在 src/utils/common.js 的 convertData(...) 调用之后按目标 provider 门控透传(已在 3.5.1 上实现并验证,随 PR 提交):

javascript
// Kiro: 顶层 reasoning_effort 透传为 output_config.effort;per-model 档位校验与回退由 provider 层(kiro-effort.js)负责
if (toProvider === MODEL_PROVIDER.KIRO_API
    && typeof preConvertBody.reasoning_effort === 'string'
    && processedRequestBody.output_config === undefined) {
    processedRequestBody.output_config = { effort: preConvertBody.reasoning_effort.trim().toLowerCase() };
}

要点:

  • 档位校验与回退完全交给 provider 层(kiro-effort.js 已有 per-model 集合、medium 回退、旧模型省略字段的 400 规避),转换层不做二次判断。
  • 门控限定 claude-kiro-oauth:claude-custom 的 callApi 原样转发请求体,无条件映射会把 output_config 带到第三方上游,门控可使其字节级零变化。
  • 第 367 行的钳位可保持不变(thinking 标签档位词表与 output_config.effort 的扩展档位是两条通道),也可以一并放宽,交给维护者定。

验证证据(3.5.1 + 上述补丁)

  • /v1/chat/completions + reasoning_effort: "xhigh"(claude-sonnet-5):日志确认 output_config.effort = "xhigh" 进入请求,上游 200 接受(非法 additionalModelRequestFields 会如 #705 一样 400,200 即上游接受)。
  • 对照组:不带 reasoning_effort 的请求无该字段、无日志,200。
  • /v1/messages 回归 200;claude-custom 无节点未实测,门控在代码层保证其零改动。

说明

  • issue 搜索未见同类报告,如已有请合并。

Source: justlovemaki/AIClient2API