OpenCode 自定义 Provider 配置

最后更新: 2026-07-16

OpenCode 原生支持在 opencode.json 里声明自定义 provider:npm 填 @ai-sdk/openai-compatible,options.baseURL 填 https://api.kkaiapi.com/v1,Key 用 {env:...} 模板从环境变量读取。声明的每个模型都会出现在 /models 选择器里,免翻墙、一个 Key,Claude、GPT、DeepSeek、Kimi 随时切换。

先说结论:一个 provider 块,一个 Key

OpenCode 原生支持自定义 OpenAI 兼容 provider。在 opencode.json 里加一个 provider 条目,npm 字段填 @ai-sdk/openai-compatible,options.baseURL 填 https://api.kkaiapi.com/v1,Key 用 {env:KKAIAPI_KEY} 模板从环境变量读取,models 里列出想用的模型 ID。顶层 model 字段填成 "kkaiapi/<模型ID>",OpenCode 的整个 Agent 循环就走这个网关。 这是 OpenCode 文档记录在案的自定义 provider 路径,不是套壳或魔改。配置文件在项目根目录的 opencode.json,或者全局的 ~/.config/opencode/opencode.json,两者会合并,所以 provider 块声明一次,所有项目都能复用。

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "kkaiapi": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "kkaiapi",
      "options": {
        "baseURL": "https://api.kkaiapi.com/v1",
        "apiKey": "{env:KKAIAPI_KEY}"
      },
      "models": {
        "claude-sonnet-5": { "name": "Claude Sonnet 5" }
      }
    }
  },
  "model": "kkaiapi/claude-sonnet-5"
}

OpenCode 怎么解析 provider 和模型

OpenCode(GitHub 上的 anomalyco,186K 星标左右,是星标数最高的终端编码 Agent 之一)的 provider 层建立在 Vercel AI SDK 之上。provider 块里的 npm 字段决定 OpenCode 用哪个 SDK 包跟这个 provider 对话:@ai-sdk/openai-compatible 说的是标准 /v1/chat/completions 协议,@ai-sdk/openai 说的是 OpenAI 自家的 /v1/responses 协议。kkaiapi 提供的是标准 chat completions 接口,所以必须选 openai-compatible;选错包是这套配置最常见的翻车点。 模型按 "provider/model" 的组合寻址,provider ID 是你在 provider 块里起的名字(上面是 kkaiapi),model ID 是 models 映射里的键,默认模型因此写成 "kkaiapi/claude-sonnet-5"。声明的每个模型都会出现在 TUI 里的 /models 选择器,会话中途随时切换。 有一点值得记住:对自定义 provider 来说,models 映射就是一份白名单。内置 provider 自带已知目录,但 OpenCode 没法自动探测自定义端点有哪些模型,所以只有显式声明过的 ID 才能被寻址。当 baseURL 背后的网关同时提供 Claude、GPT、DeepSeek、Kimi 这些模型时,每个模型声明一条,/models 选择器就变成了一个跨厂商切换台。

完整配置:全局配置、项目配置、单模型限额

干净的做法是把 provider 只声明一次,放进全局配置 ~/.config/opencode/opencode.json,每个项目自己的 opencode.json 只留模型选择和 Agent 设置这些按仓库变化的东西。OpenCode 是合并配置文件而不是覆盖,所以项目文件能一直很小,provider 块不会被重复声明。 {env:KKAIAPI_KEY} 模板在加载时从环境变量解析,这样密钥不会出现在任何可能被提交的文件里。把这个变量写进 shell 配置文件,这样每个启动 OpenCode 的终端会话都能读到。 每个模型条目还能带一个 limit 对象,写上下文和输出 token 上限。这个声明比看起来重要:OpenCode 用这个上下文数字决定会话什么时候需要摘要压缩,一个没声明限额的长上下文模型反而会被过度保守地压缩。把 limit.context 设成模型真实支持的数值,长会话会在更合理的时机才压缩,而不是过早。

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "kkaiapi": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "kkaiapi",
      "options": {
        "baseURL": "https://api.kkaiapi.com/v1",
        "apiKey": "{env:KKAIAPI_KEY}"
      },
      "models": {
        "claude-opus-4-8":   { "name": "Claude Opus 4.8",   "limit": { "context": 1000000, "output": 32000 } },
        "claude-sonnet-5":   { "name": "Claude Sonnet 5",   "limit": { "context": 1000000, "output": 64000 } },
        "gpt-5.5":           { "name": "GPT-5.5" },
        "gpt-5.3-codex-spark": { "name": "GPT-5.3 Codex Spark" },
        "kimi-k2.7-code":    { "name": "Kimi K2.7 Code" }
      }
    }
  },
  "model": "kkaiapi/claude-sonnet-5",
  "small_model": "kkaiapi/kimi-k2.7-code"
}

model 与 small_model 怎么分工

实际工作流是把主槽位固定在你信任的编辑模型上,候选模型放到真实会话里轮转对比,而不是只看跑分:一下午对着自己代码库的真实 diff 比排行榜更说明问题。走同一个网关让每个候选模型都是一行改动,按 Key 的用量视图会显示每次试验实际花了多少钱。

  • model 驱动主 Agent 循环:读文件、规划改动、写 diff、跑工具。这个槽位承载最长的上下文,也是真正做工程活的地方,claude-sonnet-5、claude-opus-4-8、gpt-5.5 这类旗舰编码模型放这里
  • small_model 处理会话标题生成这类轻量任务,调用频繁但从不承担编码工作,选一个便宜快速的 ID 就够,没必要在标题上烧旗舰 token
  • kimi-k2.7-code、gpt-5.3-codex-spark 这类编码专精模型即使不是默认值也值得声明:重构密集的会话切过去只是一次 /models 选择,不是改配置
  • 因为 model 和 small_model 两个槽位对着同一个 provider 块传 provider/model 字符串,同一个会话里主模型和小模型可以来自不同厂商,这是单一厂商 Key 做不到的

