openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
316 lines (315 loc) • 13.6 kB
JavaScript
import { l as hasConfiguredSecretInput, m as normalizeResolvedSecretInputString, s as coerceSecretRef } from "./types.secrets-kC0nOetj.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { t as resolveConfiguredSecretInputString } from "./resolve-configured-secret-input-string-B8zlpQOU.js";
import { m as readProviderJsonResponse, v as readProviderResponseErrorText } from "./provider-http-errors-U-nhuk_f.js";
import { b as ssrfPolicyFromHttpBaseUrlAllowedOrigin } from "./ssrf-0QyXWOVG.js";
import { r as fetchConfiguredLocalOriginWithSsrFGuard } from "./fetch-guard-BMdGQhbb.js";
import { t as normalizeOptionalSecretInput } from "./normalize-secret-input-Df_qhWv_.js";
import { c as isNonSecretApiKeyMarker, s as isKnownEnvApiKeyMarker } from "./model-auth-markers-jBKQn38x.js";
import { t as resolveEnvApiKey } from "./model-auth-env-Dq9W4xg9.js";
import "./provider-auth-BeZ7NZUU.js";
import { d as normalizeProviderId } from "./provider-model-shared-fX-Ywqb3.js";
import "./ssrf-runtime-Bum5C6NN.js";
import "./ssrf-runtime-internal-jBnbJqBP.js";
import "./provider-auth-runtime-BqyR7mkf.js";
import "./provider-http-k9RMI7iG.js";
import "./secret-input-runtime-Clavxxg-.js";
import { n as OLLAMA_CLOUD_BASE_URL, t as DEFAULT_OLLAMA_EMBEDDING_MODEL } from "./defaults-BiE2_Zq0.js";
import { t as readProviderBaseUrl } from "./provider-base-url-E6aWTKii.js";
import { y as resolveOllamaApiBase } from "./provider-models-BK60TdeT.js";
import { t as normalizeOllamaWireModelId } from "./model-id-rul1xM90.js";
//#region extensions/ollama/src/embedding-provider.runtime.ts
const OLLAMA_EMBED_ERROR_BODY_LIMIT_BYTES = 8192;
const QUERY_INSTRUCTION_TEMPLATES = [
{
prefix: "qwen3-embedding",
template: "Instruct: Given a user query, retrieve relevant memory notes and documents\nQuery:{query}"
},
{
prefix: "nomic-embed-text",
template: "search_query: {query}"
},
{
prefix: "mxbai-embed-large",
template: "Represent this sentence for searching relevant passages: {query}"
}
];
function sanitizeAndNormalizeEmbedding(vec, outputDimensionality) {
const sanitized = (typeof outputDimensionality === "number" ? vec.slice(0, outputDimensionality) : vec).map((value) => {
if (typeof value !== "number") throw new Error("Ollama embed response contains a non-number embedding value");
return Number.isFinite(value) ? value : 0;
});
const magnitude = Math.sqrt(sanitized.reduce((sum, value) => sum + value * value, 0));
if (magnitude < 1e-10) return sanitized;
return sanitized.map((value) => value / magnitude);
}
async function withRemoteHttpResponse(params) {
const { response, release } = await fetchConfiguredLocalOriginWithSsrFGuard({
url: params.url,
init: params.init,
signal: params.signal,
policy: params.ssrfPolicy,
configuredLocalOriginBaseUrl: params.configuredLocalOriginBaseUrl,
auditContext: "ollama-memory-embedding"
});
try {
return await params.onResponse(response);
} finally {
await release();
}
}
async function readOllamaEmbeddingJsonResponse(response) {
const payload = await readProviderJsonResponse(response, "Ollama embed response");
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) throw new Error("Ollama embed response returned a non-object JSON payload");
return payload;
}
function normalizeEmbeddingModel(model, providerId) {
const trimmed = model.trim();
if (!trimmed) return DEFAULT_OLLAMA_EMBEDDING_MODEL;
return normalizeOllamaWireModelId(trimmed, providerId);
}
function applyQueryInstructionTemplate(model, queryText) {
const normalizedModel = model.trim().toLowerCase().replace(/^.*\//, "");
const match = QUERY_INSTRUCTION_TEMPLATES.find(({ prefix }) => normalizedModel.startsWith(prefix));
return match ? match.template.replace("{query}", () => queryText) : queryText;
}
function resolveConfiguredProvider(options) {
const providers = options.config.models?.providers;
if (!providers) return;
const providerId = options.provider?.trim() || "ollama";
const direct = providers[providerId];
if (direct) return {
providerId,
config: direct
};
const normalized = normalizeProviderId(providerId);
for (const [candidateId, candidate] of Object.entries(providers)) if (normalizeProviderId(candidateId) === normalized) return {
providerId: candidateId,
config: candidate
};
const fallback = providers.ollama;
return fallback ? {
providerId: "ollama",
config: fallback
} : void 0;
}
function resolveMemorySecretInputString(params) {
if (!hasConfiguredSecretInput(params.value)) return;
return normalizeResolvedSecretInputString({
value: params.value,
path: params.path
});
}
function resolveSourcedOllamaEmbeddingKey(params) {
if (params.configString !== void 0) {
if (params.resolvedSecretRef || !isNonSecretApiKeyMarker(params.configString)) return { apiKey: params.configString };
if (!isKnownEnvApiKeyMarker(params.configString)) return "opt-out";
const envKey = resolveEnvApiKey("ollama")?.apiKey;
return envKey && !isNonSecretApiKeyMarker(envKey) ? { apiKey: envKey } : "opt-out";
}
return params.declared ? "opt-out" : "unset";
}
async function resolveConfiguredOllamaEmbeddingSecret(params) {
if (!coerceSecretRef(params.value, params.config.secrets?.defaults)) return normalizeOptionalSecretInput(params.value);
const resolved = await resolveConfiguredSecretInputString({
config: params.config,
env: process.env,
value: params.value,
path: params.path,
unresolvedReasonStyle: "detailed"
});
if (resolved.unresolvedRefReason) throw new Error(resolved.unresolvedRefReason);
return normalizeOptionalSecretInput(resolved.value);
}
async function resolveOllamaEmbeddingResolvedKeys(options, providerConfig, providerOwnsHost) {
const remoteValue = options.remote?.apiKey;
const remote = resolveSourcedOllamaEmbeddingKey({
configString: resolveMemorySecretInputString({
value: remoteValue,
path: "memory.search.remote.apiKey"
}),
declared: hasConfiguredSecretInput(remoteValue)
});
const providerValue = providerConfig?.config.apiKey;
let provider = "unset";
if (remote === "unset" && providerOwnsHost && providerConfig) provider = resolveSourcedOllamaEmbeddingKey({
configString: await resolveConfiguredOllamaEmbeddingSecret({
config: options.config,
value: providerValue,
path: `models.providers.${providerConfig.providerId}.apiKey`
}),
declared: hasConfiguredSecretInput(providerValue),
resolvedSecretRef: Boolean(coerceSecretRef(providerValue, options.config.secrets?.defaults))
});
const envKey = resolveEnvApiKey("ollama")?.apiKey;
const env = envKey && !isNonSecretApiKeyMarker(envKey) ? envKey : void 0;
return {
remote,
provider,
env
};
}
function resolveOllamaEmbeddingBaseUrl(params) {
const remoteBaseUrl = params.remoteBaseUrl?.trim();
if (remoteBaseUrl) return {
baseUrl: resolveOllamaApiBase(remoteBaseUrl),
origin: "remote-config"
};
const providerBaseUrl = readProviderBaseUrl(params.providerConfig?.config);
if (providerBaseUrl) return {
baseUrl: resolveOllamaApiBase(providerBaseUrl),
origin: "provider-config"
};
return {
baseUrl: resolveOllamaApiBase(void 0),
origin: "default"
};
}
function normalizeOllamaHostKey(baseUrl) {
try {
const parsed = new URL(baseUrl);
let hostname = parsed.hostname.toLowerCase();
if (hostname === "localhost" || hostname === "::1" || hostname === "[::1]") hostname = "127.0.0.1";
const port = parsed.port || (parsed.protocol === "https:" ? "443" : "80");
const path = parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/$/, "");
return `${parsed.protocol}//${hostname}:${port}${path}`;
} catch {
return;
}
}
function areOllamaHostsEquivalent(a, b) {
const aKey = normalizeOllamaHostKey(a);
const bKey = normalizeOllamaHostKey(b);
return aKey !== void 0 && bKey !== void 0 && aKey === bKey;
}
function isOllamaCloudBaseUrl(baseUrl) {
return areOllamaHostsEquivalent(baseUrl, OLLAMA_CLOUD_BASE_URL);
}
function selectOllamaEmbeddingApiKey(params) {
if (params.resolved.remote !== "unset") return typeof params.resolved.remote === "object" ? params.resolved.remote.apiKey : void 0;
if (params.resolved.provider !== "unset" && params.providerOwnsHost) return typeof params.resolved.provider === "object" ? params.resolved.provider.apiKey : void 0;
if (params.resolved.env && isOllamaCloudBaseUrl(params.baseUrl)) return params.resolved.env;
}
async function resolveOllamaEmbeddingClient(options) {
const providerConfig = resolveConfiguredProvider(options);
const { baseUrl, origin: baseUrlOrigin } = resolveOllamaEmbeddingBaseUrl({
remoteBaseUrl: options.remote?.baseUrl,
providerConfig
});
const model = normalizeEmbeddingModel(options.model, options.provider);
const providerOwnedHost = resolveOllamaApiBase(readProviderBaseUrl(providerConfig?.config));
const providerOwnsHost = baseUrlOrigin !== "remote-config" || areOllamaHostsEquivalent(baseUrl, providerOwnedHost);
const remoteHeaderNames = new Set(Object.keys(options.remote?.headers ?? {}).map((headerName) => headerName.toLowerCase()));
const headerOverrides = {};
if (providerOwnsHost && providerConfig?.config.headers) for (const [headerName, headerValue] of Object.entries(providerConfig.config.headers)) {
if (remoteHeaderNames.has(headerName.toLowerCase())) continue;
const resolvedValue = await resolveConfiguredOllamaEmbeddingSecret({
config: options.config,
value: headerValue,
path: `models.providers.${providerConfig.providerId}.headers.${headerName}`
});
if (resolvedValue) headerOverrides[headerName] = resolvedValue;
}
Object.assign(headerOverrides, options.remote?.headers);
const headers = {
"Content-Type": "application/json",
...headerOverrides
};
const apiKey = Object.entries(headers).some(([name, value]) => name.toLowerCase() === "authorization" && value.trim().length > 0) ? void 0 : selectOllamaEmbeddingApiKey({
resolved: await resolveOllamaEmbeddingResolvedKeys(options, providerConfig, providerOwnsHost),
baseUrl,
providerOwnsHost
});
if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
const localService = providerConfig?.config.localService;
return {
baseUrl,
headers,
ssrfPolicy: ssrfPolicyFromHttpBaseUrlAllowedOrigin(baseUrl),
model,
outputDimensionality: options.dimensions,
...localService && baseUrlOrigin !== "remote-config" ? {
localServiceTarget: {
providerId: providerConfig.providerId,
baseUrl: `${baseUrl.replace(/\/+$/, "")}/v1`,
headers
},
acquireLocalService: options.acquireLocalService
} : {}
};
}
async function createOllamaEmbeddingProvider(options) {
const client = await resolveOllamaEmbeddingClient(options);
const embedUrl = `${client.baseUrl.replace(/\/$/, "")}/api/embed`;
const embedMany = async (input, signal) => {
const localServiceLease = client.localServiceTarget && client.acquireLocalService ? await client.acquireLocalService(client.localServiceTarget, signal) : void 0;
let json;
try {
json = await withRemoteHttpResponse({
url: embedUrl,
ssrfPolicy: client.ssrfPolicy,
configuredLocalOriginBaseUrl: client.baseUrl,
signal,
init: {
method: "POST",
headers: client.headers,
body: JSON.stringify({
model: client.model,
input
})
},
onResponse: async (response) => {
if (!response.ok) {
const detail = await readProviderResponseErrorText(response, OLLAMA_EMBED_ERROR_BODY_LIMIT_BYTES, client.headers).catch(() => "unknown error");
throw new Error(`Ollama embed HTTP ${response.status}: ${detail}`);
}
return await readOllamaEmbeddingJsonResponse(response);
}
});
} finally {
localServiceLease?.release();
}
if (!Array.isArray(json.embeddings)) throw new Error("Ollama embed response missing embeddings[]");
const expectedCount = Array.isArray(input) ? input.length : 1;
if (json.embeddings.length !== expectedCount) throw new Error(`Ollama embed response returned ${json.embeddings.length} embeddings for ${expectedCount} inputs`);
return json.embeddings.map((embedding) => {
if (!Array.isArray(embedding)) throw new Error("Ollama embed response contains a non-array embedding");
return sanitizeAndNormalizeEmbedding(embedding, client.outputDimensionality);
});
};
const embedOne = async (text, signal) => {
const [embedding] = await embedMany(text, signal);
if (!embedding) throw new Error("Ollama embed response returned no embedding");
return embedding;
};
const embedQuery = async (text, optionsValue) => await embedOne(applyQueryInstructionTemplate(client.model, text), optionsValue?.signal);
const provider = {
id: "ollama",
model: client.model,
embed: async (input, optionsValue) => {
const text = typeof input === "string" ? input : input.text;
return optionsValue?.inputType === "query" ? await embedQuery(text, optionsValue) : (await embedMany([text], optionsValue?.signal))[0] ?? [];
},
embedBatch: async (inputs, optionsLocal) => {
const texts = inputs.map((input) => typeof input === "string" ? input : input.text);
if (texts.length === 0) return [];
if (optionsLocal?.inputType === "query") return await Promise.all(texts.map((text) => embedQuery(text, optionsLocal)));
return await embedMany(texts, optionsLocal?.signal);
}
};
return {
provider,
client: {
...client,
embedBatch: async (texts) => {
try {
return await provider.embedBatch(texts, { inputType: "document" });
} catch (err) {
throw new Error(formatErrorMessage(err), { cause: err });
}
}
}
};
}
//#endregion
export { DEFAULT_OLLAMA_EMBEDDING_MODEL, createOllamaEmbeddingProvider };