Use any provider in opencode
To give opencode a custom provider, yoink adds a block under provider in ~/.config/opencode/opencode.json, choosing the AI SDK package that matches the endpoint. It edits the file in place, so JSONC comments and your own formatting survive every re-sync. Save the key with yoink add, then run yoink connect <name> --to opencode.
On this page
At a glance
| Fact | Value |
|---|---|
| Harness id | opencode |
| Config yoink writes | ~/.config/opencode/opencode.json |
| Format | JSON or JSONC |
| Protocols, in order of preference | openai-chat, anthropic-messages, openai-responses |
| Default model | "model": "<provider>/<model>" |
| Path override | XDG_CONFIG_HOME |
| Providers at once | many, side by side |
| Experimental | no |
| Presets that reach it | 7 of 7: openai, kimi-code, moonshot, openrouter, deepseek, zai, ollama |
| Connect command | yoink connect <name> --to opencode |
Generated from the yoink CLI source and docs/harnesses.md.
What yoink writes
opencode loads each custom provider through an AI SDK package named in npm. yoink picks it from the protocol it connects with:
| Protocol yoink picks | npm package | options.baseURL |
|---|---|---|
| OpenAI Chat | @ai-sdk/openai-compatible | the provider URL as saved, usually ending in /v1 |
| Anthropic Messages | @ai-sdk/anthropic | the provider URL with /v1 appended |
| OpenAI Responses | @ai-sdk/openai | the provider URL as saved |
Here is the block for an OpenRouter key saved as openrouter, with the key redacted:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"openrouter": {
"npm": "@ai-sdk/openai-compatible",
"name": "OpenRouter",
"options": {
"baseURL": "https://openrouter.ai/api/v1",
"apiKey": "<your key>"
},
"models": {
"z-ai/glm-4.7": {
"name": "GLM-4.7",
"tool_call": true,
"reasoning": true,
"limit": { "context": 204800, "output": 131072 }
}
}
}
},
"model": "openrouter/z-ai/glm-4.7"
}Every model gets tool_call: true, plus reasoning and limit from models.dev. A model that accepts images also gets modalities with its input types. The custom provider section of opencode's docs describes the same npm, options.baseURL and models shape for hand-written entries.
Comments and formatting survive
yoink never reparses and reprints your whole config. It changes only the provider.<id> block and, when asked, the model key, as text edits in place. If your file is opencode.jsonc with comments, the comments are still there after a connect, a model change, or a disconnect. If opencode.json does not exist yet, yoink creates it with opencode's $schema line.
Inside a model entry, yoink owns name, tool_call, reasoning, limit and modalities. Any other key you add to a model by hand, such as a custom option, is kept on re-sync.
Which providers work
opencode accepts all three protocols, so every preset reaches it, including ollama for local models. Its order is OpenAI Chat, then Anthropic Messages, then OpenAI Responses. Two results follow from that order:
- The
openaipreset offers Responses and Chat, and lands in opencode as@ai-sdk/openai-compatiblebecause Chat comes first. - A gateway that speaks only Responses and Anthropic Messages lands as
@ai-sdk/anthropic, not@ai-sdk/openai.
yoink connect openrouter --to opencode
yoink connect deepseek --to opencode --default <model>
yoink connect ollama --to opencodeSet the default model
--default <model> sets the top-level "model" key to <provider>/<model>, which is the format opencode expects. Leave it out and your current default stays. On disconnect, yoink clears model and small_model only when they start with that provider's id.
Backups and disconnect
The first change to an existing opencode.json saves opencode.json.yoink.bak once (see Backups). yoink disconnect openrouter --from opencode removes provider.openrouter, and drops the provider object entirely if it is left empty.
Detection and gotchas
- Desktop app. yoink treats opencode as installed when the
opencodebinary is on yourPATH, when~/.config/opencodeexists, or when it finds the OpenCode desktop app (/Applications/OpenCode.appon macOS,/opt/OpenCodeor the.desktopfile on Linux,%LOCALAPPDATA%\Programs\OpenCode\OpenCode.exeon Windows). - XDG_CONFIG_HOME. When it is set, the config is read from
$XDG_CONFIG_HOME/opencodeinstead of~/.config/opencode. - Import.
yoink importskips entries whoseapiKeyis{env:VAR},{file:...},$VARor!command, since yoink cannot sync a key it never sees. - Restart opencode after a change so it reloads the file.
FAQ
Will yoink strip the comments from my opencode.jsonc?
No. yoink edits only the provider block and the model key in place, and leaves the rest of the text, comments included, as it was.
Why does my OpenAI key show up as @ai-sdk/openai-compatible?
opencode's preference order puts OpenAI Chat first, and yoink takes the first protocol the provider offers. The openai preset has a Chat endpoint, so opencode gets the OpenAI-compatible package instead of @ai-sdk/openai.
Related
- Use OpenRouter in your coding agents
Add your OpenRouter key once and yoink writes it into Claude Code, opencode, pi, Qwen Code, Crush and 7 more. Codex is not supported: no Responses endpoint.
- One API key in every coding agent
Add an API key once and yoink writes it into the native config of up to 13 coding agents: pi, opencode, codex, Claude Code, Qwen Code, Crush and more.
- Connect a custom OpenAI- or Anthropic-compatible provider
Paste a base URL and key, and yoink probes /v1/models, /v1/messages, /chat/completions and /responses to find what a gateway or vLLM server speaks.
- Use any provider in pi
Connect any OpenAI- or Anthropic-compatible provider to pi. yoink writes ~/.pi/agent/models.json with model limits from models.dev and sets the default.
- Use any provider in omp
Connect a provider to omp. yoink writes ~/.omp/agent/models.yml (or models.yaml), carries over a legacy models.json and sets modelRoles.default in config.yml.
- Use a custom provider in Codex CLI
yoink writes a [model_providers] table into ~/.codex/config.toml with wire_api = "responses". Codex needs a Responses endpoint, so few presets fit.