UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

351 lines (350 loc) 15.4 kB
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js"; import { t as createLazyImportLoader } from "./lazy-promise-DGqyc4Y4.js"; import { n as getRuntimeConfig } from "./io.runtime-B9iJRs3w.js"; import { n as computeBackoff } from "./src-BQ327IOM.js"; import { c as normalizeProviderId } from "./model-ref-shared-Dz7QU0Lx.js"; import "./config-Cs0XXL3x.js"; import "./backoff-BkMI1WEL.js"; import "./model-selection-di2kjKCB.js"; import { _ as replaceContextWindowCaches, d as clearContextWindowCaches, f as getContextWindowCaches, g as providerContextTokenCacheKey, h as minPositiveContextTokens, l as resolveContextTokensForModelFromCache, m as lookupCachedContextWindow, p as lookupCachedContextTokens, s as resolveAnthropicFixedContextWindow, u as REUSED_CONTEXT_WINDOW_CACHE_STATE, v as replaceDiscoveredContextTokenCache } from "./context-resolution-DlNIXncb.js"; //#region src/agents/context-cache-projection.ts const CONTEXT_PROJECTION_BATCH_SIZE = 512; function cacheMinimum(cache, key, contextTokens) { const existing = cache.get(key); if (existing === void 0 || contextTokens < existing) cache.set(key, contextTokens); } function applyDiscoveredContextWindow(cache, model) { if (!model?.id) return; const discoveredContextTokens = typeof model.contextTokens === "number" ? Math.trunc(model.contextTokens) : typeof model.contextWindow === "number" ? Math.trunc(model.contextWindow) : void 0; const contextTokens = resolveDiscoveredAnthropicFixedContextWindow(model) ?? discoveredContextTokens; if (!contextTokens || contextTokens <= 0) return; cacheMinimum(cache, model.id, contextTokens); if (typeof model.provider !== "string") return; const provider = normalizeProviderId(model.provider); if (!provider) return; cacheMinimum(cache, providerContextTokenCacheKey(provider, model.id), contextTokens); const slash = model.id.indexOf("/"); const prefixedProvider = slash > 0 ? normalizeProviderId(model.id.slice(0, slash)) : ""; const bareModelId = slash > 0 ? model.id.slice(slash + 1).trim() : ""; if (prefixedProvider === provider && bareModelId) cacheMinimum(cache, providerContextTokenCacheKey(provider, bareModelId), contextTokens); } function applyDiscoveredContextWindows(params) { for (const model of params.models) applyDiscoveredContextWindow(params.cache, model); } function applyConfiguredContextWindows(params) { const providers = params.modelsConfig?.providers; if (!providers || typeof providers !== "object") return; for (const [providerId, provider] of Object.entries(providers)) { if (!Array.isArray(provider?.models)) continue; for (const model of provider.models) applyConfiguredContextWindow({ cache: params.cache, windowCache: params.windowCache, providerId, model }); } } function applyConfiguredContextWindow(params) { const modelId = typeof params.model?.id === "string" ? params.model.id : void 0; const contextTokens = typeof params.model?.contextTokens === "number" ? params.model.contextTokens : void 0; const contextWindow = typeof params.model?.contextWindow === "number" ? params.model.contextWindow : void 0; const configuredValue = contextTokens && contextTokens > 0 ? { cache: params.cache, value: contextTokens } : contextWindow && contextWindow > 0 ? { cache: params.windowCache, value: contextWindow } : void 0; if (!modelId || !configuredValue) return; const provider = normalizeProviderId(params.providerId); configuredValue.cache.set(modelId, configuredValue.value); configuredValue.cache.set(providerContextTokenCacheKey(provider, modelId), configuredValue.value); const slash = modelId.indexOf("/"); const prefixedProvider = slash > 0 ? normalizeProviderId(modelId.slice(0, slash)) : ""; const bareModelId = slash > 0 ? modelId.slice(slash + 1).trim() : ""; if (provider && prefixedProvider === provider && bareModelId) configuredValue.cache.set(providerContextTokenCacheKey(provider, bareModelId), configuredValue.value); } function resolveDiscoveredAnthropicFixedContextWindow(model) { const provider = typeof model.provider === "string" ? normalizeProviderId(model.provider) : void 0; if (provider) return resolveAnthropicFixedContextWindow(provider, model.id); const normalized = normalizeLowercaseStringOrEmpty(model.id); const slash = normalized.indexOf("/"); if (slash < 0) return; const inferredProvider = normalizeProviderId(normalized.slice(0, slash)); const inferredModel = normalized.slice(slash + 1); return inferredProvider === "claude-cli" ? resolveAnthropicFixedContextWindow(inferredProvider, inferredModel) : void 0; } function yieldToGateway() { return new Promise((resolve) => { setImmediate(resolve); }); } async function projectModels(params) { for (const model of params.models) { applyDiscoveredContextWindow(params.cache, model); params.processed.count += 1; if (params.processed.count % CONTEXT_PROJECTION_BATCH_SIZE === 0) { await yieldToGateway(); params.assertCurrent?.(); } } } async function prepareContextWindowCaches(params) { const caches = { configuredTokenCache: /* @__PURE__ */ new Map(), discoveredTokenCache: /* @__PURE__ */ new Map(), contextWindowCache: /* @__PURE__ */ new Map() }; const processed = { count: 0 }; const providers = params.config.models?.providers; if (providers && typeof providers === "object") for (const [providerId, provider] of Object.entries(providers)) { if (!Array.isArray(provider?.models)) continue; for (const model of provider.models) { applyConfiguredContextWindow({ cache: caches.configuredTokenCache, windowCache: caches.contextWindowCache, providerId, model }); processed.count += 1; if (processed.count % CONTEXT_PROJECTION_BATCH_SIZE === 0) { await yieldToGateway(); params.assertCurrent?.(); } } } await projectModels({ cache: caches.discoveredTokenCache, models: params.modelCatalog.entries, processed, assertCurrent: params.assertCurrent }); await projectModels({ cache: caches.discoveredTokenCache, models: params.modelCatalog.staticEntries ?? [], processed, assertCurrent: params.assertCurrent }); params.assertCurrent?.(); return caches; } async function prepareDiscoveredContextTokenCache(params) { const cache = /* @__PURE__ */ new Map(); const processed = { count: 0 }; await projectModels({ cache, models: params.modelCatalog.entries, processed, assertCurrent: params.assertCurrent }); await projectModels({ cache, models: params.modelCatalog.staticEntries ?? [], processed, assertCurrent: params.assertCurrent }); params.assertCurrent?.(); return cache; } //#endregion //#region src/agents/context-runtime-state.ts const CONTEXT_WINDOW_RUNTIME_STATE_KEY = Symbol.for("openclaw.contextWindowRuntimeState"); /** Shared mutable state for context-window resolution and model discovery. */ const CONTEXT_WINDOW_RUNTIME_STATE = (() => { const globalState = globalThis; let state = globalState[CONTEXT_WINDOW_RUNTIME_STATE_KEY]; if (!state) { state = { generation: 0, loadPromise: null, loadGeneration: null, configuredConfig: void 0, configLoadFailures: 0, nextConfigLoadAttemptAtMs: 0, modelsConfigRuntimeLoader: createLazyImportLoader(() => import("./agents/models-config.runtime.js")) }; globalState[CONTEXT_WINDOW_RUNTIME_STATE_KEY] = state; } else { if (!REUSED_CONTEXT_WINDOW_CACHE_STATE) { state.loadPromise = null; state.loadGeneration = null; } if (typeof state.generation !== "number") state.generation = 0; if (state.loadGeneration === void 0) state.loadGeneration = null; state.modelsConfigRuntimeLoader ??= createLazyImportLoader(() => import("./agents/models-config.runtime.js")); } return state; })(); /** Invalidate prepared context metadata while a replacement load is staged. */ function beginContextWindowCacheRefresh() { CONTEXT_WINDOW_RUNTIME_STATE.generation += 1; CONTEXT_WINDOW_RUNTIME_STATE.configuredConfig = void 0; CONTEXT_WINDOW_RUNTIME_STATE.configLoadFailures = 0; CONTEXT_WINDOW_RUNTIME_STATE.nextConfigLoadAttemptAtMs = 0; } /** Reset prepared context-window state after model config or plugin metadata changes. */ function resetContextWindowCache() { beginContextWindowCacheRefresh(); CONTEXT_WINDOW_RUNTIME_STATE.modelsConfigRuntimeLoader.clear(); clearContextWindowCaches(); } /** Reset context-window runtime state and token cache for isolated tests. */ function resetContextWindowCacheForTest() { resetContextWindowCache(); } //#endregion //#region src/agents/context.ts const CONFIG_LOAD_RETRY_POLICY = { initialMs: 1e3, maxMs: 6e4, factor: 2, jitter: 0 }; const loadPreparedModelCatalogRuntime = () => import("./prepared-model-catalog-BH7NC_p8.js"); function primeConfiguredContextWindowsFromConfig(cfg) { const caches = getContextWindowCaches(); applyConfiguredContextWindows({ cache: caches.configuredTokenCache, windowCache: caches.contextWindowCache, modelsConfig: cfg.models }); CONTEXT_WINDOW_RUNTIME_STATE.configuredConfig = cfg; CONTEXT_WINDOW_RUNTIME_STATE.configLoadFailures = 0; CONTEXT_WINDOW_RUNTIME_STATE.nextConfigLoadAttemptAtMs = 0; return cfg; } function primeConfiguredContextWindows() { if (CONTEXT_WINDOW_RUNTIME_STATE.configuredConfig) return primeConfiguredContextWindowsFromConfig(CONTEXT_WINDOW_RUNTIME_STATE.configuredConfig); if (Date.now() < CONTEXT_WINDOW_RUNTIME_STATE.nextConfigLoadAttemptAtMs) return; try { return primeConfiguredContextWindowsFromConfig(getRuntimeConfig()); } catch { CONTEXT_WINDOW_RUNTIME_STATE.configLoadFailures += 1; const backoffMs = computeBackoff(CONFIG_LOAD_RETRY_POLICY, CONTEXT_WINDOW_RUNTIME_STATE.configLoadFailures); CONTEXT_WINDOW_RUNTIME_STATE.nextConfigLoadAttemptAtMs = Date.now() + backoffMs; return; } } function ensureContextWindowCacheLoaded(cfgOverride) { const generation = CONTEXT_WINDOW_RUNTIME_STATE.generation; if (CONTEXT_WINDOW_RUNTIME_STATE.loadPromise && CONTEXT_WINDOW_RUNTIME_STATE.loadGeneration === generation) return CONTEXT_WINDOW_RUNTIME_STATE.loadPromise; const cfg = cfgOverride ? primeConfiguredContextWindowsFromConfig(cfgOverride) : primeConfiguredContextWindows(); if (!cfg) return Promise.resolve(); CONTEXT_WINDOW_RUNTIME_STATE.loadPromise = Promise.resolve().then(async () => { if (CONTEXT_WINDOW_RUNTIME_STATE.generation !== generation) return; let stagedTokenCache = /* @__PURE__ */ new Map(); try { const catalogResult = await (async () => { const { loadPreparedModelCatalogOwnerSnapshot } = await loadPreparedModelCatalogRuntime(); return await loadPreparedModelCatalogOwnerSnapshot({ config: cfg, readOnly: true }).then((value) => ({ status: "fulfilled", value }), (reason) => ({ status: "rejected", reason })); })(); if (CONTEXT_WINDOW_RUNTIME_STATE.generation !== generation) return; if (catalogResult.status === "fulfilled") stagedTokenCache = await prepareDiscoveredContextTokenCache({ modelCatalog: catalogResult.value.modelCatalog, assertCurrent: () => { if (CONTEXT_WINDOW_RUNTIME_STATE.generation !== generation) throw new Error("context window cache generation was superseded"); } }); } catch {} if (CONTEXT_WINDOW_RUNTIME_STATE.generation === generation) replaceDiscoveredContextTokenCache(stagedTokenCache); }).catch(() => {}); CONTEXT_WINDOW_RUNTIME_STATE.loadGeneration = generation; return CONTEXT_WINDOW_RUNTIME_STATE.loadPromise; } /** * Reuse the Gateway's published catalog generation. Omitting the Gateway binding * falls through to a read-only owner whose key hashes the full model config. */ async function prewarmContextWindowCacheAfterReady(params) { beginContextWindowCacheRefresh(); const generation = CONTEXT_WINDOW_RUNTIME_STATE.generation; const shouldStop = () => CONTEXT_WINDOW_RUNTIME_STATE.generation !== generation || params.isCancelled?.() === true; if (shouldStop()) return; let published = false; const loadPromise = (async () => { const { getPublishedPreparedModelCatalogOwnerSnapshot } = await loadPreparedModelCatalogRuntime(); if (shouldStop()) return; const owner = getPublishedPreparedModelCatalogOwnerSnapshot({ config: params.config, allowGatewaySubagentBinding: true }); if (!owner) throw new Error("published Gateway model catalog owner is unavailable"); if (shouldStop()) return; const caches = await prepareContextWindowCaches({ config: owner.config, modelCatalog: owner.modelCatalog, assertCurrent: () => { if (shouldStop()) throw new Error("context window cache prewarm cancelled"); } }); if (shouldStop()) return; replaceContextWindowCaches(caches); CONTEXT_WINDOW_RUNTIME_STATE.configuredConfig = owner.config; CONTEXT_WINDOW_RUNTIME_STATE.configLoadFailures = 0; CONTEXT_WINDOW_RUNTIME_STATE.nextConfigLoadAttemptAtMs = 0; published = true; })(); const trackedLoadPromise = loadPromise.catch(() => {}); CONTEXT_WINDOW_RUNTIME_STATE.loadPromise = trackedLoadPromise; CONTEXT_WINDOW_RUNTIME_STATE.loadGeneration = generation; try { await loadPromise; } catch {} finally { if (!published && CONTEXT_WINDOW_RUNTIME_STATE.generation === generation && CONTEXT_WINDOW_RUNTIME_STATE.loadPromise === trackedLoadPromise) { CONTEXT_WINDOW_RUNTIME_STATE.loadPromise = null; CONTEXT_WINDOW_RUNTIME_STATE.loadGeneration = null; } } } async function waitForContextWindowCacheLoad(options) { const promise = CONTEXT_WINDOW_RUNTIME_STATE.loadPromise; if (!promise || CONTEXT_WINDOW_RUNTIME_STATE.loadGeneration !== CONTEXT_WINDOW_RUNTIME_STATE.generation) return "idle"; const timeoutMs = Math.max(0, Math.trunc(options?.timeoutMs ?? 250)); if (timeoutMs === 0) return "timeout"; let timeoutHandle = null; try { return await Promise.race([promise.then(() => "loaded"), new Promise((resolve) => { timeoutHandle = setTimeout(() => resolve("timeout"), timeoutMs); timeoutHandle.unref?.(); })]); } finally { if (timeoutHandle) clearTimeout(timeoutHandle); } } /** Replace cached model context metadata for the active runtime configuration. */ async function refreshContextWindowCache(cfg) { beginContextWindowCacheRefresh(); const caches = getContextWindowCaches(); caches.configuredTokenCache.clear(); caches.contextWindowCache.clear(); primeConfiguredContextWindowsFromConfig(cfg); await ensureContextWindowCacheLoaded(); } function prepareContextWindowCache(options) { if (options?.skipRuntimeConfigLoad) return; if (options?.allowAsyncLoad === false) primeConfiguredContextWindows(); else ensureContextWindowCacheLoaded(); } function lookupContextTokens(modelId, options) { if (!modelId) return; prepareContextWindowCache(options); return minPositiveContextTokens(lookupCachedContextTokens(modelId), lookupCachedContextWindow(modelId)); } function resolveContextTokensForModel(params) { prepareContextWindowCache({ allowAsyncLoad: params.allowAsyncLoad, skipRuntimeConfigLoad: Boolean(params.cfg) }); return resolveContextTokensForModelFromCache(params, (modelId) => lookupCachedContextTokens(modelId), (modelId) => lookupCachedContextWindow(modelId)); } //#endregion export { resolveContextTokensForModel as a, applyConfiguredContextWindows as c, refreshContextWindowCache as i, applyDiscoveredContextWindows as l, lookupContextTokens as n, waitForContextWindowCacheLoad as o, prewarmContextWindowCacheAfterReady as r, resetContextWindowCacheForTest as s, ensureContextWindowCacheLoaded as t };