feat(models): browse and price every OpenRouter model from APPE's catalogue #4
Reference in New Issue
Block a user
Delete Branch "or-models"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
The pi.dev harness could only be pointed at two hand-written qwen entries, and
every pi session was costed at one flat qwen rate whatever it actually ran on.
OpenRouter serves several hundred models, and picking between them is a price
decision — so the composer now browses the whole catalogue with the three rates
that bill an agent run on every row, and the backend prices a session at the
model's real rates.
None of those numbers are ours: they come from APPE
(
appe.dev.gabvdl.xyz/api/models/openrouter.json, a daily models.dev sync), so are-priced model follows automatically instead of drifting against a hard-coded
table. Companion PR: GabrielVidal1/appe#1, which publishes that endpoint.
Key changes
backend/openrouter.py— new: fetches APPE's OpenRouter slice, shapes it into{id, name, vendor, inputCost, outputCost, cacheCost, contextWindow, modelSize, tags, tier, license, speedTps}, cheapest first. In-process cache(6h) + a disk copy under
/dataso a restart with no network still listsmodels; warmed in a background thread at startup.
GET /api/models/openrouter(main.py,schemas.py) — the catalogue, keptout of
/api/modelson purpose: that is the composer's short chip list, thisis a few hundred rows only the browser renders.
backend/conversations.py—rates_for(model)replacesprice_foras thepricing primitive and returns (input, output, cache-read). A vendor-
prefixed id is priced from the catalogue; unknown ids keep the old estimate;
Claude models keep the built-in table and the flat 10% cache-read cut. pi's own
costUSDstill wins over any estimate, unchanged.frontend/.../composer/components/OpenRouterBrowser.tsx— new modal: aFuzzyListover the catalogue, each row = name + id, a parameter-size chip, acontext chip and IN / OUT / CACHE $/Mtok, with capability filters (tools,
reasoning, vision, open weights, free).
frontend/src/lib/models.tsx— the pi harness's model select gainsBrowse OpenRouter…; a model picked there gets its own option + chip;isOpenRouterId()(a slash — Claude ids never have one) routes any such id tothe pi harness, so an arbitrary pick survives a resume.
frontend/src/lib/costAnalysis.ts— a vendor-prefixed id no longer splits itscost at Opus rates in the dashboards.
CLAUDE.md+runner/README.mdupdated.
Key decisions
prices is exactly the thing that goes quietly wrong; APPE already syncs
models.dev daily for this.
hold on OpenRouter, so
rates_forcarries a third rate. A model with no promptcache reports its cache-read rate as the input rate — what those tokens would
actually cost — and the row greys it to say so.
through it, so it reads an in-process index (falling back to the disk copy
once); a cold, network-less start prices at the estimate rather than stalling a
parse on HTTP.
/api/models. Every composer paintsthe chip list; only the modal needs 340 rows, and it fetches them on first open.
(slash ⇒ OpenRouter, bare ⇒ local EVOX2), so the browser can offer the whole
catalogue without a second list to keep in step.
Changelog
harness can run, with its input / output / cache price per million tokens,
parameter size and context window, plus capability filters.
(cache reads included) instead of a single flat qwen estimate.
harness on resume, and no longer has its cost split at Opus rates in the
dashboards.
Test notes
npx tsc --noEmitclean;npm run buildclean.openrouter.json: 339 models load,rates_forreturnsmeta-llama/llama-3.1-8b-instruct→ (0.05, 0.08, 0.025),openai/gpt-oss-20b→ (0.03, 0.13, 0.03),claude-opus-4-8→ (5, 25, 0.5)unchanged, an unknown vendor id → the qwen fallback, bare
qwen3.6-35b-a3b→free. Same usage block costs $0.0121 vs $0.0135 vs $0.46 respectively.
Browse OpenRouter…→ the list, filters and fuzzy search.OPENROUTER_API_KEYin.env.claudeis currently rejected by OpenRouter (401 User not found), sothe two runs attempted (
meta-llama/llama-3.1-8b-instruct,openai/gpt-oss-20b) came back with an empty assistant message and zero usage.Nothing in this change is involved in that path — the runner already routed
vendor-prefixed ids to OpenRouter — but the end-to-end run should be repeated
once a valid key is in place.
Screenshots