@@ -1305,6 +1305,71 @@ describe("model-pricing-cache", () => {
|
1305 | 1305 | cacheWrite: 0, |
1306 | 1306 | }); |
1307 | 1307 | }); |
| 1308 | + |
| 1309 | +it("bounds streamed LiteLLM catalog responses without content-length", async () => { |
| 1310 | +const config = { |
| 1311 | +agents: { |
| 1312 | +defaults: { |
| 1313 | +model: { primary: "kimi/kimi-k2.6" }, |
| 1314 | +}, |
| 1315 | +}, |
| 1316 | +} as unknown as OpenClawConfig; |
| 1317 | + |
| 1318 | +const liteLLMCancel = vi.fn(async () => undefined); |
| 1319 | +let liteLLMPullCount = 0; |
| 1320 | +const fetchImpl = withFetchPreconnect(async (input: RequestInfo | URL) => { |
| 1321 | +const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url; |
| 1322 | +if (url.includes("openrouter.ai")) { |
| 1323 | +return new Response( |
| 1324 | +JSON.stringify({ |
| 1325 | +data: [ |
| 1326 | +{ |
| 1327 | +id: "moonshotai/kimi-k2.6", |
| 1328 | +pricing: { |
| 1329 | +prompt: "0.00000095", |
| 1330 | +completion: "0.000004", |
| 1331 | +input_cache_read: "0.00000016", |
| 1332 | +}, |
| 1333 | +}, |
| 1334 | +], |
| 1335 | +}), |
| 1336 | +{ |
| 1337 | +status: 200, |
| 1338 | +headers: { "Content-Type": "application/json" }, |
| 1339 | +}, |
| 1340 | +); |
| 1341 | +} |
| 1342 | +return new Response( |
| 1343 | +new ReadableStream({ |
| 1344 | +pull(controller) { |
| 1345 | +liteLLMPullCount += 1; |
| 1346 | +controller.enqueue(new Uint8Array(liteLLMPullCount === 1 ? 5 * 1024 * 1024 : 1)); |
| 1347 | +}, |
| 1348 | +cancel: liteLLMCancel, |
| 1349 | +}), |
| 1350 | +{ |
| 1351 | +status: 200, |
| 1352 | +headers: { "Content-Type": "application/json" }, |
| 1353 | +}, |
| 1354 | +); |
| 1355 | +}); |
| 1356 | + |
| 1357 | +await refreshGatewayModelPricingCache({ config, fetchImpl }); |
| 1358 | + |
| 1359 | +expect(liteLLMPullCount).toBeGreaterThanOrEqual(2); |
| 1360 | +expect(liteLLMCancel).toHaveBeenCalledOnce(); |
| 1361 | +const health = getGatewayModelPricingHealth(); |
| 1362 | +expect(health.sources[0]?.source).toBe("litellm"); |
| 1363 | +expect(health.sources[0]?.detail).toContain( |
| 1364 | +"LiteLLM pricing response too large: 5242881 bytes", |
| 1365 | +); |
| 1366 | +expect(getCachedGatewayModelPricing({ provider: "kimi", model: "kimi-k2.6" })).toEqual({ |
| 1367 | +input: 0.95, |
| 1368 | +output: 4, |
| 1369 | +cacheRead: 0.16, |
| 1370 | +cacheWrite: 0, |
| 1371 | +}); |
| 1372 | +}); |
1308 | 1373 | }); |
1309 | 1374 | |
1310 | 1375 | function createManifestRecord(overrides: Partial<PluginManifestRecord>): PluginManifestRecord { |
|