四个住在终端里的开源编程 agent,同一个问题:把模型调用改走 Router One 之后,到底变了什么?工具本身没有变——Goose 从 CLI 或桌面应用运行扩展(MCP 服务器)和会话;OpenCode 把模型 provider 当配置来处理;Qwen Code 用 /auth 和 /model 切换 provider 与模型;Aider 编辑文件并把每次改动提交到 git。变的是底下那次请求:一把 sk- Key,/models 里的精确模型 id,以及每次模型请求在 Dashboard → Logs 里的一条 Trace——模型、tokens、花费、延迟、状态和 request_id。网关负责请求,循环仍归工具。
一句话结论。Goose:内置 OpenAI provider,OPENAI_HOST 填主机根地址、OPENAI_BASE_PATH 保持默认;或者写一个自定义 provider 的 JSON 文件。OpenCode:opencode.json 里一段基于 @ai-sdk/openai-compatible 的 provider 配置,模型以 router-one/<id> 选用。Qwen Code:.qwen/.env 里三个 OPENAI_* 变量,或者在 modelProviders.openai 下每个模型一条。Aider:OPENAI_API_BASE、OPENAI_API_KEY,再加 openai/ 前缀规则。四者发出的都是 Chat Completions,所以目录里的每个聊天模型从任何一个工具都能到达。
对照表
| 工具 | 安装与运行位置 | 怎样指向网关 | 实际协议 | 能到达的目录系列 | 留在工具侧的部分 |
|---|---|---|---|---|---|
| Goose | CLI(Homebrew 或下载脚本)和桌面应用 | OPENAI_HOST=https://api.router.one、OPENAI_API_KEY、GOOSE_PROVIDER=openai、GOOSE_MODEL=<id>;或 ~/.config/goose/custom_providers/ 下的一个 JSON 文件 | Chat Completions(OPENAI_BASE_PATH 默认 v1/chat/completions);gpt-5、gpt-6 与 o 系列名称走 /v1/responses | 全部聊天模型 | 扩展(MCP 服务器)、GOOSE_MODE 工具审批、会话、GOOSE_MAX_TURNS |
| OpenCode | 终端 TUI、桌面应用或 IDE 扩展(npm、Homebrew 或安装脚本) | opencode.json 的 provider.router-one:npm 填 @ai-sdk/openai-compatible,options.baseURL 带 /v1,options.apiKey 用 {env:ROUTER_ONE_API_KEY},models 逐个列出 | Chat Completions(换成 @ai-sdk/openai 就是 Responses) | 全部聊天模型;可选再加一条 @ai-sdk/anthropic provider 让 Claude id 走 /v1/messages | 工具、mcp 服务器、permission 规则、agent |
| Qwen Code | Node.js 22+ 的 CLI(npm 或 Homebrew) | .qwen/.env 里的 OPENAI_API_KEY、OPENAI_BASE_URL=https://api.router.one/v1、OPENAI_MODEL;或 ~/.qwen/settings.json 的 modelProviders.openai[] | 经官方 OpenAI Node SDK 发送 Chat Completions(openai-responses 发往 /v1/responses) | 全部聊天模型;Responses 原生服务 GPT 系列与 DeepSeek id | 内置工具、/mcp、/approval-mode、/resume、/compress |
| Aider | Python CLI(aider-install),在你的仓库目录里运行 | OPENAI_API_BASE=https://api.router.one/v1、OPENAI_API_KEY、aider --model openai/<id> | 经 LiteLLM 发送 Chat Completions | 全部聊天模型 | 仓库地图、git 自动提交、/undo、/diff、生成提交信息的 --weak-model |
四者说的都是 Chat Completions,而 /v1/chat/completions 服务目录里的所有聊天模型——GPT、Claude、Gemini、Grok 和 DeepSeek 系列——所以同一个 anthropic/claude-sonnet-5 或 google/gemini-3.8-flash id(连同自带前缀)在下面每份配置里都能用。最后一列是边界:里面没有一项跑在网关上。唯一的例外是 Goose:base path 保持默认时,它的 OpenAI provider 会自行把 gpt-5、gpt-6 与 o 系列名称发到 /v1/responses,而 Router One 对当前上架的 GPT 系列 id 原生提供该端点,所以设置本身不用改。
Goose:OPENAI_HOST 填主机根地址,GOOSE_MODEL 填精确 id
Goose 内置的 OpenAI provider 可以连接「任何 OpenAI 兼容端点」,文档对代理和网关的写法说得很明确:OPENAI_HOST 填不带尾部路径的根地址,OPENAI_BASE_PATH 填端点实际服务的路径,而它的默认值 v1/chat/completions 正是需要的那个——所以主机根地址 https://api.router.one 就是全部设置。GOOSE_PROVIDER 和 GOOSE_MODEL 两个环境变量会在当前进程里覆盖配置文件,GOOSE_MODEL 原样发出,所以填目录里的精确 id。还有一条规则叠在默认值之上:名字匹配 gpt-5、gpt-6 或 o 系列的模型(openai/gpt-5.5 就符合),Goose 的 OpenAI provider 会按 Responses 请求发到 /v1/responses(store 为 false),其他 id 一律发 Chat Completions;如果自定义的 OPENAI_BASE_PATH 含有 responses,所有模型都会被强制走 Responses,Claude、Gemini 和 Grok 的 id 会收到 HTTP 400,所以这个路径不要动。
export OPENAI_HOST=https://api.router.one
export OPENAI_API_KEY=sk-your-router-one-key
export GOOSE_PROVIDER=openai
export GOOSE_MODEL=<exact-model-id-from-/models>
goose session
goose configure 会把 provider 和模型写进 ~/.config/goose/config.yaml、把 Key 存进系统密钥库,但文档写明它不接受自定义模型名——anthropic/claude-sonnet-5 这类目录 id 不在它的列表里——所以 GOOSE_MODEL 要在环境变量里设,或直接改 config.yaml。另一条路是自定义 provider:在 ~/.config/goose/custom_providers/ 放一个 JSON 文件,engine 填 openai,base_url 填完整的 chat-completions URL,api_key_env 填存放 Key 的变量名,再加一个 models 数组;之后它会出现在 provider 列表里,goose run --provider <name> 可为单次运行选用它。桌面版在 Settings → Models → Configure providers → Add Custom Provider 里填同样几项,类型选「OpenAI Compatible」。
坑在这里。 404 说明 OPENAI_BASE_PATH 与端点对不上——保持默认即可。401 且提示 No api key passed in 说明 Key 没被加载:goose 不从 config.yaml 读取 provider 的 Key,环境变量则优先于已存储的密钥。goose 依赖工具调用,模型要选支持工具调用的。指南:Goose 接入 Router One。
OpenCode:一段 provider 配置,模型写成 router-one/<id>
OpenCode 从 opencode.json 读取自定义 provider——项目里一份,或全局的 ~/.config/opencode/opencode.json。配置块里的 npm 指定说哪种协议的包:文档把 @ai-sdk/openai-compatible 对应到 /v1/chat/completions,把 @ai-sdk/openai 对应到 /v1/responses,所以用前者。options.baseURL 填带 /v1 的地址,options.apiKey 用 {env:变量名} 语法读 Key,想进选择器的每个模型都是 models 里的一个键——精确的目录 id——选用时写成 provider-id/model-id。
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"router-one": {
"npm": "@ai-sdk/openai-compatible",
"name": "Router One",
"options": {
"baseURL": "https://api.router.one/v1",
"apiKey": "{env:ROUTER_ONE_API_KEY}"
},
"models": {
"<exact-model-id-from-/models>": { "name": "<选择器里显示的名称>" }
}
}
},
"model": "router-one/<exact-model-id-from-/models>"
}
TUI 里的 /models 命令列出你声明的模型,顶层 model 字段固定其中一个。其余能力都留在 OpenCode:mcp 下的 MCP 服务器(本地 command 或远程 url),文档写明其工具「自动与内置工具一起提供给模型」;permission 规则把每个动作——edit、bash、webfetch、代表子 agent 的 task——判定为 allow、ask 或 deny,agent 级规则优先。
坑在这里。 model 字符串里的 provider id 必须与 provider 下的键名一致;没在 models 里声明的模型对 OpenCode 来说不存在;启动 opencode 的 shell 里没有该变量时,{env:ROUTER_ONE_API_KEY} 会变成空字符串而不是报错。想让 Claude id 走原生 Messages 路径,就用另一个 id 再加一条 @ai-sdk/anthropic 的 provider,base URL 同样填 /v1——网关接受该包发送的 x-api-key 头。指南:OpenCode 接入 Router One;与两个厂商 CLI 的对比见 OpenCode vs Claude Code vs Codex CLI。
Qwen Code:.qwen/.env 里三个变量,或每个模型一条 modelProviders
Qwen Code 的 OpenAI 兼容认证类型是 openai,读取 OPENAI_API_KEY、OPENAI_BASE_URL 和 OPENAI_MODEL(别名 QWEN_MODEL)。base URL 带 /v1,请求经官方 OpenAI Node SDK 发出,协议就是 Chat Completions。文档推荐把这些变量放在项目的 .qwen/.env 里——与其他工具的变量隔离,并且不要提交到 git。它只加载找到的第一个 .env 文件(先 .qwen/.env、.env,再是 ~ 下的同名两个),shell 里 export 的值覆盖它们全部。
# .qwen/.env
OPENAI_API_KEY=sk-your-router-one-key
OPENAI_BASE_URL=https://api.router.one/v1
OPENAI_MODEL=<exact-model-id-from-/models>
想在 /model 选择器里保留多个目录 id,就在 ~/.qwen/settings.json 的 modelProviders.openai 下声明——每条带 id(发给 API 的模型 id)、显示用的 name、envKey(存放 Key 的变量名,不是 Key 本身)和 baseUrl——并配上 security.auth.selectedType: "openai" 和一个与某条 id 一致的 model.name;运行中的会话会直接读到修改,/model <model-id> 立即切换。
坑在这里。 openai-responses 是另一种认证类型,以直接 HTTP 请求发往 /v1/responses——这个端点在 Router One 上只对当前上架的 GPT 系列和 DeepSeek id 原生可用,其他 id 会在任何模型运行之前收到 HTTP 400,所以 Claude、Gemini、Grok 的 id 要放在 openai 下。id 和 baseUrl 都相同的两条只保留第一条,并给出警告。generationConfig.maxRetries 触发的重试是独立请求,各有各的 Trace。审批模式是工具自己的:/approval-mode yolo 会不经确认地执行 shell 命令、文件写入和网络请求,文档要求只在可信或可丢弃的环境里使用。指南:Qwen Code 接入 Router One。
Aider:OPENAI_API_BASE、OPENAI_API_KEY 和一层 openai/ 前缀
Aider 关于 OpenAI 兼容 API 的文档就是两个变量加一个参数:OPENAI_API_BASE 填带 /v1 的端点,OPENAI_API_KEY,以及 aider --model openai/<model-name>。Aider 通过 LiteLLM 连接模型,openai/ 前缀是协议选择器——「对 OPENAI_API_BASE 使用 OpenAI 兼容协议」——并且会被吃掉,所以后面原样接目录 id。
export OPENAI_API_BASE=https://api.router.one/v1
export OPENAI_API_KEY=sk-your-router-one-key
aider --model openai/<exact-model-id-from-/models>
因为只会吃掉一层前缀,GPT 系列的 id 要写两遍——openai/openai/gpt-5.5——Claude 的 id 则连同自带前缀写一遍——openai/anthropic/claude-sonnet-5。命令行上 --openai-api-base 和 --openai-api-key 起同样的作用,Aider 从 git 根目录加载的 .env 文件也可以放这两个变量。Aider 拿到响应之后做的事都留在 Aider:每次改动请求都附带仓库地图(--map-tokens,默认 1k tokens),每次编辑一次 git 提交,以及 /undo 和 /diff。
坑在这里。 启动时会看到 Unknown context window size and costs, using sane defaults——Aider 没有网关 id 的元数据,文档说这个警告可以忽略,因此它显示的费用不是账单。提交信息是单独的模型请求:Aider 把 diff 和聊天记录发给 --weak-model,想让这些请求也走同一把 Key、进同一份 Logs,就给这个参数同样的 openai/ 写法;--no-auto-commits 可关闭自动提交。指南:Aider 接入 Router One。
网关做什么、不做什么
Router One 负责模型请求。每一次请求它都在 Dashboard → Logs 记一条 Trace——模型、输入输出 tokens、花费、延迟、HTTP 状态、request_id——并执行 Key 上的上限:maxSpend,以及对付失控循环的 rateLimit 和 tokenLimitTpm。id 发到不服务它的端点,会在调用任何模型之前被 HTTP 400 拒绝;各端点接受和拒绝的清单见 API 兼容性事实页,其余状态码见错误码页。
它不执行工具、不保存会话,也不审批动作。Goose 的扩展、OpenCode 的 permission 规则和 MCP 服务器、Qwen Code 的审批模式、Aider 的 git 提交——全都跑在你的机器上;网关每一轮只看到一次 Chat Completions 请求,中间发生的事它看不到。Trace 不显示上游名称或内部路由尝试,单独一个 HTTP 200 也不能证明流已经完整结束(见流式输出)。账本记什么,见成本追踪页;厂商 CLI 的两个变量配置见 CLI 配置指南、Claude Code 国内接入和 Codex CLI 国内使用。
给一次编程会话定预算
上面每个工具都有循环上限,但没有一个是花费上限:GOOSE_MAX_TURNS 限制无人干预时的轮数(默认 1000),OpenCode 的 doom_loop 权限拦截重复的相同调用,Qwen Code 的 maxRetries 限制单次请求的重试次数。重试和工具调用循环叠在这些上限之上:每一次到达网关的尝试都是独立的请求、Trace 和费用,agent 读到的每个工具结果都会作为新请求再发给模型。工具侧的费用数字——Aider 的估算、TUI 里显示的任何数字——都不是你实际被扣的钱。
真正止损的上限在 Key 上:一个工具一把 Key,各设 maxSpend,失控的会话到顶即停、返回 402,钱包和其他 Key 不受影响。按 request_id 核账——按时间窗与精确模型过滤 Logs,把条数对上会话的轮数,保留失败 Trace 的 request_id。被取消的流式请求既不会被悄悄扣费,也不会被悄悄记零:在 POST /v1/chat/completions 与 POST /v1/responses 上,客户端中途取消的请求——例如一轮进行到一半时按下 Ctrl-C——记为 HTTP 499 client_cancelled,只按上游实际报告的用量计费。
到 router.one 为每个工具建一把 Key,各设上限;/integrations 列出全部接入指南,所有编程工具走同一个网关是总览。换完配置之后工具仍然报连接错误,说明端点其实没有换掉——先看 API 连接排错。
常见问题
哪个工具用一把 Key 能到达最多模型系列? 四个一样多:每个都发 Chat Completions(只有 Goose 会把 gpt-5、gpt-6 与 o 系列名称改送 /v1/responses,网关对 GPT 系列 id 原生提供该端点),而 /v1/chat/completions 服务目录里的所有聊天模型——GPT、Claude、Gemini、Grok 和 DeepSeek 系列。区别只在 id 填在哪里:Goose 的 GOOSE_MODEL、OpenCode models 里的键、Qwen Code 的 OPENAI_MODEL 或 modelProviders 的 id、Aider 的 openai/ 前缀之后。
一把 Key 能同时接四个工具吗? 能。本文每份配置都接受同一把 Router One Key,每次调用使用同一个钱包。分开的 Key 让每个工具各有自己的 maxSpend 上限,一个失控的会话花不掉另一个工具要用的钱。
同一个 GPT 系列 id 为什么在每个工具里写法不同? 每个工具对同一个实际发送的 id 有自己的命名规则:OpenCode 前面加自己的 provider id(router-one/openai/gpt-5.5),Aider 吃掉一层 openai/(openai/openai/gpt-5.5),Goose 和 Qwen Code 原样发送目录 id(openai/gpt-5.5)。Logs 的 model 一列显示的是实际发出的 id,核对它是不是完整的目录 id。
网关看得到工具、MCP 服务器或会话吗? 看不到。扩展、MCP 服务器、权限、审批模式、会话和 git 提交都在你的机器上运行。网关每一轮收到一次 Chat Completions 请求,记录 Trace、执行 Key 上的上限、返回响应;请求体里的工具定义和工具结果按 token 计量,其中没有任何东西会被执行。