ERROR418 commited on
Commit
c10af0f
·
verified ·
1 Parent(s): 67cf308

Update main.ts

Browse files
Files changed (1) hide show
  1. main.ts +724 -356
main.ts CHANGED
@@ -1,427 +1,795 @@
1
  import { serve } from "https://deno.land/[email protected]/http/server.ts";
2
 
3
  // 定义常量
4
- const API_URL = "https://mcp.scira.ai/api/chat";
5
- const FIXED_USER_ID = "2jFMDM1A1R_XxOTxPjhwe";
6
- const FIXED_CHAT_ID = "ZIWa36kd6MSqzw-ifXGzE";
7
- const DEFAULT_MODEL = "qwen-qwq";
8
  const PORT = 7860;
 
 
 
 
 
 
 
9
 
10
  // 定义接口
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  interface Message {
12
  role: string;
13
  content: string;
14
- parts?: Array<{
15
- type: string;
16
- text: string;
17
- }>;
18
  }
19
 
20
- interface SciraPayload {
 
21
  id: string;
22
- messages: Message[];
23
- selectedModel: string;
24
- mcpServers: any[];
25
- chatId: string;
26
- userId: string;
27
  }
28
 
29
- interface OpenAIModel {
30
- id: string;
31
- created: number;
32
- object: string;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  }
34
 
35
- // 可用模型列表
36
- const AVAILABLE_MODELS: OpenAIModel[] = [
37
- {
38
- id: "qwen-qwq",
39
- created: Date.now(),
40
- object: "model",
41
- },
42
- {
43
- id: "gemini-2.5-flash",
44
- created: Date.now(),
45
- object: "model",
46
- },
47
- {
48
- id: "gpt-4.1-mini",
49
- created: Date.now(),
50
- object: "model",
51
- },
52
- {
53
- id: "claude-3-7-sonnet",
54
- created: Date.now(),
55
- object: "model",
56
- },
57
- ];
58
-
59
- // 格式化消息为Scira格式
60
- function formatMessagesForScira(messages: Message[]): Message[] {
61
- return messages.map(msg => ({
62
- role: msg.role,
63
- content: msg.content,
64
- parts: [{
65
- type: "text",
66
- text: msg.content
67
- }]
68
- }));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  }
70
 
71
- // 构建Scira请求负载
72
- function buildSciraPayload(messages: Message[], model = DEFAULT_MODEL): SciraPayload {
73
- const formattedMessages = formatMessagesForScira(messages);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  return {
75
- id: FIXED_CHAT_ID,
76
- messages: formattedMessages,
77
- selectedModel: model,
78
- mcpServers: [],
79
- chatId: FIXED_CHAT_ID,
80
- userId: FIXED_USER_ID
81
  };
82
  }
83
 
84
- // 处理模型列表请求
85
- async function handleModelsRequest(): Promise<Response> {
86
- const response = {
87
- object: "list",
88
- data: AVAILABLE_MODELS,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  };
90
- return new Response(JSON.stringify(response), {
91
- headers: {
92
- "Content-Type": "application/json",
93
- "Access-Control-Allow-Origin": "*"
94
- },
95
- });
96
  }
97
 
