( res: http.ServerResponse, chunks: SSEChunk[], optionsOrLatency?: number | StreamOptions, )
| 79 | } |
| 80 | |
| 81 | export async function writeSSEStream( |
| 82 | res: http.ServerResponse, |
| 83 | chunks: SSEChunk[], |
| 84 | optionsOrLatency?: number | StreamOptions, |
| 85 | ): Promise<boolean> { |
| 86 | const opts: StreamOptions = |
| 87 | typeof optionsOrLatency === "number" ? { latency: optionsOrLatency } : (optionsOrLatency ?? {}); |
| 88 | const latency = opts.latency ?? 0; |
| 89 | const profile = opts.streamingProfile; |
| 90 | const { recordedTimings, replaySpeed } = opts; |
| 91 | const signal = opts.signal; |
| 92 | const onChunkSent = opts.onChunkSent; |
| 93 | |
| 94 | if (res.writableEnded) return true; |
| 95 | res.setHeader("Content-Type", "text/event-stream"); |
| 96 | res.setHeader("Cache-Control", "no-cache"); |
| 97 | res.setHeader("Connection", "keep-alive"); |
| 98 | |
| 99 | let chunkIndex = 0; |
| 100 | for (const chunk of chunks) { |
| 101 | const chunkDelay = calculateDelay(chunkIndex, profile, latency, recordedTimings, replaySpeed); |
| 102 | if (chunkDelay > 0) { |
| 103 | await delay(chunkDelay, signal); |
| 104 | } |
| 105 | if (signal?.aborted) return false; |
| 106 | if (res.writableEnded) return true; |
| 107 | res.write(`data: ${JSON.stringify(chunk)}\n\n`); |
| 108 | onChunkSent?.(); |
| 109 | if (signal?.aborted) return false; |
| 110 | chunkIndex++; |
| 111 | } |
| 112 | |
| 113 | if (!res.writableEnded) { |
| 114 | if (opts.usageChunk) { |
| 115 | res.write(`data: ${JSON.stringify(opts.usageChunk)}\n\n`); |
| 116 | } |
| 117 | res.write("data: [DONE]\n\n"); |
| 118 | res.end(); |
| 119 | } |
| 120 | return true; |
| 121 | } |
| 122 | |
| 123 | /** |
| 124 | * Default rate-limit response headers matching OpenAI's format. |
no test coverage detected
searching dependent graphs…