import { afterEach, describe, expect, it, vi } from "vitest"; import { streamGoogleGeminiCli } from "../src/providers/google-gemini-cli.js"; import type { Context, Model } from "../src/types.js"; const originalFetch = global.fetch; afterEach(() => { global.fetch = originalFetch; vi.restoreAllMocks(); }); describe("google-gemini-cli empty stream retry", () => { it("retries empty SSE responses without duplicate start", async () => { const emptyStream = new ReadableStream({ start(controller) { controller.close(); }, }); const sse = `${[ `data: ${JSON.stringify({ response: { candidates: [ { content: { role: "model", parts: [{ text: "Hello" }] }, finishReason: "STOP", }, ], usageMetadata: { promptTokenCount: 1, candidatesTokenCount: 1, totalTokenCount: 2, }, }, })}`, ].join("\n\n")}\n\n`; const encoder = new TextEncoder(); const dataStream = new ReadableStream({ start(controller) { controller.enqueue(encoder.encode(sse)); controller.close(); }, }); let callCount = 0; const fetchMock = vi.fn(async () => { callCount += 1; if (callCount === 1) { return new Response(emptyStream, { status: 200, headers: { "content-type": "text/event-stream" }, }); } return new Response(dataStream, { status: 200, headers: { "content-type": "text/event-stream" }, }); }); global.fetch = fetchMock as typeof fetch; const model: Model<"google-gemini-cli"> = { id: "gemini-2.5-flash", name: "Gemini 2.5 Flash", api: "google-gemini-cli", provider: "google-gemini-cli", baseUrl: "https://cloudcode-pa.googleapis.com", reasoning: false, input: ["text"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 128000, maxTokens: 8192, }; const context: Context = { messages: [{ role: "user", content: "Say hello", timestamp: Date.now() }], }; const stream = streamGoogleGeminiCli(model, context, { apiKey: JSON.stringify({ token: "token", projectId: "project" }), }); let startCount = 0; let doneCount = 0; let text = ""; for await (const event of stream) { if (event.type === "start") { startCount += 1; } if (event.type === "done") { doneCount += 1; } if (event.type === "text_delta") { text += event.delta; } } const result = await stream.result(); expect(text).toBe("Hello"); expect(result.stopReason).toBe("stop"); expect(startCount).toBe(1); expect(doneCount).toBe(1); expect(fetchMock).toHaveBeenCalledTimes(2); }); });