Cline 0.0.48 修了一个看着小、实则卡的 bug。发 GPT-6 系列模型的请求,之前每个都被拒,因为 Cline 发错了参数名:它发 max_tokens,而 reasoning 时代的模型只认 max_completion_tokens。这个 bug 不是 Cline 独有的,是 OpenAI 兼容生态里一个普遍的坑。这篇讲这两个参数到底差在哪,Cline 源码里怎么判断哪些模型要换参数,以及你写自己的 OpenAI 兼容客户端时该不该抄这层逻辑。

两个参数,两个时代

先说结论。max_tokens 和 max_completion_tokens 不是别名,是两个时代的产物。经典 chat completions 模型,gpt-4o、gpt-4.1 这些,接受 max_tokens。o 系列和 gpt-5 之后的模型,包括 gpt-6-sol、gpt-6.1-sol,拒绝 max_tokens,只认 max_completion_tokens。你发错,它回一个 400。

参数 接受它的模型 时代
max_tokens gpt-4o、gpt-4.1 经典
max_completion_tokens o1/o3/o4、gpt-5 到 gpt-9 reasoning

这张表是我从 OpenAI 的行为和 Cline 源码里归纳的,两行对应两个时代。

Unsupported parameter: 'max_tokens' is not supported with this model.
Use 'max_completion_tokens' instead.

这个报错原文我在 Cline 源码的注释里看到了,一个字没改。它很直白,把该换哪个参数都告诉你了。问题在于,如果你的客户端是给用户填模型 id 的通用工具,你没法提前知道用户填的是 gpt-4o 还是 gpt-6-sol。

源码里是怎么判断的

我抓了 Cline 的源码来核对,就为看它怎么解决「不知道用户填哪个模型」这件事。我用 api.github.com 的 contents 接口把 openai-compatible.ts 和 model-facts.ts 两个文件拉下来,一共 2 份,逐个 grep 参数名。答案在 openai-compatible.ts 里,函数叫 withMaxCompletionTokensForReasoningModels,逻辑不复杂:把 body 里的 max_tokens 解构出来,判断 model 是不是 reasoning 时代模型,是就把它重命名成 max_completion_tokens。

const { max_tokens: maxTokens, ...rest } = body;
if (maxTokens == null || typeof body.model !== "string"
    || !isOpenAIReasoningEraModelId(body.model)) {
  return body;
}
return { ...rest, max_completion_tokens: rest.max_completion_tokens ?? maxTokens };

这段我按源码原样贴出来,变量名没动。它做对了一件事:只对需要它的模型重命名,其它请求保持原样。注释里写得很清楚,老的第三方服务只认 max_tokens,而那些服务根本不提供 o 系列和 gpt-5 的模型 id,所以别的请求不用动。

判断函数在 model-facts.ts 里,靠两个正则。一个匹配 o1、o3、o4,一个匹配 gpt-5 到 gpt-9。

const O = /(^|[^a-z0-9])o[134](?=$|[^a-z0-9])/;
const G = /(^|[^a-z0-9])gpt-?[5-9](?=$|[^a-z0-9])/;

我本机用 Node 22.22.2 跑了一遍这两个正则,逐条验证边界。结果 gpt-6-sol、gpt-6.1-sol、gpt-5.6-sol、o3-mini 都判 reasoning-era,gpt-4o、gpt-35-turbo、yolo1、gpt-4.1 都判经典。那个 [^a-z0-9] 边界锚定是关键,它保证 gpt-4o 不会因为含「o」被误判成 o 系列,Azure 的 gpt-35-turbo 也不会被 gpt-5 那条误伤。我本机跑完这 8 条,确认了一个意外:gpt-4.1 名字里有 4.1,看着像新模型,但正则不匹配它,它走经典参数。这个分界靠猜会猜错,必须跑。

这层逻辑该不该抄

这里我给个明确的判断。你自己的 OpenAI 兼容客户端,该不该也写一层这种参数重命名?我建议抄,但只抄思路,别抄范围。理由是这层逻辑的价值在「用户填什么模型都能用」,代价是你要维护那张「哪些模型是 reasoning 时代」的清单。Cline 的注释里自己也写了维护成本:新 gpt 家族到 gpt-9 不用改,出 gpt-10 或者别的命名就得加。

我的态度是,如果你的产品只面向 OpenAI 官方 API,别抄,官方 SDK 已经帮你映射了,自己写反而多一层错。如果你面向一堆 OpenAI 兼容端点,用户可能填各种模型 id,那这层重命名值得有,否则你会在 gpt-6 上收到一堆 400,还以为是 token 超了。这个分界我给得很具体。

这次 release 还改了什么

除了这个修复,release 里还有两条值得记。默认模型回到了 Claude Sonnet 5.5,它重新登顶 Cline 的推荐榜。模型目录刷了一遍,新加了 CommandCode 和 Reflection 两个 provider,一批第三方 provider 的默认模型也换了,比如 302.AI 默认 Gemini 4 Argon,CoreWeave 默认 Reflection Beam 1.0,Scaleway 默认 DeepSeek V4.1 Flash。这些我都从 release body 里核过,一共数出 2 个新 provider,CommandCode 和 Reflection。

收尾的判断。max_tokens 换成 max_completion_tokens,这个修复本身一行的事,但它暴露的是 OpenAI 生态里参数命名分叉的老问题。接 reasoning 模型,参数名别想当然,先确认模型 id 落在哪个时代。