The picker needs a list, and the three backends disagree about whether they have one. Claude answers `query.supportedModels()` and OpenCode answers `client.config.providers()`, both live and both scoped to what you are actually signed in to. Codex can enumerate nothing, at any layer. So `listModels` probes the two that can and falls back to a seed for the one that cannot, and nothing it does throws: a picker that cannot list models is a degraded menu, and one that takes the session down with it is a bug. Every failure comes back as a group carrying a `note` that says which — "Not signed in" and "Probe timed out" are different problems, and only one of them is the user's to fix. `packages/protocol/src/models.ts` is the seed, generated from models.dev by `scripts/gen-models.mjs` and reached through its own `./models` export subpath so the browser can take the list as a value without pulling zod along. Judgement lives in `scripts/models.curation.json` rather than in the output — the mechanical filter admits things like `gpt-realtime`, which is not a coding model — so the generated module stays purely derived and the taste is what gets reviewed. On the daemon, `resolveTarget` settles which backend and which model a turn runs on: what the turn asked for, else the default resolved for that backend, else the cross-harness one. Per backend rather than one string, because the picker can change harness mid-session and a single default would follow it and hand Codex an id only Claude answers to. `modelRefusal` is the other half. OpenCode drops a model id that does not name its provider, which makes asking for one indistinguishable from asking for nothing — the turn runs on the server's default while the composer goes on showing what was picked. It reads the turn's own model and deliberately not the resolved one: `--opencode-model` is already a hard error at parse time, and `--model` reaches all three backends where a bare id is right for two of them, so that one warns at launch and runs. What is left is the picker's free-text box, which had no guard at either end.
49 lines
2.1 KiB
JSON
49 lines
2.1 KiB
JSON
{
|
|
"$comment": [
|
|
"Hand-maintained curation for scripts/gen-models.mjs. The generator filters",
|
|
"models.dev mechanically; everything that needs judgement lives here, so the",
|
|
"generated file stays purely derived and this file is what gets reviewed.",
|
|
"",
|
|
"Run `make models:refresh` after editing."
|
|
],
|
|
|
|
"since": "2025-08-01",
|
|
"$since": "Release-date floor. Cuts o1/o3/o4, gpt-oss and the Claude 3.x/4.0 era without naming them one by one.",
|
|
|
|
"limit": 10,
|
|
"$limit": "Rows kept per harness from models.dev, newest first — `extra` below is pinned on top of these rather than counted against them, so claude ships 14. A picker is a menu, not a scroll: `popover-host.ts` caps and scrolls past roughly this many.",
|
|
|
|
"deny": [
|
|
"-chat-latest$",
|
|
"-instant$",
|
|
"^openai/gpt-realtime-",
|
|
"^openai/gpt-oss-",
|
|
"-deep-research$",
|
|
"-\\d{8}$"
|
|
],
|
|
"$deny": [
|
|
"Regexes matched against the models.dev id. In order:",
|
|
"chat-latest / instant — chat tiers, not agentic; they have no tool loop worth offering here.",
|
|
"gpt-realtime — voice; passes the tool_call+reasoning filter but is not a coding model.",
|
|
"gpt-oss — open weights, not served on the OpenAI API these harnesses call.",
|
|
"deep-research — a hosted research product, not a coding model.",
|
|
"-YYYYMMDD — models.dev lists both `claude-opus-4-5` and `claude-opus-4-5-20251101`.",
|
|
" The undated id is the better menu row and pins to the same weights."
|
|
],
|
|
|
|
"extra": {
|
|
"claude": [
|
|
{ "id": "opus", "label": "Opus", "hint": "latest" },
|
|
{ "id": "sonnet", "label": "Sonnet", "hint": "latest" },
|
|
{ "id": "haiku", "label": "Haiku", "hint": "latest" },
|
|
{ "id": "fable", "label": "Fable", "hint": "latest" }
|
|
]
|
|
},
|
|
"$extra": [
|
|
"Rows models.dev cannot know about, pinned above the derived ones.",
|
|
"`claude --model` accepts these aliases and resolves each to the current",
|
|
"model in its family, so they stay correct between refreshes in a way a",
|
|
"pinned id cannot. `codex -m` and opencode take no aliases, so they get none."
|
|
]
|
|
}
|