kuefr commited on
Commit
f87450a
·
verified ·
1 Parent(s): f0c76ab

Update index.js

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