yxmiler commited on
Commit
69c7de2
·
verified ·
1 Parent(s): f0c76ab

Update index.js

Browse files
Files changed (1) hide show
  1. index.js +1358 -1096
index.js CHANGED
@@ -1,1097 +1,1359 @@
1
- import express from 'express';
2
- import fetch from 'node-fetch';
3
- import dotenv from 'dotenv';
4
- import { v4 as uuidv4 } from 'uuid';
5
- import cors from 'cors';
6
-
7
- // 初始化环境变量
8
- dotenv.config();
9
-
10
- /**
11
- * 配置管理类
12
- */
13
- class Config {
14
- constructor() {
15
- this.initializeApiKeys();
16
- this.initializeAuth();
17
-
18
- // 已使用的API Key池
19
- this.usedApiKeys = [];
20
-
21
- // 失效的API Key池
22
- this.invalidApiKeys = [];
23
-
24
- // Gemini安全设置
25
- this.geminiSafety = [
26
- {
27
- category: 'HARM_CATEGORY_HARASSMENT',
28
- threshold: 'OFF',
29
- },
30
- {
31
- category: 'HARM_CATEGORY_HATE_SPEECH',
32
- threshold: 'OFF',
33
- },
34
- {
35
- category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
36
- threshold: 'OFF',
37
- },
38
- {
39
- category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
40
- threshold: 'OFF',
41
- },
42
- {
43
- category: 'HARM_CATEGORY_CIVIC_INTEGRITY',
44
- threshold: 'OFF',
45
- },
46
- ];
47
- }
48
-
49
- /**
50
- * 初始化API Keys
51
- */
52
- initializeApiKeys() {
53
- const apiKeysEnv = process.env.GEMINI_API_KEYS;
54
- if (!apiKeysEnv) {
55
- console.error('❌ 错误: 未找到 GEMINI_API_KEYS 环境变量');
56
- process.exit(1);
57
- }
58
-
59
- // 通过换行符分割API Keys
60
- this.apiKeys = apiKeysEnv
61
- .split('\n')
62
- .map(key => key.trim())
63
- .filter(key => key.length > 0);
64
-
65
- if (this.apiKeys.length === 0) {
66
- console.error('❌ 错误: 没有找到有效的API Keys');
67
- process.exit(1);
68
- }
69
-
70
- console.log(`✅ 成功加载 ${this.apiKeys.length} 个API Keys`);
71
- }
72
-
73
- /**
74
- * 初始化认证配置
75
- */
76
- initializeAuth() {
77
- this.authToken = process.env.AUTH_TOKEN || 'sk-123456';
78
- }
79
-
80
- /**
81
- * 获取可用的API Key(负载均衡)
82
- */
83
- getApiKey() {
84
- if (this.apiKeys.length === 0) {
85
- if (this.usedApiKeys.length > 0) {
86
- this.apiKeys.push(...this.usedApiKeys);
87
- this.usedApiKeys = [];
88
- } else {
89
- return null;
90
- }
91
- }
92
-
93
- const apiKey = this.apiKeys.shift();
94
- this.usedApiKeys.push(apiKey);
95
- return apiKey;
96
- }
97
-
98
- /**
99
- * 获取第一个可用的API Key(用于模型列表请求)
100
- */
101
- getFirstAvailableApiKey() {
102
- if (this.apiKeys.length > 0) {
103
- return this.apiKeys[0];
104
- }
105
- if (this.usedApiKeys.length > 0) {
106
- return this.usedApiKeys[0];
107
- }
108
- return null;
109
- }
110
-
111
- /**
112
- * 将API Key标记为失效
113
- */
114
- markKeyAsInvalid(apiKey) {
115
- const usedIndex = this.usedApiKeys.indexOf(apiKey);
116
- if (usedIndex !== -1) {
117
- this.usedApiKeys.splice(usedIndex, 1);
118
- }
119
-
120
- const mainIndex = this.apiKeys.indexOf(apiKey);
121
- if (mainIndex !== -1) {
122
- this.apiKeys.splice(mainIndex, 1);
123
- }
124
-
125
- if (!this.invalidApiKeys.includes(apiKey)) {
126
- this.invalidApiKeys.push(apiKey);
127
- }
128
-
129
- console.warn(`⚠️ API Key 已标记为失效: ${apiKey.substring(0, 10)}...`);
130
- }
131
-
132
- /**
133
- * 将API Key移回已使用池
134
- */
135
- moveToUsed(apiKey) {
136
- if (!this.usedApiKeys.includes(apiKey)) {
137
- this.usedApiKeys.push(apiKey);
138
- }
139
- }
140
-
141
- /**
142
- * 验证授权头
143
- */
144
- validateAuth(authHeader) {
145
- if (!authHeader) {
146
- return false;
147
- }
148
-
149
- const token = authHeader.replace('Bearer ', '');
150
- return token === this.authToken;
151
- }
152
- }
153
-
154
- /**
155
- * 图片处理器类
156
- */
157
- class ImageProcessor {
158
- /**
159
- * 从data URL中提取MIME类型和base64数据
160
- */
161
- static parseDataUrl(dataUrl) {
162
- try {
163
- // 匹配data:image/jpeg;base64,<base64data>格式
164
- const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
165
- if (!match) {
166
- throw new Error('无效的data URL格式');
167
- }
168
-
169
- const mimeType = match[1];
170
- const base64Data = match[2];
171
-
172
- // 验证MIME类型是否为支持的图片格式
173
- const supportedMimeTypes = [
174
- 'image/jpeg',
175
- 'image/jpg',
176
- 'image/png',
177
- 'image/gif',
178
- 'image/webp',
179
- 'image/bmp',
180
- 'image/tiff'
181
- ];
182
-
183
- if (!supportedMimeTypes.includes(mimeType.toLowerCase())) {
184
- throw new Error(`不支持的图片格式: ${mimeType}`);
185
- }
186
-
187
- return {
188
- mimeType,
189
- data: base64Data
190
- };
191
- } catch (error) {
192
- console.error('解析图片data URL错误:', error);
193
- throw error;
194
- }
195
- }
196
-
197
- /**
198
- * 验证base64数据是否有效
199
- */
200
- static validateBase64(base64String) {
201
- try {
202
- // 基本格式检查
203
- if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64String)) {
204
- return false;
205
- }
206
-
207
- // 长度检查(base64编码长度应该是4的倍数)
208
- return base64String.length % 4 === 0;
209
- } catch (error) {
210
- return false;
211
- }
212
- }
213
-
214
- /**
215
- * 从URL下载图片并转换为base64
216
- */
217
- static async fetchImageAsBase64(imageUrl) {
218
- try {
219
- const response = await fetch(imageUrl, {
220
- timeout: 30000, // 30秒超时
221
- headers: {
222
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
223
- }
224
- });
225
-
226
- if (!response.ok) {
227
- throw new Error(`获取图片失败: HTTP ${response.status}`);
228
- }
229
-
230
- const contentType = response.headers.get('content-type');
231
- if (!contentType || !contentType.startsWith('image/')) {
232
- throw new Error(`URL返回的不是图片类型: ${contentType}`);
233
- }
234
-
235
- const buffer = await response.buffer();
236
- const base64Data = buffer.toString('base64');
237
-
238
- return {
239
- mimeType: contentType,
240
- data: base64Data
241
- };
242
- } catch (error) {
243
- console.error('下载图片错误:', error);
244
- throw error;
245
- }
246
- }
247
- }
248
-
249
- /**
250
- * 消息转换器类(增强版)
251
- */
252
- class MessageConverter {
253
- /**
254
- * 将OpenAI格式的消息转换为Gemini格式(支持图片)
255
- */
256
- static async convertMessages(openaiMessages) {
257
- const geminiMessages = [];
258
- let currentRole = null;
259
- let currentParts = [];
260
-
261
- for (const message of openaiMessages) {
262
- let role = message.role;
263
- let content = message.content;
264
-
265
- // 角色转换
266
- if (role === 'system') {
267
- role = 'user';
268
- }
269
-
270
- if (role === 'assistant') {
271
- role = 'model';
272
- }
273
-
274
- // 处理内容
275
- let parts = [];
276
-
277
- if (typeof content === 'string') {
278
- // 简单文本消息
279
- parts = [{ text: content }];
280
- } else if (Array.isArray(content)) {
281
- // 多模态消息(包含文本和图片)
282
- parts = await this.convertContentArray(content);
283
- } else {
284
- // 其他格式,转为文本
285
- parts = [{ text: String(content) }];
286
- }
287
-
288
- // 合并相同角色的连续消息
289
- if (role === currentRole) {
290
- currentParts.push(...parts);
291
- } else {
292
- // 保存上一个角色的消息
293
- if (currentRole !== null && currentParts.length > 0) {
294
- geminiMessages.push({
295
- role: currentRole,
296
- parts: currentParts
297
- });
298
- }
299
-
300
- // 开始新角色
301
- currentRole = role;
302
- currentParts = [...parts];
303
- }
304
- }
305
-
306
- // 添加最后一个消息
307
- if (currentRole !== null && currentParts.length > 0) {
308
- geminiMessages.push({
309
- role: currentRole,
310
- parts: currentParts
311
- });
312
- }
313
-
314
- return geminiMessages;
315
- }
316
-
317
- /**
318
- * 转换OpenAI的content数组为Gemini的parts格式
319
- */
320
- static async convertContentArray(contentArray) {
321
- const parts = [];
322
-
323
- for (const item of contentArray) {
324
- try {
325
- if (item.type === 'text') {
326
- // 文本内容
327
- parts.push({ text: item.text || '' });
328
- } else if (item.type === 'image_url') {
329
- // 图片内容
330
- const imagePart = await this.convertImageContent(item);
331
- if (imagePart) {
332
- parts.push(imagePart);
333
- }
334
- } else {
335
- // 其他类型,尝试转为文本
336
- console.warn(`未知的内容类型: ${item.type},将转为文本处理`);
337
- parts.push({ text: JSON.stringify(item) });
338
- }
339
- } catch (error) {
340
- console.error('转换内容项错误:', error);
341
- // 出错时跳过该项,避免整个请求失败
342
- continue;
343
- }
344
- }
345
-
346
- return parts;
347
- }
348
-
349
- /**
350
- * 转换图片内容为Gemini格式
351
- */
352
- static async convertImageContent(imageItem) {
353
- try {
354
- const imageUrl = imageItem.image_url?.url;
355
- if (!imageUrl) {
356
- throw new Error('缺少图片URL');
357
- }
358
-
359
- let imageData;
360
-
361
- if (imageUrl.startsWith('data:')) {
362
- // 处理base64数据URL
363
- imageData = ImageProcessor.parseDataUrl(imageUrl);
364
-
365
- // 验证base64数据
366
- if (!ImageProcessor.validateBase64(imageData.data)) {
367
- throw new Error('无效的base64图片数据');
368
- }
369
- } else if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
370
- // 处理网络图片URL
371
- imageData = await ImageProcessor.fetchImageAsBase64(imageUrl);
372
- } else {
373
- throw new Error(`不支持的图片URL格式: ${imageUrl}`);
374
- }
375
-
376
- // 返回Gemini格式的图片数据
377
- return {
378
- inlineData: {
379
- mimeType: imageData.mimeType,
380
- data: imageData.data
381
- }
382
- };
383
- } catch (error) {
384
- console.error('转换图片内容错误:', error);
385
- // 返回错误信息文本,而不是抛出异常
386
- return { text: `[图片处理失败: ${error.message}]` };
387
- }
388
- }
389
-
390
- /**
391
- * 从OpenAI请求中提取参数
392
- */
393
- static extractParams(openaiRequest) {
394
- return {
395
- model: openaiRequest.model || 'gemini-1.5-flash',
396
- messages: openaiRequest.messages || [],
397
- stream: openaiRequest.stream || false,
398
- temperature: openaiRequest.temperature,
399
- maxTokens: openaiRequest.max_tokens,
400
- topP: openaiRequest.top_p
401
- };
402
- }
403
- }
404
-
405
- /**
406
- * 模型管理类
407
- */
408
- class ModelManager {
409
- constructor(config) {
410
- this.config = config;
411
- this.cachedModels = null;
412
- this.cacheExpiry = null;
413
- this.cacheTimeout = 5 * 60 * 1000; // 5分钟缓存
414
- }
415
-
416
- /**
417
- * 获取模型列表
418
- */
419
- async getModels() {
420
- if (this.cachedModels && this.cacheExpiry && Date.now() < this.cacheExpiry) {
421
- return { success: true, data: this.cachedModels };
422
- }
423
-
424
- const apiKey = this.config.getFirstAvailableApiKey();
425
- if (!apiKey) {
426
- return {
427
- success: false,
428
- error: '没有可用的API Key',
429
- status: 503
430
- };
431
- }
432
-
433
- try {
434
- const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`, {
435
- method: 'GET',
436
- headers: {
437
- 'Content-Type': 'application/json'
438
- }
439
- });
440
-
441
- if (!response.ok) {
442
- return {
443
- success: false,
444
- error: `获取模型列表失败: ${response.status}`,
445
- status: response.status
446
- };
447
- }
448
-
449
- const geminiResponse = await response.json();
450
- const filteredModels = this.filterModels(geminiResponse.models || []);
451
-
452
- this.cachedModels = filteredModels;
453
- this.cacheExpiry = Date.now() + this.cacheTimeout;
454
-
455
- return { success: true, data: filteredModels };
456
-
457
- } catch (error) {
458
- console.error('获取模型列表错误:', error);
459
- return {
460
- success: false,
461
- error: '网络请求失败',
462
- status: 500
463
- };
464
- }
465
- }
466
-
467
- /**
468
- * 过滤模型 - 保持原始Gemini模型名,并增加视觉模型支持
469
- */
470
- filterModels(models) {
471
- const allowedPrefixes = [
472
- 'models/gemini-2.5-flash',
473
- 'models/gemini-2.0-flash',
474
- 'models/gemini-1.5-flash'
475
- ];
476
-
477
- const excludedModels = [
478
- 'models/gemini-1.5-flash-8b'
479
- ];
480
-
481
- const filteredModels = models.filter(model => {
482
- const modelName = model.name;
483
-
484
- if (excludedModels.some(excluded => modelName.startsWith(excluded))) {
485
- return false;
486
- }
487
- if(modelName == "models/gemini-2.5-pro"){
488
- return true;
489
- }
490
-
491
- return allowedPrefixes.some(prefix => modelName.startsWith(prefix));
492
- });
493
-
494
- // 转换为OpenAI格式但保持Gemini模型名
495
- const processedModels = filteredModels.map(model => {
496
- const modelId = model.name.replace('models/', '');
497
-
498
- return {
499
- id: modelId, // 直接使用Gemini模型名
500
- object: 'model',
501
- created: Math.floor(Date.now() / 1000),
502
- owned_by: 'google',
503
- permission: [
504
- {
505
- id: `modelperm-${modelId}`,
506
- object: 'model_permission',
507
- created: Math.floor(Date.now() / 1000),
508
- allow_create_engine: false,
509
- allow_sampling: true,
510
- allow_logprobs: false,
511
- allow_search_indices: false,
512
- allow_view: true,
513
- allow_fine_tuning: false,
514
- organization: '*',
515
- group: null,
516
- is_blocking: false
517
- }
518
- ],
519
- root: modelId,
520
- parent: null
521
- };
522
- });
523
-
524
- return {
525
- object: 'list',
526
- data: processedModels
527
- };
528
- }
529
- }
530
-
531
- /**
532
- * Gemini API 请求构建器类
533
- */
534
- class GeminiRequestBuilder {
535
- constructor(config) {
536
- this.config = config;
537
- }
538
-
539
- /**
540
- * 构建Gemini API请求体
541
- */
542
- buildRequestBody(geminiMessages, params) {
543
- const requestBody = {
544
- contents: geminiMessages,
545
- safetySettings: this.config.geminiSafety,
546
- generationConfig: {}
547
- };
548
-
549
- if (params.temperature !== undefined) {
550
- requestBody.generationConfig.temperature = params.temperature;
551
- }
552
-
553
- if (params.maxTokens !== undefined) {
554
- requestBody.generationConfig.maxOutputTokens = params.maxTokens;
555
- }
556
-
557
- if (params.topP !== undefined) {
558
- requestBody.generationConfig.topP = params.topP;
559
- }
560
-
561
- return requestBody;
562
- }
563
-
564
- /**
565
- * 构建Gemini API URL
566
- */
567
- buildApiUrl(model, apiKey, isStream = false) {
568
- const method = isStream ? 'streamGenerateContent' : 'generateContent';
569
- return `https://generativelanguage.googleapis.com/v1beta/models/${model}:${method}?key=${apiKey}`;
570
- }
571
- }
572
-
573
- /**
574
- * 响应转换器类
575
- */
576
- class ResponseConverter {
577
- /**
578
- * 将Gemini流式响应块转换为OpenAI格式
579
- */
580
- static convertStreamChunk(geminiData, requestId, model) {
581
- try {
582
- if (geminiData.candidates && geminiData.candidates[0]) {
583
- const candidate = geminiData.candidates[0];
584
- if (candidate.content && candidate.content.parts) {
585
- const text = candidate.content.parts[0]?.text || '';
586
- const openaiChunk = {
587
- id: requestId,
588
- object: 'chat.completion.chunk',
589
- created: Math.floor(Date.now() / 1000),
590
- model: model, // 使用当前请求的模型名
591
- choices: [{
592
- index: 0,
593
- delta: { content: text },
594
- finish_reason: candidate.finishReason === 'STOP' ? 'stop' : null
595
- }]
596
- };
597
- return `data: ${JSON.stringify(openaiChunk)}\n\n`;
598
- }
599
- }
600
- return '';
601
- } catch (error) {
602
- console.error('转换流响应块错误:', error);
603
- return '';
604
- }
605
- }
606
-
607
- /**
608
- * 将Gemini非流式响应转换为OpenAI格式
609
- */
610
- static convertNormalResponse(geminiResponse, requestId, model) {
611
- const openaiResponse = {
612
- id: requestId,
613
- object: 'chat.completion',
614
- created: Math.floor(Date.now() / 1000),
615
- model: model, // 使用当前请求的模型名
616
- choices: [],
617
- usage: {
618
- prompt_tokens: 0,
619
- completion_tokens: 0,
620
- total_tokens: 0
621
- }
622
- };
623
-
624
- if (geminiResponse.candidates && geminiResponse.candidates[0]) {
625
- const candidate = geminiResponse.candidates[0];
626
- if (candidate.content && candidate.content.parts) {
627
- const text = candidate.content.parts.map(part => part.text).join('');
628
- openaiResponse.choices.push({
629
- index: 0,
630
- message: {
631
- role: 'assistant',
632
- content: text
633
- },
634
- finish_reason: candidate.finishReason === 'STOP' ? 'stop' : 'length'
635
- });
636
- }
637
- }
638
-
639
- // 尝试从usage信息中获取token使用量
640
- if (geminiResponse.usageMetadata) {
641
- openaiResponse.usage = {
642
- prompt_tokens: geminiResponse.usageMetadata.promptTokenCount || 0,
643
- completion_tokens: geminiResponse.usageMetadata.candidatesTokenCount || 0,
644
- total_tokens: geminiResponse.usageMetadata.totalTokenCount || 0
645
- };
646
- }
647
-
648
- return openaiResponse;
649
- }
650
- }
651
-
652
- /**
653
- * Gemini实时流式响应解析器
654
- */
655
- class GeminiRealtimeStreamParser {
656
- constructor(response, onChunk) {
657
- this.response = response;
658
- this.onChunk = onChunk;
659
- this.buffer = '';
660
- this.bufferLv = 0;
661
- this.inString = false;
662
- this.escapeNext = false;
663
- this.decoder = new TextDecoder();
664
- }
665
-
666
- async start() {
667
- try {
668
- for await (const chunk of this.response.body) {
669
- const text = this.decoder.decode(chunk, { stream: true });
670
- await this.processText(text);
671
- }
672
-
673
- await this.handleRemainingBuffer();
674
- } catch (error) {
675
- console.error('流式解析错误:', error);
676
- throw error;
677
- }
678
- }
679
-
680
- async processText(text) {
681
- for (const char of text) {
682
- if (this.escapeNext) {
683
- if (this.bufferLv > 1) {
684
- this.buffer += char;
685
- }
686
- this.escapeNext = false;
687
- continue;
688
- }
689
-
690
- if (char === '\\' && this.inString) {
691
- this.escapeNext = true;
692
- if (this.bufferLv > 1) {
693
- this.buffer += char;
694
- }
695
- continue;
696
- }
697
-
698
- if (char === '"') {
699
- this.inString = !this.inString;
700
- }
701
-
702
- if (!this.inString) {
703
- if (char === '{' || char === '[') {
704
- this.bufferLv++;
705
- } else if (char === '}' || char === ']') {
706
- this.bufferLv--;
707
- }
708
- }
709
-
710
- if (this.bufferLv > 1) {
711
- if (this.inString && char === '\n') {
712
- this.buffer += '\\n';
713
- } else {
714
- this.buffer += char;
715
- }
716
- } else if (this.bufferLv === 1 && this.buffer) {
717
- this.buffer += '}';
718
-
719
- try {
720
- const bufferJson = JSON.parse(this.buffer);
721
- await this.onChunk(bufferJson);
722
- } catch (parseError) {
723
- console.error('解析Gemini流数据错误:', parseError);
724
- }
725
-
726
- this.buffer = '';
727
- }
728
- }
729
- }
730
-
731
- async handleRemainingBuffer() {
732
- if (this.buffer.trim() && this.bufferLv >= 1) {
733
- try {
734
- if (!this.buffer.endsWith('}')) {
735
- this.buffer += '}';
736
- }
737
- const bufferJson = JSON.parse(this.buffer);
738
- await this.onChunk(bufferJson);
739
- } catch (parseError) {
740
- console.error('解析最后的缓冲区数据错误:', parseError);
741
- }
742
- }
743
- }
744
- }
745
-
746
- /**
747
- * 认证中间件
748
- */
749
- class AuthMiddleware {
750
- constructor(config) {
751
- this.config = config;
752
- }
753
-
754
- middleware() {
755
- return (req, res, next) => {
756
- // 跳过健康检查和预检请求
757
- if (req.path === '/health' || req.method === 'OPTIONS') {
758
- return next();
759
- }
760
-
761
- const authHeader = req.headers.authorization;
762
-
763
- if (!this.config.validateAuth(authHeader)) {
764
- return res.status(401).json({
765
- error: {
766
- message: 'Invalid authentication credentials',
767
- type: 'invalid_request_error',
768
- code: 'invalid_api_key'
769
- }
770
- });
771
- }
772
-
773
- next();
774
- };
775
- }
776
- }
777
-
778
- /**
779
- * API代理服务类
780
- */
781
- class ApiProxyService {
782
- constructor() {
783
- this.config = new Config();
784
- this.requestBuilder = new GeminiRequestBuilder(this.config);
785
- this.modelManager = new ModelManager(this.config);
786
- this.authMiddleware = new AuthMiddleware(this.config);
787
- }
788
-
789
- /**
790
- * 处理聊天API请求(支持图片)
791
- */
792
- async handleChatRequest(req, res) {
793
- try {
794
- const requestId = `chatcmpl-${uuidv4()}`;
795
-
796
- const params = MessageConverter.extractParams(req.body);
797
-
798
- // 异步转换消息(支持图片处理)
799
- const geminiMessages = await MessageConverter.convertMessages(params.messages);
800
-
801
- if (!geminiMessages || geminiMessages.length === 0) {
802
- return res.status(400).json({
803
- error: {
804
- message: '无效的消息格式或消息为空',
805
- type: 'invalid_request_error',
806
- code: 'invalid_messages'
807
- }
808
- });
809
- }
810
-
811
- const requestBody = this.requestBuilder.buildRequestBody(geminiMessages, params);
812
-
813
- if (params.stream) {
814
- const result = await this.handleStreamRequest(requestBody, params, requestId, res);
815
- if (!result.success) {
816
- res.status(result.status || 500).json({ error: result.error });
817
- }
818
- } else {
819
- const result = await this.executeNormalRequest(requestBody, params, requestId);
820
- if (result.success) {
821
- res.json(result.data);
822
- } else {
823
- res.status(result.status || 500).json({ error: result.error });
824
- }
825
- }
826
- } catch (error) {
827
- console.error('处理聊天请求错误:', error);
828
- res.status(500).json({
829
- error: {
830
- message: '内部服务器错误: ' + error.message,
831
- type: 'internal_server_error',
832
- code: 'server_error'
833
- }
834
- });
835
- }
836
- }
837
-
838
- /**
839
- * 处理流式请求
840
- */
841
- async handleStreamRequest(requestBody, params, requestId, res, retryCount = 0) {
842
- const maxRetries = 3;
843
-
844
- const apiKey = this.config.getApiKey();
845
- if (!apiKey) {
846
- return { success: false, error: '目前暂无可用的API Key', status: 503 };
847
- }
848
-
849
- try {
850
- const apiUrl = this.requestBuilder.buildApiUrl(params.model, apiKey, true);
851
-
852
- const response = await fetch(apiUrl, {
853
- method: 'POST',
854
- headers: { 'Content-Type': 'application/json' },
855
- body: JSON.stringify(requestBody)
856
- });
857
-
858
- if (response.status === 403) {
859
- this.config.markKeyAsInvalid(apiKey);
860
- if (retryCount < maxRetries) {
861
- return await this.handleStreamRequest(requestBody, params, requestId, res, retryCount + 1);
862
- }
863
- return { success: false, error: 'API Key 无效', status: 403 };
864
- }
865
-
866
- if (response.status === 429) {
867
- this.config.moveToUsed(apiKey);
868
- if (retryCount < maxRetries) {
869
- return await this.handleStreamRequest(requestBody, params, requestId, res, retryCount + 1);
870
- }
871
- return { success: false, error: '请求频率过高,请稍后重试', status: 429 };
872
- }
873
-
874
- if (response.status === 500) {
875
- this.config.moveToUsed(apiKey);
876
- return { success: false, error: '目前服务器繁忙,请稍后重试', status: 500 };
877
- }
878
-
879
- if (!response.ok) {
880
- const errorText = await response.text();
881
- console.error(`API请求失败: ${response.status}, 错误信息: ${errorText}`);
882
- return { success: false, error: `API请求失败: ${response.status}`, status: response.status };
883
- }
884
-
885
- res.writeHead(200, {
886
- 'Content-Type': 'text/event-stream',
887
- 'Cache-Control': 'no-cache',
888
- 'Connection': 'keep-alive',
889
- 'Access-Control-Allow-Origin': '*'
890
- });
891
-
892
- const parser = new GeminiRealtimeStreamParser(response, async (geminiData) => {
893
- const convertedChunk = ResponseConverter.convertStreamChunk(geminiData, requestId, params.model);
894
- if (convertedChunk) {
895
- res.write(convertedChunk);
896
- }
897
- });
898
-
899
- await parser.start();
900
- res.write('data: [DONE]\n\n');
901
- res.end();
902
-
903
- return { success: true };
904
-
905
- } catch (error) {
906
- console.error('执行流式请求错误:', error);
907
- this.config.moveToUsed(apiKey);
908
-
909
- if (retryCount < maxRetries) {
910
- return await this.handleStreamRequest(requestBody, params, requestId, res, retryCount + 1);
911
- }
912
-
913
- return { success: false, error: '网络请求失败: ' + error.message, status: 500 };
914
- }
915
- }
916
-
917
- /**
918
- * 处理非流式请求
919
- */
920
- async executeNormalRequest(requestBody, params, requestId, retryCount = 0) {
921
- const maxRetries = 3;
922
-
923
- const apiKey = this.config.getApiKey();
924
- if (!apiKey) {
925
- return { success: false, error: '目前暂无可用的API Key', status: 503 };
926
- }
927
-
928
- try {
929
- const apiUrl = this.requestBuilder.buildApiUrl(params.model, apiKey, false);
930
-
931
- const response = await fetch(apiUrl, {
932
- method: 'POST',
933
- headers: { 'Content-Type': 'application/json' },
934
- body: JSON.stringify(requestBody)
935
- });
936
-
937
- if (response.status === 403) {
938
- this.config.markKeyAsInvalid(apiKey);
939
- if (retryCount < maxRetries) {
940
- return await this.executeNormalRequest(requestBody, params, requestId, retryCount + 1);
941
- }
942
- return { success: false, error: 'API Key 无效', status: 403 };
943
- }
944
-
945
- if (response.status === 429) {
946
- this.config.moveToUsed(apiKey);
947
- if (retryCount < maxRetries) {
948
- return await this.executeNormalRequest(requestBody, params, requestId, retryCount + 1);
949
- }
950
- return { success: false, error: '请求频率过高,请稍后重试', status: 429 };
951
- }
952
-
953
- if (response.status === 500) {
954
- this.config.moveToUsed(apiKey);
955
- return { success: false, error: '目前服务器繁忙,请稍后重试', status: 500 };
956
- }
957
-
958
- if (!response.ok) {
959
- const errorText = await response.text();
960
- console.error(`API请求失败: ${response.status}, 错误信息: ${errorText}`);
961
- return { success: false, error: `API请求失败: ${response.status}`, status: response.status };
962
- }
963
-
964
- const geminiResponse = await response.json();
965
- const openaiResponse = ResponseConverter.convertNormalResponse(geminiResponse, requestId, params.model);
966
- return { success: true, data: openaiResponse };
967
-
968
- } catch (error) {
969
- console.error('执行非流式请求错误:', error);
970
- this.config.moveToUsed(apiKey);
971
-
972
- if (retryCount < maxRetries) {
973
- return await this.executeNormalRequest(requestBody, params, requestId, retryCount + 1);
974
- }
975
-
976
- return { success: false, error: '网络请求失败: ' + error.message, status: 500 };
977
- }
978
- }
979
-
980
- /**
981
- * 处理模型列表请求
982
- */
983
- async handleModelsRequest(req, res) {
984
- try {
985
- const result = await this.modelManager.getModels();
986
-
987
- if (result.success) {
988
- res.json(result.data);
989
- } else {
990
- res.status(result.status || 500).json({ error: result.error });
991
- }
992
- } catch (error) {
993
- console.error('处理模型列表请求错误:', error);
994
- res.status(500).json({ error: '内部服务器错误' });
995
- }
996
- }
997
- }
998
-
999
- /**
1000
- * Express 服务器
1001
- */
1002
- class Server {
1003
- constructor() {
1004
- this.app = express();
1005
- this.apiProxy = new ApiProxyService();
1006
- this.setupMiddleware();
1007
- this.setupRoutes();
1008
- }
1009
-
1010
- setupMiddleware() {
1011
- // CORS配置
1012
- this.app.use(cors({
1013
- origin: '*',
1014
- credentials: true,
1015
- optionsSuccessStatus: 200
1016
- }));
1017
-
1018
- // JSON解析 - 增加大小限制以支持图片
1019
- this.app.use(express.json({ limit: '50mb' }));
1020
- this.app.use(express.urlencoded({ limit: '50mb', extended: true }));
1021
-
1022
- // 认证中间件
1023
- this.app.use(this.apiProxy.authMiddleware.middleware());
1024
-
1025
- // 请求日志中间件
1026
- this.app.use((req, res, next) => {
1027
- const start = Date.now();
1028
- res.on('finish', () => {
1029
- const duration = Date.now() - start;
1030
- console.log(`${req.method} ${req.path} - ${res.statusCode} [${duration}ms]`);
1031
- });
1032
- next();
1033
- });
1034
- }
1035
-
1036
- setupRoutes() {
1037
- // 聊天接口(支持图片)
1038
- this.app.post('/v1/chat/completions', (req, res) => {
1039
- this.apiProxy.handleChatRequest(req, res);
1040
- });
1041
-
1042
- // 模型列表接口
1043
- this.app.get('/v1/models', (req, res) => {
1044
- this.apiProxy.handleModelsRequest(req, res);
1045
- });
1046
-
1047
- // 健康检查接口
1048
- this.app.get('/health', (req, res) => {
1049
- res.json({
1050
- status: 'healthy',
1051
- timestamp: new Date().toISOString(),
1052
- availableKeys: this.apiProxy.config.apiKeys.length,
1053
- usedKeys: this.apiProxy.config.usedApiKeys.length,
1054
- invalidKeys: this.apiProxy.config.invalidApiKeys.length,
1055
- version: '2.0.0',
1056
- features: ['text', 'vision', 'stream', 'load_balancing']
1057
- });
1058
- });
1059
-
1060
- // 404处理
1061
- this.app.use('*', (req, res) => {
1062
- res.status(404).json({
1063
- error: {
1064
- message: 'Not Found',
1065
- type: 'invalid_request_error',
1066
- code: 'not_found'
1067
- }
1068
- });
1069
- });
1070
-
1071
- // 全局错误处理
1072
- this.app.use((err, req, res, next) => {
1073
- console.error('服务器错误:', err);
1074
- res.status(500).json({
1075
- error: {
1076
- message: '内部服务器错误',
1077
- type: 'internal_server_error',
1078
- code: 'server_error'
1079
- }
1080
- });
1081
- });
1082
- }
1083
-
1084
- start(port = 3000) {
1085
- this.app.listen(port, () => {
1086
- console.log(`🚀 OpenAI to Gemini Proxy Server (Enhanced) 启动在端口 ${port}`);
1087
- console.log(`📍 聊天API: http://localhost:${port}/v1/chat/completions`);
1088
- console.log(`📋 模型列表: http://localhost:${port}/v1/models`);
1089
- console.log(`🔍 健康检查: http://localhost:${port}/health`);
1090
- });
1091
- }
1092
- }
1093
-
1094
- // 启动服务器
1095
- const server = new Server();
1096
- const port = process.env.PORT || 3000;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1097
  server.start(port);
 
1
+ import express from 'express';
2
+ import fetch from 'node-fetch';
3
+ import dotenv from 'dotenv';
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import cors from 'cors';
6
+
7
+ // 初始化环境变量
8
+ dotenv.config();
9
+
10
+ /**
11
+ * 配置管理类
12
+ */
13
+ class Config {
14
+ constructor() {
15
+ this.initializeApiKeys();
16
+ this.initializeAuth();
17
+
18
+ // API Key池 (可用于新请求)
19
+ this.apiKeys = []; // This will be filled by initializeApiKeys
20
+
21
+ // 已使用的API Key池 (成功请求后放入,用于循环使用)
22
+ this.usedApiKeys = [];
23
+
24
+ // 失效的API Key池 (永久失效,不再使用)
25
+ this.invalidApiKeys = [];
26
+
27
+ // Gemini安全设置
28
+ this.geminiSafety = [
29
+ {
30
+ category: 'HARM_CATEGORY_HARASSMENT',
31
+ threshold: 'OFF',
32
+ },
33
+ {
34
+ category: 'HARM_CATEGORY_HATE_SPEECH',
35
+ threshold: 'OFF',
36
+ },
37
+ {
38
+ category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
39
+ threshold: 'OFF',
40
+ },
41
+ {
42
+ category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
43
+ threshold: 'OFF',
44
+ },
45
+ {
46
+ category: 'HARM_CATEGORY_CIVIC_INTEGRITY',
47
+ threshold: 'OFF',
48
+ },
49
+ ];
50
+ }
51
+
52
+ /**
53
+ * 初始化API Keys
54
+ */
55
+ initializeApiKeys() {
56
+ const apiKeysEnv = process.env.GEMINI_API_KEYS;
57
+ if (!apiKeysEnv) {
58
+ console.error('❌ 错误: 未找到 GEMINI_API_KEYS 环境变量');
59
+ process.exit(1);
60
+ }
61
+
62
+ // 通过换行符分割API Keys
63
+ this.apiKeys = apiKeysEnv
64
+ .split('\n')
65
+ .map(key => key.trim())
66
+ .filter(key => key.length > 0);
67
+
68
+ if (this.apiKeys.length === 0) {
69
+ console.error('❌ 错误: 没有找到有效的API Keys');
70
+ process.exit(1);
71
+ }
72
+
73
+ console.log(`✅ 成功加载 ${this.apiKeys.length} 个API Keys`);
74
+ }
75
+
76
+ /**
77
+ * 初始化认证配置
78
+ */
79
+ initializeAuth() {
80
+ this.authToken = process.env.AUTH_TOKEN || 'sk-123456';
81
+ }
82
+
83
+ /**
84
+ * 获取可用的API Key(负载均衡)
85
+ * 只负责从主池获取一个key,不负责移动到used池
86
+ */
87
+ getApiKeyForRequest() {
88
+ if (this.apiKeys.length === 0) {
89
+ if (this.usedApiKeys.length > 0) {
90
+ // 如果主池为空,则从已使用池回收key
91
+ this.apiKeys.push(...this.usedApiKeys);
92
+ this.usedApiKeys = [];
93
+ console.log('🔄 回收已使用的API Keys到主池。');
94
+ } else {
95
+ return null; // 没有可用的key
96
+ }
97
+ }
98
+ return this.apiKeys.shift(); // 从主池取出第一个key
99
+ }
100
+
101
+ /**
102
+ * 获取第一个可用的API Key(用于模型列表请求等非核心请求)
103
+ */
104
+ getFirstAvailableApiKey() {
105
+ if (this.apiKeys.length > 0) {
106
+ return this.apiKeys[0];
107
+ }
108
+ if (this.usedApiKeys.length > 0) {
109
+ return this.usedApiKeys[0];
110
+ }
111
+ return null;
112
+ }
113
+
114
+ /**
115
+ * 将API Key标记为失效
116
+ */
117
+ markKeyAsInvalid(apiKey) {
118
+ // 从所有池中移除
119
+ this.apiKeys = this.apiKeys.filter(key => key !== apiKey);
120
+ this.usedApiKeys = this.usedApiKeys.filter(key => key !== apiKey);
121
+
122
+ // 添加到失效池 (如果不在里面)
123
+ if (apiKey && !this.invalidApiKeys.includes(apiKey)) {
124
+ this.invalidApiKeys.push(apiKey);
125
+ }
126
+
127
+ console.warn(`⚠️ API Key 已标记为失效: ${apiKey ? apiKey.substring(0, 10) : 'N/A'}...`);
128
+ console.warn(`当前可用API Keys: ${this.apiKeys.length}, 已使用: ${this.usedApiKeys.length}, 失效: ${this.invalidApiKeys.length}`);
129
+ }
130
+
131
+ /**
132
+ * 将API Key返回到已使用池 (表示成功使用过,可以循环)
133
+ */
134
+ returnApiKeyToUsed(apiKey) {
135
+ if (apiKey && !this.usedApiKeys.includes(apiKey) && !this.invalidApiKeys.includes(apiKey)) {
136
+ this.usedApiKeys.push(apiKey);
137
+ }
138
+ }
139
+
140
+ /**
141
+ * 将API Key返回到主池 (例如,遇到暂时性错误如429,放回队列末尾)
142
+ */
143
+ returnApiKeyToMain(apiKey) {
144
+ if (apiKey && !this.apiKeys.includes(apiKey) && !this.invalidApiKeys.includes(apiKey)) {
145
+ this.apiKeys.push(apiKey);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * 验证授权头
151
+ */
152
+ validateAuth(authHeader) {
153
+ if (!authHeader) {
154
+ return false;
155
+ }
156
+
157
+ const token = authHeader.replace('Bearer ', '');
158
+ return token === this.authToken;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * 图片处理器类
164
+ */
165
+ class ImageProcessor {
166
+ /**
167
+ * 从data URL中提取MIME类型和base64数据
168
+ */
169
+ static parseDataUrl(dataUrl) {
170
+ try {
171
+ // 匹配data:image/jpeg;base64,<base64data>格式
172
+ const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
173
+ if (!match) {
174
+ throw new Error('无效的data URL格式');
175
+ }
176
+
177
+ const mimeType = match[1];
178
+ const base64Data = match[2];
179
+
180
+ // 验证MIME类型是否为支持的图片格式
181
+ const supportedMimeTypes = [
182
+ 'image/jpeg',
183
+ 'image/jpg',
184
+ 'image/png',
185
+ 'image/gif',
186
+ 'image/webp',
187
+ 'image/bmp',
188
+ 'image/tiff'
189
+ ];
190
+
191
+ if (!supportedMimeTypes.includes(mimeType.toLowerCase())) {
192
+ throw new Error(`不支持的图片格式: ${mimeType}`);
193
+ }
194
+
195
+ return {
196
+ mimeType,
197
+ data: base64Data
198
+ };
199
+ } catch (error) {
200
+ console.error('解析图片data URL错误:', error);
201
+ throw error;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * 验证base64数据是否有效
207
+ */
208
+ static validateBase64(base64String) {
209
+ try {
210
+ // 基本格式检查
211
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64String)) {
212
+ return false;
213
+ }
214
+
215
+ // 长度检查(base64编码长度应该是4的倍数)
216
+ return base64String.length % 4 === 0;
217
+ } catch (error) {
218
+ return false;
219
+ }
220
+ }
221
+
222
+ /**
223
+ * 从URL下载图片并转换为base64
224
+ */
225
+ static async fetchImageAsBase64(imageUrl) {
226
+ try {
227
+ const response = await fetch(imageUrl, {
228
+ timeout: 30000, // 30秒超时
229
+ headers: {
230
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
231
+ }
232
+ });
233
+
234
+ if (!response.ok) {
235
+ throw new Error(`获取图片失败: HTTP ${response.status}`);
236
+ }
237
+
238
+ const contentType = response.headers.get('content-type');
239
+ if (!contentType || !contentType.startsWith('image/')) {
240
+ throw new Error(`URL返回的不是图片类型: ${contentType}`);
241
+ }
242
+
243
+ const buffer = await response.buffer();
244
+ const base64Data = buffer.toString('base64');
245
+
246
+ return {
247
+ mimeType: contentType,
248
+ data: base64Data
249
+ };
250
+ } catch (error) {
251
+ console.error('下载图片错误:', error);
252
+ throw error;
253
+ }
254
+ }
255
+ }
256
+
257
+ /**
258
+ * 消息转换器类(增强版)
259
+ */
260
+ class MessageConverter {
261
+ /**
262
+ * 将OpenAI格式的消息转换为Gemini格式(支持图片)
263
+ */
264
+ static async convertMessages(openaiMessages) {
265
+ const geminiMessages = [];
266
+ let currentRole = null;
267
+ let currentParts = [];
268
+
269
+ for (const message of openaiMessages) {
270
+ let role = message.role;
271
+ let content = message.content;
272
+
273
+ // 角色转换
274
+ if (role === 'system') {
275
+ // Gemini doesn't have a direct 'system' role. It's usually integrated into the first 'user' turn.
276
+ // For simplicity, we convert 'system' to 'user'.
277
+ role = 'user';
278
+ }
279
+
280
+ if (role === 'assistant') {
281
+ role = 'model';
282
+ }
283
+
284
+ // 处理内容
285
+ let parts = [];
286
+
287
+ if (typeof content === 'string') {
288
+ // 简单文本消息
289
+ parts = [{ text: content }];
290
+ } else if (Array.isArray(content)) {
291
+ // 多模态消息(包含文本和图片)
292
+ parts = await this.convertContentArray(content);
293
+ } else {
294
+ // 其他格式,转为文本
295
+ console.warn(`未知的内容格式: ${typeof content},将转为文本处理`);
296
+ parts = [{ text: String(content) }];
297
+ }
298
+
299
+ // 合并相同角色的连续消息以符合Gemini的单轮对话结构
300
+ // Gemini API expects alternating user/model turns (e.g., user, model, user, model).
301
+ // If OpenAI messages have consecutive 'user' or 'assistant' roles,
302
+ // we should merge them into a single Gemini turn.
303
+ if (role === currentRole) {
304
+ currentParts.push(...parts);
305
+ } else {
306
+ // 保存上一个角色的消息
307
+ if (currentRole !== null && currentParts.length > 0) {
308
+ geminiMessages.push({
309
+ role: currentRole,
310
+ parts: currentParts
311
+ });
312
+ }
313
+
314
+ // 开始新角色
315
+ currentRole = role;
316
+ currentParts = [...parts];
317
+ }
318
+ }
319
+
320
+ // 添加最后一个消息
321
+ if (currentRole !== null && currentParts.length > 0) {
322
+ geminiMessages.push({
323
+ role: currentRole,
324
+ parts: currentParts
325
+ });
326
+ }
327
+
328
+ return geminiMessages;
329
+ }
330
+
331
+ /**
332
+ * 转换OpenAI的content数组为Gemini的parts格式
333
+ */
334
+ static async convertContentArray(contentArray) {
335
+ const parts = [];
336
+
337
+ for (const item of contentArray) {
338
+ try {
339
+ if (item.type === 'text') {
340
+ // 文本内容
341
+ parts.push({ text: item.text || '' });
342
+ } else if (item.type === 'image_url') {
343
+ // 图片内容
344
+ const imagePart = await this.convertImageContent(item);
345
+ if (imagePart) {
346
+ parts.push(imagePart);
347
+ }
348
+ } else {
349
+ // 其他类型,尝试转为文本
350
+ console.warn(`未知的内容类型: ${item.type},将转为文本处理`);
351
+ parts.push({ text: JSON.stringify(item) });
352
+ }
353
+ } catch (error) {
354
+ console.error('转换内容项错误:', error);
355
+ // 出错时返回错误信息文本,而不是抛出异常,避免整个请求失败
356
+ parts.push({ text: `[内容项处理失败: ${error.message}]` });
357
+ }
358
+ }
359
+
360
+ return parts;
361
+ }
362
+
363
+ /**
364
+ * 转换图片内容为Gemini格式
365
+ */
366
+ static async convertImageContent(imageItem) {
367
+ try {
368
+ const imageUrl = imageItem.image_url?.url;
369
+ if (!imageUrl) {
370
+ throw new Error('缺少图片URL');
371
+ }
372
+
373
+ let imageData;
374
+
375
+ if (imageUrl.startsWith('data:')) {
376
+ // 处理base64数据URL
377
+ imageData = ImageProcessor.parseDataUrl(imageUrl);
378
+
379
+ // 验证base64数据
380
+ if (!ImageProcessor.validateBase64(imageData.data)) {
381
+ throw new Error('无效的base64图片数据');
382
+ }
383
+ } else if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
384
+ // 处理网络图片URL
385
+ imageData = await ImageProcessor.fetchImageAsBase64(imageUrl);
386
+ } else {
387
+ throw new Error(`不支持的图片URL格式: ${imageUrl}`);
388
+ }
389
+
390
+ // 返回Gemini格式的图片数据
391
+ return {
392
+ inlineData: {
393
+ mimeType: imageData.mimeType,
394
+ data: imageData.data
395
+ }
396
+ };
397
+ } catch (error) {
398
+ console.error('转换图片内容错误:', error);
399
+ // 返回错误信息文本,而不是抛出异常
400
+ return { text: `[图片处理失败: ${error.message}]` };
401
+ }
402
+ }
403
+
404
+ /**
405
+ * 从OpenAI请求中提取参数
406
+ */
407
+ static extractParams(openaiRequest) {
408
+ return {
409
+ model: openaiRequest.model || 'gemini-1.5-flash',
410
+ messages: openaiRequest.messages || [],
411
+ stream: openaiRequest.stream || false,
412
+ temperature: openaiRequest.temperature,
413
+ maxTokens: openaiRequest.max_tokens,
414
+ topP: openaiRequest.top_p
415
+ };
416
+ }
417
+ }
418
+
419
+ /**
420
+ * 模型管理类
421
+ */
422
+ class ModelManager {
423
+ constructor(config) {
424
+ this.config = config;
425
+ this.cachedModels = null;
426
+ this.cacheExpiry = null;
427
+ this.cacheTimeout = 5 * 60 * 1000; // 5分钟缓存
428
+ }
429
+
430
+ /**
431
+ * 获取模型列表
432
+ */
433
+ async getModels() {
434
+ if (this.cachedModels && this.cacheExpiry && Date.now() < this.cacheExpiry) {
435
+ return { success: true, data: this.cachedModels };
436
+ }
437
+
438
+ const apiKey = this.config.getFirstAvailableApiKey();
439
+ if (!apiKey) {
440
+ return {
441
+ success: false,
442
+ error: '没有可用的API Key',
443
+ status: 503
444
+ };
445
+ }
446
+
447
+ try {
448
+ const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`, {
449
+ method: 'GET',
450
+ headers: {
451
+ 'Content-Type': 'application/json'
452
+ }
453
+ });
454
+
455
+ if (!response.ok) {
456
+ // 如果是 403 错误,标记 key 失效
457
+ if (response.status === 403) {
458
+ this.config.markKeyAsInvalid(apiKey);
459
+ }
460
+ return {
461
+ success: false,
462
+ error: `获取模型列表失败: ${response.status}`,
463
+ status: response.status
464
+ };
465
+ }
466
+
467
+ const geminiResponse = await response.json();
468
+ const filteredModels = this.filterModels(geminiResponse.models || []);
469
+
470
+ this.cachedModels = filteredModels;
471
+ this.cacheExpiry = Date.now() + this.cacheTimeout;
472
+
473
+ return { success: true, data: filteredModels };
474
+
475
+ } catch (error) {
476
+ console.error('获取模型列表错误:', error);
477
+ return {
478
+ success: false,
479
+ error: '网络请求失败',
480
+ status: 500
481
+ };
482
+ }
483
+ }
484
+
485
+ /**
486
+ * 过滤模型 - 保持原始Gemini模型名,并增加视觉模型支持
487
+ */
488
+ filterModels(models) {
489
+ const allowedPrefixes = [
490
+ 'models/gemini-2.5-flash',
491
+ 'models/gemini-2.0-flash',
492
+ 'models/gemini-1.5-flash',
493
+ 'models/gemini-1.5-pro' // 允许 Gemini 1.5 Pro 模型
494
+ ];
495
+
496
+ const excludedModels = [
497
+ 'models/gemini-1.5-flash-8b' // 排除特定的较小模型
498
+ ];
499
+
500
+ const filteredModels = models.filter(model => {
501
+ const modelName = model.name;
502
+
503
+ // 排除特定模型
504
+ if (excludedModels.some(excluded => modelName.startsWith(excluded))) {
505
+ return false;
506
+ }
507
+
508
+ // 允许特定的完整模型名 (例如, "models/gemini-2.5-pro" 不以任何前缀开始)
509
+ if (modelName === "models/gemini-2.5-pro" || modelName === "models/gemini-1.5-pro") {
510
+ return true;
511
+ }
512
+
513
+ // 允许以特定前缀开头的模型
514
+ return allowedPrefixes.some(prefix => modelName.startsWith(prefix));
515
+ });
516
+
517
+ // 转换为OpenAI格式但保持Gemini模型名
518
+ const processedModels = filteredModels.map(model => {
519
+ const modelId = model.name.replace('models/', '');
520
+
521
+ return {
522
+ id: modelId, // 直接使用Gemini模型名
523
+ object: 'model',
524
+ created: Math.floor(Date.now() / 1000),
525
+ owned_by: 'google',
526
+ permission: [
527
+ {
528
+ id: `modelperm-${modelId}`,
529
+ object: 'model_permission',
530
+ created: Math.floor(Date.now() / 1000),
531
+ allow_create_engine: false,
532
+ allow_sampling: true,
533
+ allow_logprobs: false,
534
+ allow_search_indices: false,
535
+ allow_view: true,
536
+ allow_fine_tuning: false,
537
+ organization: '*',
538
+ group: null,
539
+ is_blocking: false
540
+ }
541
+ ],
542
+ root: modelId,
543
+ parent: null
544
+ };
545
+ });
546
+
547
+ return {
548
+ object: 'list',
549
+ data: processedModels
550
+ };
551
+ }
552
+ }
553
+
554
+ /**
555
+ * Gemini API 请求构建器类
556
+ */
557
+ class GeminiRequestBuilder {
558
+ constructor(config) {
559
+ this.config = config;
560
+ }
561
+
562
+ /**
563
+ * 构建Gemini API请求体
564
+ */
565
+ buildRequestBody(geminiMessages, params) {
566
+ const requestBody = {
567
+ contents: geminiMessages,
568
+ safetySettings: this.config.geminiSafety,
569
+ generationConfig: {}
570
+ };
571
+
572
+ if (params.temperature !== undefined) {
573
+ requestBody.generationConfig.temperature = params.temperature;
574
+ }
575
+
576
+ if (params.maxTokens !== undefined) {
577
+ requestBody.generationConfig.maxOutputTokens = params.maxTokens;
578
+ }
579
+
580
+ if (params.topP !== undefined) {
581
+ requestBody.generationConfig.topP = params.topP;
582
+ }
583
+
584
+ return requestBody;
585
+ }
586
+
587
+ /**
588
+ * 构建Gemini API URL
589
+ */
590
+ buildApiUrl(model, apiKey, isStream = false) {
591
+ const method = isStream ? 'streamGenerateContent' : 'generateContent';
592
+ return `https://generativelanguage.googleapis.com/v1beta/models/${model}:${method}?key=${apiKey}`;
593
+ }
594
+ }
595
+
596
+ /**
597
+ * 响应转换器类
598
+ */
599
+ class ResponseConverter {
600
+ /**
601
+ * 将Gemini流式响应块转换为OpenAI格式
602
+ */
603
+ static convertStreamChunk(geminiData, requestId, model) {
604
+ try {
605
+ if (geminiData.candidates && geminiData.candidates[0]) {
606
+ const candidate = geminiData.candidates[0];
607
+ if (candidate.content && candidate.content.parts) {
608
+ const text = candidate.content.parts[0]?.text || '';
609
+ const openaiChunk = {
610
+ id: requestId,
611
+ object: 'chat.completion.chunk',
612
+ created: Math.floor(Date.now() / 1000),
613
+ model: model, // 使用当前请求的模型名
614
+ choices: [{
615
+ index: 0,
616
+ delta: { content: text },
617
+ finish_reason: candidate.finishReason === 'STOP' ? 'stop' : null
618
+ }]
619
+ };
620
+ return `data: ${JSON.stringify(openaiChunk)}\n\n`;
621
+ }
622
+ }
623
+ // 处理安全设置阻断等情况
624
+ if (geminiData.promptFeedback?.blockReason || geminiData.candidates?.[0]?.finishReason === 'SAFETY') {
625
+ console.warn('Gemini stream blocked:', geminiData.promptFeedback?.blockReason || 'SAFETY');
626
+ const openaiChunk = {
627
+ id: requestId,
628
+ object: 'chat.completion.chunk',
629
+ created: Math.floor(Date.now() / 1000),
630
+ model: model,
631
+ choices: [{
632
+ index: 0,
633
+ delta: { content: `[Content blocked due to: ${geminiData.promptFeedback?.blockReason || 'SAFETY'}]` },
634
+ finish_reason: 'content_filter'
635
+ }]
636
+ };
637
+ return `data: ${JSON.stringify(openaiChunk)}\n\n`;
638
+ }
639
+
640
+ return '';
641
+ } catch (error) {
642
+ console.error('转换流响应块错误:', error);
643
+ return '';
644
+ }
645
+ }
646
+
647
+ /**
648
+ * 将Gemini非流式响应转换为OpenAI格式
649
+ */
650
+ static convertNormalResponse(geminiResponse, requestId, model) {
651
+ const openaiResponse = {
652
+ id: requestId,
653
+ object: 'chat.completion',
654
+ created: Math.floor(Date.now() / 1000),
655
+ model: model, // 使用当前请求的模型名
656
+ choices: [],
657
+ usage: {
658
+ prompt_tokens: 0,
659
+ completion_tokens: 0,
660
+ total_tokens: 0
661
+ }
662
+ };
663
+
664
+ if (geminiResponse.candidates && geminiResponse.candidates[0]) {
665
+ const candidate = geminiResponse.candidates[0];
666
+ if (candidate.content && candidate.content.parts) {
667
+ const text = candidate.content.parts.map(part => part.text).join('');
668
+ openaiResponse.choices.push({
669
+ index: 0,
670
+ message: {
671
+ role: 'assistant',
672
+ content: text
673
+ },
674
+ finish_reason: candidate.finishReason === 'STOP' ? 'stop' : 'length' // Gemini 'STOP' is stop, others often length
675
+ });
676
+ } else if (candidate.finishReason === 'SAFETY') {
677
+ // 如果有内容但被安全机制阻断
678
+ openaiResponse.choices.push({
679
+ index: 0,
680
+ message: {
681
+ role: 'assistant',
682
+ content: `Content generation was blocked due to safety policy.`
683
+ },
684
+ finish_reason: 'content_filter'
685
+ });
686
+ }
687
+ } else if (geminiResponse.promptFeedback && geminiResponse.promptFeedback.blockReason) {
688
+ // Handle content moderation blocks (even before candidate generation)
689
+ console.warn('Gemini response blocked:', geminiResponse.promptFeedback.blockReason);
690
+ openaiResponse.choices.push({
691
+ index: 0,
692
+ message: {
693
+ role: 'assistant',
694
+ content: `Content generation was blocked due to: ${geminiResponse.promptFeedback.blockReason}.`
695
+ },
696
+ finish_reason: 'content_filter'
697
+ });
698
+ }
699
+
700
+ // 尝试从usage信息中获取token使用量
701
+ if (geminiResponse.usageMetadata) {
702
+ openaiResponse.usage = {
703
+ prompt_tokens: geminiResponse.usageMetadata.promptTokenCount || 0,
704
+ completion_tokens: geminiResponse.usageMetadata.candidatesTokenCount || 0,
705
+ total_tokens: geminiResponse.usageMetadata.totalTokenCount || 0
706
+ };
707
+ }
708
+
709
+ return openaiResponse;
710
+ }
711
+
712
+ /**
713
+ * 将文本内容分块为假流式响应的生成器
714
+ */
715
+ static * chunkTextForFakeStream(text, requestId, model, chunkSize = 50) {
716
+ if (!text) {
717
+ yield `data: ${JSON.stringify({
718
+ id: requestId,
719
+ object: 'chat.completion.chunk',
720
+ created: Math.floor(Date.now() / 1000),
721
+ model: model,
722
+ choices: [{ index: 0, delta: { content: '' }, finish_reason: 'stop' }]
723
+ })}\n\n`;
724
+ return;
725
+ }
726
+
727
+ let offset = 0;
728
+ while (offset < text.length) {
729
+ const chunk = text.substring(offset, Math.min(offset + chunkSize, text.length));
730
+ offset += chunkSize;
731
+
732
+ const openaiChunk = {
733
+ id: requestId,
734
+ object: 'chat.completion.chunk',
735
+ created: Math.floor(Date.now() / 1000),
736
+ model: model,
737
+ choices: [{
738
+ index: 0,
739
+ delta: { content: chunk },
740
+ finish_reason: null // Not yet finished
741
+ }]
742
+ };
743
+ yield `data: ${JSON.stringify(openaiChunk)}\n\n`;
744
+ }
745
+
746
+ // Final chunk with finish_reason
747
+ const finalChunk = {
748
+ id: requestId,
749
+ object: 'chat.completion.chunk',
750
+ created: Math.floor(Date.now() / 1000),
751
+ model: model,
752
+ choices: [{
753
+ index: 0,
754
+ delta: {}, // Delta for final chunk might be empty
755
+ finish_reason: 'stop' // Indicate end of stream
756
+ }]
757
+ };
758
+ yield `data: ${JSON.stringify(finalChunk)}\n\n`;
759
+ }
760
+ }
761
+
762
+ /**
763
+ * Gemini实时流式响应解析器
764
+ */
765
+ class GeminiRealtimeStreamParser {
766
+ constructor(response, onChunk) {
767
+ this.response = response;
768
+ this.onChunk = onChunk;
769
+ this.buffer = '';
770
+ this.bufferLv = 0; // Tracks JSON object nesting level
771
+ this.inString = false;
772
+ this.escapeNext = false;
773
+ this.decoder = new TextDecoder();
774
+ }
775
+
776
+ async start() {
777
+ try {
778
+ const reader = this.response.body.getReader();
779
+ while (true) {
780
+ const { done, value } = await reader.read();
781
+ if (done) break;
782
+
783
+ const text = this.decoder.decode(value, { stream: true });
784
+ await this.processText(text);
785
+ }
786
+
787
+ // Handle any remaining buffer after stream ends
788
+ await this.handleRemainingBuffer();
789
+ } catch (error) {
790
+ console.error('流式解析错误:', error);
791
+ throw error;
792
+ }
793
+ }
794
+
795
+ async processText(text) {
796
+ for (const char of text) {
797
+ // Handle escaped characters
798
+ if (this.escapeNext) {
799
+ this.buffer += char;
800
+ this.escapeNext = false;
801
+ continue;
802
+ }
803
+
804
+ // Toggle inString state
805
+ if (char === '"') {
806
+ this.inString = !this.inString;
807
+ } else if (char === '\\' && this.inString) {
808
+ // Handle escape character itself within a string
809
+ this.escapeNext = true;
810
+ this.buffer += char; // Add the backslash to buffer
811
+ continue;
812
+ }
813
+
814
+ // Track JSON nesting level outside of strings
815
+ if (!this.inString) {
816
+ if (char === '{' || char === '[') {
817
+ this.bufferLv++;
818
+ } else if (char === '}' || char === ']') {
819
+ this.bufferLv--;
820
+ }
821
+ }
822
+
823
+ this.buffer += char; // Always add char to buffer
824
+
825
+ // If we are at the top level of a complete JSON object (bufferLv === 0 for a root object)
826
+ // For Gemini's stream format, each object typically ends with } and is a standalone chunk
827
+ if (!this.inString && this.bufferLv === 0 && char === '}' && this.buffer.trim() !== '') {
828
+ try {
829
+ const bufferJson = JSON.parse(this.buffer);
830
+ await this.onChunk(bufferJson);
831
+ } catch (parseError) {
832
+ console.error('解析Gemini流数据错误:', parseError);
833
+ // It's possible to get partial JSON or errors, log and clear buffer
834
+ }
835
+ this.buffer = ''; // Reset buffer after processing a complete object
836
+ }
837
+ }
838
+ }
839
+
840
+ async handleRemainingBuffer() {
841
+ if (this.buffer.trim() !== '') {
842
+ try {
843
+ // Attempt to parse any remaining content as a full JSON object.
844
+ const bufferJson = JSON.parse(this.buffer);
845
+ await this.onChunk(bufferJson);
846
+ } catch (parseError) {
847
+ console.error('解析最后的缓冲区数据错误:', parseError);
848
+ }
849
+ }
850
+ }
851
+ }
852
+
853
+ /**
854
+ * 认证中间件
855
+ */
856
+ class AuthMiddleware {
857
+ constructor(config) {
858
+ this.config = config;
859
+ }
860
+
861
+ middleware() {
862
+ return (req, res, next) => {
863
+ // 跳过健康检查和预检请求
864
+ if (req.path === '/health' || req.method === 'OPTIONS') {
865
+ return next();
866
+ }
867
+
868
+ const authHeader = req.headers.authorization;
869
+
870
+ if (!this.config.validateAuth(authHeader)) {
871
+ return res.status(401).json({
872
+ error: {
873
+ message: 'Invalid authentication credentials',
874
+ type: 'invalid_request_error',
875
+ code: 'invalid_api_key'
876
+ }
877
+ });
878
+ }
879
+
880
+ next();
881
+ };
882
+ }
883
+ }
884
+
885
+ /**
886
+ * API代理服务类
887
+ */
888
+ class ApiProxyService {
889
+ constructor() {
890
+ this.config = new Config();
891
+ this.requestBuilder = new GeminiRequestBuilder(this.config);
892
+ this.modelManager = new ModelManager(this.config);
893
+ this.authMiddleware = new AuthMiddleware(this.config);
894
+ }
895
+
896
+ /**
897
+ * 处理聊天API请求(支持图片)- 真实流式
898
+ */
899
+ async handleChatRequest(req, res) {
900
+ try {
901
+ const requestId = `chatcmpl-${uuidv4()}`;
902
+
903
+ const params = MessageConverter.extractParams(req.body);
904
+
905
+ // 异步转换消息(支持图片处理)
906
+ const geminiMessages = await MessageConverter.convertMessages(params.messages);
907
+
908
+ if (!geminiMessages || geminiMessages.length === 0) {
909
+ return res.status(400).json({
910
+ error: {
911
+ message: '无效的消息格式或消息为空',
912
+ type: 'invalid_request_error',
913
+ code: 'invalid_messages'
914
+ }
915
+ });
916
+ }
917
+
918
+ const requestBody = this.requestBuilder.buildRequestBody(geminiMessages, params);
919
+
920
+ if (params.stream) {
921
+ const result = await this.executeRealStreamRequest(requestBody, params, requestId, res);
922
+ if (!result.success) {
923
+ res.status(result.status || 500).json({ error: result.error });
924
+ }
925
+ } else {
926
+ const result = await this.executeNormalRequest(requestBody, params, requestId);
927
+ if (result.success) {
928
+ res.json(result.data);
929
+ } else {
930
+ res.status(result.status || 500).json({ error: result.error });
931
+ }
932
+ }
933
+ } catch (error) {
934
+ console.error('处理聊天请求错误:', error);
935
+ res.status(500).json({
936
+ error: {
937
+ message: '内部服务器错误: ' + error.message,
938
+ type: 'internal_server_error',
939
+ code: 'server_error'
940
+ }
941
+ });
942
+ }
943
+ }
944
+
945
+ /**
946
+ * 新增处理假流式聊天API请求
947
+ * 如果客户端请求流式,则内部调用非流式 Gemini API,然后转为假流发送给客户端。
948
+ * 如果客户端请求非流式,则直接代理非流式请求。
949
+ */
950
+ async handleFakeStreamChatRequest(req, res) {
951
+ try {
952
+ const requestId = `chatcmpl-${uuidv4()}`;
953
+
954
+ const params = MessageConverter.extractParams(req.body);
955
+
956
+ const geminiMessages = await MessageConverter.convertMessages(params.messages);
957
+
958
+ if (!geminiMessages || geminiMessages.length === 0) {
959
+ return res.status(400).json({
960
+ error: {
961
+ message: '无效的消息格式或消息为空',
962
+ type: 'invalid_request_error',
963
+ code: 'invalid_messages'
964
+ }
965
+ });
966
+ }
967
+
968
+ const requestBody = this.requestBuilder.buildRequestBody(geminiMessages, params);
969
+
970
+ if (params.stream) {
971
+ // 客户端请求流式,但我们内部执行非流式 Gemini 请求,然后假流返回
972
+ const result = await this.executeFakeStreamResponse(requestBody, params, requestId, res);
973
+ if (!result.success) {
974
+ res.status(result.status || 500).json({ error: result.error });
975
+ }
976
+ } else {
977
+ // 客户端请求非流式,直接执行非流式请求
978
+ const result = await this.executeNormalRequest(requestBody, params, requestId);
979
+ if (result.success) {
980
+ res.json(result.data);
981
+ } else {
982
+ res.status(result.status || 500).json({ error: result.error });
983
+ }
984
+ }
985
+ } catch (error) {
986
+ console.error('处理假流式聊天请求错误:', error);
987
+ res.status(500).json({
988
+ error: {
989
+ message: '内部服务器错误: ' + error.message,
990
+ type: 'internal_server_error',
991
+ code: 'server_error'
992
+ }
993
+ });
994
+ }
995
+ }
996
+
997
+ /**
998
+ * 执行真实的流式请求
999
+ */
1000
+ async executeRealStreamRequest(requestBody, params, requestId, res, retryCount = 0) {
1001
+ const maxRetries = 3;
1002
+
1003
+ let apiKey = this.config.getApiKeyForRequest();
1004
+ if (!apiKey) {
1005
+ return { success: false, error: '目前暂无可用的API Key', status: 503 };
1006
+ }
1007
+
1008
+ try {
1009
+ const apiUrl = this.requestBuilder.buildApiUrl(params.model, apiKey, true);
1010
+
1011
+ const response = await fetch(apiUrl, {
1012
+ method: 'POST',
1013
+ headers: { 'Content-Type': 'application/json' },
1014
+ body: JSON.stringify(requestBody)
1015
+ });
1016
+
1017
+ if (response.status === 403) {
1018
+ this.config.markKeyAsInvalid(apiKey);
1019
+ if (retryCount < maxRetries) {
1020
+ console.log(`真实流式请求重试 ${retryCount + 1}/${maxRetries} (403错误)。`);
1021
+ return await this.executeRealStreamRequest(requestBody, params, requestId, res, retryCount + 1);
1022
+ }
1023
+ return { success: false, error: 'API Key 无效或已失效', status: 403 };
1024
+ }
1025
+
1026
+ if (response.status === 429) {
1027
+ this.config.returnApiKeyToMain(apiKey); // 放回主池末尾,等待冷却
1028
+ if (retryCount < maxRetries) {
1029
+ console.log(`真实流式请求重试 ${retryCount + 1}/${maxRetries} (429错误)。`);
1030
+ return await this.executeRealStreamRequest(requestBody, params, requestId, res, retryCount + 1);
1031
+ }
1032
+ return { success: false, error: '请求频率过高,请稍后重试', status: 429 };
1033
+ }
1034
+
1035
+ if (response.status >= 500 && response.status < 600) { // 5xx errors
1036
+ this.config.returnApiKeyToMain(apiKey); // 服务器错误,放回主池
1037
+ if (retryCount < maxRetries) {
1038
+ console.log(`真实流式请求重试 ${retryCount + 1}/${maxRetries} (${response.status}错误)。`);
1039
+ return await this.executeRealStreamRequest(requestBody, params, requestId, res, retryCount + 1);
1040
+ }
1041
+ return { success: false, error: '目前服务器繁忙,请稍后重试', status: response.status };
1042
+ }
1043
+
1044
+ if (!response.ok) {
1045
+ const errorText = await response.text();
1046
+ console.error(`API请求失败: ${response.status}, 错误信息: ${errorText}`);
1047
+ this.config.returnApiKeyToMain(apiKey); // 其他非 2xx 错误,也放回主池
1048
+ return { success: false, error: `API请求失败: ${response.status} - ${errorText.substring(0, 100)}`, status: response.status };
1049
+ }
1050
+
1051
+ // 如果请求成功,将 key 放入已使用池
1052
+ this.config.returnApiKeyToUsed(apiKey);
1053
+
1054
+ res.writeHead(200, {
1055
+ 'Content-Type': 'text/event-stream',
1056
+ 'Cache-Control': 'no-cache',
1057
+ 'Connection': 'keep-alive',
1058
+ 'Access-Control-Allow-Origin': '*'
1059
+ });
1060
+
1061
+ const parser = new GeminiRealtimeStreamParser(response, async (geminiData) => {
1062
+ const convertedChunk = ResponseConverter.convertStreamChunk(geminiData, requestId, params.model);
1063
+ if (convertedChunk) {
1064
+ res.write(convertedChunk);
1065
+ }
1066
+ });
1067
+
1068
+ await parser.start();
1069
+ res.write('data: [DONE]\n\n');
1070
+ res.end();
1071
+
1072
+ return { success: true };
1073
+
1074
+ } catch (error) {
1075
+ console.error('执行流式请求错误:', error);
1076
+ this.config.returnApiKeyToMain(apiKey); // 网络错误,放回主池
1077
+
1078
+ if (retryCount < maxRetries) {
1079
+ console.log(`真实流式请求重试 ${retryCount + 1}/${maxRetries} (网络错误)。`);
1080
+ return await this.executeRealStreamRequest(requestBody, params, requestId, res, retryCount + 1);
1081
+ }
1082
+
1083
+ return { success: false, error: '网络请求失败: ' + error.message, status: 500 };
1084
+ }
1085
+ }
1086
+
1087
+ /**
1088
+ * 执行非流式请求
1089
+ */
1090
+ async executeNormalRequest(requestBody, params, requestId, retryCount = 0) {
1091
+ const maxRetries = 3;
1092
+
1093
+ let apiKey = this.config.getApiKeyForRequest();
1094
+ if (!apiKey) {
1095
+ return { success: false, error: '目前暂无可用的API Key', status: 503 };
1096
+ }
1097
+
1098
+ try {
1099
+ const apiUrl = this.requestBuilder.buildApiUrl(params.model, apiKey, false);
1100
+
1101
+ const response = await fetch(apiUrl, {
1102
+ method: 'POST',
1103
+ headers: { 'Content-Type': 'application/json' },
1104
+ body: JSON.stringify(requestBody)
1105
+ });
1106
+
1107
+ if (response.status === 403) {
1108
+ this.config.markKeyAsInvalid(apiKey);
1109
+ if (retryCount < maxRetries) {
1110
+ console.log(`非流式请求重试 ${retryCount + 1}/${maxRetries} (403错误)。`);
1111
+ return await this.executeNormalRequest(requestBody, params, requestId, retryCount + 1);
1112
+ }
1113
+ return { success: false, error: 'API Key 无效或已失效', status: 403 };
1114
+ }
1115
+
1116
+ if (response.status === 429) {
1117
+ this.config.returnApiKeyToMain(apiKey);
1118
+ if (retryCount < maxRetries) {
1119
+ console.log(`非流式请求重试 ${retryCount + 1}/${maxRetries} (429错误)。`);
1120
+ return await this.executeNormalRequest(requestBody, params, requestId, retryCount + 1);
1121
+ }
1122
+ return { success: false, error: '请求频率过高,请稍后重试', status: 429 };
1123
+ }
1124
+
1125
+ if (response.status >= 500 && response.status < 600) { // 5xx errors
1126
+ this.config.returnApiKeyToMain(apiKey);
1127
+ if (retryCount < maxRetries) {
1128
+ console.log(`非流式请求重试 ${retryCount + 1}/${maxRetries} (${response.status}错误)。`);
1129
+ return await this.executeNormalRequest(requestBody, params, requestId, retryCount + 1);
1130
+ }
1131
+ return { success: false, error: '目前服务器繁忙,请稍后重试', status: response.status };
1132
+ }
1133
+
1134
+ if (!response.ok) {
1135
+ const errorText = await response.text();
1136
+ console.error(`API请求失败: ${response.status}, 错误信息: ${errorText}`);
1137
+ this.config.returnApiKeyToMain(apiKey);
1138
+ return { success: false, error: `API请求失败: ${response.status} - ${errorText.substring(0, 100)}`, status: response.status };
1139
+ }
1140
+
1141
+ this.config.returnApiKeyToUsed(apiKey);
1142
+ const geminiResponse = await response.json();
1143
+ const openaiResponse = ResponseConverter.convertNormalResponse(geminiResponse, requestId, params.model);
1144
+ return { success: true, data: openaiResponse };
1145
+
1146
+ } catch (error) {
1147
+ console.error('执行非流式请求错误:', error);
1148
+ this.config.returnApiKeyToMain(apiKey);
1149
+
1150
+ if (retryCount < maxRetries) {
1151
+ console.log(`非流式请求重试 ${retryCount + 1}/${maxRetries} (网络错误)。`);
1152
+ return await this.executeNormalRequest(requestBody, params, requestId, retryCount + 1);
1153
+ }
1154
+
1155
+ return { success: false, error: '网络请求失败: ' + error.message, status: 500 };
1156
+ }
1157
+ }
1158
+
1159
+ /**
1160
+ * 执行假流式响应 (内部调用非流式 Gemini API, 然后转换为流式响应)
1161
+ */
1162
+ async executeFakeStreamResponse(requestBody, params, requestId, res) {
1163
+ let pingInterval;
1164
+ const pingDelayMs = 1000; // 每1秒发送一次ping消息
1165
+
1166
+ try {
1167
+ res.writeHead(200, {
1168
+ 'Content-Type': 'text/event-stream',
1169
+ 'Cache-Control': 'no-cache',
1170
+ 'Connection': 'keep-alive',
1171
+ 'Access-Control-Allow-Origin': '*'
1172
+ });
1173
+
1174
+ // 开始发送ping消息
1175
+ pingInterval = setInterval(() => {
1176
+ const pingChunk = {
1177
+ id: requestId,
1178
+ object: 'chat.completion.chunk',
1179
+ created: Math.floor(Date.now() / 1000),
1180
+ model: params.model,
1181
+ choices: [{
1182
+ index: 0,
1183
+ delta: {}, // 空 delta 用于 ping
1184
+ finish_reason: null
1185
+ }]
1186
+ };
1187
+ // console.log('发送 ping 块...');
1188
+ res.write(`data: ${JSON.stringify(pingChunk)}\n\n`);
1189
+ }, pingDelayMs);
1190
+
1191
+ // 执行实际的非流式请求到 Gemini
1192
+ const result = await this.executeNormalRequest(requestBody, params, requestId);
1193
+
1194
+ // 请求完成后立即清除 ping 间隔
1195
+ clearInterval(pingInterval);
1196
+ pingInterval = null;
1197
+
1198
+ if (result.success) {
1199
+ const fullContent = result.data.choices[0]?.message?.content || '';
1200
+
1201
+ // 将完整内容分块并以假流形式发送
1202
+ for (const chunk of ResponseConverter.chunkTextForFakeStream(fullContent, requestId, params.model)) {
1203
+ res.write(chunk);
1204
+ // 可以添加一个小的延迟来模拟更真实的“流式”体验
1205
+ await new Promise(resolve => setTimeout(resolve, 50));
1206
+ }
1207
+ res.write('data: [DONE]\n\n');
1208
+ res.end();
1209
+ return { success: true };
1210
+ } else {
1211
+ // 如果非流式请求失败,发送错误块并结束流
1212
+ res.write(`data: ${JSON.stringify({ error: result.error })}\n\n`);
1213
+ res.write('data: [DONE]\n\n');
1214
+ res.end();
1215
+ return { success: false, error: result.error, status: result.status };
1216
+ }
1217
+
1218
+ } catch (error) {
1219
+ console.error('执行假流式请求错误:', error);
1220
+ if (pingInterval) {
1221
+ clearInterval(pingInterval);
1222
+ }
1223
+ // 如果在响应发送前或发送过程中出现错误
1224
+ res.status(500).json({
1225
+ error: {
1226
+ message: '内部服务器错误: ' + error.message,
1227
+ type: 'internal_server_error',
1228
+ code: 'server_error'
1229
+ }
1230
+ });
1231
+ return { success: false, error: '内部服务器错误: ' + error.message, status: 500 };
1232
+ }
1233
+ }
1234
+
1235
+ /**
1236
+ * 处理模型列表请求
1237
+ */
1238
+ async handleModelsRequest(req, res) {
1239
+ try {
1240
+ const result = await this.modelManager.getModels();
1241
+
1242
+ if (result.success) {
1243
+ res.json(result.data);
1244
+ } else {
1245
+ res.status(result.status || 500).json({ error: result.error });
1246
+ }
1247
+ } catch (error) {
1248
+ console.error('处理模型列表请求错误:', error);
1249
+ res.status(500).json({ error: '内部服务器错误' });
1250
+ }
1251
+ }
1252
+ }
1253
+
1254
+ /**
1255
+ * Express 服务器
1256
+ */
1257
+ class Server {
1258
+ constructor() {
1259
+ this.app = express();
1260
+ this.apiProxy = new ApiProxyService();
1261
+ this.setupMiddleware();
1262
+ this.setupRoutes();
1263
+ }
1264
+
1265
+ setupMiddleware() {
1266
+ // CORS配置
1267
+ this.app.use(cors({
1268
+ origin: '*',
1269
+ credentials: true,
1270
+ optionsSuccessStatus: 200
1271
+ }));
1272
+
1273
+ // JSON解析 - 增加大小限制以支持图片
1274
+ this.app.use(express.json({ limit: '50mb' }));
1275
+ this.app.use(express.urlencoded({ limit: '50mb', extended: true }));
1276
+
1277
+ // 认证中间件
1278
+ this.app.use(this.apiProxy.authMiddleware.middleware());
1279
+
1280
+ // 请求日志中间件
1281
+ this.app.use((req, res, next) => {
1282
+ const start = Date.now();
1283
+ res.on('finish', () => {
1284
+ const duration = Date.now() - start;
1285
+ console.log(`${req.method} ${req.path} - ${res.statusCode} [${duration}ms]`);
1286
+ });
1287
+ next();
1288
+ });
1289
+ }
1290
+
1291
+ setupRoutes() {
1292
+ // 聊天接口(支持图片,真实流式)
1293
+ this.app.post('/v1/chat/completions', (req, res) => {
1294
+ this.apiProxy.handleChatRequest(req, res);
1295
+ });
1296
+
1297
+ // 新增聊天接口(支持图片,假流式)
1298
+ this.app.post('/fakestream/v1/chat/completions', (req, res) => {
1299
+ this.apiProxy.handleFakeStreamChatRequest(req, res);
1300
+ });
1301
+
1302
+ // 模型列表接口
1303
+ this.app.get('/v1/models', (req, res) => {
1304
+ this.apiProxy.handleModelsRequest(req, res);
1305
+ });
1306
+
1307
+ // 健康检查接口
1308
+ this.app.get('/health', (req, res) => {
1309
+ res.json({
1310
+ status: 'healthy',
1311
+ timestamp: new Date().toISOString(),
1312
+ availableKeys: this.apiProxy.config.apiKeys.length, // 可用于新请求的key
1313
+ usedKeys: this.apiProxy.config.usedApiKeys.length, // 成功使用过的key (待下次��环)
1314
+ invalidKeys: this.apiProxy.config.invalidApiKeys.length, // 已失效的key
1315
+ totalKeysConfigured: this.apiProxy.config.apiKeys.length + this.apiProxy.config.usedApiKeys.length + this.apiProxy.config.invalidApiKeys.length,
1316
+ version: '2.1.0', // 更新版本号以反映新功能
1317
+ features: ['text', 'vision', 'stream_real', 'stream_fake', 'load_balancing']
1318
+ });
1319
+ });
1320
+
1321
+ // 404处理
1322
+ this.app.use('*', (req, res) => {
1323
+ res.status(404).json({
1324
+ error: {
1325
+ message: 'Not Found',
1326
+ type: 'invalid_request_error',
1327
+ code: 'not_found'
1328
+ }
1329
+ });
1330
+ });
1331
+
1332
+ // 全局错误处理
1333
+ this.app.use((err, req, res, next) => {
1334
+ console.error('服务器错误:', err);
1335
+ res.status(500).json({
1336
+ error: {
1337
+ message: '内部服务器错误',
1338
+ type: 'internal_server_error',
1339
+ code: 'server_error'
1340
+ }
1341
+ });
1342
+ });
1343
+ }
1344
+
1345
+ start(port = 3000) {
1346
+ this.app.listen(port, () => {
1347
+ console.log(`🚀 OpenAI to Gemini Proxy Server (Enhanced) 启动在端口 ${port}`);
1348
+ console.log(`📍 真实流式聊天API: http://localhost:${port}/v1/chat/completions`);
1349
+ console.log(`📍 假流式聊天API (新功能): http://localhost:${port}/v1/chat/completions/fakestream`);
1350
+ console.log(`📋 模型列表: http://localhost:${port}/v1/models`);
1351
+ console.log(`🔍 健康检查: http://localhost:${port}/health`);
1352
+ });
1353
+ }
1354
+ }
1355
+
1356
+ // 启动服务器
1357
+ const server = new Server();
1358
+ const port = process.env.PORT || 3000;
1359
  server.start(port);