| 130 | } |
| 131 | |
| 132 | function writeSseCompletion(res: ServerResponse, model: string, content: string): void { |
| 133 | const id = `chatcmpl-e2e-stream-${Date.now()}`; |
| 134 | const created = Math.floor(Date.now() / 1000); |
| 135 | res.writeHead(200, { |
| 136 | "Content-Type": "text/event-stream", |
| 137 | "Cache-Control": "no-cache", |
| 138 | Connection: "keep-alive", |
| 139 | }); |
| 140 | res.write(": heartbeat\n\n"); |
| 141 | |
| 142 | const chunks = content.match(/.{1,32}/g) ?? [content]; |
| 143 | for (const chunk of chunks) { |
| 144 | res.write( |
| 145 | `data: ${JSON.stringify({ |
| 146 | id, |
| 147 | object: "chat.completion.chunk", |
| 148 | created, |
| 149 | model, |
| 150 | choices: [{ index: 0, delta: { content: chunk }, finish_reason: null }], |
| 151 | })}\n\n`, |
| 152 | ); |
| 153 | } |
| 154 | |
| 155 | res.write( |
| 156 | `data: ${JSON.stringify({ |
| 157 | id, |
| 158 | object: "chat.completion.chunk", |
| 159 | created, |
| 160 | model, |
| 161 | choices: [{ index: 0, delta: {}, finish_reason: "stop" }], |
| 162 | })}\n\n`, |
| 163 | ); |
| 164 | res.write("data: [DONE]\n\n"); |
| 165 | res.end(); |
| 166 | } |
| 167 | |
| 168 | function asRecord(value: unknown): Record<string, unknown> { |
| 169 | return value && typeof value === "object" ? (value as Record<string, unknown>) : {}; |