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 +1 -0
- package/dist/server/fs-database.js +80 -2
- package/dist/server/main.js +23 -0
- package/dist/server/proxy-server.js +128 -13
- package/dist/server/type-migration.js +68 -0
- package/dist/ui/assets/{index-BCdyCT1c.js → index-B_Vn-9wd.js} +8 -7
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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 +
|
|
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 +
|
|
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 })));
|
package/dist/server/main.js
CHANGED
|
@@ -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
|
|
1319
|
+
return sourceType === 'claude-chat' || sourceType === 'claude';
|
|
1215
1320
|
}
|
|
1216
1321
|
isOpenAIChatSource(sourceType) {
|
|
1217
|
-
return sourceType === 'openai-chat' || sourceType === 'openai
|
|
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
|
+
}
|