惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hugging Face - Blog
Hugging Face - Blog
云风的 BLOG
云风的 BLOG
大猫的无限游戏
大猫的无限游戏
M
MIT News - Artificial intelligence
L
LangChain Blog
阮一峰的网络日志
阮一峰的网络日志
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Recent Announcements
Recent Announcements
IT之家
IT之家
Google DeepMind News
Google DeepMind News
罗磊的独立博客
爱范儿
爱范儿
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
U
Unit 42
MongoDB | Blog
MongoDB | Blog
S
SegmentFault 最新的问题
B
Blog
博客园 - 叶小钗
月光博客
月光博客
Stack Overflow Blog
Stack Overflow Blog
V
Visual Studio Blog
C
Check Point Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(gateway): abort stopped pricing refreshes · openclaw/...
steipete · 2026-05-02 · via Recent Commits to openclaw:main

@@ -50,6 +50,17 @@ type OpenRouterModelPayload = {

5050

pricing?: 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+5364

type ExternalPricingPolicy = {

5465

external: boolean;

5566

openRouter?: ExternalPricingSourcePolicy;

@@ -143,6 +154,11 @@ function isTimeoutError(error: unknown): boolean {

143154

return /\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+146162

function formatPricingFetchFailure(source: "LiteLLM" | "OpenRouter", error: unknown): string {

147163

if (isTimeoutError(error)) {

148164

return `${source} pricing fetch failed (timeout ${formatTimeoutSeconds(FETCH_TIMEOUT_MS)}): ${String(error)}`;

@@ -326,10 +342,13 @@ function parseLiteLLMPricing(entry: LiteLLMModelEntry): CachedModelPricing | nul

326342327343

type 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> {

330349

const response = await fetchImpl(LITELLM_PRICING_URL, {

331350

headers: { Accept: "application/json" },

332-

signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),

351+

signal: createPricingFetchSignal(signal),

333352

});

334353

if (!response.ok) {

335354

throw new Error(`LiteLLM pricing fetch failed: HTTP ${response.status}`);

@@ -982,10 +1001,11 @@ export function collectConfiguredModelPricingRefs(

98210019831002

async function fetchOpenRouterPricingCatalog(

9841003

fetchImpl: typeof fetch,

1004+

signal?: AbortSignal,

9851005

): Promise<Map<string, OpenRouterPricingEntry>> {

9861006

const response = await fetchImpl(OPENROUTER_MODELS_URL, {

9871007

headers: { Accept: "application/json" },

988-

signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),

1008+

signal: createPricingFetchSignal(signal),

9891009

});

9901010

if (!response.ok) {

9911011

throw new Error(`OpenRouter /models failed: HTTP ${response.status}`);

@@ -1064,14 +1084,23 @@ function resolveLiteLLMPricingForRef(params: {

10641084

return undefined;

10651085

}

106610861067-

function scheduleRefresh(params: { config: OpenClawConfig; fetchImpl: typeof fetch }): void {

1087+

function scheduleRefresh(

1088+

params: GatewayModelPricingRefreshParams & { fetchImpl: typeof fetch },

1089+

): void {

10681090

clearRefreshTimer();

1091+

if (params.signal?.aborted) {

1092+

return;

1093+

}

10691094

refreshTimer = setTimeout(() => {

10701095

refreshTimer = null;

1096+

if (params.signal?.aborted) {

1097+

return;

1098+

}

10711099

void refreshGatewayModelPricingCache(params).catch((error: unknown) => {

10721100

log.warn(`pricing refresh failed: ${String(error)}`);

10731101

});

10741102

}, CACHE_TTL_MS);

1103+

refreshTimer.unref?.();

10751104

}

1076110510771106

function collectSeededPricing(params: {

@@ -1100,19 +1129,16 @@ function collectSeededPricing(params: {

11001129

return 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> {

11121135

if (!isGatewayModelPricingEnabled(params.config)) {

11131136

clearRefreshTimer();

11141137

return;

11151138

}

1139+

if (params.signal?.aborted) {

1140+

return;

1141+

}

11161142

if (inFlightRefresh) {

11171143

return await inFlightRefresh;

11181144

}

@@ -1147,6 +1173,9 @@ export async function refreshGatewayModelPricingCache(params: {

11471173

allowPluginNormalization: normalizationOptions.allowPluginNormalization,

11481174

});

11491175

if (refs.length === 0) {

1176+

if (params.signal?.aborted) {

1177+

return;

1178+

}

11501179

replaceGatewayModelPricingCache(seededPricing);

11511180

clearRefreshTimer();

11521181

return;

@@ -1157,18 +1186,22 @@ export async function refreshGatewayModelPricingCache(params: {

11571186

let openRouterFailed = false;

11581187

let litellmFailed = false;

11591188

const [catalogById, litellmCatalog] = await Promise.all([

1160-

fetchOpenRouterPricingCatalog(fetchImpl).catch((error: unknown) => {

1189+

fetchOpenRouterPricingCatalog(fetchImpl, params.signal).catch((error: unknown) => {

11611190

log.warn(formatPricingFetchFailure("OpenRouter", error));

11621191

openRouterFailed = true;

11631192

return new Map<string, OpenRouterPricingEntry>();

11641193

}),

1165-

fetchLiteLLMPricingCatalog(fetchImpl).catch((error: unknown) => {

1194+

fetchLiteLLMPricingCatalog(fetchImpl, params.signal).catch((error: unknown) => {

11661195

log.warn(formatPricingFetchFailure("LiteLLM", error));

11671196

litellmFailed = true;

11681197

return new Map<string, CachedModelPricing>() as LiteLLMPricingCatalog;

11691198

}),

11701199

]);

117112001201+

if (params.signal?.aborted) {

1202+

return;

1203+

}

1204+11721205

const catalogByNormalizedId = new Map<string, OpenRouterPricingEntry>();

11731206

for (const entry of catalogById.values()) {

11741207

const normalizedId = canonicalizeOpenRouterLookupId(entry.id, normalizationOptions);

@@ -1226,7 +1259,7 @@ export async function refreshGatewayModelPricingCache(params: {

12261259

if (nextPricing.size === 0 && existingMeta.size > 0) {

12271260

// Both sources failed — retain the entire existing cache.

12281261

log.warn("Both pricing sources returned empty data — retaining existing cache");

1229-

scheduleRefresh({ config: params.config, fetchImpl });

1262+

scheduleRefresh({ ...params, fetchImpl });

12301263

return;

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+

}

12471283

replaceGatewayModelPricingCache(nextPricing);

1248-

scheduleRefresh({ config: params.config, fetchImpl });

1284+

scheduleRefresh({ ...params, fetchImpl });

12491285

})();

1250128612511287

try {

@@ -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 {

12671297

if (!isGatewayModelPricingEnabled(params.config)) {

12681298

clearRefreshTimer();

12691299

return () => {};

12701300

}

12711301

let stopped = false;

1302+

const abortController = new AbortController();

12721303

queueMicrotask(() => {

12731304

if (stopped) {

12741305

return;

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

});

12801313

return () => {

12811314

stopped = true;

1315+

abortController.abort();

12821316

clearRefreshTimer();

12831317

};

12841318

}