aicodeswitch 3.0.19 → 3.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -158,7 +158,10 @@ class ProxyServer {
158
158
  }
159
159
  // 尝试每个规则,直到成功或全部失败
160
160
  let lastError = null;
161
- for (const rule of allRules) {
161
+ let lastFailedRule = null;
162
+ let lastFailedService = null;
163
+ for (let index = 0; index < allRules.length; index++) {
164
+ const rule = allRules[index];
162
165
  const service = this.getServiceById(rule.targetServiceId);
163
166
  if (!service)
164
167
  continue;
@@ -169,13 +172,19 @@ class ProxyServer {
169
172
  continue;
170
173
  }
171
174
  try {
175
+ const nextServiceName = yield this.findNextAvailableServiceName(allRules, index + 1, route.id);
172
176
  // 尝试代理请求
173
- yield this.proxyRequest(req, res, route, rule, service);
177
+ yield this.proxyRequest(req, res, route, rule, service, {
178
+ failoverEnabled: true,
179
+ forwardedToServiceName: nextServiceName,
180
+ });
174
181
  return; // 成功,直接返回
175
182
  }
176
183
  catch (error) {
177
184
  console.error(`Service ${service.name} failed:`, error.message);
178
185
  lastError = error;
186
+ lastFailedRule = rule;
187
+ lastFailedService = service;
179
188
  // 检测是否是 timeout 错误
180
189
  const isTimeout = error.code === 'ECONNABORTED' ||
181
190
  ((_b = error.message) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes('timeout')) ||
@@ -201,6 +210,21 @@ class ProxyServer {
201
210
  }
202
211
  // 所有服务都失败了
203
212
  console.error('All services failed');
213
+ // 如果有失败的服务但都在黑名单中,尝试使用最后一个失败的服务(作为 fallback)
214
+ if (lastFailedRule && lastFailedService) {
215
+ console.log(`All services in blacklist, attempting fallback to last failed service: ${lastFailedService.name}`);
216
+ try {
217
+ yield this.proxyRequest(req, res, route, lastFailedRule, lastFailedService, {
218
+ failoverEnabled: false, // Fallback 模式不启用故障切换
219
+ forwardedToServiceName: undefined,
220
+ });
221
+ return;
222
+ }
223
+ catch (fallbackError) {
224
+ console.error(`Fallback to service ${lastFailedService.name} also failed:`, fallbackError.message);
225
+ lastError = fallbackError;
226
+ }
227
+ }
204
228
  // 记录日志
205
229
  if (((_d = this.config) === null || _d === void 0 ? void 0 : _d.enableLogging) !== false && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
206
230
  yield this.dbManager.addLog({
@@ -339,7 +363,10 @@ class ProxyServer {
339
363
  }
340
364
  // 尝试每个规则,直到成功或全部失败
341
365
  let lastError = null;
342
- for (const rule of allRules) {
366
+ let lastFailedRule = null;
367
+ let lastFailedService = null;
368
+ for (let index = 0; index < allRules.length; index++) {
369
+ const rule = allRules[index];
343
370
  const service = this.getServiceById(rule.targetServiceId);
344
371
  if (!service)
345
372
  continue;
@@ -350,13 +377,19 @@ class ProxyServer {
350
377
  continue;
351
378
  }
352
379
  try {
380
+ const nextServiceName = yield this.findNextAvailableServiceName(allRules, index + 1, route.id);
353
381
  // 尝试代理请求
354
- yield this.proxyRequest(req, res, route, rule, service);
382
+ yield this.proxyRequest(req, res, route, rule, service, {
383
+ failoverEnabled: true,
384
+ forwardedToServiceName: nextServiceName,
385
+ });
355
386
  return; // 成功,直接返回
356
387
  }
357
388
  catch (error) {
358
389
  console.error(`Service ${service.name} failed:`, error.message);
359
390
  lastError = error;
391
+ lastFailedRule = rule;
392
+ lastFailedService = service;
360
393
  // 检测是否是 timeout 错误
361
394
  const isTimeout = error.code === 'ECONNABORTED' ||
362
395
  ((_b = error.message) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes('timeout')) ||
@@ -382,6 +415,21 @@ class ProxyServer {
382
415
  }
383
416
  // 所有服务都失败了
384
417
  console.error('All services failed');
418
+ // 如果有失败的服务但都在黑名单中,尝试使用最后一个失败的服务(作为 fallback)
419
+ if (lastFailedRule && lastFailedService) {
420
+ console.log(`All services in blacklist, attempting fallback to last failed service: ${lastFailedService.name}`);
421
+ try {
422
+ yield this.proxyRequest(req, res, route, lastFailedRule, lastFailedService, {
423
+ failoverEnabled: false, // Fallback 模式不启用故障切换
424
+ forwardedToServiceName: undefined,
425
+ });
426
+ return;
427
+ }
428
+ catch (fallbackError) {
429
+ console.error(`Fallback to service ${lastFailedService.name} also failed:`, fallbackError.message);
430
+ lastError = fallbackError;
431
+ }
432
+ }
385
433
  // 记录日志
386
434
  if (((_d = this.config) === null || _d === void 0 ? void 0 : _d.enableLogging) !== false && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
387
435
  yield this.dbManager.addLog({
@@ -497,6 +545,36 @@ class ProxyServer {
497
545
  const activeRoutes = this.getActiveRoutes();
498
546
  return activeRoutes.find(route => route.targetType === targetType && route.isActive);
499
547
  }
548
+ findNextAvailableServiceName(allRules, startIndex, routeId) {
549
+ return __awaiter(this, void 0, void 0, function* () {
550
+ for (let index = startIndex; index < allRules.length; index++) {
551
+ const rule = allRules[index];
552
+ const service = this.getServiceById(rule.targetServiceId);
553
+ if (!service)
554
+ continue;
555
+ const isBlacklisted = yield this.dbManager.isServiceBlacklisted(service.id, routeId, rule.contentType);
556
+ if (isBlacklisted)
557
+ continue;
558
+ return service.name;
559
+ }
560
+ return undefined;
561
+ });
562
+ }
563
+ buildFailoverHint(forwardedToServiceName) {
564
+ if (!forwardedToServiceName) {
565
+ return '';
566
+ }
567
+ return `;已自动转发给 ${forwardedToServiceName} 服务继续处理`;
568
+ }
569
+ createFailoverError(message, statusCode, originalError) {
570
+ const failoverError = new Error(message);
571
+ failoverError.isFailoverCandidate = true;
572
+ failoverError.response = { status: statusCode };
573
+ if (originalError === null || originalError === void 0 ? void 0 : originalError.stack) {
574
+ failoverError.stack = originalError.stack;
575
+ }
576
+ return failoverError;
577
+ }
500
578
  /**
501
579
  * 计算请求内容的哈希值,用于去重
502
580
  * 基于请求的关键字段生成唯一标识
@@ -945,6 +1023,10 @@ class ProxyServer {
945
1023
  type: 'thinking',
946
1024
  match: (_req, body) => this.hasThinkingSignal(body),
947
1025
  },
1026
+ {
1027
+ type: 'high-iq',
1028
+ match: (_req, body) => this.hasHighIqSignal(body),
1029
+ },
948
1030
  {
949
1031
  type: 'long-context',
950
1032
  match: (_req, body) => this.hasLongContextSignal(body),
@@ -1009,6 +1091,10 @@ class ProxyServer {
1009
1091
  bg: 'background',
1010
1092
  thinking: 'thinking',
1011
1093
  reasoning: 'thinking',
1094
+ 'high-iq': 'high-iq',
1095
+ high_iq: 'high-iq',
1096
+ highiq: 'high-iq',
1097
+ smart: 'high-iq',
1012
1098
  'long-context': 'long-context',
1013
1099
  long_context: 'long-context',
1014
1100
  long: 'long-context',
@@ -1050,6 +1136,65 @@ class ProxyServer {
1050
1136
  ((_a = body === null || body === void 0 ? void 0 : body.reasoning) === null || _a === void 0 ? void 0 : _a.effort) ||
1051
1137
  ((_b = body === null || body === void 0 ? void 0 : body.reasoning) === null || _b === void 0 ? void 0 : _b.enabled));
1052
1138
  }
1139
+ hasHighIqSignal(body) {
1140
+ const messages = body === null || body === void 0 ? void 0 : body.messages;
1141
+ if (!Array.isArray(messages)) {
1142
+ return false;
1143
+ }
1144
+ for (const message of messages) {
1145
+ if ((message === null || message === void 0 ? void 0 : message.role) !== 'user')
1146
+ continue;
1147
+ const content = message === null || message === void 0 ? void 0 : message.content;
1148
+ // 处理字符串类型的 content
1149
+ if (typeof content === 'string') {
1150
+ if (content.trim().startsWith('!!')) {
1151
+ return true;
1152
+ }
1153
+ }
1154
+ // 处理数组类型的 content
1155
+ else if (Array.isArray(content)) {
1156
+ for (const block of content) {
1157
+ if ((block === null || block === void 0 ? void 0 : block.type) === 'text' && typeof block.text === 'string') {
1158
+ if (block.text.trim().startsWith('!!')) {
1159
+ return true;
1160
+ }
1161
+ }
1162
+ }
1163
+ }
1164
+ }
1165
+ return false;
1166
+ }
1167
+ removeHighIqPrefix(body) {
1168
+ if (!(body === null || body === void 0 ? void 0 : body.messages) || !Array.isArray(body.messages)) {
1169
+ return body;
1170
+ }
1171
+ // 深拷贝 body 以避免修改原始对象
1172
+ const processedBody = JSON.parse(JSON.stringify(body));
1173
+ for (const message of processedBody.messages) {
1174
+ if ((message === null || message === void 0 ? void 0 : message.role) !== 'user')
1175
+ continue;
1176
+ const content = message === null || message === void 0 ? void 0 : message.content;
1177
+ // 处理字符串类型的 content
1178
+ if (typeof content === 'string') {
1179
+ if (content.trim().startsWith('!!')) {
1180
+ // 移除 !! 前缀并执行 trim
1181
+ message.content = content.replace(/^!!\s*/, '').trim();
1182
+ }
1183
+ }
1184
+ // 处理数组类型的 content
1185
+ else if (Array.isArray(content)) {
1186
+ for (const block of content) {
1187
+ if ((block === null || block === void 0 ? void 0 : block.type) === 'text' && typeof block.text === 'string') {
1188
+ if (block.text.trim().startsWith('!!')) {
1189
+ // 移除 !! 前缀并执行 trim
1190
+ block.text = block.text.replace(/^!!\s*/, '').trim();
1191
+ }
1192
+ }
1193
+ }
1194
+ }
1195
+ }
1196
+ return processedBody;
1197
+ }
1053
1198
  hasBackgroundSignal(body) {
1054
1199
  var _a, _b, _c;
1055
1200
  // 检测 count tokens 请求:messages 只有一条,role 为 "user",content 为 "count"
@@ -1171,15 +1316,19 @@ class ProxyServer {
1171
1316
  }
1172
1317
  /** 判断是否为 Claude 相关类型(使用 x-api-key 认证) */
1173
1318
  isClaudeSource(sourceType) {
1174
- return sourceType === 'claude-chat' || sourceType === 'claude-code';
1319
+ return sourceType === 'claude-chat' || sourceType === 'claude';
1175
1320
  }
1176
1321
  isOpenAIChatSource(sourceType) {
1177
- return sourceType === 'openai-chat' || sourceType === 'openai-responses' || sourceType === 'deepseek-reasoning-chat';
1322
+ return sourceType === 'openai-chat' || sourceType === 'openai' || sourceType === 'deepseek-reasoning-chat';
1178
1323
  }
1179
1324
  /** 判断是否为 Gemini 类型 */
1180
1325
  isGeminiSource(sourceType) {
1181
1326
  return sourceType === 'gemini';
1182
1327
  }
1328
+ /** 判断是否为 Gemini Chat 类型 */
1329
+ isGeminiChatSource(sourceType) {
1330
+ return sourceType === 'gemini-chat';
1331
+ }
1183
1332
  isChatType(sourceType) {
1184
1333
  return sourceType.endsWith('-chat') || sourceType === 'gemini';
1185
1334
  }
@@ -1301,8 +1450,8 @@ class ProxyServer {
1301
1450
  // 向下兼容:检测旧数据的 'auto' 值
1302
1451
  // TODO: 删除
1303
1452
  const isAuto = authType === 'auto';
1304
- // 使用 x-goog-api-key 认证(适用于 Google Gemini API)
1305
- if (authType === types_1.AuthType.G_API_KEY || (isAuto && this.isGeminiSource(sourceType))) {
1453
+ // 使用 x-goog-api-key 认证(适用于 Google Gemini API 和 Gemini Chat
1454
+ if (authType === types_1.AuthType.G_API_KEY || (isAuto && (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType)))) {
1306
1455
  headers['x-goog-api-key'] = service.apiKey;
1307
1456
  }
1308
1457
  // 使用 x-api-key 认证(适用于 claude-chat, claude-code 及某些需要 x-api-key 的 openai-chat 兼容 API)
@@ -1559,13 +1708,15 @@ class ProxyServer {
1559
1708
  // 默认:直接返回原始路径
1560
1709
  return originalPath;
1561
1710
  }
1562
- proxyRequest(req, res, route, rule, service) {
1711
+ proxyRequest(req, res, route, rule, service, options) {
1563
1712
  return __awaiter(this, void 0, void 0, function* () {
1564
- var _a, _b, _c, _d, _e, _f;
1713
+ var _a, _b, _c, _d, _e;
1565
1714
  res.locals.skipLog = true;
1566
1715
  const startTime = Date.now();
1567
1716
  const sourceType = (service.sourceType || 'openai-chat');
1568
1717
  const targetType = route.targetType;
1718
+ const failoverEnabled = (options === null || options === void 0 ? void 0 : options.failoverEnabled) === true;
1719
+ const forwardedToServiceName = options === null || options === void 0 ? void 0 : options.forwardedToServiceName;
1569
1720
  let requestBody = req.body || {};
1570
1721
  let usageForLog;
1571
1722
  let logged = false;
@@ -1686,6 +1837,11 @@ class ProxyServer {
1686
1837
  useMCPProcessing = false;
1687
1838
  }
1688
1839
  }
1840
+ // 高智商请求处理:移除 !! 前缀
1841
+ if (rule.contentType === 'high-iq' && requestBody.messages) {
1842
+ requestBody = this.removeHighIqPrefix(requestBody);
1843
+ console.log('[HIGH-IQ] Removed !! prefix from user messages');
1844
+ }
1689
1845
  // 用于收集响应数据的变量
1690
1846
  let responseHeadersForLog;
1691
1847
  let responseBodyForLog;
@@ -1801,6 +1957,62 @@ class ProxyServer {
1801
1957
  console.log(`[MCP] Cleaned up ${tempImageFiles.length} temporary image files`);
1802
1958
  }
1803
1959
  });
1960
+ const handleUpstreamHttpError = (statusCode, responseData, responseHeaders, contentType) => __awaiter(this, void 0, void 0, function* () {
1961
+ var _a, _b;
1962
+ usageForLog = this.extractTokenUsage(responseData === null || responseData === void 0 ? void 0 : responseData.usage);
1963
+ responseBodyForLog = typeof responseData === 'string' ? responseData : JSON.stringify(responseData);
1964
+ let errorDetail;
1965
+ if (typeof (responseData === null || responseData === void 0 ? void 0 : responseData.error) === 'string') {
1966
+ errorDetail = responseData.error;
1967
+ }
1968
+ else if (typeof (responseData === null || responseData === void 0 ? void 0 : responseData.message) === 'string') {
1969
+ errorDetail = responseData.message;
1970
+ }
1971
+ else if (responseData === null || responseData === void 0 ? void 0 : responseData.error) {
1972
+ errorDetail = JSON.stringify(responseData.error);
1973
+ }
1974
+ else {
1975
+ errorDetail = JSON.stringify(responseData);
1976
+ }
1977
+ const failoverHint = failoverEnabled ? this.buildFailoverHint(forwardedToServiceName) : '';
1978
+ const upstreamErrorMessage = `Upstream API returned ${statusCode}: ${errorDetail}${failoverHint}`;
1979
+ const vendors = this.dbManager.getVendors();
1980
+ const vendor = vendors.find(v => v.id === service.vendorId);
1981
+ yield this.dbManager.addErrorLog({
1982
+ timestamp: Date.now(),
1983
+ method: req.method,
1984
+ path: req.path,
1985
+ statusCode,
1986
+ errorMessage: upstreamErrorMessage,
1987
+ errorStack: undefined,
1988
+ requestHeaders: this.normalizeHeaders(req.headers),
1989
+ requestBody: req.body ? JSON.stringify(req.body) : undefined,
1990
+ responseHeaders: responseHeadersForLog,
1991
+ responseBody: responseBodyForLog,
1992
+ ruleId: rule.id,
1993
+ targetType,
1994
+ targetServiceId: service.id,
1995
+ targetServiceName: service.name,
1996
+ targetModel: rule.targetModel || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
1997
+ vendorId: service.vendorId,
1998
+ vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
1999
+ requestModel: (_b = req.body) === null || _b === void 0 ? void 0 : _b.model,
2000
+ upstreamRequest: upstreamRequestForLog,
2001
+ responseTime: Date.now() - startTime,
2002
+ });
2003
+ if (failoverEnabled) {
2004
+ yield finalizeLog(statusCode, upstreamErrorMessage);
2005
+ throw this.createFailoverError(upstreamErrorMessage, statusCode);
2006
+ }
2007
+ this.copyResponseHeaders(responseHeaders, res);
2008
+ if (contentType.includes('application/json')) {
2009
+ res.status(statusCode).json(responseData);
2010
+ }
2011
+ else {
2012
+ res.status(statusCode).send(responseData);
2013
+ }
2014
+ yield finalizeLog(res.statusCode);
2015
+ });
1804
2016
  try {
1805
2017
  if (targetType === 'claude-code') {
1806
2018
  if (this.isClaudeSource(sourceType)) {
@@ -1809,7 +2021,7 @@ class ProxyServer {
1809
2021
  else if (this.isOpenAIChatSource(sourceType)) {
1810
2022
  requestBody = (0, claude_openai_1.transformClaudeRequestToOpenAIChat)(requestBody, rule.targetModel);
1811
2023
  }
1812
- else if (this.isGeminiSource(sourceType)) {
2024
+ else if (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType)) {
1813
2025
  requestBody = (0, gemini_1.transformClaudeRequestToGemini)(requestBody);
1814
2026
  }
1815
2027
  else {
@@ -1825,7 +2037,7 @@ class ProxyServer {
1825
2037
  else if (this.isClaudeSource(sourceType)) {
1826
2038
  requestBody = (0, claude_openai_1.transformClaudeRequestToOpenAIChat)(requestBody, rule.targetModel);
1827
2039
  }
1828
- else if (this.isGeminiSource(sourceType)) {
2040
+ else if (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType)) {
1829
2041
  requestBody = (0, gemini_1.transformOpenAIChatRequestToGemini)(requestBody);
1830
2042
  }
1831
2043
  else {
@@ -1854,7 +2066,8 @@ class ProxyServer {
1854
2066
  const model = requestBody.model || rule.targetModel || 'gemini-pro';
1855
2067
  upstreamUrl = this.buildGeminiUrl(service.apiUrl, model, streamRequested);
1856
2068
  }
1857
- else if (this.isChatType(sourceType)) {
2069
+ else if (this.isChatType(sourceType) || this.isGeminiChatSource(sourceType)) {
2070
+ // Chat 类型(包括 gemini-chat)直接使用用户配置的完整 URL
1858
2071
  upstreamUrl = service.apiUrl;
1859
2072
  }
1860
2073
  else {
@@ -1922,6 +2135,17 @@ class ProxyServer {
1922
2135
  const responseHeaders = response.headers || {};
1923
2136
  const contentType = typeof responseHeaders['content-type'] === 'string' ? responseHeaders['content-type'] : '';
1924
2137
  const isEventStream = streamRequested && contentType.includes('text/event-stream');
2138
+ // 先处理 4xx/5xx:在故障切换模式下抛错,由上层继续切换下一服务
2139
+ if (response.status >= 400) {
2140
+ let errorResponseData = response.data;
2141
+ if (streamRequested && response.data && typeof response.data.on === 'function') {
2142
+ const raw = yield this.readStreamBody(response.data);
2143
+ errorResponseData = (_a = this.safeJsonParse(raw)) !== null && _a !== void 0 ? _a : raw;
2144
+ }
2145
+ responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
2146
+ yield handleUpstreamHttpError(response.status, errorResponseData, responseHeaders, contentType);
2147
+ return;
2148
+ }
1925
2149
  if (isEventStream && response.data) {
1926
2150
  res.status(response.status);
1927
2151
  if (targetType === 'claude-code' && this.isOpenAIChatSource(sourceType)) {
@@ -2126,8 +2350,8 @@ class ProxyServer {
2126
2350
  }));
2127
2351
  return;
2128
2352
  }
2129
- // Gemini -> Claude Code 流式转换
2130
- if (targetType === 'claude-code' && this.isGeminiSource(sourceType)) {
2353
+ // Gemini / Gemini Chat -> Claude Code 流式转换
2354
+ if (targetType === 'claude-code' && (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType))) {
2131
2355
  res.setHeader('Content-Type', 'text/event-stream');
2132
2356
  res.setHeader('Cache-Control', 'no-cache');
2133
2357
  res.setHeader('Connection', 'keep-alive');
@@ -2222,8 +2446,8 @@ class ProxyServer {
2222
2446
  }));
2223
2447
  return;
2224
2448
  }
2225
- // Gemini -> Codex 流式转换
2226
- if (targetType === 'codex' && this.isGeminiSource(sourceType)) {
2449
+ // Gemini / Gemini Chat -> Codex 流式转换
2450
+ if (targetType === 'codex' && (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType))) {
2227
2451
  res.setHeader('Content-Type', 'text/event-stream');
2228
2452
  res.setHeader('Cache-Control', 'no-cache');
2229
2453
  res.setHeader('Connection', 'keep-alive');
@@ -2379,65 +2603,10 @@ class ProxyServer {
2379
2603
  let responseData = response.data;
2380
2604
  if (streamRequested && response.data && typeof response.data.on === 'function' && !isEventStream) {
2381
2605
  const raw = yield this.readStreamBody(response.data);
2382
- responseData = (_a = this.safeJsonParse(raw)) !== null && _a !== void 0 ? _a : raw;
2606
+ responseData = (_b = this.safeJsonParse(raw)) !== null && _b !== void 0 ? _b : raw;
2383
2607
  }
2384
2608
  // 收集响应头
2385
2609
  responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
2386
- if (response.status >= 400) {
2387
- usageForLog = this.extractTokenUsage(responseData === null || responseData === void 0 ? void 0 : responseData.usage);
2388
- // 记录错误响应体
2389
- responseBodyForLog = typeof responseData === 'string' ? responseData : JSON.stringify(responseData);
2390
- // 将 4xx/5xx 错误记录到错误日志
2391
- // 确保 errorDetail 总是字符串类型
2392
- let errorDetail;
2393
- if (typeof (responseData === null || responseData === void 0 ? void 0 : responseData.error) === 'string') {
2394
- errorDetail = responseData.error;
2395
- }
2396
- else if (typeof (responseData === null || responseData === void 0 ? void 0 : responseData.message) === 'string') {
2397
- errorDetail = responseData.message;
2398
- }
2399
- else if (responseData === null || responseData === void 0 ? void 0 : responseData.error) {
2400
- errorDetail = JSON.stringify(responseData.error);
2401
- }
2402
- else {
2403
- errorDetail = JSON.stringify(responseData);
2404
- }
2405
- // 获取供应商信息
2406
- const vendors = this.dbManager.getVendors();
2407
- const vendor = vendors.find(v => v.id === service.vendorId);
2408
- yield this.dbManager.addErrorLog({
2409
- timestamp: Date.now(),
2410
- method: req.method,
2411
- path: req.path,
2412
- statusCode: response.status,
2413
- errorMessage: `Upstream API returned ${response.status}: ${errorDetail}`,
2414
- errorStack: undefined,
2415
- requestHeaders: this.normalizeHeaders(req.headers),
2416
- requestBody: req.body ? JSON.stringify(req.body) : undefined,
2417
- responseHeaders: responseHeadersForLog,
2418
- responseBody: responseBodyForLog,
2419
- // 添加请求详情和实际转发信息
2420
- ruleId: rule.id,
2421
- targetType,
2422
- targetServiceId: service.id,
2423
- targetServiceName: service.name,
2424
- targetModel: rule.targetModel || ((_b = req.body) === null || _b === void 0 ? void 0 : _b.model),
2425
- vendorId: service.vendorId,
2426
- vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
2427
- requestModel: (_c = req.body) === null || _c === void 0 ? void 0 : _c.model,
2428
- upstreamRequest: upstreamRequestForLog,
2429
- responseTime: Date.now() - startTime,
2430
- });
2431
- this.copyResponseHeaders(responseHeaders, res);
2432
- if (contentType.includes('application/json')) {
2433
- res.status(response.status).json(responseData);
2434
- }
2435
- else {
2436
- res.status(response.status).send(responseData);
2437
- }
2438
- yield finalizeLog(res.statusCode);
2439
- return;
2440
- }
2441
2610
  if (targetType === 'claude-code' && this.isOpenAIChatSource(sourceType)) {
2442
2611
  const converted = (0, claude_openai_1.transformOpenAIChatResponseToClaude)(responseData);
2443
2612
  usageForLog = (0, claude_openai_1.extractTokenUsageFromOpenAIUsage)(responseData === null || responseData === void 0 ? void 0 : responseData.usage);
@@ -2445,7 +2614,7 @@ class ProxyServer {
2445
2614
  responseBodyForLog = JSON.stringify(converted);
2446
2615
  res.status(response.status).json(converted);
2447
2616
  }
2448
- else if (targetType === 'claude-code' && this.isGeminiSource(sourceType)) {
2617
+ else if (targetType === 'claude-code' && (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType))) {
2449
2618
  const converted = (0, gemini_1.transformGeminiResponseToClaude)(responseData, rule.targetModel);
2450
2619
  usageForLog = (0, gemini_1.extractTokenUsageFromGeminiUsage)(responseData === null || responseData === void 0 ? void 0 : responseData.usageMetadata);
2451
2620
  responseBodyForLog = JSON.stringify(converted);
@@ -2457,7 +2626,7 @@ class ProxyServer {
2457
2626
  responseBodyForLog = JSON.stringify(converted);
2458
2627
  res.status(response.status).json(converted);
2459
2628
  }
2460
- else if (targetType === 'codex' && this.isGeminiSource(sourceType)) {
2629
+ else if (targetType === 'codex' && (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType))) {
2461
2630
  const converted = (0, gemini_1.transformGeminiResponseToOpenAIChat)(responseData, rule.targetModel);
2462
2631
  usageForLog = (0, gemini_1.extractTokenUsageFromGeminiUsage)(responseData === null || responseData === void 0 ? void 0 : responseData.usageMetadata);
2463
2632
  responseBodyForLog = JSON.stringify(converted);
@@ -2479,14 +2648,19 @@ class ProxyServer {
2479
2648
  yield finalizeLog(res.statusCode);
2480
2649
  }
2481
2650
  catch (error) {
2651
+ if (failoverEnabled && (error === null || error === void 0 ? void 0 : error.isFailoverCandidate)) {
2652
+ throw error;
2653
+ }
2482
2654
  console.error('Proxy error:', error);
2483
2655
  // 检测是否是 timeout 错误
2484
2656
  const isTimeout = error.code === 'ECONNABORTED' ||
2485
- ((_d = error.message) === null || _d === void 0 ? void 0 : _d.toLowerCase().includes('timeout')) ||
2657
+ ((_c = error.message) === null || _c === void 0 ? void 0 : _c.toLowerCase().includes('timeout')) ||
2486
2658
  (error.errno && error.errno === 'ETIMEDOUT');
2487
- const errorMessage = isTimeout
2659
+ const baseErrorMessage = isTimeout
2488
2660
  ? 'Request timeout - the upstream API took too long to respond'
2489
2661
  : (error.message || 'Internal server error');
2662
+ const failoverHint = failoverEnabled ? this.buildFailoverHint(forwardedToServiceName) : '';
2663
+ const errorMessage = `${baseErrorMessage}${failoverHint}`;
2490
2664
  // 将错误记录到错误日志 - 包含请求详情和实际转发信息
2491
2665
  // 获取供应商信息
2492
2666
  const vendors = this.dbManager.getVendors();
@@ -2505,14 +2679,17 @@ class ProxyServer {
2505
2679
  targetType,
2506
2680
  targetServiceId: service.id,
2507
2681
  targetServiceName: service.name,
2508
- targetModel: rule.targetModel || ((_e = req.body) === null || _e === void 0 ? void 0 : _e.model),
2682
+ targetModel: rule.targetModel || ((_d = req.body) === null || _d === void 0 ? void 0 : _d.model),
2509
2683
  vendorId: service.vendorId,
2510
2684
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
2511
- requestModel: (_f = req.body) === null || _f === void 0 ? void 0 : _f.model,
2685
+ requestModel: (_e = req.body) === null || _e === void 0 ? void 0 : _e.model,
2512
2686
  upstreamRequest: upstreamRequestForLog,
2513
2687
  responseTime: Date.now() - startTime,
2514
2688
  });
2515
2689
  yield finalizeLog(isTimeout ? 504 : 500, errorMessage);
2690
+ if (failoverEnabled) {
2691
+ throw this.createFailoverError(errorMessage, isTimeout ? 504 : 500, error);
2692
+ }
2516
2693
  // 根据请求类型返回适当格式的错误响应
2517
2694
  const streamRequested = this.isStreamRequested(req, req.body || {});
2518
2695
  if (route.targetType === 'claude-code') {
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.migrateSourceType = migrateSourceType;
4
+ exports.downgradeSourceType = downgradeSourceType;
5
+ exports.isLegacySourceType = isLegacySourceType;
6
+ exports.normalizeSourceType = normalizeSourceType;
7
+ /**
8
+ * 旧类型 → 新类型映射表
9
+ */
10
+ const SOURCE_TYPE_MIGRATION_MAP = {
11
+ 'openai-chat': 'openai-chat',
12
+ 'openai-responses': 'openai', // 重命名
13
+ 'claude-chat': 'claude-chat',
14
+ 'claude-code': 'claude', // 重命名
15
+ 'deepseek-reasoning-chat': 'deepseek-reasoning-chat',
16
+ 'gemini': 'gemini',
17
+ 'gemini-chat': 'gemini-chat',
18
+ };
19
+ /**
20
+ * 新类型 → 旧类型映射表
21
+ * 用于向下兼容导出
22
+ */
23
+ const SOURCE_TYPE_REVERSE_MAP = {
24
+ 'openai-chat': 'openai-chat',
25
+ 'openai': 'openai-responses',
26
+ 'claude-chat': 'claude-chat',
27
+ 'claude': 'claude-code',
28
+ 'deepseek-reasoning-chat': 'deepseek-reasoning-chat',
29
+ 'gemini': 'gemini',
30
+ 'gemini-chat': 'gemini-chat',
31
+ };
32
+ /**
33
+ * 将旧类型转换为新类型
34
+ * @param legacyType 旧的数据源类型
35
+ * @returns 新的数据源类型
36
+ */
37
+ function migrateSourceType(legacyType) {
38
+ return SOURCE_TYPE_MIGRATION_MAP[legacyType];
39
+ }
40
+ /**
41
+ * 将新类型转换为旧类型
42
+ * 用于向下兼容导出
43
+ * @param newType 新的数据源类型
44
+ * @returns 旧的数据源类型
45
+ */
46
+ function downgradeSourceType(newType) {
47
+ return SOURCE_TYPE_REVERSE_MAP[newType];
48
+ }
49
+ /**
50
+ * 检查是否为旧类型
51
+ * @param type 类型字符串
52
+ * @returns 是否为旧类型
53
+ */
54
+ function isLegacySourceType(type) {
55
+ return type === 'openai-responses' || type === 'claude-code';
56
+ }
57
+ /**
58
+ * 标准化类型
59
+ * 自动处理新旧类型,将旧类型转换为新类型,新类型保持不变
60
+ * @param type 类型字符串(可能是旧类型或新类型)
61
+ * @returns 标准化后的新类型
62
+ */
63
+ function normalizeSourceType(type) {
64
+ if (isLegacySourceType(type)) {
65
+ return migrateSourceType(type);
66
+ }
67
+ return type;
68
+ }