Skip to content

OpenCode

Catalog name opencode. OpenCode is sst’s open-source, provider-agnostic AI coding agent (a terminal UI).

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.

Terminal window
monoceros init acme --with-features=opencode
monoceros add-feature acme opencode
Terminal window
monoceros remove-feature acme opencode

Or delete the feature’s entry from acme.yml by hand and re-run monoceros apply acme - the yml is the source of truth.

OptionDefaultDescription
versionlatestnpm-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.
lspfalseStart 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.

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.

The common case: a hosted provider that authenticates with a single key.

acme.yml
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.

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.

acme.yml
features:
- ref: ghcr.io/getmonoceros/monoceros-features/opencode:1
options:
model: ''
apiToken: ''

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.

acme.yml
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.

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):

Terminal window
monoceros run acme -- opencode

Interactive, seeded with an opening prompt (the TUI starts with your message already sent, then you keep going from there):

Terminal window
monoceros run acme -- opencode --prompt "Explain the auth flow in this repo"

One-shot (non-interactive: prints the answer and exits):

Terminal window
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:

Terminal window
monoceros run acme -- opencode run -m anthropic/claude-sonnet-4-6 "Summarise README.md"

Coming from Claude Code, the three modes map cleanly:

Claude CodeOpenCode
claudeopencode
claude "<prompt>"opencode --prompt "<prompt>"
claude -p "<prompt>"opencode run "<prompt>"

See the OpenCode CLI reference.

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.