OpenCode
Catalog name opencode. OpenCode is sst’s open-source,
provider-agnostic AI coding agent (a terminal UI).
What it’s for
Section titled “What it’s for”An AI coding agent that, unlike Claude Code, is not tied to one model vendor:
you choose a model as provider/model-id and supply that provider’s key - a
hosted provider, or a local/self-hosted OpenAI-compatible endpoint. Its config
and login state persist across container rebuilds.
Add it
Section titled “Add it”monoceros init acme --with-features=opencodemonoceros add-feature acme opencodeRemove it
Section titled “Remove it”monoceros remove-feature acme opencodeOr delete the feature’s entry from acme.yml by hand and re-run
monoceros apply acme - the yml is the source of truth.
Options
Section titled “Options”| Option | Default | Description |
|---|---|---|
version | latest | npm-style version spec for opencode-ai (latest, ^0.4, 0.4.2). |
model | (empty) | Default model as provider/model-id, e.g. anthropic/claude-sonnet-4-6. The provider is the part before the /. Empty means pick one interactively on first run. |
apiToken | (empty) | API key for the model’s provider. Empty means log in interactively (opencode auth login). |
npm | (empty) | Custom/local providers only. The AI-SDK driver package, usually @ai-sdk/openai-compatible. Setting it switches to custom-provider mode (see Local models). |
baseUrl | (empty) | Custom/local providers only. The endpoint URL, e.g. http://ollama:11434/v1. |
theme | (empty) | TUI theme, e.g. system, tokyonight, catppuccin. Empty means whatever you pick with /theme. |
lsp | false | Start the built-in language servers, so the agent sees type errors right after an edit. |
Monoceros turns these into ~/.config/opencode/opencode.json and
~/.config/opencode/tui.json on every apply, so the yml stays the source of
truth. These options are the knobs you touch; you never edit those files by
hand.
/theme in the TUI changes the theme for that container and stays there, which
means every new workbench starts over. Set theme in the yml and each one comes
up the way you like it. system follows your terminal’s background, which is
usually the right answer over SSH.
The option wins over a theme picked with /theme. Both end up in the same
setting, the option through tui.json and the picker through OpenCode’s own
state, and the config is what the container comes up with. So use /theme to
try something out, and the option for the one you want to keep.
Only the theme key in tui.json is ours: the sidebar, the diff style and the
notification settings stay whatever you made them, and clearing the option
removes the key again rather than leaving a value nothing declares.
Language servers
Section titled “Language servers”OpenCode can feed the diagnostics of a language server back to the agent, so it sees a type error or an unresolved import the moment it makes one, rather than when the acceptance command runs. It ships with servers for TypeScript, Go, Python, Java, Rust, PHP and about thirty more, and they are off by default.
lsp: true switches them on. A server whose toolchain is missing does not
start, so a Node workbench does not suddenly run a Go language server, and the
few that install themselves on first contact only do so for a file type you
actually opened. Those downloads are kept across an apply, so you pay for them
once.
Look into opencode.json afterwards and you will find more than the switch:
"lsp": { "typescript": { "disabled": true }, "tsgo": { "command": ["tsc", "--lsp", "--stdio"], "extensions": [".ts", ".js", "…"] }}That is the switch doing its job for the language most people write here. The
container ships TypeScript 7, the native port, which has no tsserver.js for
OpenCode’s built-in TypeScript server to launch, so that server fails to start
and says nothing about it. TypeScript 7 speaks the protocol itself, so
Monoceros registers it instead and takes the built-in one out of the way. Both
at once would mean the same file diagnosed twice by two different checkers in a
project that pins TypeScript 5.
If that is what you want in one project, its own .opencode/opencode.json wins
over the container’s, and you can re-enable the built-in server there. And to
keep any server out everywhere, add it to opencode.json yourself: Monoceros
merges that file rather than overwriting it, so a
"lsp": { "bash": { "disabled": true } } block survives the next apply.
Configurations
Section titled “Configurations”Hosted model with an API key
Section titled “Hosted model with an API key”The common case: a hosted provider that authenticates with a single key.
features: - ref: ghcr.io/getmonoceros/monoceros-features/opencode:1 options: model: anthropic/claude-sonnet-4-6 apiToken: ${OPENCODE_API_TOKEN}The provider (anthropic) is derived from the model prefix, and the key is
written to that provider. Supported single-key providers today: anthropic,
openai, google, openrouter, mistral, groq, deepseek, xai. To swap
models, change model and re-run apply.
Subscription / interactive login
Section titled “Subscription / interactive login”Leave both model and apiToken empty to stay provider-neutral. On first run
OpenCode prompts you to choose a provider and authenticate with
opencode auth login; the result is stored under ~/.local/share/opencode and
survives rebuilds.
features: - ref: ghcr.io/getmonoceros/monoceros-features/opencode:1 options: model: '' apiToken: ''Local models (Ollama)
Section titled “Local models (Ollama)”OpenCode can talk to a local or self-hosted, OpenAI-compatible endpoint. Set
npm (the driver) and baseUrl (the endpoint); Monoceros then assembles the
full provider block from model + npm + baseUrl.
features: - ref: ghcr.io/getmonoceros/monoceros-features/opencode:1 options: model: ollama/llama3 npm: '@ai-sdk/openai-compatible' baseUrl: 'http://ollama:11434/v1'This pairs naturally with running the model as a service in the same workbench:
add an ollama service and point baseUrl at it by its service name on the
Docker network (http://ollama:11434/v1). Nothing leaves the machine.
Starting OpenCode
Section titled “Starting OpenCode”OpenCode runs inside the container. Use monoceros run
with -- to separate Monoceros flags from the inner command (or open a
monoceros shell and call opencode
directly).
Interactive (the terminal UI):
monoceros run acme -- opencodeInteractive, seeded with an opening prompt (the TUI starts with your message already sent, then you keep going from there):
monoceros run acme -- opencode --prompt "Explain the auth flow in this repo"One-shot (non-interactive: prints the answer and exits):
monoceros run acme -- opencode run "Explain the auth flow in this repo"Pick the model inline with -m (on either opencode or opencode run),
overriding the yml default:
monoceros run acme -- opencode run -m anthropic/claude-sonnet-4-6 "Summarise README.md"Coming from Claude Code, the three modes map cleanly:
| Claude Code | OpenCode |
|---|---|
claude | opencode |
claude "<prompt>" | opencode --prompt "<prompt>" |
claude -p "<prompt>" | opencode run "<prompt>" |
See the OpenCode CLI reference.
The briefing
Section titled “The briefing”On apply Monoceros writes an AGENTS.md stack briefing at the workspace root
and registers it (plus the Monoceros command reference) as OpenCode
instructions, so the agent knows the real stack - languages, services, tools -
without per-session setup. See Features.