mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 16:10:49 +00:00
fix: log pricing fetch timeout duration
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { modelKey } from "../agents/model-selection.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resetLogger, setLoggerOverride } from "../logging/logger.js";
|
||||
import { loggingState } from "../logging/state.js";
|
||||
import type { normalizeProviderModelIdWithPlugin } from "../plugins/provider-runtime.js";
|
||||
import { withFetchPreconnect } from "../test-utils/fetch-mock.js";
|
||||
|
||||
@@ -26,6 +28,8 @@ describe("model-pricing-cache", () => {
|
||||
|
||||
afterEach(() => {
|
||||
__resetGatewayModelPricingCacheForTest();
|
||||
loggingState.rawConsole = null;
|
||||
resetLogger();
|
||||
});
|
||||
|
||||
it("collects configured model refs across defaults, aliases, overrides, and media tools", () => {
|
||||
@@ -515,6 +519,45 @@ describe("model-pricing-cache", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("logs configured timeout seconds when pricing fetches time out", async () => {
|
||||
const warnings: string[] = [];
|
||||
loggingState.rawConsole = {
|
||||
log: vi.fn(),
|
||||
info: vi.fn(),
|
||||
warn: vi.fn((message: string) => warnings.push(message)),
|
||||
error: vi.fn(),
|
||||
};
|
||||
setLoggerOverride({ level: "silent", consoleLevel: "warn" });
|
||||
|
||||
const config = {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "anthropic/claude-opus-4-6" },
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig;
|
||||
const timeoutError = new DOMException(
|
||||
"The operation was aborted due to timeout",
|
||||
"TimeoutError",
|
||||
);
|
||||
const fetchImpl = withFetchPreconnect(async () => {
|
||||
throw timeoutError;
|
||||
});
|
||||
|
||||
await refreshGatewayModelPricingCache({ config, fetchImpl });
|
||||
|
||||
expect(warnings).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.stringContaining(
|
||||
"OpenRouter pricing fetch failed (timeout 15s): TimeoutError: The operation was aborted due to timeout",
|
||||
),
|
||||
expect.stringContaining(
|
||||
"LiteLLM pricing fetch failed (timeout 15s): TimeoutError: The operation was aborted due to timeout",
|
||||
),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("treats oversized LiteLLM catalog responses as source failures", async () => {
|
||||
const config = {
|
||||
agents: {
|
||||
|
||||
@@ -98,6 +98,31 @@ function parseNumberString(value: unknown): number | null {
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
function formatTimeoutSeconds(timeoutMs: number): string {
|
||||
const seconds = timeoutMs / 1000;
|
||||
return Number.isInteger(seconds) ? `${seconds}s` : `${seconds.toFixed(1)}s`;
|
||||
}
|
||||
|
||||
function readErrorName(error: unknown): string | undefined {
|
||||
return error && typeof error === "object" && "name" in error
|
||||
? String((error as { name?: unknown }).name)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function isTimeoutError(error: unknown): boolean {
|
||||
if (readErrorName(error) === "TimeoutError") {
|
||||
return true;
|
||||
}
|
||||
return /\bTimeoutError\b/u.test(String(error));
|
||||
}
|
||||
|
||||
function formatPricingFetchFailure(source: "LiteLLM" | "OpenRouter", error: unknown): string {
|
||||
if (isTimeoutError(error)) {
|
||||
return `${source} pricing fetch failed (timeout ${formatTimeoutSeconds(FETCH_TIMEOUT_MS)}): ${String(error)}`;
|
||||
}
|
||||
return `${source} pricing fetch failed: ${String(error)}`;
|
||||
}
|
||||
|
||||
function toPricePerMillion(value: number | null): number {
|
||||
if (value === null || value < 0 || !Number.isFinite(value)) {
|
||||
return 0;
|
||||
@@ -535,12 +560,12 @@ export async function refreshGatewayModelPricingCache(params: {
|
||||
let litellmFailed = false;
|
||||
const [catalogById, litellmCatalog] = await Promise.all([
|
||||
fetchOpenRouterPricingCatalog(fetchImpl).catch((error: unknown) => {
|
||||
log.warn(`OpenRouter pricing fetch failed: ${String(error)}`);
|
||||
log.warn(formatPricingFetchFailure("OpenRouter", error));
|
||||
openRouterFailed = true;
|
||||
return new Map<string, OpenRouterPricingEntry>();
|
||||
}),
|
||||
fetchLiteLLMPricingCatalog(fetchImpl).catch((error: unknown) => {
|
||||
log.warn(`LiteLLM pricing fetch failed: ${String(error)}`);
|
||||
log.warn(formatPricingFetchFailure("LiteLLM", error));
|
||||
litellmFailed = true;
|
||||
return new Map<string, CachedModelPricing>() as LiteLLMPricingCatalog;
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user