( res: http.ServerResponse, chunks: GeminiResponseChunk[], optionsOrLatency?: number | GeminiStreamOptions, )
| 704 | } |
| 705 | |
| 706 | async function writeGeminiSSEStream( |
| 707 | res: http.ServerResponse, |
| 708 | chunks: GeminiResponseChunk[], |
| 709 | optionsOrLatency?: number | GeminiStreamOptions, |
| 710 | ): Promise<boolean> { |
| 711 | const opts: GeminiStreamOptions = |
| 712 | typeof optionsOrLatency === "number" ? { latency: optionsOrLatency } : (optionsOrLatency ?? {}); |
| 713 | const latency = opts.latency ?? 0; |
| 714 | const profile = opts.streamingProfile; |
| 715 | const { recordedTimings, replaySpeed } = opts; |
| 716 | const signal = opts.signal; |
| 717 | const onChunkSent = opts.onChunkSent; |
| 718 | |
| 719 | if (res.writableEnded) return true; |
| 720 | res.setHeader("Content-Type", "text/event-stream"); |
| 721 | res.setHeader("Cache-Control", "no-cache"); |
| 722 | res.setHeader("Connection", "keep-alive"); |
| 723 | |
| 724 | let chunkIndex = 0; |
| 725 | for (const chunk of chunks) { |
| 726 | const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed); |
| 727 | if (chunkDelay > 0) await delay(chunkDelay, signal); |
| 728 | if (signal?.aborted) return false; |
| 729 | if (res.writableEnded) return true; |
| 730 | // Gemini uses data-only SSE (no event: prefix, no [DONE]) |
| 731 | res.write(`data: ${JSON.stringify(chunk)}\n\n`); |
| 732 | onChunkSent?.(); |
| 733 | if (signal?.aborted) return false; |
| 734 | chunkIndex++; |
| 735 | } |
| 736 | |
| 737 | if (!res.writableEnded) { |
| 738 | res.end(); |
| 739 | } |
| 740 | return true; |
| 741 | } |
| 742 | |
| 743 | // ─── Request handler ──────────────────────────────────────────────────────── |
| 744 |
no test coverage detected
searching dependent graphs…