UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

130 lines (129 loc) 5.75 kB
import { t as VERSION } from "./version-v1kuAkGj.js"; import { t as compareOpenClawVersions } from "./version-BXayNebU.js"; import "./http-body-D3IMwTJJ.js"; import { i as readResponseWithLimit } from "./http-response-body-CwT_cCNz.js"; import { a as readRemoteModelCatalog, c as resolveRemoteCatalogUrl, d as validateAndSanitizeRemoteModelCatalogBundle, i as markRemoteModelCatalogChecked, l as bundledCatalogGeneratedAt, o as writeRemoteModelCatalog, s as isRemoteModelCatalogRefreshEnabled, u as parseRemoteModelCatalogBundle } from "./model-catalog-BiOJ2EJd.js"; import { i as fetchWithSsrFGuard, r as fetchConfiguredLocalOriginWithSsrFGuard } from "./fetch-guard-BMdGQhbb.js"; //#region src/model-catalog/remote-refresh.ts const REMOTE_MODEL_CATALOG_TTL_MS = 216e5; const REMOTE_MODEL_CATALOG_TIMEOUT_MS = 15e3; const REMOTE_MODEL_CATALOG_MAX_BYTES = 4194304; function bundleCounts(bundle) { const providers = Object.values(bundle.providers); return { providers: providers.length, models: providers.reduce((total, provider) => total + provider.models.length, 0) }; } function storedCounts(bundleJson) { const bundle = parseRemoteModelCatalogBundle(JSON.parse(bundleJson)); return { ...bundleCounts(bundle), generatedAt: bundle.generatedAt }; } function assertCompatibleMinVersion(bundle) { if (!bundle.minVersion) return; const comparison = compareOpenClawVersions(VERSION, bundle.minVersion); if (comparison === null) throw new Error(`invalid remote catalog minVersion: ${bundle.minVersion}`); if (comparison < 0) throw new Error(`remote catalog requires OpenClaw ${bundle.minVersion} or newer (current ${VERSION})`); } function isExplicitLocalHttpUrl(config, url) { if (!config.models?.catalogRefresh?.url) return false; const parsed = new URL(url); return parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]"); } async function refreshRemoteModelCatalog(params) { if (!isRemoteModelCatalogRefreshEnabled(params.config)) return { status: "disabled", providers: 0, models: 0 }; const databaseOptions = params.databaseOptions ?? {}; const now = (params.now ?? Date.now)(); try { const url = resolveRemoteCatalogUrl(params.config); const stored = readRemoteModelCatalog(databaseOptions); const activeStored = stored?.source_url === url ? stored : void 0; if (!params.force && activeStored && now - activeStored.checked_at < 216e5) return { status: "fresh", nextCheckInMs: Math.max(0, REMOTE_MODEL_CATALOG_TTL_MS - (now - activeStored.checked_at)), ...storedCounts(activeStored.bundle_json) }; const headers = new Headers({ Accept: "application/json" }); if (activeStored?.etag) headers.set("If-None-Match", activeStored.etag); if (activeStored?.last_modified) headers.set("If-Modified-Since", activeStored.last_modified); const fetchParams = { url, init: { headers }, timeoutMs: REMOTE_MODEL_CATALOG_TIMEOUT_MS, signal: params.signal, fetchImpl: params.fetchImpl, auditContext: "remote-model-catalog" }; const explicitOverride = Boolean(params.config.models?.catalogRefresh?.url); const localHttp = isExplicitLocalHttpUrl(params.config, url); let guarded; if (explicitOverride) { const configuredOrigin = new URL(url).origin; guarded = await fetchConfiguredLocalOriginWithSsrFGuard({ ...fetchParams, configuredLocalOriginBaseUrl: configuredOrigin, policy: { allowedOrigins: [configuredOrigin] }, requireHttps: !localHttp }); } else guarded = await fetchWithSsrFGuard({ ...fetchParams, requireHttps: true }); try { if (guarded.response.status === 304) { if (!activeStored) throw new Error("remote catalog returned 304 without a stored bundle"); return { status: "unchanged", ...storedCounts((markRemoteModelCatalogChecked(now, { expected: activeStored, etag: guarded.response.headers.get("etag") ?? activeStored.etag, lastModified: guarded.response.headers.get("last-modified") ?? activeStored.last_modified }, databaseOptions) ? activeStored : readRemoteModelCatalog(databaseOptions))?.bundle_json ?? activeStored.bundle_json) }; } if (!guarded.response.ok) throw new Error(`remote catalog request failed: HTTP ${guarded.response.status}`); const body = await readResponseWithLimit(guarded.response, REMOTE_MODEL_CATALOG_MAX_BYTES, { chunkTimeoutMs: REMOTE_MODEL_CATALOG_TIMEOUT_MS }); const bundle = validateAndSanitizeRemoteModelCatalogBundle(JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(body))); assertCompatibleMinVersion(bundle); const bundleJson = JSON.stringify(bundle); const unchanged = activeStored?.bundle_json === bundleJson; const writeResult = writeRemoteModelCatalog({ bundle_json: bundleJson, generated_at: bundle.generatedAt, min_version: bundle.minVersion ?? null, source_url: url, etag: guarded.response.headers.get("etag"), last_modified: guarded.response.headers.get("last-modified"), checked_at: now }, databaseOptions); if (writeResult.status === "retained-newer") return { status: "unchanged", ...storedCounts(writeResult.row.bundle_json) }; const bundledGeneratedAt = (params.bundledGeneratedAt ?? bundledCatalogGeneratedAt)(); return { status: unchanged || bundledGeneratedAt === void 0 || bundle.generatedAt <= bundledGeneratedAt ? "unchanged" : "updated", generatedAt: bundle.generatedAt, ...bundleCounts(bundle) }; } finally { await guarded.release(); } } catch (error) { return { status: "error", error: String(error), providers: 0, models: 0 }; } } //#endregion export { refreshRemoteModelCatalog as n, REMOTE_MODEL_CATALOG_TTL_MS as t };