UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

192 lines (191 loc) 7.66 kB
import { a as normalizeLowercaseStringOrEmpty, c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js"; import { i as formatErrorMessage } from "./errors-BXgSefBE.js"; import { l as normalizeStringEntries, p as normalizeUniqueStringEntries } from "./string-normalization-WNUDCpXX.js"; import { t as getProviderEnvVars } from "./provider-env-vars-Clp1DREb.js"; import { o as normalizeProviderId } from "./model-selection-normalize-roKDxQ9_.js"; import "./model-selection-CA_65iXi.js"; import { n as sleepWithAbort } from "./backoff-S1yazmy1.js"; import { a as resolveTransientProviderRetryOptions, i as resolveTransientProviderDelayMs, o as shouldRetrySameKeyProviderOperation, r as resolveTransientProviderAttempts } from "./operation-retry-BXSt_--7.js"; //#region src/agents/live-auth-keys.ts /** * Live-test provider API-key discovery. * Reads provider-specific and manifest-declared env names without logging or * exposing secret values, with explicit single-key pins for flaky live lanes. */ const KEY_SPLIT_RE = /[\s,;]+/g; const GOOGLE_LIVE_SINGLE_KEY = "OPENCLAW_LIVE_GEMINI_KEY"; const PROVIDER_PREFIX_OVERRIDES = { google: "GEMINI", "google-vertex": "GEMINI" }; const PROVIDER_API_KEY_CONFIG = { anthropic: { liveSingle: "OPENCLAW_LIVE_ANTHROPIC_KEY", listVar: "OPENCLAW_LIVE_ANTHROPIC_KEYS", primaryVar: "ANTHROPIC_API_KEY", prefixedVar: "ANTHROPIC_API_KEY_" }, google: { liveSingle: GOOGLE_LIVE_SINGLE_KEY, listVar: "GEMINI_API_KEYS", primaryVar: "GEMINI_API_KEY", prefixedVar: "GEMINI_API_KEY_" }, "google-vertex": { liveSingle: GOOGLE_LIVE_SINGLE_KEY, listVar: "GEMINI_API_KEYS", primaryVar: "GEMINI_API_KEY", prefixedVar: "GEMINI_API_KEY_" }, openai: { liveSingle: "OPENCLAW_LIVE_OPENAI_KEY", listVar: "OPENAI_API_KEYS", primaryVar: "OPENAI_API_KEY", prefixedVar: "OPENAI_API_KEY_" } }; function parseKeyList(raw) { if (!raw) return []; return normalizeStringEntries(raw.split(KEY_SPLIT_RE)); } function collectEnvPrefixedKeys(prefix, env) { const keys = []; for (const [name, value] of Object.entries(env)) { if (!name.startsWith(prefix)) continue; const trimmed = normalizeOptionalString(value); if (!trimmed) continue; keys.push(trimmed); } return keys; } function resolveProviderApiKeyConfig(provider) { const normalized = normalizeProviderId(provider); const custom = PROVIDER_API_KEY_CONFIG[normalized]; const base = PROVIDER_PREFIX_OVERRIDES[normalized] ?? normalized.toUpperCase().replace(/-/g, "_"); const liveSingle = custom?.liveSingle ?? `OPENCLAW_LIVE_${base}_KEY`; const listVar = custom?.listVar ?? `${base}_API_KEYS`; const primaryVar = custom?.primaryVar ?? `${base}_API_KEY`; const prefixedVar = custom?.prefixedVar ?? `${base}_API_KEY_`; if (normalized === "google" || normalized === "google-vertex") return { liveSingle, listVar, primaryVar, prefixedVar, fallbackVars: ["GOOGLE_API_KEY"] }; return { liveSingle, listVar, primaryVar, prefixedVar, fallbackVars: [] }; } /** Collect configured API keys for live provider tests without exposing values. */ function collectProviderApiKeys(provider, options = {}) { const env = options.env ?? process.env; const normalizedProvider = normalizeProviderId(provider); const config = resolveProviderApiKeyConfig(normalizedProvider); const forcedSingle = config.liveSingle ? normalizeOptionalString(env[config.liveSingle]) : void 0; if (forcedSingle) return [forcedSingle]; const fromList = parseKeyList(config.listVar ? env[config.listVar] : void 0); const primary = config.primaryVar ? normalizeOptionalString(env[config.primaryVar]) : void 0; const fromPrefixed = config.prefixedVar ? collectEnvPrefixedKeys(config.prefixedVar, env) : []; const fallback = config.fallbackVars.map((envVar) => normalizeOptionalString(env[envVar])).filter(Boolean); const manifestFallback = (options.providerEnvVars ?? getProviderEnvVars(normalizedProvider)).map((envVar) => normalizeOptionalString(env[envVar])).filter(Boolean); const seen = /* @__PURE__ */ new Set(); const add = (value) => { if (!value) return; if (seen.has(value)) return; seen.add(value); }; for (const value of fromList) add(value); add(primary); for (const value of fromPrefixed) add(value); for (const value of fallback) add(value); for (const value of manifestFallback) add(value); return Array.from(seen); } /** Return whether a provider error message indicates API-key rate limiting. */ function isApiKeyRateLimitError(message) { const lower = normalizeLowercaseStringOrEmpty(message); if (lower.includes("rate_limit")) return true; if (lower.includes("rate limit")) return true; if (lower.includes("429")) return true; if (lower.includes("quota exceeded") || lower.includes("quota_exceeded")) return true; if (lower.includes("resource exhausted") || lower.includes("resource_exhausted")) return true; if (lower.includes("too many requests")) return true; return false; } //#endregion //#region src/agents/api-key-rotation.ts /** * Provider API-key rotation wrapper. * Runs provider calls across configured keys on rate-limit failures and keeps * same-key transient retries separate from key rotation. */ function dedupeApiKeys(raw) { return normalizeUniqueStringEntries(raw); } /** Collect primary and live-discovered provider keys in stable de-duped order. */ function collectProviderApiKeysForExecution(params) { const { primaryApiKey, provider } = params; return dedupeApiKeys([primaryApiKey?.trim() ?? "", ...collectProviderApiKeys(provider)]); } /** * Execute a provider operation with key rotation and optional same-key transient * retries. */ async function executeWithApiKeyRotation(params) { const keys = dedupeApiKeys(params.apiKeys); if (keys.length === 0) throw new Error(`No API keys configured for provider "${params.provider}".`); let lastError; const transientRetry = resolveTransientProviderRetryOptions(params.transientRetry); keyLoop: for (let apiKeyIndex = 0; apiKeyIndex < keys.length; apiKeyIndex += 1) { const apiKey = keys[apiKeyIndex]; const maxOperationAttempts = resolveTransientProviderAttempts(transientRetry); for (let attemptNumber = 1; attemptNumber <= maxOperationAttempts; attemptNumber += 1) try { return await params.execute(apiKey); } catch (error) { lastError = error; const message = formatErrorMessage(error); if (params.shouldRetry ? params.shouldRetry({ apiKey, error, attempt: apiKeyIndex, message }) : isApiKeyRateLimitError(message)) { if (apiKeyIndex + 1 >= keys.length) break; params.onRetry?.({ apiKey, error, attempt: apiKeyIndex, message }); break; } if (!transientRetry || !shouldRetrySameKeyProviderOperation({ options: transientRetry, error, message, provider: params.provider, apiKeyIndex, attemptNumber, maxAttempts: maxOperationAttempts })) break keyLoop; const delayMs = resolveTransientProviderDelayMs(transientRetry, attemptNumber); await (transientRetry.sleep ?? sleepWithAbort)(delayMs, transientRetry.signal); } } if (lastError === void 0) throw new Error(`Failed to run API request for ${params.provider}.`); throw toLintErrorObject(lastError, "Non-Error thrown"); } function toLintErrorObject(value, fallbackMessage) { if (value instanceof Error) return value; if (typeof value === "string") return new Error(value); const error = new Error(fallbackMessage, { cause: value }); if (typeof value === "object" && value !== null || typeof value === "function") Object.assign(error, value); return error; } //#endregion export { executeWithApiKeyRotation as n, collectProviderApiKeysForExecution as t };