import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent"; import { type Api, type AssistantMessageEventStream, type Context, type Model, type SimpleStreamOptions, getSupportedThinkingLevels, } from "@earendil-works/pi-ai"; import { streamSimpleOpenAICompletions } from "@earendil-works/pi-ai/compat"; import { readStoredCredential } from "@earendil-works/pi-coding-agent"; import { type CloudModelsGetter, createOllamaCloudOAuthProvider, loginOllamaCloud, refreshOllamaCloudCredential, } from "./auth.js"; import { loadCachedOllamaCloudModels, saveCachedOllamaCloudModels } from "./cache.js"; import { getOllamaCloudRuntimeConfig, getOllamaLocalRuntimeConfig, OLLAMA_API, OLLAMA_CLOUD_API_KEY_ENV, OLLAMA_CLOUD_PROVIDER, OLLAMA_LOCAL_API_KEY_LITERAL, OLLAMA_LOCAL_PROVIDER, } from "./config.js"; import { clearOllamaCliStatusCache, getOllamaCliStatus, type OllamaCliStatus, pullOllamaModel } from "./local.js"; import { discoverOllamaCloudModelList, discoverOllamaCloudModels, discoverOllamaLocalModelList, discoverOllamaLocalModels, getCredentialModels, getFallbackOllamaCloudModels, mergeOllamaLocalCatalog, type OllamaCloudCredentials, type OllamaProviderModel, toDownloadableOllamaLocalModel, toProviderModels, } from "./models.js"; type RuntimeDiscoveryState = { models: OllamaProviderModel[]; lastRefresh: number | null; lastError: string | null; }; type ModelRegistryLike = { refresh?: ExtensionCommandContext["modelRegistry"]["refresh"]; }; type UiLike = { notify: (msg: string, type?: "error" | "info" | "warning") => void; setStatus: (key: string, value: string | undefined) => void; confirm?: (title: string, message: string) => Promise; }; type CommandContextLike = { hasUI?: boolean; ui: UiLike; modelRegistry: ModelRegistryLike; }; type CollectedOllamaModel = OllamaProviderModel & { provider: string; baseUrl: string; }; function getInitialOllamaCloudModels(): OllamaProviderModel[] { return mergeOllamaModelCatalogs(loadCachedOllamaCloudModels(), getFallbackOllamaCloudModels()); } function mergeOllamaModelCatalogs( primaryModels: readonly OllamaProviderModel[], secondaryModels: readonly OllamaProviderModel[], ): OllamaProviderModel[] { const models = new Map(); for (const model of secondaryModels) models.set(model.id, model); for (const model of primaryModels) models.set(model.id, model); return [...models.values()].toSorted((left, right) => left.id.localeCompare(right.id)); } function saveOllamaCloudModelCache(models: readonly OllamaProviderModel[]): void { void saveCachedOllamaCloudModels(models).catch(() => { // Cache persistence must never make provider registration fail. }); } const localDiscoveryState: RuntimeDiscoveryState = { models: [], lastRefresh: null, lastError: null, }; const cloudEnvDiscoveryState: RuntimeDiscoveryState = { models: getInitialOllamaCloudModels(), lastRefresh: null, lastError: null, }; const activeLocalPulls = new Map>(); const PULL_STATUS_KEY = "ollama.pull"; const OLLAMA_STARTUP_CLI_REFRESH_DELAY_MS = 250; const OLLAMA_STARTUP_CLOUD_REFRESH_DELAY_MS = 250; const OLLAMA_STARTUP_CLOUD_DISCOVERY_TIMEOUT_MS = 5_000; const OLLAMA_STARTUP_LOCAL_DISCOVERY_TIMEOUT_MS = 1_000; let ollamaCliStatus: OllamaCliStatus | null = null; let missingCliWarningShown = false; function registerOllamaLocalProvider(pi: ExtensionAPI): void { pi.registerProvider(OLLAMA_LOCAL_PROVIDER, { api: OLLAMA_API, apiKey: OLLAMA_LOCAL_API_KEY_LITERAL, baseUrl: getOllamaLocalRuntimeConfig().apiUrl, models: toProviderModels(getRegisteredLocalModels()), streamSimple: streamSimpleOllama, }); } function registerOllamaCloudProvider(pi: ExtensionAPI): void { pi.registerProvider(OLLAMA_CLOUD_PROVIDER, { api: OLLAMA_API, apiKey: `$${OLLAMA_CLOUD_API_KEY_ENV}`, baseUrl: getOllamaCloudRuntimeConfig().apiUrl, oauth: createOllamaCloudOAuthProvider(() => cloudEnvDiscoveryState.models), models: toProviderModels(cloudEnvDiscoveryState.models), async refreshModels(context) { const credential = context.credential?.type === "oauth" ? (context.credential as OllamaCloudCredentials) : undefined; const credentialModels = credential ? getCredentialModels(credential) : []; if (!context.allowNetwork) { cloudEnvDiscoveryState.models = mergeOllamaModelCatalogs(credentialModels, cloudEnvDiscoveryState.models); return toProviderModels(cloudEnvDiscoveryState.models); } try { const discoveredModels = await discoverOllamaCloudModels(credential?.access, { signal: context.signal, }); cloudEnvDiscoveryState.models = mergeOllamaModelCatalogs( discoveredModels ?? credentialModels, getInitialOllamaCloudModels(), ); if (discoveredModels) saveOllamaCloudModelCache(discoveredModels); cloudEnvDiscoveryState.lastError = null; cloudEnvDiscoveryState.lastRefresh = Date.now(); return toProviderModels(cloudEnvDiscoveryState.models); } catch (error) { cloudEnvDiscoveryState.lastError = error instanceof Error ? error.message : String(error); cloudEnvDiscoveryState.lastRefresh = Date.now(); throw error; } }, streamSimple: streamSimpleOllama, }); } async function refreshRegisteredLocalModels( pi: ExtensionAPI, options: { forceCli?: boolean } = {}, ): Promise { ollamaCliStatus = await getOllamaCliStatus({ force: options.forceCli }); if (!ollamaCliStatus.available) { localDiscoveryState.models = []; localDiscoveryState.lastError = null; localDiscoveryState.lastRefresh = Date.now(); registerOllamaLocalProvider(pi); return []; } try { localDiscoveryState.models = (await discoverOllamaLocalModels()) ?? []; localDiscoveryState.lastError = null; } catch (error) { localDiscoveryState.models = []; localDiscoveryState.lastError = error instanceof Error ? error.message : String(error); } localDiscoveryState.lastRefresh = Date.now(); registerOllamaLocalProvider(pi); return localDiscoveryState.models; } async function refreshRegisteredCloudEnvModels(pi: ExtensionAPI): Promise { const apiKey = process.env[OLLAMA_CLOUD_API_KEY_ENV]?.trim(); try { const discoveredModels = await discoverOllamaCloudModels(apiKey); cloudEnvDiscoveryState.models = discoveredModels ?? getInitialOllamaCloudModels(); if (discoveredModels) saveOllamaCloudModelCache(discoveredModels); cloudEnvDiscoveryState.lastError = null; } catch (error) { cloudEnvDiscoveryState.models = getInitialOllamaCloudModels(); cloudEnvDiscoveryState.lastError = error instanceof Error ? error.message : String(error); } cloudEnvDiscoveryState.lastRefresh = Date.now(); registerOllamaCloudProvider(pi); registerOllamaLocalProvider(pi); return cloudEnvDiscoveryState.models; } function registerOllamaCommands(pi: ExtensionAPI): void { const ollamaCommand = { description: "Inspect or refresh local + cloud Ollama providers: /ollama, /ollama status, /ollama refresh-models, /ollama models, /ollama info , /ollama pull ", async handler(args: string, ctx: ExtensionCommandContext) { const trimmed = args.trim(); const [rawAction = "status", ...rest] = trimmed ? trimmed.split(/\s+/) : ["status"]; const action = rawAction.toLowerCase(); const credential = getStoredCloudCredential(); if (action === "refresh-models") { clearOllamaCliStatusCache(); const localModels = await refreshRegisteredLocalModels(pi, { forceCli: true, }); const cloudModels = await refreshCloudModels(pi, ctx, credential); const cloudStatus = hasCloudAuth(credential) ? `${cloudModels.length} cloud available` : `${cloudModels.length} public cloud discovered; run /login ollama-cloud to use them`; const parts = [`Refreshed Ollama models (${localModels.length} local installed, ${cloudStatus}).`]; if (cloudEnvDiscoveryState.lastError) { parts.push(`Cloud discovery error: ${cloudEnvDiscoveryState.lastError}`); } if (localDiscoveryState.lastError) { parts.push(`Local discovery error: ${localDiscoveryState.lastError}`); } ctx.ui.notify(parts.join(" "), parts.length > 1 ? "warning" : "info"); return; } if (action === "models") { ctx.ui.notify(renderModelList(collectOllamaModels(credential)), "info"); return; } if (action === "pull" || action === "download") { const query = rest.join(" ").trim(); if (!query) { ctx.ui.notify("Usage: /ollama pull ", "warning"); return; } const localModel = findLocalModelForQuery(query, credential); if (!localModel) { ctx.ui.notify(`No Ollama model matched "${query}". Run /ollama refresh-models first.`, "warning"); return; } if (localModel.localAvailability === "installed") { ctx.ui.notify(`ollama/${localModel.id} is already installed locally.`, "info"); return; } await pullLocalModel(pi, ctx, localModel.id); return; } if (action === "info") { const query = rest.join(" ").trim(); if (!query) { ctx.ui.notify("Usage: /ollama info ", "warning"); return; } const model = findModelForQuery(query, collectOllamaModels(credential)); if (!model) { ctx.ui.notify(`No Ollama model matched "${query}". Run /ollama refresh-models first.`, "warning"); return; } ctx.ui.notify(renderModelInfo(model), "info"); return; } ctx.ui.notify(renderUnifiedStatus(credential), "info"); }, }; pi.registerCommand("ollama", ollamaCommand); const ollamaAliases: Array<{ name: string; subcommand: string; description: string }> = [ { name: "ollama status", subcommand: "status", description: "Show local and cloud Ollama status.", }, { name: "ollama refresh-models", subcommand: "refresh-models", description: "Refresh local and cloud Ollama models.", }, { name: "ollama models", subcommand: "models", description: "List local and cloud Ollama models.", }, { name: "ollama info", subcommand: "info", description: "Show detailed metadata for one Ollama model.", }, { name: "ollama pull", subcommand: "pull", description: "Download a local Ollama model via the CLI.", }, ]; for (const alias of ollamaAliases) { pi.registerCommand(alias.name, { description: alias.description, handler: (args: string, ctx: ExtensionCommandContext) => ollamaCommand.handler(args ? `${alias.subcommand} ${args}` : alias.subcommand, ctx), }); } pi.registerCommand("ollama-cloud", { description: "Backward-compatible alias for cloud-only Ollama status and refresh: /ollama-cloud [status|refresh-models]", async handler(args: string, ctx: ExtensionCommandContext) { const action = args.trim().toLowerCase() || "status"; const credential = getStoredCloudCredential(); if (action === "refresh-models") { const cloudModels = await refreshCloudModels(pi, ctx, credential); const suffix = hasCloudAuth(credential) ? `${cloudModels.length} available` : `${cloudModels.length} public models discovered; run /login ollama-cloud to use them`; const parts = [`Refreshed Ollama Cloud models (${suffix}).`]; if (cloudEnvDiscoveryState.lastError) { parts.push(`Discovery error: ${cloudEnvDiscoveryState.lastError}`); } ctx.ui.notify(parts.join(" "), parts.length > 1 ? "warning" : "info"); return; } ctx.ui.notify(renderCloudStatus(credential), "info"); }, }); } function registerOllamaLifecycle(pi: ExtensionAPI): { scheduleCloudBootstrapRefresh: (ctx?: CommandContextLike) => void; scheduleLocalBootstrapRefresh: (ctx?: CommandContextLike) => void; } { let startupCliRefreshTimer: ReturnType | null = null; let startupCloudRefreshTimer: ReturnType | null = null; let pendingLocalBootstrapRefresh: Promise | null = null; let pendingCloudBootstrapRefresh: Promise | null = null; let pendingStartupContext: CommandContextLike | null = null; const notifyMissingCli = (ctx: CommandContextLike | null) => { if (!ctx?.hasUI || missingCliWarningShown || ollamaCliStatus?.available !== false) { return; } missingCliWarningShown = true; ctx.ui.notify( "Ollama CLI not found. Only ollama-cloud models are available right now because Ollama is not installed.", "warning", ); }; const updatePendingStartupContext = (ctx?: CommandContextLike) => { if (ctx) { pendingStartupContext = ctx; } }; const clearPendingStartupContext = () => { if ( startupCliRefreshTimer || startupCloudRefreshTimer || pendingLocalBootstrapRefresh || pendingCloudBootstrapRefresh ) { return; } pendingStartupContext = null; }; const scheduleLocalBootstrapRefresh = (ctx?: CommandContextLike) => { updatePendingStartupContext(ctx); if (pendingLocalBootstrapRefresh || startupCliRefreshTimer) { return; } startupCliRefreshTimer = setTimeout(() => { startupCliRefreshTimer = null; pendingLocalBootstrapRefresh = refreshRegisteredLocalModels(pi) .then(() => { pendingStartupContext?.modelRegistry.refresh?.(); notifyMissingCli(pendingStartupContext); }) .finally(() => { pendingLocalBootstrapRefresh = null; clearPendingStartupContext(); }); }, OLLAMA_STARTUP_CLI_REFRESH_DELAY_MS); startupCliRefreshTimer.unref?.(); }; const scheduleCloudBootstrapRefresh = (ctx?: CommandContextLike) => { updatePendingStartupContext(ctx); if (pendingCloudBootstrapRefresh || startupCloudRefreshTimer) { return; } startupCloudRefreshTimer = setTimeout(() => { startupCloudRefreshTimer = null; pendingCloudBootstrapRefresh = (async () => { const startupCtx = pendingStartupContext; if (!startupCtx) { await refreshRegisteredCloudEnvModels(pi); return; } const credential = getStoredCloudCredential(); await refreshCloudModels(pi, startupCtx, credential); startupCtx.modelRegistry.refresh?.(); })() .catch(() => { // Auth storage can be unavailable during early startup depending on initialization order. // Keep boot resilient and rely on manual /ollama refresh-models as fallback. }) .finally(() => { pendingCloudBootstrapRefresh = null; clearPendingStartupContext(); }); }, OLLAMA_STARTUP_CLOUD_REFRESH_DELAY_MS); startupCloudRefreshTimer.unref?.(); }; pi.on("session_start", (_event, ctx) => { scheduleCloudBootstrapRefresh(ctx); scheduleLocalBootstrapRefresh(ctx); notifyMissingCli(ctx); }); pi.on("session_shutdown", () => { if (startupCliRefreshTimer) { clearTimeout(startupCliRefreshTimer); startupCliRefreshTimer = null; } if (startupCloudRefreshTimer) { clearTimeout(startupCloudRefreshTimer); startupCloudRefreshTimer = null; } pendingStartupContext = null; }); pi.on("model_select", async (event, ctx) => { if (event.model.provider !== OLLAMA_LOCAL_PROVIDER) { return; } const localModel = getRegisteredLocalModels().find((model) => model.id === event.model.id); if (!localModel || localModel.localAvailability !== "downloadable") { return; } ollamaCliStatus = await getOllamaCliStatus(); if (!ollamaCliStatus.available) { ctx.ui.notify( "Ollama CLI not found. Only ollama-cloud models are available right now because Ollama is not installed.", "warning", ); return; } if (event.source === "restore" || !ctx.hasUI || typeof ctx.ui.confirm !== "function") { ctx.ui.notify( `ollama/${event.model.id} is not installed locally yet. Run /ollama pull ${event.model.id} to download it.`, "warning", ); return; } const shouldDownload = await ctx.ui.confirm( "Download local Ollama model?", [ "Would you like to download this model?", "", `ollama/${event.model.id}`, "", `pi will use the Ollama CLI against ${getOllamaLocalRuntimeConfig().origin}.`, `Ollama Cloud is usually faster for the same model: ollama-cloud/${event.model.id}`, ].join("\n"), ); if (!shouldDownload) { ctx.ui.notify(`Skipped local download for ollama/${event.model.id}.`, "info"); return; } await pullLocalModel(pi, ctx, event.model.id); }); return { scheduleCloudBootstrapRefresh, scheduleLocalBootstrapRefresh }; } async function refreshCloudModels( pi: ExtensionAPI, ctx: CommandContextLike, credential: OllamaCloudCredentials | null, ): Promise { registerOllamaCloudProvider(pi); registerOllamaLocalProvider(pi); const result = await ctx.modelRegistry.refresh?.({ force: true, providers: [OLLAMA_CLOUD_PROVIDER] }); if (result) { const error = result.errors.get(OLLAMA_CLOUD_PROVIDER); if (error) { throw error; } return cloudEnvDiscoveryState.models; } if (!credential) { return refreshRegisteredCloudEnvModels(pi); } const discoveredModels = await discoverOllamaCloudModels(credential.access); cloudEnvDiscoveryState.models = mergeOllamaModelCatalogs( discoveredModels ?? getCredentialModels(credential), getInitialOllamaCloudModels(), ); cloudEnvDiscoveryState.lastRefresh = Date.now(); cloudEnvDiscoveryState.lastError = null; return cloudEnvDiscoveryState.models; } async function pullLocalModel(pi: ExtensionAPI, ctx: CommandContextLike, modelId: string): Promise { const existing = activeLocalPulls.get(modelId); if (existing) { return existing; } const run = (async () => { clearOllamaCliStatusCache(); ollamaCliStatus = await getOllamaCliStatus({ force: true }); if (!ollamaCliStatus.available) { registerOllamaLocalProvider(pi); ctx.ui.notify( "Ollama CLI not found. Only ollama-cloud models are available right now because Ollama is not installed.", "warning", ); return false; } ctx.ui.notify(`Downloading ollama/${modelId} via the Ollama CLI...`, "info"); ctx.ui.setStatus(PULL_STATUS_KEY, `Pulling ${modelId}...`); try { await pullOllamaModel(modelId, { env: createOllamaProcessEnv(), onOutput: (line) => { ctx.ui.setStatus(PULL_STATUS_KEY, `Pulling ${modelId}: ${line}`); }, }); await refreshRegisteredLocalModels(pi); ctx.modelRegistry.refresh?.(); if (!isLocalModelInstalled(modelId)) { throw new Error(`Downloaded ${modelId}, but pi could not rediscover it from the local Ollama instance.`); } ctx.ui.notify(`Downloaded ollama/${modelId}.`, "info"); return true; } catch (error) { ctx.ui.notify( `Failed to download ollama/${modelId}: ${error instanceof Error ? error.message : String(error)}`, "error", ); return false; } finally { ctx.ui.setStatus(PULL_STATUS_KEY, undefined); activeLocalPulls.delete(modelId); } })(); activeLocalPulls.set(modelId, run); return run; } function streamSimpleOllama( model: Model, context: Context, options?: SimpleStreamOptions, ): AssistantMessageEventStream { if (model.provider === OLLAMA_LOCAL_PROVIDER) { return streamSimpleOllamaLocal(model, context, options); } if (model.provider === OLLAMA_CLOUD_PROVIDER) { return streamSimpleOllamaCloud(model, context, options); } return streamSimpleOpenAICompletions(model as Model<"openai-completions">, context, options); } function streamSimpleOllamaCloud( model: Model, context: Context, options?: SimpleStreamOptions, ): AssistantMessageEventStream { return streamSimpleOpenAICompletions(model as Model<"openai-completions">, context, options); } function streamSimpleOllamaLocal( model: Model, context: Context, options?: SimpleStreamOptions, ): AssistantMessageEventStream { if (!isLocalModelInstalled(model.id)) { if (!ollamaCliStatus?.available) { throw new Error("Ollama CLI is not installed. Only ollama-cloud models are available right now."); } throw new Error( `ollama/${model.id} is not installed locally yet. Select it again to download it, or run /ollama pull ${model.id}.`, ); } return streamSimpleOpenAICompletions(model as Model<"openai-completions">, context, options); } function getCloudModels(credential: OllamaCloudCredentials | null): OllamaProviderModel[] { return cloudEnvDiscoveryState.models.length > 0 ? cloudEnvDiscoveryState.models : credential ? getCredentialModels(credential) : getFallbackOllamaCloudModels(); } function getCloudRefreshAge(credential: OllamaCloudCredentials | null): string { return formatRefreshAge(cloudEnvDiscoveryState.lastRefresh ?? credential?.lastModelRefresh); } function renderUnifiedStatus(credential: OllamaCloudCredentials | null): string { const localConfig = getOllamaLocalRuntimeConfig(); const cloudConfig = getOllamaCloudRuntimeConfig(); const localModels = getRegisteredLocalModels(); const downloadableLocalModels = localModels.filter((model) => model.localAvailability === "downloadable"); const cloudModels = getCloudModels(credential); const lines = [ `Ollama CLI: ${describeLocalCliStatus()}`, `Ollama local: ${describeLocalRuntime()}`, `Local installed: ${localDiscoveryState.models.length}${formatRefreshAge(localDiscoveryState.lastRefresh)}`, `Local download candidates: ${downloadableLocalModels.length}`, `Local base URL: ${localConfig.apiUrl}`, `Ollama cloud auth: ${describeCloudAuth(credential)}`, `Cloud models: ${cloudModels.length}${getCloudRefreshAge(credential)}`, ]; if (cloudEnvDiscoveryState.lastError) { lines.push(`Cloud discovery error: ${cloudEnvDiscoveryState.lastError}`); } lines.push( `Cloud base URL: ${cloudConfig.apiUrl}`, `Tip: prefer ollama-cloud/... for speed, and use /ollama pull only when you need a local copy.`, ); return lines.join("\n"); } function renderCloudStatus(credential: OllamaCloudCredentials | null): string { const config = getOllamaCloudRuntimeConfig(); const cloudModels = getCloudModels(credential); const lines = [ `Ollama cloud auth: ${describeCloudAuth(credential)}`, `Cloud models: ${cloudModels.length}${getCloudRefreshAge(credential)}`, ]; if (cloudEnvDiscoveryState.lastError) { lines.push(`Discovery error: ${cloudEnvDiscoveryState.lastError}`); } lines.push(`Cloud base URL: ${config.apiUrl}`); return lines.join("\n"); } function collectOllamaModels(credential: OllamaCloudCredentials | null): CollectedOllamaModel[] { const localConfig = getOllamaLocalRuntimeConfig(); const cloudConfig = getOllamaCloudRuntimeConfig(); const cloudModels = getCloudModels(credential); return [ ...cloudModels.map((model) => ({ ...model, provider: OLLAMA_CLOUD_PROVIDER, baseUrl: cloudConfig.apiUrl, })), ...getRegisteredLocalModels().map((model) => ({ ...model, provider: OLLAMA_LOCAL_PROVIDER, baseUrl: localConfig.apiUrl, })), ]; } function findLocalModelForQuery(query: string, credential: OllamaCloudCredentials | null): OllamaProviderModel | null { const localModels = getRegisteredLocalModels(); const localCollectedModels = localModels.map((model) => ({ ...model, provider: OLLAMA_LOCAL_PROVIDER, baseUrl: getOllamaLocalRuntimeConfig().apiUrl, })); const localMatch = findModelForQuery(query, localCollectedModels); if (localMatch) { return localMatch; } const cloudModels = getCloudModels(credential).map((model) => ({ ...model, provider: OLLAMA_CLOUD_PROVIDER, baseUrl: getOllamaCloudRuntimeConfig().apiUrl, })); const cloudMatch = findModelForQuery(query, cloudModels); if (!cloudMatch) { return null; } return localModels.find((model) => model.id === cloudMatch.id) ?? toDownloadableOllamaLocalModel(cloudMatch); } function findModelForQuery(query: string, models: CollectedOllamaModel[]): CollectedOllamaModel | null { const normalized = query.trim().toLowerCase(); if (!normalized) { return null; } return ( models.find((model) => `${model.provider}/${model.id}`.toLowerCase() === normalized) ?? models.find((model) => model.id.toLowerCase() === normalized) ?? models.find((model) => model.name.toLowerCase() === normalized) ?? models.find((model) => `${model.provider}/${model.id}`.toLowerCase().includes(normalized)) ?? models.find((model) => model.id.toLowerCase().includes(normalized)) ?? models.find((model) => model.name.toLowerCase().includes(normalized)) ?? null ); } function renderModelInfo(model: CollectedOllamaModel): string { const lines = [ `${sourceIcon(model.provider)} ${model.provider}/${model.id}`, `Name: ${model.name}`, `Source: ${model.provider === OLLAMA_LOCAL_PROVIDER ? "Local Ollama daemon" : "Ollama Cloud"}`, `Inputs: ${model.input.join(", ")}`, `Reasoning: ${model.reasoning ? "yes" : "no"}`, `Thinking levels: ${getSupportedThinkingLevels(model as Model).join(", ")}`, `Context window: ${model.contextWindow.toLocaleString()}`, `Max tokens: ${model.maxTokens.toLocaleString()}`, `Base URL: ${model.baseUrl}`, ]; if (model.provider === OLLAMA_LOCAL_PROVIDER) { lines.splice( 3, 0, `Local availability: ${model.localAvailability === "downloadable" ? "download required" : "installed"}`, ); } if (model.family) { lines.splice(4, 0, `Family: ${model.family}`); } if (model.parameterSize) { lines.splice(5, 0, `Parameter size: ${model.parameterSize}`); } if (model.quantization) { lines.splice(6, 0, `Quantization: ${model.quantization}`); } const capabilitySummary = summarizeCapabilities(model); if (capabilitySummary) { lines.splice(lines.length - 1, 0, `Capabilities: ${capabilitySummary}`); } return lines.join("\n"); } function renderModelList(models: CollectedOllamaModel[]): string { if (models.length === 0) { return "No Ollama models are currently registered. Run /ollama refresh-models."; } const sections = [ { title: "Cloud", models: models.filter((model) => model.provider === OLLAMA_CLOUD_PROVIDER), }, { title: "Local", models: models.filter((model) => model.provider === OLLAMA_LOCAL_PROVIDER), }, ].filter((section) => section.models.length > 0); return sections .map((section) => [ `Ollama ${section.title}:`, ...section.models .sort((left, right) => sortCollectedModels(left, right)) .map( (model) => ` ${sourceIcon(model.provider)} ${model.provider}/${model.id}: ${model.name}${renderModelBadges( model, )} · ${model.contextWindow.toLocaleString()} ctx`, ), ].join("\n"), ) .join("\n\n"); } function renderModelBadges(model: OllamaProviderModel): string { const badges: string[] = []; if (model.localAvailability === "downloadable") { badges.push("download"); } if (model.input.includes("image")) { badges.push("vision"); } if (model.reasoning) { badges.push("reasoning"); } if (model.parameterSize) { badges.push(model.parameterSize); } return badges.length > 0 ? ` [${badges.join(" · ")}]` : ""; } function summarizeCapabilities(model: OllamaProviderModel): string | null { const values = new Set(); for (const capability of model.capabilities ?? []) { values.add(capability); } if (model.localAvailability === "downloadable") { values.add("download"); } if (model.input.includes("image")) { values.add("vision"); } if (model.reasoning) { values.add("thinking"); } return values.size > 0 ? [...values].join(", ") : null; } function getRegisteredLocalModels(): OllamaProviderModel[] { if (ollamaCliStatus === null) { // Initial model matching runs before the deferred CLI probe, but installed models only need the local API. return localDiscoveryState.models; } if (!ollamaCliStatus.available) { return []; } const cloudCatalog = cloudEnvDiscoveryState.models.length > 0 ? cloudEnvDiscoveryState.models : getFallbackOllamaCloudModels(); return mergeOllamaLocalCatalog(localDiscoveryState.models, cloudCatalog); } function isLocalModelInstalled(modelId: string): boolean { return localDiscoveryState.models.some((model) => model.id === modelId); } function hasCloudAuth(credential: OllamaCloudCredentials | null): boolean { return Boolean(credential || process.env[OLLAMA_CLOUD_API_KEY_ENV]?.trim()); } function describeCloudAuth(credential: OllamaCloudCredentials | null): string { if (credential) { return "stored via /login"; } if (process.env[OLLAMA_CLOUD_API_KEY_ENV]?.trim()) { return "environment only"; } return "public catalog only (run /login ollama-cloud to use models)"; } function describeLocalCliStatus(): string { if (ollamaCliStatus?.available) { return ollamaCliStatus.version ? `available (${ollamaCliStatus.version})` : "available"; } return "missing (only cloud instances are available right now because Ollama is not installed)"; } function describeLocalRuntime(): string { if (!ollamaCliStatus?.available) { return "downloads unavailable"; } if (localDiscoveryState.lastError) { return `unreachable (${localDiscoveryState.lastError})`; } if (localDiscoveryState.lastRefresh) { return "reachable"; } return "probing"; } function sortCollectedModels(left: CollectedOllamaModel, right: CollectedOllamaModel): number { if (left.provider === right.provider && left.provider === OLLAMA_LOCAL_PROVIDER) { const leftWeight = left.localAvailability === "downloadable" ? 1 : 0; const rightWeight = right.localAvailability === "downloadable" ? 1 : 0; if (leftWeight !== rightWeight) { return leftWeight - rightWeight; } } return left.id.localeCompare(right.id); } function sourceIcon(provider: string): string { return provider === OLLAMA_LOCAL_PROVIDER ? "⌂" : "☁"; } function formatRefreshAge(timestamp: number | null | undefined): string { if (!timestamp) { return ""; } const seconds = Math.max(0, Math.round((Date.now() - timestamp) / 1000)); if (seconds < 5) { return " (just refreshed)"; } if (seconds < 60) { return ` (${seconds}s ago)`; } const minutes = Math.round(seconds / 60); if (minutes < 60) { return ` (${minutes}m ago)`; } const hours = Math.round(minutes / 60); return ` (${hours}h ago)`; } function getStoredCloudCredential(): OllamaCloudCredentials | null { if (process.env.VITEST || process.env.NODE_ENV === "test") return null; const credential = readStoredCredential(OLLAMA_CLOUD_PROVIDER); return credential?.type === "oauth" ? (credential as OllamaCloudCredentials) : null; } function createOllamaProcessEnv(): NodeJS.ProcessEnv { const env = { ...process.env }; if (!env.OLLAMA_HOST) { env.OLLAMA_HOST = getOllamaLocalRuntimeConfig().origin; } return env; } async function primeOllamaLocalModelsBeforeRegistration(): Promise { const abortController = new AbortController(); const timeout = setTimeout(() => abortController.abort(), OLLAMA_STARTUP_LOCAL_DISCOVERY_TIMEOUT_MS); timeout.unref?.(); try { localDiscoveryState.models = (await discoverOllamaLocalModelList({ signal: abortController.signal })) ?? []; localDiscoveryState.lastError = null; localDiscoveryState.lastRefresh = Date.now(); } catch (error) { localDiscoveryState.lastError = error instanceof Error ? error.message : String(error); } finally { clearTimeout(timeout); } } async function primeOllamaCloudModelsBeforeRegistration(): Promise { const abortController = new AbortController(); const timeout = setTimeout(() => abortController.abort(), OLLAMA_STARTUP_CLOUD_DISCOVERY_TIMEOUT_MS); timeout.unref?.(); try { const apiKey = process.env[OLLAMA_CLOUD_API_KEY_ENV]?.trim(); const discoveredModels = await discoverOllamaCloudModelList(apiKey, { signal: abortController.signal, }); if (!discoveredModels) return; cloudEnvDiscoveryState.models = mergeOllamaModelCatalogs(discoveredModels, getInitialOllamaCloudModels()); cloudEnvDiscoveryState.lastError = null; cloudEnvDiscoveryState.lastRefresh = Date.now(); saveOllamaCloudModelCache(cloudEnvDiscoveryState.models); } catch (error) { cloudEnvDiscoveryState.lastError = error instanceof Error ? error.message : String(error); } finally { clearTimeout(timeout); } } function bootstrapOllamaProviders( pi: ExtensionAPI, scheduleRefreshes: { scheduleCloudBootstrapRefresh: () => void; scheduleLocalBootstrapRefresh: () => void; }, ): void { registerOllamaCloudProvider(pi); registerOllamaLocalProvider(pi); scheduleRefreshes.scheduleCloudBootstrapRefresh(); scheduleRefreshes.scheduleLocalBootstrapRefresh(); } export { type CloudModelsGetter, createOllamaCloudOAuthProvider, discoverOllamaCloudModelList, discoverOllamaCloudModels, discoverOllamaLocalModelList, discoverOllamaLocalModels, getCredentialModels, getFallbackOllamaCloudModels, loginOllamaCloud, refreshOllamaCloudCredential, }; export { type OllamaCloudCredentials, type OllamaProviderModel, toOllamaCloudModel, toOllamaModel } from "./models.js"; export default async function ollamaProviderExtension(pi: ExtensionAPI): Promise { await Promise.all([primeOllamaCloudModelsBeforeRegistration(), primeOllamaLocalModelsBeforeRegistration()]); const registerLifecycle = registerOllamaLifecycle(pi); bootstrapOllamaProviders(pi, registerLifecycle); registerOllamaCommands(pi); }