openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
219 lines (218 loc) • 10.5 kB
JavaScript
import { D as withPluginCache, c as getPluginCacheRetirementSignal, g as isPluginCacheFactInvalidatedError, o as getPluginCache, x as retainPluginCache } from "./plugin-cache-BABafcXD.mjs";
import { r as cloneEnvWithPlatformSemantics } from "./config-env-vars-DjPHKieF.mjs";
import { r as normalizeProviderId } from "./provider-id-D1VImdKk.mjs";
import { n as buildModelCatalogRef } from "./model-catalog-refs-D3xvQZh0.mjs";
import { r as isInstalledPluginEnabled } from "./installed-plugin-index-BayjZgqB.mjs";
import { t as MODEL_PRICING_SOURCES } from "./model-catalog-pricing-C9-7BMLS.mjs";
import { d as resolvePluginMetadataSnapshotAsync, u as resolvePluginMetadataSnapshot } from "./plugin-metadata-snapshot-pYUEfhY-.mjs";
import { n as createStaticProviderModelIdNormalizer } from "./model-ref-shared-KBg0y299.mjs";
import { f as isRemoteModelCatalogRefreshEnabled, i as getRemoteModelCatalogPricing, o as getRemoteModelCatalogUpstreamPricing, r as getActiveRemoteModelCatalog, s as prepareRemoteModelCatalogStartupSnapshot } from "./remote-overlay-C3A-3o4_.mjs";
import { t as planEffectiveModelCatalogRows } from "./model-catalog-ChjLpeN0.mjs";
import { isIP } from "node:net";
//#region src/model-catalog/pricing.ts
const EMPTY_CONFIG = {};
const pricingContextByConfig = /* @__PURE__ */ new WeakMap();
function readPricingContext(config, catalog) {
const contexts = pricingContextByConfig.get(config);
return catalog ? contexts?.catalogs.get(catalog) : contexts?.absent;
}
function retainPricingContext(config, catalog, context) {
let contexts = pricingContextByConfig.get(config);
if (!contexts) {
contexts = { catalogs: /* @__PURE__ */ new WeakMap() };
pricingContextByConfig.set(config, contexts);
}
if (catalog) contexts.catalogs.set(catalog, context);
else contexts.absent = context;
}
function activeManifestRegistry(snapshot, config) {
if (config.plugins?.enabled === false) return {
plugins: [],
diagnostics: []
};
return {
diagnostics: snapshot.manifestRegistry.diagnostics,
plugins: snapshot.manifestRegistry.plugins.filter((plugin) => isInstalledPluginEnabled(snapshot.index, plugin.id, config))
};
}
function normalizedHostedKey(key, normalizeKey) {
const slash = key.indexOf("/");
if (slash <= 0 || slash === key.length - 1) return;
return normalizeKey(key.slice(0, slash), key.slice(slash + 1));
}
function buildPricingContext(config, snapshot) {
const registry = snapshot ? activeManifestRegistry(snapshot, config) : {
plugins: [],
diagnostics: []
};
const normalizeModel = createStaticProviderModelIdNormalizer({ manifestPlugins: snapshot ?? [] });
const normalizeKey = (provider, model) => {
const providerId = normalizeProviderId(provider);
return buildModelCatalogRef(providerId, normalizeModel(providerId, model.trim()));
};
const catalog = /* @__PURE__ */ new Map();
const rowKeys = /* @__PURE__ */ new Set();
for (const row of planEffectiveModelCatalogRows({
registry,
config
}).rows) {
const ref = buildModelCatalogRef(row.provider, row.id);
rowKeys.add(ref);
if (row.cost) catalog.set(ref, row.cost);
}
const policies = /* @__PURE__ */ new Map();
for (const plugin of registry.plugins) for (const [provider, policy] of Object.entries(plugin.modelPricing?.providers ?? {})) policies.set(provider, {
external: policy.external !== false,
authoritative: MODEL_PRICING_SOURCES.some(({ id, authoritative }) => authoritative && Boolean(policy[id])),
passthroughSources: MODEL_PRICING_SOURCES.flatMap(({ id }) => {
const source = policy[id];
return source && source.passthroughProviderModel ? [id] : [];
})
});
const hosted = snapshot ? getRemoteModelCatalogPricing(config) ?? {} : {};
const upstream = snapshot ? getRemoteModelCatalogUpstreamPricing(config) ?? {} : {};
const policyFree = [...Object.entries(hosted), ...Object.entries(upstream).flatMap(([key, { rates, passthroughOnly }]) => passthroughOnly || !rates[0] ? [] : [[key, {
cost: rates[0].cost,
explicit: false
}]])];
const normalizedHosted = /* @__PURE__ */ new Map();
for (const [key, pricing] of policyFree.toSorted(([a], [b]) => a.localeCompare(b))) {
const normalized = normalizedHostedKey(key, normalizeKey);
if (normalized && normalized !== key && rowKeys.has(normalized)) continue;
if (normalized && !normalizedHosted.has(normalized)) normalizedHosted.set(normalized, pricing);
}
return {
config,
normalizeKey,
catalog,
hosted,
upstream,
normalizedHosted,
policies,
fingerprint: JSON.stringify({
catalog: [...catalog.entries()].toSorted(([a], [b]) => a.localeCompare(b)),
hosted: Object.entries(hosted).toSorted(([a], [b]) => a.localeCompare(b)),
upstream: Object.entries(upstream).toSorted(([a], [b]) => a.localeCompare(b)),
normalizedHosted: [...normalizedHosted.entries()].toSorted(([a], [b]) => a.localeCompare(b)),
policies: [...policies.entries()].toSorted(([a], [b]) => a.localeCompare(b)),
normalization: [...snapshot?.owners.modelIdNormalizationPolicies ?? []].toSorted(([a], [b]) => a.localeCompare(b))
})
};
}
/** Reuses policy and prices for the config's exact accepted catalog generation. */
function resolveModelPricingContext(config = EMPTY_CONFIG) {
const existing = readPricingContext(config, getActiveRemoteModelCatalog(config, false));
if (existing) return existing;
let snapshot;
try {
snapshot = resolvePluginMetadataSnapshot({
config,
env: process.env,
allowWorkspaceScopedCurrent: true
});
} catch {
snapshot = void 0;
}
const context = buildPricingContext(config, snapshot);
retainPricingContext(config, getActiveRemoteModelCatalog(config, false), context);
return context;
}
/** Prepare the existing config-owned context before synchronous per-record pricing. */
async function prepareModelPricingContext(config = EMPTY_CONFIG) {
if (readPricingContext(config, getActiveRemoteModelCatalog(config, false))) return;
const env = cloneEnvWithPlatformSemantics(process.env);
const cache = getPluginCache();
const release = retainPluginCache(cache);
const metadata = cache.metadata;
const signal = getPluginCacheRetirementSignal(cache);
const assertCurrent = () => {
signal.throwIfAborted();
if (cache.metadata !== metadata) throw new Error("Pricing metadata changed during preparation; retry the operation.");
};
try {
await withPluginCache(cache, async () => {
let snapshot;
try {
snapshot = await resolvePluginMetadataSnapshotAsync({
config,
env,
allowWorkspaceScopedCurrent: true
});
} catch (error) {
if (isPluginCacheFactInvalidatedError(error)) throw error;
snapshot = void 0;
}
assertCurrent();
if (snapshot && isRemoteModelCatalogRefreshEnabled(config)) await prepareRemoteModelCatalogStartupSnapshot({ env });
assertCurrent();
const catalog = getActiveRemoteModelCatalog(config, false);
if (!readPricingContext(config, catalog)) {
const context = buildPricingContext(config, snapshot);
assertCurrent();
retainPricingContext(config, catalog, context);
}
});
} finally {
release();
}
}
function hasKnownPricing(pricing) {
return Boolean(pricing.tieredPricing?.some((tier) => tier.input > 0 || tier.output > 0 || tier.cacheRead > 0 || tier.cacheWrite > 0)) || (pricing.input ?? 0) > 0 || (pricing.output ?? 0) > 0 || (pricing.cacheRead ?? 0) > 0 || (pricing.cacheWrite ?? 0) > 0;
}
function isPrivateOrLoopbackHost(hostname) {
const host = hostname.trim().toLowerCase().replace(/^\[|\]$/gu, "");
if (host === "localhost" || host === "localhost.localdomain" || host.endsWith(".localhost") || host.endsWith(".local")) return true;
const addressFamily = isIP(host);
if (addressFamily === 6) return host === "::1" || host === "0:0:0:0:0:0:0:1" || host.startsWith("fe80:") || host.startsWith("fc") || host.startsWith("fd");
if (addressFamily === 4) return host.startsWith("127.") || host.startsWith("10.") || host.startsWith("192.168.") || host.startsWith("169.254.") || /^172\.(1[6-9]|2\d|3[0-1])\./u.test(host);
return false;
}
function isPrivateOrLoopbackUrl(value) {
if (!value) return false;
try {
return isPrivateOrLoopbackHost(new URL(value).hostname);
} catch {
return false;
}
}
/** Resolves catalog-first pricing for a key prepared by this metadata context. */
function resolveModelPricing(context, key) {
const provider = key.slice(0, key.indexOf("/"));
const providerConfig = context.config.models?.providers?.[provider];
const configuredModel = providerConfig?.models?.find((entry) => context.normalizeKey(provider, entry.id) === key);
if (isPrivateOrLoopbackUrl(configuredModel?.baseUrl) || isPrivateOrLoopbackUrl(providerConfig?.baseUrl)) return;
const baseModel = /^openrouter\/([^:]+):(?:nitro|floor)$/u.exec(key)?.[1];
const pricingKeys = baseModel ? [key, `openrouter/${baseModel}`] : [key];
const policy = context.policies.get(provider);
for (const pricingKey of pricingKeys) {
const catalog = context.catalog.get(pricingKey);
if (catalog && hasKnownPricing(catalog)) return catalog;
if (policy?.external === false) return;
const vendorKey = policy?.passthroughSources.length ? pricingKey.slice(provider.length + 1) : void 0;
const vendorRow = vendorKey ? context.catalog.get(vendorKey) : void 0;
const passthrough = vendorRow && hasKnownPricing(vendorRow) ? vendorRow : vendorKey ? context.upstream[vendorKey]?.rates.find(({ source }) => policy?.passthroughSources.includes(source))?.cost : void 0;
const hosted = context.hosted[pricingKey] ?? (policy ? passthrough && {
cost: passthrough,
explicit: false
} : context.normalizedHosted.get(pricingKey));
if (hosted && (!hosted.explicit || catalog) && (hasKnownPricing(hosted.cost) || hosted.explicit || policy?.authoritative)) return hosted.cost;
}
}
function modelCatalogPricingFingerprint(context) {
const resolvedConfig = context.config;
const configuredEndpoints = Object.entries(resolvedConfig.models?.providers ?? {}).toSorted(([a], [b]) => a.localeCompare(b)).map(([provider, providerConfig]) => ({
provider,
baseUrl: providerConfig.baseUrl,
models: (providerConfig.models ?? []).map((model) => ({
id: model.id,
baseUrl: model.baseUrl
})).toSorted((a, b) => a.id.localeCompare(b.id))
}));
return JSON.stringify({
policyVersion: 3,
pricing: context.fingerprint,
configuredEndpoints
});
}
//#endregion
export { resolveModelPricingContext as i, prepareModelPricingContext as n, resolveModelPricing as r, modelCatalogPricingFingerprint as t };