Skip to main content

opencode-models

List Opencode Zen / Go models by agentic coding score and score per dollar.

Tired of trying to correlate the latest OpenCode model listings with available benchmarks and costs? This utility uses public/scraped benchmark reports and the opencode API to show the current best and cheapest available models.

TLDR; quick steps

uvx opencode-models
alias ocm=opencode-models

ocm auth  # register an openapi token to always fetch the current 'live' models
ocm fetch # fetch benchmarks and model info from supported sources
ocm list  # List non-expensive, non-trivial models, sorted by score/$
ocm next  # 6 next-best recommended models to use (tell your agent to use this)

Install

Run it once with uvx, or install the opencode-models binary to your $PATH:

uvx opencode-models              # no install, just run it
uv tool install opencode-models  # install the binary

The name is longer than it wants to be for daily typing; a shell alias fixes that in one line: alias ocm=opencode-models.

First run

ocm auth   # save a Console token so the views are live (recommended)
ocm fetch  # download benchmark data and model info (~1 minute first time)

auth stores an OpenCode Console service-account key at ~/.config/opencode-models/api_key (owner-only, never echoed); the OPENCODE_API_KEY environment variable takes precedence if you set one. fetch is the only command that downloads data — benchmark numbers, tier prices, official boards — into your data directory: $OPENCODE_MODELS_DATA if set, else ~/.local/share/opencode-models.

Working from a checkout

just setup       # uv sync + enable git hooks (pre-commit lint/audit,
                 # pre-push guard for the local-data branch)
just test        # unittest suite
just lint        # ruff lint, format check, complexity gate
just check       # tests + entry point + wheel build
just run <cmd>   # the console against ./data, e.g. just run next

just install additionally seeds the reference dataset into your user data dir (missing files only — it never overwrites fetched results, while tracked curation deploys as shipped). The tool is stdlib-only: Python ≥ 3.10, no runtime dependencies; ruff comes from the locked dev group.

Project pointers: SECURITY.md (what touches the network, scraper etiquette, how to report a problem), LIMITS.md (what the plans actually meter), CONTRIBUTING.md (workflow, releasing).

Usage

Three commands cover daily use; four more exist for auditing and maintenance:

command what it shows
list the best-value table of what the plans offer today — free, included-usage and pay-as-you-go bands ranked by score per dollar
next the next models to reach for: free and paid picks interleaved, Zen/Go alternating (default 6; tell your agent to use these)
scores every offered model, one column per benchmark — the raw number beside where it lands
models the API endpoint and display name of every model each plan currently offers
overrides benchmark numbers more than one source reports, and which value the tool keeps
fetch refresh everything: benchmarks, tier prices, official boards, availability
auth store the Console token for live views (owner-only file)

Live views and the token

Every list and next run looks for a Console token first — the OPENCODE_API_KEY environment variable, then the file auth wrote. With a token the view is live: it queries the Console config API on each run, shows only the models the plans offer right now, and records the result so later offline runs stay current. Without a token the first line warns that availability may be stale and the last fetch snapshots decide (those are refreshed by any keyed fetch or models run).

--all-models (alias --all) bypasses the availability filter entirely — what you want when auditing rather than choosing.

Common options

  • --source zen|go|all — one provider or the full-catalog view (default: both providers merged)
  • --all-models / --all — ignore the availability filter (list, next, scores)
  • --tables all — add the worst-score and highest-cost tables to list
  • --benchmark NAME — rank list on one benchmark instead of the combined score
  • --count N — how many picks next recommends (default 6)
  • --price input|output — cost on a single rate instead of the 20/60/20 input-cached-output mix
  • --data DIR — point at a different data directory (checkout: --data data)
ocm list --tables all               # also the culled worst/pricey tables
ocm list --source zen               # one provider only
ocm next --count 8 --source go      # eight picks, Go plan only
ocm scores                          # one column per tracked benchmark

How it works

The rest of this document is the detailed semantics: what a score means, how the views differ, and where every number comes from.

Which benchmarks are scored

Five benchmarks carry the scores, and they are named one by one in scoring._TRACKED rather than matched by prefix:

benchmark tier models all models
Terminal-Bench 2.1 37 50
SWE-Bench Pro 34 57
Terminal-Bench 4.0 27 28
SWE-Bench Verified 18 77
DeepSWE 1.1 25 29

