* feat(settings): Universal API Config panel with server-side model fetch (#90)
Consolidates provider configuration into one "Universal API Config" panel in
Settings — provider dropdown (OpenAI-compatible / Anthropic), base URL, API key,
model with a live "Fetch models" button, and max context tokens — above the
existing per-provider key sections.
Backward-compatible by design: the panel reads and writes the SAME state.settings
keys the individual sections already use (${provider}Key, selectedModel), so
nothing migrates and nothing is lost; the individual sections keep working and
stay in sync.
- src/config/provider-models.ts (new): listProviderModels queries a provider's
own model endpoint (OpenAI-compatible Bearer /models, Anthropic x-api-key
/v1/models, OpenRouter public list); resolveModels wraps it fail-open.
- POST /api/models (new route): server-side so the browser dodges provider CORS.
Binds key+baseUrl by source — a caller-supplied base URL must bring its own
key, so the server never lends its configured key to a body-chosen URL. Always
responds, failing open to the static AVAILABLE_MODELS list on a missing key,
network error, or unknown provider (incl. local-agent) — never hangs.
- Model options are built via the DOM API (createElement/textContent), never
innerHTML, so untrusted provider-returned model ids cannot inject markup.
Scope notes: augment-not-replace (removing the individual sections is deferred
to maintainer preference); base URL + context cap are stored and used for model
discovery and server/agent flows — in-browser mission routing still uses
OpenRouter/Venice, matching the existing "not wired" hints; the "Model Reasoning
(Eino)" sub-proposal is a separate follow-up pending a mapping.
Verified on macOS: 12 unit tests (RED->GREEN) for the model module, full suite
462 green, and Playwright QA (17 checks) driving the real panel — render,
provider switch, persistence across reload, backward-compat, and both the static
and live Fetch paths.
Refs #90
* fix(settings): harden model list fetch
---------
Co-authored-by: Joseph Magly <1159087+jmagly@users.noreply.github.com>