fix(web): list the providers the server actually has, not a hardcoded copy - #2203
Conversation
… copy The settings picker rendered from a `PROVIDERS` array compiled into the page, while `/api/providers` served the real catalog. The two had already drifted: `codex_cli` and `openrouter` are in the server catalog and were never added here, so neither could be selected from this page even though the backend would have resolved both. The array stays as a cold-load fallback for an API that has not answered yet, but the list now comes from the catalog, and a provider's `default_model` backfills the model placeholder. A provider added on the Python side shows up here with no change to this file, which is what stops the next one drifting.
…s-from-api # Conflicts: # packages/web/src/components/settings/provider-section.tsx
Reading the catalog for the picker left MODEL_PLACEHOLDERS ahead of it for the model hint, so a stale local entry still beat the default the server would actually use -- the same drift the catalog read exists to close, one level down. The catalog is authoritative; the local table is the cold-load stand-in behind it. Covered by tests that fail when the fix is reverted and when the precedence is swapped back.
…s it Rendering the server catalog verbatim dropped any provider it does not advertise. `mock` is flag-only and is genuinely absent from it, so a user with mock saved got a Select whose value matched no item -- a blank trigger with nothing to recover with. The selected value is now always among the options. Also gives codex_cli and openrouter the env-var and install-hint entries they never had, since this is the change that first makes them selectable, and names both Select triggers so they have an accessible name at all.
The previous union only rescued the *selected* provider, so a user on gemini silently lost the ability to switch to `mock` at all -- it is registerable and keyless but deliberately absent from PROVIDER_CATALOG, and the trigger still looked correct, so nothing surfaced the missing option. The flag-only set is unioned in alongside the selection.
|
Thanks for this, and for writing up the three bugs your own tests caught. The third one is the interesting one, and I am glad you chased it rather than stopping at the blank trigger. Rebasing onto #2155 was the right instinct. That PR moved the active-provider read onto the typed client, and this renders the picker from the same response with no extra request, so the two really are halves of one change. I confirmed the gap you describe: Unioning the flag-only set in alongside the selection is the right resolution for |
What this is
Not a fix for a reported symptom. It came out of wiring a new provider: it reached
PROVIDER_CATALOG,/api/providersreturned it, and the settings page still could not show it. The page renders its picker from aPROVIDERSarray compiled into the bundle, so every new provider needs a second manual edit here — and two have been missed.codex_cliandopenrouterare in the server catalog and are not in that array, so neither can be picked from this page today even though the backend resolves both.The change
Rebased onto #2155, which is what makes this small: that PR already moved the active-provider read onto the typed
getProviders()client, and the same response already carries the catalog. So this adds no request and no new data-fetching pattern — it destructuresprovidersalongsideactiveand renders the picker from it.Three bugs the tests found, all mine
I wrote these tests expecting to confirm the change. Each time they showed it was wrong instead. Stating them because the third is the interesting one.
1. A stale local default beat the server's real one.
MODEL_PLACEHOLDERSis a hardcoded table of each provider's default model and my first version left it ahead of the catalog — exactly the drift this PR exists to close, one level down. The catalog wins now; the local table is the cold-load stand-in behind it.2. The catalog is not a superset of what can be selected.
mockis registerable and keyless (_BUILTIN_PROVIDERS,KEYLESS_PROVIDERS) but is deliberately absent fromPROVIDER_CATALOG. Rendering the catalog verbatim dropped it, so a user withmocksaved got a<Select value="mock">with no matching item — a blank trigger with nothing to recover with. Confirmed in jsdom before fixing: the provider trigger rendered""while the embedder beside it rendered"mock".3. My first fix for #2 was still wrong. I unioned in the selected provider, which repairs the blank trigger but not the real loss: a user sitting on
geminicould no longer switch tomockat all. Nothing surfaces that — the trigger still readsgeminiand looks entirely correct. The flag-only set is now unioned in alongside the selection:If you would rather
mocksimply joinPROVIDER_CATALOGserver-side, that is arguably more consistent with this PR's thesis and I am happy to switch — I kept it client-side because the omission looks deliberate and the server list feeds more than this page.Also
codex_cliandopenrouterhad noPROVIDER_ENV_VARSentry, so the two providers this PR newly makes selectable would have rendered with no key guidance and no install hint. Both added. NeitherSelecthad an accessible name; both triggers now have one.What it does not close
ProviderEntryis{id, name, models?, default_model?, configured?}— noenv_keys— soPROVIDER_ENV_VARSstays local and can still drift. Closing that means addingenv_keysto the payload, which is a server change and not in this diff.Merge order with #2194
They touch the same constants block: #2194 appends
ompto thePROVIDERSarray this PR renames toFALLBACK_PROVIDERS, so whichever lands second conflicts there. Resolving toward #2194's side would silently revert the catalog read. Each PR is correct standalone — #2194 keeps its array entry so it works on today'smain— and if this lands first I'll rebase #2194 to drop that entry, sinceompthen arrives through the catalog for free. Flagging it so the conflict isn't resolved the other way by reflex.Verification
7 tests in #2155's
provider-section.test.tsx, reusing itsgetProvidersmock. I mutation-checked all five new ones individually rather than trusting green — each fails for its own reason:default_modelfalls back to the local table?? MODEL_PLACEHOLDERSterm is droppedFLAG_ONLY_PROVIDERSis dropped from the unionFull
packages/websuite: 20 failures before and after, identical toorigin/mainmeasured in a clean worktree; my 5 additions all pass.tsc --noEmitclean, no new lint warnings.This is my least urgent open PR — entirely reasonable to park it or close it if you would rather the catalog stay duplicated in the bundle.