- JavaScript 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| test | ||
| dynamic-models.js | ||
| oh-my-opencode-slim.md | ||
| package.json | ||
| README.md | ||
opencode-plugins
Custom plugins for opencode.
Setup
git clone ssh://git@git.chaops.de/rwxd/opencode-plugins.git <path>
mkdir -p ~/.config/opencode/plugin
ln -s <path>/dynamic-models.js ~/.config/opencode/plugin/dynamic-models.js
Replace <path> with the directory you want to clone into, e.g. ~/dev/opencode-plugins.
The same file works on opencode v1 and v2. It exposes a single default export object with id, setup and server: v1 (>= 1.18.29) calls server, v2 reads id/setup. No separate build or entrypoint is needed.
Install the symlink in exactly one location. v2 scans both ~/.config/opencode/plugin/ and ~/.config/opencode/plugins/, so installing it in both makes the second copy fail with Duplicate plugin ID: dynamic-models.
opencode v1
Register the symlink in ~/.config/opencode/opencode.json and restart opencode:
{
"plugin": ["/home/<user>/.config/opencode/plugin/dynamic-models.js"]
}
Requires opencode >= 1.18.29, the first v1 release that understands the combined id/setup/server export. On older v1 releases use a check-out from before the v2 support commit, when the file still exported a plain callable function.
opencode v2
Drop the symlink into a discovery directory (no config entry needed) — ~/.config/opencode/plugins/ is the v2-native one, while ~/.config/opencode/plugin/ works on both v1 and v2 — and declare dynamic providers under the top-level providers map (v2 uses plugins/providers/settings instead of plugin/provider/options):
{
"providers": {
"my-gateway": {
"name": "My Gateway",
"package": "@opencode/ai/providers/openai-compatible",
"settings": {
"baseURL": "https://<your-gateway>/v1",
"apiKey": "sk-...",
"dynamic": true
}
}
}
}
v2 discovers local plugins as direct .ts/.js files in .opencode/plugins/ (project) or ~/.config/opencode/plugins/ (global), and also scans the v1-style singular plugin/ directories. Entries in the plugins config array must reference a package or a plugin directory, a single .js file path is rejected with configured plugin path must be a directory.
Restart the v2 service (opencode service restart) after changing the plugin or its configuration.
Plugins
dynamic-models
Discovers models, pricing and capabilities for any OpenAI-compatible provider by querying its /v1/models endpoint at startup. Use it for gateways that expose an OpenAI-compatible API but are not a provider opencode already knows a model catalogue for (e.g. GoModel instances). opencode has no built-in support for this, so a custom provider plus this plugin is required.
Set dynamic: true on the provider's options (v1) or settings (v2). No models entry is needed, they are discovered automatically. Multiple providers are supported, add dynamic: true to each. On v1 discovery failures are shown as a toast and logged via client.app.log; on v2 they are logged to stderr. Either way the provider simply has no models for that session.
apiKey (v1 options.apiKey / v2 settings.apiKey) is optional. If set it is used as a fallback, but credentials stored for the same provider ID take precedence. These are read from $XDG_DATA_HOME/opencode/auth.json (default ~/.local/share/opencode/auth.json), honoring the OPENCODE_AUTH_CONTENT environment variable. Only api type credentials are used, OAuth credentials are ignored. A provider without a resolvable key is skipped and reported as an error.
Custom providers like this do not appear in /connect's provider list, that list only contains models.dev providers and providers registered by plugin auth hooks. To store a key via opencode, run /connect (or opencode auth login), pick Other, enter the provider ID (e.g. my-gateway) and paste the API key, the plugin picks it up from auth.json automatically.
{
"provider": {
"my-gateway": {
"npm": "@ai-sdk/openai-compatible",
"name": "My Gateway",
"options": {
"baseURL": "https://<your-gateway>/v1",
"apiKey": "sk-...",
"dynamic": true
}
}
}
}
Discovered metadata is mapped to opencode's model fields when present:
/v1/models field |
opencode field |
|---|---|
metadata.display_name |
name |
metadata.capabilities.reasoning |
reasoning |
metadata.capabilities.function_calling |
tool_call |
metadata.capabilities.vision |
attachment |
metadata.context_window / metadata.max_output_tokens |
limit.context / limit.output |
metadata.pricing.*_per_mtok |
cost.input / cost.output / cost.cache_read / cost.cache_write |
On v2 the same data is mapped to the v2 Model.Info shape (capabilities.tools, capabilities.input/output, limit, cost[].cache.read/write, and variants as an array of { id, settings } entries).
Reasoning models also get effort variants from the models.dev catalog (reasoning_options with type: effort), so gateway models expose the same effort picker as built-in providers (e.g. deepseek-flash lists low, high, max). The catalog URL defaults to https://models.opencode.ai/api.json and can be overridden with OPENCODE_MODEL_CATALOG_URL. When the catalog is unreachable or lists no effort options for a model, discovery still succeeds and opencode's built-in variants apply.
Fields declared for a model in opencode.json (v1 provider.<id>.models.<id>, v2 providers.<id>.models.<id>) take precedence over discovered metadata, so a model can pin options such as reasoningEffort without losing the rest of the discovered metadata.
Model IDs are used verbatim, so a gateway that advertises a virtual model (e.g. deepseek-flash) alongside the concrete provider model it targets (deepseek/deepseek-flash) lists both. When both share a display name, the concrete entry is named by its full ID so the two stay distinguishable in the picker; the virtual model keeps its display name. Missing metadata fields are omitted so opencode's defaults apply (e.g. tool_call defaults to true).
Only text generation models are listed; image, embedding, audio, rerank and other non-text models are skipped. A model is kept when its metadata.modes include chat, completion or responses, or when its metadata.categories include text_generation. LiteLLM-style gateways that send a top-level mode instead are matched on chat/completion. Models with no mode or category information are kept, so gateways that send neither are unaffected. Cost is only emitted when both input_per_mtok and output_per_mtok are present, since opencode requires both.
Tests
npm test
Uses Node's built-in test runner, no dependencies. Gateway /v1/models responses are stored as fixtures in test/fixtures/.