dan92 commited on
Commit
3278977
·
verified ·
1 Parent(s): 8d6a6b2

Upload 2 files

Browse files
Files changed (1) hide show
  1. src/index.js +74 -150
src/index.js CHANGED
@@ -4,6 +4,7 @@ const bodyParser = require('koa-bodyparser')
4
  const models = require("./models")
5
  const crypto = require('crypto')
6
 
 
7
  const app = new Koa()
8
  const router = new Router()
9
 
@@ -13,9 +14,9 @@ app.use(bodyParser())
13
  // 配置 bodyParser
14
  app.use(bodyParser({
15
  enableTypes: ['json', 'form', 'text'],
16
- jsonLimit: '30mb',
17
- formLimit: '30mb',
18
- textLimit: '30mb',
19
  }))
20
 
21
  // 添加 CORS 中间件
@@ -25,6 +26,7 @@ app.use(async (ctx, next) => {
25
  ctx.set('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, OPTIONS');
26
  ctx.set('Access-Control-Max-Age', '3600');
27
 
 
28
  if (ctx.method === 'OPTIONS') {
29
  ctx.status = 204;
30
  return;
@@ -51,6 +53,7 @@ app.use(async (ctx, next) => {
51
  app.use(async (ctx, next) => {
52
  try {
53
  await next()
 
54
  if (ctx.status === 404 && !ctx.body) {
55
  ctx.status = 404
56
  ctx.body = {
@@ -84,6 +87,7 @@ app.use(async (ctx, next) => {
84
  const makeRequest = async (session_id, requestModel, messages) => {
85
  console.log('开始请求:', { session_id: '***', model: requestModel })
86
  try {
 
87
  const myHeaders = new Headers()
88
  myHeaders.append("Cookie", `session_id=${session_id}`)
89
  myHeaders.append("User-Agent", "Apifox/1.0.0 (https://apifox.com)");
@@ -92,7 +96,8 @@ const makeRequest = async (session_id, requestModel, messages) => {
92
  myHeaders.append("Host", "www.genspark.ai")
93
  myHeaders.append("Connection", "keep-alive")
94
 
95
- const body = JSON.stringify({
 
96
  "type": "COPILOT_MOA_CHAT",
97
  "current_query_string": "type=COPILOT_MOA_CHAT",
98
  "messages": messages,
@@ -119,91 +124,14 @@ const makeRequest = async (session_id, requestModel, messages) => {
119
  throw new Error(`HTTP error! status: ${response.status}`)
120
  }
121
 
122
- // 验证响应是否有效
123
- if (!response.body) {
124
- throw new Error('Invalid response: body is missing')
125
- }
126
-
127
  return response
128
  } catch (error) {
129
  console.error('请求出错:', error)
130
- // 添加重试逻辑
131
- if (error.message.includes('HTTP error! status: 500')) {
132
- console.log('尝试重新请求...')
133
- await new Promise(resolve => setTimeout(resolve, 1000)) // 等待1秒后重试
134
- return makeRequest(session_id, requestModel, messages)
135
- }
136
  throw error
137
  }
138
  }
139
 
140
- // 用于存储未完成的数据块
141
- let dataBuffer = '';
142
-
143
- const parseStreamData = (data) => {
144
- try {
145
- if (!data) return null
146
-
147
- // 将新数据添加到缓冲区
148
- dataBuffer += data;
149
-
150
- // 查找完整的JSON对象
151
- const matches = dataBuffer.match(/data: ({[^}]+})/g);
152
- if (!matches) return null;
153
-
154
- // 处理找到的每个完整JSON对象
155
- for (const match of matches) {
156
- try {
157
- const cleanData = match.replace("data: ", '').trim();
158
- if (cleanData === '[DONE]') return null;
159
-
160
- // 尝试解析JSON
161
- const parsed = JSON.parse(cleanData);
162
-
163
- // 从缓冲区移除已处理的数据
164
- dataBuffer = dataBuffer.slice(dataBuffer.indexOf(match) + match.length);
165
-
166
- // 返回解析后的对象
167
- return parsed;
168
- } catch (parseError) {
169
- // 如果解析失败,尝试提取字段
170
- const messageIdMatch = cleanData.match(/"message_id":\s*"([^"]+)"/);
171
- const fieldNameMatch = cleanData.match(/"field_name":\s*"([^"]+)"/);
172
- const fieldValueMatch = cleanData.match(/"field_value":\s*"([^]*?)(?:(?<!\\)",|$)/);
173
-
174
- if (messageIdMatch && fieldNameMatch) {
175
- const fieldValue = fieldValueMatch ? fieldValueMatch[1] : '';
176
- return {
177
- message_id: messageIdMatch[1],
178
- field_name: fieldNameMatch[1],
179
- field_value: fieldValue,
180
- type: 'text'
181
- };
182
- }
183
- }
184
- }
185
-
186
- // 如果缓冲区过大,清理它
187
- if (dataBuffer.length > 10000) {
188
- console.warn('缓冲区过大,正在清理...');
189
- dataBuffer = dataBuffer.slice(-5000);
190
- }
191
-
192
- return {
193
- field_name: 'content',
194
- field_value: '',
195
- type: 'text'
196
- };
197
- } catch (error) {
198
- console.error('解析数据出错:', error, '原始数据:', data);
199
- return {
200
- field_name: 'content',
201
- field_value: '',
202
- type: 'text'
203
- };
204
- }
205
- }
206
-
207
  const handleChatCompletions = async (ctx) => {
208
  const { messages, stream = false, model = 'claude-3-5-sonnet' } = ctx.request.body
209
  const session_id = ctx.get('Authorization')?.replace('Bearer ', '')
@@ -223,6 +151,24 @@ const handleChatCompletions = async (ctx) => {
223
 
224
  try {
225
  const response = await makeRequest(session_id, model, messages)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
  const messageId = crypto.randomUUID()
228
  const reader = response.body.getReader()
@@ -240,14 +186,6 @@ const handleChatCompletions = async (ctx) => {
240
  }
241
 
242
  if (stream == "true" || stream == true) {
243
- ctx.status = 200
244
- ctx.set({
245
- 'Content-Type': 'text/event-stream',
246
- 'Cache-Control': 'no-cache',
247
- 'Connection': 'keep-alive',
248
- })
249
-
250
- // 发送初始消息
251
  ctx.res.write(`data: ${JSON.stringify({
252
  "id": `chatcmpl-${messageId}`,
253
  "choices": [
@@ -263,11 +201,6 @@ const handleChatCompletions = async (ctx) => {
263
  "model": model,
264
  "object": "chat.completion.chunk"
265
  })}\n\n`)
266
- } else {
267
- ctx.status = 200
268
- ctx.set({
269
- 'Content-Type': 'application/json',
270
- })
271
  }
272
 
273
  try {
@@ -280,61 +213,49 @@ const handleChatCompletions = async (ctx) => {
280
  break
281
  }
282
 
283
- const text = new TextDecoder().decode(value)
284
- const textContent = [...text.matchAll(/data:.*?(?=(\n\ndata:|$))/gs)]
 
285
 
286
- for (const item of textContent) {
287
- if (!item[0]) continue
288
-
289
- const content = parseStreamData(item[0])
290
- if (!content) continue
291
 
292
- if (stream == "true" || stream == true) {
293
- // 处理流式响应
294
- const delta = content.delta ||
295
- (content.field_name === 'content' ? content.field_value : null)
296
-
297
- if (delta) {
298
- try {
299
- const chunk = {
300
- "id": `chatcmpl-${messageId}`,
301
- "choices": [
302
- {
303
- "index": 0,
304
- "delta": {
305
- "content": delta
306
- }
307
- }
308
- ],
309
- "created": Math.floor(Date.now() / 1000),
310
- "model": model,
311
- "object": "chat.completion.chunk"
312
- };
313
-
314
- ctx.res.write(`data: ${JSON.stringify(chunk)}\n\n`)
315
- } catch (e) {
316
- console.error('写入流数据失败:', e, '数据:', delta)
317
- }
318
- }
319
- } else {
320
- // 处理非流式响应
321
- if (content.field_value &&
322
- content.field_name !== 'session_state.answer_is_finished' &&
323
- content.field_name !== 'content' &&
324
- content.field_name !== 'session_state' &&
325
- !content.delta &&
326
- content.type !== 'project_field') {
327
- resBody.choices.push({
328
- index: 0,
329
- message: {
330
- role: 'assistant',
331
- content: content.field_value,
332
- },
333
- finish_reason: 'stop',
334
- })
335
- resBody.usage.total_tokens = content.field_value.length
336
- }
337
- }
338
  }
339
  }
340
 
@@ -405,12 +326,13 @@ const handleModels = async (ctx) => {
405
  }
406
  }
407
 
408
- // 注册路由
409
  router.post('/v1/chat/completions', handleChatCompletions)
410
  router.post('/hf/v1/chat/completions', handleChatCompletions)
411
  router.get('/v1/models', handleModels)
412
  router.get('/hf/v1/models', handleModels)
413
 
 
414
  router.get('/', async (ctx) => {
415
  ctx.body = {
416
  status: 'ok',
@@ -418,15 +340,17 @@ router.get('/', async (ctx) => {
418
  version: '1.0.0',
419
  endpoints: [
420
  '/v1/chat/completions',
421
- '/hf/v1/chat/completions',
422
  '/v1/models',
423
  '/hf/v1/models'
424
  ]
425
  }
426
  })
427
 
 
428
  app.use(router.routes()).use(router.allowedMethods())
429
 
 
430
  const PORT = process.env.PORT || 3000
431
  app.listen(PORT, '0.0.0.0', () => {
432
  console.log('='.repeat(50))
 
4
  const models = require("./models")
5
  const crypto = require('crypto')
6
 
7
+
8
  const app = new Koa()
9
  const router = new Router()
10
 
 
14
  // 配置 bodyParser
15
  app.use(bodyParser({
16
  enableTypes: ['json', 'form', 'text'],
17
+ jsonLimit: '30mb', // JSON 数据大小限制
18
+ formLimit: '30mb', // form 数据大小限制
19
+ textLimit: '30mb', // text 数据大小限制
20
  }))
21
 
22
  // 添加 CORS 中间件
 
26
  ctx.set('Access-Control-Allow-Methods', 'POST, GET, PUT, DELETE, OPTIONS');
27
  ctx.set('Access-Control-Max-Age', '3600');
28
 
29
+ // 处理 OPTIONS 请求
30
  if (ctx.method === 'OPTIONS') {
31
  ctx.status = 204;
32
  return;
 
53
  app.use(async (ctx, next) => {
54
  try {
55
  await next()
56
+ // 只有当没有任何路由处理请求时才设置 404
57
  if (ctx.status === 404 && !ctx.body) {
58
  ctx.status = 404
59
  ctx.body = {
 
87
  const makeRequest = async (session_id, requestModel, messages) => {
88
  console.log('开始请求:', { session_id: '***', model: requestModel })
89
  try {
90
+ // 设置请求头
91
  const myHeaders = new Headers()
92
  myHeaders.append("Cookie", `session_id=${session_id}`)
93
  myHeaders.append("User-Agent", "Apifox/1.0.0 (https://apifox.com)");
 
96
  myHeaders.append("Host", "www.genspark.ai")
97
  myHeaders.append("Connection", "keep-alive")
98
 
99
+ // 设置请求体
100
+ var body = JSON.stringify({
101
  "type": "COPILOT_MOA_CHAT",
102
  "current_query_string": "type=COPILOT_MOA_CHAT",
103
  "messages": messages,
 
124
  throw new Error(`HTTP error! status: ${response.status}`)
125
  }
126
 
 
 
 
 
 
127
  return response
128
  } catch (error) {
129
  console.error('请求出错:', error)
 
 
 
 
 
 
130
  throw error
131
  }
132
  }
133
 
134
+ // 创建处理函数
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  const handleChatCompletions = async (ctx) => {
136
  const { messages, stream = false, model = 'claude-3-5-sonnet' } = ctx.request.body
137
  const session_id = ctx.get('Authorization')?.replace('Bearer ', '')
 
151
 
152
  try {
153
  const response = await makeRequest(session_id, model, messages)
154
+
155
+ if (!response.body) {
156
+ throw new Error('Response body is null')
157
+ }
158
+
159
+ if (stream == "true" || stream == true) {
160
+ ctx.status = 200
161
+ ctx.set({
162
+ 'Content-Type': 'text/event-stream',
163
+ 'Cache-Control': 'no-cache',
164
+ 'Connection': 'keep-alive',
165
+ })
166
+ } else {
167
+ ctx.status = 200
168
+ ctx.set({
169
+ 'Content-Type': 'application/json',
170
+ })
171
+ }
172
 
173
  const messageId = crypto.randomUUID()
174
  const reader = response.body.getReader()
 
186
  }
187
 
188
  if (stream == "true" || stream == true) {
 
 
 
 
 
 
 
 
189
  ctx.res.write(`data: ${JSON.stringify({
190
  "id": `chatcmpl-${messageId}`,
191
  "choices": [
 
201
  "model": model,
202
  "object": "chat.completion.chunk"
203
  })}\n\n`)
 
 
 
 
 
204
  }
205
 
206
  try {
 
213
  break
214
  }
215
 
216
+ if (stream == "true" || stream == true) {
217
+ const text = new TextDecoder().decode(value)
218
+ const textContent = [...text.matchAll(/data:.*"}/g)]
219
 
220
+ textContent.forEach(item => {
221
+ if (!item[0]) return
222
+ const content = JSON.parse(item[0].replace("data: ", ''))
223
+ if (!content || !content.delta) return
 
224
 
225
+ ctx.res.write(`data: ${JSON.stringify({
226
+ "id": `chatcmpl-${messageId}`,
227
+ "choices": [
228
+ {
229
+ "index": 0,
230
+ "delta": {
231
+ "content": content.delta
232
+ }
233
+ }
234
+ ],
235
+ "created": Math.floor(Date.now() / 1000),
236
+ "model": model,
237
+ "object": "chat.completion.chunk"
238
+ })}\n\n`)
239
+ })
240
+ } else {
241
+ const text = new TextDecoder().decode(value)
242
+ const textContent = [...text.matchAll(/data:.*"}/g)]
243
+
244
+ textContent.forEach(item => {
245
+ if (!item[0]) return
246
+ const content = JSON.parse(item[0].replace("data: ", ''))
247
+ if (!content || !content.field_value || content.field_name == 'session_state.answer_is_finished' || content.field_name == 'content' || content.field_name == 'session_state' || content.delta || content.type == 'project_field') return
248
+
249
+ resBody.choices.push({
250
+ index: 0,
251
+ message: {
252
+ role: 'assistant',
253
+ content: content.field_value,
254
+ },
255
+ finish_reason: 'stop',
256
+ })
257
+ resBody.usage.total_tokens = content.field_value.length
258
+ })
 
 
 
 
 
 
 
 
 
 
 
 
259
  }
260
  }
261
 
 
326
  }
327
  }
328
 
329
+ // 注册路由 - 同时支持 /v1 和 /hf/v1 路径
330
  router.post('/v1/chat/completions', handleChatCompletions)
331
  router.post('/hf/v1/chat/completions', handleChatCompletions)
332
  router.get('/v1/models', handleModels)
333
  router.get('/hf/v1/models', handleModels)
334
 
335
+ // 添加根路由处理器
336
  router.get('/', async (ctx) => {
337
  ctx.body = {
338
  status: 'ok',
 
340
  version: '1.0.0',
341
  endpoints: [
342
  '/v1/chat/completions',
343
+ '/hf/v1/chat/completions',
344
  '/v1/models',
345
  '/hf/v1/models'
346
  ]
347
  }
348
  })
349
 
350
+ // 注册路由中间件
351
  app.use(router.routes()).use(router.allowedMethods())
352
 
353
+ // 启动服务器
354
  const PORT = process.env.PORT || 3000
355
  app.listen(PORT, '0.0.0.0', () => {
356
  console.log('='.repeat(50))