OpenCode 模型精细化配置:中转站接入的 models.dev 深水区

把第三方中转站接入 OpenCode 后,很多人以为”填对供应商标识就万事大吉”,随后却遇到一连串怪象:图片识别失效、上下文在奇怪的位置被截断、会话标题永远停在 New session - <时间戳>。这些问题的根源都指向同一件事——OpenCode 依赖 models.dev 自动注入的模型能力清单,在中转站场景下并不总是可靠或完整

OpenCode 启动时用 供应商标识 + 模型 ID 去 models.dev 查询模型的上下文长度、输出上限、多模态、推理变体等信息并自动注入。但中转站本质是聚合层,它转发的模型未必与 models.dev 记录的是同一份部署,自动注入因此可能缺失或错配。这时就需要手动补写 limitvariantsmodalitiessmall_model 四个字段。

本文的定位:

  • 前置知识:假设你已用 CC Switch 把中转站供应商接入 OpenCode。接入流程见 OpenCode + CC Switch 协同部署:第三方中转站多设备同步方案
  • 核心内容:讲清自动注入为什么不够用,以及四个关键字段各自解决什么问题、何时必须手动配置、在 CC Switch 里怎么填。
  • 适用读者:多模态失效、上下文异常、标题不生成等问题的排查者。

目录


1. 自动注入为什么不够用

本节说明 models.dev 自动注入的前提假设,以及它在中转站场景下失效的根本原因。

OpenCode 依据 供应商标识 + 模型 ID 在 models.dev 查询模型能力清单,查到后自动注入。这套机制成立的前提是:中转站转发的模型,与 models.dev 记录的那一个是同一份部署

但第三方中转站本质是聚合层,它可能把同一个模型名转发到不同的上游云。同一个模型部署在不同的云上,参数并不一致。例如 models.dev 记载 gpt-5.6-sol 部署在 Amazon Bedrock,其 context272000output128000,而官网直连版本的上下文/输出可能是另一组数值。

这带来一个隐蔽问题:OpenCode 依赖 limit 判断上下文窗口大小、决定何时截断历史或触发压缩。如果自动注入的 limit 与中转站实际转发的部署不符,就会在错误的位置截断,或过早/过晚触发压缩。

结论:接入中转站的模型后,务必核对 models.dev 上该模型的实际部署与能力清单,需要时手动覆盖 limitvariantsmodalities 等字段。下面逐个说明。


2. limit:手动校准上下文与输出上限

limit 声明模型的上下文窗口(context)与单次输出上限(output)。当中转站上游与官方不一致时,按 models.dev 记录的对应部署手动填写:

"models": {
  "gpt-5.6-sol": {
    "name": "gpt-5.6-sol",
    "limit": {
      "context": 272000,
      "output": 128000
    }
  }
}

提示:数值以 models.dev 上该模型实际部署的那一行为准(注意区分 Bedrock、Azure、官方直连等不同来源),不要凭印象填官网默认值。


3. variants:推理强度档位

variants 为同一模型定义多组推理配置(如 reasoningEffort),用户可用 variant_cycle 键位在档位间快速切换,无需为每个强度建重复条目。OpenCode 为部分供应商内置了默认变体,但中转站模型往往查不到,需要手动补写:

"models": {
  "gpt-5.6-sol": {
    "name": "gpt-5.6-sol",
    "variants": {
      "low":    { "reasoningEffort": "low" },
      "medium": { "reasoningEffort": "medium" },
      "high":   { "reasoningEffort": "high" },
      "xhigh":  { "reasoningEffort": "xhigh" },
      "max":    { "reasoningEffort": "max" }
    }
  }
}

配好后即可通过 variant_cycle 键位在 low / medium / high / xhigh / max 之间循环切换推理强度。


4. modalities:即使用官方标识也可能失效

即使供应商标识填了标准的 openai,实测也可能出现 modalities 未生效的情况——图片、PDF 等多模态输入被退化为纯文本模式。这是否为 bug 尚不确定,但可靠的解决方案是手动在模型上显式声明 modalities

"models": {
  "gpt-5.6-sol": {
    "name": "gpt-5.6-sol",
    "modalities": {
      "input": ["text", "image", "pdf"],
      "output": ["text"]
    }
  }
}

手动声明后,多模态能力不再依赖自动注入是否成功,行为更可预期。


5. small_model:标题与摘要的隐藏依赖

small_model 是四个字段里最隐蔽的一个。它不影响主对话,却决定了会话标题和摘要能否生成,而且在 CC Switch 场景下有个必须绕开的重写陷阱。

5.1 问题现象与成因

OpenCode 生成会话标题/compact 对话摘要等轻量任务,用的不是你聊天的主模型,而是一个独立的 small_model。若不显式配置,OpenCode 会按 models.dev 目录从当前供应商自动挑一个”便宜的小模型”(Claude 系常被选中 claude-3-5-haiku 之类)。

问题在于:中转站分组里往往只有大模型,没有这些 haiku 小模型。此时标题生成请求会返回 model_not_found

{
  "error": {
    "code": "model_not_found",
    "message": "No available channel for model claude-3-5-haiku-20241022 under group ...",
    "type": "new_api_error"
  }
}

标题生成是后台异步任务,失败不报错、不重试,于是会话标题永远停在默认的 New session - <时间戳>

解决思路:显式指定一个中转站确实存在的模型作为 small_model。它是全局设置,与主模型无关——无论新会话用哪个供应商聊天,标题/摘要都统一走它。推荐用便宜且稳定的官方小模型,如 deepseek/deepseek-v4-flash

