(body, res, config)
| 248 | } |
| 249 | |
| 250 | async function handleStream(body, res, config) { |
| 251 | const upstreamBody = anthropicToOpenAI(body, true, config) |
| 252 | const upstreamResponse = await fetch( |
| 253 | joinUrl(config.upstreamBaseUrl, config.upstreamChatPath), |
| 254 | { |
| 255 | method: 'POST', |
| 256 | headers: buildUpstreamHeaders(config), |
| 257 | body: JSON.stringify(upstreamBody), |
| 258 | }, |
| 259 | ) |
| 260 | |
| 261 | if (!upstreamResponse.ok || !upstreamResponse.body) { |
| 262 | const text = await upstreamResponse.text() |
| 263 | res.statusCode = upstreamResponse.status |
| 264 | res.setHeader('content-type', 'application/json') |
| 265 | res.end(text) |
| 266 | return |
| 267 | } |
| 268 | |
| 269 | res.writeHead(200, { |
| 270 | 'content-type': 'text/event-stream; charset=utf-8', |
| 271 | 'cache-control': 'no-cache, no-transform', |
| 272 | connection: 'keep-alive', |
| 273 | }) |
| 274 | |
| 275 | const state = { |
| 276 | messageId: `msg_${crypto.randomUUID().replace(/-/g, '')}`, |
| 277 | textStarted: false, |
| 278 | textIndex: 0, |
| 279 | text: '', |
| 280 | finishReason: null, |
| 281 | usage: { input_tokens: 0, output_tokens: 0 }, |
| 282 | toolCalls: new Map(), |
| 283 | } |
| 284 | |
| 285 | writeSse(res, 'message_start', { |
| 286 | type: 'message_start', |
| 287 | message: { |
| 288 | id: state.messageId, |
| 289 | type: 'message', |
| 290 | role: 'assistant', |
| 291 | model: body.model, |
| 292 | content: [], |
| 293 | stop_reason: null, |
| 294 | stop_sequence: null, |
| 295 | usage: state.usage, |
| 296 | }, |
| 297 | }) |
| 298 | |
| 299 | const decoder = new TextDecoder('utf8') |
| 300 | let buffer = '' |
| 301 | |
| 302 | for await (const chunk of upstreamResponse.body) { |
| 303 | buffer += decoder.decode(chunk, { stream: true }) |
| 304 | const parts = buffer.split('\n\n') |
| 305 | buffer = parts.pop() ?? '' |
| 306 | |
| 307 | for (const part of parts) { |
no test coverage detected