feat(x): rowboat config IPC + model recommendations plumbing

- RowboatApiConfig gains optional modelRecommendations (one model per
  provider FLAVOR, native id format) matching the merged backend payload;
  optional so older API deployments and failed fetches never break parsing
- new rowboat:getConfig IPC serves the unauthenticated /v1/config bootstrap
  independent of sign-in (signed-out BYOK users need recommendations when
  connecting a provider); account:getRowboat is back to pure account state
  (signedIn/accessToken) — config is no longer a property of the account
- renderer useRowboatConfig() hook: one shared store for all config
  consumers (appUrl dialogs/sidebar/settings), plus imperative
  fetchRowboatConfig() for event-time consumers (voice, meeting) that must
  await the value when starting a session
- selectInitialModel(flavor, models, recommendations): pure spec-ordered
  initial pick (recommendation if listed, else first, else null) — runs
  only when a provider is first connected, never over a saved choice

Inert plumbing: nothing calls selectInitialModel yet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ramnique Singh 2026-07-24 13:36:20 +05:30
parent 5729e21d77
commit b0000a10d9
13 changed files with 257 additions and 52 deletions

View file

@ -790,9 +790,16 @@ const ipcSchemas = {
res: z.object({
signedIn: z.boolean(),
accessToken: z.string().nullable(),
config: RowboatApiConfig.nullable(),
}),
},
// The unauthenticated /v1/config bootstrap (service URLs, billing catalog,
// model recommendations). Independent of sign-in state — main caches the
// fetch once per app run; null when the API is unreachable. Renderer
// consumers go through the useRowboatConfig() hook.
'rowboat:getConfig': {
req: z.null(),
res: RowboatApiConfig.nullable(),
},
'oauth:didConnect': {
req: z.object({
provider: z.string(),

View file

@ -11,4 +11,13 @@ export const RowboatApiConfig = z.object({
// app keeps working against API deployments that predate it — the rewards
// UI just stays empty until the backend serves the catalog
creditActivations: z.array(CreditActivationCatalogEntrySchema).optional(),
// One recommended model id per provider FLAVOR (e.g. { openai: "gpt-5.4",
// openrouter: "anthropic/claude-opus-4.8" }), in each provider's native id
// format. A hint for the INITIAL selection when a provider is first
// connected — never a catalog, and never applied over a saved choice
// (see core/models/initial-selection.ts). Local/custom flavors are
// intentionally absent: the API can't know which models exist in a user's
// environment. Optional so older API deployments and failed fetches never
// break parsing — recommendations are best-effort by design.
modelRecommendations: z.record(z.string(), z.string()).optional(),
});