
























@@ -50,6 +50,17 @@ type OpenRouterModelPayload = {
5050pricing?: unknown;
5151};
525253+type GatewayModelPricingRefreshParams = {
54+config: OpenClawConfig;
55+env?: NodeJS.ProcessEnv;
56+fetchImpl?: typeof fetch;
57+workspaceDir?: string;
58+pluginMetadataSnapshot?: PluginMetadataRegistryView;
59+pluginLookUpTable?: PluginMetadataRegistryView;
60+manifestRegistry?: PluginManifestRegistry;
61+signal?: AbortSignal;
62+};
63+5364type ExternalPricingPolicy = {
5465external: boolean;
5566openRouter?: ExternalPricingSourcePolicy;
@@ -143,6 +154,11 @@ function isTimeoutError(error: unknown): boolean {
143154return /\bTimeoutError\b/u.test(String(error));
144155}
145156157+function createPricingFetchSignal(signal: AbortSignal | undefined): AbortSignal {
158+const timeoutSignal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
159+return signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
160+}
161+146162function formatPricingFetchFailure(source: "LiteLLM" | "OpenRouter", error: unknown): string {
147163if (isTimeoutError(error)) {
148164return `${source} pricing fetch failed (timeout ${formatTimeoutSeconds(FETCH_TIMEOUT_MS)}): ${String(error)}`;
@@ -326,10 +342,13 @@ function parseLiteLLMPricing(entry: LiteLLMModelEntry): CachedModelPricing | nul
326342327343type LiteLLMPricingCatalog = Map<string, CachedModelPricing>;
328344329-async function fetchLiteLLMPricingCatalog(fetchImpl: typeof fetch): Promise<LiteLLMPricingCatalog> {
345+async function fetchLiteLLMPricingCatalog(
346+fetchImpl: typeof fetch,
347+signal?: AbortSignal,
348+): Promise<LiteLLMPricingCatalog> {
330349const response = await fetchImpl(LITELLM_PRICING_URL, {
331350headers: { Accept: "application/json" },
332-signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
351+signal: createPricingFetchSignal(signal),
333352});
334353if (!response.ok) {
335354throw new Error(`LiteLLM pricing fetch failed: HTTP ${response.status}`);
@@ -982,10 +1001,11 @@ export function collectConfiguredModelPricingRefs(
98210019831002async function fetchOpenRouterPricingCatalog(
9841003fetchImpl: typeof fetch,
1004+signal?: AbortSignal,
9851005): Promise<Map<string, OpenRouterPricingEntry>> {
9861006const response = await fetchImpl(OPENROUTER_MODELS_URL, {
9871007headers: { Accept: "application/json" },
988-signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
1008+signal: createPricingFetchSignal(signal),
9891009});
9901010if (!response.ok) {
9911011throw new Error(`OpenRouter /models failed: HTTP ${response.status}`);
@@ -1064,14 +1084,23 @@ function resolveLiteLLMPricingForRef(params: {
10641084return undefined;
10651085}
106610861067-function scheduleRefresh(params: { config: OpenClawConfig; fetchImpl: typeof fetch }): void {
1087+function scheduleRefresh(
1088+params: GatewayModelPricingRefreshParams & { fetchImpl: typeof fetch },
1089+): void {
10681090clearRefreshTimer();
1091+if (params.signal?.aborted) {
1092+return;
1093+}
10691094refreshTimer = setTimeout(() => {
10701095refreshTimer = null;
1096+if (params.signal?.aborted) {
1097+return;
1098+}
10711099void refreshGatewayModelPricingCache(params).catch((error: unknown) => {
10721100log.warn(`pricing refresh failed: ${String(error)}`);
10731101});
10741102}, CACHE_TTL_MS);
1103+refreshTimer.unref?.();
10751104}
1076110510771106function collectSeededPricing(params: {
@@ -1100,19 +1129,16 @@ function collectSeededPricing(params: {
11001129return seeded;
11011130}
110211311103-export async function refreshGatewayModelPricingCache(params: {
1104-config: OpenClawConfig;
1105-env?: NodeJS.ProcessEnv;
1106-fetchImpl?: typeof fetch;
1107-workspaceDir?: string;
1108-pluginMetadataSnapshot?: PluginMetadataRegistryView;
1109-pluginLookUpTable?: PluginMetadataRegistryView;
1110-manifestRegistry?: PluginManifestRegistry;
1111-}): Promise<void> {
1132+export async function refreshGatewayModelPricingCache(
1133+params: GatewayModelPricingRefreshParams,
1134+): Promise<void> {
11121135if (!isGatewayModelPricingEnabled(params.config)) {
11131136clearRefreshTimer();
11141137return;
11151138}
1139+if (params.signal?.aborted) {
1140+return;
1141+}
11161142if (inFlightRefresh) {
11171143return await inFlightRefresh;
11181144}
@@ -1147,6 +1173,9 @@ export async function refreshGatewayModelPricingCache(params: {
11471173allowPluginNormalization: normalizationOptions.allowPluginNormalization,
11481174});
11491175if (refs.length === 0) {
1176+if (params.signal?.aborted) {
1177+return;
1178+}
11501179replaceGatewayModelPricingCache(seededPricing);
11511180clearRefreshTimer();
11521181return;
@@ -1157,18 +1186,22 @@ export async function refreshGatewayModelPricingCache(params: {
11571186let openRouterFailed = false;
11581187let litellmFailed = false;
11591188const [catalogById, litellmCatalog] = await Promise.all([
1160-fetchOpenRouterPricingCatalog(fetchImpl).catch((error: unknown) => {
1189+fetchOpenRouterPricingCatalog(fetchImpl, params.signal).catch((error: unknown) => {
11611190log.warn(formatPricingFetchFailure("OpenRouter", error));
11621191openRouterFailed = true;
11631192return new Map<string, OpenRouterPricingEntry>();
11641193}),
1165-fetchLiteLLMPricingCatalog(fetchImpl).catch((error: unknown) => {
1194+fetchLiteLLMPricingCatalog(fetchImpl, params.signal).catch((error: unknown) => {
11661195log.warn(formatPricingFetchFailure("LiteLLM", error));
11671196litellmFailed = true;
11681197return new Map<string, CachedModelPricing>() as LiteLLMPricingCatalog;
11691198}),
11701199]);
117112001201+if (params.signal?.aborted) {
1202+return;
1203+}
1204+11721205const catalogByNormalizedId = new Map<string, OpenRouterPricingEntry>();
11731206for (const entry of catalogById.values()) {
11741207const normalizedId = canonicalizeOpenRouterLookupId(entry.id, normalizationOptions);
@@ -1226,7 +1259,7 @@ export async function refreshGatewayModelPricingCache(params: {
12261259if (nextPricing.size === 0 && existingMeta.size > 0) {
12271260// Both sources failed — retain the entire existing cache.
12281261log.warn("Both pricing sources returned empty data — retaining existing cache");
1229-scheduleRefresh({ config: params.config, fetchImpl });
1262+scheduleRefresh({ ...params, fetchImpl });
12301263return;
12311264}
12321265// Partial failure — back-fill missing models from the existing cache.
@@ -1244,8 +1277,11 @@ export async function refreshGatewayModelPricingCache(params: {
12441277}
12451278}
124612791280+if (params.signal?.aborted) {
1281+return;
1282+}
12471283replaceGatewayModelPricingCache(nextPricing);
1248-scheduleRefresh({ config: params.config, fetchImpl });
1284+scheduleRefresh({ ...params, fetchImpl });
12491285})();
1250128612511287try {
@@ -1255,30 +1291,28 @@ export async function refreshGatewayModelPricingCache(params: {
12551291}
12561292}
125712931258-export function startGatewayModelPricingRefresh(params: {
1259-config: OpenClawConfig;
1260-env?: NodeJS.ProcessEnv;
1261-fetchImpl?: typeof fetch;
1262-workspaceDir?: string;
1263-pluginMetadataSnapshot?: PluginMetadataRegistryView;
1264-pluginLookUpTable?: PluginMetadataRegistryView;
1265-manifestRegistry?: PluginManifestRegistry;
1266-}): () => void {
1294+export function startGatewayModelPricingRefresh(
1295+params: GatewayModelPricingRefreshParams,
1296+): () => void {
12671297if (!isGatewayModelPricingEnabled(params.config)) {
12681298clearRefreshTimer();
12691299return () => {};
12701300}
12711301let stopped = false;
1302+const abortController = new AbortController();
12721303queueMicrotask(() => {
12731304if (stopped) {
12741305return;
12751306}
1276-void refreshGatewayModelPricingCache(params).catch((error: unknown) => {
1277-log.warn(`pricing bootstrap failed: ${String(error)}`);
1278-});
1307+void refreshGatewayModelPricingCache({ ...params, signal: abortController.signal }).catch(
1308+(error: unknown) => {
1309+log.warn(`pricing bootstrap failed: ${String(error)}`);
1310+},
1311+);
12791312});
12801313return () => {
12811314stopped = true;
1315+abortController.abort();
12821316clearRefreshTimer();
12831317};
12841318}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。