# opencode-provider-switch (`ocswitch`) English README: `README_EN.md` `ocswitch` 是 OpenCode Provider Switch CLI,给 OpenCode 使用的本地代理。 它解决的问题很简单: - 你在 OpenCode 里只使用一个稳定的模型名,例如 `ocswitch/gpt-5.4` - `ocswitch` 在本地把这个别名映射到多个上游 `provider/model` - 按你配置的顺序依次尝试上游 - 如果主上游在响应开始前失败,自动切到下一个上游 当前实现只支持 OpenAI Responses 协议,也就是 `POST /v1/responses`,并且支持流式响应。 ## 适合什么场景 - 你有多个 OpenAI 兼容上游 - 你不想在 OpenCode 里频繁切换 provider 或模型入口 - 你希望用一个固定别名承接多个备用上游 - 你希望失败切换行为是确定的、可预期的 ## 当前能力 - 本地维护 `ocswitch` 配置文件:上游 provider、alias、监听地址 - 支持手动添加 provider,并自动发现其 `/v1/models` 模型列表 - 支持从 OpenCode 配置导入 `@ai-sdk/openai` 自定义 provider - 支持创建 alias,并按顺序绑定多个上游 target - 支持把 alias 同步到 OpenCode 的 `provider.ocswitch.models` - 支持本地代理 `POST /v1/responses` - 支持流式透传 - 支持首字节前失败切换 - 返回调试响应头,便于确认这次请求实际落到了哪个上游 ## 不支持的内容 - Anthropic 原生协议 - 多协议路由 - 自动按延迟、价格、提示词类型选路 - 中途流式拼接切换 - SQLite、管理后台、计费统计 - 完整接管 OpenCode 所有配置层 - 从 `auth.json` 自动导入 provider 定义 ## 安装 ```bash go build -o ocswitch ./cmd/ocswitch ``` 如果你只想临时运行,也可以直接: ```bash go run ./cmd/ocswitch --help ``` ## 5 分钟快速上手 ### 1. 添加上游 provider `ocswitch` 要求上游是 OpenAI 兼容接口,并且 `--base-url` 需要带上 `/v1`。默认会自动调用上游 `/v1/models` 拉取模型列表并缓存到本地配置,后续绑定 alias 时可用于校验模型名、减少手写 typo;如果发现失败,只会输出 warning,不会阻止连接信息保存。如果某些 provider 不开放该接口,可以显式加 `--skip-models` 仅保存连接信息。 ```bash ocswitch provider add --id su8 --base-url https://cn2.su8.codes/v1 --api-key sk-xxx ocswitch provider add --id codex --base-url https://api-vip.codex-for.me/v1 --api-key sk-yyy ``` 如果某个上游还需要额外请求头,可以重复传 `--header`: ```bash ocswitch provider add \ --id relay \ --base-url https://example.com/v1 \ --api-key sk-zzz \ --header "X-Custom-Token=abc" \ --header "X-Workspace=my-team" ``` 如果你之后想把这些额外 header 全部清空,可以显式传: ```bash ocswitch provider add --id relay --base-url https://example.com/v1 --clear-headers --skip-models ``` 查看当前 provider: ```bash ocswitch provider list ``` ### 2. 创建 alias,并绑定多个上游 target 下面这个例子表示:当你使用 `ocswitch/gpt-5.4` 时,优先走 `su8/gpt-5.4`,失败后再走 `codex/GPT-5.4`。推荐在未传 `--provider` 时直接把上游 target 写成 `Provider/Model`;旧的 `--provider` + `--model` 写法仍然保留作为兼容兜底,而且 `model` 本身即使包含 `/` 也不会被强行改判。 ```bash ocswitch alias add --name gpt-5.4 --display-name "GPT 5.4" ocswitch alias bind --alias gpt-5.4 --model su8/gpt-5.4 ocswitch alias bind --alias gpt-5.4 --model codex/GPT-5.4 ``` 查看当前 alias: ```bash ocswitch alias list ``` 注意: - target 的顺序就是失败切换顺序 - enabled alias 必须至少有一个可路由 target - `ocswitch alias bind` 在 alias 不存在时会自动创建一个 enabled alias ### 3. 先做一次静态检查 ```bash ocswitch doctor ``` `ocswitch doctor` 只做静态校验,不会真的请求上游,不会消耗额度。 它会检查: - 本地 `ocswitch` 配置能不能正常加载 - alias 是否引用了不存在的 provider - enabled alias 是否至少有一个可路由 target - 本地代理监听地址是否合理 - 默认会同步到哪个 OpenCode 配置文件 ### 4. 把 alias 同步到 OpenCode ```bash ocswitch opencode sync ``` 这个命令会做一件事:把当前可路由的 alias 列表同步进 OpenCode 的 `provider.ocswitch.models`。 注意:如果目标文件原本是 JSONC,`sync` 写回时会规范化成普通 JSON,因此注释和尾逗号不会保留。 默认行为: - 优先复用全局 OpenCode 配置文件:`opencode.jsonc` > `opencode.json` > `config.json` - 如果都不存在,就创建 `~/.config/opencode/opencode.jsonc` - 默认目标明确只看全局用户配置目录,不跟随 `OPENCODE_CONFIG_DIR` - 只更新 `provider.ocswitch` - 不会修改顶层 `model` - 不会修改顶层 `small_model` 如果你希望顺手把默认模型也切到 `ocswitch`,需要显式指定: ```bash ocswitch opencode sync --set-model ocswitch/gpt-5.4 ``` 如果你还有小模型 alias,也可以这样: ```bash ocswitch opencode sync \ --set-model ocswitch/gpt-5.4 \ --set-small-model ocswitch/gpt-5.4-mini ``` 先预览不写入: ```bash ocswitch opencode sync --dry-run ``` 写到指定 OpenCode 配置文件: ```bash ocswitch opencode sync --target /path/to/opencode.jsonc ``` ### 5. 启动本地代理 ```bash ocswitch serve ``` 默认监听地址: - `127.0.0.1:9982` - 本地 API Key:`ocswitch-local` 启动后,本地代理地址是: ```text http://127.0.0.1:9982/v1 ``` ### 6. 在 OpenCode 里使用 完成 `ocswitch opencode sync` 后,你应该能在 OpenCode 里看到 `ocswitch/`。 例如: - `ocswitch/gpt-5.4` 如果你执行了: ```bash ocswitch opencode sync --set-model ocswitch/gpt-5.4 ``` 那么 OpenCode 默认模型也会直接切到这个 alias。 ## 直接验证本地代理 如果你想先不走 OpenCode,直接验证 `ocswitch` 是否能正常代理,可以自己发一个请求: ```bash curl -sN -X POST http://127.0.0.1:9982/v1/responses \ -H "Authorization: Bearer ocswitch-local" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-5.4","stream":true,"input":"hello"}' ``` 注意这里请求体里的 `model` 可以直接写 alias 本身,例如 `gpt-5.4`;也兼容 `ocswitch/gpt-5.4` 这种带前缀的写法。 因为 `ocswitch/gpt-5.4` 是 OpenCode 侧常见的模型选择写法;真正路由到本地时,工具会统一解析成 alias 自身。 ## 从现有 OpenCode 配置导入 provider 如果你原来已经在 OpenCode 里配置过一些自定义 provider,可以直接导入: ```bash ocswitch provider import-opencode ``` 或者指定导入源: ```bash ocswitch provider import-opencode --from ./examples/opencode.jsonc ``` 支持范围只有这一类: - `npm: @ai-sdk/openai` - 有 `options.baseURL` - `options.apiKey` 可以为空;当前静态校验不会单独拦截空上游 API Key,这类问题通常会在真实请求上游时暴露 注意: - 这不是完整迁移工具 - 默认导入源也只看全局用户配置目录,不跟随 `OPENCODE_CONFIG_DIR` - 如果你要导入别的 OpenCode 配置文件,请显式传 `--from` - 导入时仍要求 `baseURL` 满足 `/v1` 约束;不合法的 provider 会被跳过 - 如果源 OpenCode 配置已经声明 `models`,导入时会一并保留,但这类导入值默认只作为展示/迁移信息,不会像主动发现到的 catalog 那样做硬校验 - 如果你修改了 provider 的连接信息,但本次 discovery 被跳过、失败或返回空列表,旧 catalog 会降级成“不可信元数据”:仍保留在配置里,但不会继续用于强校验 - 如果你的 provider 不开放 `/v1/models`,后续可用 `ocswitch provider add --skip-models ...` 仅保存连接信息 - `--overwrite` 会更新导入到的基本连接信息,但仍保留本地的 disabled 状态、额外 header,以及仍然可信的 discovered catalog - 如果你的旧配置依赖额外自定义 header,首次导入后仍可能需要自己用 `ocswitch provider add --header ...` 补齐 - `ocswitch` 自己不会被反向导入 覆盖已存在的 provider: ```bash ocswitch provider import-opencode --overwrite ``` ## 常用命令 ### provider 添加或更新 provider: ```bash ocswitch provider add --id --base-url --api-key ocswitch provider add --id --base-url --api-key "" ocswitch provider add --id --base-url --clear-headers ocswitch provider add --id --base-url --skip-models ``` 如果你需要把已保存的上游 API key 清空,显式传 `--api-key ""` 即可。 如果你需要把已保存的额外 header 清空,显式传 `--clear-headers` 即可。 查看 provider: ```bash ocswitch provider list ``` 禁用 provider: ```bash ocswitch provider disable ``` 重新启用 provider: ```bash ocswitch provider enable ``` 删除 provider: ```bash ocswitch provider remove ``` 注意:删除 provider 不会自动帮你清理 alias 里的引用。引用还在的话,`ocswitch doctor` 会报错。 ### alias 创建或更新 alias: ```bash ocswitch alias add --name ``` 给 alias 追加一个 target: ```bash ocswitch alias bind --alias --model / ocswitch alias bind --alias --provider --model ``` 解绑 target: ```bash ocswitch alias unbind --alias --model / ocswitch alias unbind --alias --provider --model ``` 查看 alias: ```bash ocswitch alias list ``` 删除 alias: ```bash ocswitch alias remove ``` ### 其他 静态检查: ```bash ocswitch doctor ``` 启动代理: ```bash ocswitch serve ``` 同步到 OpenCode: ```bash ocswitch opencode sync ``` 全局帮助: ```bash ocswitch --help ocswitch provider --help ocswitch alias --help ocswitch opencode sync --help ``` ## 配置文件说明 本地 `ocswitch` 配置文件默认路径: - 如果设置了 `OCSWITCH_CONFIG`,优先使用它 - 否则使用 `$XDG_CONFIG_HOME/ocswitch/config.json` - 再否则使用 `~/.config/ocswitch/config.json` 也可以对每个命令显式指定: ```bash ocswitch --config /path/to/config.json doctor ``` 命令级行为、默认值、写入范围与副作用,以对应命令的 `--help` 为准;README 主要保留快速上手与背景说明。 一个最小配置示例: ```json { "server": { "host": "127.0.0.1", "port": 9982, "api_key": "ocswitch-local" }, "providers": [ { "id": "su8", "name": "SU8", "base_url": "https://cn2.su8.codes/v1", "api_key": "sk-xxx" }, { "id": "codex", "name": "Codex", "base_url": "https://api-vip.codex-for.me/v1", "api_key": "sk-yyy", "disabled": true } ], "aliases": [ { "alias": "gpt-5.4", "display_name": "GPT 5.4", "enabled": true, "targets": [ { "provider": "su8", "model": "gpt-5.4", "enabled": true }, { "provider": "codex", "model": "GPT-5.4", "enabled": true } ] } ] } ``` ## 失败切换规则 `ocswitch` 的切换规则很保守,也很容易理解。 会切换到下一个 target 的情况: - 连接失败 - DNS / 网络错误 - 上游在返回首字节前超时或断开 - 上游返回 `429` - 上游返回 `5xx` 不会切换的情况: - alias 不存在 - alias 被禁用 - alias 没有可用 target - 上游返回 `400` - 上游返回 `401` - 上游返回 `403` - 上游返回 `404` - 响应已经开始向客户端写出后才出错 重点是: - 只有在“还没向下游写出任何字节”之前,才允许切换 - 一旦开始向客户端输出流,当前上游就锁定了 - 不支持中途把一半流接到另一个 provider 上继续输出 ## 调试响应头 当请求已经选定某个上游并开始向客户端返回该次尝试的结果时,响应里会附带这些头: - `X-OCSWITCH-Alias` - `X-OCSWITCH-Provider` - `X-OCSWITCH-Remote-Model` - `X-OCSWITCH-Attempt` - `X-OCSWITCH-Failover-Count` 你可以用它们确认: - 本次请求命中了哪个 alias - 实际走了哪个 provider - 实际转发成了哪个上游 model - 这是第几次尝试 - 前面已经失败切换过几次 ## 常见问题 ### 为什么 `opencode models` 里看不到 `ocswitch/`? 先检查这几件事: 1. 你是否执行过 `ocswitch opencode sync` 2. 你的 alias 是否是 enabled 状态 3. alias 是否至少绑定了一个可路由 target 4. alias 绑定的 provider 是否都被禁用了 5. OpenCode 当前实际使用的配置文件,是否就是 `ocswitch opencode sync` 写入的那个文件 6. 执行一次 `ocswitch doctor`,看输出里的 `opencode config target` ### 为什么 `ocswitch doctor` 报 alias 没有可用 target? 因为当前实现要求:只要 alias 是 enabled,就必须至少有一个可路由的 target。 可路由的意思是同时满足: - target 自己是 enabled - target 引用的 provider 存在且没有被禁用 你可以: - 给它绑定 target - 重新启用被禁用的 provider - 或者把这个 alias 改成 disabled 后再保存 ### 为什么要禁用 provider,而不是把 alias target 也改成 disabled? 因为 provider 可能被多个 alias 复用。 `ocswitch provider disable` 只会让路由层在 failover 时自动跳过这个 provider,不会改写 alias 里的 target 状态,这样重新启用 provider 时不会和 alias 上原有的启用关系打架。 ### 删除 provider 后为什么还有报错? 因为 alias 里的 target 还是旧引用。需要继续执行: ```bash ocswitch alias unbind --alias --model / ocswitch alias unbind --alias --provider --model ``` ### 本地代理鉴权是什么? 默认是静态 key: ```text ocswitch-local ``` OpenCode 在 `provider.ocswitch.options.apiKey` 里会使用这个值。直接手工请求本地代理时,也要带上这个 key。 ## 安全说明 - 默认只监听 `127.0.0.1` - 上游凭据保存在本地 `ocswitch` 配置文件中 - 本项目当前没有做多用户或远程网络安全保证 所以请把本地配置文件当成敏感文件处理。 ## 英文版 README 原英文版文档已保存在: - `README_EN.md`