UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

257 lines (256 loc) 11.7 kB
import { a as asOptionalRecord } from "./record-coerce-DItp3I4t.js"; import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js"; import { o as createProviderHttpError, p as readProviderJsonObjectResponse } from "./provider-http-errors-U-nhuk_f.js"; import { F as buildRemoteBaseUrlPolicy, I as withRemoteHttpResponse, R as sanitizeAndNormalizeEmbedding } from "./gateway-startup-plugin-config-Bq9ZdScF.js"; import { o as requireApiKey } from "./model-auth-runtime-shared-C48YoQY0.js"; import { r as providerOperationRetryConfig } from "./operation-retry-v4wKh_39.js"; import { n as executeWithApiKeyRotation, t as collectProviderApiKeysForExecution } from "./api-key-rotation-BgbmJggZ.js"; import "./string-coerce-runtime-GQa0ehRA.js"; import { a as resolveApiKeyForProvider } from "./provider-auth-runtime-BqyR7mkf.js"; import "./provider-http-k9RMI7iG.js"; import { f as embeddingProviderOwnsDestination, o as debugEmbeddingsLog, p as resolveEmbeddingEndpointUrl } from "./memory-core-host-engine-embeddings-DwuZ1cl_.js"; import { n as resolveMemorySecretInputString } from "./secret-input-B_UskQwz.js"; import "./memory-core-host-secret-Bq5o_pya.js"; import { t as parseGeminiAuth } from "./gemini-auth-BEe0_U7l.js"; import { t as resolveGoogleApiClientHeaders } from "./google-api-client-header-DHDLnetg.js"; //#region extensions/google/embedding-provider.ts const DEFAULT_GEMINI_EMBEDDING_MODEL = "gemini-embedding-001"; const DEFAULT_GOOGLE_API_BASE_URL = "https://generativelanguage.googleapis.com/v1beta"; const GEMINI_MAX_INPUT_TOKENS = { "gemini-embedding-001": 2048, "gemini-embedding-2": 8192, "gemini-embedding-2-preview": 8192 }; const GEMINI_EMBEDDING_2_MODELS = /* @__PURE__ */ new Set(["gemini-embedding-2", "gemini-embedding-2-preview"]); const GEMINI_EMBEDDING_2_DEFAULT_DIMENSIONS = 3072; const GEMINI_EMBEDDING_2_TASK_PREFIXES = { RETRIEVAL_QUERY: "task: search result | query:", RETRIEVAL_DOCUMENT: "title: none | text:", SEMANTIC_SIMILARITY: "task: sentence similarity | query:", CLASSIFICATION: "task: classification | query:", CLUSTERING: "task: clustering | query:", QUESTION_ANSWERING: "task: question answering | query:", FACT_VERIFICATION: "task: fact checking | query:" }; function malformedGeminiEmbeddingResponse() { return /* @__PURE__ */ new Error("gemini embeddings failed: malformed JSON response"); } function unexpectedGeminiEmbeddingDimensions(expected, actual) { return /* @__PURE__ */ new Error(`gemini embeddings failed: expected ${expected} dimensions, received ${actual}`); } function readGeminiEmbeddingValues(value) { if (!Array.isArray(value)) throw malformedGeminiEmbeddingResponse(); for (const entry of value) if (typeof entry !== "number" || !Number.isFinite(entry)) throw malformedGeminiEmbeddingResponse(); return value; } function readGeminiSingleEmbedding(payload) { const embedding = asOptionalRecord(payload.embedding); if (!embedding) throw malformedGeminiEmbeddingResponse(); return readGeminiEmbeddingValues(embedding.values); } function readGeminiBatchEmbeddings(payload, expectedCount) { if (!Array.isArray(payload.embeddings) || payload.embeddings.length !== expectedCount) throw malformedGeminiEmbeddingResponse(); return payload.embeddings.map((entry) => { const embedding = asOptionalRecord(entry); if (!embedding) throw malformedGeminiEmbeddingResponse(); return readGeminiEmbeddingValues(embedding.values); }); } function buildGeminiEmbeddingRequest(params) { const input = typeof params.input === "string" ? { text: params.input } : params.input; const parts = input.parts?.map((part) => part.type === "text" ? { text: part.text } : { inlineData: { mimeType: part.mimeType, data: part.data } }) ?? [{ text: input.text }]; const isStableEmbedding2 = normalizeGeminiModel(params.model) === "gemini-embedding-2"; const request = { content: { parts } }; if (isStableEmbedding2 && parts.every((part) => "text" in part)) { const first = parts[0]; if (first && "text" in first) { const taskType = params.role === "document" && (params.taskType === "RETRIEVAL_QUERY" || params.taskType === "QUESTION_ANSWERING" || params.taskType === "FACT_VERIFICATION") ? "RETRIEVAL_DOCUMENT" : params.taskType; first.text = `${GEMINI_EMBEDDING_2_TASK_PREFIXES[taskType]} ${first.text}`; } } else if (!isStableEmbedding2) request.taskType = params.taskType; if (params.modelPath) request.model = params.modelPath; if (params.outputDimensionality != null) request.outputDimensionality = params.outputDimensionality; return request; } /** Returns true for Gemini Embedding 2 variants with multimodal and extended task support. */ function isGeminiEmbedding2Model(model) { return GEMINI_EMBEDDING_2_MODELS.has(normalizeGeminiModel(model)); } function resolveGeminiOutputDimensionality(model, requested) { const isEmbedding2 = isGeminiEmbedding2Model(model); if (!isEmbedding2 && model !== "gemini-embedding-001") return; if (requested == null) return isEmbedding2 ? GEMINI_EMBEDDING_2_DEFAULT_DIMENSIONS : void 0; if (!Number.isInteger(requested) || requested < 128 || requested > 3072) throw new Error(`Invalid outputDimensionality ${requested} for ${model}. Use an integer between 128 and 3072.`); return requested; } function resolveRemoteApiKey(remoteApiKey) { return resolveMemorySecretInputString({ value: remoteApiKey, path: "memory.search.remote.apiKey" }); } function normalizeGeminiModel(model) { const trimmed = model.trim(); if (!trimmed) return DEFAULT_GEMINI_EMBEDDING_MODEL; const withoutPrefix = trimmed.replace(/^models\//, ""); if (withoutPrefix.startsWith("gemini/")) return withoutPrefix.slice(7); if (withoutPrefix.startsWith("google/")) return withoutPrefix.slice(7); return withoutPrefix; } function sanitizeGeminiEmbedding(values, expectedDimensions) { if (expectedDimensions != null && values.length !== expectedDimensions) throw unexpectedGeminiEmbeddingDimensions(expectedDimensions, values.length); return sanitizeAndNormalizeEmbedding(values); } async function fetchGeminiEmbeddingPayload(params) { return await executeWithApiKeyRotation({ provider: "google", apiKeys: params.client.apiKeys, transientRetry: providerOperationRetryConfig("read"), execute: async (apiKey) => { const headers = { ...parseGeminiAuth(apiKey).headers, ...params.client.headers }; return await withRemoteHttpResponse({ url: params.endpoint, ssrfPolicy: params.client.ssrfPolicy, signal: params.signal, init: { method: "POST", headers, body: JSON.stringify(params.body) }, onResponse: async (res) => { if (!res.ok) throw await createProviderHttpError(res, "gemini embeddings failed"); return await readProviderJsonObjectResponse(res, "gemini embeddings failed"); } }); } }); } function normalizeGeminiBaseUrl(raw) { const trimmed = raw.trim(); if (!trimmed) return DEFAULT_GOOGLE_API_BASE_URL; try { const url = new URL(trimmed); url.hash = ""; const openAiIndex = url.pathname.indexOf("/openai"); url.pathname = (openAiIndex < 0 ? url.pathname : url.pathname.slice(0, openAiIndex)).replace(/\/+$/, ""); if (url.origin.toLowerCase() === "https://generativelanguage.googleapis.com" && url.pathname === "/") url.pathname = "/v1beta"; return url.search ? url.href : url.href.replace(/\/$/, ""); } catch { return trimmed; } } function buildGeminiModelPath(model) { return model.startsWith("models/") ? model : `models/${model}`; } async function createGeminiEmbeddingProvider(options) { const client = await resolveGeminiEmbeddingClient(options); const embedUrl = resolveEmbeddingEndpointUrl(client.baseUrl, `${client.modelPath}:embedContent`); const batchUrl = resolveEmbeddingEndpointUrl(client.baseUrl, `${client.modelPath}:batchEmbedContents`); const outputDimensionality = client.outputDimensionality; const embedQuery = async (text, callOptions) => { if (!text.trim()) return []; return sanitizeGeminiEmbedding(readGeminiSingleEmbedding(await fetchGeminiEmbeddingPayload({ client, endpoint: embedUrl, body: buildGeminiEmbeddingRequest({ input: text, model: client.model, role: "query", taskType: options.taskType ?? "RETRIEVAL_QUERY", outputDimensionality }), signal: callOptions?.signal })), outputDimensionality); }; const embedDocuments = async (inputs, callOptions) => { if (inputs.length === 0) return []; return readGeminiBatchEmbeddings(await fetchGeminiEmbeddingPayload({ client, endpoint: batchUrl, body: { requests: inputs.map((input) => buildGeminiEmbeddingRequest({ input, model: client.model, role: "document", modelPath: client.modelPath, taskType: options.taskType ?? "RETRIEVAL_DOCUMENT", outputDimensionality })) }, signal: callOptions?.signal }), inputs.length).map((values) => sanitizeGeminiEmbedding(values, outputDimensionality)); }; return { provider: { id: "gemini", model: client.model, maxInputTokens: GEMINI_MAX_INPUT_TOKENS[client.model], embed: async (input, callOptions) => { if (callOptions?.inputType === "query") return await embedQuery(typeof input === "string" ? input : input.text, callOptions); return (await embedDocuments([input], callOptions))[0] ?? []; }, embedBatch: async (inputs, callOptions) => callOptions?.inputType === "query" ? await Promise.all(inputs.map((input) => embedQuery(typeof input === "string" ? input : input.text, callOptions))) : await embedDocuments(inputs, callOptions) }, client }; } async function resolveGeminiEmbeddingClient(options) { const remote = options.remote; const remoteApiKey = resolveRemoteApiKey(remote?.apiKey); const remoteBaseUrl = remote?.baseUrl?.trim(); const providerConfig = options.config.models?.providers?.google; const providerBaseUrl = normalizeGeminiBaseUrl(normalizeOptionalString(providerConfig?.baseUrl) || DEFAULT_GOOGLE_API_BASE_URL); const rawBaseUrl = remoteBaseUrl || providerBaseUrl; const baseUrl = normalizeGeminiBaseUrl(rawBaseUrl); const providerOwnsDestination = embeddingProviderOwnsDestination({ baseUrl, providerBaseUrl }); const apiKey = remoteApiKey ? remoteApiKey : providerOwnsDestination ? requireApiKey(await resolveApiKeyForProvider({ provider: "google", cfg: options.config, agentDir: options.agentDir }), "google") : void 0; if (!apiKey) throw new Error(`Google embedding credentials are not configured for ${baseUrl}. Set memory.search.remote.apiKey for this destination.`); const ssrfPolicy = buildRemoteBaseUrlPolicy(baseUrl); const headers = { ...Object.assign({}, providerOwnsDestination ? providerConfig?.headers : void 0, remote?.headers), ...resolveGoogleApiClientHeaders({ baseUrl, api: "google-generative-ai", capability: "other", transport: "http" }) }; const apiKeys = remoteApiKey ? [apiKey] : collectProviderApiKeysForExecution({ provider: "google", primaryApiKey: apiKey }); const model = normalizeGeminiModel(options.model); const modelPath = buildGeminiModelPath(model); const outputDimensionality = resolveGeminiOutputDimensionality(model, options.dimensions); debugEmbeddingsLog("memory embeddings: gemini client", { rawBaseUrl, baseUrl, model, modelPath, outputDimensionality, embedEndpoint: resolveEmbeddingEndpointUrl(baseUrl, `${modelPath}:embedContent`), batchEndpoint: resolveEmbeddingEndpointUrl(baseUrl, `${modelPath}:batchEmbedContents`) }); return { baseUrl, headers, ssrfPolicy, model, modelPath, apiKeys, outputDimensionality }; } //#endregion export { sanitizeGeminiEmbedding as a, isGeminiEmbedding2Model as i, buildGeminiEmbeddingRequest as n, createGeminiEmbeddingProvider as r, DEFAULT_GEMINI_EMBEDDING_MODEL as t };