AI Development Tooling

Configuring OpenCode to Work Across Multiple LLM Providers

A practical, verified guide to running one coding agent against Anthropic, OpenAI, Google, and local models — with credentials that never touch your repository.

Daniel Dayto

Forward Deployed Engineer · Co-Founder & Technical Lead at Playmaker

Published
Reading time
12 min read
Abstract diagram of one developer interface fanning out to multiple model providers through a single configuration layer

OpenCode's most useful property is one most coding agents don't have: it treats the model as configuration. One terminal interface, one set of project instructions, one agent system — pointed at Anthropic today, an OpenAI-compatible endpoint tomorrow, and a local model on a plane. This guide covers the configuration that makes that real, with every path, key, and command verified against the official documentation at the time of writing. OpenCode moves fast; when this article and the current docs disagree, trust the docs.

Why run multiple providers at all

Three practical reasons, none of them ideological. Cost shaping — routine work doesn't need your most expensive model, and OpenCode's small_model setting exists precisely to route lightweight internal tasks somewhere cheaper. Availability — provider outages and rate limits are real; a second configured provider turns an outage into an inconvenience. Privacy tiers — some repositories can use hosted frontier models, some client work can't, and a local model behind the same interface means the workflow doesn't change when the privacy requirements do. What multi-provider setup does not buy you is interchangeability — models behave differently on the same prompt, and pretending otherwise is how teams ship inconsistency. More on that boundary at the end.

Where configuration lives

OpenCode reads two config files, both JSON (JSONC — comments allowed), both validated by the same schema:

  • Global: ~/.config/opencode/opencode.json — your personal defaults across every project.
  • Per-project: opencode.json in the project root — checked into Git, shared with the team. The docs are explicit that this file is designed to be committed.
text
~/.config/opencode/opencode.json    # personal defaults (global)
~/.local/share/opencode/auth.json   # credentials — managed by OpenCode, never committed
~/.local/share/opencode/log/        # logs for debugging

your-project/
├── opencode.json                   # team config — committed
├── .opencode/agents/               # project agent definitions — committed
├── AGENTS.md                       # project instructions (created by /init)
└── .env                            # local secrets — gitignored
The layout that keeps personal, project, and secret concerns separate.

Start every config file with the schema line — it turns editor autocomplete into documentation:

jsonc
{
  "$schema": "https://opencode.ai/config.json"
}

Credentials without leaks

There are two documented ways to get credentials into OpenCode, and they serve different purposes:

  1. 01Interactive auth. Run /connect inside the TUI (or opencode auth login from the shell), pick a provider, and paste a key or complete the browser OAuth flow. Credentials land in ~/.local/share/opencode/auth.json — outside your repository, which is exactly where they belong.
  2. 02Environment interpolation. Anywhere in opencode.json, the value {env:VARIABLE_NAME} is replaced with the environment variable at runtime, and {file:path} reads a file's contents. This is how a committed team config references secrets without containing any.
bash
# .env.example — copy to .env (gitignored) and fill in
ANTHROPIC_API_KEY=
OPENAI_API_KEY=
GEMINI_API_KEY=
# Local endpoints need no keys — Ollama/LM Studio run unauthenticated on localhost
An environment template for the variables your config interpolates. Commit the template; never the values.

One licensing caution from the official docs: Anthropic explicitly prohibits routing Claude subscription (Pro/Max) access through third-party tools. Use API credentials with OpenCode, not subscription plumbing.

Provider configuration

OpenCode ships with built-in knowledge of 75+ providers via the AI SDK and the models.dev database, so for major providers you often need zero provider config — authenticate and go. The provider block exists for customization: OpenAI-compatible endpoints, base-URL overrides, model allowlists, and options like timeouts. A complete multi-provider project config:

