Skip to content

Commit d5eeb85

Browse files
authored
fix local development proxy to docs.github.com for Copilot Search (#56311)
1 parent 17d7646 commit d5eeb85

File tree

2 files changed

+72
-10
lines changed

2 files changed

+72
-10
lines changed

src/frame/middleware/api.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createProxyMiddleware } from 'http-proxy-middleware'
44
import events from '@/events/middleware.js'
55
import anchorRedirect from '@/rest/api/anchor-redirect.js'
66
import aiSearch from '@/search/middleware/ai-search'
7+
import aiSearchLocalProxy from '@/search/middleware/ai-search-local-proxy'
78
import search from '@/search/middleware/search-routes.js'
89
import pageList from '@/article-api/middleware/pagelist'
910
import article from '@/article-api/middleware/article'
@@ -31,16 +32,7 @@ if (process.env.CSE_COPILOT_ENDPOINT || process.env.NODE_ENV === 'test') {
3132
console.log(
3233
'Proxying AI Search requests to docs.github.com. To use the cse-copilot endpoint, set the CSE_COPILOT_ENDPOINT environment variable.',
3334
)
34-
router.use(
35-
'/ai-search',
36-
createProxyMiddleware({
37-
target: 'https://docs.github.com',
38-
changeOrigin: true,
39-
pathRewrite: function (path, req: ExtendedRequest) {
40-
return req.originalUrl
41-
},
42-
}),
43-
)
35+
router.use(aiSearchLocalProxy)
4436
}
4537
if (process.env.ELASTICSEARCH_URL) {
4638
router.use('/search', search)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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

Comments
 (0)