The AI assistant is no longer hardwired to one server-held OpenRouter key. Users can supply their own provider + model + key (billed to them), and self-hosters can write a server-wide config from the settings page. The model choice is now provider-agnostic across OpenRouter, OpenAI, Anthropic (native), and any OpenAI-compatible self-hosted endpoint (Ollama / LM Studio / vLLM).
Added
supabase/migrations/20260627000100_ai_credentials.sql— per-user AI config (provider,model,vision_model,base_url,context_window,api_key_encrypted). Mirrorsconnectionssecret handling: the key is stored AES-256-GCM-encrypted and the ciphertext column is REVOKEd from the browser role (read/written only via the service role). Types added tolib/supabase/types.ts.lib/ai/types.ts— shared, client-safe types (ProviderId,AICredentialView,ResolvedAIConfig) + provider base URLs and the Anthropic-model test that gates prompt caching.lib/ai/providers.ts— onecallModel()adapter over two wire dialects: OpenAI-compatible (openrouter/openai/custom) and Anthropic Messages (anthropic).cache_controlephemeral breakpoints are emitted only for Anthropic-family models; actionable errors for 401/402/404.lib/ai/config.ts— credential resolver (user DB → server file → env) with AES-256-GCM encrypt/decrypt (keyed byAI_CREDENTIALS_KEY), plusupsert/delete/getCredentialView. A user row without a key borrows the host key when providers match ("my model on the host's account").lib/ai/budget.ts— context budgeting keyed off the model's window (board context vs. history), replacing the fixedMAX_HISTORY/char caps.app/api/ai/credentials/route.ts— GET/PUT/DELETE the user's AI config (key encrypted before storage, never returned).app/api/ai/config-file/route.ts— self-host only (ALLOW_AI_CONFIG_FILE=1): writes/readsai.config.json(mode 0600, gitignored).components/AISettingsPanel.tsx— settings sheet to pick provider/model/key, with a "My account" vs "This server" target (the latter shown only when the server enables the config file). Opened via a new gear in the Ask AI header.
Changed
app/api/ai/route.ts— resolves config per request and callscallModel()instead of a hardcoded OpenRouter fetch; board context + history now trimmed by the budget allocator. Returns a clear "AI isn't configured" error when nothing resolves. ExistingOPENROUTER_*env vars still work (lowest-priority fallback).lib/ai-enrich.ts— image captioning uses the resolved config's vision model/key/endpoint (OpenAI-compatible providers); skipped foranthropic..env.example— documentsAI_CREDENTIALS_KEY,ALLOW_AI_CONFIG_FILE,AI_CONFIG_PATH, and genericAI_PROVIDER/AI_API_KEY/AI_MODEL/AI_BASE_URL/AI_CONTEXT_WINDOWalongside the legacyOPENROUTER_*names.
Note: storing user keys requires AI_CREDENTIALS_KEY (32-byte base64/hex). The
migration must be applied before users can save credentials.