主力模型价格

价格按 kkaiapi 中文区实时价格计算,单位 ¥/1M tokens(输入/输出),完整价目见价格页。

实时价格以 /pricing 为准
模型模型 IDkkaiapi 价格(输入/输出)
Claude Opus 4.8claude-opus-4-8¥5 / ¥25
Claude Sonnet 5claude-sonnet-5¥3 / ¥15
GPT-5.5gpt-5.5¥4 / ¥25
GPT-5.3 Codex Sparkgpt-5.3-codex-spark¥1.5 / ¥12
Kimi K2.7 Codekimi-k2.7-code¥5.85 / ¥24.3
DeepSeek V4 Prodeepseek-v4-pro¥3 / ¥6

OpenCode 自定义 provider 专属的翻车点

SDK 包选错。@ai-sdk/openai 会往 /v1/responses 发请求,kkaiapi 这类 chat-completions 网关对这个路径只会报错。如果第一次请求失败的报错像是协议或路由问题而不是鉴权问题,先查 npm 字段是不是精确写的 @ai-sdk/openai-compatible。 模型不在选择器里。自定义 provider 的模型只有声明过才存在,models 键名打错字,或者以为加了其实没加的 ID,不会出现在 /models 里。ID 是精确字符串,包括版本后缀,以网关 /v1/models 的返回列表为准。 {env:...} 没解析上。这个模板从启动 OpenCode 的那个进程的环境变量里读。在一个终端里 export 的 Key,另一个终端启动的 OpenCode 实例、或者没 source 过 profile 的桌面启动器都看不到,把 export 写进 shell 配置文件而不是一次性会话里。 配置合并带来的意外。因为全局配置和项目配置会合并,项目里的 opencode.json 如果把 model 设成了别的 provider,会悄悄覆盖你的全局默认值,旧项目里残留的 provider 块也可能挡住预期行为。路由看起来不对时,先读两份配置文件,别急着怀疑网关。 baseURL 漏了 /v1。SDK 会在你给的 base 后面自动拼接 /chat/completions 这类路径,所以 https://api.kkaiapi.com/v1 是对的,裸域名不行。一个配置看起来都对却连不上或 404,几乎都是这个原因。

验证接入与排查首次会话

开始会话前先看一眼网关实际提供哪些模型,/v1/models 返回的 ID 就是 models 映射里键名必须匹配的精确字符串。 首次会话的失败很规律。401 意味着 KKAIAPI_KEY 没有被启动 OpenCode 的那个进程看到,在同一个终端里 echo 一下这个变量。网关返回模型不存在,说明声明的键名和网关提供的 ID 不匹配,包括版本后缀。provider 完全没出现,先校验一次 JSON 语法,一个多余的逗号或错位的括号就能让整个文件读取失败,OpenCode 会退回默认配置。 请求跑通之后,kkaiapi 控制台会显示每次请求的模型、token 数和花费。编码 Agent 是长上下文、多轮次的负载,看清楚哪些会话和哪些模型在消耗 token,才知道主槽位这个价钱花得值不值。

curl -s https://api.kkaiapi.com/v1/models \
  -H "Authorization: Bearer $KKAIAPI_KEY" | grep -E "claude|gpt|kimi|deepseek"

常见问题

OpenCode 能通过一个自定义 provider 同时用 Claude、GPT、Kimi 吗?

能。自定义 provider 就是一个 baseURL 加一份模型白名单。当端点背后同时提供多个厂商的模型时,每个 ID 声明一条,声明过的模型都会出现在 /models 选择器里,归在同一个 provider 和 Key 下,会话中途可切换。

API Key 填在 opencode.json 的哪里?

填在 options.apiKey 里,用环境变量模板,比如 "{env:KKAIAPI_KEY}"。这个模板在加载时才解析,密钥字面值不会留在配置文件里。把变量导出到 shell 配置文件,这样每个启动 OpenCode 的终端都能继承到。

provider 块应该放在全局配置还是项目配置?

放全局,~/.config/opencode/opencode.json。OpenCode 会合并配置文件,所以 provider 只声明一次,每个项目只决定用哪个模型,仓库里不用管密钥配置,也不会有重复的 provider 块彼此漂移。

为什么我的模型没出现在 /models 选择器里?

自定义 provider 的模型必须显式声明,OpenCode 无法自动探测自定义端点。检查 models 映射里键名是不是和 ID 完全一致(包括版本后缀),ID 直接从网关的 /v1/models 返回结果里复制,别凭记忆手打。

@ai-sdk/openai-compatible 和 @ai-sdk/openai 有什么区别?

@ai-sdk/openai-compatible 说标准 /v1/chat/completions 协议,多厂商网关走的都是这个。@ai-sdk/openai 说 OpenAI 自家的 /v1/responses 协议。接 kkaiapi 用 openai-compatible,另一个包会把请求发到网关不支持这个用途的路径上。

声明的上下文限额真的有影响吗?

有。OpenCode 用 limit.context 决定会话什么时候需要压缩。一个长上下文模型不声明限额,会话会被过早摘要压缩,把 limit.context 和 limit.output 设成模型真实支持的数值即可。