jsonc
{
  "$schema": "https://opencode.ai/config.json",

  // Default model for real work, cheaper model for lightweight internal tasks
  "model": "anthropic/claude-sonnet-4-5",
  "small_model": "anthropic/claude-haiku-4-5",

  "provider": {
    // Built-in providers usually need no block at all.
    // Add one only to customize — e.g. restrict which models appear:
    "anthropic": {
      "whitelist": ["claude-sonnet-4-5", "claude-haiku-4-5"]
    },

    // OpenAI-compatible endpoint (works for any conforming gateway)
    "internal-gateway": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "Internal Gateway",
      "options": {
        "baseURL": "https://llm-gateway.example.com/v1",
        "apiKey": "{env:GATEWAY_API_KEY}"
      },
      "models": {
        "code-model-v2": {
          "name": "Code Model v2",
          "limit": { "context": 200000, "output": 65536 }
        }
      }
    },

    // Local models — see next section
    "ollama": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "Ollama (local)",
      "options": { "baseURL": "http://localhost:11434/v1" },
      "models": {
        "qwen2.5-coder": { "name": "Qwen 2.5 Coder (local)" }
      }
    }
  }
}
opencode.json — a committed team configuration spanning hosted and local providers. No secrets present; everything sensitive arrives via {env:...}.

