|
| 1 | +// When in local development we want to proxy to the ai-search route at docs.github.com |
| 2 | + |
| 3 | +import { Router, Request, Response, NextFunction } from 'express' |
| 4 | +import got from 'got' |
| 5 | +import { pipeline } from 'node:stream' |
| 6 | + |
| 7 | +const router = Router() |
| 8 | + |
| 9 | +const hopByHop = new Set([ |
| 10 | + 'connection', |
| 11 | + 'keep-alive', |
| 12 | + 'proxy-authenticate', |
| 13 | + 'proxy-authorization', |
| 14 | + 'te', |
| 15 | + 'trailers', |
| 16 | + 'transfer-encoding', |
| 17 | + 'upgrade', |
| 18 | +]) |
| 19 | + |
| 20 | +function filterRequestHeaders(src: Request['headers']) { |
| 21 | + const out: Record<string, string | string[]> = {} |
| 22 | + for (const [key, value] of Object.entries(src)) { |
| 23 | + if (!value) continue |
| 24 | + const k = key.toLowerCase() |
| 25 | + if (hopByHop.has(k) || k === 'cookie' || k === 'host') continue |
| 26 | + out[key] = value |
| 27 | + } |
| 28 | + out['accept'] = 'application/x-ndjson' |
| 29 | + out['content-type'] = 'application/json' |
| 30 | + return out |
| 31 | +} |
| 32 | + |
| 33 | +router.post('/ai-search/v1', async (req: Request, res: Response, next: NextFunction) => { |
| 34 | + try { |
| 35 | + const upstream = got.stream.post('https://docs.github.com/api/ai-search/v1', { |
| 36 | + headers: filterRequestHeaders(req.headers), |
| 37 | + body: JSON.stringify(req.body ?? {}), |
| 38 | + decompress: false, |
| 39 | + throwHttpErrors: false, |
| 40 | + retry: { limit: 0 }, |
| 41 | + }) |
| 42 | + |
| 43 | + upstream.on('response', (uRes) => { |
| 44 | + res.status(uRes.statusCode || 500) |
| 45 | + |
| 46 | + for (const [k, v] of Object.entries(uRes.headers)) { |
| 47 | + if (!v) continue |
| 48 | + const key = k.toLowerCase() |
| 49 | + // Never forward hop-by-hop; got already handles chunked → strip content-length |
| 50 | + if (hopByHop.has(key) || key === 'content-length') continue |
| 51 | + res.setHeader(k, v as string) |
| 52 | + } |
| 53 | + res.flushHeaders?.() |
| 54 | + }) |
| 55 | + |
| 56 | + pipeline(upstream, res, (err) => { |
| 57 | + if (err) { |
| 58 | + console.error('[ai-search proxy] pipeline error:', err) |
| 59 | + if (!res.headersSent) res.status(502).end('Bad Gateway') |
| 60 | + } |
| 61 | + }) |
| 62 | + |
| 63 | + upstream.on('error', (err) => console.error('[ai-search proxy] upstream error:', err)) |
| 64 | + } catch (err) { |
| 65 | + console.error('[ai-search proxy] request failed:', err) |
| 66 | + next(err) |
| 67 | + } |
| 68 | +}) |
| 69 | + |
| 70 | +export default router |
0 commit comments