UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

412 lines (411 loc) 16.3 kB
import { a as normalizeLowercaseStringOrEmpty } from "./string-coerce-mnp54Vah.js"; import { a as assertSecretInputResolved } from "./types.secrets-_0JOMGE5.js"; import { i as COPILOT_INTEGRATION_ID, s as buildCopilotIdeHeaders } from "./copilot-dynamic-headers-CF9zPORX.js"; import { i as resolveProviderRequestPolicy, r as resolveProviderRequestCapabilities } from "./provider-attribution-GEHCfopl.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 = 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 resolveProviderDefaultRequestHeaders(provider) { if (normalizeLowercaseStringOrEmpty(provider) !== "github-copilot") return; return { ...buildCopilotIdeHeaders(), "Copilot-Integration-Id": COPILOT_INTEGRATION_ID, "Openai-Organization": "github-copilot" }; } 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" }); return { ...model, ...preparedAuth.baseUrl ? { baseUrl: preparedAuth.baseUrl } : {}, headers: requestConfig.headers }; } 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 = 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, capability, transport }; const policy = resolveProviderRequestPolicy(policyInput); const capabilities = resolveProviderRequestCapabilities({ ...policyInput, compat: params.compat, modelId: params.modelId }); const auth = resolveAuthOverride({ authHeader: params.authHeader, request: params.request }); const extraHeaders = applyResolvedAuthHeader(mergeProviderRequestHeaders(resolveProviderDefaultRequestHeaders(params.provider), params.discoveredHeaders, params.providerHeaders, params.modelHeaders, params.request?.headers), auth); const protectedAttributionKeys = new Set(Object.keys(policy.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, policy.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), policy, capabilities, allowPrivateNetwork: privateNetworkAccess.allowPrivateNetwork, privateNetworkExplicitlyDenied: privateNetworkAccess.explicitlyDenied }; } /** 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, policy: resolved.policy }; } /** 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"); /** 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]; } //#endregion export { mergeModelProviderRequestOverrides as a, resolveProviderRequestHeaders as c, sanitizeConfiguredProviderRequest as d, getModelProviderRequestTransport as i, resolveProviderRequestPolicyConfig as l, attachModelProviderRequestTransport as n, normalizeBaseUrl as o, buildProviderRequestDispatcherPolicy as r, resolveProviderRequestConfig as s, applyPreparedRuntimeAuthToModel as t, sanitizeConfiguredModelProviderRequest as u };