hzruo commited on
Commit
346f0d6
·
verified ·
1 Parent(s): dd1f327

Create main.ts

Browse files
Files changed (1) hide show
  1. main.ts +671 -0
main.ts ADDED
@@ -0,0 +1,671 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { serve } from "https://deno.land/[email protected]/http/server.ts";
2
+ import { Md5 } from "https://deno.land/[email protected]/hash/md5.ts";
3
+
4
+ const API_DOMAIN = 'https://ai-api.dangbei.net';
5
+ const USER_AGENTS = [
6
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
7
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
8
+ 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36',
9
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1',
10
+ 'Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1'
11
+ ];
12
+ const VALID_API_KEY = Deno.env.get('VALID_API_KEY');
13
+ const MAX_CONVERSATIONS_PER_DEVICE = 10; // 每个设备最多创建的会话数
14
+
15
+ class ChatManage {
16
+ private currentDeviceId: string | null = null;
17
+ private currentConversationId: string | null = null;
18
+ private conversationCount = 0;
19
+ private currentUserAgent: string;
20
+
21
+ constructor() {
22
+ this.currentUserAgent = this.getRandomUserAgent();
23
+ }
24
+
25
+ private getRandomUserAgent(): string {
26
+ return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
27
+ }
28
+
29
+ getOrCreateIds(forceNew = false) {
30
+ let newDeviceId = this.currentDeviceId;
31
+ let newConversationId = this.currentConversationId;
32
+
33
+ if (forceNew || !newDeviceId || this.conversationCount >= MAX_CONVERSATIONS_PER_DEVICE) {
34
+ newDeviceId = this.generateDeviceId();
35
+ newConversationId = null;
36
+ this.conversationCount = 0;
37
+ // 在生成新设备ID时更新 User-Agent
38
+ this.currentUserAgent = this.getRandomUserAgent();
39
+ }
40
+
41
+ this.currentDeviceId = newDeviceId;
42
+ this.currentConversationId = newConversationId;
43
+
44
+ return {
45
+ deviceId: newDeviceId,
46
+ conversationId: newConversationId,
47
+ userAgent: this.currentUserAgent
48
+ };
49
+ }
50
+
51
+ updateConversationId(conversationId: string) {
52
+ this.currentConversationId = conversationId;
53
+ this.conversationCount++;
54
+ }
55
+
56
+ generateDeviceId() {
57
+ const uuid = crypto.randomUUID();
58
+ const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';
59
+ const nanoid = Array.from(crypto.getRandomValues(new Uint8Array(20)))
60
+ .map(b => urlAlphabet[b % urlAlphabet.length])
61
+ .join('');
62
+ return `${uuid.replace(/-/g, '')}_${nanoid}`;
63
+ }
64
+ }
65
+
66
+ class Pipe {
67
+ private dataPrefix = 'data:';
68
+ private chatManage = new ChatManage();
69
+ private searchModels: Record<string, string> = {
70
+ 'DeepSeek-R1-Search': 'deepseek',
71
+ 'DeepSeek-V3-Search': 'deepseek',
72
+ 'Doubao-Search': 'doubao',
73
+ 'Qwen-Search': 'qwen'
74
+ };
75
+
76
+ // 创建新的会话
77
+ async _create_conversation(deviceId: string) {
78
+ const { userAgent } = this.chatManage.getOrCreateIds(false);
79
+ const payload = { botCode: "AI_SEARCH" };
80
+ const timestamp = Math.floor(Date.now() / 1000).toString();
81
+ const nonce = this.nanoid(21);
82
+ const sign = await this.generateSign(timestamp, payload, nonce);
83
+
84
+ const headers = {
85
+ "Origin": "https://ai.dangbei.com",
86
+ "Referer": "https://ai.dangbei.com/",
87
+ "User-Agent": userAgent,
88
+ "deviceId": deviceId,
89
+ "nonce": nonce,
90
+ "sign": sign,
91
+ "timestamp": timestamp,
92
+ "Content-Type": "application/json"
93
+ };
94
+
95
+ try {
96
+ console.log('Creating conversation with:', {
97
+ url: `${API_DOMAIN}/ai-search/conversationApi/v1/create`,
98
+ headers,
99
+ payload
100
+ });
101
+
102
+ const response = await fetch(`${API_DOMAIN}/ai-search/conversationApi/v1/create`, {
103
+ method: 'POST',
104
+ headers,
105
+ body: JSON.stringify(payload),
106
+ });
107
+
108
+ console.log('Response status:', response.status);
109
+ const responseText = await response.text();
110
+ console.log('Response body:', responseText);
111
+
112
+ if (response.ok) {
113
+ try {
114
+ const data = JSON.parse(responseText);
115
+ if (data.success) {
116
+ console.log('Successfully created conversation:', data.data.conversationId);
117
+ return data.data.conversationId;
118
+ } else {
119
+ console.error('API returned success: false:', data);
120
+ }
121
+ } catch (e) {
122
+ console.error('Failed to parse response:', e);
123
+ }
124
+ } else {
125
+ console.error('HTTP error:', response.status, responseText);
126
+ }
127
+ } catch (e) {
128
+ console.error('Error creating conversation:', e);
129
+ }
130
+ return null;
131
+ }
132
+
133
+ // 新增方法:构建完整提示
134
+ _buildFullPrompt(messages: any[]): string {
135
+ if (!messages || messages.length === 0) {
136
+ return '';
137
+ }
138
+
139
+ let systemPrompt = '';
140
+ const history: string[] = [];
141
+ let lastUserMessage = '';
142
+
143
+ for (const msg of messages) {
144
+ if (msg.role === 'system' && !systemPrompt) {
145
+ systemPrompt = msg.content;
146
+ } else if (msg.role === 'user') {
147
+ history.push(`user: ${msg.content}`);
148
+ lastUserMessage = msg.content;
149
+ } else if (msg.role === 'assistant') {
150
+ history.push(`assistant: ${msg.content}`);
151
+ }
152
+ }
153
+
154
+ const parts: string[] = [];
155
+ if (systemPrompt) {
156
+ parts.push(`[System Prompt]\n${systemPrompt}`);
157
+ }
158
+ if (history.length > 1) {
159
+ parts.push(`[Chat History]\n${history.slice(0, -1).join('\n')}`);
160
+ }
161
+ parts.push(`[Question]\n${lastUserMessage}`);
162
+
163
+ return parts.join('\n\n');
164
+ }
165
+
166
+ async* pipe(body: any) {
167
+ const thinkingState = { thinking: -1 };
168
+
169
+ // Build full prompt
170
+ const fullPrompt = this._buildFullPrompt(body.messages);
171
+
172
+ // Check if we need to force new conversation
173
+ let forceNew = false;
174
+ const messages = body.messages;
175
+ if (messages.length === 1) {
176
+ forceNew = true;
177
+ } else if (messages.length >= 2) {
178
+ const lastTwo = messages.slice(-2);
179
+ if (lastTwo[0].role === 'user' && lastTwo[1].role === 'user') {
180
+ forceNew = true;
181
+ }
182
+ }
183
+
184
+ // Get or create device ID and conversation ID with User-Agent
185
+ const { deviceId, conversationId: storedConversationId, userAgent } = this.chatManage.getOrCreateIds(forceNew);
186
+ let conversationId = storedConversationId;
187
+
188
+ // Create new conversation if needed
189
+ if (!conversationId) {
190
+ conversationId = await this._create_conversation(deviceId);
191
+ if (!conversationId) {
192
+ yield { error: 'Failed to create conversation' };
193
+ return;
194
+ }
195
+ this.chatManage.updateConversationId(conversationId);
196
+ }
197
+
198
+ // Model name handling
199
+ let modelName;
200
+ const isSearchModel = body.model.endsWith('-Search');
201
+ if (isSearchModel) {
202
+ modelName = this.searchModels[body.model] || body.model.replace('-Search', '').toLowerCase();
203
+ } else {
204
+ const isDeepSeekModel = ['DeepSeek-R1', 'DeepSeek-V3'].includes(body.model);
205
+ modelName = isDeepSeekModel ? 'deepseek' : body.model.toLowerCase();
206
+ }
207
+
208
+ // 确定 userAction 参数
209
+ let userAction = '';
210
+ if (body.model.includes('DeepSeek-R1')) {
211
+ userAction = 'deep';
212
+ }
213
+ if (isSearchModel) {
214
+ userAction = userAction ? `${userAction},online` : 'online';
215
+ }
216
+
217
+ const payload = {
218
+ stream: true,
219
+ botCode: 'AI_SEARCH',
220
+ userAction,
221
+ model: modelName,
222
+ conversationId: conversationId,
223
+ question: fullPrompt,
224
+ };
225
+
226
+ const timestamp = Math.floor(Date.now() / 1000).toString();
227
+ const nonce = this.nanoid(21);
228
+ const sign = await this.generateSign(timestamp, payload, nonce);
229
+
230
+ const headers = {
231
+ 'Origin': 'https://ai.dangbei.com',
232
+ 'Referer': 'https://ai.dangbei.com/',
233
+ 'User-Agent': userAgent,
234
+ 'deviceId': deviceId,
235
+ 'nonce': nonce,
236
+ 'sign': sign,
237
+ 'timestamp': timestamp,
238
+ 'Content-Type': 'application/json',
239
+ };
240
+
241
+ try {
242
+ const response = await fetch(`${API_DOMAIN}/ai-search/chatApi/v1/chat`, {
243
+ method: 'POST',
244
+ headers,
245
+ body: JSON.stringify(payload),
246
+ });
247
+
248
+ if (!response.ok) {
249
+ const error = await response.text();
250
+ console.error('HTTP Error:', response.status, error);
251
+ yield { error: `HTTP ${response.status}: ${error}` };
252
+ return;
253
+ }
254
+
255
+ const reader = response.body!.getReader();
256
+ const decoder = new TextDecoder();
257
+ let buffer = '';
258
+ let cardMessages: string[] = [];
259
+
260
+ while (true) {
261
+ const { done, value } = await reader.read();
262
+ if (done) break;
263
+
264
+ buffer += decoder.decode(value, { stream: true });
265
+ const lines = buffer.split('\n');
266
+ buffer = lines.pop() || '';
267
+
268
+ for (const line of lines) {
269
+ if (!line.startsWith(this.dataPrefix)) continue;
270
+
271
+ try {
272
+ const data = JSON.parse(line.slice(this.dataPrefix.length));
273
+ if (data.type === 'answer') {
274
+ const content = data.content;
275
+ const contentType = data.content_type;
276
+
277
+ if (thinkingState.thinking === -1 && contentType === 'thinking') {
278
+ thinkingState.thinking = 0;
279
+ yield { choices: [{ delta: { content: '<think>\n\n' }, finish_reason: null }] };
280
+ } else if (thinkingState.thinking === 0 && contentType === 'text') {
281
+ thinkingState.thinking = 1;
282
+ yield { choices: [{ delta: { content: '\n' }, finish_reason: null }] };
283
+ yield { choices: [{ delta: { content: '</think>' }, finish_reason: null }] };
284
+ yield { choices: [{ delta: { content: '\n\n' }, finish_reason: null }] };
285
+ }
286
+
287
+ if (contentType === 'card') {
288
+ try {
289
+ const cardContent = JSON.parse(content);
290
+ const cardItems = cardContent.cardInfo.cardItems;
291
+ let markdownOutput = '\n\n---\n\n';
292
+
293
+ const searchKeywords = cardItems.find((item: any) => item.type === '2001');
294
+ if (searchKeywords) {
295
+ const keywords = JSON.parse(searchKeywords.content);
296
+ markdownOutput += `搜索关键字:${keywords.join('; ')}\n\n`;
297
+ }
298
+
299
+ const searchResults = cardItems.find((item: any) => item.type === '2002');
300
+ if (searchResults) {
301
+ const results = JSON.parse(searchResults.content);
302
+ markdownOutput += `共找到 ${results.length} 个搜索结果:\n\n`;
303
+
304
+ results.forEach((result: any) => {
305
+ markdownOutput += `[${result.idIndex}] [${result.name}](${result.url}) 来源:${result.siteName}\n`;
306
+ });
307
+ }
308
+
309
+ cardMessages.push(markdownOutput);
310
+ } catch (e) {
311
+ console.error('Error processing card:', e);
312
+ }
313
+ }
314
+
315
+ if (content && (contentType === 'text' || contentType === 'thinking')) {
316
+ yield { choices: [{ delta: { content }, finish_reason: null }] };
317
+ }
318
+ }
319
+ } catch (e) {
320
+ console.error('Parse error:', e, 'Line:', line);
321
+ yield { error: `JSONDecodeError: ${(e as Error).message}` };
322
+ return;
323
+ }
324
+ }
325
+ }
326
+
327
+ if (cardMessages.length > 0) {
328
+ yield { choices: [{ delta: { content: cardMessages.join('') }, finish_reason: null }] };
329
+ }
330
+
331
+ yield {
332
+ choices: [{
333
+ delta: {
334
+ meta: {
335
+ device_id: deviceId,
336
+ conversation_id: conversationId
337
+ }
338
+ },
339
+ finish_reason: null
340
+ }]
341
+ };
342
+
343
+ } catch (e) {
344
+ console.error('Error in pipe:', e);
345
+ yield { error: `${(e as Error).name}: ${(e as Error).message}` };
346
+ }
347
+ }
348
+
349
+ nanoid(size = 21) {
350
+ const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';
351
+ const bytes = new Uint8Array(size);
352
+ crypto.getRandomValues(bytes);
353
+ return Array.from(bytes).reverse().map(b => urlAlphabet[b & 63]).join('');
354
+ }
355
+
356
+ async generateSign(timestamp: string, payload: any, nonce: string) {
357
+ const payloadStr = JSON.stringify(payload);
358
+ const signStr = `${timestamp}${payloadStr}${nonce}`;
359
+ console.log('Sign string:', signStr);
360
+
361
+ // 使用 Deno 标准库的 MD5
362
+ const sign = new Md5()
363
+ .update(signStr)
364
+ .toString()
365
+ .toUpperCase();
366
+
367
+ console.log('Generated sign:', sign);
368
+ return sign;
369
+ }
370
+ }
371
+
372
+ const pipe = new Pipe();
373
+
374
+ // 验证 API 密钥
375
+ function verifyApiKey(request: Request) {
376
+ const authorization = request.headers.get('Authorization');
377
+ if (!authorization) {
378
+ return new Response(JSON.stringify({ error: 'Missing API key' }), {
379
+ status: 401,
380
+ headers: {
381
+ 'Content-Type': 'application/json',
382
+ 'Access-Control-Allow-Origin': '*',
383
+ },
384
+ });
385
+ }
386
+
387
+ const apiKey = authorization.replace('Bearer ', '').trim();
388
+ if (apiKey !== VALID_API_KEY) {
389
+ return new Response(JSON.stringify({ error: 'Invalid API key' }), {
390
+ status: 401,
391
+ headers: {
392
+ 'Content-Type': 'application/json',
393
+ 'Access-Control-Allow-Origin': '*',
394
+ },
395
+ });
396
+ }
397
+
398
+ return null;
399
+ }
400
+
401
+ async function handleRequest(request: Request) {
402
+ const url = new URL(request.url);
403
+
404
+ // 添加根路径处理
405
+ if (request.method === 'GET' && url.pathname === '/') {
406
+ return new Response("it's work!", {
407
+ headers: {
408
+ 'Content-Type': 'text/plain',
409
+ 'Access-Control-Allow-Origin': '*',
410
+ },
411
+ });
412
+ }
413
+
414
+ if (request.method === 'OPTIONS') {
415
+ return new Response(null, {
416
+ headers: {
417
+ 'Access-Control-Allow-Origin': '*',
418
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
419
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
420
+ },
421
+ });
422
+ }
423
+
424
+ // 验证 API 密钥(除了 OPTIONS 请求)
425
+ const authError = verifyApiKey(request);
426
+ if (authError) return authError;
427
+
428
+ if (request.method === 'GET' && url.pathname === '/v1/models') {
429
+ const currentTime = Math.floor(Date.now() / 1000);
430
+ return new Response(JSON.stringify({
431
+ object: 'list',
432
+ data: [
433
+ // Original models
434
+ {
435
+ id: 'DeepSeek-R1',
436
+ object: 'model',
437
+ created: currentTime,
438
+ owned_by: 'library'
439
+ },
440
+ {
441
+ id: 'DeepSeek-V3',
442
+ object: 'model',
443
+ created: currentTime,
444
+ owned_by: 'library'
445
+ },
446
+ {
447
+ id: 'Doubao',
448
+ object: 'model',
449
+ created: currentTime,
450
+ owned_by: 'library'
451
+ },
452
+ {
453
+ id: 'Qwen',
454
+ object: 'model',
455
+ created: currentTime,
456
+ owned_by: 'library'
457
+ },
458
+ {
459
+ id: 'Glm3',
460
+ object: 'model',
461
+ created: currentTime,
462
+ owned_by: 'library'
463
+ },
464
+ {
465
+ id: 'Moonshot_v1',
466
+ object: 'model',
467
+ created: currentTime,
468
+ owned_by: 'library'
469
+ },
470
+ // Search-enabled models
471
+ {
472
+ id: 'DeepSeek-R1-Search',
473
+ object: 'model',
474
+ created: currentTime,
475
+ owned_by: 'library',
476
+ features: ['online_search']
477
+ },
478
+ {
479
+ id: 'DeepSeek-V3-Search',
480
+ object: 'model',
481
+ created: currentTime,
482
+ owned_by: 'library',
483
+ features: ['online_search']
484
+ },
485
+ {
486
+ id: 'Doubao-Search',
487
+ object: 'model',
488
+ created: currentTime,
489
+ owned_by: 'library',
490
+ features: ['online_search']
491
+ },
492
+ {
493
+ id: 'Qwen-Search',
494
+ object: 'model',
495
+ created: currentTime,
496
+ owned_by: 'library',
497
+ features: ['online_search']
498
+ },
499
+ {
500
+ id: 'Glm3-Search',
501
+ object: 'model',
502
+ created: currentTime,
503
+ owned_by: 'library',
504
+ features: ['online_search']
505
+ },
506
+ {
507
+ id: 'Moonshot_v1-Search',
508
+ object: 'model',
509
+ created: currentTime,
510
+ owned_by: 'library',
511
+ features: ['online_search']
512
+ }
513
+ ]
514
+ }), {
515
+ headers: {
516
+ 'Content-Type': 'application/json',
517
+ 'Access-Control-Allow-Origin': '*',
518
+ },
519
+ });
520
+ }
521
+
522
+ if (request.method === 'POST' && url.pathname === '/v1/chat/completions') {
523
+ const body = await request.json();
524
+ const isStream = body.stream || false;
525
+
526
+ if (isStream) {
527
+ const stream = new ReadableStream({
528
+ async start(controller) {
529
+ try {
530
+ for await (const chunk of pipe.pipe(body)) {
531
+ controller.enqueue(new TextEncoder().encode(`data: ${JSON.stringify(chunk)}\n\n`));
532
+ }
533
+ controller.enqueue(new TextEncoder().encode('data: [DONE]\n\n'));
534
+ controller.close();
535
+ } catch (e) {
536
+ console.error('Error in stream:', e);
537
+ controller.error(e);
538
+ }
539
+ },
540
+ });
541
+
542
+ return new Response(stream, {
543
+ headers: {
544
+ 'Content-Type': 'text/event-stream',
545
+ 'Cache-Control': 'no-cache',
546
+ 'Connection': 'keep-alive',
547
+ 'Access-Control-Allow-Origin': '*',
548
+ },
549
+ });
550
+ }
551
+
552
+ if (!isStream) {
553
+ let content = '';
554
+ let meta = null;
555
+ let thinking_content: string[] = [];
556
+ let is_thinking = false;
557
+
558
+ try {
559
+ for await (const chunk of pipe.pipe(body)) {
560
+ if (chunk.choices?.[0]?.delta?.content) {
561
+ const content_chunk = chunk.choices[0].delta.content;
562
+ if (content_chunk === '<think>\n\n') {
563
+ is_thinking = true;
564
+ } else if (content_chunk === '\n</think>\n\n') {
565
+ is_thinking = false;
566
+ } else if (is_thinking) {
567
+ thinking_content.push(content_chunk);
568
+ } else {
569
+ content += content_chunk;
570
+ }
571
+ }
572
+ if (chunk.choices?.[0]?.delta?.meta) {
573
+ meta = chunk.choices[0].delta.meta;
574
+ }
575
+ }
576
+
577
+ // 处理思考内容
578
+ const reasoningContent = thinking_content.join('');
579
+
580
+ return new Response(JSON.stringify({
581
+ id: crypto.randomUUID(),
582
+ object: 'chat.completion',
583
+ created: Math.floor(Date.now() / 1000),
584
+ model: body.model,
585
+ choices: [{
586
+ message: {
587
+ role: 'assistant',
588
+ reasoning_content: reasoningContent ? `<think>\n${reasoningContent}\n</think>` : '',
589
+ content: content.trim(),
590
+ meta: meta
591
+ },
592
+ finish_reason: 'stop'
593
+ }]
594
+ } as NonStreamResponse), {
595
+ headers: {
596
+ 'Content-Type': 'application/json',
597
+ 'Access-Control-Allow-Origin': '*',
598
+ },
599
+ });
600
+ } catch (e) {
601
+ console.error('Error processing chat request:', e);
602
+ return new Response(JSON.stringify({ error: 'Internal Server Error' }), {
603
+ status: 500,
604
+ headers: {
605
+ 'Content-Type': 'application/json',
606
+ 'Access-Control-Allow-Origin': '*',
607
+ },
608
+ });
609
+ }
610
+ }
611
+ }
612
+
613
+ return new Response('Not Found', { status: 404 });
614
+ }
615
+
616
+ serve(handleRequest, { port: 8787 });
617
+
618
+ interface Message {
619
+ role: string;
620
+ content: string;
621
+ }
622
+
623
+ interface ChatRequest {
624
+ model: string;
625
+ messages: Message[];
626
+ stream: boolean;
627
+ temperature?: number;
628
+ top_p?: number;
629
+ n?: number;
630
+ max_tokens?: number;
631
+ presence_penalty?: number;
632
+ frequency_penalty?: number;
633
+ user?: string;
634
+ }
635
+
636
+ interface DeltaContent {
637
+ content?: string;
638
+ meta?: {
639
+ device_id: string;
640
+ conversation_id: string;
641
+ };
642
+ }
643
+
644
+ interface Choice {
645
+ delta: DeltaContent;
646
+ finish_reason: string | null;
647
+ }
648
+
649
+ interface StreamResponse {
650
+ choices?: Choice[];
651
+ error?: string;
652
+ }
653
+
654
+ interface NonStreamResponse {
655
+ id: string;
656
+ object: string;
657
+ created: number;
658
+ model: string;
659
+ choices: Array<{
660
+ message: {
661
+ role: string;
662
+ reasoning_content: string;
663
+ content: string;
664
+ meta: {
665
+ device_id: string;
666
+ conversation_id: string;
667
+ };
668
+ };
669
+ finish_reason: string;
670
+ }>;
671
+ }