They are the most relevant and the most broadly available at the same time: Terminal-Bench is the closest thing to "can this model finish a task on a real machine", and SWE-bench is the closest thing to "does it fix real bugs", so together they cover the work this tool ranks on; and between them they are measured on enough models to compare anyone to anyone. The two SWE-bench subsets are both kept deliberately — Pro is what the tier broadly carries (34 models against Verified's 18), and Verified is the canonical metric and by far the broadest number in the catalog (77).

DeepSWE 1.1 is the exception, and it earns its place by pairing rather than by relevance. It is the narrowest thing here — 29 models out of 476 — and the weakest on provenance: all 29 numbers are vendor self-reports, and no board runs it, so it can never set the ends of its own scale. What it does is supply the second axis for models that have nothing else: nine of the 25 offered models sitting on a single benchmark carry it, and admitting it takes the scored field from 36 models to 45. opencode-models scores names that group explicitly — "N on one benchmark" — so a thin row is never presented as a ranked one.

Its signal is largely redundant with the axis next to it. Rank correlation with Terminal-Bench 2.1, over the models carrying both, is 0.87: it mostly re-ranks what TB 2.1 already says. A median over five benchmarks therefore leans on TB 2.1 twice, which is worth knowing before reading a small score gap as a real difference. The bare DeepSWE name stays out — it shares no model with DeepSWE 1.1 in the catalog, so it is an older revision carrying four models.

Everything else the catalog mentions is context rather than signal, and the list is short enough to be worth spelling out. The catalog carries 212 benchmark names, of which 137 are held by one or two models — an evaluation nobody else ran. Those cannot discriminate, so they are excluded twice over: no spread means no scale, and a benchmark four models carry still hands its worst a hard 0 that drags the median down. A model resolving 82 on SWE-bench Verified was landing on 0 overall because of a 17.7 on SEC-Bench Pro.

The rest are excluded on the same ground — each would widen the scoring equation without adding a model that could not otherwise be scored:

  • Terminal-Bench 3.0 (2 models) and -Science are separate task sets rather than runs of 2.1, and share only a prefix.
  • Terminal-Bench 2.0 is the same benchmark as 2.1 with an easier, older task set — and a public task set gets trained against. A vendor can publish a 2.0 number without the model being able to do the work, which is the overfitting the rest of this list exists to exclude. Leaving it out costs no coverage: the five tier models whose only Terminal-Bench run is a 2.0 all carry SWE-bench, so none of them loses a score.
  • SWE-Bench Lite and Multimodal reach no tier model at all (9 models each, none of them one the console prices).
  • SWE-Bench Multilingual is carried by 29 models but never alone: every model that has it also has Verified or Pro, so it narrows nothing.
  • Terminal-Bench Hard is a distinct benchmark despite the name.
  • LMArena Elo measures preference votes on chat answers, not task completion, and no model in the catalog carries it.

Adding one is a deliberate change to what a score means, so it wants a reason: benchmark coverage in the live data is the test.

How a model's score is computed

Raw benchmark numbers are not comparable: in the live catalog Terminal-Bench 2.1 runs 24.58–89.9, Terminal-Bench 4.0 runs 1.5–64.85 and SWE-bench Verified 0.4–96. So each benchmark gets its own 0–100 scale — the best model in the catalog is 100, the lowest is 0, and distances are proportionate and linear — and a model's score is the median of the scales it has.

The median, not the mean: coverage here is wildly uneven, and one bad result must not bury a model that is strong on everything else. opencode-models scores gives each of those benchmarks its own column and shows the raw number beside where it lands, so the arithmetic is checkable:

476 models · 5 benchmarks scored on · 45 with scores · 21 on one benchmark · 12 without any

  MODEL                     SCORE  TB 2.1    TB 4.0     SWE Vrfd  SWE Pro   DeepSWE
  Muse Spark 1.3 Contributor     99  88.8→98                                      75.4→100
  Claude Fable 5                 99  88→97     44.55→62   95→100        80.3→100

Two more rules decide what counts as evidence.

A model needs two benchmarks to have a score. The median of one number is that number, so a model measured on a single benchmark it happens to lead reads as a top model on the strength of one result — in the live catalog 26 of 63 scored models were in that position, and 6 of the top 9 rows were, including the one leading the whole catalog on a lone Terminal-Bench 4.0 run. Those models are unranked, not ranked last, and the counts line above the table says how many rest on one benchmark.

The ends of a scale are set by numbers someone else can check — an official board or an independent evaluator, not a vendor's own figure. Min-max is only as good as its extremes: counting every number, one uncited 1.5 from a launch post was the floor of the Terminal-Bench 4.0 scale, which stretched the range and compressed every model's derived score toward the middle. That is the rule scoring.scales applies, with one guard. Where the checkable numbers are a small minority of a benchmark's field, the ends stand aside: only 8 of 50 Terminal-Bench 2.1 numbers come from a board, and scaling that axis to those eight gave it bounds of 64.3–73.4, which clamped nearly every model to 0 or 100 and stopped the primary axis discriminating at all. A scale drawn from a few points is a threshold, not a scale.

So: a benchmark only one or two models carry has no spread, so it cannot discriminate and is left out — 137 of 212 benchmarks are in that position. And the score answers "how does this model typically do on what it was measured on", so a model evaluated on a handful of benchmarks it excels at still scores near 100.

The console prints what it measured and points here for the arithmetic; scoring.py carries the reasoning next to the functions that do it. The column headers are short (TB 2.1, SWE Vrfd) and the legend under the table spells out which benchmark each one is.

Each row shows the model's calculated score, its cost, and which plan lists it — the per-axis raw numbers live in scores, not here. --benchmark X switches to the single-score layout: that benchmark's raw score and its tag.

Each line is prefixed with a rank marker read off the score itself — ++ at 90 and above, + at 75, o at 60, - at 50, -- below that, · where a row has no score to rank (unranked, not last). Absolute bands mean a strong field can share the top and a weak one is never flattered by it.

Ranking is best score per dollar first. The score is already comparable across axes — each benchmark reads on its own 0–100 scale and the median combines them — so value order is the combined score divided by the price, ties toward the higher score. Rows no benchmark reaches cannot be valued: free ones close the listing, paid ones follow, and rows nothing reaches drop out (--benchmark X ranks on one benchmark instead). Terminal-Bench 4.0 is a harder task set — its leaders reach ~58 where 2.1's reach ~87 — so a 4.0 score never competes directly with 2.x numbers, and --benchmark X tags which benchmark a row was ranked on. The axes are not comparable, so they only indicate relative performance.

Pricing comes from the tier docs rather than the catalog, which carries one Zen-style price per model: documented rates replace it, and every priced variant (peak/off-peak, token window) gets its own line. Free lines — -free tier slugs and zero-priced models — are ranked at their model's nominal free-tier rate (pricing.FREE_TIER_NOMINAL_RATES, recomputed on every fetch from the usage export; see LIMITS.md), so "free" sorts and divides like any other cost instead of going infinite, and the value column prints that division — the cheapest free models read highest. They are shown with an actual 0 price: the nominal price never reaches the cost column.

The list views

list covers the Console providers (zen = curated, go = open) and prints the best value table. --tables all also prints the worst score (bottom 30%) and highest cost (top 30%) tables. Either way the cull happens: those two groups of rows are taken out of best value, so the default view is the models worth looking at and nothing else. The culls are disjoint and cost claims first, so a model that is both priciest and lowest-scoring is reported once, under cost. Cost decides who lands in the highest-cost table and score decides who lands in the worst-score table, but both culls are shown worst-scoring first.

By default both providers are merged into one view. Rows that agree on model, score and cost are collapsed to a single line before ranking and culling — they are one offer, not two — with the providers enumerated in the PROV column immediately before the model name (go,zen, zen, go, or - for catalog models in neither tier; those only appear with --source all). When only some of the named providers fund a line from a monthly allowance, the more generous band wins: the line is available that way from at least one of them. Rates that differ (off-peak, token window, a limit one provider has and the other has not) keep their own line each. --source zen / --source go narrows the view to a single provider.

Best value splits into bands — free (use first), included usage (lines the tier grants a monthly allowance for), pay-as-you-go — and is ranked by score per dollar inside each band. A line's cost is 20% input + 60% cached read + 20% output; --price input|output picks a single rate instead. The numbers are all rounded integers: the score is the model's calculated 0–100 number, score per dollar explains the best-value order — free lines divide by their nominal rate, so the cheapest free models read highest — and the cost column counts cents (¢/1M = round(price·100)), so a $0.0612 line reads as 6, not 0.0612. Free lines still show a 0 cost: the nominal never reaches the price column, only the value one. Columns are measured from the lines actually being shown (header included), so nothing shifts when a price or a provider list outgrows the rest.

The next sequence

next answers the narrower question "what should I reach for next?". It draws from the same pipeline list uses, so it can never disagree with the ranking it came from. Three rules shape the sequence:

  • Eligibility is strength, not the ++ marker. Picks must be within 75% of the strongest score in the catalog. The marker is not used to gate them: it is a position in an ordering that divides by price, so it measures value. In the live data MiMo-V2.6-Pro scores 89.9 — the best model in the catalog — and is graded o, while a free model scoring 24.58 is graded ++. Gating on the marker recommends the weak model and misses the strong one.
  • Alternation is forced, on two axes. Slots alternate between the free and paid bands — even slots take the best remaining free pick, odd slots the best remaining paid one — so a capped free tier still leaves half the sequence usable, and a fresh one still leads with what costs nothing. Within each slot the pick alternates between Zen and Go wherever both can supply one, so following the sequence spreads across the two plans instead of draining one allowance before touching the other. Either alternation gives way when it must: a dry band yields its slot to the other band, a plan with nothing eligible left is passed over, and the strongest free pick always leads.
  • One model, one slot. Free on Zen and paid on Go are two lines for one model — the [free] suffix is the only difference in the name — so a model is never recommended twice, and price variants of one model do not take two slots either.

The view keeps the rules out of the output: it prints one headline, the sequence, and a pointer to LIMITS.md for what allowances cost. It does not consult how much of each allowance is left.

Where the data comes from

data/ is split by who writes it — and the scraped layers are fetched, not committed. They are third-party records, they go stale, and they are large; opencode-models fetch populates them into your data directory, and a private local-data branch keeps a committed copy for offline work (see below).

directory contents written by in the repo?
opencode/ upstream records, one JSON per model, verbatim from https://opencode.ai/data/<lab>/<model>.json fetch only no — .gitignored
aiarena/ arena.ai (LMArena) Elo cache, {slug: elo} fetch no — .gitignored
tier_prices/ snapshots of the Console tier pricing tables (see below for what counts as offered) fetch no — .gitignored
available/ the live model set per tier from the Console config API — the plans as the API serves them right now keyed fetch / models no — .gitignored
boards/ official benchmark boards, fetched from whoever runs them (see below) fetch no — .gitignored
usage/ free-tier nominal rates derived from the workspace usage export (per-model traffic only, no user columns) keyed fetch (needs OPENCODE_API_KEY) no — .gitignored
overrides/ hand-pinned benchmark values (see data/overrides/README.md) nobody — overrides reports disagreements yes (the directory, no data)

fetch downloads only the models the plans offer (use --all for the full catalog). Records already on disk are never deleted, so a dropped model keeps its place in the scales rather than moving every score.

Which models the plans actually offer

Providers unlist models and move them between plans, so "which models can I use" is a fact about today, not a property of the codebase. It used to come from committed text files — opencode_models/sources/zen.txt and go.txt — which are hand-maintained and drifted in both directions. In the live data 13 Go models were still being listed, scored and recommended after the provider's own documentation had stopped listing them, among them hy3-preview and glm-5, two of the models an active backfill was chasing numbers for.

fetch already snapshots each tier's documentation for prices, but the docs go stale — providers unlist models the pages still name, and list new ones the pages do not. So availability comes from the Console config API (/api/v2/config, the workspace's live model set), snapshotted to data/available/ on every keyed run (opencode_models/live.py). The docs snapshot stays the offline fallback, and opencode-models models prints whichever record is in force as one sorted table: the endpoint (as next shows it, preferring Go where both plans list the model) beside the display name (as list shows it, — where the catalog has none).

  • list and next show only what a plan lists — live with a token (checked every run), the last snapshots offline. next therefore never recommends a model that has been dropped.
  • --all-models (alias --all, also on next) lists the whole catalog instead, bypassing every availability filter — which is what you want when auditing rather than choosing.
  • The view says what it covers. list notes it shows only what the plans offer right now and points at --all-models for the rest, without naming names — which models a plan dropped is audit material, not a headline.
  • A tier with no snapshot is unknown, not empty. Offline and before the first fetch, nothing is filtered; restricting the console to an empty set because nobody fetched would hide everything.
  • The scales do not move. A model you cannot buy still marks where the field is, so a score never changes because a plan changed what it sells.

