





























@@ -356,4 +356,90 @@ describe("createOllamaStreamFn thinking events", () => {
356356arguments: { path: "/path/to/file", line_start: 1, line_end: 400 },
357357});
358358});
359+360+it("yields to the event loop while processing dense native stream chunks", async () => {
361+const chunks = [
362+ ...Array.from({ length: 65 }, (_value, index) => ({
363+model: "qwen3.5",
364+created_at: `2026-01-01T00:00:${String(index % 60).padStart(2, "0")}Z`,
365+message: { role: "assistant" as const, content: "x" },
366+done: false,
367+})),
368+makeOllamaResponse({ content: "" }),
369+];
370+const body = makeNdjsonBody(chunks);
371+fetchWithSsrFGuardMock.mockResolvedValue({
372+response: new Response(body, { status: 200 }),
373+release: vi.fn(async () => undefined),
374+});
375+376+const streamFn = createOllamaStreamFn("http://localhost:11434");
377+const stream = streamFn(
378+{ api: "ollama", provider: "ollama", id: "qwen3.5", contextWindow: 65536 } as never,
379+{ messages: [{ role: "user", content: "test" }] } as never,
380+{},
381+);
382+383+let timerFired = false;
384+const timerPromise = new Promise<void>((resolve) => {
385+setTimeout(() => {
386+timerFired = true;
387+resolve();
388+}, 0);
389+});
390+let yieldedBeforeDone = false;
391+for await (const event of stream as AsyncIterable<{ type: string }>) {
392+if (timerFired && event.type !== "done") {
393+yieldedBeforeDone = true;
394+}
395+}
396+await timerPromise;
397+398+expect(yieldedBeforeDone).toBe(true);
399+});
400+401+it("reports caller aborts during dense native stream processing as aborted", async () => {
402+const chunks = [
403+ ...Array.from({ length: 65 }, (_value, index) => ({
404+model: "qwen3.5",
405+created_at: `2026-01-01T00:00:${String(index % 60).padStart(2, "0")}Z`,
406+message: { role: "assistant" as const, content: "x" },
407+done: false,
408+})),
409+makeOllamaResponse({ content: "" }),
410+];
411+const body = makeNdjsonBody(chunks);
412+fetchWithSsrFGuardMock.mockResolvedValue({
413+response: new Response(body, { status: 200 }),
414+release: vi.fn(async () => undefined),
415+});
416+417+const controller = new AbortController();
418+const streamFn = createOllamaStreamFn("http://localhost:11434");
419+const stream = streamFn(
420+{ api: "ollama", provider: "ollama", id: "qwen3.5", contextWindow: 65536 } as never,
421+{ messages: [{ role: "user", content: "test" }] } as never,
422+{ signal: controller.signal },
423+);
424+425+setTimeout(() => {
426+controller.abort();
427+}, 0);
428+429+const events: Array<{ type: string; reason?: string; error?: { stopReason?: string } }> = [];
430+for await (const event of stream as AsyncIterable<{
431+type: string;
432+reason?: string;
433+error?: { stopReason?: string };
434+}>) {
435+events.push(event);
436+}
437+438+const lastEvent = events.at(-1);
439+expect(lastEvent).toMatchObject({
440+type: "error",
441+reason: "aborted",
442+error: { stopReason: "aborted" },
443+});
444+});
359445});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。