opencode-cpa-provider
v0.1.0
Published
Metadata-aware CLIProxyAPI provider for OpenCode v2
Readme
OpenCode v2 CLIProxyAPI provider
Personal-use provider plugin built from scratch for OpenCode v2. Discovers CPA's served models and uses OpenCode's native OpenAI-compatible runtime for inference. No management key, upstream authentication or existing discovery plugin code.
Load
cd /mnt/media/Git/opencode-cpa-provider
bun install
git config --local core.hooksPath .githooksRemove your old discovery plugin from plugins, then add this entry to your
OpenCode configuration. Keep unrelated plugins and settings. This repository does
not modify your live configuration.
{
"$schema": "https://opencode.ai/config.json",
"plugins": [
{
"package": "/mnt/media/Git/opencode-cpa-provider",
"options": {
"baseURL": "http://192.168.1.1:8317/v1",
"apiKeyEnv": "CPA_API_KEY",
},
},
],
}Set CPA_API_KEY in the OpenCode server's environment if CPA requires a client
key. No key is needed for an unauthenticated endpoint. Restart/reload OpenCode,
then select cpa/<advertised-id> in /models. Namespaced and ~ IDs are preserved.
Discovery runs in the background; the initial list may take a few seconds.
To keep your current provider prefix (and saved selections), set providerID to
that prefix instead of cpa. Existing providers.<id> settings and explicitly
defined models take precedence over discovered defaults. Old static models remain
until you remove them from configuration, even if CPA stops advertising them.
Provider settings are also the place for custom headers, model overrides or a Responses runtime:
{
"providers": {
"cpa": {
"settings": {
"baseURL": "http://192.168.1.1:8317/v1",
"apiKey": "{env:CPA_API_KEY}",
},
"models": {
"unusual-model": {
"limit": { "context": 131072, "output": 8192 },
"capabilities": { "tools": true, "input": ["text"], "output": ["text"] },
},
},
},
},
}Metadata
Works with or without /mnt/media/Git/cpa-plugin-model-context installed in CPA.
It reads ordinary /v1/models, not the synthetic Codex catalogue. Both
data[].id and models[].slug list formats are accepted.
- Uses advertised names, family, context/input/output limits, modalities, tool support, release dates and per-million-token prices (including cache prices).
- Turns
thinking.levels,supported_reasoning_levelsand effort-typereasoning_optionsinto selectable variants. Explicit false/empty controls win. - Advertised budget bounds create numeric variants;
zero_allowedaddsnoneanddynamic_allowedaddsauto. An advertised toggle also addsnoneunless nativezero_allowedexplicitly forbids it. No invented effort names or budgets. - Variants send CPA's
model(effort)ormodel(budget)syntax through a raw body overlay. CPA handles the upstream translation. For example,cpa/~vendor/model#maxsends~vendor/model(max). - Recognises Chat Completions
reasoning_contentfor streamed reasoning and conversation replay. Overridecompatibility.reasoningFieldper model if needed. - Bare rows assume text input/output and tool support, with a 32,768 context and 8,192 output limit. These are fallback assumptions, not detected capabilities. Missing vision support is not guessed. Explicit non-text output is excluded.
attachment,temperatureandstructured_outputflags have no corresponding v2 model capability field; they do not become unsupported schema extensions. Output limits not emitted by your CPA metadata plugin remain fallback values.
No secondary models.dev/OpenRouter fetches or model-name heuristics are used. The metadata plugin remains the single owner of catalogue matching.
Options
| Option | Default | Meaning |
| ------------------- | -------------------------- | ------------------------------------------------ |
| providerID | cpa | OpenCode provider/model prefix |
| baseURL | http://127.0.0.1:8317/v1 | CPA API URL; root URLs get /v1 appended |
| apiKeyEnv | CPA_API_KEY | Client key environment variable name |
| refreshIntervalMs | 60000 | Inventory refresh interval; 0 disables polling |
| timeoutMs | 5000 | Discovery deadline |
| fallbackContext | 32768 | Context when no positive limit is advertised |
| fallbackOutput | 8192 | Output when no positive limit is advertised |
| assumeTools | true | Tool support when CPA supplies no explicit flag |
Provider settings.baseURL takes precedence over plugin options. Credentials
follow OpenCode's inference precedence: an active connection for integrationID
(or the provider ID by default), then settings.apiKey, then the configured
environment variable. Account-bound inventory is hidden after switching accounts
until refresh succeeds. Discovery uses provider headers and does not follow redirects. API keys
and model lists are not stored on disk by this plugin.
Run /cpa-refresh for an immediate refresh. Requests are serialised. A failed or
malformed response keeps the last successful inventory; a valid empty list clears
discovered models. If CPA is unavailable on first load, only explicit configured
models are available until discovery succeeds. Failure messages go to the server
log without keys or response bodies.
Local verification
bun run check
bun run lint
bun run smoke
bun run buildThe smoke script runs a short in-process fixture server and closes it afterwards.
It checks metadata mapping, the refresh lifecycle and a native OpenCode runtime
request against a local streaming fixture, including reasoning and variant
lowering. It does not call paid upstream models or check upstream model quality.
dist/index.js is an optional compiled entrypoint; the local package loads
src/index.ts through the root index.ts directory-loader entrypoint.
Architecture: docs/design.md.
Linting uses the thea-mode oxlint.config.ts template, including type-aware lint
and type checking. Targeted exceptions cover OpenCode's required default export,
intentional environment lookup, plain schema-data copying, the captured lifecycle
scope and the minimal Bun-only smoke fixture; no blanket lint disable is used.
bunfig.toml, oxfmt.config.ts, tsconfig.json and commitlint.config.ts also
use the thea-mode templates. TypeScript's application-specific Vite/Worker types
are replaced with Bun types; the strict compiler checks are retained. Formatting
sorts imports and package scripts. Bun disables telemetry and applies the
template's three-day minimum release age to dependency resolution. Local Git
hooks run check before commits and enforce conventional commit messages.
bun run lint:commits checks the latest commit independently.
