import { createErrorResult, parseUpstreamError, formatProviderError } from "../utils/error.js"; import { HTTP_STATUS } from "../config/runtimeConfig.js"; import { getExecutor } from "../executors/index.js"; import { refreshWithRetry } from "../services/tokenRefresh.js"; import { getEmbeddingAdapter } from "./embeddingProviders/index.js"; /** * Core embeddings handler — orchestrator only. Provider-specific URL/headers/body/normalize * live in `./embeddingProviders/{id}.js`. * * @returns {Promise<{ success: boolean, response: Response, status?: number, error?: string }>} */ export async function handleEmbeddingsCore({ body, modelInfo, credentials, log, onCredentialsRefreshed, onRequestSuccess, }) { const { provider, model } = modelInfo; // Validate input const input = body.input; if (!input) { return createErrorResult(HTTP_STATUS.BAD_REQUEST, "Missing required field: input"); } if (typeof input !== "string" && !Array.isArray(input)) { return createErrorResult(HTTP_STATUS.BAD_REQUEST, "input must be a string or array of strings"); } const adapter = getEmbeddingAdapter(provider); if (!adapter) { return createErrorResult( HTTP_STATUS.BAD_REQUEST, `Provider '${provider}' does not support embeddings.` ); } const ctx = { input }; const url = adapter.buildUrl(model, credentials, ctx); const headers = adapter.buildHeaders(credentials, ctx); const requestBody = adapter.buildBody(model, { input, encoding_format: body.encoding_format || "float", dimensions: body.dimensions, }); log?.debug?.("EMBEDDINGS", `${provider.toUpperCase()} | ${model} | input_type=${Array.isArray(input) ? `array[${input.length}]` : "string"}`); let providerResponse; try { providerResponse = await fetch(url, { method: "POST", headers, body: JSON.stringify(requestBody), }); } catch (error) { const errMsg = formatProviderError(error, provider, model, HTTP_STATUS.BAD_GATEWAY); log?.debug?.("EMBEDDINGS", `Fetch error: ${errMsg}`); return createErrorResult(HTTP_STATUS.BAD_GATEWAY, errMsg); } // Handle 401/403 — try token refresh (skip for noAuth providers) const executor = getExecutor(provider); if ( !executor?.noAuth && (providerResponse.status === HTTP_STATUS.UNAUTHORIZED || providerResponse.status === HTTP_STATUS.FORBIDDEN) ) { const newCredentials = await refreshWithRetry( () => executor.refreshCredentials(credentials, log), 3, log ); if (newCredentials?.accessToken || newCredentials?.apiKey) { log?.info?.("TOKEN", `${provider.toUpperCase()} | refreshed for embeddings`); Object.assign(credentials, newCredentials); if (onCredentialsRefreshed) await onCredentialsRefreshed(newCredentials); try { const retryHeaders = adapter.buildHeaders(credentials, ctx); const retryUrl = adapter.buildUrl(model, credentials, ctx); providerResponse = await fetch(retryUrl, { method: "POST", headers: retryHeaders, body: JSON.stringify(requestBody), }); } catch { log?.warn?.("TOKEN", `${provider.toUpperCase()} | retry after refresh failed`); } } else { log?.warn?.("TOKEN", `${provider.toUpperCase()} | refresh failed`); } } if (!providerResponse.ok) { const { statusCode, message } = await parseUpstreamError(providerResponse); const errMsg = formatProviderError(new Error(message), provider, model, statusCode); log?.debug?.("EMBEDDINGS", `Provider error: ${errMsg}`); return createErrorResult(statusCode, errMsg); } let responseBody; try { responseBody = await providerResponse.json(); } catch { return createErrorResult(HTTP_STATUS.BAD_GATEWAY, `Invalid JSON response from ${provider}`); } if (onRequestSuccess) await onRequestSuccess(); const normalized = adapter.normalize(responseBody, model); log?.debug?.("EMBEDDINGS", `Success | usage=${JSON.stringify(normalized.usage || {})}`); return { success: true, response: new Response(JSON.stringify(normalized), { headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, }), }; }