fabro/apps/fabro-web/app/lib
Bryan Helmkamp fbe8b50a16
feat(server): add GET /api/v1/providers and /settings/models page (#321)
## Summary

Operators had no UI surface to see which LLM providers their Fabro
server has configured — provider state was only inferable indirectly via
the per-model `configured` flag on `GET /api/v1/models`. This adds a
dedicated **Models** settings tab backed by a new providers endpoint.

- **`fabro_model::Provider`** — a public projection of the internal
`CatalogProvider` that *structurally* excludes credential-bearing fields
(`auth`, `extra_headers`, `billing_policy`, `agent_profile`). Reused by
the generated API client via progenitor `with_replacement`, mirroring
the existing `Model` pattern — no parallel API DTO.
- **`GET /api/v1/providers`** — lists catalog providers with effective
config and a `configured` status stamped per request from
`ready_llm_provider_ids()`. Sorted by the catalog's existing
`provider_order`. No write endpoints.
- **`/settings/models` web page** — new route + nav entry
(`CpuChipIcon`, between Integrations and Security) rendering each
provider with model count, default model, configured status, and a "Get
API key" link for unconfigured providers.

## Key decisions

- Provider sort: reuse catalog `provider_order` (priority desc, id asc)
— zero extra code.
- `adapter` is hidden in the UI row (noisy for first-party providers);
the OpenAPI `adapter` field is pinned to an enum matching the closed
`AdapterKind` type.
- `configured` reflects credential resolution **at the time of the
response**, not a frozen startup snapshot — doc/spec wording corrected
to match.

## Testing

- `fabro-model`: `From<&CatalogProvider>` + serde `skip_serializing_if`
unit tests.
- `fabro-api`: `Provider` type-identity + JSON-parity tests, including
the required/optional field split.
- `fabro-server`: handler tests for configured vs unconfigured
providers, exact `model_count`/`default_model` against catalog truth,
and credential-omission (asserts internal field names *and* the injected
credential value never reach the wire).
- OpenAPI route conformance test covers `GET /api/v1/providers`.
- `cargo build --workspace`, `fmt --check`, `clippy -D warnings` clean;
935 Rust tests pass; web `tsc` typecheck passes.
- Reviewed via a 10-persona `ce:review` (autofix) — no P0/P1 in shipped
code; 8 safe fixes applied.

Not done: manual UI screenshots — the `apps/fabro-web` build is blocked
in this environment by an unrelated missing `@assistant-ui/react`
dependency. Run `bun install` in `apps/fabro-web` to verify
`/settings/models` manually.

## Post-Deploy Monitoring & Validation

- **What to watch:** request logs for `GET /api/v1/providers` — expect
`200`s for authenticated users, `401` for unauthenticated. The handler
resolves LLM credentials per request via `ready_llm_provider_ids()` (the
same path the existing `list_models` handler already uses).
- **Healthy signals:** `/settings/models` renders the provider list;
`configured` matches each provider's actual credential state; no
credential strings appear in any response body or log line.
- **Failure signals / rollback trigger:** any provider object in the
response containing `auth`, `extra_headers`, or a raw key/token value →
roll back immediately (the projection type makes this structurally
impossible, but treat any occurrence as P0). 5xx spikes on the new
route.
- **Validation window / owner:** first 24h after deploy, owned by the
deploying engineer. Pre-existing note (not introduced here): credential
resolution can refresh OAuth tokens and write the vault as a side effect
of this read — shared with `list_models`; flagged for a future caching
pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:05:00 -04:00
..
api-client.test.ts fix(api): type interview answer submissions 2026-05-08 09:18:35 -07:00
api-client.ts feat(server): add GET /api/v1/providers and /settings/models page (#321) 2026-05-20 09:05:00 -04:00
board-events.test.tsx refactor: remove compatibility shims (#281) 2026-05-16 15:29:25 -04:00
board-events.ts feat(pr): support GitHub pull request associations (#270) 2026-05-16 12:47:27 -04:00
chats-runtime.test.ts feat(fabro-web): port /chats/new + /chats/:id from prototype (#289) 2026-05-16 17:10:16 -04:00
chats-runtime.ts feat(fabro-web): port /chats/new + /chats/:id from prototype (#289) 2026-05-16 17:10:16 -04:00
chats-script.ts feat(fabro-web): port /chats/new + /chats/:id from prototype (#289) 2026-05-16 17:10:16 -04:00
chats-store.test.tsx feat(fabro-web): port /chats/new + /chats/:id from prototype (#289) 2026-05-16 17:10:16 -04:00
chats-store.tsx feat(fabro-web): port /chats/new + /chats/:id from prototype (#289) 2026-05-16 17:10:16 -04:00
chats-types.ts feat(fabro-web): port /chats/new + /chats/:id from prototype (#289) 2026-05-16 17:10:16 -04:00
cross-tab-sse.test.ts fix(web): recover cross-tab SSE coordination after fallback 2026-05-04 15:52:18 -04:00
cross-tab-sse.ts refactor: remove compatibility shims (#281) 2026-05-16 15:29:25 -04:00
demo-mode.test.tsx feat: wire web UI to real server with demo mode toggle 2026-04-08 04:46:08 -04:00
demo-mode.tsx feat: wire web UI to real server with demo mode toggle 2026-04-08 04:46:08 -04:00
format.ts feat(web): add Overview summary panel and promote PR to header pill (#299) 2026-05-17 16:52:48 -04:00
graph-theme.ts refactor(web): drop light mode, go dark-only 2026-04-20 09:27:08 -04:00
live-events.test.tsx feat(web): build live events page in settings 2026-05-10 14:05:10 -04:00
live-events.ts refactor: remove compatibility shims (#281) 2026-05-16 15:29:25 -04:00
mutations.ts refactor: remove compatibility shims (#281) 2026-05-16 15:29:25 -04:00
pierre-diffs-worker.ts fix(web): virtualize run file diffs consistently 2026-05-05 16:41:39 -04:00
queries.ts feat(server): add GET /api/v1/providers and /settings/models page (#321) 2026-05-20 09:05:00 -04:00
query-keys.test.ts feat(web): add Sandbox tab and route to run detail 2026-05-09 21:48:31 -04:00
query-keys.ts feat(server): add GET /api/v1/providers and /settings/models page (#321) 2026-05-20 09:05:00 -04:00
run-actions.test.ts feat(api): unify public run shape 2026-05-10 20:48:55 -04:00
run-actions.ts feat(api): unify public run shape 2026-05-10 20:48:55 -04:00
run-events.test.tsx refactor: remove compatibility shims (#281) 2026-05-16 15:29:25 -04:00
run-events.ts refactor: remove compatibility shims (#281) 2026-05-16 15:29:25 -04:00
run-settings-snapshot.ts refactor: remove compatibility shims (#281) 2026-05-16 15:29:25 -04:00
sse.ts refactor: remove compatibility shims (#281) 2026-05-16 15:29:25 -04:00
stage-sidebar.test.ts feat(api): expose stage handlers on run stages 2026-05-08 13:36:02 -07:00
stage-sidebar.ts feat(web): add specialized stage renderers for non-agent handlers 2026-05-09 18:35:19 -04:00
test-utils.tsx feat(fabro-web): port /chats/new + /chats/:id from prototype (#289) 2026-05-16 17:10:16 -04:00
time.ts Read billing and stages from RunProjection with live runtimes (#213) 2026-05-05 09:32:33 -04:00
unknown.ts refactor(web): narrow event property reads via shared helpers 2026-04-30 22:45:38 -04:00