UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

729 lines (728 loc) 32.6 kB
import { r as createLazyRuntimeModule } from "../../lazy-runtime-CgCh8H_K.js"; import { t as findNormalizedProviderKey } from "../../provider-id-DMd-TDFp.js"; import { s as coerceSecretRef } from "../../types.secrets-kC0nOetj.js"; import { n as collectConfiguredModelRefValues } from "../../configured-model-refs-DEXTV_K3.js"; import { t as resolveConfiguredSecretInputString } from "../../resolve-configured-secret-input-string-B8zlpQOU.js"; import { c as isNonSecretApiKeyMarker } from "../../model-auth-markers-jBKQn38x.js"; import { a as buildOpenAICompatibleReplayPolicy } from "../../provider-replay-helpers-36erCKTj.js"; import "../../provider-auth-BeZ7NZUU.js"; import { n as buildApiKeyCredential } from "../../provider-auth-helpers-CaYTVMoC.js"; import { t as createProviderApiKeyAuthMethod } from "../../provider-api-key-auth-mNtReYE0.js"; import { t as definePluginEntry } from "../../plugin-entry-zfBGJaNO.js"; import "../../provider-model-shared-fX-Ywqb3.js"; import { r as resolvePluginConfigObject } from "../../plugin-config-runtime-BaaMs-tR.js"; import "../../provider-auth-api-key-DSeBB_JQ.js"; import { r as buildProviderToolCompatFamilyHooks } from "../../provider-tools-D4EQoyQM.js"; import "../../secret-input-runtime-Clavxxg-.js"; import { a as OLLAMA_DEFAULT_API_KEY, d as OLLAMA_GLM52_CLOUD_MODEL_ID, i as OLLAMA_CLOUD_PROVIDER_ID, m as resolveOllamaSetupDefaultBaseUrl, n as OLLAMA_CLOUD_BASE_URL, o as OLLAMA_DEFAULT_BASE_URL, r as OLLAMA_CLOUD_DEFAULT_MODELS, t as DEFAULT_OLLAMA_EMBEDDING_MODEL } from "../../defaults-BiE2_Zq0.js"; import { r as resolveThinkingProfile } from "../../provider-policy-api-BAps4YAp.js"; import { t as readProviderBaseUrl } from "../../provider-base-url-E6aWTKii.js"; import { _ as queryOllamaModelShowInfo, a as capLocalOllamaModelContext, d as isOllamaCloudModel, i as buildOllamaProvider, l as fetchLoadedOllamaModelNames, o as capLocalOllamaProviderContext, r as buildOllamaModelDefinition, t as buildDefaultOllamaCloudModelDefinition, u as fetchOllamaModels, y as resolveOllamaApiBase } from "../../provider-models-BK60TdeT.js"; import { a as shouldUseSyntheticOllamaAuth, i as resolveOllamaRuntimeBaseUrl, n as isLocalOllamaBaseUrl, r as resolveOllamaDiscoveryResult, t as OLLAMA_PROVIDER_ID } from "../../discovery-shared-BZtIqpM2.js"; import { c as OLLAMA_MODELS_COMMAND, d as OLLAMA_NODE_INFERENCE_DEFAULT_PLATFORMS, f as ollamaNodeInferenceToolDefinition, l as OLLAMA_NODE_INFERENCE_CAPABILITY, s as OLLAMA_CHAT_COMMAND, u as OLLAMA_NODE_INFERENCE_COMMANDS } from "../../node-inference-contract-sb8R04qK.js"; import { c as orderPreferredOllamaModelIds, i as findAvailableOllamaModelName } from "../../setup-model-selection-BImV-Os6.js"; import { r as resolveConfiguredOllamaProviderConfig, t as createConfiguredOllamaCompatStreamWrapper } from "../../stream-compat-DSz3ALUi.js"; import "../../stream-contract-CaPxW4Jp.js"; import { t as createLazyOllamaWebSearchProvider } from "../../web-search-provider-registration-MJUlKCKJ.js"; //#region extensions/ollama/src/node-inference-registration.ts const loadOllamaNodeInference = createLazyRuntimeModule(() => import("../../node-inference-DIonBSBv.js")); function createLazyNodeHostCommand(command) { let runtimeCommandPromise; const loadRuntimeCommand = () => runtimeCommandPromise ??= loadOllamaNodeInference().then((runtime) => { const runtimeCommand = runtime.createOllamaNodeHostCommands().find((candidate) => candidate.command === command); if (!runtimeCommand) throw new Error(`Ollama node inference runtime missing ${command}`); return runtimeCommand; }); return { command, cap: OLLAMA_NODE_INFERENCE_CAPABILITY, handle: async (paramsJSON, io, context) => { return await (await loadRuntimeCommand()).handle(paramsJSON, io, context); } }; } function createLazyOllamaNodeHostCommands() { return [createLazyNodeHostCommand(OLLAMA_MODELS_COMMAND), createLazyNodeHostCommand(OLLAMA_CHAT_COMMAND)]; } function createOllamaNodeInvokePolicy() { return { commands: [...OLLAMA_NODE_INFERENCE_COMMANDS], defaultPlatforms: [...OLLAMA_NODE_INFERENCE_DEFAULT_PLATFORMS], handle: async (ctx) => await ctx.invokeNode() }; } function createLazyOllamaNodeInferenceTool(api) { let toolPromise; const loadTool = () => toolPromise ??= loadOllamaNodeInference().then((runtime) => runtime.createOllamaNodeInferenceTool(api)); return { ...ollamaNodeInferenceToolDefinition, execute: async (...args) => { return await (await loadTool()).execute(...args); } }; } //#endregion //#region extensions/ollama/src/stream-registration.ts const loadOllamaStreamRuntime = createLazyRuntimeModule(() => import("../../stream.runtime-B4AmG-9m.js")); function createLazyConfiguredOllamaStreamFn(params) { const streamFnPromise = loadOllamaStreamRuntime().then((runtime) => runtime.createConfiguredOllamaStreamFn(params)); return async (...args) => { return (await streamFnPromise)(...args); }; } //#endregion //#region extensions/ollama/index.ts const loadOllamaSetup = createLazyRuntimeModule(() => import("../../setup.runtime-CDzqtr4J.js")); const loadOllamaMemoryEmbeddingProviderAdapter = createLazyRuntimeModule(async () => (await import("../../memory-embedding-adapter-BwRQqjqj.js")).ollamaMemoryEmbeddingProviderAdapter); const lazyOllamaMemoryEmbeddingProviderAdapter = { id: OLLAMA_PROVIDER_ID, defaultModel: DEFAULT_OLLAMA_EMBEDDING_MODEL, transport: "remote", authProviderId: OLLAMA_PROVIDER_ID, create: async (options) => await (await loadOllamaMemoryEmbeddingProviderAdapter()).create(options) }; const ollamaMediaUnderstandingProvider = { id: OLLAMA_PROVIDER_ID, capabilities: ["image"], describeImage: void 0, describeImages: void 0 }; async function checkWsl2CrashLoopRiskLazily(api) { try { const { isWSL2Sync } = await import("../../plugin-sdk/runtime-env.js"); if (!isWSL2Sync()) return; const { checkWsl2CrashLoopRisk } = await import("../../wsl2-crash-loop-check-BYHXWlQ6.js"); await checkWsl2CrashLoopRisk(api.logger); } catch {} } function buildNativeOllamaReplayPolicy() { return { ...buildOpenAICompatibleReplayPolicy("openai-completions", { sanitizeToolCallIds: false }), sanitizeToolCallIds: false }; } function matchesOllamaContextOverflowError(errorMessage) { return /\bollama\b.*(?:context length|too many tokens|context window)/i.test(errorMessage) || /\btruncating input\b.*\btoo long\b/i.test(errorMessage); } function classifyOllamaFailoverReason(errorMessage) { return errorMessage.trim() === "Ollama API stream ended without a final response" ? "server_error" : void 0; } const OLLAMA_CLOUD_DEFAULT_MODEL_REF = `${OLLAMA_CLOUD_PROVIDER_ID}/${OLLAMA_CLOUD_DEFAULT_MODELS[0].id}`; const OLLAMA_CONFIGURED_SHOW_CONCURRENCY = 4; const OLLAMA_CONFIGURED_SHOW_MAX_MODELS = 8; async function buildLocalOllamaProvider(configuredBaseUrl, opts) { return capLocalOllamaProviderContext(await buildOllamaProvider(configuredBaseUrl, opts)); } async function resolveAppGuidedOllamaConnection(ctx) { if (resolvePluginConfigObject(ctx.config, "ollama")?.discovery?.enabled === false) return null; const existing = resolveConfiguredOllamaProviderConfig({ config: ctx.config, providerId: OLLAMA_PROVIDER_ID }); const accessValue = await resolveAppGuidedOllamaApiKey(ctx, existing); return { existing, accessValue, discoveryAccess: accessValue ? { apiKey: accessValue } : {}, baseUrl: resolveOllamaApiBase(readProviderBaseUrl(existing) ?? resolveOllamaSetupDefaultBaseUrl(ctx.env)) }; } async function detectAppGuidedOllamaAvailability(ctx) { const connection = await resolveAppGuidedOllamaConnection(ctx); if (!connection) return false; return (await fetchOllamaModels(connection.baseUrl, { ...connection.discoveryAccess, ...ctx.signal ? { signal: ctx.signal } : {} })).reachable; } async function discoverAppGuidedOllamaModel(ctx, options) { const connection = await resolveAppGuidedOllamaConnection(ctx); if (!connection) return null; const requestedPrefix = `${OLLAMA_PROVIDER_ID}/`; const requestedModelId = options?.modelRef?.startsWith(requestedPrefix) ? options.modelRef.slice(requestedPrefix.length) : void 0; if (options?.modelRef && !requestedModelId) return null; const configuredModels = connection.existing?.models ?? []; const requestedConfiguredModel = requestedModelId ? configuredModels.find((candidate) => findAvailableOllamaModelName(candidate.id, [requestedModelId]) !== void 0) : void 0; let requestedModelIsLoaded = false; let availableModelNames; if (requestedModelId) { if (!requestedConfiguredModel && !isOllamaCloudModel(requestedModelId)) { const loaded = await fetchLoadedOllamaModelNames(connection.baseUrl, { ...connection.discoveryAccess, ...ctx.signal ? { signal: ctx.signal } : {} }); if (!loaded.reachable || findAvailableOllamaModelName(requestedModelId, loaded.models) === void 0) return null; requestedModelIsLoaded = true; } availableModelNames = [requestedModelId]; } else { const loaded = await fetchLoadedOllamaModelNames(connection.baseUrl, { ...connection.discoveryAccess, ...ctx.signal ? { signal: ctx.signal } : {} }); if (!loaded.reachable || loaded.models.length === 0) return null; availableModelNames = loaded.models; } const provider = await buildOllamaProvider(connection.baseUrl, { quiet: true, ...connection.discoveryAccess }); const providerModels = provider.models ?? []; const requestedProviderModel = requestedModelId ? providerModels.find((candidate) => candidate.compat?.supportsTools === true && findAvailableOllamaModelName(candidate.id, [requestedModelId]) !== void 0) : void 0; const requestedModel = (requestedConfiguredModel || requestedModelIsLoaded) && requestedProviderModel ? requestedProviderModel : requestedConfiguredModel && requestedModelId && isOllamaCloudModel(requestedModelId) ? requestedConfiguredModel : void 0; const toolModels = requestedModelId ? requestedModel ? [requestedModel] : [] : providerModels.filter((candidate) => candidate.compat?.supportsTools === true && findAvailableOllamaModelName(candidate.id, availableModelNames) !== void 0); let model; const candidatesById = new Map(toolModels.map((candidate) => [candidate.id, candidate])); for (const candidateId of orderPreferredOllamaModelIds(candidatesById.keys())) { const candidate = candidatesById.get(candidateId); if (!candidate) continue; const showInfo = await queryOllamaModelShowInfo(provider.baseUrl, candidate.id, connection.accessValue ? { apiKey: connection.accessValue } : void 0); const contextWindow = showInfo.contextWindow ?? candidate.contextWindow; if (!(showInfo.capabilities?.includes("tools") ?? candidate.compat?.supportsTools === true) || contextWindow === void 0 || contextWindow < 16384) continue; model = capLocalOllamaModelContext({ ...candidate, contextWindow, contextTokens: contextWindow, compat: { ...candidate.compat, supportsTools: true } }); break; } if (!model) return null; const preparedProvider = capLocalOllamaProviderContext({ ...provider, models: providerModels.some((candidate) => candidate.id === model.id) ? providerModels.map((candidate) => candidate.id === model.id ? model : candidate) : [...providerModels, model] }); const ownerValue = connection.existing?.apiKey ?? (connection.accessValue ? "OLLAMA_API_KEY" : "ollama-local"); return { existing: connection.existing, provider: preparedProvider, model, ownerValue }; } function hasOllamaDiscoverySignal(providerConfig) { return Boolean(process.env.OLLAMA_API_KEY?.trim()) || shouldUseSyntheticOllamaAuth(providerConfig) || Boolean(providerConfig?.apiKey); } function toDynamicOllamaModel(params) { const input = (params.model.input ?? ["text"]).filter((value) => value === "text" || value === "image"); return { id: params.model.id, name: params.model.name ?? params.model.id, provider: params.provider, api: params.providerConfig.api ?? "ollama", baseUrl: readProviderBaseUrl(params.providerConfig) ?? "", reasoning: params.model.reasoning ?? false, input: input.length > 0 ? input : ["text"], cost: params.model.cost ?? { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: params.model.contextWindow ?? 8192, ...params.model.contextTokens !== void 0 ? { contextTokens: params.model.contextTokens } : {}, maxTokens: params.model.maxTokens ?? 8192, ...params.model.compat ? { compat: params.model.compat } : {}, ...params.model.params ? { params: params.model.params } : {} }; } function stripTrailingAuthProfile(raw) { const trimmed = raw.trim(); const lastSlash = trimmed.lastIndexOf("/"); let delimiter = trimmed.indexOf("@", lastSlash + 1); if (delimiter <= 0) return trimmed; const suffix = () => trimmed.slice(delimiter + 1); if (/^\d{8}(?:@|$)/.test(suffix())) { const next = trimmed.indexOf("@", delimiter + 9); if (next < 0) return trimmed; delimiter = next; } if (/^(?:i?q\d+(?:_[a-z0-9]+)*|\d+bit)(?:@|$)/i.test(suffix())) { const next = trimmed.indexOf("@", delimiter + 1); if (next < 0) return trimmed; delimiter = next; } const model = trimmed.slice(0, delimiter).trim(); const profile = trimmed.slice(delimiter + 1).trim(); return model && profile ? model : trimmed; } function needsOllamaCatalogMetadata(entry) { return !(entry.contextWindow !== void 0 || entry.contextTokens !== void 0) || entry.reasoning === void 0 || entry.input === void 0 || entry.compat === void 0; } function readConfiguredOllamaApiKey(value) { if (typeof value === "string") return value.trim() || void 0; if (value && typeof value === "object" && "value" in value) { const resolved = value.value; if (typeof resolved === "string") return resolved.trim() || void 0; } } function readConcreteOllamaApiKey(value) { if (coerceSecretRef(value)) return; const apiKey = readConfiguredOllamaApiKey(value); return apiKey && !isNonSecretApiKeyMarker(apiKey) ? apiKey : void 0; } async function resolveAppGuidedOllamaApiKey(ctx, provider) { const input = provider?.apiKey; if (input === void 0 || input === null) { const configuredBaseUrl = readProviderBaseUrl(provider); if (!configuredBaseUrl || isLocalOllamaBaseUrl(configuredBaseUrl)) return; return readConcreteOllamaApiKey(ctx.env.OLLAMA_API_KEY); } const resolved = await resolveConfiguredSecretInputString({ config: ctx.config, env: ctx.env, value: input, path: `models.providers.${OLLAMA_PROVIDER_ID}.apiKey`, unresolvedReasonStyle: "detailed" }); if (resolved.unresolvedRefReason) return; const value = readConfiguredOllamaApiKey(resolved.value); return value === "OLLAMA_API_KEY" ? readConcreteOllamaApiKey(ctx.env.OLLAMA_API_KEY) : readConcreteOllamaApiKey(value); } function readEnvBackedOllamaApiKey(value, env) { const ref = coerceSecretRef(value); if (ref?.source === "env") return readConcreteOllamaApiKey(env[ref.id.trim()]); } function isAmbientOllamaApiKeyMarker(value) { return value === "ollama-local" || value === "OLLAMA_API_KEY"; } function readUsableOllamaShowApiKey(params) { const explicitEnvApiKey = readEnvBackedOllamaApiKey(params.explicitApiKey, params.env); if (explicitEnvApiKey) return explicitEnvApiKey; const explicitApiKey = readConcreteOllamaApiKey(params.explicitApiKey); if (explicitApiKey) return explicitApiKey; const resolvedApiKey = readConfiguredOllamaApiKey(params.resolved?.apiKey); const canUseResolvedDiscovery = params.allowAmbientEnvFallback || !isAmbientOllamaApiKeyMarker(resolvedApiKey); const discoveryApiKey = readConcreteOllamaApiKey(params.resolved?.discoveryApiKey); if (discoveryApiKey && canUseResolvedDiscovery) return discoveryApiKey; const resolvedEnvApiKey = readEnvBackedOllamaApiKey(params.resolved?.apiKey, params.env); if (resolvedEnvApiKey && canUseResolvedDiscovery) return resolvedEnvApiKey; const apiKey = readConcreteOllamaApiKey(params.resolved?.apiKey); if (apiKey) return apiKey; return params.allowAmbientEnvFallback ? readConcreteOllamaApiKey(params.env.OLLAMA_API_KEY) : void 0; } function collectConfiguredOllamaModelIds(params) { const providerPrefix = `${params.provider.toLowerCase()}/`; const models = /* @__PURE__ */ new Map(); const addModelId = (modelId, api, name) => { const trimmed = modelId.trim(); if (!trimmed || trimmed === "*") return; const trimmedName = typeof name === "string" ? name.trim() : ""; const existing = models.get(trimmed); if (existing) { if (!existing.api && api || !existing.name && trimmedName) models.set(trimmed, { ...existing, ...api && !existing.api ? { api } : {}, ...trimmedName && !existing.name ? { name: trimmedName } : {} }); return; } models.set(trimmed, { id: trimmed, ...api ? { api } : {}, ...trimmedName ? { name: trimmedName } : {} }); }; const addRef = (raw) => { if (typeof raw !== "string") return; const trimmed = stripTrailingAuthProfile(raw); if (!trimmed.toLowerCase().startsWith(providerPrefix)) return; const modelId = trimmed.slice(providerPrefix.length).trim(); addModelId(modelId); }; for (const ref of collectConfiguredModelRefValues(params.config)) addRef(ref); for (const entry of params.entries ?? []) if (entry.provider.toLowerCase() === params.provider.toLowerCase() && entry.id.trim() && needsOllamaCatalogMetadata(entry)) addModelId(entry.id.trim(), entry.api, entry.name); return [...models.values()]; } function buildStaticOllamaCloudProvider() { return { baseUrl: OLLAMA_CLOUD_BASE_URL, api: "ollama", models: OLLAMA_CLOUD_DEFAULT_MODELS.map(buildDefaultOllamaCloudModelDefinition) }; } async function buildOllamaCloudProvider(apiKey) { const discovered = await buildOllamaProvider(OLLAMA_CLOUD_BASE_URL, { ...apiKey ? { apiKey } : {}, quiet: true }); if (!discovered.models?.length) return buildStaticOllamaCloudProvider(); if (!apiKey || discovered.models.some((model) => model.id === "glm-5.2")) return discovered; const showInfo = await queryOllamaModelShowInfo(OLLAMA_CLOUD_BASE_URL, OLLAMA_GLM52_CLOUD_MODEL_ID, { apiKey }); if (typeof showInfo.contextWindow !== "number" && (showInfo.capabilities?.length ?? 0) === 0) return discovered; const defaultModel = OLLAMA_CLOUD_DEFAULT_MODELS.find((model) => model.id === OLLAMA_GLM52_CLOUD_MODEL_ID); if (!defaultModel) return discovered; return { ...discovered, models: [...discovered.models, buildDefaultOllamaCloudModelDefinition(defaultModel)] }; } async function resolveRequestedDynamicOllamaModel(params) { const showBaseUrl = readProviderBaseUrl(params.providerConfig) ?? "http://127.0.0.1:11434"; const showInfo = params.showApiKey ? await queryOllamaModelShowInfo(showBaseUrl, params.modelId, { apiKey: params.showApiKey }) : await queryOllamaModelShowInfo(showBaseUrl, params.modelId); if (typeof showInfo.contextWindow !== "number" && (showInfo.capabilities?.length ?? 0) === 0) return; const definition = buildOllamaModelDefinition(params.modelId, showInfo.contextWindow, showInfo.capabilities); const model = params.capContextTokens ? capLocalOllamaModelContext(definition) : definition; return toDynamicOllamaModel({ provider: params.provider, providerConfig: params.providerConfig, model }); } async function augmentConfiguredOllamaCatalogModels(params) { const models = collectConfiguredOllamaModelIds({ config: params.config, provider: params.provider, entries: params.entries }); if (models.length === 0) return []; const configuredProvider = resolveConfiguredOllamaProviderConfig({ config: params.config, providerId: params.provider }); const baseUrl = readProviderBaseUrl(configuredProvider) ?? params.defaultBaseUrl; const isLocalBaseUrl = isLocalOllamaBaseUrl(baseUrl); const showApiKey = readUsableOllamaShowApiKey({ env: params.env, allowAmbientEnvFallback: !isLocalBaseUrl, explicitApiKey: configuredProvider?.apiKey, resolved: params.resolveProviderApiKey?.(params.provider) }); if (!isLocalBaseUrl && !showApiKey) return []; const providerConfig = { ...configuredProvider, models: configuredProvider?.models ?? [], baseUrl, api: configuredProvider?.api ?? "ollama" }; const entries = []; const modelsToProbe = models.slice(0, OLLAMA_CONFIGURED_SHOW_MAX_MODELS); for (let index = 0; index < modelsToProbe.length; index += OLLAMA_CONFIGURED_SHOW_CONCURRENCY) { const batch = modelsToProbe.slice(index, index + OLLAMA_CONFIGURED_SHOW_CONCURRENCY); const rows = await Promise.all(batch.map(async (model) => { const requested = await resolveRequestedDynamicOllamaModel({ provider: params.provider, providerConfig, modelId: model.id, showApiKey, capContextTokens: params.capContextTokens }); return requested ? { id: requested.id, name: model.name ?? requested.name, provider: requested.provider, api: model.api ?? providerConfig.api, reasoning: requested.reasoning, input: requested.input, contextWindow: requested.contextWindow, contextTokens: requested.contextTokens, compat: requested.compat } : void 0; })); for (const row of rows) if (row) entries.push(row); } return entries; } const createOllamaSharedProviderHooks = (api) => ({ ...buildProviderToolCompatFamilyHooks("llamacpp-gbnf"), createStreamFn: ({ config, model, provider }) => { if (model.api !== "ollama") return; const { acquireLocalService } = api.runtime.llm; const configuredProviderId = findNormalizedProviderKey(config?.models?.providers, provider) ?? provider; return createLazyConfiguredOllamaStreamFn({ model, localService: { providerId: configuredProviderId, acquire: acquireLocalService }, providerBaseUrl: readProviderBaseUrl(resolveConfiguredOllamaProviderConfig({ config, providerId: configuredProviderId })) ?? (provider === "ollama-cloud" ? "https://ollama.com" : void 0) }); }, buildReplayPolicy: ({ modelApi }) => modelApi === "ollama" ? buildNativeOllamaReplayPolicy() : buildOpenAICompatibleReplayPolicy(modelApi), resolveReasoningOutputMode: () => "native", resolveThinkingProfile, wrapStreamFn: createConfiguredOllamaCompatStreamWrapper, matchesContextOverflowError: ({ errorMessage }) => matchesOllamaContextOverflowError(errorMessage), classifyFailoverReason: ({ errorMessage }) => classifyOllamaFailoverReason(errorMessage) }); var ollama_default = definePluginEntry({ id: "ollama", name: "Ollama Provider", description: "Bundled Ollama provider plugin", register(api) { const startupPluginConfig = api.pluginConfig ?? {}; const providerHooks = createOllamaSharedProviderHooks(api); if (api.registrationMode === "full") checkWsl2CrashLoopRiskLazily(api); api.registerEmbeddingProvider(lazyOllamaMemoryEmbeddingProviderAdapter); api.registerMediaUnderstandingProvider(ollamaMediaUnderstandingProvider); if (startupPluginConfig.nodeInference?.enabled !== false) for (const command of createLazyOllamaNodeHostCommands()) api.registerNodeHostCommand(command); api.registerNodeInvokePolicy(createOllamaNodeInvokePolicy()); api.registerTool(createLazyOllamaNodeInferenceTool(api)); const resolveCurrentPluginConfig = (config) => { const runtimePluginConfig = resolvePluginConfigObject(config, "ollama"); if (runtimePluginConfig) return runtimePluginConfig; return config ? {} : startupPluginConfig; }; api.registerWebSearchProvider(createLazyOllamaWebSearchProvider()); api.registerProvider({ id: OLLAMA_CLOUD_PROVIDER_ID, label: "Ollama Cloud", docsPath: "/providers/ollama", envVars: ["OLLAMA_API_KEY"], auth: [createProviderApiKeyAuthMethod({ providerId: OLLAMA_CLOUD_PROVIDER_ID, methodId: "api-key", label: "Ollama Cloud API key", hint: "Hosted models via ollama.com", optionKey: "ollamaCloudApiKey", flagName: "--ollama-cloud-api-key", envVar: "OLLAMA_API_KEY", promptMessage: "Enter Ollama Cloud API key", defaultModel: OLLAMA_CLOUD_DEFAULT_MODEL_REF, noteTitle: "Ollama Cloud", noteMessage: "Manage API keys at https://ollama.com/settings/keys", wizard: { choiceId: "ollama-cloud", choiceLabel: "Ollama Cloud", choiceHint: "Hosted models via ollama.com", groupId: "ollama", groupLabel: "Ollama", groupHint: "Cloud and local open models" } })], catalog: { order: "simple", run: async (ctx) => { const resolvedAuth = ctx.resolveProviderApiKey(OLLAMA_CLOUD_PROVIDER_ID); const apiKey = resolvedAuth.apiKey ?? resolvedAuth.discoveryApiKey; if (!apiKey) return null; return { provider: { ...await buildOllamaCloudProvider(readUsableOllamaShowApiKey({ env: ctx.env, allowAmbientEnvFallback: true, resolved: resolvedAuth })), apiKey } }; } }, staticCatalog: { order: "simple", run: async () => ({ provider: buildStaticOllamaCloudProvider() }) }, ...providerHooks, resolveDynamicModel: ({ provider, modelId }) => { const cloudProvider = buildStaticOllamaCloudProvider(); const model = cloudProvider.models?.find((entry) => entry.id === modelId); return model ? toDynamicOllamaModel({ provider, providerConfig: cloudProvider, model }) : void 0; }, augmentModelCatalog: async (ctx) => await augmentConfiguredOllamaCatalogModels({ config: ctx.config, defaultBaseUrl: OLLAMA_CLOUD_BASE_URL, env: ctx.env, provider: OLLAMA_CLOUD_PROVIDER_ID, entries: ctx.entries, resolveProviderApiKey: ctx.resolveProviderApiKey }), buildUnknownModelHint: () => "Ollama Cloud requires an API key. Set OLLAMA_API_KEY or run \"openclaw onboard --auth-choice ollama-cloud\". See: https://docs.openclaw.ai/providers/ollama" }); api.registerProvider({ id: OLLAMA_PROVIDER_ID, label: "Ollama", docsPath: "/providers/ollama", envVars: ["OLLAMA_API_KEY"], auth: [{ id: "local", label: "Ollama", hint: "Connect to an Ollama server and select a cloud or local model", kind: "custom", appGuidedSetup: { detectAvailability: detectAppGuidedOllamaAvailability, detect: async (ctx) => { const discovered = await discoverAppGuidedOllamaModel(ctx); if (!discovered) return null; return { modelRef: `${OLLAMA_PROVIDER_ID}/${discovered.model.id}`, detail: `${discovered.model.id} at ${discovered.provider.baseUrl}` }; }, prepare: async (ctx) => { const discovered = await discoverAppGuidedOllamaModel(ctx, { modelRef: ctx.modelRef }); const prefix = `${OLLAMA_PROVIDER_ID}/`; if (!discovered || !ctx.modelRef.startsWith(prefix)) return null; if (ctx.modelRef.slice(prefix.length) !== discovered.model.id) return null; return { profiles: [], defaultModel: ctx.modelRef, configPatch: { models: { mode: ctx.config.models?.mode ?? "merge", providers: { [OLLAMA_PROVIDER_ID]: { ...discovered.existing, ...discovered.provider, ...discovered.ownerValue ? { apiKey: discovered.ownerValue } : {}, models: discovered.provider.models } } } } }; } }, run: async (ctx) => { const { promptAndConfigureOllama } = await loadOllamaSetup(); const result = await promptAndConfigureOllama({ cfg: ctx.config, env: ctx.env, workspaceDir: ctx.workspaceDir, opts: ctx.opts, prompter: ctx.prompter, ...ctx.signal ? { signal: ctx.signal } : {}, secretInputMode: ctx.secretInputMode, allowSecretRefPrompt: ctx.allowSecretRefPrompt }); return { profiles: result.credential ? [{ profileId: "ollama:default", credential: buildApiKeyCredential(OLLAMA_PROVIDER_ID, result.credential, void 0, result.credentialMode ? { secretInputMode: result.credentialMode, config: ctx.config } : void 0) }] : [], configPatch: result.config, ...result.defaultModel ? { defaultModel: result.defaultModel } : {} }; }, validateNonInteractive: async (ctx) => await (await loadOllamaSetup()).validateOllamaNonInteractive(ctx), runNonInteractive: async (ctx) => { const { configureOllamaNonInteractive } = await loadOllamaSetup(); return await configureOllamaNonInteractive({ nextConfig: ctx.config, opts: { customBaseUrl: ctx.opts.customBaseUrl, customModelId: ctx.opts.customModelId }, runtime: ctx.runtime, agentDir: ctx.agentDir }); } }], catalog: { order: "late", run: async (ctx) => await resolveOllamaDiscoveryResult({ ctx, pluginConfig: resolveCurrentPluginConfig(ctx.config), buildProvider: buildLocalOllamaProvider }) }, wizard: { setup: { choiceId: "ollama", choiceLabel: "Ollama", choiceHint: "Connect to an Ollama server and select a cloud or local model", groupId: "ollama", groupLabel: "Ollama", groupHint: "Cloud and local open models", methodId: "local", modelSelection: { promptWhenAuthChoiceProvided: true, allowKeepCurrent: false } }, modelPicker: { label: "Ollama (custom)", hint: "Detect models from a local or remote Ollama instance", methodId: "local" } }, onModelSelected: async ({ config, model, prompter }) => { if (!model.startsWith("ollama/")) return; const { ensureOllamaModelPulled } = await loadOllamaSetup(); await ensureOllamaModelPulled({ config, model, prompter }); }, ...providerHooks, augmentModelCatalog: async (ctx) => await augmentConfiguredOllamaCatalogModels({ config: ctx.config, defaultBaseUrl: OLLAMA_DEFAULT_BASE_URL, env: ctx.env, provider: OLLAMA_PROVIDER_ID, entries: ctx.entries, resolveProviderApiKey: ctx.resolveProviderApiKey, capContextTokens: true }), resolveSyntheticAuth: ({ provider, providerConfig }) => { if (!shouldUseSyntheticOllamaAuth(providerConfig)) return; return { apiKey: OLLAMA_DEFAULT_API_KEY, source: `models.providers.${provider ?? "ollama"} (synthetic local key)`, mode: "api-key" }; }, shouldDeferSyntheticProfileAuth: ({ resolvedApiKey }) => resolvedApiKey?.trim() === OLLAMA_DEFAULT_API_KEY, prepareDynamicModel: async (ctx) => { const providerConfig = resolveConfiguredOllamaProviderConfig({ config: ctx.config, providerId: ctx.provider }); if (!hasOllamaDiscoverySignal(providerConfig)) return; const baseUrl = readProviderBaseUrl(providerConfig); let discoveryApiKey; if (providerConfig?.apiKey !== void 0 && providerConfig.apiKey !== null) { const resolved = await resolveConfiguredSecretInputString({ config: ctx.config ?? {}, env: process.env, value: providerConfig.apiKey, path: `models.providers.${ctx.provider}.apiKey`, unresolvedReasonStyle: "detailed" }); if (resolved.unresolvedRefReason) return; const resolvedApiKey = readConfiguredOllamaApiKey(resolved.value); const configuredSecretRef = coerceSecretRef(providerConfig.apiKey); discoveryApiKey = configuredSecretRef ? resolvedApiKey : resolvedApiKey === "OLLAMA_API_KEY" ? readConcreteOllamaApiKey(process.env.OLLAMA_API_KEY) : readConcreteOllamaApiKey(resolvedApiKey); if (configuredSecretRef && !discoveryApiKey) return; } else if (!isLocalOllamaBaseUrl(baseUrl)) discoveryApiKey = readConcreteOllamaApiKey(process.env.OLLAMA_API_KEY); const provider = await buildLocalOllamaProvider(baseUrl, { quiet: true, ...discoveryApiKey ? { apiKey: discoveryApiKey } : {} }); const dynamicApi = providerConfig?.api ?? provider.api; const dynamicProvider = { ...provider, baseUrl: resolveOllamaRuntimeBaseUrl({ api: dynamicApi, configuredBaseUrl: baseUrl, discoveredBaseUrl: provider.baseUrl }), api: dynamicApi }; const discoveredModel = dynamicProvider.models?.find((model) => model.id === ctx.modelId); if (discoveredModel) return toDynamicOllamaModel({ provider: ctx.provider, providerConfig: dynamicProvider, model: discoveredModel }); return await resolveRequestedDynamicOllamaModel({ provider: ctx.provider, providerConfig: dynamicProvider, modelId: ctx.modelId, showApiKey: discoveryApiKey, capContextTokens: true }); }, buildUnknownModelHint: () => "Ollama requires authentication to be registered as a provider. Set OLLAMA_API_KEY=\"ollama-local\" (any value works) or run \"openclaw configure\". See: https://docs.openclaw.ai/providers/ollama" }); } }); //#endregion export { ollama_default as default };