The keys that matter: npm selects the AI SDK adapter package (for anything OpenAI-compatible, that's @ai-sdk/openai-compatible); options carries baseURL, apiKey, headers, and timeout; models declares what appears in the picker, with optional context/output limits so OpenCode budgets correctly. Per-provider whitelist and blacklist arrays control which of a provider's models are exposed — useful for keeping a team on approved models.

Model selection

Every model reference uses one format: provider_id/model_idanthropic/claude-sonnet-4-5, openai/gpt-5.1-codex, ollama/qwen2.5-coder. Three places consume it: the top-level model key (your default), small_model (lightweight internal tasks like title generation), and per-agent overrides. At runtime, opencode models lists everything currently available in exactly this format, and opencode run -m provider/model "..." pins a model for a single non-interactive run.

ProviderAuth mechanismProvider block needed?Typical use
Anthropic/connect or {env:...}Optional (whitelist, options)Primary implementation and review work
OpenAI/connect or {env:...}OptionalSecond frontier opinion; specific model strengths
Google/connect; Vertex via GOOGLE_APPLICATION_CREDENTIALSOptionalLong-context work; GCP-standard shops
OpenAI-compatible gateway{env:...} in optionsYes — npm + baseURLCorporate gateways, OpenRouter-style routers
Ollama / LM Studio / llama.cppNone (localhost)Yes — baseURL blockPrivacy-constrained repos, offline work
Provider selection at a glance — the configurations this guide covers.

Local models

OpenCode's local-model story is deliberately boring: anything that speaks the OpenAI-compatible API is a provider. The officially documented endpoints — Ollama at http://localhost:11434/v1, LM Studio at http://127.0.0.1:1234/v1, llama.cpp's server at http://127.0.0.1:8080/v1 — all use the same @ai-sdk/openai-compatible adapter shown above. Two honest caveats from production use of local models generally: tool-calling reliability varies much more across local models than across frontier APIs, and a model that fits in local memory will not match frontier repository comprehension. Local models earn their place on privacy and availability, not parity.

Restricting providers

Governance goes in the committed project config. Two documented keys control the provider surface: enabled_providers (allowlist — only these appear) and disabled_providers (blocklist). For a client repository with data-residency constraints, an allowlist plus a model whitelist is a legible, reviewable policy:

jsonc
{
  "$schema": "https://opencode.ai/config.json",
  "enabled_providers": ["anthropic"],
  "provider": {
    "anthropic": { "whitelist": ["claude-sonnet-4-5"] }
  },
  "model": "anthropic/claude-sonnet-4-5"
}
A restrictive project policy: one hosted provider, specific models, nothing else selectable.

Per-agent models

OpenCode agents — defined under the agent key in config, or as markdown files in .opencode/agents/ (project) and ~/.config/opencode/agents/ (global) — each accept their own model, so role and provider decouple cleanly. A plan-only agent can run a different model than the build agent; a review subagent can run a third. Note that the older tools field is deprecated in favor of permission, which takes allow/ask/deny per capability:

markdown
---
description: Reviews diffs against the plan; never edits or runs commands
mode: subagent
model: anthropic/claude-sonnet-4-5
temperature: 0.1
permission:
  edit: deny
  bash: deny
---
You are a code reviewer. Read the diff and the plan. Report defects,
deviations from the plan, and risks. You do not fix anything yourself.
.opencode/agents/reviewer.md — a read-only review subagent pinned to its own model.

This is the configuration seam that makes the planner–builder–reviewer pattern practical in OpenCode: roles are files, models are fields, and both are code-reviewed like everything else in the repo.

Team configuration

The pattern that works: commit opencode.json (defaults, provider policy, agent definitions), commit .opencode/agents/, commit AGENTS.md (the project-instructions file /init generates), and commit an .env.example. Keep .env gitignored and let each engineer authenticate via /connect — personal credentials in ~/.local/share/opencode/auth.json never enter the repository. The result: a new engineer clones, copies the env template, runs /connect once, and inherits the team's exact model policy and agents.

Verifying the setup

bash
opencode auth list          # which providers have credentials
opencode models             # every available model, as provider/model
opencode models --refresh   # refresh the cached model list
opencode run -m ollama/qwen2.5-coder "Say OK"   # cheap end-to-end check per provider
opencode --print-logs       # run the TUI with logs streaming to stderr
The verification pass I run after any provider change.

Troubleshooting provider failures

SymptomLikely causeFix
Auth failure on a providerExpired/invalid credentialRe-run /connect (or opencode auth login); confirm the key is active in the provider console
"Model not available"Wrong identifier format or model not exposedUse provider_id/model_id exactly as opencode models prints it; check whitelist/blacklist
ProviderInitErrorBroken provider setup or corrupted stateVerify the provider block against the docs; as a last resort clear ~/.local/share/opencode and re-authenticate
Stale provider behaving oddly after upgradeCached provider packagesrm -rf ~/.cache/opencode and restart
Silent local-model failuresEndpoint not running or wrong portcurl the baseURL directly; confirm the server exposes /v1
Anything unclearLogs live in ~/.local/share/opencode/log/; run with --log-level DEBUG
Failure symptoms and documented remedies.

What's OpenCode-specific vs. provider-specific

Worth keeping the layers straight, because debugging depends on it. OpenCode owns: the config schema, {env:...} interpolation, the provider_id/model_id namespace, agent definitions, permission policy, and credential storage. The provider owns: actual model behavior, tool-calling reliability, context limits, rate limits, pricing, and outages. A provider block can make a model reachable; it cannot make it good at your task. When something misbehaves, first ask which layer you're in — config errors reproduce identically every run; provider errors vary with load, model version, and prompt.

Key takeaways

  • Two files, two audiences: ~/.config/opencode/opencode.json for personal defaults, a committed opencode.json for team policy. Both JSONC, both schema-validated.
  • Secrets never enter the repo: interactive /connect auth stores credentials in ~/.local/share/opencode/auth.json; committed config references secrets only as {env:VAR}.
  • One model namespace: provider_id/model_id everywhere — default via model, cheap tasks via small_model, roles via per-agent model fields.
  • Local models are just another provider behind an OpenAI-compatible baseURL — same interface, honestly weaker tool-calling.
  • No automatic fallback exists — plan provider failure as an operator runbook, not an assumption about the tool.

Sources & further reading

About the author

Daniel Dayto

Forward Deployed Engineer · Co-Founder & Technical Lead at Playmaker

Daniel Dayto builds and deploys production conversational AI systems for customer operations. His work spans voice agents, RAG assistants, CRM and dispatch integrations, multi-tenant infrastructure, and workflow automation.

Related articles

Deploying AI into a real operation?

I work with teams shipping voice agents, RAG systems, and workflow automation into production. Open to Forward Deployed Engineering, Applied AI, and founding technical roles.