LLM API proxy plugin — resolves browser CORS restrictions
()
| 254 | |
| 255 | /** LLM API proxy plugin — resolves browser CORS restrictions */ |
| 256 | function llmProxyPlugin(): Plugin { |
| 257 | return { |
| 258 | name: 'llm-proxy', |
| 259 | configureServer(server) { |
| 260 | server.middlewares.use('/api/llm-proxy', async (req, res) => { |
| 261 | const targetUrl = req.headers['x-llm-target-url'] as string; |
| 262 | if (!targetUrl) { |
| 263 | res.writeHead(400, { 'Content-Type': 'application/json' }); |
| 264 | res.end(JSON.stringify({ error: 'Missing X-LLM-Target-URL header' })); |
| 265 | return; |
| 266 | } |
| 267 | const chunks: Buffer[] = []; |
| 268 | req.on('data', (chunk: Buffer) => chunks.push(chunk)); |
| 269 | req.on('end', async () => { |
| 270 | try { |
| 271 | const body = Buffer.concat(chunks).toString(); |
| 272 | const headers: Record<string, string> = {}; |
| 273 | // Forward all headers except host/connection/internal ones |
| 274 | const skipKeys = new Set(['host', 'connection', 'content-length', 'x-llm-target-url']); |
| 275 | for (const [key, val] of Object.entries(req.headers)) { |
| 276 | if (typeof val !== 'string') continue; |
| 277 | if (skipKeys.has(key)) continue; |
| 278 | if (key.startsWith('x-custom-')) { |
| 279 | headers[key.replace('x-custom-', '')] = val; |
| 280 | } else { |
| 281 | headers[key] = val; |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | const fetchRes = await fetch(targetUrl, { |
| 286 | method: req.method || 'POST', |
| 287 | headers, |
| 288 | body, |
| 289 | }); |
| 290 | |
| 291 | res.writeHead(fetchRes.status, { |
| 292 | 'Content-Type': fetchRes.headers.get('Content-Type') || 'application/json', |
| 293 | 'Transfer-Encoding': 'chunked', |
| 294 | }); |
| 295 | |
| 296 | if (fetchRes.body) { |
| 297 | const reader = (fetchRes.body as ReadableStream<Uint8Array>).getReader(); |
| 298 | const pump = async () => { |
| 299 | let done = false; |
| 300 | while (!done) { |
| 301 | const result = await reader.read(); |
| 302 | done = result.done; |
| 303 | if (!done) res.write(result.value); |
| 304 | } |
| 305 | res.end(); |
| 306 | }; |
| 307 | pump().catch(() => res.end()); |
| 308 | } else { |
| 309 | const text = await fetchRes.text(); |
| 310 | res.end(text); |
| 311 | } |
| 312 | } catch (err: unknown) { |
| 313 | res.writeHead(502, { 'Content-Type': 'application/json' }); |