forked from Portkey-AI/gateway
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
248 lines (214 loc) · 7.04 KB
/
index.ts
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/**
* Portkey AI Gateway
*
* @module index
*/
import { Context, Hono } from 'hono';
import { prettyJSON } from 'hono/pretty-json';
import { HTTPException } from 'hono/http-exception';
import { compress } from 'hono/compress';
import { getRuntimeKey } from 'hono/adapter';
// import { env } from 'hono/adapter' // Have to set this up for multi-environment deployment
// Middlewares
import { requestValidator } from './middlewares/requestValidator';
import { hooks } from './middlewares/hooks';
import { memoryCache } from './middlewares/cache';
// Handlers
import { proxyHandler } from './handlers/proxyHandler';
import { chatCompletionsHandler } from './handlers/chatCompletionsHandler';
import { completionsHandler } from './handlers/completionsHandler';
import { embeddingsHandler } from './handlers/embeddingsHandler';
import { logger } from './middlewares/log';
import { imageGenerationsHandler } from './handlers/imageGenerationsHandler';
import { createSpeechHandler } from './handlers/createSpeechHandler';
import { createTranscriptionHandler } from './handlers/createTranscriptionHandler';
import { createTranslationHandler } from './handlers/createTranslationHandler';
import { modelsHandler, providersHandler } from './handlers/modelsHandler';
import { realTimeHandler } from './handlers/realtimeHandler';
import filesHandler from './handlers/filesHandler';
import batchesHandler from './handlers/batchesHandler';
import finetuneHandler from './handlers/finetuneHandler';
// Config
import conf from '../conf.json';
// Create a new Hono server instance
const app = new Hono();
/**
* Middleware that conditionally applies compression middleware based on the runtime.
* Compression is automatically handled for lagon and workerd runtimes
* This check if its not any of the 2 and then applies the compress middleware to avoid double compression.
*/
const runtime = getRuntimeKey();
app.use('*', (c, next) => {
const runtimesThatDontNeedCompression = ['lagon', 'workerd', 'node'];
if (runtimesThatDontNeedCompression.includes(runtime)) {
return next();
}
return compress()(c, next);
});
if (runtime === 'node') {
app.use('*', async (c: Context, next) => {
if (!c.req.url.includes('/realtime')) {
return next();
}
await next();
if (
c.req.url.includes('/realtime') &&
c.req.header('upgrade') === 'websocket' &&
(c.res.status >= 400 || c.get('websocketError') === true)
) {
const finalStatus = c.get('websocketError') === true ? 500 : c.res.status;
const socket = c.env.incoming.socket;
if (socket) {
socket.write(`HTTP/1.1 ${finalStatus} ${c.res.statusText}\r\n\r\n`);
socket.destroy();
}
}
});
}
/**
* GET route for the root path.
* Returns a greeting message.
*/
app.get('/', (c) => c.text('AI Gateway says hey!'));
// Use prettyJSON middleware for all routes
app.use('*', prettyJSON());
// Use logger middleware for all routes
if (getRuntimeKey() === 'node') {
app.use(logger());
}
// Use hooks middleware for all routes
app.use('*', hooks);
if (conf.cache === true) {
app.use('*', memoryCache());
}
/**
* Default route when no other route matches.
* Returns a JSON response with a message and status code 404.
*/
app.notFound((c) => c.json({ message: 'Not Found', ok: false }, 404));
/**
* Global error handler.
* If error is instance of HTTPException, returns the custom response.
* Otherwise, logs the error and returns a JSON response with status code 500.
*/
app.onError((err, c) => {
if (err instanceof HTTPException) {
return err.getResponse();
}
c.status(500);
return c.json({ status: 'failure', message: err.message });
});
/**
* POST route for '/v1/chat/completions'.
* Handles requests by passing them to the chatCompletionsHandler.
*/
app.post('/v1/chat/completions', requestValidator, chatCompletionsHandler);
/**
* POST route for '/v1/completions'.
* Handles requests by passing them to the completionsHandler.
*/
app.post('/v1/completions', requestValidator, completionsHandler);
/**
* POST route for '/v1/embeddings'.
* Handles requests by passing them to the embeddingsHandler.
*/
app.post('/v1/embeddings', requestValidator, embeddingsHandler);
/**
* POST route for '/v1/images/generations'.
* Handles requests by passing them to the imageGenerations handler.
*/
app.post('/v1/images/generations', requestValidator, imageGenerationsHandler);
/**
* POST route for '/v1/audio/speech'.
* Handles requests by passing them to the createSpeechHandler.
*/
app.post('/v1/audio/speech', requestValidator, createSpeechHandler);
/**
* POST route for '/v1/audio/transcriptions'.
* Handles requests by passing them to the createTranscriptionHandler.
*/
app.post(
'/v1/audio/transcriptions',
requestValidator,
createTranscriptionHandler
);
/**
* POST route for '/v1/audio/translations'.
* Handles requests by passing them to the createTranslationHandler.
*/
app.post('/v1/audio/translations', requestValidator, createTranslationHandler);
// files
app.get('/v1/files', requestValidator, filesHandler('listFiles', 'GET'));
app.get('/v1/files/:id', requestValidator, filesHandler('retrieveFile', 'GET'));
app.get(
'/v1/files/:id/content',
requestValidator,
filesHandler('retrieveFileContent', 'GET')
);
app.post('/v1/files', requestValidator, filesHandler('uploadFile', 'POST'));
app.delete(
'/v1/files/:id',
requestValidator,
filesHandler('deleteFile', 'DELETE')
);
// batches
app.post(
'/v1/batches',
requestValidator,
batchesHandler('createBatch', 'POST')
);
app.get(
'/v1/batches/:id',
requestValidator,
batchesHandler('retrieveBatch', 'GET')
);
app.get(
'/v1/batches/*/output',
requestValidator,
batchesHandler('getBatchOutput', 'GET')
);
app.post(
'/v1/batches/:id/cancel',
requestValidator,
batchesHandler('cancelBatch', 'POST')
);
app.get('/v1/batches', requestValidator, batchesHandler('listBatches', 'GET'));
app.all(
'/v1/fine_tuning/jobs/:jobId?/:cancel?',
requestValidator,
finetuneHandler
);
/**
* POST route for '/v1/prompts/:id/completions'.
* Handles portkey prompt completions route
*/
app.post('/v1/prompts/*', requestValidator, (c) => {
if (c.req.url.endsWith('/v1/chat/completions')) {
return chatCompletionsHandler(c);
} else if (c.req.url.endsWith('/v1/completions')) {
return completionsHandler(c);
}
c.status(500);
return c.json({
status: 'failure',
message: 'prompt completions error: Something went wrong',
});
});
app.get('/v1/reference/models', modelsHandler);
app.get('/v1/reference/providers', providersHandler);
// WebSocket route
if (runtime === 'workerd') {
app.get('/v1/realtime', realTimeHandler);
}
/**
* @deprecated
* Support the /v1 proxy endpoint
*/
app.post('/v1/proxy/*', proxyHandler);
// Support the /v1 proxy endpoint after all defined endpoints so this does not interfere.
app.post('/v1/*', requestValidator, proxyHandler);
// Support the /v1 proxy endpoint after all defined endpoints so this does not interfere.
app.get('/v1/:path{(?!realtime).*}', requestValidator, proxyHandler);
app.delete('/v1/*', requestValidator, proxyHandler);
// Export the app
export default app;