OpenCode plugin for engines v1 and v2. Signs OpenCode in to a GenAI platform's model gateway (OAuth: browser or device code, with automatic token refresh) and, at every start, fills in the user's models, MCP servers and skills from that platform's backend. The user's own config always wins.
Your platform's backend publishes this plugin, with its own options, in
https://<origin>/.well-known/opencode. Two commands, same on v1 and v2:
opencode auth login https://<origin> # installs and configures the plugin
opencode auth login -p <provider> # sign in (v2: opencode auth login <provider>)Restart OpenCode to pick up backend changes. Signing in always re-discovers
the authorization server, so a moved one needs no restart. On v2, signing in or
out also reloads models, MCP servers and skills in the running background
service: no opencode service restart needed.
Headless machine: pick the device-code method. On opencode v2 without a
browser, put a stub xdg-open on PATH (printf '#!/bin/sh\necho "$1"\n'),
or the CLI exits instead of printing the login URL.
{
"plugin": [
["git+https://github.com/ackstorm/opencode-oidc-provider.git#v0.4.0",
{"api": "https://api.example.com/v1", "platform": "https://api.example.com", "provider": "ai-platform"}]
]
}Install from the repo root spec: opencode v1 ignores git subdirectory
(::path:) specs.
| Option | Meaning |
|---|---|
api |
The gateway's OpenAI-compatible base URL (e.g. https://api.example.com/v1). OAuth discovery starts here (RFC 9728 → RFC 8414). |
platform |
The origin serving /clients/*. Config comes from <platform>/clients/opencode/config (schema ackstorm.opencode-config/1). |
provider |
The OpenCode provider id: the name in the login picker, the model prefix (<provider>/<model>), and the plugin's data folder. |
A platform.json with the same fields next to index.mjs is read as defaults
(tarball installs); plugin options override it.
What the backend serves at /.well-known/opencode (unauthenticated; public data only):
{
"auth": {"command": ["echo", "Sign in with: opencode auth login -p <provider>"], "env": ""},
"config": {
"plugin": [["git+https://github.com/ackstorm/opencode-oidc-provider.git#v0.4.0",
{"api": "https://<origin>/v1", "platform": "https://<origin>", "provider": "<provider>"}]]
}
}OpenCode requires auth; this plugin does not use the credential it creates.
Pin a tag; bump it in the manifest to roll out a new plugin version.
One file serves both: v1 loads default.server (SsoAuth, the auth +
config hooks), v2 loads default.setup (integration OAuth methods with
refresh, plus provider/MCP/skill transforms). v2 injects the credential by
integrationID and refreshes it 5 minutes before expiry.
The server's model / small_model (<provider>/<name>) are applied as defaults
on v1 only; the user's own model / small_model win. v2 ignores them for now
(its core has model.transform → default.set, but plugin access is unverified).
| File | Content |
|---|---|
<provider>/client.json |
The dynamic client registration (client id). |
<provider>/config.json |
Last good backend config (0600), used for 30 days when the backend is down. |
<provider>/rotation.json |
The latest tokens this machine exchanged (0600): when processes race a refresh-token rotation, the loser adopts the winner's tokens from here instead of failing the request. |
<provider>/skills/<name>/SKILL.md |
Skills delivered by the backend. |
Tokens live in OpenCode's own credential store; the plugin never writes it.
rotation.json is the one exception by design: OpenCode shares one credential
across processes, and the authorization server invalidates a refresh token the
moment it rotates it, so a process that loses the race needs the winner's tokens
to keep serving the request.
How long the loser waits for them depends on the backend's answer. A token
endpoint that replies invalid_grant with error_description: "refresh token rotation in progress" (ACH 0.13.6 and later: another refresh holds the token
and may still be waiting on the identity provider) gets up to 35 s, then one
more exchange in case that refresh failed and the token was handed back. Any
other 4xx is a dead session after 2.5 s: session expired, run opencode auth login <provider>.