用自定义 openai-compat 供应商让 Crush 跑在 Router One 上
Crush 是 Charm 出品的终端编程 Agent:它在本机运行文件、Shell、LSP 与 MCP 工具,并把每一轮模型调用发给你配置的供应商。在 crushrc 中写 provider add router-one --type openai-compat --base-url https://api.router.one/v1,再把 model large 与 model small 固定到 router-one 的 ID。这个供应商会把 Crush 的调用以 POST /v1/chat/completions 发往 Router One,每一轮都是一条可计费、可追踪的请求;会话、工具、权限与上下文文件仍留在 Crush。本指南基于 2026 年 9 月 21 日发布的 Crush v0.96.1,该版本优先使用基于 Bash 的 crushrc;旧的 crush.json 仍可加载,但已弃用。
安装 Crush v0.96.1,并导出专用 Key
用 Homebrew 或 npm 安装 Crush,Windows 上用 Scoop;截至 2026 年 9 月 23 日,这三种方式都已提供 v0.96.1,而 winget 包仍停留在 0.93.1。v0.96.1 的 README 还列出了 Arch、Nix 与 FreeBSD 安装方式。crush --version 应输出 v0.96.1。为 Crush 创建一把设了 maxSpend 消费上限的 Router One Key,并在启动 Crush 的 Shell 中导出:crushrc 在 Crush 启动时执行,只能读到该环境中已经存在的变量。ROUTER_ONE_API_KEY 是本指南自定的变量名,Crush 读取它只是因为 crushrc 引用了它。crush dirs 会先列出配置目录(crushrc 放在这里),再列出 Crush 写入状态的数据目录。
brew install charmbracelet/tap/crush # 或:npm install -g @charmland/crush crush --version # crush version v0.96.1 export ROUTER_ONE_API_KEY="sk-your-router-one-key" crush dirs # 先列配置目录,再列数据目录 # Windows(Scoop;winget 包可能滞后): # scoop bucket add charm https://github.com/charmbracelet/scoop-bucket.git # scoop install crush # PowerShell:$env:ROUTER_ONE_API_KEY = "sk-your-router-one-key"
把 Crush 配置到 Router One base URL
把代码块保存为配置目录下的 crushrc:macOS 与 Linux 是 ~/.config/crush/crushrc(设置了 XDG_CONFIG_HOME 时为 $XDG_CONFIG_HOME/crush/crushrc),Windows 是 %USERPROFILE%\.config\crush\crushrc。crushrc 是 Crush 内置解释器在启动时执行的 Bash;provider add、model add、model large 与 model small 都是 Crush 的内置命令,不是系统命令。router-one 是本地供应商 ID。--type openai-compat 选用 Crush 的 Chat Completions 客户端,--base-url 填带 /v1 的基础地址,不要加 /chat/completions。${ROUTER_ONE_API_KEY:?…} 写法会在变量缺失时让 Crush 报配置错误并停止。普通的 "$ROUTER_ONE_API_KEY" 会展开成空 Key,此时如果环境中导出了 OPENAI_API_KEY,Crush 的 OpenAI 客户端会改用它,把那把 Key 发给 Router One。model add 接受 <provider>/<id>,只在第一个斜杠处拆分,因此 router-one/anthropic/claude-sonnet-5 注册的目录 ID 是 anthropic/claude-sonnet-5,并原样发送。把两个 ID 换成 /models 中当前可用、详情页标明支持工具调用的聊天模型:large 槽位运行编程 Agent,small 槽位生成会话标题,并运行一个同样会调用工具的抓取子 Agent。代码块没有写 --context-window,因为它的值来自 /models 上各模型的详情页,而详情页显示的是取整后的缩写(如 200K、1.05M):开始长会话前,请在每行 model add 后追加 --context-window,并把该值换算成整数、往小取(200K → 200000,1.05M → 1000000);直接写 200K 会让 Crush 启动失败。项目中的 .crushrc 或 crushrc 会覆盖全局文件。两者都属于受信任代码,因此 Key 应放在环境变量里,不要写进会提交的文件。
# ~/.config/crush/crushrc (Windows: %USERPROFILE%\.config\crush\crushrc)
# Router One as a custom OpenAI-compatible provider -> POST /v1/chat/completions
provider add router-one \
--name "Router One" \
--type openai-compat \
--base-url "https://api.router.one/v1" \
--api-key "${ROUTER_ONE_API_KEY:?set ROUTER_ONE_API_KEY}"
# <provider>/<id>: Crush splits at the first slash, so the catalog ID keeps its own slash
# Append --context-window <model-page value as a whole number, e.g. 200000> to each line;
# without it Crush never auto-summarizes
model add router-one/anthropic/claude-sonnet-5 --name "Claude Sonnet 5 (Router One)"
model add router-one/anthropic/claude-haiku-4.5 --name "Claude Haiku 4.5 (Router One)"
# Pin both slots so titles and the fetch sub-agent bill this key too
model large router-one/anthropic/claude-sonnet-5
model small router-one/anthropic/claude-haiku-4.5先用 crush run 测一次,-m 始终带 router-one/ 前缀
像下面这样通过管道使用时,crush models 以 provider/model 逐行列出 Crush 知道的全部模型(直接在终端运行时显示为按供应商分组的树);筛选 router-one/ 即可确认 crushrc 已加载。crush run 会发出真实、计费的请求。除了回答本身,Crush 还会生成会话标题,所以一次提问通常在 Dashboard → Logs 中对应两条 POST /v1/chat/completions。在 v0.96.1 的本地模拟测试中,标题请求使用 small 模型,输出上限 40 tokens,不带工具;主请求使用 large 模型,带 stream: true、stream_options.include_usage 和 Crush 的工具定义。这条主请求包含 Crush 完整的系统提示词和工具定义(测试中为 26 个,JSON 约 49 KB),所以即使只是这句问候,也会在 large 模型上计费数千个输入 tokens。传 -m 时要保留 router-one/ 前缀,并同时传 --small-model。crush run 只有在存在同 ID 的供应商时才把第一段当作供应商名,因此只要配置了内置 OpenAI 供应商,-m openai/gpt-5.5 就会发往它。对 router-one 这类自定义供应商,只传 -m 而不传 --small-model,还会让本次运行的标题请求改用 large 模型。在 TUI 中按 ctrl+l 打开模型选择器。在那里做的选择会保存到数据文件 ~/.local/share/crush/crush.json(Windows 为 %LOCALAPPDATA%\crush\crush.json),它在全局 crushrc 之后加载,会覆盖其中的 model 行。Crush 自己也会写这个文件:model large 或 model small 指向供应商上未注册的 ID 时,它会不报错地回退到默认模型,并把回退结果存进去;若改了 crushrc 的 model 行却不生效,请删除数据文件里的 models 项。
crush models | grep '^router-one/' crush run "Reply with one short greeting." # 临时指定模型:两个槽位都加 router-one/ 前缀 crush run -m router-one/anthropic/claude-sonnet-5 \ --small-model router-one/anthropic/claude-haiku-4.5 "Reply with one short greeting." crush # 交互模式;ctrl+l 打开模型选择器
让辅助请求也走同一把 Key
Crush 有两个模型槽位。large 模型运行 coder、plan 与 task Agent,并生成上下文摘要。small 模型生成会话标题,并运行 agentic_fetch:一个自带网页抓取、网页搜索、Sourcegraph、glob、grep 与 view 工具的子 Agent。标题请求的输出上限是 40 tokens,除非 model add 行设置了 --can-reason;请求失败或停在这个上限时,Crush 会改用 large 模型再请求一次,因此把输出花在推理上的 small 模型,可能让每个新会话多出一次计费的标题请求。未设置 model small 时由 Crush 代为选择:只有自定义供应商时沿用 large 模型,但已配置的内置供应商优先。在 v0.96.1 的模拟测试中,导出 OPENAI_API_KEY 且只把 model large 固定到 router-one 时,标题请求发给了内置 OpenAI 供应商并使用它的 Key,而不是 Router One。请像上面的配置一样,把 model small 也固定到 router-one 的 ID。若 Router One 应是唯一的供应商,再加一行 option default-providers false;此后 Crush 会忽略所有内置供应商,包括你在其他工作中使用的那些。
供应商类型决定请求路径
Crush 按 --type 选择客户端,只改 --base-url 不会在协议之间转换。Crush 的 README 建议:OpenAI 官方 API 之外的 OpenAI 兼容 API 使用 openai-compat,本指南用的就是它;--type openai 则会改用 Crush 的 OpenAI 客户端;自定义供应商不写 --type 时按 openai-compat 处理。如果还想用原生协议,再加一个供应商,使用独立的 ID、类型与模型列表。README 中 Anthropic 兼容示例的 base URL 以 /v1 结尾,在这个客户端里会把路径叠成 /v1/v1/messages。最后一列是 v0.96.1 模拟测试实际收到的请求,以及 Router One 的相关说明:
| --type | --base-url | 本地测试中 Crush 发出的请求及 Router One 说明 |
|---|---|---|
| openai-compat(本指南) | https://api.router.one/v1 | 所有模型 ID 都发 POST /v1/chat/completions,带 Authorization: Bearer;这个端点服务当前所有聊天模型 |
| openai | https://api.router.one/v1 | ID 中含 gpt- 且后接 4 及以上的代数(如 openai/gpt-5.5)时发 POST /v1/responses,带 store: false 并请求 reasoning.encrypted_content;其他 ID 仍走 POST /v1/chat/completions。先确认模型详情页列出了 /v1/responses |
| anthropic | https://api.router.one(主机根地址,不带 /v1) | 发 POST /v1/messages,带 x-api-key 与 anthropic-version 请求头;仅限 Claude 系列与 DeepSeek 的 ID。base URL 以 /v1 结尾时实际请求变成 /v1/v1/messages |
上下文窗口、重试与费用记录
你在 model add 行里填的数值(--context-window、--default-max-tokens、--price-*)都是 Crush 端的设定,Router One 不会下发这些值。不写 --context-window 时模型窗口为 0,Crush 就不会对该会话自动摘要,每次请求重发的历史会不断增长,最终可能超出模型能接受的长度。请把模型详情页上的上下文窗口换算成整数填进 --context-window,并往小取(200K → 200000,1.05M → 1000000);取小一些只会让摘要稍早触发。窗口大于 200,000 时,Crush 在剩余 20,000 tokens 时摘要;窗口不超过 200,000 时,在剩余 20% 时摘要;每次摘要都是 large 模型上的一次额外请求,option auto-summarize false 可以关闭它。--default-max-tokens 会成为主请求的输出上限;不设时,模拟测试中的 Chat Completions 请求不带 max_tokens 字段。遇到 408、409、429、5xx 与传输错误时,Crush 最多重试三次,间隔约 5、10、20 秒;Retry-After 响应头要求的时间小于 60 秒时则按其等待,因此一步操作在 Logs 中可能对应四条请求;400 与 402 会立即返回。v0.96.0 把默认请求超时提高到 120 秒(v0.96.1 的配置文档仍写 60 秒),对流式响应而言它是无活动超时;模型较慢时可设置 option request-timeout。Crush 因此中断的流式请求会在 Logs 中记为 HTTP 499 client_cancelled,只按已报告的用量计费。Crush 的会话费用由 --price-* 参数计算,不设则始终为零,因此计费仍以 Logs 为准。crush --debug 会把每个请求的 URL 与 JSON 请求体写入项目下的 .crush/logs/crush.log,可用 crush logs 查看。其中包含提示词和代码,调试结束后请删除该日志。
Crush 该填哪个模型 ID?
从 /models 页复制精确的模型 ID,保留大小写、连字符和版本后缀,不要用展示名称代替。打开该模型的详情页,核对支持的 API 端点、上下文窗口和工具调用等能力,再与 Crush 当前选择的 provider 和功能对应。模型出现在目录里,不等于当前客户端能调用它的所有功能。建议为每个客户端或应用单独建 API Key,并设置 maxSpend 消费上限。
Crush 用的是哪种 API 协议?
OpenAI 兼容描述的是接口格式,不能据此推断 Chat Completions(/v1/chat/completions)、Responses(/v1/responses)和 Anthropic Messages(/v1/messages)可以互换。先核对工具当前版本、provider 配置和实际请求路径,再查模型详情与 API 兼容性事实页。一次普通对话成功,也不能证明服务端工具、历史状态或文件编辑功能都受支持。
在 trace 里验证 Crush 的调用
先在 Crush 发出一次简单文本请求,再到 Dashboard → Logs 按时间、模型和 request_id 核对 trace:tokens、花费、延迟和状态码。成功后再分别验证流式输出、工具调用和多轮历史。失败时保留实际请求路径、完整错误消息及 request_id;没有对应日志时,先查客户端配置和网络,不能仅凭客户端报错认定是网关或上游故障。
常见问题
Crush 能启动,但每个请求都返回 401,先查什么?
先确认 Key 是否真的传进了 Crush。crushrc 在 Crush 启动时展开变量。写成普通的 "$ROUTER_ONE_API_KEY" 而当前环境又没有这个变量时,v0.96.1 仍会以空 Key 启动,其 OpenAI 客户端会改读 OPENAI_API_KEY:本地模拟测试中,未设置 OPENAI_API_KEY 时请求完全不带 Authorization 请求头;导出了 OPENAI_API_KEY 时,请求会以 Bearer 形式带上这把 OpenAI Key,Router One 返回 401,而这把 Key 已经发出本机。旧的 crush.json 表现相同。上面使用的 ${ROUTER_ONE_API_KEY:?set ROUTER_ONE_API_KEY} 写法会让 Crush 在启动时直接报配置错误并停止,报错以 "executing shell config …/crushrc: exit status 1" 结尾,:? 后面的提示文字不会显示。在启动 Crush 的 Shell 中导出该变量,然后重启 Crush。如果请求头已经带上,再确认 Key 仍然有效、复制完整,并确认这次请求确实由 router-one 发出:从内置供应商选择的模型会使用那个供应商的 Key。
我已经有 crush.json,需要改成 crushrc 吗?
不需要。v0.96.1 仍会加载全局配置目录中的 crush.json,以及项目目录中的 .crush.json 或 crush.json,只是该格式已弃用,新选项只会加到 crushrc。等价写法是 providers.router-one:type 为 openai-compat,base_url 为 https://api.router.one/v1,api_key 为 "$ROUTER_ONE_API_KEY"(模拟测试中 $VAR 与 ${VAR} 两种写法都能展开),models 数组中的每个对象包含 id、name,以及可选的 context_window 与 default_max_tokens。crush.json 没有变量缺失保护:即使写成 ${ROUTER_ONE_API_KEY:?…},v0.96.1 也只记录一条警告,并以空 Key 继续运行,所以启动 Crush 前请先导出该变量(见上面的 401 问题)。顶层 models 对象设置 large 与 small,每项的 provider 为 router-one,model 为目录 ID。可以加上 "$schema": "https://charm.land/crush.json" 引用官方发布的 schema。同一目录同时有两种格式时会合并,冲突时以 crushrc 为准;两者设置了相同的顶层键时,Crush 还会记录一条警告。
供应商能直接命名为 openai,或者对 GPT 模型用 --type openai 吗?
请使用独立的 ID,例如 router-one。openai 是 Crush 内置 OpenAI 供应商的 ID,provider add openai 只会覆盖它的地址与 Key;它自带的 gpt-5.5 等不带前缀的模型 ID 和默认模型都会保留,并以缺少目录所用 openai/ 前缀的形式发给 Router One。--type openai 是另一回事:它会把 openai/gpt-5.5 这类 ID 发往 /v1/responses,带 store: false,并请求推理摘要与加密推理内容,其他 ID 仍走 Chat Completions。Router One 对当前列出的 GPT 系列 ID 原生提供 /v1/responses,但依赖这些 Responses 字段之前,先用一次真实请求确认它们被接受。openai-compat 让所有模型都走 Chat Completions,也就是本指南全程使用的路径。
为什么模型选择器里只有我手动添加的模型?
因为这个供应商显式列出了模型。在 provider add 中加上 --discover-models true 后,Crush 每次加载配置时还会向 Router One 请求 GET /v1/models(限时 3 秒),并把返回的 ID 追加在你的模型之后。自动发现的条目只有 ID,没有上下文窗口、输出上限或价格,列表中还会包含不能用于聊天的图像生成 ID。router-one 供应商一个模型都没有时会自动触发发现,发现失败则 Crush 会丢弃这个供应商。对实际要用的槽位,显式写 model add 更可控。
Crush 能通过网关用哪些模型?
选用当前目录中同时支持 Crush 所用端点和所需功能的模型。精确 ID、当前单价与能力以 /models 及模型详情为准;不要仅按 GPT、Claude 等系列名判断兼容性。客户端能列出模型,只说明它从自身配置或 GET /v1/models 读到了这个 ID,仍需验证实际调用。
能列出模型,但调用报 400 或 404,怎么办?
先记录实际请求路径和错误消息,再核对精确模型 ID。400 可能是参数、工具类型或模型与端点不匹配;404 可能是请求路径或资源不存在,不能直接判定模型下线。若错误提示 must be called via,按它指明的端点调整客户端 provider,或换用支持当前端点的模型。不要在所有工具里统一增删 /v1 或 /chat/completions。
中国大陆能直连吗?
能。网关在大陆可直连、无需 VPN,配置与全球环境完全一致。
报 401/402/403/429 怎么排查?
先到 Dashboard → Logs 核对请求及错误消息。401 查 Key 是否传入和有效;402 查钱包余额与 maxSpend;403 查 Key 权限和访问限制;429 查请求频率、token 限额及上游限流,按错误来源处理。保留 request_id,再按错误码速查页逐项排查。