



















@@ -312,6 +312,104 @@ describe("openrouter-model-capabilities", () => {
312312});
313313});
314314315+it("bounds an oversized streamed OpenRouter catalog instead of buffering it whole", async () => {
316+await withOpenRouterStateDir(async () => {
317+// First pull emits a chunk larger than the cap; a well-behaved bounded read
318+// must cancel before requesting the (effectively infinite) second chunk.
319+let pullCount = 0;
320+const cancel = vi.fn(async () => undefined);
321+const stream = new ReadableStream<Uint8Array>({
322+pull(controller) {
323+pullCount += 1;
324+controller.enqueue(new Uint8Array(pullCount === 1 ? 16 * 1024 * 1024 + 1 : 1));
325+},
326+ cancel,
327+});
328+const fetchSpy = vi.fn(
329+async () =>
330+new Response(stream, {
331+status: 200,
332+headers: { "content-type": "application/json" },
333+}),
334+);
335+vi.stubGlobal("fetch", fetchSpy);
336+337+const module = await importOpenRouterModelCapabilities("oversized-stream");
338+await module.loadOpenRouterModelCapabilities("acme/anything");
339+340+// The body was cancelled after the first oversized chunk rather than read
341+// to completion, and the overflow left no poisoned cache entry behind.
342+expect(fetchSpy).toHaveBeenCalledTimes(1);
343+expect(pullCount).toBeLessThanOrEqual(2);
344+expect(cancel).toHaveBeenCalledOnce();
345+expect(module.getOpenRouterModelCapabilities("acme/anything")).toBeUndefined();
346+expect(fetchSpy).toHaveBeenCalledTimes(1);
347+});
348+});
349+350+it("round-trips a chunked under-cap catalog through the SQLite cache", async () => {
351+await withOpenRouterStateDir(async () => {
352+// Stream the payload across several small chunks so the bounded reader has to
353+// reassemble it; the reassembled bytes must parse and survive a cross-import
354+// SQLite read-back identical to the source catalog.
355+const payload = JSON.stringify({
356+data: [
357+{
358+id: "acme/chunked-model",
359+name: "Chunked Model",
360+architecture: { modality: "text+image->text" },
361+supported_parameters: ["reasoning", "tools"],
362+context_length: 13579,
363+max_completion_tokens: 2468,
364+pricing: { prompt: "0.000007", completion: "0.000008" },
365+},
366+],
367+});
368+const encoded = new TextEncoder().encode(payload);
369+const fetchSpy = vi.fn(async () => {
370+let offset = 0;
371+const stream = new ReadableStream<Uint8Array>({
372+pull(controller) {
373+if (offset >= encoded.length) {
374+controller.close();
375+return;
376+}
377+const end = Math.min(offset + 8, encoded.length);
378+controller.enqueue(encoded.subarray(offset, end));
379+offset = end;
380+},
381+});
382+return new Response(stream, {
383+status: 200,
384+headers: { "content-type": "application/json" },
385+});
386+});
387+vi.stubGlobal("fetch", fetchSpy);
388+389+const writer = await importOpenRouterModelCapabilities("chunked-sqlite-writer");
390+await writer.loadOpenRouterModelCapabilities("acme/chunked-model");
391+expect(fetchSpy).toHaveBeenCalledTimes(1);
392+expect(writer.getOpenRouterModelCapabilities("acme/chunked-model")).toMatchObject({
393+input: ["text", "image"],
394+reasoning: true,
395+supportsTools: true,
396+contextWindow: 13579,
397+maxTokens: 2468,
398+});
399+400+// Fresh import reads only from the SQLite cache the bounded read populated.
401+const reader = await importOpenRouterModelCapabilities("chunked-sqlite-reader");
402+expect(reader.getOpenRouterModelCapabilities("acme/chunked-model")).toMatchObject({
403+input: ["text", "image"],
404+reasoning: true,
405+supportsTools: true,
406+contextWindow: 13579,
407+maxTokens: 2468,
408+});
409+expect(fetchSpy).toHaveBeenCalledTimes(1);
410+});
411+});
412+315413it("does not refetch immediately after an awaited miss for the same model id", async () => {
316414await withOpenRouterStateDir(async () => {
317415const fetchSpy = vi.fn(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。