// First-run, zero-config default provider (product decision 2026-08-07: // "user wants to use AI so set the local one to on always by default" — not // "user wants to configure AI"). Before this, a fresh install left // `settings.provider` at `null` and every AI entry point just told the user // to go set one up in Settings. // // Priority: OpenCode first, then Ollama. OpenCode is the one local option // this app controls end to end — electron/main.ts auto-spawns // `opencode serve` itself, so "OpenCode has a model" only depends on what's // already authenticated in its own auth.json, not on the user having // installed anything separately. Ollama is second because it's an external // dependency the user must have installed and started themselves — real, // but not zero-config the way OpenCode is here. // // Never overrides an explicit choice: fires only while `provider` is still // `null`, and at most once per page load (module-level `attempted`) so a // component re-mounting doesn't re-probe on every render. import { loadAiSettings, saveAiSettings, type AiLocalSettings } from './local-settings'; import { listOpencodeModels } from './local-client'; import { discoverLocalOllama, recommendDefaultModel } from './local-discovery'; import { supportsLocalLlm } from '../platform-capabilities'; import type { AiPolicy } from './types'; let attempted = false; /** Test-only: lets a fresh module state be simulated without a full reload. */ export function _resetAutoProvisionForTests(): void { attempted = false; } export async function ensureDefaultProvider(policy: AiPolicy): Promise { const current = loadAiSettings(); if (current.provider !== null || attempted) return current; attempted = true; if (policy.entitlement.classes.includes('opencode')) { try { const models = await listOpencodeModels(); if (models[0]) { const next: AiLocalSettings = { ...current, provider: 'opencode', opencodeModel: models[0].ref }; saveAiSettings(next); return next; } } catch { // opencode not reachable yet (still starting, or the CLI isn't // installed) — fall through to Ollama rather than surfacing an error // for a default the user never asked for. } } if (supportsLocalLlm() && policy.entitlement.classes.includes('local')) { const discovery = await discoverLocalOllama(); if (discovery) { const recommended = recommendDefaultModel(discovery.models) ?? discovery.models[0]?.name ?? null; if (recommended) { const next: AiLocalSettings = { ...current, provider: 'local', localBaseUrl: discovery.baseUrl, localModel: recommended, }; saveAiSettings(next); return next; } } } return current; }