Running SOTA Open AI Models in opencode Without Paying Frontier AI Prices
How I wired opencode up to Together AI’s model catalog and built three switchable cost profiles so I get top tier reasoning where it matters and cheap tokens everywhere else.
Agentic coding tools are hungry for tokens. A single session can burn through a plan, a dozen tool calls, several rounds of edits, and a handful of subagent delegations, and every one of those is a separate model call. If you point the whole thing at a single frontier model, you are paying frontier prices for work that mostly does not need frontier reasoning, like grepping a file or generating a session title.
Together AI hosts a rotating catalog of open weight SOTA models (GLM, DeepSeek, Kimi, Qwen, MiniMax, and OpenAI’s own open weight releases) at a fraction of the per token cost of closed frontier APIs. opencode, the open source terminal coding agent, lets you assign a different model to each internal role it plays: the main build loop, the planning pass, delegated subagents, and small system tasks like titles. Put those two things together and you can route expensive reasoning only to the calls that are rare and high stakes, while the high volume work runs on much cheaper models, or on hardware you already own instead of a metered API at all.
This post walks through the whole setup: installing opencode, wiring up Together AI as a provider, understanding opencode’s role based routing, and building three switchable profiles (normal, max, and cheap) so you can dial quality and spend up or down per session without editing config files by hand.
1. Installing opencode
opencode ships a Homebrew tap:
brew install sst/tap/opencodeThat pulls in ripgrep as a dependency and installs the opencode binary. Confirm it worked:
opencode --version2. Getting a Together AI API key
Sign up at together.ai, open the API keys page in your dashboard, and generate a key. It looks like tgp_v1_.... Treat it like any other API credential: do not commit it to a repo, do not paste it into a public gist, and if it ever ends up somewhere it should not be, rotate it from the same dashboard page.
I export mine as an environment variable in ~/.zshrc rather than hardcoding it in any config file, so the config itself stays shareable:
# Together AI (used by opencode)
export TOGETHER_API_KEY="your-key-here"Open a new terminal, or run source ~/.zshrc, so the variable is live.
3. Wiring up Together AI as a custom provider
opencode’s global config lives at ~/.config/opencode/opencode.jsonc. Together AI is not one of opencode’s built in providers, but it exposes a standard OpenAI compatible chat completions endpoint, so you can describe it as a custom provider:
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"togetherai": {
"npm": "@ai-sdk/openai-compatible",
"name": "Together AI",
"options": {
"apiKey": "{env:TOGETHER_API_KEY}",
"baseURL": "https://api.together.xyz/v1"
}
}
},
"model": "togetherai/deepseek-ai/DeepSeek-V4-Pro-0813"
}Two details that are easy to get wrong:
The env var placeholder syntax is {env:VAR_NAME}, not ${VAR_NAME}. Several other tools use the shell style dollar-brace syntax, and opencode looks similar enough in its docs that it is an easy mistake to carry over. If you use the wrong syntax, opencode will not error, it will just pass the literal string ${VAR_NAME} to Together as your API key, and you will get a confusing “invalid API key” error that looks like the key itself is wrong.
You do not need to hand list every model you plan to use. Once the provider is defined, opencode queries Together’s live model catalog itself. Run this to see everything currently available, including pricing:
opencode models togetheraiThat list changes over time as Together adds and retires models, so treat any specific model ID in this post as a snapshot, and re-run the command before you lock in a config.
4. How opencode routes models by role
opencode does not use one model for everything. It has a handful of internal roles, and each one can point at a different model:
modelis the global default, and it drives the build agent, which is the main loop that reads your message, calls tools, and edits files. This is the role that fires on every single turn, so it is your highest volume, most expensive-in-aggregate role.small_modelhandles lightweight system tasks: generating a session title, summarising, compaction. These calls are frequent but trivial, and a weak cheap model is completely fine here.agent.plan.modeloverrides the model used by the plan agent, the read only mode you switch into for upfront design and architecture work before you start editing. This is called far less often than build, usually once at the start of a task, which makes it the best place to spend on a genuinely strong reasoning model. A bad plan cascades into wasted spend everywhere downstream, so paying up here has real leverage.agent.general.modeloverrides the default subagent, the one opencode spins up when it delegates a bounded piece of work (a targeted search, a scoped edit, a verification pass) via the Task tool. Subagents fire many times in a single session, and unless you override this, every one of them silently inherits whatever model the primary agent that spawned it is using. If you never touchagent.general, your cheap subagents are quietly running on your most expensive model.
That last point is the one that catches people out. opencode’s own docs put it plainly: if you do not specify a model for a subagent, it uses the model of whichever primary agent invoked it. So a strong, expensive build model means every subagent it spawns is also running at that price, whether or not the subagent’s task actually needs that much reasoning.
5. Three profiles: normal, max, and cheap
Rather than picking one model and living with the tradeoff, I built three config profiles and a way to switch between them per invocation.
First, the real numbers. Here is what I pulled straight from Together’s /v1/models endpoint for the models I ended up using, in dollars per million tokens, plus Anthropic’s own published rates for Opus 5, Sonnet 5, and Haiku 4.5 thrown in so you can see where the Together catalog actually sits relative to frontier pricing. I also added each model’s SWE-bench Verified score, since it is the closest thing coding tooling has to a standard benchmark, and it is a useful check that cheaper does not automatically mean worse:
Sorted by SWE-bench Verified score rather than price, with a cost tier marker per row: 🔴 for the $10+/MTok tier, 🟡 for the $1-10/MTok middle, 🟢 for anything under $1/MTok.
| Model | Input | Output | Context | SWE-bench Verified* | |
|---|---|---|---|---|---|
| 🔴 | Claude Opus 5 | 5.00 | 25.00 | 1M | 97.0% |
| 🟡 | DeepSeek V4 Pro (0813) | 1.32 | 3.96 | 1M | 96.4% |
| 🟡 | GLM 5.3 | 1.40 | 4.40 | 1M | 95.4% |
| 🔴 | Kimi K3 | 3.00 | 15.00 | 1M | 93.4% |
| 🟢 | DeepSeek V4 Flash (0731) | 0.14 | 0.28 | 1M | 88.7% |
| 🟡 | GLM 5.2 | 1.40 | 4.40 | 512k | 83.0% |
| 🔴 | Claude Sonnet 5 | 2.00 | 10.00 | 1M | 79.6% |
| 🟡 | MiniMax M3 | 0.30 | 1.20 | 512k | 75.3% |
| 🟡 | Qwen 3.6 Plus | 0.50 | 3.00 | 1M | 73.6% |
| 🟡 | Grok Code Fast 1 | 0.20 | 1.50 | 256k | 70.8%† |
| 🟡 | Qwen 3.7 Max | 1.25 | 3.75 | 1M | 68.4% |
| 🟡 | Claude Haiku 4.5 | 1.00 | 5.00 | 200k | 66.4% |
| 🟢 | gpt-oss-120b | 0.15 | 0.60 | 131k | 33.6% |
| 🟢 | gpt-oss-20b | 0.05 | 0.20 | 131k | — |
*Scores are from Vals AI’s SWE-bench Verified leaderboard, which runs every model through the same minimal bash-only harness instead of each lab’s own scaffolding, so the numbers are actually comparable across vendors rather than each lab grading its own homework. Where Vals AI publishes a single headline number I used that (Opus 5, DeepSeek V4 Pro 0813, Kimi K3); for the rest I weighted their published per-difficulty breakdown by each band’s share of the 500 task set. gpt-oss-20b has not been run on this benchmark as of writing. †Grok Code Fast 1 isn’t on Vals AI’s board at all, so that 70.8% is BenchLM.ai’s number instead, run on their own harness, not directly comparable to the rest of the column, but I’d rather show it with an asterisk than leave out xAI’s own answer to the cheap-fast-coding-model niche entirely. Two things jump out: Sonnet 5 is beaten on this specific benchmark by two open models a fraction of its price, and GLM 5.2, which used to carry the entire build role in every profile below, trails DeepSeek V4 Pro by 13 points despite similar pricing. That gap is exactly why build now runs DeepSeek V4 Pro in Normal and Max instead.
GLM 5.3 is already in that table even though it is not in any of the profiles below. Z.AI shipped it on August 18, at the same $1.40/$4.40 pricing as 5.2 but with the context window doubled to 1M and a SWE-bench Verified jump from 83.0% to 95.4%, close to DeepSeek V4 Pro and Opus 5. Together’s own model page has it listed as “launching soon” as of writing, so it is not swappable into togetherai/zai-org/GLM-5.2 yet regardless. And once it does land, it is not actually the upgrade it looks like at first glance: DeepSeek V4 Pro still edges it out on this benchmark and costs slightly less on both input and output. Worth re-checking once it is generally available and its score has had time to settle, but on the numbers as they stand today it does not displace DeepSeek V4 Pro in the build role. This is exactly the kind of check opencode models togetherai is there to let you make for yourself instead of taking a launch announcement’s word for it.
Normal is the everyday driver. DeepSeek V4 Pro covers both the frequent build role and the occasional plan call, since at 96.4% on SWE-bench Verified it is already close to the ceiling of what Together hosts and there is no meaningfully stronger open model left to reserve specifically for planning. A solid mid tier model handles subagents, and the cheapest usable model handles titles:
{
"model": "togetherai/deepseek-ai/DeepSeek-V4-Pro-0813",
"small_model": "togetherai/openai/gpt-oss-20b",
"agent": {
"general": { "model": "togetherai/MiniMaxAI/MiniMax-M3" }
}
}No agent.plan override here on purpose, plan just inherits the same DeepSeek V4 Pro as build.
Max keeps the build and subagent roles the same, but upgrades planning by stepping outside Together entirely. Once build itself is already running DeepSeek V4 Pro, there is no open model left on Together that clearly beats it for planning, so the only real upgrade left is Claude Opus 5, Anthropic’s actual frontier model, at 97.0% on the same benchmark. Plan is a low frequency role, so absorbing a several-times price jump there barely moves total session cost, and it buys the best reasoning available for the one decision everything downstream depends on:
{
"model": "togetherai/deepseek-ai/DeepSeek-V4-Pro-0813",
"small_model": "togetherai/deepseek-ai/DeepSeek-V4-Flash-0731",
"agent": {
"plan": { "model": "anthropic/claude-opus-5" },
"general": { "model": "togetherai/MiniMaxAI/MiniMax-M3" }
}
}anthropic is one of opencode’s built in providers, so this needs no custom provider block the way Together did, just an ANTHROPIC_API_KEY set the same way you set TOGETHER_API_KEY back in section 2.
Cheap targets the highest volume role hardest. Build drops to DeepSeek’s Flash tier, about fourteen times cheaper than DeepSeek V4 Pro on output tokens, and it is the same DeepSeek V4 family as the Pro model running build elsewhere, so it is a distilled sibling rather than an unrelated budget model. Subagents drop one notch to gpt-oss-120b instead of getting gutted entirely:
{
"model": "togetherai/deepseek-ai/DeepSeek-V4-Flash-0731",
"small_model": "togetherai/openai/gpt-oss-20b",
"agent": {
"plan": { "model": "togetherai/zai-org/GLM-5.2" },
"general": { "model": "togetherai/openai/gpt-oss-120b" }
}
}I ended up putting GLM 5.2 back on planning even in cheap mode, since plan calls are rare enough that the cost difference is negligible, and I did not want to compromise on the one role where a mistake is expensive to unwind. That gives a clean ladder across the three profiles: GLM 5.2 for plan in Cheap, DeepSeek V4 Pro for plan in Normal (inherited straight from build), and Claude Opus 5 for plan in Max.
6. Switching profiles without editing files
opencode reads a base config from ~/.config/opencode/opencode.jsonc, and it also honours an OPENCODE_CONFIG environment variable that points at an additional file, merged on top of the base config for that run only. That is the mechanism that makes profile switching painless: keep the base config as your default, and put only the values that differ in a small override file per profile.
So the everyday config above lives at ~/.config/opencode/opencode.jsonc and needs no wrapper at all, since it is what opencode loads by default. Plain opencode already gives you “normal.”
The other two profiles live in their own files:
~/.config/opencode/opencode.max.jsonc~/.config/opencode/opencode.cheap.jsonc
Each one only needs to contain the keys that differ from the base. The provider definition, with its API key and base URL, is inherited automatically since config files merge rather than replace.
Then in ~/.zshrc, a couple of small shell helpers do the switching:
# opencode profiles: plain `opencode` = normal mode (~/.config/opencode/opencode.jsonc).
# opencode-max and opencode-cheap layer an override file on top for that invocation only.
alias opencode-normal="opencode"
opencode-max() {
OPENCODE_CONFIG="$HOME/.config/opencode/opencode.max.jsonc" opencode "$@"
}
opencode-cheap() {
OPENCODE_CONFIG="$HOME/.config/opencode/opencode.cheap.jsonc" opencode "$@"
}Using a shell function rather than export-ing the variable matters here: the env var is only set for the single command that function runs, so it never leaks into other opencode invocations in the same terminal session. opencode-normal does not need a function at all, since it is identical to typing opencode on its own.
After source ~/.zshrc, you have three commands: opencode (or opencode-normal) for daily driving, opencode-max for a session where you want the strongest possible plan, and opencode-cheap for routine or low stakes work.
7. Checking it actually worked
Do not trust a config file until you have seen the real model name come back from a live call. opencode has a debug command that prints the fully resolved config, which is worth checking after any change:
opencode debug configBut a resolved config can still be wrong in a way that only shows up at request time, so I always follow up with an actual run against the specific agent I changed:
opencode run --agent plan "reply with exactly: plan-ok"The output banner tells you which model actually served the request, right above the response:
> plan · anthropic/claude-opus-5
plan-okThat one line is the difference between “I think this is configured correctly” and “I watched the right model answer.” Worth doing for every role you touch, every time, since a typo’d model ID or a missed env var substitution fails in ways that look like everything is fine until you check.
8. Routing to a private model over Cloudflare
Together AI is still a metered API: every token has a price, even the cheap gpt-oss-20b ones. If you’ve already got the hardware, that is a cost you don’t need to pay for at least some of the work. I run DeepSeek V4 Flash across two DGX boxes at home, and expose it to opencode over a Cloudflare Tunnel so it looks like any other OpenAI compatible endpoint.
The tunnel side is standard cloudflared: point it at the local inference server (vLLM, SGLang, whatever you’re serving with) and give it a public hostname, something like deepseek.yourdomain.com. I sit that behind Cloudflare Access rather than relying on my inference server’s own auth, since Access authenticates at Cloudflare’s edge before the request ever reaches the tunnel. A service token gets you two headers to send with every request:
{
"provider": {
"privatedgx": {
"npm": "@ai-sdk/openai-compatible",
"name": "Private DGX",
"options": {
"baseURL": "https://deepseek.yourdomain.com/v1",
"apiKey": "{env:PRIVATE_DGX_KEY}",
"headers": {
"CF-Access-Client-Id": "{env:CF_ACCESS_CLIENT_ID}",
"CF-Access-Client-Secret": "{env:CF_ACCESS_CLIENT_SECRET}"
}
}
}
}
}apiKey still points at whatever bearer token your inference server itself checks, that part does not change just because Cloudflare is in front of it. The two CF-Access-* headers are the service token Access issues you, and they are what actually get you through the edge; without them the request never reaches your tunnel, regardless of whether the bearer key is correct. Same as the Together AI setup, run opencode models privatedgx after adding this to confirm opencode can see the model and the auth chain works end to end, and follow up with the opencode run --agent <name> "reply with exactly: ok" check from section 7 before you trust it in a real session.
Where this earns its keep is a new role, alongside build, plan, and general subagents: a dedicated worker subagent for the high volume, low stakes, repetitive end of the work, running a test suite and triaging the output, mechanical find and replace across a batch of files, grepping through logs, anything you would otherwise burn general purpose subagent calls on. I kept it separate from agent.general on purpose. General still needs to be decent at judgment calls, since it is opencode’s default for any subagent you have not explicitly named. Worker does not; it exists for tasks where the ceiling on capability barely matters and the only thing that matters is throughput, and throughput is exactly what free, already paid for GPUs are good at:
{
"agent": {
"worker": {
"model": "privatedgx/deepseek-ai/DeepSeek-V4-Flash-0731",
"mode": "subagent"
}
}
}You invoke it the same way you would any named subagent, either by asking the build agent to delegate to it by name, or directly:
opencode run --agent worker "run the test suite and summarise any failures"Two caveats worth having up front. First, this is two physical DGX boxes, not an elastically scaling cloud fleet, so if the build agent fans out several worker subagents in parallel on a big session, you can genuinely saturate your own hardware in a way you never would hitting Together’s API. Second, the Cloudflare hop adds latency a LAN call would not, small compared to inference time on anything past a few hundred tokens, but noticeable on tiny requests. Neither is a reason not to do this, both are reasons to keep an eye on it if worker starts feeling slow.
I do not fold privatedgx into the normal, max, and cheap profiles above, since unlike the Together models its cost is fixed and sunk rather than variable. It runs as the same worker entry in all three, regardless of which profile is active for build and plan.
9. Where this leaves you
Three commands, three cost profiles, and the expensive tokens only get spent on the roles where a wrong answer is expensive to unwind. Together’s catalog moves fast, so periodically re-run opencode models togetherai and check whether a newer or cheaper model has landed in a role you care about. The config files are small enough that swapping one model ID in one profile takes a minute.
The exact model IDs above are a snapshot as of writing. Together retires and renames models, and opencode’s list of supported providers grows, so the specific names will drift. The pattern underneath, tiering by call frequency rather than picking one model for everything, is the part worth keeping.