UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

438 lines (437 loc) • 19.1 kB
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs"; import { t as VERSION } from "./version-Dx-YMDJx.mjs"; import { Et as _enum, Fn as object, Jn as string, Ln as partialRecord, Lt as boolean, Nt as array, Pn as number, Un as record, Yt as discriminatedUnion, nr as tuple, or as union, sr as unknown, xn as literal } from "./schemas-BOYIvvln.mjs"; import { t as compareOpenClawVersions } from "./version-ByD9iTct.mjs"; import { r as captureOpenClawStateWorkerContext } from "./openclaw-state-worker-context-epCM4Gdh.mjs"; import { r as normalizeProviderId } from "./provider-id-BIyeXQag.mjs"; import { n as buildModelCatalogRef } from "./model-catalog-refs-Dd-I0WMI.mjs"; import { n as MODEL_CATALOG_THINKING_LEVELS, t as MODEL_CATALOG_APIS } from "./model-catalog-types-BUOf2O4u.mjs"; import "./model-catalog-normalize-CVo8l4mj.mjs"; import { r as isProviderCatalogSourceAllowed } from "./provider-config-owner-BFMZMWmF.mjs"; import { n as planManifestModelCatalogRows } from "./manifest-planner-jaL2TnGz.mjs"; import { n as readRemoteModelCatalog, r as readRemoteModelCatalogAsync } from "./remote-store-D7hutCHf.mjs"; import { createRequire } from "node:module"; import { getEnvironmentData, setEnvironmentData } from "node:worker_threads"; //#region src/model-catalog/bundled-catalog-stamp.ts const BUILD_INFO_CANDIDATES = [ "../build-info.json", "../../build-info.json", "./build-info.json" ]; /** Reads the package build stamp once through Node's JSON module cache. */ function bundledCatalogGeneratedAt(moduleUrl = import.meta.url) { const require = createRequire(moduleUrl); for (const candidate of BUILD_INFO_CANDIDATES) try { const info = require(candidate); if (typeof info.builtAt !== "string") continue; const generatedAt = Date.parse(info.builtAt); if (Number.isFinite(generatedAt) && generatedAt > 0) return generatedAt; } catch {} } //#endregion //#region packages/model-catalog-core/src/remote-catalog-bundle.ts const REMOTE_CATALOG_MAX_FUTURE_SKEW_MS = 864e5; const stringMapSchema = record(string(), string()); const pricingTierSchema = object({ input: number().finite().nonnegative(), output: number().finite().nonnegative(), cacheRead: number().finite().nonnegative(), cacheWrite: number().finite().nonnegative(), range: union([tuple([number().finite().nonnegative()]), tuple([number().finite().nonnegative(), number().finite().nonnegative()])]) }).strict(); const costSchema = object({ input: number().finite().nonnegative().optional(), output: number().finite().nonnegative().optional(), cacheRead: number().finite().nonnegative().optional(), cacheWrite: number().finite().nonnegative().optional(), tieredPricing: array(pricingTierSchema).optional() }).strict(); const hostedPricingSchema = object({ input: number().finite().nonnegative(), output: number().finite().nonnegative(), cacheRead: number().finite().nonnegative().optional(), cacheWrite: number().finite().nonnegative().optional(), tieredPricing: array(pricingTierSchema).optional() }).strict(); const contextWindowOptionSchema = object({ id: string().trim().min(1), label: string().trim().min(1), contextWindow: number().int().positive() }).strict(); const modelFieldsSchema = object({ id: string().trim().min(1), name: string().optional(), api: _enum(MODEL_CATALOG_APIS).optional(), baseUrl: string().optional(), headers: stringMapSchema.optional(), input: array(_enum([ "text", "image", "document" ])).optional(), reasoning: boolean().optional(), contextWindow: number().finite().positive().optional(), contextWindows: array(contextWindowOptionSchema).max(16).optional(), contextWindowDefault: string().trim().min(1).optional(), contextTokens: number().int().positive().optional(), maxTokens: number().finite().positive().optional(), thinkingLevelMap: partialRecord(_enum(MODEL_CATALOG_THINKING_LEVELS), string().nullable()).optional(), cost: costSchema.optional(), compat: record(string(), unknown()).optional(), mediaInput: record(string(), unknown()).optional(), status: _enum([ "available", "preview", "deprecated", "disabled" ]).optional(), statusReason: string().optional(), replaces: array(string()).optional(), replacedBy: string().optional(), tags: array(string()).optional() }); function validateContextWindowDefault(model, context) { if (model.contextWindowDefault && !model.contextWindows?.some((option) => option.id === model.contextWindowDefault)) context.addIssue({ code: "custom", message: "contextWindowDefault must reference a declared contextWindows option", path: ["contextWindowDefault"] }); } const modelSchema = modelFieldsSchema.superRefine(validateContextWindowDefault); const remoteModelCatalogProviderSchema = object({ baseUrl: string().optional(), api: _enum(MODEL_CATALOG_APIS).optional(), headers: stringMapSchema.optional(), defaultModel: string().optional(), defaultUtilityModel: string().optional(), models: array(modelSchema).min(1) }).strict().superRefine((provider, context) => { const seen = /* @__PURE__ */ new Set(); for (const [index, model] of provider.models.entries()) { if (seen.has(model.id)) context.addIssue({ code: "custom", message: `duplicate model id: ${model.id}`, path: [ "models", index, "id" ] }); seen.add(model.id); } }); const remoteModelCatalogBundleSchema = object({ schemaVersion: literal(1), generatedAt: number().int().positive().refine((value) => value <= Date.now() + REMOTE_CATALOG_MAX_FUTURE_SKEW_MS, { message: "generatedAt is implausibly far in the future" }), minVersion: string().trim().min(1).optional(), sourceCommit: string().trim().min(1), providers: record(string().trim().min(1), remoteModelCatalogProviderSchema), pricing: record(string().trim().min(1), hostedPricingSchema).optional() }).strict(); const knownPricingV2Schema = costSchema.extend({ status: literal("known"), currency: literal("USD"), unit: literal("million_tokens"), source: string().trim().min(1).optional() }).refine((pricing) => pricing.input !== void 0 || pricing.output !== void 0 || pricing.cacheRead !== void 0 || pricing.cacheWrite !== void 0 || Boolean(pricing.tieredPricing?.length), { message: "known pricing must contain rates" }); const pricingV2Schema = discriminatedUnion("status", [knownPricingV2Schema, object({ status: _enum(["unknown", "unavailable"]), source: string().trim().min(1).optional() }).strict()]); const sourcedPricingV2Schema = hostedPricingSchema.extend({ source: string().trim().min(1) }).strict(); const upstreamPricingV2Schema = sourcedPricingV2Schema.extend({ passthroughOnly: literal(true).optional(), alternatives: array(sourcedPricingV2Schema).min(1).optional() }).strict(); const standalonePricingKeySchema = string().regex(/^[^/\s]+\/\S+$/u, "standalone pricing keys must be provider/model"); const modelV2Schema = modelFieldsSchema.omit({ cost: true, baseUrl: true, headers: true }).extend({ provider: string().trim().min(1), pricing: pricingV2Schema }).strict().superRefine(validateContextWindowDefault); const remoteModelCatalogBundleV2Schema = remoteModelCatalogBundleSchema.omit({ providers: true, pricing: true }).extend({ schemaVersion: literal(2), providers: record(string().trim().min(1), object({ api: _enum(MODEL_CATALOG_APIS).optional(), defaultModel: string().optional(), defaultUtilityModel: string().optional(), recommendedModels: array(string().trim().min(1)).refine((ids) => new Set(ids).size === ids.length, "duplicate recommended model id").optional() }).strict()), models: array(modelV2Schema).min(1), upstreamPricing: record(standalonePricingKeySchema, upstreamPricingV2Schema).optional(), providerPricing: record(standalonePricingKeySchema, sourcedPricingV2Schema).optional() }).superRefine((bundle, context) => { const providers = /* @__PURE__ */ new Map(); for (const [index, model] of bundle.models.entries()) { if (!Object.hasOwn(bundle.providers, model.provider)) context.addIssue({ code: "custom", message: `undeclared model provider: ${model.provider}`, path: [ "models", index, "provider" ] }); const ids = providers.get(model.provider) ?? /* @__PURE__ */ new Set(); if (ids.has(model.id)) context.addIssue({ code: "custom", message: `duplicate provider/model: ${model.provider}/${model.id}`, path: [ "models", index, "id" ] }); ids.add(model.id); providers.set(model.provider, ids); } for (const [provider, entry] of Object.entries(bundle.providers)) for (const [index, id] of (entry.recommendedModels ?? []).entries()) if (!providers.get(provider)?.has(id)) context.addIssue({ code: "custom", message: `recommended model must reference a model of provider ${provider}: ${id}`, path: [ "providers", provider, "recommendedModels", index ] }); }); function parseRemoteModelCatalogBundleV2(value) { return remoteModelCatalogBundleV2Schema.parse(value); } function parseRemoteModelCatalogBundle(value) { return remoteModelCatalogBundleSchema.parse(value); } function stripRemoteTransportOverrides(value) { if (Array.isArray(value)) return value.map(stripRemoteTransportOverrides); if (!value || typeof value !== "object") return value; const entries = Object.entries(value); let kept = 0; for (const entry of entries) if (entry[0] !== "baseUrl" && entry[0] !== "headers") { entry[1] = stripRemoteTransportOverrides(entry[1]); entries[kept++] = entry; } entries.length = kept; return Object.fromEntries(entries); } /** Removes every transport endpoint/header override before remote data reaches persistence. */ function sanitizeRemoteModelCatalogBundle(bundle) { return stripRemoteTransportOverrides(bundle); } function validateAndSanitizeRemoteModelCatalogBundle(value) { return sanitizeRemoteModelCatalogBundle(parseRemoteModelCatalogBundle(value)); } function validateAndSanitizeRemoteModelCatalogBundleV2(value) { const bundle = parseRemoteModelCatalogBundleV2(value); return { ...bundle, models: bundle.models.map((model) => { return stripRemoteTransportOverrides(model); }) }; } //#endregion //#region src/model-catalog/remote-bundle.ts /** Configured v1 mirrors remain a public input contract; runtime uses one projection. */ function parseRemoteModelCatalogWireBundle(value) { return isRecord(value) && value.schemaVersion === 2 ? validateAndSanitizeRemoteModelCatalogBundleV2(value) : validateAndSanitizeRemoteModelCatalogBundle(value); } function projectRemoteModelCatalog(bundle) { if (bundle.schemaVersion === 1) return { providers: bundle.providers, pricing: Object.fromEntries(Object.entries(bundle.pricing ?? {}).map(([key, cost]) => [key, { cost, explicit: false }])), upstreamPricing: {} }; const providers = Object.fromEntries(Object.entries(bundle.providers).map(([id, provider]) => [id, { ...provider, models: [] }])); const rowKeys = new Set(bundle.models.map(({ provider, id }) => buildModelCatalogRef(provider, id))); const prices = Object.entries(bundle.providerPricing ?? {}).filter(([key]) => !rowKeys.has(key)).map(([key, { source: _source, ...cost }]) => [key, { cost, explicit: false }]); for (const { provider, pricing, ...model } of bundle.models) { let cost; if (pricing.status === "known") { const { status: _status, currency: _currency, unit: _unit, source: _source, ...rates } = pricing; cost = rates; prices.push([buildModelCatalogRef(provider, model.id), { cost, explicit: true }]); } providers[provider].models.push({ ...model, ...cost ? { cost } : {} }); } return { providers, pricing: Object.fromEntries(prices), upstreamPricing: Object.fromEntries(Object.entries(bundle.upstreamPricing ?? {}).map(([key, { source, passthroughOnly = false, alternatives = [], ...cost }]) => [key, { rates: [{ source, cost }, ...alternatives.map(({ source: alternative, ...rates }) => ({ source: alternative, cost: rates }))], passthroughOnly: passthroughOnly || rowKeys.has(key) }])) }; } //#endregion //#region src/model-catalog/remote-config.ts const DEFAULT_REMOTE_MODEL_CATALOG_URL = "https://catalog.openclaw.ai/models/v2/catalog.json"; const RETIRED_DEFAULT_REMOTE_MODEL_CATALOG_URL = "https://catalog.openclaw.ai/models/v1/catalog.json"; function isRemoteModelCatalogRefreshEnabled(config) { return config.models?.catalogRefresh?.enabled !== false; } function resolveRemoteCatalogUrl(config) { return config.models?.catalogRefresh?.url?.trim() || DEFAULT_REMOTE_MODEL_CATALOG_URL; } /** Whether a catalog downloaded from `sourceUrl` may serve the configured source. */ function isRemoteCatalogSourceActive(config, sourceUrl) { const url = resolveRemoteCatalogUrl(config); return sourceUrl === url || url === DEFAULT_REMOTE_MODEL_CATALOG_URL && sourceUrl === RETIRED_DEFAULT_REMOTE_MODEL_CATALOG_URL; } //#endregion //#region src/model-catalog/remote-overlay.ts const STARTUP_SNAPSHOT_KEY = "openclaw.remoteModelCatalogStartupSnapshot"; let readBundledGeneratedAt = bundledCatalogGeneratedAt; let readStoredCatalog = readRemoteModelCatalog; let readStoredCatalogAsync = readRemoteModelCatalogAsync; function isCompatible(bundle, bundledGeneratedAt) { if (bundle.generatedAt <= bundledGeneratedAt) return false; if (!bundle.minVersion) return true; const comparison = compareOpenClawVersions(VERSION, bundle.minVersion); return comparison !== null && comparison >= 0; } function readCompatibleRemoteModelCatalog() { const bundledGeneratedAt = readBundledGeneratedAt(); if (bundledGeneratedAt === void 0) return null; return selectCompatibleRemoteModelCatalog(readStoredCatalog(), bundledGeneratedAt); } function readCompatibleRemoteModelCatalogMetadata() { const bundledGeneratedAt = readBundledGeneratedAt(); if (bundledGeneratedAt === void 0) return null; const stored = readStoredCatalog(); if (!stored) return null; const bundle = parseRemoteModelCatalogWireBundle(JSON.parse(stored.bundle_json)); return isCompatible(bundle, bundledGeneratedAt) ? { sourceUrl: stored.source_url, generatedAt: bundle.generatedAt } : null; } function selectCompatibleRemoteModelCatalog(stored, bundledGeneratedAt) { if (!stored) return null; const bundle = parseRemoteModelCatalogWireBundle(JSON.parse(stored.bundle_json)); if (!isCompatible(bundle, bundledGeneratedAt)) return null; return { sourceUrl: stored.source_url, generatedAt: bundle.generatedAt, ...projectRemoteModelCatalog(bundle) }; } function inheritedRemoteModelCatalogStartupSnapshot() { return getEnvironmentData(STARTUP_SNAPSHOT_KEY); } function publishRemoteModelCatalogStartupSnapshot(snapshot) { const inherited = inheritedRemoteModelCatalogStartupSnapshot(); if (inherited !== void 0) return inherited.catalog; setEnvironmentData(STARTUP_SNAPSHOT_KEY, { catalog: snapshot }); return snapshot; } function captureRemoteModelCatalogStartupSnapshot() { const inherited = inheritedRemoteModelCatalogStartupSnapshot(); if (inherited !== void 0) return inherited.catalog; let snapshot; try { snapshot = readCompatibleRemoteModelCatalog(); } catch { snapshot = null; } return publishRemoteModelCatalogStartupSnapshot(snapshot); } /** Prepare the same first-winner startup pair without host-thread SQLite reads. */ async function prepareRemoteModelCatalogStartupSnapshot(options = {}) { const inherited = inheritedRemoteModelCatalogStartupSnapshot(); if (inherited !== void 0) return inherited.catalog; let bundledGeneratedAt; try { bundledGeneratedAt = readBundledGeneratedAt(); } catch { return publishRemoteModelCatalogStartupSnapshot(null); } if (bundledGeneratedAt === void 0) return publishRemoteModelCatalogStartupSnapshot(null); const context = captureOpenClawStateWorkerContext(options); let snapshot; try { snapshot = selectCompatibleRemoteModelCatalog(await readStoredCatalogAsync(context), bundledGeneratedAt); } catch { snapshot = null; } context.admission.assertCurrent(); return publishRemoteModelCatalogStartupSnapshot(snapshot); } function getActiveRemoteModelCatalog(config) { if (!isRemoteModelCatalogRefreshEnabled(config)) return; const snapshot = captureRemoteModelCatalogStartupSnapshot(); return snapshot && isRemoteCatalogSourceActive(config, snapshot.sourceUrl) ? snapshot : void 0; } /** Inspects a completed check without activating its download or replacing the startup pair. */ function checkRemoteModelCatalogUpdate(config, expected) { if (!isRemoteModelCatalogRefreshEnabled(config) || resolveRemoteCatalogUrl(config) !== expected.sourceUrl) return "superseded"; const active = getActiveRemoteModelCatalog(config); if (active?.sourceUrl === expected.sourceUrl && active.generatedAt === expected.generatedAt) return "unchanged"; const stored = readCompatibleRemoteModelCatalogMetadata(); if (!stored) return "unchanged"; return stored.sourceUrl === expected.sourceUrl && stored.generatedAt === expected.generatedAt ? "restart-required" : "superseded"; } function getRemoteModelCatalogProviderOverlay(config, provider) { const providerId = normalizeProviderId(provider); return providerId ? getActiveRemoteModelCatalog(config)?.providers[providerId] : void 0; } function getRemoteModelCatalogPricing(config) { return getActiveRemoteModelCatalog(config)?.pricing; } function getRemoteModelCatalogUpstreamPricing(config) { return getActiveRemoteModelCatalog(config)?.upstreamPricing; } function setRemoteModelCatalogOverlaySourcesForTest(sources) { setEnvironmentData(STARTUP_SNAPSHOT_KEY, void 0); readBundledGeneratedAt = sources?.bundledGeneratedAt ?? bundledCatalogGeneratedAt; readStoredCatalog = sources?.readStoredCatalog ?? readRemoteModelCatalog; const readTestCatalog = sources?.readStoredCatalog; readStoredCatalogAsync = readTestCatalog ? async () => readTestCatalog() : readRemoteModelCatalogAsync; } if (process.env.VITEST || false) globalThis[Symbol.for("openclaw.remoteModelCatalogOverlayTestApi")] = { setRemoteModelCatalogOverlaySourcesForTest }; //#endregion //#region src/model-catalog/index.ts function planEffectiveModelCatalogRows(params) { return planManifestModelCatalogRows({ registry: params.registry, includeProvider: (provider, plugin) => isProviderCatalogSourceAllowed({ provider, plugin, config: params.config }), ...params.providerFilter ? { providerFilter: params.providerFilter } : {}, ...params.providerFilters ? { providerFilters: params.providerFilters } : {}, ...params.mergeKeyFilter ? { mergeKeyFilter: params.mergeKeyFilter } : {}, resolveRemoteProvider: (provider) => getRemoteModelCatalogProviderOverlay(params.config, provider), ...params.selection ? { selection: params.selection } : {} }); } //#endregion export { getRemoteModelCatalogUpstreamPricing as a, resolveRemoteCatalogUrl as c, getRemoteModelCatalogPricing as i, parseRemoteModelCatalogWireBundle as l, captureRemoteModelCatalogStartupSnapshot as n, prepareRemoteModelCatalogStartupSnapshot as o, checkRemoteModelCatalogUpdate as r, isRemoteModelCatalogRefreshEnabled as s, planEffectiveModelCatalogRows as t, bundledCatalogGeneratedAt as u };