aicodeswitch 3.0.20 → 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.
package/README.md CHANGED
@@ -29,6 +29,7 @@ AI Code Switch 是帮助你在本地管理 AI 编程工具接入大模型的工
29
29
  * 导入和导出:一键备份数据,在多太电脑间共享aicodeswitch配置
30
30
  * 自定义API Key,支持B/S架构,让aicodeswitch成为在线服务,提供给团队使用
31
31
  * 数据完全本地,自主可控
32
+ * 特殊语法:在发送的提示词最前面添加!!来直接切换为高智商模型服务,简单快捷
32
33
 
33
34
  ## 桌面客户端
34
35
 
@@ -28,6 +28,7 @@ const path_1 = __importDefault(require("path"));
28
28
  const promises_1 = __importDefault(require("fs/promises"));
29
29
  const crypto_1 = __importDefault(require("crypto"));
30
30
  const crypto_js_1 = __importDefault(require("crypto-js"));
31
+ const type_migration_1 = require("./type-migration");
31
32
  /**
32
33
  * 基于文件系统的数据库管理器
33
34
  * 使用 JSON 文件存储数据,无需编译依赖
@@ -206,6 +207,8 @@ class FileSystemDatabaseManager {
206
207
  yield promises_1.default.mkdir(this.dataPath, { recursive: true });
207
208
  // 加载所有数据
208
209
  yield this.loadAllData();
210
+ // 执行数据源类型迁移(在加载数据之后)
211
+ yield this.migrateSourceTypes();
209
212
  // 确保默认配置
210
213
  yield this.ensureDefaultConfig();
211
214
  });
@@ -293,6 +296,77 @@ class FileSystemDatabaseManager {
293
296
  }
294
297
  });
295
298
  }
299
+ /**
300
+ * 迁移数据源类型(在初始化时执行)
301
+ * 处理 vendors.json 中的 services[].sourceType
302
+ * 将旧类型 ('claude-code', 'openai-responses') 迁移为新类型 ('claude', 'openai')
303
+ */
304
+ migrateSourceTypes() {
305
+ return __awaiter(this, void 0, void 0, function* () {
306
+ console.log('[TypeMigration] Checking for source type migration...');
307
+ let needsMigration = false;
308
+ // 检查是否需要迁移
309
+ for (const vendor of this.vendors) {
310
+ if (vendor.services) {
311
+ for (const service of vendor.services) {
312
+ if (service.sourceType && (0, type_migration_1.isLegacySourceType)(service.sourceType)) {
313
+ needsMigration = true;
314
+ break;
315
+ }
316
+ }
317
+ }
318
+ if (needsMigration)
319
+ break;
320
+ }
321
+ if (!needsMigration) {
322
+ console.log('[TypeMigration] No migration needed');
323
+ return;
324
+ }
325
+ console.log('[TypeMigration] Starting source type migration...');
326
+ // 备份当前数据到 ~/.aicodeswitch/backup/YYYY-MM-DD-HH-MM/
327
+ const now = new Date();
328
+ const year = now.getFullYear();
329
+ const month = String(now.getMonth() + 1).padStart(2, '0');
330
+ const day = String(now.getDate()).padStart(2, '0');
331
+ const hours = String(now.getHours()).padStart(2, '0');
332
+ const minutes = String(now.getMinutes()).padStart(2, '0');
333
+ const dateStr = `${year}-${month}-${day}-${hours}-${minutes}`;
334
+ const appDir = path_1.default.dirname(this.dataPath); // ~/.aicodeswitch/
335
+ const backupBaseDir = path_1.default.join(appDir, 'backup');
336
+ const backupDir = path_1.default.join(backupBaseDir, dateStr);
337
+ yield promises_1.default.mkdir(backupDir, { recursive: true });
338
+ const vendorsBackupPath = path_1.default.join(backupDir, 'vendors.json');
339
+ yield promises_1.default.writeFile(vendorsBackupPath, JSON.stringify(this.vendors, null, 2));
340
+ console.log(`[TypeMigration] Backup created: ${vendorsBackupPath}`);
341
+ // 执行迁移
342
+ let migratedCount = 0;
343
+ for (const vendor of this.vendors) {
344
+ if (vendor.services) {
345
+ for (const service of vendor.services) {
346
+ if (service.sourceType && (0, type_migration_1.isLegacySourceType)(service.sourceType)) {
347
+ const oldType = service.sourceType;
348
+ service.sourceType = (0, type_migration_1.migrateSourceType)(service.sourceType);
349
+ console.log(`[TypeMigration] Migrated service "${service.name}": ${oldType} -> ${service.sourceType}`);
350
+ migratedCount++;
351
+ }
352
+ }
353
+ }
354
+ }
355
+ // 保存迁移后的数据
356
+ yield this.saveVendors();
357
+ console.log(`[TypeMigration] Migration completed. Migrated ${migratedCount} services.`);
358
+ });
359
+ }
360
+ /**
361
+ * 迁移导入数据中的类型
362
+ * 用于导入功能,自动将旧类型转换为新类型
363
+ */
364
+ migrateVendorsOnImport(vendors) {
365
+ return vendors.map(vendor => {
366
+ var _a;
367
+ return (Object.assign(Object.assign({}, vendor), { services: (_a = vendor.services) === null || _a === void 0 ? void 0 : _a.map(service => (Object.assign(Object.assign({}, service), { sourceType: service.sourceType ? (0, type_migration_1.normalizeSourceType)(service.sourceType) : undefined }))) }));
368
+ });
369
+ }
296
370
  saveVendors() {
297
371
  return __awaiter(this, void 0, void 0, function* () {
298
372
  // 确保每个供应商都有 services 数组
@@ -1507,7 +1581,7 @@ class FileSystemDatabaseManager {
1507
1581
  const existing = this.blacklist.get(key);
1508
1582
  if (existing) {
1509
1583
  existing.blacklistedAt = now;
1510
- existing.expiresAt = now + 10 * 60 * 1000;
1584
+ existing.expiresAt = now + 2 * 60 * 1000; // 2分钟黑名单(从10分钟缩短)
1511
1585
  existing.errorCount++;
1512
1586
  existing.lastError = errorMessage;
1513
1587
  existing.lastStatusCode = statusCode;
@@ -1519,7 +1593,7 @@ class FileSystemDatabaseManager {
1519
1593
  routeId,
1520
1594
  contentType,
1521
1595
  blacklistedAt: now,
1522
- expiresAt: now + 10 * 60 * 1000,
1596
+ expiresAt: now + 2 * 60 * 1000, // 2分钟黑名单(从10分钟缩短)
1523
1597
  errorCount: 1,
1524
1598
  lastError: errorMessage,
1525
1599
  lastStatusCode: statusCode,
@@ -1807,6 +1881,10 @@ class FileSystemDatabaseManager {
1807
1881
  if (!validation.valid) {
1808
1882
  return { success: false, message: '导入失败', details: `数据验证失败:${validation.error}` };
1809
1883
  }
1884
+ // 自动迁移导入数据中的旧类型
1885
+ if (importData.vendors) {
1886
+ importData.vendors = this.migrateVendorsOnImport(importData.vendors);
1887
+ }
1810
1888
  // 导入数据(更新 updatedAt)
1811
1889
  const now = Date.now();
1812
1890
  this.vendors = importData.vendors.map((v) => (Object.assign(Object.assign({}, v), { updatedAt: now })));
@@ -28,6 +28,7 @@ const utils_1 = require("./utils");
28
28
  const tools_service_1 = require("./tools-service");
29
29
  const websocket_service_1 = require("./websocket-service");
30
30
  const rules_status_service_1 = require("./rules-status-service");
31
+ const type_migration_1 = require("./type-migration");
31
32
  const config_metadata_1 = require("./config-metadata");
32
33
  const config_1 = require("./config");
33
34
  const appDir = path_1.default.join(os_1.default.homedir(), '.aicodeswitch');
@@ -75,6 +76,28 @@ const app = (0, express_1.default)();
75
76
  app.use((0, cors_1.default)());
76
77
  app.use(express_1.default.json({ limit: 'Infinity' }));
77
78
  app.use(express_1.default.urlencoded({ extended: true, limit: 'Infinity' }));
79
+ // 类型转换中间件:自动将旧的数据源类型转换为新类型
80
+ app.use((req, _res, next) => {
81
+ if (req.body && typeof req.body === 'object') {
82
+ // 转换 sourceType
83
+ if (req.body.sourceType && typeof req.body.sourceType === 'string') {
84
+ if ((0, type_migration_1.isLegacySourceType)(req.body.sourceType)) {
85
+ console.log(`[API] Converting legacy sourceType: ${req.body.sourceType} -> ${(0, type_migration_1.normalizeSourceType)(req.body.sourceType)}`);
86
+ req.body.sourceType = (0, type_migration_1.normalizeSourceType)(req.body.sourceType);
87
+ }
88
+ }
89
+ // 转换数组中的 sourceType(如 vendors 的 services)
90
+ if (Array.isArray(req.body.services)) {
91
+ req.body.services = req.body.services.map((service) => {
92
+ if (service.sourceType && (0, type_migration_1.isLegacySourceType)(service.sourceType)) {
93
+ return Object.assign(Object.assign({}, service), { sourceType: (0, type_migration_1.normalizeSourceType)(service.sourceType) });
94
+ }
95
+ return service;
96
+ });
97
+ }
98
+ }
99
+ next();
100
+ });
78
101
  const asyncHandler = (handler) => (req, res, next) => {
79
102
  Promise.resolve(handler(req, res, next)).catch((err) => {
80
103
  console.error('[asyncHandler] Caught error:', err);
@@ -158,6 +158,8 @@ class ProxyServer {
158
158
  }
159
159
  // 尝试每个规则,直到成功或全部失败
160
160
  let lastError = null;
161
+ let lastFailedRule = null;
162
+ let lastFailedService = null;
161
163
  for (let index = 0; index < allRules.length; index++) {
162
164
  const rule = allRules[index];
163
165
  const service = this.getServiceById(rule.targetServiceId);
@@ -181,6 +183,8 @@ class ProxyServer {
181
183
  catch (error) {
182
184
  console.error(`Service ${service.name} failed:`, error.message);
183
185
  lastError = error;
186
+ lastFailedRule = rule;
187
+ lastFailedService = service;
184
188
  // 检测是否是 timeout 错误
185
189
  const isTimeout = error.code === 'ECONNABORTED' ||
186
190
  ((_b = error.message) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes('timeout')) ||
@@ -206,6 +210,21 @@ class ProxyServer {
206
210
  }
207
211
  // 所有服务都失败了
208
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
+ }
209
228
  // 记录日志
210
229
  if (((_d = this.config) === null || _d === void 0 ? void 0 : _d.enableLogging) !== false && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
211
230
  yield this.dbManager.addLog({
@@ -344,6 +363,8 @@ class ProxyServer {
344
363
  }
345
364
  // 尝试每个规则,直到成功或全部失败
346
365
  let lastError = null;
366
+ let lastFailedRule = null;
367
+ let lastFailedService = null;
347
368
  for (let index = 0; index < allRules.length; index++) {
348
369
  const rule = allRules[index];
349
370
  const service = this.getServiceById(rule.targetServiceId);
@@ -367,6 +388,8 @@ class ProxyServer {
367
388
  catch (error) {
368
389
  console.error(`Service ${service.name} failed:`, error.message);
369
390
  lastError = error;
391
+ lastFailedRule = rule;
392
+ lastFailedService = service;
370
393
  // 检测是否是 timeout 错误
371
394
  const isTimeout = error.code === 'ECONNABORTED' ||
372
395
  ((_b = error.message) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes('timeout')) ||
@@ -392,6 +415,21 @@ class ProxyServer {
392
415
  }
393
416
  // 所有服务都失败了
394
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
+ }
395
433
  // 记录日志
396
434
  if (((_d = this.config) === null || _d === void 0 ? void 0 : _d.enableLogging) !== false && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
397
435
  yield this.dbManager.addLog({
@@ -985,6 +1023,10 @@ class ProxyServer {
985
1023
  type: 'thinking',
986
1024
  match: (_req, body) => this.hasThinkingSignal(body),
987
1025
  },
1026
+ {
1027
+ type: 'high-iq',
1028
+ match: (_req, body) => this.hasHighIqSignal(body),
1029
+ },
988
1030
  {
989
1031
  type: 'long-context',
990
1032
  match: (_req, body) => this.hasLongContextSignal(body),
@@ -1049,6 +1091,10 @@ class ProxyServer {
1049
1091
  bg: 'background',
1050
1092
  thinking: 'thinking',
1051
1093
  reasoning: 'thinking',
1094
+ 'high-iq': 'high-iq',
1095
+ high_iq: 'high-iq',
1096
+ highiq: 'high-iq',
1097
+ smart: 'high-iq',
1052
1098
  'long-context': 'long-context',
1053
1099
  long_context: 'long-context',
1054
1100
  long: 'long-context',
@@ -1090,6 +1136,65 @@ class ProxyServer {
1090
1136
  ((_a = body === null || body === void 0 ? void 0 : body.reasoning) === null || _a === void 0 ? void 0 : _a.effort) ||
1091
1137
  ((_b = body === null || body === void 0 ? void 0 : body.reasoning) === null || _b === void 0 ? void 0 : _b.enabled));
1092
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
+ }
1093
1198
  hasBackgroundSignal(body) {
1094
1199
  var _a, _b, _c;
1095
1200
  // 检测 count tokens 请求:messages 只有一条,role 为 "user",content 为 "count"
@@ -1211,15 +1316,19 @@ class ProxyServer {
1211
1316
  }
1212
1317
  /** 判断是否为 Claude 相关类型(使用 x-api-key 认证) */
1213
1318
  isClaudeSource(sourceType) {
1214
- return sourceType === 'claude-chat' || sourceType === 'claude-code';
1319
+ return sourceType === 'claude-chat' || sourceType === 'claude';
1215
1320
  }
1216
1321
  isOpenAIChatSource(sourceType) {
1217
- return sourceType === 'openai-chat' || sourceType === 'openai-responses' || sourceType === 'deepseek-reasoning-chat';
1322
+ return sourceType === 'openai-chat' || sourceType === 'openai' || sourceType === 'deepseek-reasoning-chat';
1218
1323
  }
1219
1324
  /** 判断是否为 Gemini 类型 */
1220
1325
  isGeminiSource(sourceType) {
1221
1326
  return sourceType === 'gemini';
1222
1327
  }
1328
+ /** 判断是否为 Gemini Chat 类型 */
1329
+ isGeminiChatSource(sourceType) {
1330
+ return sourceType === 'gemini-chat';
1331
+ }
1223
1332
  isChatType(sourceType) {
1224
1333
  return sourceType.endsWith('-chat') || sourceType === 'gemini';
1225
1334
  }
@@ -1341,8 +1450,8 @@ class ProxyServer {
1341
1450
  // 向下兼容:检测旧数据的 'auto' 值
1342
1451
  // TODO: 删除
1343
1452
  const isAuto = authType === 'auto';
1344
- // 使用 x-goog-api-key 认证(适用于 Google Gemini API)
1345
- 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)))) {
1346
1455
  headers['x-goog-api-key'] = service.apiKey;
1347
1456
  }
1348
1457
  // 使用 x-api-key 认证(适用于 claude-chat, claude-code 及某些需要 x-api-key 的 openai-chat 兼容 API)
@@ -1728,6 +1837,11 @@ class ProxyServer {
1728
1837
  useMCPProcessing = false;
1729
1838
  }
1730
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
+ }
1731
1845
  // 用于收集响应数据的变量
1732
1846
  let responseHeadersForLog;
1733
1847
  let responseBodyForLog;
@@ -1907,7 +2021,7 @@ class ProxyServer {
1907
2021
  else if (this.isOpenAIChatSource(sourceType)) {
1908
2022
  requestBody = (0, claude_openai_1.transformClaudeRequestToOpenAIChat)(requestBody, rule.targetModel);
1909
2023
  }
1910
- else if (this.isGeminiSource(sourceType)) {
2024
+ else if (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType)) {
1911
2025
  requestBody = (0, gemini_1.transformClaudeRequestToGemini)(requestBody);
1912
2026
  }
1913
2027
  else {
@@ -1923,7 +2037,7 @@ class ProxyServer {
1923
2037
  else if (this.isClaudeSource(sourceType)) {
1924
2038
  requestBody = (0, claude_openai_1.transformClaudeRequestToOpenAIChat)(requestBody, rule.targetModel);
1925
2039
  }
1926
- else if (this.isGeminiSource(sourceType)) {
2040
+ else if (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType)) {
1927
2041
  requestBody = (0, gemini_1.transformOpenAIChatRequestToGemini)(requestBody);
1928
2042
  }
1929
2043
  else {
@@ -1952,7 +2066,8 @@ class ProxyServer {
1952
2066
  const model = requestBody.model || rule.targetModel || 'gemini-pro';
1953
2067
  upstreamUrl = this.buildGeminiUrl(service.apiUrl, model, streamRequested);
1954
2068
  }
1955
- else if (this.isChatType(sourceType)) {
2069
+ else if (this.isChatType(sourceType) || this.isGeminiChatSource(sourceType)) {
2070
+ // Chat 类型(包括 gemini-chat)直接使用用户配置的完整 URL
1956
2071
  upstreamUrl = service.apiUrl;
1957
2072
  }
1958
2073
  else {
@@ -2235,8 +2350,8 @@ class ProxyServer {
2235
2350
  }));
2236
2351
  return;
2237
2352
  }
2238
- // Gemini -> Claude Code 流式转换
2239
- if (targetType === 'claude-code' && this.isGeminiSource(sourceType)) {
2353
+ // Gemini / Gemini Chat -> Claude Code 流式转换
2354
+ if (targetType === 'claude-code' && (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType))) {
2240
2355
  res.setHeader('Content-Type', 'text/event-stream');
2241
2356
  res.setHeader('Cache-Control', 'no-cache');
2242
2357
  res.setHeader('Connection', 'keep-alive');
@@ -2331,8 +2446,8 @@ class ProxyServer {
2331
2446
  }));
2332
2447
  return;
2333
2448
  }
2334
- // Gemini -> Codex 流式转换
2335
- if (targetType === 'codex' && this.isGeminiSource(sourceType)) {
2449
+ // Gemini / Gemini Chat -> Codex 流式转换
2450
+ if (targetType === 'codex' && (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType))) {
2336
2451
  res.setHeader('Content-Type', 'text/event-stream');
2337
2452
  res.setHeader('Cache-Control', 'no-cache');
2338
2453
  res.setHeader('Connection', 'keep-alive');
@@ -2499,7 +2614,7 @@ class ProxyServer {
2499
2614
  responseBodyForLog = JSON.stringify(converted);
2500
2615
  res.status(response.status).json(converted);
2501
2616
  }
2502
- else if (targetType === 'claude-code' && this.isGeminiSource(sourceType)) {
2617
+ else if (targetType === 'claude-code' && (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType))) {
2503
2618
  const converted = (0, gemini_1.transformGeminiResponseToClaude)(responseData, rule.targetModel);
2504
2619
  usageForLog = (0, gemini_1.extractTokenUsageFromGeminiUsage)(responseData === null || responseData === void 0 ? void 0 : responseData.usageMetadata);
2505
2620
  responseBodyForLog = JSON.stringify(converted);
@@ -2511,7 +2626,7 @@ class ProxyServer {
2511
2626
  responseBodyForLog = JSON.stringify(converted);
2512
2627
  res.status(response.status).json(converted);
2513
2628
  }
2514
- else if (targetType === 'codex' && this.isGeminiSource(sourceType)) {
2629
+ else if (targetType === 'codex' && (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType))) {
2515
2630
  const converted = (0, gemini_1.transformGeminiResponseToOpenAIChat)(responseData, rule.targetModel);
2516
2631
  usageForLog = (0, gemini_1.extractTokenUsageFromGeminiUsage)(responseData === null || responseData === void 0 ? void 0 : responseData.usageMetadata);
2517
2632
  responseBodyForLog = JSON.stringify(converted);
@@ -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
+ }