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.