5.2 CC Switch 会重写全局配置文件

small_modelopencode.json顶层字段(与 providermodel 平级),不属于单个供应商。然而 CC Switch 会整体重写 ~/.config/opencode/opencode.json——每次在 CC Switch 中增删改 OpenCode 供应商,它都以自己的 SQLite 数据库为唯一真源重新生成整个文件,只保留 provider,把你手动加的顶层字段(如 small_model、自定义 model 等)全部冲掉。

OpenCode 供应商编辑页的「配置 JSON」区域(用于填写 npmoptionsmodels 等)只能编辑 provider.<供应商标识> 这一层级,无法编辑顶层字段。Claude Code / Codex 供应商有「共享配置面板」用于保留跨供应商的公共数据,但 OpenCode 供应商没有这个面板(截至 CC Switch v3.18.0)。

因此,直接手动编辑 ~/.config/opencode/opencode.jsonsmall_model 是无效的——下次在 CC Switch 里改供应商就会被冲掉。

5.3 正确做法:独立配置文件 + 环境变量

OpenCode 支持多文件配置合并,优先级为:远程 → 全局(CC Switch 管) → 自定义(OPENCODE_CONFIG) → 项目。把 small_model 放到一个 CC Switch 不碰的独立文件,用 OPENCODE_CONFIG 环境变量指向它,OpenCode 启动时会自动合并。

步骤 1:创建独立配置文件

平台推荐路径
WindowsC:\Users\<用户名>\.config\opencode\extra.json
macOS / Linux~/.config/opencode/extra.json

文件内容只放 small_model

{
  "$schema": "https://opencode.ai/config.json",
  "small_model": "deepseek/deepseek-v4-flash"
}

字段值格式为 provider_id/model_idprovider_id 是 CC Switch 配置里「供应商标识」那一栏的值(如 deepseek),model_id 是该供应商下定义的模型 ID(如 deepseek-v4-flash)。

步骤 2:设置环境变量

OPENCODE_CONFIG 指向上述文件。User 级环境变量在当前用户所有终端会话中全局生效,推荐使用。

Windows(PowerShell,当前用户权限即可):

[Environment]::SetEnvironmentVariable('OPENCODE_CONFIG', 'C:\Users\<你的用户名>\.config\opencode\extra.json', 'User')

设置后需重开终端窗口让新环境变量生效。

macOS / Linux(Bash/Zsh),在 ~/.bashrc~/.zshrc 末尾加入:

export OPENCODE_CONFIG="$HOME/.config/opencode/extra.json"

保存后执行 source ~/.bashrc(或 ~/.zshrc)使其立即生效,或重开终端。

步骤 3:验证

重启 OpenCode,开启新会话,标题应能正常生成。用以下命令确认环境变量已生效:

# Windows PowerShell
[Environment]::GetEnvironmentVariable('OPENCODE_CONFIG','User')
# macOS / Linux
echo $OPENCODE_CONFIG

5.4 多设备同步局限性

OPENCODE_CONFIG 环境变量和 extra.json 不在 CC Switch 的 R2 云同步范围内。换设备时需在新设备上手动执行上述两步。若需随 Git 走,可考虑项目级配置(在项目根目录放 opencode.jsonsmall_model,可提交仓库),但这样每个项目都要单独配置,不再是全局生效。

提示small_model 只对新会话生效,且需重启 OpenCode。历史会话已失败的标题不会自动补生成,需在客户端手动重命名。


6. 在 CC Switch 中填写模型属性

limitvariantsmodalities 属于 provider.<供应商标识>.models.<模型 ID> 层级,可以直接在 CC Switch 的供应商编辑页填写。CC Switch 的”模型配置”区域为每个模型 ID 提供了 Token 限制(上下文 / 输出)与模型属性(键值对)两块可视化输入:

字段位置键名值示例
Token 限制 · 上下文context272000
Token 限制 · 输出output128000
模型属性modalities{"input":["text","image","pdf"],"output":["text"]}
模型属性variants{"low":{"reasoningEffort":"low"},"medium":{"reasoningEffort":"medium"},"high":{"reasoningEffort":"high"},"xhigh":{"reasoningEffort":"xhigh"},"max":{"reasoningEffort":"max"}}

模型属性区的值为 JSON 片段,填好后保存即写入底层配置,等价于前几节 models.<id> 下的对应字段。

关键区别limitvariantsmodalities 在供应商层级,能通过 CC Switch GUI 填写并随 R2 同步;而 small_model 是顶层字段,CC Switch 无法编辑,只能用 §5.3 的环境变量方案。


7. 注意事项

自动能力注入不可全信

即使供应商标识正确,limitmodalitiesvariants 也可能未注入或与中转站实际部署不符。接入新模型后务必核对 models.dev 上对应部署,并在需要时手动覆盖。

small_model 必须显式配置且绕开 CC Switch 重写

不配置 small_model 时,OpenCode 自动选的小模型(常为 haiku 系)多半不在中转站分组内,标题/摘要生成会静默失败。而直接改全局 opencode.json 会被 CC Switch 冲掉,必须用独立文件 + OPENCODE_CONFIG 环境变量。详见 §5

顶层字段 vs 供应商字段

判断一个字段能否在 CC Switch 里填:看它在 opencode.json 的层级。provider.<id>.models 下的(limit/variants/modalities)可以;根对象下的(small_model/model/instructions 等)不行,需走独立配置文件。


8. 参考资料