OmniRoute OpenCode 插件实战指南:动态模型目录、实时 Combo 发现与多实例接入
@omniroute/opencode-plugin 是 OmniRoute 官方推荐的 OpenCode 集成方式,它把 OmniRoute 网关的「动态模型目录 + Combo 聚合路由 + 多上游统一鉴权」能力以 OpenCode 原生 Plugin 形态接入:启动时在 Node.js 侧实时拉取 /v1/models 与 /api/combos,以能力交集(least-common-denominator)聚合 Combo,并支持开箱即用的多 OmniRoute 实例并存。读完本文,你将掌握从零配置、自动/手动刷新、模型筛选、MCP 自动发射到生产/预生产双实例隔离的完整实操路径,并理解其背后 provider/config/auth 三个 Plugin hook 的源码级实现原理。
本插件位于仓库 @omniroute/opencode-plugin(当前 package.json 版本为 0.2.1,MIT 协议),其设计与竞品对照、配置项定义与 Feature 开关全部围绕一个核心目标:让 OmniRoute 成为模型事实的唯一来源(source of truth),OpenCode 端不做任何客户端侧的能力拼装。
为什么选 Plugin,而不是 @omniroute/opencode-provider
@omniroute/opencode-provider 是旧的「配置生成器」包:它会在构建/CLI 阶段把一段冻结的 provider.omniroute 配置块写入 opencode.json,模型清单是写死在源码里的 8 个模型(见 @omniroute/opencode-provider/src/index.ts 中的 OMNIROUTE_DEFAULT_OPENCODE_MODELS)。它在纯 CLI 环境下可以工作,但在 OpenCode Desktop / Web(Tauri / Electron) 构建中,运行时模型选择器会重新执行,静态配置块只能暴露出其中一小部分模型,并且会随 OmniRoute 在线目录的更新而逐渐失真。
本插件(Plugin)则完全改走「运行时集成」路线:
- 在 OpenCode 启动时、于 Node.js 进程内拉取
/v1/models与/api/combos,天然绕开浏览器 WebView 的 CORS 限制; - 在插件自身的
provider/confighook 中动态发射 provider 配置块,因此opencode.json只需要一行插件入口,不再需要手写静态的provider.omniroute; - 支持按可配置 TTL(默认 5 分钟)按需重拉,同时提供后台自动再发现(
autoSyncIntervalMs,默认 5 分钟),新模型与 Combo 变更无需重启 OpenCode 即可生效; - 暴露强刷路径(
omniroute_sync_modelstool +/omni-sync命令模板),对齐 Pi 的/omni sync; - 对 Combo 的
limit.context统一计算为min(member.context_length)(取各成员上下文长度的最小值),杜绝null导致的 4K 截断问题; - 自动识别思考模型的
interleaved能力(经 PR #3138 合并)。
源码层面,这两条路线的差异在 插件主文件 顶部注释中有明确论述:@omniroute/opencode-provider 的 fetchLiveModels 返回的是被裁剪过的 {id, name, contextLength?} 结构,丢掉了插件做 ModelV2 透传所需的 capabilities / *_modalities / max_*_tokens,因此插件直接实现了一个约 30 行的轻量 fetcher 来保留完整字段。
如果当前 opencode.json 里还残留旧的 opencode-provider 静态块,把它替换成一行插件入口即可,其余配置(包括同一份 auth.json API Key)无需任何改动。
下面是两者的整体对比:
@omniroute/opencode-plugin(本插件) |
@omniroute/opencode-provider |
|
|---|---|---|
| 类型 | OC Plugin(运行时) | 配置生成器(CLI / 构建期) |
| 模型 | 从 /v1/models 实时获取 |
生成时冻结 |
| Combo | LCD 聚合、实时 | 无 |
| Gemini schema 清洗 | 有 | N/A |
| OC UI 集成 | /connect、/models |
无 |
| 多实例 | 原生 | 手动 |
两种方案可以共存,按你的运行环境选择即可。
安装:一条命令(omniroute setup opencode)
从 OmniRoute v3.8.23 起,插件随 omniroute npm 包预构建发布。只要安装了 OmniRoute,插件就已经在你的磁盘上,无需单独安装:
# 1. 一条命令——把插件复制进 OpenCode 并更新 opencode.json
omniroute setup opencode --auth
# 2. 跟随交互提示输入 OmniRoute API Key
# 3. 重启 OpenCode——/models 即列出完整实时目录
--auth 标志会自动执行 opencode auth login --provider opencode-omniroute。如果指向非默认的 OmniRoute 地址,用 --base-url 指定:
omniroute setup opencode --base-url https://or.example.com --auth
该命令实际做四件事:
- 定位 omniroute 安装目录内捆绑的插件;
- 将
dist/与package.json复制到~/.config/opencode/plugins/omniroute/; - 把插件入口写入 / 更新
opencode.json(幂等操作,且会替换掉旧版条目); - (配合
--auth)执行opencode auth login以持久化 API Key。
该命令可随时重跑,用于升级插件或更换 base URL;旧的 @omniroute/opencode-provider 条目与历史 opencode-omniroute-auth 包都会被自动清理。
手动安装(无 omniroute CLI 场景)
无法运行 omniroute setup opencode 时(本地开发、CI、离线/内网环境),可以直接构建并打包产物:
cd @omniroute/opencode-plugin && npm run build && npm pack
# 然后将产物解压到 ~/.config/opencode/plugins/omniroute-opencode-plugin/
再手工把插件入口写入 opencode.json(参考下文 Quick Start)。构建脚本由 tsup.config.ts 驱动,构建产物 main 指向 ./dist/index.js,files 字段只发布 dist、README.md 与 LICENSE。
Peer 依赖为 @opencode-ai/plugin(由你的 OpenCode 安装负责管理)。插件的 engines.node 要求 >=22.22.3,官方在 Node 22 与 24 上做过测试。
单实例快速上手(手动配置)
最小可用配置只需一个指向插件 dist 的入口和必要的运行参数:
// opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
[
"./plugins/omniroute-opencode-plugin/dist/index.js",
{
"providerId": "omniroute",
"baseURL": "https://or.example.com",
// OpenCode 运行期间后台再发现(对齐 Pi)。
// 默认 300000(5 分钟)。最小 60000。设为 0 可关闭。
"autoSyncIntervalMs": 300000,
},
],
],
}
随后写入 API Key:
opencode auth login --provider opencode-omniroute
# 提示输入 OmniRoute API Key,写入 ~/.local/share/opencode/auth.json
⚠ 请务必显式使用
--provider标志。当前 OpenCode 版本(≤1.15.5)会把opencode auth login omniroute解析为位置参数url,并以fetch() URL is invalid失败(上游已知问题)。
重启 OpenCode 后,/models 即列出完整实时目录。-low、-medium、-high、-thinking 变体和 Combo 会以一等公民的模型 ID 出现——因为 OmniRoute 就是事实源,客户端不做任何二次合成。
从源码结构看,这一行为的核心是 createOmniRouteAuthHook 中的 loader:它通过 getAuth() 读取已存凭证,把 apiKey/baseURL 投影成 AI-SDK 的 openai-compatible 配置形态;只有 type === "api" 且 key 非空的凭证才会返回有效配置,否则返回 {} 让 OpenCode 弹出 /connect 流程——避免带着错误凭证发请求。
实时目录刷新:TTL 自动 + 后台定时 + 手动强刷
OpenCode 运行期间,插件用两条独立机制保持目录新鲜:
| 机制 | 默认值 | 作用 |
|---|---|---|
modelCacheTtl |
300000(5 分钟) |
按需 TTL:过期后下一次 provider/models hook 触发时重新拉取 /v1/models |
autoSyncIntervalMs |
300000(5 分钟) |
后台定时器:harness 运行期间主动失效并重拉。最小 60000。设为 0 关闭后台轮询(TTL 仍然生效) |
两个默认值在源码中以常量定义(DEFAULT_MODEL_CACHE_TTL_MS = 300_000、DEFAULT_AUTO_SYNC_INTERVAL_MS = 300_000、MIN_AUTO_SYNC_INTERVAL_MS = 60_000)。后台间隔会先经过 sanitizeAutoSyncIntervalMs 归一化:未设置/非法值回落到 300_000;显式 0 表示关闭;(0, 60000) 区间的值会被钳制到 60000。定时器的首个 tick 会推迟一个间隔执行,避免与会话开始时的 provider.models 冷启动拉取重叠;setInterval 的句柄还被 unref(),因此不会拖住 Node 进程退出。
立即强刷(等价于 Pi 的 /omni sync)——OpenCode 没有 Pi 风格的斜杠命令注册 API,因此插件同时接入了 tool 与命令模板两条路:
- Tool:
omniroute_sync_models—— 失效内存 + 磁盘缓存,重新拉取GET /v1/models(以及按开关拉取 combos / enrichment 等),返回{ ok, count, ... }。 - 命令模板(在 OpenCode 中直接输入):
/omni-sync—— 让 agent 调用omniroute_sync_models并汇报结果;/omni-autosync—— 让 agent 汇报当前autoSyncIntervalMs/modelCacheTtl状态。
/omni-sync
/omni-autosync
在 OmniRoutePlugin 的 config hook 实现里,两个命令模板被写进 cfg.command,模板文本中会内插实际的 provider id(如 omniroute)与解析后的间隔值;omniroute_sync_models 强刷逻辑由 forceSyncOmniRouteModels 提供——它先清空针对该 baseURL 的内存缓存与磁盘快照,再依次拉取 models、combos、auto-combos、enrichment、compression 元数据与连接表,最后把带 expiresAt 的结果重新写入缓存(并在 diskCache 开启时落盘)。sync 返回体里包含模型数、Combo 数、清除的内存条目数与是否清理了磁盘快照,便于用户与 agent 核对。
多实例:生产 + 预生产并存
⚠ OpenCode ≤1.15.5 按模块绝对路径对插件加载去重。两条
plugin:条目若指向同一个dist/index.js,会合并为一条(后者选项生效)。解决方法是把插件分别安装到不同目录,使每条入口解析到不同的模块文件。README 提到后续 v0.2.x 将引入instances: [...]形态,实现单次加载注册 N 个 provider(当前package.json版本为 0.2.1)。
双实例工作区方案(OpenCode ≤1.15.5 当下可用)
先打包插件,再解压成两份命名目录,每条 plugin: 入口指向各自副本:
# 1. 构建 + 打包(在插件工作区执行)
cd /path/to/OmniRoute/@omniroute/opencode-plugin
npm run build
npm pack
# 产出 omniroute-opencode-plugin-0.1.0.tgz(以实际版本号为准)
# 2. 每个 OmniRoute 端点解压一份
mkdir -p ~/.config/opencode/plugins/omniroute-opencode-plugin-prod
mkdir -p ~/.config/opencode/plugins/omniroute-opencode-plugin-preprod
tar -xzf omniroute-opencode-plugin-0.1.0.tgz -C ~/.config/opencode/plugins/omniroute-opencode-plugin-prod --strip-components=1
tar -xzf omniroute-opencode-plugin-0.1.0.tgz -C ~/.config/opencode/plugins/omniroute-opencode-plugin-preprod --strip-components=1
然后在 ~/.config/opencode/opencode.json 中按绝对路径引用两个目录:
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
[
"./plugins/omniroute-opencode-plugin-prod/dist/index.js",
{
"providerId": "omniroute",
"displayName": "OmniRoute",
"baseURL": "https://or.example.com",
},
],
[
"./plugins/omniroute-opencode-plugin-preprod/dist/index.js",
{
"providerId": "omniroute-preprod",
"displayName": "OmniRoute Preprod",
"baseURL": "https://or-preprod.example.com",
},
],
],
}
路径相对于 ~/.config/opencode/。此时每条入口都解析到不同的模块文件,OpenCode 会把它们当作两个独立插件实例。分别完成鉴权:
opencode auth login --provider opencode-omniroute
opencode auth login --provider opencode-omniroute-preprod
每条入口获得独立的 provider id、独立的模型选择器条目、auth.json 中独立槽位与独立 TTL 缓存。闭包按插件实例隔离——实例之间零串扰(源码层面,每次 OmniRoutePlugin(...) 调用都通过闭包创建自己的 sharedCache: new Map(),生产/预生产实例不会共用缓存键)。
发布后(@omniroute/opencode-plugin npm)
包发布后,双实例安装可从 tar -xzf 退化为两条 npm install --prefix:
mkdir -p ~/.config/opencode/plugins/omniroute-opencode-plugin-prod
mkdir -p ~/.config/opencode/plugins/omniroute-opencode-plugin-preprod
npm install --prefix ~/.config/opencode/plugins/omniroute-opencode-plugin-prod @omniroute/opencode-plugin
npm install --prefix ~/.config/opencode/plugins/omniroute-opencode-plugin-preprod @omniroute/opencode-plugin
此时 opencode.json 路径变为 ./plugins/omniroute-opencode-plugin-prod/node_modules/@omniroute/opencode-plugin/dist/index.js(preprod 同理)。
功能全景与各功能对应的 hook
插件把全部能力挂接在三个 OpenCode hook 之上,README 给出的功能矩阵如下:
| 功能 | 作用 | Hook |
|---|---|---|
动态 /v1/models |
每次刷新拉取实时目录(生产上 455+ 条目),TTL 缓存 | provider.models |
| 变体透传 | -low/-medium/-high/-thinking 以一等公民 ID 由 OmniRoute 提供(无客户端合成) |
provider.models |
| Combo LCD 聚合 | Combo 以成员间的能力交集 + 上下文/输出最小值呈现 | provider.models + config |
combo/<slug> 命名空间 + Combo: 前缀 |
Combo 以 combo/claude-primary 形式(而非上游 UUID)出现,选择器显示 Combo: claude-primary |
两个 hook |
| 友好名称 + 成本 | /api/pricing/models 显示名 + /api/pricing 每百万 token 价格叠加到实时目录 |
两个 hook |
| Canonical 孪生去重 + alias 回退 | /v1/models 会同时暴露短别名(cc/claude-opus-4-7)与规范名(claude/claude-opus-4-7);存在别名孪生时丢弃规范孪生(选择器不出现重复行),并将规范名反向映射为 alias 以补齐 /api/pricing/models 只按规范名索引的 enrichment |
两个 hook |
| 压缩管线标签 | features.compressionMetadata: true 时,Combo 名带压缩管线标签(如 Combo: claude-primary [rtk🟡 → caveman🟠]),强度用红绿灯 emoji 表达 |
两个 hook |
| Provider 标签前缀 | 富化名前置短上游供应商标签(如 Claude - Claude Opus 4.7 对比 Kiro - Claude Opus 4.7),默认开启,可用 features.providerTag: false 关闭 |
两个 hook |
| 仅可用过滤 | 过滤到 /api/providers 中至少有一条健康连接的上游(features.usableOnly 开启) |
两个 hook |
| 模型白/黑名单 | 用 features.visibleModels(白)与 features.hiddenModels(黑)把选择器收敛到固定 ID 集合;裸后缀(如 claude-opus-4-7)匹配任意 {prefix}/claude-opus-4-7。与 usableOnly 全部 AND 叠加,黑名单优先(deny 优先) |
两个 hook |
| 磁盘缓存兜底 | 最近一次成功目录持久化到磁盘;冷启动时若 /v1/models 不可达则据此水合(默认开启,features.diskCache: false 关闭) |
config |
| Bearer 注入 + 后缀仿冒防护 | 仅在 baseURL 匹配的请求上加 Authorization |
auth.loader.fetch |
| Gemini schema 清洗 | 对 gemini-*/google-vertex-gemini/* 剥离 $schema/$ref/additionalProperties |
auth.loader.fetch 包装 |
| 多实例 | 每个插件条目绑定自己的 providerId;闭包隔离 |
factory |
| Config-hook shim | OC ≤1.15.5 回退:把静态目录写入 config.provider[id](这些版本在 serve 模式下只有 config hook 会触发) |
config |
结合仓库源码,其中几个关键行为的内部机制值得展开:
- 模型映射与能力透传:mapRawModelToModelV2 把
/v1/models原始条目映射为 AI-SDK 的ModelV2。capabilities.thinking会并入reasoning,同时单独置位interleaved(这正是思考模型自动拾取的核心);capabilities.vision按 OmniRoute 惯例映射为attachment;输入/输出模态由input_modalities/output_modalities数组逐项点亮布尔位。/v1/models不承载价格,因此插件先输出零值cost块,真实计费仍由 OmniRoute 在路由时负责。 - schema 严格校验:插件选项经 parseOmniRoutePluginOptions 用 Zod 严格解析,未知键直接抛错,
opencode.json里的拼写错误会在插件构造期立即暴露,而不是被静默丢弃;空providerId、非 URL 的baseURL、负数modelCacheTtl都会得到可操作的报错信息。 - providerId 前缀归一:
resolveOmniRoutePluginOptions会为 OC ≥1.17.8 的原生适配器门禁自动添加opencode-前缀(门禁只接受{openai, anthropic, opencode*}),而所有要交给 OmniRoute 服务端解析的位置(模型 ID 前缀、providerID、combo 目录键)一律使用无前缀的omnirouteProviderId,避免parseModel()因无法解析opencode-<x>而报 "No credentials for opencode-"。
插件选项(Plugin options)
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
providerId |
string |
"omniroute" |
OpenCode provider id;在多个插件条目间必须唯一 |
displayName |
string |
"OmniRoute" 或 OmniRoute (<id>) |
OC UI 中显示的标签 |
modelCacheTtl |
number |
300000(5 分钟) |
/v1/models TTL(毫秒) |
baseURL |
string |
由 /connect 后 auth.json 解析 |
覆盖 OmniRoute base URL |
managementReadToken |
string |
回落到 apiKey |
管理目录 GET 用的只读 token;/v1 推理仍走已连接的 apiKey |
features |
object |
见下 | 功能开关(均可选/关,默认保持 v0.1.0 行为) |
最小权限部署:managementReadToken
对于最小权限部署,可在顶层把 managementReadToken 设为一个只读管理 token。它只会被发往目录类读取接口:/api/combos、/api/combos/auto、/api/pricing/models、/api/pricing、/api/context/combos 与 /api/providers;/v1 下的推理请求(含 chat)仍使用 OpenCode 存储的 apiKey。features.mcpToken 保持独立。若省略 managementReadToken,目录读取沿用原有 apiKey 行为。上述这些读取接口在仓库中均有对应实现,例如 src/app/api/combos/route.ts(含 combos/auto/ 子目录)、src/app/api/pricing/models/route.ts 与 src/app/api/pricing/route.ts。
features 块
每个字段都可省略,默认值镜像 v0.1.0 行为,因此既有的 opencode.json 无需改动。
| 功能 | 类型 | 默认值 | 作用 |
|---|---|---|---|
combos |
boolean |
true |
发现 /api/combos 并把 Combo 以 LCD 能力呈现为伪模型。Combo 在 combo/<slug> 命名空间下按键存储,选择器里以 Combo: <name> 标签呈现,与裸的 provider/模型组合区分开 |
enrichment |
boolean |
true |
从 /api/pricing/models 拉显示名、从 /api/pricing 拉每百万 token 价格(input、output、cached→cacheRead、cache_creation→cacheWrite),叠加到实时目录上 |
compressionMetadata |
boolean |
false |
拉取 /api/context/combos,给 Combo 名加压缩管线标签,如 Combo: claude-primary [rtk🟡 → caveman🟠]。强度 token 用红绿灯 emoji 渲染:🟢 lite/minimal · 🟡 standard · 🟠 aggressive/full · 🔴 ultra |
providerTag |
boolean |
true |
给富化显示名前置短上游标签并用 " - " 连接,如 cc/claude-opus-4-7 → Claude - Claude Opus 4.7,区别于 kr/claude-opus-4-7 → Kiro - Claude Opus 4.7。标签解析:/api/pricing/models[<alias>].name ≤8 字符时原文使用(如 Claude、Kiro、Codex、Qwen),否则回退为 UPPER(alias)(如 GitHub Models→GHM、Gemini→GEMINI)。幂等。Combo 有意跳过(Combo: 前缀已传达多上游语义) |
usableOnly |
boolean |
false |
读取 /api/providers,把目录过滤到至少有一条 isActive: true 且 testStatus: 'active' 连接的供应商。采用减集过滤语义:对 pricing-models 目录和连接表都未知的供应商(如 agentrouter/* 这类合成前缀)放行。拉取失败时该次刷新禁用过滤——绝不因此隐藏整个目录 |
visibleModels |
string[] |
未设置 | 白名单——设置且非空时,只发射原始 /v1/models ID 匹配的模型。裸 ID(无斜杠,如 claude-opus-4-7)匹配任意 {prefix}/claude-opus-4-7;完整 ID(如 cc/claude-opus-4-7)精确匹配。与 usableOnly、hiddenModels 组合(所有过滤 AND)。未设置或空 = 不过滤 |
hiddenModels |
string[] |
未设置 | 黑名单——原始 ID 匹配的模型被丢弃。匹配规则与 visibleModels 相同。模型同时出现在两份名单中时黑名单胜出(deny 优先)。组合规则同上 |
diskCache |
boolean |
true |
把最近一次成功的 /v1/models + /api/combos + enrichment + 连接 + 压缩快照持久化到 ${OPENCODE_DATA_DIR ?? ~/.local/share/opencode}/plugins/omniroute-<providerId>.json。后续冷启动时若 /v1/models 抛错(断网 / IP 白名单掉线 / 5xx),静态块从快照水合,使 OC 模型选择器离线仍可用。读写失败软失败——绝不阻塞发布 |
geminiSanitization |
boolean |
true |
当模型 ID 匹配 gemini 时,从工具参数中剥离 $schema/$ref/additionalProperties |
mcpAutoEmit |
boolean |
false |
自动把指向 <baseURL>/api/mcp/stream、携带解析后 Bearer token 的 mcp.<providerId> 远端条目写入 OC 配置 |
mcpToken |
string |
未设置 | 自动发射的 MCP 条目使用的独立 Bearer。未设置时回退到 provider 的 apiKey(取自 auth.json) |
fetchInterceptor |
boolean |
true |
对每个目标为 baseURL 的外发请求注入 Authorization: Bearer + 默认 Content-Type(带后缀仿冒防护) |
示例:enrichment + 压缩标签 + MCP 自动发射
{
"plugin": [
[
"@omniroute/opencode-plugin",
{
"providerId": "omniroute",
"baseURL": "https://or.example.com",
"managementReadToken": "<read-only-management-token>",
"features": {
"combos": true,
"enrichment": true,
"compressionMetadata": true,
"mcpAutoEmit": true,
},
},
],
],
}
mcpAutoEmit: true 时,插件合成的 mcp.omniroute 条目等价于手工配置:
"mcp": {
"omniroute": {
"type": "remote",
"url": "https://or.example.com/api/mcp/stream",
"enabled": true,
"headers": { "Authorization": "Bearer <apiKey-from-auth.json>" }
}
}
若想为 MCP 使用更小权限范围的 Bearer(不同于聊天/推理 key),设置 features.mcpToken。操作者手动配置优先:若你已在 opencode.json 中配置了 mcp.omniroute,插件不会覆盖它。
示例:生产取向默认(干净选择器 + 离线韧性)
{
"plugin": [
[
"@omniroute/opencode-plugin",
{
"providerId": "omniroute",
"baseURL": "https://or.example.com",
"features": {
"combos": true,
"enrichment": true,
"compressionMetadata": true,
"usableOnly": true,
"diskCache": true,
},
},
],
],
}
usableOnly: true会把其规范 provider 在你的 OmniRoute 实例中没有健康连接的模型丢弃——/models选择器只保留你真能调用的模型。diskCache: true(默认)每次健康刷新时把快照写入${OPENCODE_DATA_DIR}/plugins/omniroute-<providerId>.json。冷启动时若/v1/models不可达(笔记本离线、IP 白名单掉线),快照会水合静态块,OC 依然展示完整目录而非占位 stub。compressionMetadata: true用红绿灯 emoji 把 Combo 显示名标注上压缩管线与强度(例如Combo: claude-primary [rtk🟡 → caveman🟠])。调色板:🟢 lite/minimal · 🟡 standard · 🟠 aggressive/full · 🔴 ultra。未知强度回退为原始文本([rtk:custom-thing]),插件绝不隐藏 OmniRoute 已知而插件未知的值。providerTag: true(默认)前置短上游标签,使选择器对cc/claude-opus-4-7显示Claude - Claude Opus 4.7、对kr/claude-opus-4-7显示Kiro - Claude Opus 4.7、对ghm/gpt-5显示GHM - GPT 5(slot.nameGitHub Models超过 8 字符因此缩写)。当同一模型 ID 经由多个成本/鉴权/限流画像不同的上游连接售卖时,这一点至关重要。设为false可回到 v3.8.3 之前的无前缀格式。
显示名的统一装配逻辑在 src/naming.ts 的 buildModelDisplayName 中实现,优先级为:Auto Combo → Auto: <variant> (<N>p);DB Combo → Combo: <name>;Free + enrichment + providerTag → [Free] <label> - <name> · <budget>;Free + raw → [Free] <rawId> · <budget>;Enrichment + providerTag → <label> - <name>;仅 enrichment → <name>;最终兜底 → normaliseFreeLabel(rawId)。免费预算后缀(如 25M tokens/day、10M credits)由 formatFreeBudget 按 freeType 生成,令牌量级(K/M/B)由 fmtTokens 处理边界进位。
示例:收敛模型选择器(白名单 + 黑名单)
典型 OmniRoute 实例提供 600+ 个模型。要在几百条记录里翻出实际使用的约 30 个模型时,OpenCode TUI/CLI 选择器会变得不可用。visibleModels 与 hiddenModels 可以把选择器收敛到固定的模型 ID 集合,且该集合持久保存在 opencode.json 中,配置重置也不会丢:
{
"plugin": [
[
"@omniroute/opencode-plugin",
{
"providerId": "omniroute",
"baseURL": "https://or.example.com",
"features": {
"combos": true,
"enrichment": true,
"usableOnly": true,
"visibleModels": [
"claude-opus-4-7", // 裸后缀:匹配 cc/claude-opus-4-7、kr/claude-opus-4-7 等
"cc/claude-sonnet-4-6", // 精确:仅 cc/ 别名
"gemini-2.5-pro",
"gpt-5",
"o3",
"o3-pro",
"o4-mini",
],
"hiddenModels": [
"o3-mini", // 即使 visibleModels 未设置也隐藏 mini 变体
],
},
},
],
],
}
visibleModels是白名单——只发射原始 ID 匹配的模型。裸 ID(无斜杠)匹配任意 provider 前缀;完整 ID(含斜杠)精确匹配。hiddenModels是黑名单——被列出的模型直接丢弃。当模型同时出现在两份名单里时,黑名单胜出(deny 优先)。- 两者都与
usableOnly组合(全部 AND:模型必须同时通过 usableOnly、visibleModels 且不在 hiddenModels)。 - 未设置或为空 = 不过滤(当前行为)。
这类过滤与 URL 门禁、模型 id 解析规则共同被 multi-instance.test.ts、model-allowlist.test.ts、usable-combo.test.ts 等 25 个测试文件覆盖,覆盖点包括 option schema 校验、Gemini 清洗、fetch 拦截、磁盘快照权限、feature 默认值、管理只读 token、自动同步、命名与免费预算等——它们都运行在 npm test 之下。
Gemini 清洗与 Bearer 注入的底层实现
README 的 Features 矩阵里「Bearer 注入」与「Gemini schema 清洗」都落在 auth.loader.fetch 上,其实现值得单列说明:
- fetch 拦截器(createOmniRouteFetchInterceptor)只对「源 == baseURL origin 且路径命中
/v1/chat/completions或/v1/models」的请求注入Authorization: Bearer与默认Content-Type。baseURL 无法被new URL解析时,注入会被禁用而非扩大凭证作用域——这是「后缀仿冒防护」的落点:伪造的相似 host 永远拿不到你的 key。 - Gemini schema 清洗(createGeminiSanitizingFetch)递归删除
$schema、$ref、ref、additionalProperties四类 Gemini 会拒绝的 JSON-Schema 关键字,覆盖tools[].function.parameters(OpenAI chat-completions 形态)、tools[].function_declaration.parameters(Gemini 原生形态)与tools[].input_schema(Responses API 形态),并处理 payload 顶层关键字。模型判定由shouldSanitizeForGemini完成:payload.model命中大小写不敏感的gemini子串即触发(覆盖裸gemini-*、models/gemini-…与google-vertex/gemini-…)。清洗整体fail-open:任何解析/清洗异常都回退为转发原始请求——清洗是尽力而为的护栏,绝不是硬失败路径。ReadableStream流式 body 因无法安全克隆而跳过,只做一次性console.warn提示。 - 两层通过
loader按序组合:先包上 Bearer 注入拦截器,再包 Gemini 清洗层(两层均可由features.fetchInterceptor/features.geminiSanitization独立关闭);两者都关闭时回退到 SDK 默认 fetch(仅带apiKey)。
运行环境要求与版本兼容
- Node
>=22.22.3(见engines.node);已在 Node 22 与 24 上测试。 - OpenCode:已针对
opencode@1.15.5+@opencode-ai/plugin@1.15.6做过端到端验证。 - OC 插件 peer(
@opencode-ai/plugin)>=1.14.49才能获得完整功能(provider hook 会把模型展现在/models)。在<=1.14.48上,插件回退到自身confighook,把静态目录快照写入config.provider[id],模型依然可见。 - 插件使用 OC v1 插件形态(
default: { id, server },见 index.ts 底部的OmniRouteV1Plugin)。只遍历命名导出的旧版 OC 会拒绝它——请保持在 OC ≥1.15。
结语
@omniroute/opencode-plugin 把「运行时实时目录、Combo LCD 聚合、Gemini 兼容清洗、多实例隔离、离线磁盘兜底」完整收拢进 OpenCode 的原生 Plugin 契约中。对使用者而言,收益是零静态维护:/models 永远等于 OmniRoute 此刻的真实目录;对开发者而言,它同样是一个值得研读的参考实现——从 插件主源码、命名管道 到 25 个专项测试,完整展示了如何把 AI 网关的丰富目录模型以合规、安全、可离线的方式接入桌面级 Agent 客户端。
License
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00