Model IDs go stale: previews get promoted, point releases land, and the IDs pinned in your
agents keep pointing at last month's model. This OpenCode v2 plugin
adds floating aliases to the model catalog: a stable ID such as opencode/zen-plan that
always resolves to the newest model matching your rules.
Docs vs releases. This README follows the default branch (
master), which can be ahead of the published npm package; the GitHub Releases page lists what has shipped.
A concrete model ID (github-copilot/claude-sonnet-4.5) pins one model you review and
change yourself; OpenCode resolves it natively, no plugin needed. An alias earns its keep
when you mean a policy, not a model: "the newest free model with tools and a large
context", "the current Sonnet, whichever point release it is". The policy outlives the model
list; the alias keeps resolving to whatever satisfies it today.
Add the plugin and one alias to opencode.json:
{
"plugins": [{
"package": "opencode-model-aliases@latest",
"options": {
"aliases": {
"github-copilot/claude-sonnet": { "match": "github-copilot/claude-sonnet-*" }
}
}
}]
}Run /model-aliases to see what each alias resolved to, then select
github-copilot/claude-sonnet like any other model; requests go to the real winning model.
OpenCode caches installed plugin packages: restarting alone doesn't install a newer release. Update with
opencode plugin update opencode-model-aliases@latest.
Each alias key is <provider>/<alias-id>. Every pattern must use the same literal provider
as its key: an alias never selects a model from another provider.
Follow a family and skip previews or a specific model with exclude, in
.opencode/opencode-model-aliases.jsonc:
Require capabilities — tools, image input, large context — in
.opencode/opencode-model-aliases.jsonc:
// .opencode/opencode-model-aliases.jsonc — require capabilities
{
"$schema": "https://raw.githubusercontent.com/vmvarela/opencode-model-aliases/master/schema.json",
"aliases": {
"github-copilot/vision": {
"match": "github-copilot/gemini-*-flash",
"filter": { "capabilities": { "tools": true, "input": ["image"], "output": ["text"] }, "minContext": 128000 }
}
}
}Pick a model per job in opencode.json: filter the free set by what each job needs,
then point agents at the aliases:
{
"plugins": [{
"package": "opencode-model-aliases@latest",
"options": {
"aliases": {
"opencode/zen-plan": {
"match": ["opencode/*-free", "opencode/big-pickle"],
"filter": { "capabilities": { "tools": true }, "minContext": 256000 },
"name": "Zen Free — Plan"
},
"opencode/zen-build": {
"match": ["opencode/*-free", "opencode/big-pickle"],
"filter": { "capabilities": { "tools": true, "input": ["text"], "output": ["text"] }, "minContext": 64000 },
"name": "Zen Free — Build"
}
}
}
}],
"agents": {
"plan": { "model": "opencode/zen-plan" },
"build": { "model": "opencode/zen-build" }
}
}Filters decide eligibility, not quality. If several models qualify, the newest wins, so
both aliases may currently pick the same model. *-free is a naming convention, not a price
check. Only active models are eligible by default; opt into alpha/beta via
filter.status.
When the plugin starts and at every catalog refresh, each alias:
- Matches models with
match, then removesexcludematches (picomatch globs). - Filters by
enabled, status, capabilities andminContext. All filters must pass; missing metadata fails the filter that needs it. - Selects the newest
time.released. Ties use the descending model ID (gpt-6.1-sol-probeatsgpt-6.1-sol-fast). Models without release dates never win.
Selection is deterministic: same catalog, same rules, same winner. The alias gets a copy of the winner's model info under the alias ID; declaration order doesn't matter.
| Option | Default | Notes |
|---|---|---|
aliases |
required | Object of aliases; {} is valid. |
strict |
false |
Fail startup if any alias doesn't resolve; strict applies at startup only (see Limits). |
debug |
false |
Log each resolution as a [debug] warning. |
match |
required | Glob or list of globs: "provider/pattern". |
exclude |
none | Same format as match. |
filter.status |
["active"] |
Any of active, alpha, beta. |
filter.capabilities |
none | tools (boolean), input/output (all listed modalities required). |
filter.minContext |
none | Minimum context window, inclusive. |
name |
generated | Display name. Without it, sonnet becomes Sonnet (alias). |
select |
latest |
Only { "strategy": "latest" } is supported. |
Aliases can also live in .opencode/opencode-model-aliases.jsonc (JSONC comments and
trailing commas are fine); the plugin uses the nearest file found from the working directory
upward. Inline options win: an inline alias replaces a file alias with the same key, and
inline strict/debug values win. Inline options apply when OpenCode reloads its
configuration; the .jsonc file is read at plugin start, so restart after editing it.
For editor autocompletion and shape validation, schema.json ships with the package as a
draft-07 JSON Schema; point the config file's $schema at the default-branch URL
https://raw.githubusercontent.com/vmvarela/opencode-model-aliases/master/schema.json
(file-only metadata: editors may require it, the plugin strips it, and inline plugin options
never accept it). The schema checks shape only — glob compilation, the literal-provider rule
and provider equality remain runtime-enforced and authoritative.
// .opencode/opencode-model-aliases.jsonc — complete example
{
"$schema": "https://raw.githubusercontent.com/vmvarela/opencode-model-aliases/master/schema.json",
"strict": true,
"aliases": {
"openai/latest": {
"match": ["openai/gpt-*", "openai/o*"],
"exclude": ["openai/*-preview"],
"filter": { "minContext": 128000 }
}
}
}Run /model-aliases (or Model aliases in the command palette) for the current target and
the real model ID used for requests, with unresolved reasons where a selection failed. Run
/model-aliases explain <provider>/<alias-id> for the full decision: matching patterns,
rejected candidates with their reasons, and whether the runner-up lost on release date or
the tie-break. Nothing here calls a model. In OpenCode 2.0.22, selecting the slash
suggestion leaves a trailing space; press Enter again. Without a TUI:
opencode api --standalone post /api/rpc/opencode-model-aliases/inspect --data '{"input":{}}'The backend also exposes explain({ alias })
on the same opencode-model-aliases RPC. Explanations reflect the catalog visible to the
plugin's transform: models hidden with disabled: true can still appear there and be
selected by an alias (see Limits); with strict: true, a startup failure prevents the
plugin and its RPC from becoming available.
Change visibility. The first resolution sets a silent baseline. When an alias later
changes target, the TUI shows a toast and /model-aliases shows the previous target, the
current one and when the change was detected. Editing an alias's match or filter resets
its baseline: a changed alias is a config change you should see, not hunt for in logs.
Plugin ordering. The plugin resolves against the fresh catalog it receives and observes
later model.updated refreshes. The inspect report is built from a fresh source catalog; a
downstream rewrite of the alias modelID by another plugin can make the report return
unavailable. Don't rely on plugin execution order.
- Invalid configuration (including unknown keys and invalid globs) fails at startup, and alias/source ID collisions are fatal on every catalog replay, including startup.
- An alias with no candidate logs a warning and is left out; other aliases keep working.
With
strict: true, startup fails instead. Strict applies at startup only: after a successful startup, a target loss skips only that alias, and it recovers when a matching model returns. - Only the
lateststrategy exists; the plugin can't rank by price or quality. Aliases are provider-isolated and never chain, and the plugin only reads OpenCode's catalog — it never fetches external model lists. - Models hidden with
disabled: truein your OpenCode provider config can still be selected by an alias (#42); useexcludeto keep a model out of an alias. - Compatibility is verified only for OpenCode 2.0.16 and 2.0.24 (real-host smoke tests in CI, tracked in #29); other host versions are not on the verified matrix.
Explicitly out of scope: prompt classification ("which prompt needs which model"), smart routing, provider proxying, credential management, retries/failover, billing, and session orchestration. What happens to a request once it leaves the plugin is OpenCode's job.
Requires Node.js 22+ and pnpm 11. Use pnpm install then pnpm run verify (lint, typecheck,
tests, build), pnpm run check:release for offline release checks, and the optional
pnpm smoke:opencode (needs an installed OpenCode v2 CLI).
MIT License — see LICENSE.