-
Notifications
You must be signed in to change notification settings - Fork 519
Expand file tree
/
Copy pathindex.ts
More file actions
188 lines (160 loc) · 4.71 KB
/
index.ts
File metadata and controls
188 lines (160 loc) · 4.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
import http from 'http'
import { setupBigQuery } from '@codebuff/bigquery'
import { flushAnalytics, initAnalytics } from '@codebuff/common/analytics'
import { env } from '@codebuff/internal'
import cors from 'cors'
import express from 'express'
import {
getTracesForUserHandler,
relabelForUserHandler,
} from './admin/relabelRuns'
import { validateAgentNameHandler } from './api/agents'
import { isRepoCoveredHandler } from './api/org'
import usageHandler from './api/usage'
import { completionsStreamHandler } from './api/v1/chat/completions'
import { checkAdmin } from './util/check-auth'
import { logger } from './util/logger'
import {
sendRequestReconnect,
waitForAllClientsDisconnected,
listen as webSocketListen,
} from './websockets/server'
// Grace period for graceful shutdown
const SHUTDOWN_GRACE_PERIOD_MS = 30 * 60 * 1000
const app = express()
const port = env.PORT
app.use(express.json())
app.get('/', (req, res) => {
res.send('Codebuff Backend Server')
})
app.get('/healthz', (req, res) => {
res.send('ok')
})
app.post('/api/usage', usageHandler)
app.post('/api/orgs/is-repo-covered', isRepoCoveredHandler)
app.get('/api/agents/validate-name', validateAgentNameHandler)
// Enable CORS for preflight requests to the admin relabel endpoint
app.options('/api/admin/relabel-for-user', cors())
// Add the admin routes with CORS and auth
app.get(
'/api/admin/relabel-for-user',
cors(),
checkAdmin,
getTracesForUserHandler,
)
app.post(
'/api/admin/relabel-for-user',
cors(),
checkAdmin,
relabelForUserHandler,
)
// Openai compatible completions API
app.post('/api/v1/chat/completions', completionsStreamHandler)
app.use(
(
err: Error,
req: express.Request,
res: express.Response,
next: express.NextFunction,
) => {
logger.error({ err }, 'Something broke!')
res.status(500).send('Something broke!')
},
)
// Initialize BigQuery before starting the server
setupBigQuery().catch((err) => {
logger.error(
{
error: err,
stack: err.stack,
message: err.message,
name: err.name,
code: err.code,
details: err.details,
},
'Failed to initialize BigQuery client',
)
})
initAnalytics()
const server = http.createServer(app)
server.listen(port, () => {
logger.debug(`🚀 Server is running on port ${port}`)
console.log(`🚀 Server is running on port ${port}`)
})
webSocketListen(server, '/ws')
let shutdownInProgress = false
// Graceful shutdown handler for both SIGTERM and SIGINT
async function handleShutdown(signal: string) {
flushAnalytics()
if (env.NEXT_PUBLIC_CB_ENVIRONMENT === 'dev') {
server.close((error) => {
console.log('Received error closing server', { error })
})
process.exit(0)
}
if (shutdownInProgress) {
console.log(`\nReceived ${signal}. Already shutting down...`)
return
}
shutdownInProgress = true
console.log(
`\nReceived ${signal}. Starting ${SHUTDOWN_GRACE_PERIOD_MS / 60000} minute graceful shutdown period...`,
)
// Don't shutdown, instead ask clients to disconnect from us
sendRequestReconnect()
waitForAllClientsDisconnected().then(() => {
console.log('All clients disconnected. Shutting down...')
process.exit(0)
})
// Wait for the grace period to allow clients to switch to new instances
await new Promise((resolve) => setTimeout(resolve, SHUTDOWN_GRACE_PERIOD_MS))
console.log('Grace period over. Proceeding with final shutdown...')
process.exit(1)
}
process.on('SIGTERM', () => handleShutdown('SIGTERM'))
process.on('SIGINT', () => handleShutdown('SIGINT'))
process.on('unhandledRejection', (reason, promise) => {
// Don't rethrow the error, just log it. Keep the server running.
const stack = reason instanceof Error ? reason.stack : undefined
const message = reason instanceof Error ? reason.message : undefined
const name = reason instanceof Error ? reason.name : undefined
console.error('unhandledRejection', message, reason, stack)
logger.error(
{
reason,
stack,
message,
name,
promise,
},
`Unhandled promise rejection: ${reason instanceof Error ? reason.message : 'Unknown reason'}`,
)
})
process.on('uncaughtException', (err, origin) => {
console.error('uncaughtException', {
error: err,
message: err.message,
stack: err.stack,
name: err.name,
origin,
})
logger.fatal(
{
err,
stack: err.stack,
message: err.message,
name: err.name,
origin,
},
'uncaught exception detected',
)
server.close(() => {
process.exit(1)
})
// If a graceful shutdown is not achieved after 1 second,
// shut down the process completely
setTimeout(() => {
process.abort() // exit immediately and generate a core dump file
}, 1000).unref()
process.exit(1)
})