fix: record pricing refresh health failures

This commit is contained in:
Peter Steinberger
2026-05-10 14:44:06 +01:00
parent 02d3fe343d
commit fa2b97da4a
3 changed files with 85 additions and 2 deletions

View File

@@ -20,7 +20,7 @@ export type CachedModelPricing = {
tieredPricing?: CachedPricingTier[];
};
export type GatewayModelPricingHealthSource = "openrouter" | "litellm" | "bootstrap";
export type GatewayModelPricingHealthSource = "openrouter" | "litellm" | "bootstrap" | "refresh";
export type GatewayModelPricingHealth = {
state: "ok" | "degraded" | "disabled";

View File

@@ -456,6 +456,84 @@ describe("model-pricing-cache", () => {
});
});
it("records and clears scheduled refresh rejections for health surfaces", async () => {
vi.useFakeTimers();
try {
const manifestRegistry: PluginManifestRegistry = { diagnostics: [], plugins: [] };
let failManifestRead = false;
const pluginMetadataSnapshot = {
index: { plugins: [] } as never,
get manifestRegistry() {
if (failManifestRead) {
throw new Error("manifest metadata failed");
}
return manifestRegistry;
},
};
const config = {
agents: {
defaults: {
model: { primary: "custom/gpt-remote" },
},
},
models: {
providers: {
custom: {
baseUrl: "https://models.example/v1",
api: "openai-completions",
models: [{ id: "gpt-remote" }],
},
},
},
} as unknown as OpenClawConfig;
const fetchImpl = withFetchPreconnect(async (input: RequestInfo | URL) => {
const url =
typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
return new Response(JSON.stringify(url.includes("openrouter.ai") ? { data: [] } : {}), {
status: 200,
headers: { "Content-Type": "application/json" },
});
});
await refreshGatewayModelPricingCache({
config,
fetchImpl,
pluginMetadataSnapshot,
});
expect(getGatewayModelPricingHealth()).toEqual({
state: "ok",
sources: [],
});
failManifestRead = true;
await vi.runOnlyPendingTimersAsync();
expect(getGatewayModelPricingHealth()).toMatchObject({
state: "degraded",
sources: [
{
source: "refresh",
state: "degraded",
detail: "pricing refresh failed: Error: manifest metadata failed",
},
],
});
failManifestRead = false;
await refreshGatewayModelPricingCache({
config,
fetchImpl,
pluginMetadataSnapshot,
});
expect(getGatewayModelPricingHealth()).toEqual({
state: "ok",
sources: [],
});
} finally {
vi.useRealTimers();
}
});
it("seeds pricing from explicit configured model cost without external catalog fetches", async () => {
const config = {
agents: {

View File

@@ -1125,7 +1125,11 @@ function scheduleRefresh(
return;
}
void refreshGatewayModelPricingCache(params).catch((error: unknown) => {
log.warn(`pricing refresh failed: ${String(error)}`);
const message = `pricing refresh failed: ${String(error)}`;
log.warn(message);
if (!params.signal?.aborted) {
recordGatewayModelPricingSourceFailure("refresh", message);
}
});
}, CACHE_TTL_MS);
refreshTimer.unref?.();
@@ -1340,6 +1344,7 @@ export async function refreshGatewayModelPricingCache(
return;
}
clearGatewayModelPricingSourceFailure("bootstrap");
clearGatewayModelPricingSourceFailure("refresh");
replaceGatewayModelPricingCache(nextPricing);
scheduleRefresh({ ...params, fetchImpl });
})();