fix(models): stop silently defaulting to Gemini outside Google-OAuth deployments

⛔ closed · #378 · open-legal-products/mike ← duncanmcqueen/mike · opened 16d ago by duncanmcqueen · closed 13d ago · +413-105 across 19 files · ↗ on GitHub

From the PR description

Summary

Removes the hardcoded gemini-3-flash-preview default. Deployments with Sign-in-with-Google enabled keep it; everywhere else, models resolve from what the signed-in user can actually use, and requests fail with an actionable message when nothing is configured.

What changed

Backend

  • New lib/googleOauth.ts: detects the Supabase external Google provider (SUPABASE_AUTH_EXTERNAL_GOOGLE_CLIENT_ID/_SECRET) and exposes legacyDefaultModel(tier) - the historical Gemini constants, retained only for Google-OAuth deployments.
  • getUserModelSettings: title/tabular fallbacks become availability-based chains (first-party keys by tier → saved router selections). When nothing is usable they return null for non-Google-OAuth deployments; OAuth deployments keep today's Gemini defaults.
  • Chat streaming: requests without a model use saved router selections first, then first-party keys, and fail loudly ("No AI provider is configured...") instead of running an unchosen model. Explicit-but-unknown model ids now also fail loudly instead of silently degrading to the default.
  • Title generation and tabular generation entry points skip or return a clear 409 when no provider is usable.
  • Profile payloads expose nullable titleModel/tabularModel plus composerDefaultModel so clients preselect the deployment's intended default without hardcoding it.

Frontend / add-in

  • Composer renders an explicit "Select model" state instead of assuming Gemini, resets stale selections atomically, and preselects the first usable candidate - server default first, then saved router selections, then first-party models filtered by real key availability.
  • Word add-in catalog mirrors the change; REACT_APP_DEFAULT_MODEL remains available as an operator override.
  • Tabular views skip key checks while no model is selected.
  • .env.example documents that setting both Google vars retains the legacy default.

Why

A stale composer selection (e.g. after a catalog rename) or an omitted model field silently ran Gemini - surfacing only as an opaque stream failure (in one observed deployment, a monthly spend-cap 400) that users could not connect to their own selection. Model choice should come from configuration and user keys, not a compile-time constant.

Testing performed

  • backend: full vitest suite green on Node 22 (727 passed), including new coverage: availability-based fallbacks, null-when-nothing-usable outside Google-OAuth deployments, retained Gemini defaults under the Google flag, and loud rejection of unknown model ids.
  • frontend: full vitest suite green (548 passed; three pre-existing wordAddin/* load failures are identical on pristine main in containerized runs and unrelated to this change), tsc clean, eslint clean.
  • word-addin: tsc clean.
  • Manually exercised both modes against a local stack: with no Google env, a request omitting model streams from the user's saved OpenRouter selection; with the Google vars set, the legacy Gemini default is restored.

Our analysis

Remove Gemini as the implicit model default — read the full analysis →

Think the analysis missed something the PR description covers?

Capture this PR into my fork

Download a Markdown prompt that tells Claude how to port every commit in this PR into your working tree. Run it via claude -p < capture-pull-378.md from inside the repo you want the changes in.

⬇ Download capture-pull-378.md