Files
openclaw/src/gateway/model-pricing-cache-state.ts
2026-03-28 11:43:12 +00:00

108 lines
2.8 KiB
TypeScript

import { normalizeModelRef } from "../agents/model-selection.js";
import { normalizeProviderId } from "../agents/provider-id.js";
export type CachedModelPricing = {
input: number;
output: number;
cacheRead: number;
cacheWrite: number;
};
let cachedPricing = new Map<string, CachedModelPricing>();
let cachedAt = 0;
const WRAPPER_PROVIDERS = new Set([
"cloudflare-ai-gateway",
"kilocode",
"openrouter",
"vercel-ai-gateway",
]);
function modelPricingCacheKey(provider: string, model: string): string {
const providerId = normalizeProviderId(provider);
const modelId = model.trim();
if (!providerId || !modelId) {
return "";
}
return modelId.toLowerCase().startsWith(`${providerId.toLowerCase()}/`)
? modelId
: `${providerId}/${modelId}`;
}
function shouldNormalizeCachedPricingLookup(provider: string): boolean {
const normalized = normalizeProviderId(provider);
return (
normalized === "anthropic" ||
normalized === "openrouter" ||
normalized === "xai" ||
WRAPPER_PROVIDERS.has(normalized)
);
}
export function replaceGatewayModelPricingCache(
nextPricing: Map<string, CachedModelPricing>,
nextCachedAt = Date.now(),
): void {
cachedPricing = nextPricing;
cachedAt = nextCachedAt;
}
export function clearGatewayModelPricingCacheState(): void {
cachedPricing = new Map();
cachedAt = 0;
}
export function getCachedGatewayModelPricing(params: {
provider?: string;
model?: string;
}): CachedModelPricing | undefined {
const provider = params.provider?.trim();
const model = params.model?.trim();
if (!provider || !model) {
return undefined;
}
const key = modelPricingCacheKey(provider, model);
const direct = key ? cachedPricing.get(key) : undefined;
if (direct) {
return direct;
}
if (!shouldNormalizeCachedPricingLookup(provider)) {
return undefined;
}
const normalized = normalizeModelRef(provider, model);
const normalizedKey = modelPricingCacheKey(normalized.provider, normalized.model);
return normalizedKey ? cachedPricing.get(normalizedKey) : undefined;
}
export function getGatewayModelPricingCacheMeta(): {
cachedAt: number;
ttlMs: number;
size: number;
} {
return {
cachedAt,
ttlMs: 0,
size: cachedPricing.size,
};
}
export function __resetGatewayModelPricingCacheForTest(): void {
clearGatewayModelPricingCacheState();
}
export function __setGatewayModelPricingForTest(
entries: Array<{ provider: string; model: string; pricing: CachedModelPricing }>,
): void {
replaceGatewayModelPricingCache(
new Map(
entries.flatMap((entry) => {
const normalized = normalizeModelRef(entry.provider, entry.model, {
allowPluginNormalization: false,
});
const key = modelPricingCacheKey(normalized.provider, normalized.model);
return key ? ([[key, entry.pricing]] as const) : [];
}),
),
);
}