// What the model settings show about providers: the groups of the provider // picker, and how far a provider's setup has come import { PROVIDER_NAMES, PROVIDERS, type ProviderGroup as ProviderGroupId, } from "@/lib/providers" import { PROVIDER_INFO, type ProviderConfig, type ProviderName, } from "@/lib/types/model-config" export type { ProviderGroupId } /** The provider picker's groups, each provider in exactly one, in record order */ export const PROVIDER_GROUPS: { id: ProviderGroupId providers: ProviderName[] }[] = (["vendors", "aggregators", "cloud", "selfHosted"] as const).map( (id) => ({ id, providers: PROVIDER_NAMES.filter((p) => PROVIDERS[p].group === id), }), ) /** Shown when the user has no provider yet */ export const POPULAR_PROVIDERS: ProviderName[] = [ "openai", "anthropic", "google", "deepseek", "openrouter", "qwen", ] /** Providers that need something other than a plain API key */ export const PROVIDER_NOTE_KEYS: Partial> = { bedrock: "noteBedrock", vertexai: "noteVertex", azure: "noteAzure", edgeone: "noteEdgeone", ollama: "noteOllama", sglang: "noteSglang", } /** * Providers whose base URL usually needs changing (Azure's default is only * an example; Ollama's is its cloud, SGLang's a local server) or comes with * a hint (MiniMax, MiMo): their folded options start open */ export const OPTIONS_OPEN: ProviderName[] = PROVIDER_NAMES.filter( (p) => PROVIDERS[p].optionsOpen, ) /** The groups with only the providers whose name matches the search */ export function filterProviderGroups(query: string) { const q = query.trim().toLowerCase() if (!q) return PROVIDER_GROUPS return PROVIDER_GROUPS.map((group) => ({ ...group, providers: group.providers.filter( (p) => PROVIDER_INFO[p].label.toLowerCase().includes(q) || p.includes(q), ), })).filter((group) => group.providers.length > 0) } /** The credentials a test or a chat request needs are filled in */ export function hasCredentials(p: ProviderConfig): boolean { switch (p.provider) { case "bedrock": return ( (!!p.apiKey || (!!p.awsAccessKeyId && !!p.awsSecretAccessKey)) && !!p.awsRegion ) case "vertexai": return !!p.vertexApiKey // Its default base URL names no real resource case "azure": return ( !!p.apiKey && !!p.baseUrl && p.baseUrl !== PROVIDER_INFO.azure.defaultBaseUrl ) case "edgeone": case "ollama": return true default: return !!p.apiKey } } export type ProviderStatus = | { kind: "error"; count: number } | { kind: "incomplete" } | { kind: "untested"; count: number } | { kind: "ok"; count: number } /** The one thing about a provider that most needs the user's attention */ export function providerStatus(p: ProviderConfig): ProviderStatus { const failed = p.models.filter((m) => m.validated === false).length if (failed > 0) return { kind: "error", count: failed } if (!hasCredentials(p) || p.models.length === 0) { return { kind: "incomplete" } } const untested = p.models.filter((m) => m.validated !== true).length if (untested > 0) return { kind: "untested", count: untested } return { kind: "ok", count: p.models.length } }