98
- // 处理聊天补全请求
99
- async function handleChatCompletionsRequest(req: Request): Promise<Response> {
100
- const requestData = await req.json();
101
- const { messages, model = DEFAULT_MODEL, stream = false } = requestData;
102
-
103
- const sciraPayload = buildSciraPayload(messages, model);
104
- const response = await fetch(API_URL, {
105
- method: "POST",
106
- headers: {
107
- "Content-Type": "application/json",
108
- "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:137.0) Gecko/20100101 Firefox/137.0",
109
- "Accept": "*/*",
110
- "Referer": `https://mcp.scira.ai/chat/${FIXED_CHAT_ID}`,
111
- "Origin": "https://mcp.scira.ai",
112
- },
113
- body: JSON.stringify(sciraPayload),
114
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
- if (stream) {
117
- return handleStreamResponse(response, model);
118
- } else {
119
- return handleRegularResponse(response, model);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  }
121
  }
122
 
123
- // 处理流式响应
124
- async function handleStreamResponse(response: Response, model: string): Promise<Response> {
125
- const reader = response.body!.getReader();
126
- const encoder = new TextEncoder();
 
127
  const decoder = new TextDecoder();
128
-
129
- const id = `chatcmpl-${Date.now().toString(36)}${Math.random().toString(36).substring(2, 10)}`;
130
- const createdTime = Math.floor(Date.now() / 1000);
131
- const systemFingerprint = `fp_${Math.random().toString(36).substring(2, 12)}`;
132
-
133
- const stream = new ReadableStream({
134
- async start(controller) {
135
- // 发送流式头部
136
- const headerEvent = {
137
- id: id,
138
- object: "chat.completion.chunk",
139
- created: createdTime,
140
- model: model,
141
- system_fingerprint: systemFingerprint,
142
- choices: [{
143
- index: 0,
144
- delta: { role: "assistant" },
145
- logprobs: null,
146
- finish_reason: null
147
- }]
148
- };
149
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(headerEvent)}\n\n`));
150
-
151
- try {
152
- let buffer = "";
153
-
154
- while (true) {
155
- const { done, value } = await reader.read();
156
- if (done) break;
157
-
158
- // 解码当前数据块并添加到缓冲区
159
- buffer += decoder.decode(value, { stream: true });
160
-
161
- // 处理完整的行
162
- const lines = buffer.split('\n');
163
- // 保留最后一个可能不完整的行
164
- buffer = lines.pop() || "";
165
-
166
- // 处理并立即发送每一行
167
- for (const line of lines) {
168
- if (!line.trim()) continue;
169
-
170
- if (line.startsWith('g:')) {
171
- // 对于g开头的行,输出reasoning_content
172
- let content = line.slice(2).replace(/^"/, "").replace(/"$/, "");
173
- content = content.replace(/\\n/g, "\n");
174
-
175
- const event = {
176
- id: id,
177
- object: "chat.completion.chunk",
178
- created: createdTime,
179
- model: model,
180
- system_fingerprint: systemFingerprint,
181
- choices: [{
182
- index: 0,
183
- delta: { reasoning_content: content },
184
- logprobs: null,
185
- finish_reason: null
186
- }]
187
- };
188
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
189
- } else if (line.startsWith('0:')) {
190
- // 对于0开头的行,输出content
191
- let content = line.slice(2).replace(/^"/, "").replace(/"$/, "");
192
- content = content.replace(/\\n/g, "\n");
193
-
194
- const event = {
195
- id: id,
196
- object: "chat.completion.chunk",
197
- created: createdTime,
198
- model: model,
199
- system_fingerprint: systemFingerprint,
200
- choices: [{
201
  index: 0,
202
- delta: { content: content },
203
- logprobs: null,
204
- finish_reason: null
205
- }]
206
- };
207
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
208
- } else if (line.startsWith('e:')) {
209
- // 完成消息
210
- try {
211
- const finishData = JSON.parse(line.slice(2));
212
- const event = {
213
- id: id,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  object: "chat.completion.chunk",
215
- created: createdTime,
216
- model: model,
217
- system_fingerprint: systemFingerprint,
218
- choices: [{
219
- index: 0,
220
- delta: {},
221
- logprobs: null,
222
- finish_reason: finishData.finishReason || "stop"
223
- }]
224
- };
225
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
226
- } catch (error) {
227
- console.error("Error parsing finish data:", error);
228
  }
229
  }
230
- }
231
- }
232
-
233
- // 处理缓冲区中剩余的内容(如果有的话)
234
- if (buffer.trim()) {
235
- const line = buffer.trim();
236
- if (line.startsWith('g:')) {
237
- let content = line.slice(2).replace(/^"/, "").replace(/"$/, "");
238
- content = content.replace(/\\n/g, "\n");
239
-
240
- const event = {
241
- id: id,
242
- object: "chat.completion.chunk",
243
- created: createdTime,
244
- model: model,
245
- system_fingerprint: systemFingerprint,
246
- choices: [{
247
- index: 0,
248
- delta: { reasoning_content: content },
249
- logprobs: null,
250
- finish_reason: null
251
- }]
252
- };
253
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
254
- } else if (line.startsWith('0:')) {
255
- let content = line.slice(2).replace(/^"/, "").replace(/"$/, "");
256
- content = content.replace(/\\n/g, "\n");
257
-
258
- const event = {
259
- id: id,
260
  object: "chat.completion.chunk",
261
- created: createdTime,
262
- model: model,
263
- system_fingerprint: systemFingerprint,
264
- choices: [{
265
- index: 0,
266
- delta: { content: content },
267
- logprobs: null,
268
- finish_reason: null
269
- }]
 
 
 
 
270
  };
271
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
 
272
  }
 
 
 
273
  }
274
- } catch (error) {
275
- console.error("Stream error:", error);
276
- } finally {
277
- // 确保发送 "data: [DONE]"
278
- controller.enqueue(encoder.encode("data: [DONE]\n\n"));
279
- controller.close();
280
  }
281
  }
282
- });
 
 
 
283
 
284
- return new Response(stream, {
285
- headers: {
286
- "Content-Type": "text/event-stream",
287
- "Cache-Control": "no-cache",
288
- "Connection": "keep-alive",
289
- "Access-Control-Allow-Origin": "*",
290
- },
291
  });
 
 
292
  }
293
 
294
- // 处理非流式响应
295
- async function handleRegularResponse(response: Response, model: string): Promise<Response> {
296
- const text = await response.text();
297
- const lines = text.split('\n');
298
-
299
- let content = "";
300
- let reasoning_content = "";
301
- let usage = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
302
- let finish_reason = "stop";
303
-
304
- for (const line of lines) {
305
- if (!line.trim()) continue;
306
-
307
- if (line.startsWith('0:')) {
308
- // 常规内容 - 处理转义的换行符
309
- let lineContent = line.slice(2).replace(/^"/, "").replace(/"$/, "");
310
- lineContent = lineContent.replace(/\\n/g, "\n");
311
- content += lineContent;
312
- } else if (line.startsWith('g:')) {
313
- // 推理内容 - 处理转义的换行符
314
- let lineContent = line.slice(2).replace(/^"/, "").replace(/"$/, "");
315
- lineContent = lineContent.replace(/\\n/g, "\n");
316
- reasoning_content += lineContent;
317
- } else if (line.startsWith('e:')) {
318
- try {
319
- const finishData = JSON.parse(line.slice(2));
320
- if (finishData.finishReason) {
321
- finish_reason = finishData.finishReason;
322
- }
323
- } catch (error) {
324
- console.error("Error parsing finish data:", error);
325
- }
326
- } else if (line.startsWith('d:')) {
327
- try {
328
- const finishData = JSON.parse(line.slice(2));
329
- if (finishData.usage) {
330
- usage.prompt_tokens = finishData.usage.promptTokens || 0;
331
- usage.completion_tokens = finishData.usage.completionTokens || 0;
332
- usage.total_tokens = usage.prompt_tokens + usage.completion_tokens;
333
- }
334
- } catch (error) {
335
- console.error("Error parsing usage data:", error);
336
- }
337
  }
338
  }
339
-
340
- const systemFingerprint = `fp_${Math.random().toString(36).substring(2, 12)}`;
341
- const id = `chatcmpl-${Date.now().toString(36)}${Math.random().toString(36).substring(2, 10)}`;
342
 
343
- const openAIResponse = {
344
- id: id,
345
  object: "chat.completion",
346
  created: Math.floor(Date.now() / 1000),
347
- model: model,
348
- system_fingerprint: systemFingerprint,
349
- choices: [{
350
- index: 0,
351
- message: {
352
- role: "assistant",
353
- content: content
 
 
 
354
  },
355
- logprobs: null,
356
- finish_reason: finish_reason
357
- }],
358
- usage: usage
359
- };
360
-
361
- // 如果存在推理内容,添加到消息中
362
- if (reasoning_content.trim()) {
363
- openAIResponse.choices[0].message.reasoning_content = reasoning_content;
364
- }
365
-
366
- return new Response(JSON.stringify(openAIResponse), {
367
- headers: {
368
- "Content-Type": "application/json",
369
- "Access-Control-Allow-Origin": "*"
370
  },
371
- });
372
  }
373
 
374
- // 主请求处理函数
375
  async function handler(req: Request): Promise<Response> {
376
  const url = new URL(req.url);
377
-
378
- // 设置CORS头
379
- const headers = {
380
- "Access-Control-Allow-Origin": "*",
381
- "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
382
- "Access-Control-Allow-Headers": "Content-Type, Authorization",
383
- };
384
-
385
- // 处理OPTIONS请求(CORS预检)
386
  if (req.method === "OPTIONS") {
387
- return new Response(null, {
388
- headers,
389
- status: 204
 
 
 
 
 
390
  });
391
  }
392
-
 
 
393
  try {
394
- // 处理模型列表接口
395
- if (url.pathname === "/v1/models") {
396
- return handleModelsRequest();
397
- }
398
-
399
- // 处理聊天补全接口
400
- if (url.pathname === "/v1/chat/completions") {
401
- return handleChatCompletionsRequest(req);
402
- }
403
-
404
- // 未找到的路由
405
  return new Response(
406
- JSON.stringify({ error: "Not found" }), {
407
- status: 404,
408
- headers: {
 
409
  "Content-Type": "application/json",
410
- ...headers
411
  },
412
- }
413
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
  } catch (error) {
415
- console.error("Error processing request:", error);
 
 
416
  return new Response(
417
- JSON.stringify({ error: error.message || "Internal server error" }),
 
 
 
 
 
 
418
  {
419
- status: 500,
420
- headers: {
421
  "Content-Type": "application/json",
422
- ...headers
423
  },
424
- }
425
  );
426
  }
427
  }
 
1
  import { serve } from "https://deno.land/[email protected]/http/server.ts";
2
 
3
  // 定义常量
4
+ const SOPHNET_BASE_URL = "https://www.sophnet.com/api";
5
+ const PROJECT_UUID = "Ar79PWUQUAhjJOja2orHs";
 
 
6
  const PORT = 7860;
7
+ const TOKEN_KEY = "sophnet_anonymous_token";
8
+ const MAX_RETRIES = 5; // 增加最大重试次数
9
+ const INITIAL_RETRY_DELAY_MS = 100; // 初始重试延迟(毫秒)
10
+ const MAX_RETRY_DELAY_MS = 5000; // 最大重试延迟(毫秒)
11
+
12
+ // 初始化Deno KV
13
+ const kv = await Deno.openKv();
14
 
15
  // 定义接口
16
+ interface AnonymousTokenResponse {
17
+ status: number;
18
+ message: string;
19
+ result: {
20
+ anonymousToken: string;
21
+ expires: string;
22
+ };
23
+ timestamp: number;
24
+ }
25
+
26
+ interface SophNetModel {
27
+ id: number;
28
+ serviceUuid: string | null;
29
+ projectUuid: string;
30
+ displayName: string;
31
+ modelFamily: string;
32
+ available: boolean;
33
+ isBaseModel: boolean;
34
+ features: any;
35
+ supportedStream: boolean;
36
+ supportedImageInputs: boolean;
37
+ schema: Array<{
38
+ name: string;
39
+ displayName: string;
40
+ des: string;
41
+ type: string;
42
+ range: number[];
43
+ defaultValue: number;
44
+ required: boolean;
45
+ }>;
46
+ }
47
+
48
+ interface ModelsResponse {
49
+ status: number;
50
+ message: string;
51
+ result: SophNetModel[];
52
+ timestamp: number;
53
+ }
54
+
55
+ interface TokenInfo {
56
+ token: string;
57
+ expires: string;
58
+ }
59
+
60
  interface Message {
61
  role: string;
62
  content: string;
 
 
 
 
63
  }
64
 
65
+ interface Reference {
66
+ content: string;
67
  id: string;
68
+ index: number;
69
+ title: string;
70
+ type: string;
71
+ url: string;
 
72
  }
73
 
74
+ // 随机生成一个用户代理字符串
75
+ function getRandomUserAgent(): string {
76
+ const userAgents = [
77
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
78
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15",
79
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.2151.44",
80
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
81
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
82
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
83
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0",
84
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/119.0",
85
+ "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/119.0",
86
+ ];
87
+ return userAgents[Math.floor(Math.random() * userAgents.length)];
88
+ }
89
+
90
+ // 计算指数退避延迟
91
+ function getExponentialBackoffDelay(retryCount: number): number {
92
+ const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount);
93
+ const jitter = Math.random() * INITIAL_RETRY_DELAY_MS; // 添加随机抖动
94
+ return Math.min(delay + jitter, MAX_RETRY_DELAY_MS);
95
+ }
96
+
97
+ // 延迟函数
98
+ function sleep(ms: number): Promise<void> {
99
+ return new Promise(resolve => setTimeout(resolve, ms));
100
+ }
101
+
102
+ // 从KV获取token
103
+ async function getTokenFromKV(): Promise<TokenInfo | null> {
104
+ const tokenEntry = await kv.get<TokenInfo>([TOKEN_KEY]);
105
+ return tokenEntry.value;
106
+ }
107
+
108
+ // 存储token到KV
109
+ async function storeTokenToKV(token: string, expires: string): Promise<void> {
110
+ await kv.set([TOKEN_KEY], { token, expires });
111
  }
112
 
113
+ // 获取匿名token
114
+ async function getAnonymousToken(retryCount = 0): Promise<string> {
115
+ try {
116
+ const response = await fetch(`${SOPHNET_BASE_URL}/sys/login/anonymous`, {
117
+ method: "GET",
118
+ headers: {
119
+ "Accept": "application/json",
120
+ "User-Agent": getRandomUserAgent(), // 使用随机UA
121
+ },
122
+ });
123
+
124
+ // 如果是 429 或 500 错误,进行重试
125
+ if ((response.status === 429 || response.status >= 500) && retryCount < MAX_RETRIES) {
126
+ const delay = getExponentialBackoffDelay(retryCount);
127
+ console.warn(`Get token failed with status ${response.status}. Retrying in ${delay}ms... (${retryCount + 1}/${MAX_RETRIES})`);
128
+ await sleep(delay);
129
+ return getAnonymousToken(retryCount + 1);
130
+ }
131
+
132
+ if (!response.ok) {
133
+ throw new Error(`Failed to get token: ${response.status}`);
134
+ }
135
+
136
+ const data = await response.json() as AnonymousTokenResponse;
137
+ await storeTokenToKV(data.result.anonymousToken, data.result.expires);
138
+ return data.result.anonymousToken;
139
+ } catch (error) {
140
+ console.error("Error getting anonymous token:", error);
141
+ throw error;
142
+ }
143
+ }
144
+
145
+ // 获取有效token
146
+ async function getValidToken(): Promise<string> {
147
+ // 先尝试从KV获取
148
+ const tokenInfo = await getTokenFromKV();
149
+
150
+ // 如果KV中有token且未过期,则使用该token
151
+ if (tokenInfo && new Date(tokenInfo.expires) > new Date()) {
152
+ return tokenInfo.token;
153
+ }
154
+
155
+ // 否则获取新token
156
+ return await getAnonymousToken();
157
+ }
158
+
159
+ // 获取模型列表
160
+ async function getModels(token: string, retryCount = 0): Promise<SophNetModel[]> {
161
+ try {
162
+ const response = await fetch(
163
+ `${SOPHNET_BASE_URL}/public/playground/models?projectUuid=${PROJECT_UUID}`,
164
+ {
165
+ method: "GET",
166
+ headers: {
167
+ "Accept": "application/json",
168
+ "User-Agent": getRandomUserAgent(), // 使用随机UA
169
+ "Authorization": `Bearer anon-${token}`,
170
+ },
171
+ },
172
+ );
173
+
174
+ // 如果是401或403错误,尝试刷新token并重试
175
+ if ((response.status === 401 || response.status === 403) && retryCount < MAX_RETRIES) {
176
+ console.log(`Token expired, refreshing and retrying models request (${retryCount + 1}/${MAX_RETRIES})...`);
177
+ const newToken = await getAnonymousToken();
178
+ return await getModels(newToken, retryCount + 1);
179
+ }
180
+
181
+ // 如果是 429 或 500 错误,进行重试
182
+ if ((response.status === 429 || response.status >= 500) && retryCount < MAX_RETRIES) {
183
+ const delay = getExponentialBackoffDelay(retryCount);
184
+ console.warn(`Get models failed with status ${response.status}. Retrying in ${delay}ms... (${retryCount + 1}/${MAX_RETRIES})`);
185
+ await sleep(delay);
186
+ return getModels(token, retryCount + 1); // 使用当前token重试,如果失败会在上面的逻辑中刷新
187
+ }
188
+
189
+ if (!response.ok) {
190
+ throw new Error(`Failed to get models: ${response.status}`);
191
+ }
192
+
193
+ const data = await response.json() as ModelsResponse;
194
+
195
+ // 请求成功后获取新token并存储 (后台刷新)
196
+ getAnonymousToken().catch(err => console.error("Background token refresh failed:", err));
197
+
198
+ return data.result;
199
+ } catch (error) {
200
+ console.error("Error getting models:", error);
201
+ throw error;
202
+ }
203
  }
204
 
205
+ // 将SophNet模型转换为OpenAI格式
206
+ function transformModelsToOpenAIFormat(models: SophNetModel[]) {
207
+ const transformedModels = [];
208
+
209
+ // 为每个模型创建标准版本、搜索版本和全上下文版本
210
+ for (const model of models) {
211
+ // 添加标准模型
212
+ transformedModels.push({
213
+ id: model.modelFamily,
214
+ object: "model",
215
+ created: Date.now(),
216
+ owned_by: "sophnet",
217
+ permission: [{
218
+ id: `modelperm-${model.id}`,
219
+ object: "model_permission",
220
+ created: Date.now(),
221
+ allow_create_engine: false,
222
+ allow_sampling: true,
223
+ allow_logprobs: false,
224
+ allow_search_indices: false,
225
+ allow_view: true,
226
+ allow_fine_tuning: false,
227
+ organization: "*",
228
+ group: null,
229
+ is_blocking: false,
230
+ }],
231
+ root: model.modelFamily,
232
+ parent: null,
233
+ });
234
+
235
+ // 添加搜索版本模型
236
+ transformedModels.push({
237
+ id: `${model.modelFamily}-Search`,
238
+ object: "model",
239
+ created: Date.now(),
240
+ owned_by: "sophnet",
241
+ permission: [{
242
+ id: `modelperm-${model.id}-Search`,
243
+ object: "model_permission",
244
+ created: Date.now(),
245
+ allow_create_engine: false,
246
+ allow_sampling: true,
247
+ allow_logprobs: false,
248
+ allow_search_indices: true,
249
+ allow_view: true,
250
+ allow_fine_tuning: false,
251
+ organization: "*",
252
+ group: null,
253
+ is_blocking: false,
254
+ }],
255
+ root: model.modelFamily,
256
+ parent: null,
257
+ });
258
+
259
+ // 添加全上下文版本模型
260
+ transformedModels.push({
261
+ id: `${model.modelFamily}-Full-Context`,
262
+ object: "model",
263
+ created: Date.now(),
264
+ owned_by: "sophnet",
265
+ permission: [{
266
+ id: `modelperm-${model.id}-Full-Context`,
267
+ object: "model_permission",
268
+ created: Date.now(),
269
+ allow_create_engine: false,
270
+ allow_sampling: true,
271
+ allow_logprobs: false,
272
+ allow_search_indices: false,
273
+ allow_view: true,
274
+ allow_fine_tuning: false,
275
+ organization: "*",
276
+ group: null,
277
+ is_blocking: false,
278
+ }],
279
+ root: model.modelFamily,
280
+ parent: null,
281
+ });
282
+
283
+ // 添加全上下文+搜索版本模型
284
+ transformedModels.push({
285
+ id: `${model.modelFamily}-Full-Context-Search`,
286
+ object: "model",
287
+ created: Date.now(),
288
+ owned_by: "sophnet",
289
+ permission: [{
290
+ id: `modelperm-${model.id}-Full-Context-Search`,
291
+ object: "model_permission",
292
+ created: Date.now(),
293
+ allow_create_engine: false,
294
+ allow_sampling: true,
295
+ allow_logprobs: false,
296
+ allow_search_indices: true,
297
+ allow_view: true,
298
+ allow_fine_tuning: false,
299
+ organization: "*",
300
+ group: null,
301
+ is_blocking: false,
302
+ }],
303
+ root: model.modelFamily,
304
+ parent: null,
305
+ });
306
+ }
307
+
308
  return {
309
+ object: "list",
310
+ data: transformedModels,
 
 
 
 
311
  };
312
  }
313
 
314
+ // 处理全上下文功能
315
+ function processFullContext(messages: Message[]): Message[] {
316
+ // 复制消息数组,避免修改原数组
317
+ const messagesCopy = [...messages];
318
+
319
+ // 提取系统消息(如果存在)
320
+ const systemMessages = messagesCopy.filter(msg => msg.role === "system");
321
+
322
+ // 获取非系统消息
323
+ const nonSystemMessages = messagesCopy.filter(msg => msg.role !== "system");
324
+
325
+ // 如果消息总数少于或等于3对(6条消息),则不需要处理
326
+ if (nonSystemMessages.length <= 6) {
327
+ return messages;
328
+ }
329
+
330
+ // 提取最后3轮对话(最多6条消息)
331
+ const recentMessages = nonSystemMessages.slice(-6);
332
+
333
+ // 提取需要合并的历史消息
334
+ const historyMessages = nonSystemMessages.slice(0, -6);
335
+
336
+ // 创建历史消息的摘要
337
+ const historySummary = {
338
+ role: "user",
339
+ content: `这里是此前的对话上下文: ${JSON.stringify(historyMessages)}`
340
  };
341
+
342
+ // 组合新的消息数组:系统消息 + 历史摘要 + 最近消息
343
+ return [...systemMessages, historySummary, ...recentMessages];
 
 
 
344
  }
345
 
346
+ // 将数字转换为上标形式
347
+ function convertToSuperscript(num: number): string {
348
+ const normalDigits = '0123456789';
349
+ const superscriptDigits = '⁰¹²³⁴⁵⁶⁷⁸⁹';
350
+
351
+ return num.toString()
352
+ .split('')
353
+ .map(char => {
354
+ const index = normalDigits.indexOf(char);
355
+ return index !== -1 ? superscriptDigits[index] : char;
356
+ })
357
+ .join('');
358
+ }
359
+
360
+ // 处理聊天完成请求
361
+ async function handleChatCompletions(
362
+ token: string,
363
+ requestBody: any,
364
+ stream: boolean,
365
+ retryCount = 0,
366
+ ): Promise<Response> {
367
+ // 检查模型名称的后缀
368
+ const modelId = requestBody.model;
369
+ const webSearchEnable = modelId.includes("-Search");
370
+ const fullContextEnable = modelId.includes("-Full-Context");
371
+
372
+ // 根据后缀确定实际模型ID
373
+ let actualModelId = modelId;
374
+ if (webSearchEnable) actualModelId = actualModelId.replace("-Search", "");
375
+ if (fullContextEnable) actualModelId = actualModelId.replace("-Full-Context", "");
376
+
377
+ // 处理消息
378
+ let processedMessages = requestBody.messages;
379
+ if (fullContextEnable) {
380
+ processedMessages = processFullContext(requestBody.messages);
381
+ }
382
+
383
+ const sophNetBody = {
384
+ temperature: requestBody.temperature || 0.7,
385
+ top_p: requestBody.top_p || 0.9,
386
+ frequency_penalty: requestBody.frequency_penalty || 0,
387
+ presence_penalty: requestBody.presence_penalty || 0,
388
+ max_tokens: requestBody.max_tokens || 2048,
389
+ webSearchEnable: webSearchEnable,
390
+ stop: requestBody.stop || [],
391
+ stream: stream.toString(),
392
+ model_id: actualModelId,
393
+ messages: processedMessages,
394
+ };
395
+
396
+ try {
397
+ const response = await fetch(
398
+ `${SOPHNET_BASE_URL}/open-apis/projects/${PROJECT_UUID}/chat/completions`,
399
+ {
400
+ method: "POST",
401
+ headers: {
402
+ "Content-Type": "application/json",
403
+ "Authorization": `Bearer anon-${token}`,
404
+ "Accept": stream ? "text/event-stream" : "application/json",
405
+ "User-Agent": getRandomUserAgent(), // 使用随机UA
406
+ },
407
+ body: JSON.stringify(sophNetBody),
408
+ },
409
+ );
410
 
411
+ // 如果是401或403错误,尝试刷新token并重试
412
+ if ((response.status === 401 || response.status === 403) && retryCount < MAX_RETRIES) {
413
+ console.log(`Chat completion token expired, refreshing and retrying (${retryCount + 1}/${MAX_RETRIES})...`);
414
+ const newToken = await getAnonymousToken();
415
+ // 使用指数退避等待
416
+ const delay = getExponentialBackoffDelay(retryCount);
417
+ await sleep(delay);
418
+ return await handleChatCompletions(newToken, requestBody, stream, retryCount + 1);
419
+ }
420
+
421
+ // 如果是 429 或 500 错误,进行指数退避重试
422
+ if ((response.status === 429 || response.status >= 500) && retryCount < MAX_RETRIES) {
423
+ const delay = getExponentialBackoffDelay(retryCount);
424
+ console.warn(`Chat completion failed with status ${response.status}. Retrying in ${delay}ms... (${retryCount + 1}/${MAX_RETRIES})`);
425
+ await sleep(delay);
426
+ return handleChatCompletions(token, requestBody, stream, retryCount + 1); // 使用当前token重试,如果失败会在上面的逻辑中刷新
427
+ }
428
+
429
+ if (!response.ok) {
430
+ throw new Error(`Chat completion failed: ${response.status}`);
431
+ }
432
+
433
+ // 请求成功后获取新token并存储 (后台刷新)
434
+ getAnonymousToken().catch(err => console.error("Background token refresh failed:", err));
435
+
436
+ return response;
437
+
438
+ } catch (error) {
439
+ console.error("Error during chat completion fetch:", error);
440
+ // 如果是网络错误或其他非HTTP错误,也进行指数退避重试
441
+ if (retryCount < MAX_RETRIES) {
442
+ const delay = getExponentialBackoffDelay(retryCount);
443
+ console.warn(`Chat completion network error. Retrying in ${delay}ms... (${retryCount + 1}/${MAX_RETRIES})`);
444
+ await sleep(delay);
445
+ return handleChatCompletions(token, requestBody, stream, retryCount + 1);
446
+ }
447
+ throw error; // 达到最大重试次数后抛出错误
448
  }
449
  }
450
 
451
+ // 转换流式响应
452
+ async function* transformStreamResponse(
453
+ readableStream: ReadableStream<Uint8Array>,
454
+ ) {
455
+ const reader = readableStream.getReader();
456
  const decoder = new TextDecoder();
457
+ let buffer = "";
458
+
459
+ // 用于存储所有引用,以便在结束时生成参考资料部分
460
+ const references: Reference[] = [];
461
+ let referencesEmitted = false; // 标记是否已经发送过参考资料部分
462
+
463
+ try {
464
+ while (true) {
465
+ const { done, value } = await reader.read();
466
+ if (done) {
467
+ // 如果有引用但尚未发送,在结束前发送参考资料部分
468
+ if (references.length > 0 && !referencesEmitted) {
469
+ const referencesSection = generateReferencesSection(references);
470
+ yield `data: ${JSON.stringify({
471
+ id: `chatcmpl-${Date.now()}`, // 生成一个唯一的ID
472
+ object: "chat.completion.chunk",
473
+ created: Math.floor(Date.now() / 1000),
474
+ model: "sophnet-model", // 可以使用 SophNet 返回的模型名,或者固定一个
475
+ choices: [
476
+ {
477
+ index: 0,
478
+ delta: {
479
+ content: `\n\n${referencesSection}`,
480
+ },
481
+ finish_reason: null, // 在发送参考资料时,finish_reason通常为null
482
+ },
483
+ ],
484
+ })}\n\n`;
485
+ referencesEmitted = true;
486
+ }
487
+ break;
488
+ }
489
+
490
+ buffer += decoder.decode(value, { stream: true });
491
+ const lines = buffer.split("\n");
492
+ buffer = lines.pop() || "";
493
+
494
+ for (const line of lines) {
495
+ if (line.trim() === "" || !line.startsWith("data:")) continue;
496
+
497
+ const data = line.substring(5).trim();
498
+ if (data === "[DONE]") {
499
+ // 如果有引用但尚未发送,在结束前发送参考资料部分
500
+ if (references.length > 0 && !referencesEmitted) {
501
+ const referencesSection = generateReferencesSection(references);
502
+ yield `data: ${JSON.stringify({
503
+ id: `chatcmpl-${Date.now()}`, // 生成一个唯一的ID
504
+ object: "chat.completion.chunk",
505
+ created: Math.floor(Date.now() / 1000),
506
+ model: "sophnet-model", // 可以使用 SophNet 返回的模型名,或者固定一个
507
+ choices: [
508
+ {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
509
  index: 0,
510
+ delta: {
511
+ content: `\n\n${referencesSection}`,
512
+ },
513
+ finish_reason: null, // 在发送参考资料时,finish_reason通常为null
514
+ },
515
+ ],
516
+ })}\n\n`;
517
+ referencesEmitted = true;
518
+ }
519
+
520
+ yield "data: [DONE]\n\n";
521
+ continue;
522
+ }
523
+
524
+ try {
525
+ const sophNetEvent = JSON.parse(data);
526
+
527
+ // 检查是否包含引用
528
+ if (sophNetEvent.choices?.[0]?.refs && sophNetEvent.choices[0].refs.length > 0) {
529
+ // 处理引用
530
+ for (const ref of sophNetEvent.choices[0].refs) {
531
+ // 检查是否已经存在相同URL的引用
532
+ const existingRefIndex = references.findIndex(r => r.url === ref.url);
533
+ if (existingRefIndex === -1) {
534
+ // 添加新引用
535
+ references.push(ref);
536
+
537
+ // 生成引用标记,使用上标数字
538
+ const refIndex = references.length;
539
+ const superscriptIndex = `⁽${convertToSuperscript(refIndex)}⁾`;
540
+
541
+ // 创建带引用标记的事件
542
+ yield `data: ${JSON.stringify({
543
+ id: sophNetEvent.id || `chatcmpl-${Date.now()}`, // 使用SophNet ID或生成新ID
544
  object: "chat.completion.chunk",
545
+ created: Math.floor(Date.now() / 1000),
546
+ model: sophNetEvent.model || "sophnet-model", // 使用SophNet模型名或固定
547
+ choices: [
548
+ {
549
+ index: 0,
550
+ delta: {
551
+ // SophNet的引用信息通常不在content中,我们需要手动添加
552
+ content: `[${superscriptIndex}](${ref.url})`,
553
+ },
554
+ finish_reason: null, // 引用事件通常没有finish_reason
555
+ },
556
+ ],
557
+ })}\n\n`;
558
  }
559
  }
560
+ } else {
561
+ // 转换为OpenAI格式的事件
562
+ const openAIEvent = {
563
+ id: sophNetEvent.id || `chatcmpl-${Date.now()}`, // 使用SophNet ID或生成新ID
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
  object: "chat.completion.chunk",
565
+ created: Math.floor(Date.now() / 1000),
566
+ model: sophNetEvent.model || "sophnet-model", // 使用SophNet模型名或固定
567
+ choices: [
568
+ {
569
+ index: 0,
570
+ delta: {
571
+ // OpenAI通常将内容放在delta.content
572
+ reasoning_content: sophNetEvent.choices?.[0]?.delta?.reasoning_content || "",
573
+ content: sophNetEvent.choices?.[0]?.delta?.content || "",
574
+ },
575
+ finish_reason: sophNetEvent.choices?.[0]?.finish_reason || null,
576
+ },
577
+ ],
578
  };
579
+
580
+ yield `data: ${JSON.stringify(openAIEvent)}\n\n`;
581
  }
582
+ } catch (e) {
583
+ console.error("Error parsing event:", e, "Line:", line);
584
+ // 可以选择在这里发送一个错误事件给客户端
585
  }
 
 
 
 
 
 
586
  }
587
  }
588
+ } finally {
589
+ reader.releaseLock();
590
+ }
591
+ }
592
 
593
+ // 生成参考资料部分
594
+ function generateReferencesSection(references: Reference[]): string {
595
+ if (references.length === 0) return "";
596
+
597
+ let section = "## 参考资料\n\n";
598
+ references.forEach((ref, index) => {
599
+ section += `${index + 1}. [${ref.title}](${ref.url})\n`;
600
  });
601
+
602
+ return section;
603
  }
604
 
605
+ // 转换非流式响应
606
+ async function transformNonStreamResponse(response: Response) {
607
+ const sophNetResponse = await response.json();
608
+
609
+ // 处理引用
610
+ let content = sophNetResponse.choices?.[0]?.message?.content || "";
611
+ const references: Reference[] = [];
612
+
613
+ // 收集所有引用
614
+ if (sophNetResponse.choices?.[0]?.message?.refs && sophNetResponse.choices[0].message.refs.length > 0) {
615
+ for (const ref of sophNetResponse.choices[0].message.refs) {
616
+ references.push(ref);
617
+ }
618
+
619
+ // 为每个引用添加上标标记
620
+ references.forEach((ref, index) => {
621
+ const refIndex = index + 1;
622
+ const superscriptIndex = `⁽${convertToSuperscript(refIndex)}⁾`;
623
+ // 在内容末尾添加引用标记
624
+ content += ` [${superscriptIndex}](${ref.url})`;
625
+ });
626
+
627
+ // 添加参考资料部分
628
+ if (references.length > 0) {
629
+ content += "\n\n" + generateReferencesSection(references);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
630
  }
631
  }
 
 
 
632
 
633
+ return {
634
+ id: sophNetResponse.id || `chatcmpl-${Date.now()}`, // 使用SophNet ID或生成新ID
635
  object: "chat.completion",
636
  created: Math.floor(Date.now() / 1000),
637
+ model: sophNetResponse.model || "sophnet-model", // 使用SophNet模型名或固定
638
+ choices: [
639
+ {
640
+ index: 0,
641
+ message: {
642
+ role: "assistant",
643
+ reasoning_content: sophNetResponse.choices?.[0]?.message?.reasoning_content || "",
644
+ content: content,
645
+ },
646
+ finish_reason: sophNetResponse.choices?.[0]?.finish_reason || "stop",
647
  },
648
+ ],
649
+ usage: sophNetResponse.usage || {
650
+ prompt_tokens: 0,
651
+ completion_tokens: 0,
652
+ total_tokens: 0,
 
 
 
 
 
 
 
 
 
 
653
  },
654
+ };
655
  }
656
 
657
+ // 主处理函数
658
  async function handler(req: Request): Promise<Response> {
659
  const url = new URL(req.url);
660
+ const path = url.pathname;
661
+
662
+ // CORS预检请求处理
 
 
 
 
 
 
663
  if (req.method === "OPTIONS") {
664
+ return new Response(null, {
665
+ status: 204,
666
+ headers: {
667
+ "Access-Control-Allow-Origin": "*",
668
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
669
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
670
+ "Access-Control-Max-Age": "86400",
671
+ },
672
  });
673
  }
674
+
675
+ // 获取有效token
676
+ let token;
677
  try {
678
+ token = await getValidToken();
679
+ } catch (error) {
680
+ console.error("Failed to get token in handler:", error);
 
 
 
 
 
 
 
 
681
  return new Response(
682
+ JSON.stringify({ error: "Failed to get token", details: error.message }),
683
+ {
684
+ status: 500,
685
+ headers: {
686
  "Content-Type": "application/json",
687
+ "Access-Control-Allow-Origin": "*",
688
  },
689
+ },
690
  );
691
+ }
692
+
693
+ try {
694
+ // 模型列表接口
695
+ if (path === "/v1/models" && req.method === "GET") {
696
+ const models = await getModels(token);
697
+ const openAIModels = transformModelsToOpenAIFormat(models);
698
+
699
+ return new Response(JSON.stringify(openAIModels), {
700
+ status: 200,
701
+ headers: {
702
+ "Content-Type": "application/json",
703
+ "Access-Control-Allow-Origin": "*",
704
+ },
705
+ });
706
+ }
707
+
708
+ // 聊天完成接口
709
+ else if (path === "/v1/chat/completions" && req.method === "POST") {
710
+ const requestBody = await req.json();
711
+ const stream = requestBody.stream === true;
712
+
713
+ const sophNetResponse = await handleChatCompletions(token, requestBody, stream);
714
+
715
+ if (stream) {
716
+ const transformedStream = new ReadableStream({
717
+ async start(controller) {
718
+ try {
719
+ for await (const chunk of transformStreamResponse(sophNetResponse.body!)) {
720
+ controller.enqueue(new TextEncoder().encode(chunk));
721
+ }
722
+ controller.close();
723
+ } catch (error) {
724
+ console.error("Stream transformation error:", error);
725
+ // 在流中发送错误信息
726
+ const errorData = JSON.stringify({
727
+ error: {
728
+ message: `Stream processing error: ${error.message}`,
729
+ type: "stream_error",
730
+ code: null,
731
+ }
732
+ });
733
+ controller.enqueue(new TextEncoder().encode(`data: ${errorData}\n\n`));
734
+ controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"));
735
+ controller.close();
736
+ }
737
+ },
738
+ });
739
+
740
+ return new Response(transformedStream, {
741
+ headers: {
742
+ "Content-Type": "text/event-stream",
743
+ "Cache-Control": "no-cache",
744
+ "Connection": "keep-alive",
745
+ "Access-Control-Allow-Origin": "*",
746
+ },
747
+ });
748
+ } else {
749
+ const transformedResponse = await transformNonStreamResponse(sophNetResponse);
750
+
751
+ return new Response(JSON.stringify(transformedResponse), {
752
+ status: 200,
753
+ headers: {
754
+ "Content-Type": "application/json",
755
+ "Access-Control-Allow-Origin": "*",
756
+ },
757
+ });
758
+ }
759
+ }
760
+
761
+ // 未找到路由
762
+ else {
763
+ return new Response(
764
+ JSON.stringify({ error: "Not found", message: "Endpoint not supported" }),
765
+ {
766
+ status: 404,
767
+ headers: {
768
+ "Content-Type": "application/json",
769
+ "Access-Control-Allow-Origin": "*",
770
+ },
771
+ },
772
+ );
773
+ }
774
  } catch (error) {
775
+ console.error("Request handling error:", error);
776
+
777
+ // 返回统一的错误响应格式
778
  return new Response(
779
+ JSON.stringify({
780
+ error: {
781
+ message: error.message,
782
+ type: "api_error", // 或者更具体的错误类型
783
+ code: error.status || null, // 如果是HTTP错误,包含状态码
784
+ }
785
+ }),
786
  {
787
+ status: error.status || 500, // 使用错误状态码或默认500
788
+ headers: {
789
  "Content-Type": "application/json",
790
+ "Access-Control-Allow-Origin": "*",
791
  },
792
+ },
793
  );
794
  }
795
  }