dan92 commited on
Commit
b4f2dea
·
verified ·
1 Parent(s): 056a2d0

Upload 2 files

Browse files
Files changed (1) hide show
  1. src/index.js +88 -74
src/index.js CHANGED
@@ -4,7 +4,6 @@ const bodyParser = require('koa-bodyparser')
4
  const models = require("./models")
5
  const crypto = require('crypto')
6
 
7
-
8
  const app = new Koa()
9
  const router = new Router()
10
 
@@ -14,9 +13,9 @@ app.use(bodyParser())
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,7 +25,6 @@ app.use(async (ctx, next) => {
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,7 +51,6 @@ app.use(async (ctx, next) => {
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,7 +84,6 @@ app.use(async (ctx, next) => {
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,8 +92,7 @@ const makeRequest = async (session_id, requestModel, messages) => {
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,14 +119,36 @@ const makeRequest = async (session_id, requestModel, 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,24 +168,6 @@ const handleChatCompletions = async (ctx) => {
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,6 +185,14 @@ const handleChatCompletions = async (ctx) => {
186
  }
187
 
188
  if (stream == "true" || stream == true) {
 
 
 
 
 
 
 
 
189
  ctx.res.write(`data: ${JSON.stringify({
190
  "id": `chatcmpl-${messageId}`,
191
  "choices": [
@@ -201,6 +208,11 @@ const handleChatCompletions = async (ctx) => {
201
  "model": model,
202
  "object": "chat.completion.chunk"
203
  })}\n\n`)
 
 
 
 
 
204
  }
205
 
206
  try {
@@ -213,49 +225,54 @@ const handleChatCompletions = async (ctx) => {
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,13 +343,12 @@ const handleModels = async (ctx) => {
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,17 +356,15 @@ router.get('/', async (ctx) => {
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))
 
4
  const models = require("./models")
5
  const crypto = require('crypto')
6
 
 
7
  const app = new Koa()
8
  const router = new Router()
9
 
 
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
  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
  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
  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
  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
  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
+ const parseStreamData = (data) => {
141
+ try {
142
+ if (!data) return null
143
+ const cleanData = data.replace("data: ", '').trim()
144
+ if (!cleanData || cleanData === '[DONE]') return null
145
+ return JSON.parse(cleanData)
146
+ } catch (error) {
147
+ console.error('解析数据出错:', error, '原始数据:', data)
148
+ return null
149
+ }
150
+ }
151
+
152
  const handleChatCompletions = async (ctx) => {
153
  const { messages, stream = false, model = 'claude-3-5-sonnet' } = ctx.request.body
154
  const session_id = ctx.get('Authorization')?.replace('Bearer ', '')
 
168
 
169
  try {
170
  const response = await makeRequest(session_id, model, messages)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
  const messageId = crypto.randomUUID()
173
  const reader = response.body.getReader()
 
185
  }
186
 
187
  if (stream == "true" || stream == true) {
188
+ ctx.status = 200
189
+ ctx.set({
190
+ 'Content-Type': 'text/event-stream',
191
+ 'Cache-Control': 'no-cache',
192
+ 'Connection': 'keep-alive',
193
+ })
194
+
195
+ // 发送初始消息
196
  ctx.res.write(`data: ${JSON.stringify({
197
  "id": `chatcmpl-${messageId}`,
198
  "choices": [
 
208
  "model": model,
209
  "object": "chat.completion.chunk"
210
  })}\n\n`)
211
+ } else {
212
+ ctx.status = 200
213
+ ctx.set({
214
+ 'Content-Type': 'application/json',
215
+ })
216
  }
217
 
218
  try {
 
225
  break
226
  }
227
 
228
+ const text = new TextDecoder().decode(value)
229
+ const textContent = [...text.matchAll(/data:.*?(?=(\n\ndata:|$))/gs)]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
 
231
+ for (const item of textContent) {
232
+ if (!item[0]) continue
233
+
234
+ const content = parseStreamData(item[0])
235
+ if (!content) continue
236
 
237
+ if (stream == "true" || stream == true) {
238
+ if (content.delta) {
239
+ try {
240
+ ctx.res.write(`data: ${JSON.stringify({
241
+ "id": `chatcmpl-${messageId}`,
242
+ "choices": [
243
+ {
244
+ "index": 0,
245
+ "delta": {
246
+ "content": content.delta
247
+ }
248
+ }
249
+ ],
250
+ "created": Math.floor(Date.now() / 1000),
251
+ "model": model,
252
+ "object": "chat.completion.chunk"
253
+ })}\n\n`)
254
+ } catch (e) {
255
+ console.error('写入流数据失败:', e)
256
+ }
257
+ }
258
+ } else {
259
+ if (content.field_value &&
260
+ content.field_name !== 'session_state.answer_is_finished' &&
261
+ content.field_name !== 'content' &&
262
+ content.field_name !== 'session_state' &&
263
+ !content.delta &&
264
+ content.type !== 'project_field') {
265
+ resBody.choices.push({
266
+ index: 0,
267
+ message: {
268
+ role: 'assistant',
269
+ content: content.field_value,
270
+ },
271
+ finish_reason: 'stop',
272
+ })
273
+ resBody.usage.total_tokens = content.field_value.length
274
+ }
275
+ }
276
  }
277
  }
278
 
 
343
  }
344
  }
345
 
346
+ // 注册路由
347
  router.post('/v1/chat/completions', handleChatCompletions)
348
  router.post('/hf/v1/chat/completions', handleChatCompletions)
349
  router.get('/v1/models', handleModels)
350
  router.get('/hf/v1/models', handleModels)
351
 
 
352
  router.get('/', async (ctx) => {
353
  ctx.body = {
354
  status: 'ok',
 
356
  version: '1.0.0',
357
  endpoints: [
358
  '/v1/chat/completions',
359
+ '/hf/v1/chat/completions',
360
  '/v1/models',
361
  '/hf/v1/models'
362
  ]
363
  }
364
  })
365
 
 
366
  app.use(router.routes()).use(router.allowedMethods())
367
 
 
368
  const PORT = process.env.PORT || 3000
369
  app.listen(PORT, '0.0.0.0', () => {
370
  console.log('='.repeat(50))