opencode Setup Guide: Custom Providers in the Terminal
Guides

opencode Setup Guide: Custom Providers in the Terminal

Configure opencode against your own OpenAI-compatible endpoint — the provider block, model naming, per-mode model choice, and how to verify tool calls work.

opencode is a terminal coding agent: it runs in your shell, reads the repository, edits files and executes commands, with a text interface instead of an editor panel. Because it is provider-agnostic by design, pointing it at an endpoint of your own is a first-class path rather than a workaround — but it is a configuration-file path, not a settings dialog.

Everything it needs is the same triple as any other client: a base URL for an OpenAI-compatible surface, an API key, and a model identifier the endpoint publishes. The work is in expressing that in the config file and in telling the agent what the model can do.

Where the configuration lives

opencode reads a JSON configuration file, and it looks in two places: a per-project file at the root of the repository, and a global one in your user configuration directory. Project settings win where the two overlap, which is the behaviour you want — a repository can pin the model everyone uses while individuals keep their own defaults elsewhere.

Commit the project file. A model choice that lives only on one laptop is a source of unexplained differences in output quality, and putting it under review makes changing it a deliberate act. Keep the key out of it: reference an environment variable instead, so the file is safe to push.

If the config declares a schema URL at the top, most editors will offer completion for the keys, which is far more reliable than copying a block from a post — including this one — since key names do shift between releases.

The provider block, in shape

A custom provider is declared as a named entry with two parts: connection options, and a map of the models you are making available. Conceptually it looks like this, and you should treat it as a shape rather than a literal paste:

{
  "provider": {
    "my-endpoint": {
      "options": {
        "baseURL": "https://api.example.com/v1",
        "apiKey": "{env:MY_API_KEY}"
      },
      "models": {
        "the-model-name": {}
      }
    }
  }
}

The base URL should end at the version segment of your provider path. Do not append the path for a specific operation; the client adds it, and doubling it produces a 404 that looks like an authentication error. The rest of that surface, and where compatibility stops being reliable, is covered in the compatibility explainer.

The keys inside the models map are the identifiers sent upstream verbatim. If your provider publishes a name, use it exactly; there is no fuzzy matching, and an unknown name errors rather than falling back.

Telling it what the model can do

For providers it ships knowledge of, opencode knows the context window, the pricing and whether the model handles tools. For an endpoint it has never seen, it knows none of that, and the model entry is where you fill the gap.

The context window is the field that changes behaviour rather than display. It determines when the agent trims or summarises history. Declare it below the real window and long sessions get compacted for no reason, discarding detail the agent needed; declare it above and requests start failing partway through a task. Check the provider page for the real figure instead of guessing.

Tool support is the other one that matters. A terminal agent is nothing but a tool loop, so a model that does not negotiate tool calls produces a description of the change instead of the change. If your output reads like a plan that never executes, suspect this before you rewrite your prompt.

Different models for different jobs

opencode separates the agent that plans from the agent that builds, and each can be given its own model. This is worth setting up on day one, because the two jobs reward different things.

Planning rewards reasoning quality and is cheap to correct — a bad plan costs you a paragraph of reading. Execution is largely mechanical file editing, and a bad edit costs you a broken branch. So a strong model for planning and a faster, cheaper one for the build loop is usually the right default, and you tune it by watching how often you have to fix the build model rather than by comparing scores.

Switch models inside a session from the interface rather than editing config mid-task. If a model consistently needs correcting on your codebase, move it up a tier rather than moving the planning model down.

Instructions files carry the real configuration

opencode reads an agent instructions file from the repository, the same convention several other terminal agents follow. That file is where the difference between a useful agent and an expensive one is decided.

Write the checkable facts: the package manager, the test command, the directories that are generated and must never be hand-edited, the error-handling convention, and an explicit instruction to make the smallest change that satisfies the request and ask before refactoring adjacent code. Agents over-reach by default.

Because the file lives in the repository, it is reviewable, and a new joiner inherits it without being told. That property is worth more than any individual rule in it.

Verifying, in three steps

Start with a one-sentence question. If that returns, the base URL, key and model name all agree, which is most of what goes wrong.

Then ask it to read a named file and quote a line back. That exercises tool calling and file access, a different path from plain chat, and it is the step that catches a model that cannot use tools.

Only then give it a real task, on a branch. A terminal agent that can run commands is a terminal agent that can run the wrong command, so decide your approval settings deliberately and consider running it inside a container — the reasoning is set out in agent sandboxing.

What commonly goes wrong

A 401 is a key from a different provider or whitespace from a paste. A 404 on the endpoint is a duplicated version segment. A 404 naming the model means the identifier is not one your provider serves — list the models from the endpoint directly to settle it. If the config looks right but the key is empty at runtime, the environment variable is not exported in the shell that launched the agent.

If output arrives in a single lump rather than streaming, something in the path is buffering the response. If the agent talks but never edits, it is tool calling. If costs surprise you, the context window and how much of the repository you are attaching are the two levers, not the model price.

The takeaway

Put a provider block in the project config with the base URL, an environment-variable key reference and the exact model name, declare the real context window, then prove a file read before you trust a refactor. If you also run other terminal agents, Aider and OpenClaw want the same three facts in different files, and sharing one key across them keeps rotation from becoming a chore.

Common questions

Where should the API key go?

In an environment variable, referenced from the config file. The project config is meant to be committed so the team shares a model choice, and a key inside a committed file is a key you will be rotating. If the key reads as empty, check it is exported in the shell that launched the agent.

Why does the agent describe edits instead of making them?

Tool calling is not happening. Either the model behind your identifier does not support it, or the model entry has not declared tool capability. opencode cannot infer capabilities for an endpoint it does not ship knowledge of.

Is it worth using separate models for planning and building?

Usually. Planning rewards reasoning and is cheap to correct; building is mechanical editing where speed and cost matter more. Judge the split by how often you have to fix the build model output, not by benchmark scores.

Similar articles

Aider Setup Guide: Any OpenAI-Compatible Endpoint
Guides
Guides·8 min read

Aider Setup Guide: Any OpenAI-Compatible Endpoint

Configure Aider against a custom base URL — the openai/ prefix, .aider.conf.yml, model metadata for unknown models, and picking the right edit format.

Read
Cline Setup Guide: Pointing It at a Custom Endpoint
Guides
Guides·8 min read

Cline Setup Guide: Pointing It at a Custom Endpoint

Configure Cline against any OpenAI-compatible base URL — the provider fields, the model configuration block that people skip, and separate Plan and Act models.

Read
Continue.dev Setup: config.yaml for a Custom Endpoint
Guides
Guides·8 min read

Continue.dev Setup: config.yaml for a Custom Endpoint

Point Continue at any OpenAI-compatible base URL using config.yaml — model roles, apiBase, request options, and why the autocomplete slot needs its own model.

Read