UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

207 lines (206 loc) 8.04 kB
import { r as fetchWithSsrFGuard } from "./fetch-guard-BttkNCLm.js"; import "./ssrf-runtime-BOGN5pUi.js"; import { i as OLLAMA_DEFAULT_BASE_URL, o as OLLAMA_DEFAULT_COST, s as OLLAMA_DEFAULT_MAX_TOKENS } from "./defaults-CPlCYE-I.js"; //#region extensions/ollama/src/provider-models.ts const OLLAMA_SHOW_CONCURRENCY = 8; const OLLAMA_CONTEXT_ENRICH_LIMIT = 200; const MAX_OLLAMA_SHOW_CACHE_ENTRIES = 256; const ollamaModelShowInfoCache = /* @__PURE__ */ new Map(); const OLLAMA_ALWAYS_BLOCKED_HOSTNAMES = new Set(["metadata.google.internal"]); function buildOllamaBaseUrlSsrFPolicy(baseUrl) { const trimmed = baseUrl.trim(); if (!trimmed) return; try { const parsed = new URL(trimmed); if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return; if (OLLAMA_ALWAYS_BLOCKED_HOSTNAMES.has(parsed.hostname)) return; return { hostnameAllowlist: [parsed.hostname], allowPrivateNetwork: true }; } catch { return; } } function resolveOllamaApiBase(configuredBaseUrl) { if (!configuredBaseUrl) return OLLAMA_DEFAULT_BASE_URL; return configuredBaseUrl.replace(/\/+$/, "").replace(/\/v1$/i, ""); } function buildOllamaModelShowCacheKey(apiBase, model) { const version = model.digest?.trim() || model.modified_at?.trim(); if (!version) return; return `${resolveOllamaApiBase(apiBase)}|${model.name}|${version}`; } function setOllamaModelShowCacheEntry(key, value) { if (ollamaModelShowInfoCache.size >= MAX_OLLAMA_SHOW_CACHE_ENTRIES) { const oldestKey = ollamaModelShowInfoCache.keys().next().value; if (typeof oldestKey === "string") ollamaModelShowInfoCache.delete(oldestKey); } ollamaModelShowInfoCache.set(key, value); } function hasCachedOllamaModelShowInfo(info) { return typeof info.contextWindow === "number" || (info.capabilities?.length ?? 0) > 0; } function parseOllamaNumCtxParameter(parameters) { if (typeof parameters !== "string" || !parameters.trim()) return; let lastValue; for (const rawLine of parameters.split(/\r?\n/)) { const match = rawLine.trim().match(/^num_ctx\s+(-?\d+)\b/); if (!match) continue; const parsed = Number.parseInt(match[1], 10); if (Number.isFinite(parsed) && parsed > 0) lastValue = parsed; } return lastValue; } async function queryOllamaModelShowInfo(apiBase, modelName, opts) { const normalizedApiBase = resolveOllamaApiBase(apiBase); try { const headers = { "Content-Type": "application/json" }; if (opts?.apiKey) headers.Authorization = `Bearer ${opts.apiKey}`; const { response, release } = await fetchWithSsrFGuard({ url: `${normalizedApiBase}/api/show`, init: { method: "POST", headers, body: JSON.stringify({ name: modelName }), signal: AbortSignal.timeout(3e3) }, policy: buildOllamaBaseUrlSsrFPolicy(normalizedApiBase), auditContext: "ollama-provider-models.show" }); try { if (!response.ok) return {}; const data = await response.json(); let contextWindow; if (data.model_info) { for (const [key, value] of Object.entries(data.model_info)) if (key.endsWith(".context_length") && typeof value === "number" && Number.isFinite(value)) { const ctx = Math.floor(value); if (ctx > 0) { contextWindow = ctx; break; } } } const paramCtx = parseOllamaNumCtxParameter(data.parameters); if (paramCtx !== void 0 && (contextWindow === void 0 || paramCtx > contextWindow)) contextWindow = paramCtx; const capabilities = Array.isArray(data.capabilities) ? data.capabilities.filter((c) => typeof c === "string") : void 0; return { contextWindow, capabilities }; } finally { await release(); } } catch { return {}; } } async function queryOllamaModelShowInfoCached(apiBase, model) { const normalizedApiBase = resolveOllamaApiBase(apiBase); const cacheKey = buildOllamaModelShowCacheKey(normalizedApiBase, model); if (!cacheKey) return await queryOllamaModelShowInfo(normalizedApiBase, model.name); const cached = ollamaModelShowInfoCache.get(cacheKey); if (cached) return await cached; const pending = queryOllamaModelShowInfo(normalizedApiBase, model.name).then((result) => { if (!hasCachedOllamaModelShowInfo(result)) ollamaModelShowInfoCache.delete(cacheKey); return result; }); setOllamaModelShowCacheEntry(cacheKey, pending); return await pending; } /** @deprecated Use queryOllamaModelShowInfo instead. */ async function queryOllamaContextWindow(apiBase, modelName) { return (await queryOllamaModelShowInfo(apiBase, modelName)).contextWindow; } async function enrichOllamaModelsWithContext(apiBase, models, opts) { const concurrency = Math.max(1, Math.floor(opts?.concurrency ?? OLLAMA_SHOW_CONCURRENCY)); const enriched = []; for (let index = 0; index < models.length; index += concurrency) { const batch = models.slice(index, index + concurrency); const batchResults = await Promise.all(batch.map(async (model) => { const showInfo = await queryOllamaModelShowInfoCached(apiBase, model); return Object.assign({}, model, { contextWindow: showInfo.contextWindow, capabilities: showInfo.capabilities }); })); enriched.push(...batchResults); } return enriched; } function isReasoningModelHeuristic(modelId) { return /r1|reasoning|think|reason/i.test(modelId); } function isKnownOllamaCloudReasoningModel(modelId) { const normalized = modelId.trim().toLowerCase(); return /^deepseek-v4-(?:flash|pro):cloud$/.test(normalized); } function buildOllamaModelDefinition(modelId, contextWindow, capabilities) { const input = capabilities?.includes("vision") ?? false ? ["text", "image"] : ["text"]; const reasoning = isKnownOllamaCloudReasoningModel(modelId) || (capabilities === void 0 ? isReasoningModelHeuristic(modelId) : capabilities.includes("thinking")); const compat = capabilities === void 0 ? { supportsTools: true, supportsUsageInStreaming: true } : { supportsTools: capabilities.includes("tools"), supportsUsageInStreaming: true }; return { id: modelId, name: modelId, reasoning, input, cost: OLLAMA_DEFAULT_COST, contextWindow: contextWindow ?? 128e3, maxTokens: OLLAMA_DEFAULT_MAX_TOKENS, compat }; } async function fetchOllamaModels(baseUrl) { try { const apiBase = resolveOllamaApiBase(baseUrl); const { response, release } = await fetchWithSsrFGuard({ url: `${apiBase}/api/tags`, init: { signal: AbortSignal.timeout(5e3) }, policy: buildOllamaBaseUrlSsrFPolicy(apiBase), auditContext: "ollama-provider-models.tags" }); try { if (!response.ok) return { reachable: true, models: [] }; return { reachable: true, models: ((await response.json()).models ?? []).filter((m) => m.name) }; } finally { await release(); } } catch { return { reachable: false, models: [] }; } } async function buildOllamaProvider(configuredBaseUrl, opts) { const apiBase = resolveOllamaApiBase(configuredBaseUrl); const { reachable, models } = await fetchOllamaModels(apiBase); if (!reachable && !opts?.quiet) console.warn(`Ollama could not be reached at ${apiBase}.`); return { baseUrl: apiBase, api: "ollama", models: (await enrichOllamaModelsWithContext(apiBase, models.slice(0, OLLAMA_CONTEXT_ENRICH_LIMIT))).map((model) => buildOllamaModelDefinition(model.name, model.contextWindow, model.capabilities)) }; } //#endregion //#region extensions/ollama/src/provider-base-url.ts function readProviderBaseUrl(provider) { if (!provider) return; if (Object.hasOwn(provider, "baseUrl") && typeof provider.baseUrl === "string" && provider.baseUrl.trim()) return provider.baseUrl.trim(); const alternate = provider; if (Object.hasOwn(alternate, "baseURL") && typeof alternate.baseURL === "string" && alternate.baseURL.trim()) return alternate.baseURL.trim(); } //#endregion export { enrichOllamaModelsWithContext as a, queryOllamaContextWindow as c, buildOllamaProvider as i, queryOllamaModelShowInfo as l, buildOllamaBaseUrlSsrFPolicy as n, fetchOllamaModels as o, buildOllamaModelDefinition as r, isReasoningModelHeuristic as s, readProviderBaseUrl as t, resolveOllamaApiBase as u };