UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

438 lines (437 loc) 17.7 kB
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js"; import { o as assertSecretInputResolved } from "./types.secrets-kC0nOetj.js"; import { r as resolveProviderRequestCapabilities } from "./provider-attribution-CDS-94hQ.js"; //#region src/agents/provider-request-config.ts /** * Provider request configuration resolver. * * Normalizes operator request overrides into transport-ready auth, proxy, TLS, header, and SSRF policy state. */ const FORBIDDEN_HEADER_KEYS = /* @__PURE__ */ new Set([ "__proto__", "prototype", "constructor" ]); const FORBIDDEN_INSECURE_TLS_MESSAGE = "Provider transport overrides do not allow insecureSkipVerify"; const FORBIDDEN_RUNTIME_TRANSPORT_OVERRIDE_MESSAGE = "Runtime auth request overrides do not allow proxy or TLS transport settings"; function resolvePrivateNetworkAccess(params) { const configuredAllowPrivateNetwork = params.allowPrivateNetwork ?? params.request?.allowPrivateNetwork; if (configuredAllowPrivateNetwork !== void 0) return { allowPrivateNetwork: configuredAllowPrivateNetwork, explicitlyDenied: !configuredAllowPrivateNetwork }; return { allowPrivateNetwork: false, explicitlyDenied: false }; } function sanitizeConfiguredRequestString(value, path) { if (typeof value !== "string") { assertSecretInputResolved({ value, path }); return; } const trimmed = value.trim(); return trimmed ? trimmed : void 0; } /** Sanitizes provider-level request overrides after secret resolution. */ function sanitizeConfiguredProviderRequest(request) { if (!request || typeof request !== "object" || Array.isArray(request)) return; let headers; if (request.headers && typeof request.headers === "object" && !Array.isArray(request.headers)) { const nextHeaders = {}; for (const [key, value] of Object.entries(request.headers)) { const sanitized = sanitizeConfiguredRequestString(value, `request.headers.${key}`); if (sanitized) nextHeaders[key] = sanitized; } if (Object.keys(nextHeaders).length > 0) headers = nextHeaders; } let auth; const rawAuth = request.auth; if (rawAuth && typeof rawAuth === "object" && !Array.isArray(rawAuth)) { if (rawAuth.mode === "provider-default") auth = { mode: "provider-default" }; else if (rawAuth.mode === "authorization-bearer") { const token = sanitizeConfiguredRequestString(rawAuth.token, "request.auth.token"); if (token) auth = { mode: "authorization-bearer", token }; } else if (rawAuth.mode === "header") { const headerName = sanitizeConfiguredRequestString(rawAuth.headerName, "request.auth.headerName"); const value = sanitizeConfiguredRequestString(rawAuth.value, "request.auth.value"); const prefix = sanitizeConfiguredRequestString(rawAuth.prefix, "request.auth.prefix"); if (headerName && value) auth = { mode: "header", headerName, value, ...prefix ? { prefix } : {} }; } } const sanitizeTls = (tls, pathPrefix) => { if (!tls || typeof tls !== "object" || Array.isArray(tls)) return; const rawTls = tls; const next = {}; const ca = sanitizeConfiguredRequestString(rawTls.ca, `${pathPrefix}.ca`); const cert = sanitizeConfiguredRequestString(rawTls.cert, `${pathPrefix}.cert`); const key = sanitizeConfiguredRequestString(rawTls.key, `${pathPrefix}.key`); const passphrase = sanitizeConfiguredRequestString(rawTls.passphrase, `${pathPrefix}.passphrase`); const serverName = sanitizeConfiguredRequestString(rawTls.serverName, `${pathPrefix}.serverName`); if (ca) next.ca = ca; if (cert) next.cert = cert; if (key) next.key = key; if (passphrase) next.passphrase = passphrase; if (serverName) next.serverName = serverName; if (rawTls.insecureSkipVerify === true) next.insecureSkipVerify = true; else if (rawTls.insecureSkipVerify === false) next.insecureSkipVerify = false; return Object.keys(next).length > 0 ? next : void 0; }; let proxy; const rawProxy = request.proxy; if (rawProxy && typeof rawProxy === "object" && !Array.isArray(rawProxy)) { const tls = sanitizeTls(rawProxy.tls, "request.proxy.tls"); if (rawProxy.mode === "env-proxy") proxy = { mode: "env-proxy", ...tls ? { tls } : {} }; else if (rawProxy.mode === "explicit-proxy") { const url = sanitizeConfiguredRequestString(rawProxy.url, "request.proxy.url"); if (url) proxy = { mode: "explicit-proxy", url, ...tls ? { tls } : {} }; } } const tls = sanitizeTls(request.tls, "request.tls"); if (!headers && !auth && !proxy && !tls) return; return { ...headers ? { headers } : {}, ...auth ? { auth } : {}, ...proxy ? { proxy } : {}, ...tls ? { tls } : {} }; } /** Sanitizes model-level request overrides after secret resolution. */ function sanitizeConfiguredModelProviderRequest(request) { const sanitized = sanitizeConfiguredProviderRequest(request); const rawAllow = request?.allowPrivateNetwork; const allowPrivateNetwork = rawAllow === true ? true : rawAllow === false ? false : void 0; if (!sanitized && allowPrivateNetwork === void 0) return; return { ...sanitized, ...allowPrivateNetwork !== void 0 ? { allowPrivateNetwork } : {} }; } /** Merges provider request overrides with later entries taking precedence. */ function mergeProviderRequestOverrides(...overrides) { const merged = {}; let hasMerged = false; for (const current of overrides) { if (!current) continue; hasMerged = true; if (current.headers) merged.headers = Object.assign({}, merged.headers, current.headers); if (current.auth) merged.auth = current.auth; if (current.proxy) merged.proxy = current.proxy; if (current.tls) merged.tls = current.tls; } return hasMerged ? merged : void 0; } /** Merges model request overrides, preserving the latest private-network policy. */ function mergeModelProviderRequestOverrides(...overrides) { let merged = mergeProviderRequestOverrides(...overrides); for (const current of overrides) if (current?.allowPrivateNetwork !== void 0) { merged ??= {}; merged.allowPrivateNetwork = current.allowPrivateNetwork; } return merged; } function normalizeBaseUrl(baseUrl, fallback) { const raw = baseUrl?.trim() || fallback?.trim(); if (!raw) return; return raw.replace(/\/+$/, ""); } function mergeProviderRequestHeaders(...headerSets) { let merged; const headerNamesByLowerKey = /* @__PURE__ */ new Map(); for (const headers of headerSets) { if (!headers) continue; if (!merged) merged = Object.create(null); for (const [key, value] of Object.entries(headers)) { const normalizedKey = normalizeLowercaseStringOrEmpty(key); if (FORBIDDEN_HEADER_KEYS.has(normalizedKey)) continue; const previousKey = headerNamesByLowerKey.get(normalizedKey); if (previousKey && previousKey !== key) delete merged[previousKey]; merged[key] = value; headerNamesByLowerKey.set(normalizedKey, key); } } return merged && Object.keys(merged).length > 0 ? merged : void 0; } function resolveTlsOverride(tls) { if (!tls) return { configured: false }; if (tls.insecureSkipVerify === true) throw new Error(FORBIDDEN_INSECURE_TLS_MESSAGE); const ca = tls.ca?.trim(); const cert = tls.cert?.trim(); const key = tls.key?.trim(); const passphrase = tls.passphrase?.trim(); const serverName = tls.serverName?.trim(); const rejectUnauthorized = tls.insecureSkipVerify === false ? true : void 0; if (!ca && !cert && !key && !passphrase && !serverName && rejectUnauthorized === void 0) return { configured: false }; return { configured: true, ...ca ? { ca } : {}, ...cert ? { cert } : {}, ...key ? { key } : {}, ...passphrase ? { passphrase } : {}, ...serverName ? { serverName } : {}, ...rejectUnauthorized !== void 0 ? { rejectUnauthorized } : {} }; } function resolveAuthOverride(params) { const auth = params.request?.auth; if (auth?.mode === "authorization-bearer") { const value = auth.token.trim(); if (value) return { configured: true, mode: "authorization-bearer", headerName: "Authorization", value, injectAuthorizationHeader: true }; } if (auth?.mode === "header") { const headerName = auth.headerName.trim(); const value = auth.value.trim(); const prefix = auth.prefix?.trim(); if (headerName && value) return { configured: true, mode: "header", headerName, value, ...prefix ? { prefix } : {}, injectAuthorizationHeader: false }; } return { configured: false, mode: params.authHeader ? "authorization-bearer" : "provider-default", injectAuthorizationHeader: params.authHeader === true }; } /** Sanitizes runtime-only provider request overrides for auth request paths. */ function sanitizeRuntimeProviderRequestOverrides(request) { if (!request) return; if (request.proxy || request.tls) throw new Error(FORBIDDEN_RUNTIME_TRANSPORT_OVERRIDE_MESSAGE); const headers = request.headers; const auth = request.auth; if (!headers && !auth) return; return { ...headers ? { headers } : {}, ...auth ? { auth } : {} }; } /** Applies provider-prepared runtime auth overrides to a resolved model. */ function applyPreparedRuntimeAuthToModel(model, preparedAuth) { if (!preparedAuth?.baseUrl && !preparedAuth?.request) return model; const providerHeaders = preparedAuth.request?.auth ? Object.fromEntries(Object.entries(model.headers ?? {}).filter(([key]) => ![ "authorization", "api-key", "x-api-key" ].includes(key.toLowerCase()))) : model.headers; const requestConfig = resolveProviderRequestConfig({ provider: model.provider, api: model.api, baseUrl: preparedAuth.baseUrl ?? model.baseUrl, providerHeaders, request: sanitizeRuntimeProviderRequestOverrides(preparedAuth.request), capability: "llm", transport: "stream" }); const next = { ...model, ...preparedAuth.baseUrl ? { baseUrl: preparedAuth.baseUrl } : {}, headers: requestConfig.headers }; const routeFacts = getModelProviderRequestRouteFacts(model); return routeFacts ? attachModelProviderRequestRouteFacts(next, routeFacts.providerMetadataOwners) : next; } function resolveProxyOverride(request) { const proxy = request?.proxy; if (!proxy) return { configured: false }; const tls = resolveTlsOverride(proxy.tls); if (proxy.mode === "env-proxy") return { configured: true, mode: "env-proxy", tls }; const proxyUrl = proxy.url.trim(); if (!proxyUrl) return { configured: false }; return { configured: true, mode: "explicit-proxy", proxyUrl, tls }; } function applyResolvedAuthHeader(headers, auth) { if (!auth.configured) return headers; const next = mergeProviderRequestHeaders(headers) ?? Object.create(null); const keysToDelete = /* @__PURE__ */ new Set([normalizeLowercaseStringOrEmpty(auth.headerName)]); if (auth.mode === "header") keysToDelete.add("authorization"); for (const key of Object.keys(next)) if (keysToDelete.has(normalizeLowercaseStringOrEmpty(key))) delete next[key]; next[auth.headerName] = auth.mode === "authorization-bearer" ? `Bearer ${auth.value}` : `${auth.prefix ?? ""}${auth.value}`; return Object.keys(next).length > 0 ? next : void 0; } function toTlsConnectOptions(tls) { if (!tls.configured) return; const next = {}; if (tls.ca) next.ca = tls.ca; if (tls.cert) next.cert = tls.cert; if (tls.key) next.key = tls.key; if (tls.passphrase) next.passphrase = tls.passphrase; if (tls.serverName) next.servername = tls.serverName; if (tls.rejectUnauthorized !== void 0) next.rejectUnauthorized = tls.rejectUnauthorized; return Object.keys(next).length > 0 ? next : void 0; } /** Builds the dispatcher proxy/TLS policy for outbound provider requests. */ function buildProviderRequestDispatcherPolicy(request) { const targetTls = toTlsConnectOptions(request.tls); if (!request.proxy.configured) return targetTls ? { mode: "direct", connect: targetTls } : void 0; const proxiedTls = toTlsConnectOptions(request.proxy.tls); if (request.proxy.mode === "env-proxy") return { mode: "env-proxy", ...targetTls ? { connect: { ...targetTls } } : {}, ...proxiedTls ? { proxyTls: { ...proxiedTls } } : {} }; return { mode: "explicit-proxy", proxyUrl: request.proxy.proxyUrl, ...proxiedTls ? { proxyTls: proxiedTls } : {} }; } /** Resolves the full provider request policy, headers, auth, proxy, and TLS config. */ function resolveProviderRequestPolicyConfig(params) { const baseUrl = normalizeBaseUrl(params.baseUrl, params.defaultBaseUrl); const capability = params.capability ?? "llm"; const transport = params.transport ?? "http"; const policyInput = { provider: params.provider, api: params.api, baseUrl, ...params.providerMetadataOwners ? { providerMetadataOwners: params.providerMetadataOwners } : {}, capability, transport }; const capabilities = params.routeFacts?.capabilities ?? resolveProviderRequestCapabilities({ ...policyInput, compat: params.compat, modelId: params.modelId }); const auth = resolveAuthOverride({ authHeader: params.authHeader, request: params.request }); const extraHeaders = applyResolvedAuthHeader(mergeProviderRequestHeaders(params.discoveredHeaders, params.providerHeaders, params.modelHeaders, params.request?.headers), auth); const protectedAttributionKeys = new Set(Object.keys(capabilities.attributionHeaders ?? {}).map((key) => normalizeLowercaseStringOrEmpty(key))); const unprotectedCallerHeaders = params.callerHeaders ? Object.fromEntries(Object.entries(params.callerHeaders).filter(([key]) => !protectedAttributionKeys.has(normalizeLowercaseStringOrEmpty(key)))) : void 0; const mergedDefaults = mergeProviderRequestHeaders(extraHeaders, capabilities.attributionHeaders); const headers = params.precedence === "caller-wins" ? mergeProviderRequestHeaders(mergedDefaults, unprotectedCallerHeaders) : mergeProviderRequestHeaders(unprotectedCallerHeaders, mergedDefaults); const privateNetworkAccess = resolvePrivateNetworkAccess(params); return { api: params.api, baseUrl, headers, extraHeaders: { configured: Boolean(extraHeaders), headers: extraHeaders }, auth, proxy: resolveProxyOverride(params.request), tls: resolveTlsOverride(params.request?.tls), capabilities, allowPrivateNetwork: privateNetworkAccess.allowPrivateNetwork, trustConfiguredBaseUrlOrigin: !privateNetworkAccess.explicitlyDenied && (capabilities.endpointClass === "custom" || capabilities.endpointClass === "local") }; } /** Resolves request config used during model/catalog setup paths. */ function resolveProviderRequestConfig(params) { const resolved = resolveProviderRequestPolicyConfig(params); return { api: resolved.api, baseUrl: resolved.baseUrl, headers: resolved.extraHeaders.headers, extraHeaders: resolved.extraHeaders, auth: resolved.auth, proxy: resolved.proxy, tls: resolved.tls }; } /** Resolves final headers for one provider request route. */ function resolveProviderRequestHeaders(params) { return resolveProviderRequestPolicyConfig({ provider: params.provider, api: params.api, baseUrl: params.baseUrl, capability: params.capability, transport: params.transport, callerHeaders: params.callerHeaders, providerHeaders: params.defaultHeaders, precedence: params.precedence, request: params.request }).headers; } const MODEL_PROVIDER_REQUEST_TRANSPORT_SYMBOL = Symbol.for("openclaw.modelProviderRequestTransport"); const MODEL_PROVIDER_REQUEST_ROUTE_FACTS_SYMBOL = Symbol.for("openclaw.modelProviderRequestRouteFacts"); /** Attaches model-scoped provider request transport metadata without mutating the model. */ function attachModelProviderRequestTransport(model, request) { if (!request) return model; const next = { ...model }; next[MODEL_PROVIDER_REQUEST_TRANSPORT_SYMBOL] = request; return next; } /** Reads provider request transport metadata attached to a model definition. */ function getModelProviderRequestTransport(model) { return model[MODEL_PROVIDER_REQUEST_TRANSPORT_SYMBOL]; } /** Resolves and attaches the final provider route against one lifecycle-owned metadata generation. */ function attachModelProviderRequestRouteFacts(model, providerMetadataOwners) { if (!providerMetadataOwners || !model.provider) return model; const next = { ...model }; const capabilities = resolveProviderRequestCapabilities({ provider: model.provider, api: model.api, baseUrl: normalizeBaseUrl(model.baseUrl), providerMetadataOwners, capability: "llm", transport: "stream", modelId: model.id, compat: model.compat }); next[MODEL_PROVIDER_REQUEST_ROUTE_FACTS_SYMBOL] = { providerMetadataOwners, capabilities, ...![ "default", "invalid", "local", "custom" ].includes(capabilities.endpointClass) ? { providerOwner: capabilities.endpointClass } : {} }; return next; } /** Reads the prepared provider route attached to a transport model. */ function getModelProviderRequestRouteFacts(model) { return model[MODEL_PROVIDER_REQUEST_ROUTE_FACTS_SYMBOL]; } /** Re-resolves a projected transport model against the source model's metadata generation. */ function inheritModelProviderRequestRouteFacts(source, target) { return attachModelProviderRequestRouteFacts(target, getModelProviderRequestRouteFacts(source)?.providerMetadataOwners); } //#endregion export { getModelProviderRequestRouteFacts as a, mergeModelProviderRequestOverrides as c, resolveProviderRequestHeaders as d, resolveProviderRequestPolicyConfig as f, buildProviderRequestDispatcherPolicy as i, normalizeBaseUrl as l, sanitizeConfiguredProviderRequest as m, attachModelProviderRequestRouteFacts as n, getModelProviderRequestTransport as o, sanitizeConfiguredModelProviderRequest as p, attachModelProviderRequestTransport as r, inheritModelProviderRequestRouteFacts as s, applyPreparedRuntimeAuthToModel as t, resolveProviderRequestConfig as u };