Custom plugins for opencode.
  • JavaScript 100%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-18 17:48:08 +02:00
test feat(dynamic-models): support opencode v2 alongside v1 2026-10-18 13:43:20 +02:00
dynamic-models.js feat(dynamic-models): support opencode v2 alongside v1 2026-10-18 13:43:20 +02:00
oh-my-opencode-slim.md docs(oh-my-opencode-slim): add install and config guide 2026-10-18 17:48:08 +02:00
package.json feat(dynamic-models): support opencode v2 alongside v1 2026-10-18 13:43:20 +02:00
README.md feat(dynamic-models): support opencode v2 alongside v1 2026-10-18 13:43:20 +02:00

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/.