Official boards, fetched from their owners

fetch also pulls the boards themselves, so the axes are anchored on the numbers of whoever runs each benchmark rather than on one registry's transcription (which is ~93% vendor self-report):

board where it comes from what it carries
Terminal-Bench 4.0 the official board page's own data payload (tbench.ai; the page renders in a browser, but streams the leaderboard it renders from) accuracy ± CI95, the agent, the reasoning effort and the date per board row
SWE-bench (Verified, Multilingual, Multimodal, Lite) swebench.com's embedded leaderboard resolved per checked submission, with the agent that ran it

A board row replaces a model's score only when its source is strictly better tiered than what the model already carries — an official board beats a vendor's own number (MiniMax-M2's self-reported 69.4 on SWE-bench Verified yields to the board's 61.0), but it never displaces an independent run, and every replacement is printed by overrides. Sources that only render numbers in a browser with no embedded data (Artificial Analysis, Vals AI, Scale's SWE-Atlas, DeepSWE) are not fetched; their numbers arrive via the registry or a curated override. A JS-rendered page is not automatically a dead end, though — tbench.ai looked like one and is not, because a Next.js page streams the data it renders from. aider's polyglot board is static HTML but was dropped as stale and redundant: its newest entry is GPT-5-era (Aug 2025), and diff-editing is already covered by SWE-bench and Terminal-Bench.

If you want the dataset committed rather than fetched, the local-data branch keeps a copy — it is intentionally never pushed.

Curated overrides

The upstream layer is never edited: curated scores are merged at load time, keyed by (benchmark name, version), so an overlay can add a benchmark the registry lacks or pin a value it also carries. A pinned value applies only when its source is at least as good as upstream's — sources are tiered: independent evaluators that run every model on one harness (Artificial Analysis) beat official benchmark boards (tbench.ai), which beat vendor model cards and launch posts. Everything a disagreement touches is printed by overrides; to accept the catalog value, delete the entry. Rows whose model carries an overlay are marked * in list. See data/overrides/README.md for the entry format and rules.

Parsing is tolerant: models without pricing or without the selected benchmark are skipped rather than failing the view.

Data due diligence

What this repo publishes: hand-pinned benchmark numbers (data/overrides/, each carrying its source URL and provenance note), plus code and docs. Everything the tool reads in bulk — the model catalog, tier snapshots, official boards, arena cache — is fetched into gitignored directories and never committed. No workspace or user data is ever written to the repo: the usage export is aggregated in memory and only per-model numeric totals reach disk.

Why we think republishing those numbers is fair: benchmark scores are facts, and every curated value cites the page, chart, or board it was read from, with the date and method, so anyone can check or contest it. What we have not done is review every source site's Terms of Service, so if any number here violates a site's terms as you read them, that is unintentional — please open a GitHub issue, or a private security advisory if the matter is sensitive (see SECURITY.md), and we will correct or remove it.

Caveats

  • Prices are list prices from one catalog (OpenCode's); they can lag provider price changes. Tier pricing is snapshotted offline (data/tier_prices/), so a stale snapshot is stale prices until the next fetch.
  • Benchmarks are heterogeneous (metric, harness, conditions differ per source). list orders Terminal-Bench rows and SWE-Bench rows separately rather than mixing their scores; compare within one benchmark.
  • Coverage depends on which models the opencode catalog carries: a model absent from the catalog is invisible to every view.

Limitations

What the tool deliberately does not do, and why.

Cost per request is not a ranking axis. The same model served to two workspaces burns a different number of tokens per request — agent traffic with full context re-sends versus chat-sized prompts differ by orders of magnitude — so a model's $/request is a property of how you use it, not of the model. Folding a per-request cost into score-per-dollar would rank models on one workspace's habits: the same table would order differently for every reader. The metric is not universally available either — no catalog, board, or Console endpoint publishes typical per-request token volumes (the docs' estimated-request tables imply one, but disagree with measured traffic by 10–100× on heavy agent use) — so there is nothing stable to divide by. list and next rank on list price and on the nominal free-tier rate instead: comparable across workspaces, even where they are less precise for any single one.

Allowances and per-request spend are not shown. For the same reason, there is no allowance column and no $/request column: a Go plan's remaining dollars depend on metered usage the tool would have to snapshot per workspace, and free tiers publish no cap at all. What the plans grant is documented once in LIMITS.md; what you have used is in the Console, not here.

Metadata

Release files for opencode-models 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for opencode-models 0.1.0
File Size Uploaded Provenance
opencode_models-0.1.0.tar.gz 170.8 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for opencode-models 0.1.0
File Interpreter ABI Platform
opencode_models-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 249.5 kB

Release files / opencode_models-0.1.0.tar.gz

Download URL opencode_models-0.1.0.tar.gz
Size 170.8 kB
Tags Source
SHA-256 checksum
How to use checksums
3f48d4555ee944aee0f46c19b459a6e15fda04f123b03636a6e335b92f8698c8
BLAKE2b-256 checksum
How to use checksums
c2c83e0f905c4bf0d873d24d458efb78d9a8406e89697ea3eaf20e796049765b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / opencode_models-0.1.0-py3-none-any.whl

Download URL opencode_models-0.1.0-py3-none-any.whl
Size 78.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4e55f4726c0dc8685f328412471782015371644501f7c5a401aeb599dacb90d3
BLAKE2b-256 checksum
How to use checksums
a549fa62885d165e016b43edf161cc7501f9b6d8b9cc4a5a7a46fbf989d9e277
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Security for opencode-models 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for opencode_models-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page