Skip to content

Commit 773b540

Browse files
committed
add server
1 parent 50cc5a9 commit 773b540

File tree

5 files changed

+54
-0
lines changed

5 files changed

+54
-0
lines changed

backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"cors": "^2.8.5",
3737
"diff": "5.2.0",
3838
"dotenv": "16.4.5",
39+
"eventsource": "^4.0.0",
3940
"express": "4.19.2",
4041
"gpt-tokenizer": "2.8.1",
4142
"ignore": "5.3.2",
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { Request, Response } from 'express'
2+
3+
const RECONNECT_TIME_MS = 5000
4+
5+
export function completionsStreamHandler(req: Request, res: Response) {
6+
// Mandatory SSE headers
7+
res.setHeader('Content-Type', 'text/event-stream')
8+
res.setHeader('Cache-Control', 'no-cache')
9+
res.setHeader('Connection', 'keep-alive')
10+
// (optional) allow local browser demos
11+
res.setHeader('Access-Control-Allow-Origin', '*')
12+
13+
// Flush the headers immediately
14+
res.flushHeaders?.()
15+
16+
// Recommended: send a comment or retry hint right away so the client knows we're live
17+
res.write(`: connected ${new Date().toISOString()}\n`)
18+
const heartbeat = setInterval(() => {
19+
res.write(`: heartbeat ${new Date().toISOString()}\n`)
20+
}, 30000)
21+
res.write(`retry: ${RECONNECT_TIME_MS}\n\n`)
22+
23+
// Send a few messages, then end
24+
const messages = ['hello', 'from', 'the', 'server']
25+
let i = 0
26+
27+
const timer = setInterval(() => {
28+
if (i >= messages.length) {
29+
// End the SSE stream gracefully
30+
res.write('event: asdf\ndata: bye\n\n')
31+
clearInterval(timer)
32+
clearInterval(heartbeat)
33+
res.end()
34+
return
35+
}
36+
// Each SSE message must end with a blank line
37+
res.write(`data: ${messages[i++]}\n\n`)
38+
}, 600)
39+
40+
// Clean up if the client disconnects
41+
req.on('close', () => {
42+
clearInterval(timer)
43+
clearInterval(heartbeat)
44+
res.end()
45+
})
46+
}

backend/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
relabelForUserHandler,
1212
} from './admin/relabelRuns'
1313
import { validateAgentNameHandler } from './api/agents'
14+
import { completionsStreamHandler } from './api/chat/completions'
1415
import { isRepoCoveredHandler } from './api/org'
1516
import usageHandler from './api/usage'
1617
import { checkAdmin } from './util/check-auth'
@@ -37,6 +38,7 @@ app.get('/healthz', (req, res) => {
3738
res.send('ok')
3839
})
3940

41+
app.post('/api/chat/completions', completionsStreamHandler)
4042
app.post('/api/usage', usageHandler)
4143
app.post('/api/orgs/is-repo-covered', isRepoCoveredHandler)
4244
app.get('/api/agents/validate-name', validateAgentNameHandler)

bun.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
},
4040
"dependencies": {
4141
"@t3-oss/env-nextjs": "^0.7.3",
42+
"eventsource": "^4.0.0",
4243
"zod": "3.25.67"
4344
},
4445
"overrides": {

0 commit comments

Comments
 (0)