UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

234 lines (233 loc) 9.22 kB
import { l as normalizeStringEntries } from "./string-normalization-WNUDCpXX.js"; import { o as createProviderHttpError } from "./provider-http-errors-DqaqQLLZ.js"; import "./string-coerce-runtime-CEGJWkQ_.js"; import "./provider-http-BXCBCi4E.js"; import { r as sanitizeAndNormalizeEmbedding } from "./embeddings-C9SBNXgr.js"; import { C as withRemoteHttpResponse, _ as buildBatchHeaders, g as runEmbeddingBatchGroups, h as buildEmbeddingBatchGroupOptions, o as debugEmbeddingsLog, v as normalizeBatchBaseUrl } from "./memory-core-host-engine-embeddings-nTgmnTkX.js"; import crypto from "node:crypto"; //#region extensions/google/embedding-batch.ts const GEMINI_BATCH_MAX_REQUESTS = 5e4; function hashText(text) { return crypto.createHash("sha256").update(text).digest("hex"); } function getGeminiUploadUrl(baseUrl) { if (baseUrl.includes("/v1beta")) return baseUrl.replace(/\/v1beta\/?$/, "/upload/v1beta"); return `${baseUrl.replace(/\/$/, "")}/upload`; } function buildGeminiUploadBody(params) { const boundary = `openclaw-${hashText(params.displayName)}`; const jsonPart = JSON.stringify({ file: { displayName: params.displayName, mimeType: "application/jsonl" } }); const delimiter = `--${boundary}\r\n`; const closeDelimiter = `--${boundary}--\r\n`; const parts = [ `${delimiter}Content-Type: application/json; charset=UTF-8\r\n\r\n${jsonPart}\r\n`, `${delimiter}Content-Type: application/jsonl; charset=UTF-8\r\n\r\n${params.jsonl}\r\n`, closeDelimiter ]; return { body: new Blob([parts.join("")], { type: "multipart/related" }), contentType: `multipart/related; boundary=${boundary}` }; } async function submitGeminiBatch(params) { const baseUrl = normalizeBatchBaseUrl(params.gemini); const uploadPayload = buildGeminiUploadBody({ jsonl: params.requests.map((request) => JSON.stringify({ key: request.custom_id, request: request.request })).join("\n"), displayName: `memory-embeddings-${hashText(String(Date.now()))}` }); const uploadUrl = `${getGeminiUploadUrl(baseUrl)}/files?uploadType=multipart`; debugEmbeddingsLog("memory embeddings: gemini batch upload", { uploadUrl, baseUrl, requests: params.requests.length }); const filePayload = await withRemoteHttpResponse({ url: uploadUrl, ssrfPolicy: params.gemini.ssrfPolicy, init: { method: "POST", headers: { ...buildBatchHeaders(params.gemini, { json: false }), "Content-Type": uploadPayload.contentType }, body: uploadPayload.body }, onResponse: async (fileRes) => { if (!fileRes.ok) { const text = await fileRes.text(); throw new Error(`gemini batch file upload failed: ${fileRes.status} ${text}`); } return await fileRes.json(); } }); const fileId = filePayload.name ?? filePayload.file?.name; if (!fileId) throw new Error("gemini batch file upload failed: missing file id"); const batchBody = { batch: { displayName: `memory-embeddings-${params.agentId}`, inputConfig: { file_name: fileId } } }; const batchEndpoint = `${baseUrl}/${params.gemini.modelPath}:asyncBatchEmbedContent`; debugEmbeddingsLog("memory embeddings: gemini batch create", { batchEndpoint, fileId }); return await withRemoteHttpResponse({ url: batchEndpoint, ssrfPolicy: params.gemini.ssrfPolicy, init: { method: "POST", headers: buildBatchHeaders(params.gemini, { json: true }), body: JSON.stringify(batchBody) }, onResponse: async (batchRes) => { if (batchRes.ok) return await batchRes.json(); const text = await batchRes.text(); if (batchRes.status === 404) throw new Error("gemini batch create failed: 404 (asyncBatchEmbedContent not available for this model/baseUrl). Disable remote.batch.enabled or switch providers."); throw new Error(`gemini batch create failed: ${batchRes.status} ${text}`); } }); } async function fetchGeminiBatchStatus(params) { const statusUrl = `${normalizeBatchBaseUrl(params.gemini)}/${params.batchName.startsWith("batches/") ? params.batchName : `batches/${params.batchName}`}`; debugEmbeddingsLog("memory embeddings: gemini batch status", { statusUrl }); return await withRemoteHttpResponse({ url: statusUrl, ssrfPolicy: params.gemini.ssrfPolicy, init: { headers: buildBatchHeaders(params.gemini, { json: true }) }, onResponse: async (res) => { if (!res.ok) throw await createProviderHttpError(res, "gemini batch status failed"); return await res.json(); } }); } async function fetchGeminiFileContent(params) { const downloadUrl = `${normalizeBatchBaseUrl(params.gemini)}/${params.fileId.startsWith("files/") ? params.fileId : `files/${params.fileId}`}:download`; debugEmbeddingsLog("memory embeddings: gemini batch download", { downloadUrl }); return await withRemoteHttpResponse({ url: downloadUrl, ssrfPolicy: params.gemini.ssrfPolicy, init: { headers: buildBatchHeaders(params.gemini, { json: true }) }, onResponse: async (res) => { if (!res.ok) throw await createProviderHttpError(res, "gemini batch file content failed"); return await res.text(); } }); } function parseGeminiBatchOutput(text) { if (!text.trim()) return []; return normalizeStringEntries(text.split("\n")).map((line) => JSON.parse(line)); } async function waitForGeminiBatch(params) { const start = Date.now(); let current = params.initial; while (true) { const status = current ?? await fetchGeminiBatchStatus({ gemini: params.gemini, batchName: params.batchName }); const state = status.state ?? "UNKNOWN"; if ([ "SUCCEEDED", "COMPLETED", "DONE" ].includes(state)) { const outputFileId = status.outputConfig?.file ?? status.outputConfig?.fileId ?? status.metadata?.output?.responsesFile; if (!outputFileId) throw new Error(`gemini batch ${params.batchName} completed without output file`); return { outputFileId }; } if ([ "FAILED", "CANCELLED", "CANCELED", "EXPIRED" ].includes(state)) { const message = status.error?.message ?? "unknown error"; throw new Error(`gemini batch ${params.batchName} ${state}: ${message}`); } if (!params.wait) throw new Error(`gemini batch ${params.batchName} still ${state}; wait disabled`); if (Date.now() - start > params.timeoutMs) throw new Error(`gemini batch ${params.batchName} timed out after ${params.timeoutMs}ms`); params.debug?.(`gemini batch ${params.batchName} ${state}; waiting ${params.pollIntervalMs}ms`); await new Promise((resolve) => { setTimeout(resolve, params.pollIntervalMs); }); current = void 0; } } async function runGeminiEmbeddingBatches(params) { return await runEmbeddingBatchGroups({ ...buildEmbeddingBatchGroupOptions(params, { maxRequests: GEMINI_BATCH_MAX_REQUESTS, debugLabel: "memory embeddings: gemini batch submit" }), runGroup: async ({ group, groupIndex, groups, byCustomId, pollIntervalMs, timeoutMs }) => { const batchInfo = await submitGeminiBatch({ gemini: params.gemini, requests: group, agentId: params.agentId }); const batchName = batchInfo.name ?? ""; if (!batchName) throw new Error("gemini batch create failed: missing batch name"); params.debug?.("memory embeddings: gemini batch created", { batchName, state: batchInfo.state, group: groupIndex + 1, groups, requests: group.length }); if (!params.wait && batchInfo.state && ![ "SUCCEEDED", "COMPLETED", "DONE" ].includes(batchInfo.state)) throw new Error(`gemini batch ${batchName} submitted; enable remote.batch.wait to await completion`); const completed = batchInfo.state && [ "SUCCEEDED", "COMPLETED", "DONE" ].includes(batchInfo.state) ? { outputFileId: batchInfo.outputConfig?.file ?? batchInfo.outputConfig?.fileId ?? batchInfo.metadata?.output?.responsesFile ?? "" } : await waitForGeminiBatch({ gemini: params.gemini, batchName, wait: params.wait, pollIntervalMs, timeoutMs, debug: params.debug, initial: batchInfo }); if (!completed.outputFileId) throw new Error(`gemini batch ${batchName} completed without output file`); const outputLines = parseGeminiBatchOutput(await fetchGeminiFileContent({ gemini: params.gemini, fileId: completed.outputFileId })); const errors = []; const remaining = new Set(group.map((request) => request.custom_id)); for (const line of outputLines) { const customId = line.key ?? line.custom_id ?? line.request_id; if (!customId) continue; remaining.delete(customId); if (line.error?.message) { errors.push(`${customId}: ${line.error.message}`); continue; } if (line.response?.error?.message) { errors.push(`${customId}: ${line.response.error.message}`); continue; } const embedding = sanitizeAndNormalizeEmbedding(line.embedding?.values ?? line.response?.embedding?.values ?? []); if (embedding.length === 0) { errors.push(`${customId}: empty embedding`); continue; } byCustomId.set(customId, embedding); } if (errors.length > 0) throw new Error(`gemini batch ${batchName} failed: ${errors.join("; ")}`); if (remaining.size > 0) throw new Error(`gemini batch ${batchName} missing ${remaining.size} embedding responses`); } }); } //#endregion export { runGeminiEmbeddingBatches as t };