UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

223 lines (222 loc) 8.67 kB
import { a as normalizeLowercaseStringOrEmpty, c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js"; import { i as formatErrorMessage } from "./errors-BXgSefBE.js"; import "./agent-scope-MrLta7Pq.js"; import { a as resolveAgentDir } from "./agent-scope-config-CgCYpZfK.js"; import { a as logWarn } from "./logger-lqqYRtFw.js"; import { i as getRuntimeConfig } from "./io-Gi7-pyU-.js"; import { t as getEmbeddingProvider } from "./embedding-provider-runtime-gzo2-uRP.js"; import { t as resolveMemorySearchConfig } from "./memory-search-CTXTHmj4.js"; import { t as getMemoryEmbeddingProvider } from "./memory-embedding-provider-runtime-BghnNnjb.js"; import { a as sendJson } from "./http-common-DunL6val.js"; import { a as getHeader, l as resolveOpenAiCompatibleHttpOperatorScopes } from "./http-auth-utils-Du8mRZIw.js"; import { i as resolveAgentIdFromModel, r as resolveAgentIdForRequest } from "./http-utils-leZWdpma.js"; import { t as handleGatewayPostJsonEndpoint } from "./http-endpoint-helpers-BkKm9tZp.js"; import { Buffer } from "node:buffer"; //#region src/gateway/embeddings-http.ts const DEFAULT_EMBEDDINGS_BODY_BYTES = 5 * 1024 * 1024; const MAX_EMBEDDING_INPUTS = 128; const MAX_EMBEDDING_INPUT_CHARS = 8192; const MAX_EMBEDDING_TOTAL_CHARS = 65536; const DEFAULT_MEMORY_EMBEDDING_PROVIDER = "openai"; function coerceRequest(value) { return value && typeof value === "object" ? value : {}; } function resolveInputTexts(input) { if (typeof input === "string") return [input]; if (!Array.isArray(input)) return null; if (input.every((entry) => typeof entry === "string")) return input; return null; } function encodeEmbeddingBase64(embedding) { const float32 = Float32Array.from(embedding); return Buffer.from(float32.buffer).toString("base64"); } function validateInputTexts(texts) { if (texts.length > MAX_EMBEDDING_INPUTS) return `Too many inputs (max ${MAX_EMBEDDING_INPUTS}).`; let totalChars = 0; for (const text of texts) { if (text.length > MAX_EMBEDDING_INPUT_CHARS) return `Input too long (max ${MAX_EMBEDDING_INPUT_CHARS} chars).`; totalChars += text.length; if (totalChars > MAX_EMBEDDING_TOTAL_CHARS) return `Total input too large (max ${MAX_EMBEDDING_TOTAL_CHARS} chars).`; } } function resolveEmbeddingProviderRemoteConfig(remote) { return remote ? { baseUrl: remote.baseUrl, apiKey: remote.apiKey, headers: remote.headers } : void 0; } async function createConfiguredEmbeddingProvider(params) { const providerId = params.provider === "auto" ? DEFAULT_MEMORY_EMBEDDING_PROVIDER : params.provider; const createWithAdapter = async (adapter) => { return (await adapter.create({ config: params.cfg, agentDir: params.agentDir, model: params.model || adapter.defaultModel || "", local: params.memorySearch?.local, remote: resolveEmbeddingProviderRemoteConfig(params.memorySearch?.remote), outputDimensionality: params.memorySearch?.outputDimensionality })).provider; }; const createWithGenericAdapter = async (adapter) => { const result = await adapter.create({ config: params.cfg, agentDir: params.agentDir, provider: providerId, model: params.model || adapter.defaultModel || "", local: params.memorySearch?.local, remote: resolveEmbeddingProviderRemoteConfig(params.memorySearch?.remote), dimensions: params.memorySearch?.outputDimensionality, inputType: params.memorySearch?.inputType, queryInputType: params.memorySearch?.queryInputType, documentInputType: params.memorySearch?.documentInputType }); return result.provider ? adaptGenericEmbeddingProvider(result.provider) : null; }; const adapter = getMemoryEmbeddingProvider(providerId, params.cfg); if (adapter) { const provider = await createWithAdapter(adapter); if (!provider) throw new Error(`Memory embedding provider ${providerId} is unavailable.`); return provider; } const genericAdapter = getEmbeddingProvider(providerId, params.cfg); if (!genericAdapter) throw new Error(`Unknown memory embedding provider: ${providerId}`); const provider = await createWithGenericAdapter(genericAdapter); if (!provider) throw new Error(`Embedding provider ${providerId} is unavailable.`); return provider; } function adaptGenericEmbeddingProvider(provider) { return { id: provider.id, model: provider.model, ...typeof provider.maxInputTokens === "number" ? { maxInputTokens: provider.maxInputTokens } : {}, embedQuery: async (text, options) => await provider.embed(text, { ...options, inputType: "query" }), embedBatch: async (texts, options) => await provider.embedBatch(texts, { ...options, inputType: "document" }), ...provider.close ? { close: provider.close } : {} }; } function resolveEmbeddingsTarget(params) { const configuredProvider = params.configuredProvider === "auto" ? DEFAULT_MEMORY_EMBEDDING_PROVIDER : params.configuredProvider; const raw = params.requestModel.trim(); const slash = raw.indexOf("/"); if (slash === -1) return { provider: configuredProvider, model: raw }; const provider = normalizeLowercaseStringOrEmpty(raw.slice(0, slash)); const model = raw.slice(slash + 1).trim(); if (!model) return { errorMessage: "Unsupported embedding model reference." }; if (provider !== configuredProvider) return { errorMessage: "This agent does not allow that embedding provider on `/v1/embeddings`." }; return { provider: configuredProvider, model }; } /** Handles OpenAI-compatible embeddings requests for the configured agent memory provider. */ async function handleOpenAiEmbeddingsHttpRequest(req, res, opts) { const handled = await handleGatewayPostJsonEndpoint(req, res, { pathname: "/v1/embeddings", requiredOperatorMethod: "chat.send", resolveOperatorScopes: resolveOpenAiCompatibleHttpOperatorScopes, auth: opts.auth, trustedProxies: opts.trustedProxies, allowRealIpFallback: opts.allowRealIpFallback, rateLimiter: opts.rateLimiter, maxBodyBytes: opts.maxBodyBytes ?? DEFAULT_EMBEDDINGS_BODY_BYTES }); if (handled === false) return false; if (!handled) return true; const payload = coerceRequest(handled.body); const requestModel = normalizeOptionalString(payload.model) ?? ""; if (!requestModel) { sendJson(res, 400, { error: { message: "Missing `model`.", type: "invalid_request_error" } }); return true; } const cfg = getRuntimeConfig(); if (requestModel !== "openclaw" && !resolveAgentIdFromModel(requestModel, cfg)) { sendJson(res, 400, { error: { message: "Invalid `model`. Use `openclaw` or `openclaw/<agentId>`.", type: "invalid_request_error" } }); return true; } const texts = resolveInputTexts(payload.input); if (!texts) { sendJson(res, 400, { error: { message: "`input` must be a string or an array of strings.", type: "invalid_request_error" } }); return true; } const inputError = validateInputTexts(texts); if (inputError) { sendJson(res, 400, { error: { message: inputError, type: "invalid_request_error" } }); return true; } const agentId = resolveAgentIdForRequest({ req, model: requestModel }); const agentDir = resolveAgentDir(cfg, agentId); const memorySearch = resolveMemorySearchConfig(cfg, agentId); const configuredProvider = memorySearch?.provider ?? "openai"; const target = resolveEmbeddingsTarget({ requestModel: normalizeOptionalString(getHeader(req, "x-openclaw-model")) || normalizeOptionalString(memorySearch?.model) || "", configuredProvider }); if ("errorMessage" in target) { sendJson(res, 400, { error: { message: target.errorMessage, type: "invalid_request_error" } }); return true; } try { const embeddings = await (await createConfiguredEmbeddingProvider({ cfg, agentDir, provider: target.provider, model: target.model, memorySearch: memorySearch ? { ...memorySearch, outputDimensionality: typeof payload.dimensions === "number" && payload.dimensions > 0 ? Math.floor(payload.dimensions) : memorySearch.outputDimensionality } : void 0 })).embedBatch(texts); const encodingFormat = payload.encoding_format === "base64" ? "base64" : "float"; sendJson(res, 200, { object: "list", data: embeddings.map((embedding, index) => ({ object: "embedding", index, embedding: encodingFormat === "base64" ? encodeEmbeddingBase64(embedding) : embedding })), model: requestModel, usage: { prompt_tokens: 0, total_tokens: 0 } }); } catch (err) { logWarn(`openai-compat: embeddings request failed: ${formatErrorMessage(err)}`); sendJson(res, 500, { error: { message: "internal error", type: "api_error" } }); } return true; } //#endregion export { handleOpenAiEmbeddingsHttpRequest };