aicodeswitch 3.0.20 → 3.6.2
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/bin/upgrade.js +29 -5
- package/dist/server/fs-database.js +80 -2
- package/dist/server/main.js +23 -0
- package/dist/server/proxy-server.js +345 -14
- package/dist/server/type-migration.js +68 -0
- package/dist/ui/assets/{index-BCdyCT1c.js → index-BtoE4g4L.js} +8 -7
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
|
@@ -56,6 +56,7 @@ const claude_openai_1 = require("./transformers/claude-openai");
|
|
|
56
56
|
const gemini_1 = require("./transformers/gemini");
|
|
57
57
|
const types_1 = require("../types");
|
|
58
58
|
const mcp_image_handler_1 = require("./mcp-image-handler");
|
|
59
|
+
const type_migration_1 = require("./type-migration");
|
|
59
60
|
const SUPPORTED_TARGETS = ['claude-code', 'codex'];
|
|
60
61
|
class ProxyServer {
|
|
61
62
|
constructor(dbManager, app) {
|
|
@@ -136,6 +137,58 @@ class ProxyServer {
|
|
|
136
137
|
if (!route) {
|
|
137
138
|
return res.status(404).json({ error: 'No matching route found' });
|
|
138
139
|
}
|
|
140
|
+
// 高智商命令检测和会话状态管理
|
|
141
|
+
const highIqCommand = this.detectHighIqCommand(req.body);
|
|
142
|
+
const sessionId = this.defaultExtractSessionId(req, route.targetType);
|
|
143
|
+
if (highIqCommand === 'on') {
|
|
144
|
+
// 检查是否有可用的高智商规则
|
|
145
|
+
const highIqRule = yield this.findHighIqRule(route.id);
|
|
146
|
+
if (highIqRule) {
|
|
147
|
+
console.log('[HIGH-IQ] Command detected: ON');
|
|
148
|
+
// 更新会话状态
|
|
149
|
+
if (sessionId) {
|
|
150
|
+
const session = this.dbManager.getSession(sessionId);
|
|
151
|
+
this.dbManager.upsertSession({
|
|
152
|
+
id: sessionId,
|
|
153
|
+
targetType: route.targetType,
|
|
154
|
+
title: session === null || session === void 0 ? void 0 : session.title,
|
|
155
|
+
firstRequestAt: (session === null || session === void 0 ? void 0 : session.firstRequestAt) || Date.now(),
|
|
156
|
+
lastRequestAt: Date.now(),
|
|
157
|
+
vendorId: session === null || session === void 0 ? void 0 : session.vendorId,
|
|
158
|
+
vendorName: session === null || session === void 0 ? void 0 : session.vendorName,
|
|
159
|
+
serviceId: session === null || session === void 0 ? void 0 : session.serviceId,
|
|
160
|
+
serviceName: session === null || session === void 0 ? void 0 : session.serviceName,
|
|
161
|
+
model: session === null || session === void 0 ? void 0 : session.model,
|
|
162
|
+
totalTokens: (session === null || session === void 0 ? void 0 : session.totalTokens) || 0,
|
|
163
|
+
requestCount: ((session === null || session === void 0 ? void 0 : session.requestCount) || 0) + 1,
|
|
164
|
+
// 新增字段
|
|
165
|
+
highIqMode: true,
|
|
166
|
+
highIqRuleId: highIqRule.id,
|
|
167
|
+
highIqEnabledAt: Date.now()
|
|
168
|
+
});
|
|
169
|
+
console.log(`[HIGH-IQ] Session ${sessionId} enabled with rule ${highIqRule.id}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
console.log('[HIGH-IQ] No available high-iq rule found');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else if (highIqCommand === 'off') {
|
|
177
|
+
console.log('[HIGH-IQ] Command detected: OFF');
|
|
178
|
+
// 关闭会话的高智商模式
|
|
179
|
+
if (sessionId) {
|
|
180
|
+
const session = this.dbManager.getSession(sessionId);
|
|
181
|
+
if (session === null || session === void 0 ? void 0 : session.highIqMode) {
|
|
182
|
+
this.dbManager.upsertSession(Object.assign(Object.assign({}, session), { highIqMode: false, lastRequestAt: Date.now(), requestCount: (session.requestCount || 0) + 1 }));
|
|
183
|
+
console.log(`[HIGH-IQ] Session ${sessionId} disabled`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// 移除命令前缀(在发送给上游API之前)
|
|
188
|
+
if (highIqCommand) {
|
|
189
|
+
req.body = this.removeHighIqCommand(req.body);
|
|
190
|
+
console.log(`[HIGH-IQ] Removed command prefix`);
|
|
191
|
+
}
|
|
139
192
|
// 检查是否启用故障切换
|
|
140
193
|
const enableFailover = ((_a = this.config) === null || _a === void 0 ? void 0 : _a.enableFailover) !== false; // 默认为 true
|
|
141
194
|
if (!enableFailover) {
|
|
@@ -158,6 +211,8 @@ class ProxyServer {
|
|
|
158
211
|
}
|
|
159
212
|
// 尝试每个规则,直到成功或全部失败
|
|
160
213
|
let lastError = null;
|
|
214
|
+
let lastFailedRule = null;
|
|
215
|
+
let lastFailedService = null;
|
|
161
216
|
for (let index = 0; index < allRules.length; index++) {
|
|
162
217
|
const rule = allRules[index];
|
|
163
218
|
const service = this.getServiceById(rule.targetServiceId);
|
|
@@ -181,6 +236,8 @@ class ProxyServer {
|
|
|
181
236
|
catch (error) {
|
|
182
237
|
console.error(`Service ${service.name} failed:`, error.message);
|
|
183
238
|
lastError = error;
|
|
239
|
+
lastFailedRule = rule;
|
|
240
|
+
lastFailedService = service;
|
|
184
241
|
// 检测是否是 timeout 错误
|
|
185
242
|
const isTimeout = error.code === 'ECONNABORTED' ||
|
|
186
243
|
((_b = error.message) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes('timeout')) ||
|
|
@@ -206,6 +263,21 @@ class ProxyServer {
|
|
|
206
263
|
}
|
|
207
264
|
// 所有服务都失败了
|
|
208
265
|
console.error('All services failed');
|
|
266
|
+
// 如果有失败的服务但都在黑名单中,尝试使用最后一个失败的服务(作为 fallback)
|
|
267
|
+
if (lastFailedRule && lastFailedService) {
|
|
268
|
+
console.log(`All services in blacklist, attempting fallback to last failed service: ${lastFailedService.name}`);
|
|
269
|
+
try {
|
|
270
|
+
yield this.proxyRequest(req, res, route, lastFailedRule, lastFailedService, {
|
|
271
|
+
failoverEnabled: false, // Fallback 模式不启用故障切换
|
|
272
|
+
forwardedToServiceName: undefined,
|
|
273
|
+
});
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
catch (fallbackError) {
|
|
277
|
+
console.error(`Fallback to service ${lastFailedService.name} also failed:`, fallbackError.message);
|
|
278
|
+
lastError = fallbackError;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
209
281
|
// 记录日志
|
|
210
282
|
if (((_d = this.config) === null || _d === void 0 ? void 0 : _d.enableLogging) !== false && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
|
|
211
283
|
yield this.dbManager.addLog({
|
|
@@ -322,6 +394,58 @@ class ProxyServer {
|
|
|
322
394
|
if (!route) {
|
|
323
395
|
return res.status(404).json({ error: `No active route found for target type: ${targetType}` });
|
|
324
396
|
}
|
|
397
|
+
// 高智商命令检测和会话状态管理
|
|
398
|
+
const highIqCommand = this.detectHighIqCommand(req.body);
|
|
399
|
+
const sessionId = this.defaultExtractSessionId(req, targetType);
|
|
400
|
+
if (highIqCommand === 'on') {
|
|
401
|
+
// 检查是否有可用的高智商规则
|
|
402
|
+
const highIqRule = yield this.findHighIqRule(route.id);
|
|
403
|
+
if (highIqRule) {
|
|
404
|
+
console.log('[HIGH-IQ] Command detected: ON');
|
|
405
|
+
// 更新会话状态
|
|
406
|
+
if (sessionId) {
|
|
407
|
+
const session = this.dbManager.getSession(sessionId);
|
|
408
|
+
this.dbManager.upsertSession({
|
|
409
|
+
id: sessionId,
|
|
410
|
+
targetType: route.targetType,
|
|
411
|
+
title: session === null || session === void 0 ? void 0 : session.title,
|
|
412
|
+
firstRequestAt: (session === null || session === void 0 ? void 0 : session.firstRequestAt) || Date.now(),
|
|
413
|
+
lastRequestAt: Date.now(),
|
|
414
|
+
vendorId: session === null || session === void 0 ? void 0 : session.vendorId,
|
|
415
|
+
vendorName: session === null || session === void 0 ? void 0 : session.vendorName,
|
|
416
|
+
serviceId: session === null || session === void 0 ? void 0 : session.serviceId,
|
|
417
|
+
serviceName: session === null || session === void 0 ? void 0 : session.serviceName,
|
|
418
|
+
model: session === null || session === void 0 ? void 0 : session.model,
|
|
419
|
+
totalTokens: (session === null || session === void 0 ? void 0 : session.totalTokens) || 0,
|
|
420
|
+
requestCount: ((session === null || session === void 0 ? void 0 : session.requestCount) || 0) + 1,
|
|
421
|
+
// 新增字段
|
|
422
|
+
highIqMode: true,
|
|
423
|
+
highIqRuleId: highIqRule.id,
|
|
424
|
+
highIqEnabledAt: Date.now()
|
|
425
|
+
});
|
|
426
|
+
console.log(`[HIGH-IQ] Session ${sessionId} enabled with rule ${highIqRule.id}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
console.log('[HIGH-IQ] No available high-iq rule found');
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
else if (highIqCommand === 'off') {
|
|
434
|
+
console.log('[HIGH-IQ] Command detected: OFF');
|
|
435
|
+
// 关闭会话的高智商模式
|
|
436
|
+
if (sessionId) {
|
|
437
|
+
const session = this.dbManager.getSession(sessionId);
|
|
438
|
+
if (session === null || session === void 0 ? void 0 : session.highIqMode) {
|
|
439
|
+
this.dbManager.upsertSession(Object.assign(Object.assign({}, session), { highIqMode: false, lastRequestAt: Date.now(), requestCount: (session.requestCount || 0) + 1 }));
|
|
440
|
+
console.log(`[HIGH-IQ] Session ${sessionId} disabled`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// 移除命令前缀(在发送给上游API之前)
|
|
445
|
+
if (highIqCommand) {
|
|
446
|
+
req.body = this.removeHighIqCommand(req.body);
|
|
447
|
+
console.log(`[HIGH-IQ] Removed command prefix`);
|
|
448
|
+
}
|
|
325
449
|
// 检查是否启用故障切换
|
|
326
450
|
const enableFailover = ((_a = this.config) === null || _a === void 0 ? void 0 : _a.enableFailover) !== false; // 默认为 true
|
|
327
451
|
if (!enableFailover) {
|
|
@@ -344,6 +468,8 @@ class ProxyServer {
|
|
|
344
468
|
}
|
|
345
469
|
// 尝试每个规则,直到成功或全部失败
|
|
346
470
|
let lastError = null;
|
|
471
|
+
let lastFailedRule = null;
|
|
472
|
+
let lastFailedService = null;
|
|
347
473
|
for (let index = 0; index < allRules.length; index++) {
|
|
348
474
|
const rule = allRules[index];
|
|
349
475
|
const service = this.getServiceById(rule.targetServiceId);
|
|
@@ -367,6 +493,8 @@ class ProxyServer {
|
|
|
367
493
|
catch (error) {
|
|
368
494
|
console.error(`Service ${service.name} failed:`, error.message);
|
|
369
495
|
lastError = error;
|
|
496
|
+
lastFailedRule = rule;
|
|
497
|
+
lastFailedService = service;
|
|
370
498
|
// 检测是否是 timeout 错误
|
|
371
499
|
const isTimeout = error.code === 'ECONNABORTED' ||
|
|
372
500
|
((_b = error.message) === null || _b === void 0 ? void 0 : _b.toLowerCase().includes('timeout')) ||
|
|
@@ -392,6 +520,21 @@ class ProxyServer {
|
|
|
392
520
|
}
|
|
393
521
|
// 所有服务都失败了
|
|
394
522
|
console.error('All services failed');
|
|
523
|
+
// 如果有失败的服务但都在黑名单中,尝试使用最后一个失败的服务(作为 fallback)
|
|
524
|
+
if (lastFailedRule && lastFailedService) {
|
|
525
|
+
console.log(`All services in blacklist, attempting fallback to last failed service: ${lastFailedService.name}`);
|
|
526
|
+
try {
|
|
527
|
+
yield this.proxyRequest(req, res, route, lastFailedRule, lastFailedService, {
|
|
528
|
+
failoverEnabled: false, // Fallback 模式不启用故障切换
|
|
529
|
+
forwardedToServiceName: undefined,
|
|
530
|
+
});
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
catch (fallbackError) {
|
|
534
|
+
console.error(`Fallback to service ${lastFailedService.name} also failed:`, fallbackError.message);
|
|
535
|
+
lastError = fallbackError;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
395
538
|
// 记录日志
|
|
396
539
|
if (((_d = this.config) === null || _d === void 0 ? void 0 : _d.enableLogging) !== false && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
|
|
397
540
|
yield this.dbManager.addLog({
|
|
@@ -733,6 +876,24 @@ class ProxyServer {
|
|
|
733
876
|
return undefined;
|
|
734
877
|
const body = req.body;
|
|
735
878
|
const requestModel = body === null || body === void 0 ? void 0 : body.model;
|
|
879
|
+
// 新增:检查会话是否在高智商模式中
|
|
880
|
+
const targetType = this.getRouteTargetType(routeId);
|
|
881
|
+
if (targetType) {
|
|
882
|
+
const sessionId = this.defaultExtractSessionId(req, targetType);
|
|
883
|
+
if (sessionId) {
|
|
884
|
+
const session = this.dbManager.getSession(sessionId);
|
|
885
|
+
if ((session === null || session === void 0 ? void 0 : session.highIqMode) && session.highIqRuleId) {
|
|
886
|
+
// 验证规则是否仍然可用
|
|
887
|
+
const highIqRule = yield this.findHighIqRule(routeId);
|
|
888
|
+
if (highIqRule && highIqRule.id === session.highIqRuleId) {
|
|
889
|
+
console.log(`[HIGH-IQ] Session ${sessionId} using high-iq rule ${highIqRule.id}`);
|
|
890
|
+
return highIqRule;
|
|
891
|
+
}
|
|
892
|
+
// 规则不可用,自动关闭高智商模式
|
|
893
|
+
console.log(`[HIGH-IQ] Rule ${session.highIqRuleId} no longer available, auto-disable`);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
736
897
|
// 1. 首先查找 model-mapping 类型的规则,按 sortOrder 降序匹配
|
|
737
898
|
if (requestModel) {
|
|
738
899
|
const modelMappingRules = rules.filter(rule => rule.contentType === 'model-mapping' &&
|
|
@@ -985,6 +1146,10 @@ class ProxyServer {
|
|
|
985
1146
|
type: 'thinking',
|
|
986
1147
|
match: (_req, body) => this.hasThinkingSignal(body),
|
|
987
1148
|
},
|
|
1149
|
+
{
|
|
1150
|
+
type: 'high-iq',
|
|
1151
|
+
match: (_req, body) => this.hasHighIqSignal(body),
|
|
1152
|
+
},
|
|
988
1153
|
{
|
|
989
1154
|
type: 'long-context',
|
|
990
1155
|
match: (_req, body) => this.hasLongContextSignal(body),
|
|
@@ -1049,6 +1214,10 @@ class ProxyServer {
|
|
|
1049
1214
|
bg: 'background',
|
|
1050
1215
|
thinking: 'thinking',
|
|
1051
1216
|
reasoning: 'thinking',
|
|
1217
|
+
'high-iq': 'high-iq',
|
|
1218
|
+
high_iq: 'high-iq',
|
|
1219
|
+
highiq: 'high-iq',
|
|
1220
|
+
smart: 'high-iq',
|
|
1052
1221
|
'long-context': 'long-context',
|
|
1053
1222
|
long_context: 'long-context',
|
|
1054
1223
|
long: 'long-context',
|
|
@@ -1090,6 +1259,157 @@ class ProxyServer {
|
|
|
1090
1259
|
((_a = body === null || body === void 0 ? void 0 : body.reasoning) === null || _a === void 0 ? void 0 : _a.effort) ||
|
|
1091
1260
|
((_b = body === null || body === void 0 ? void 0 : body.reasoning) === null || _b === void 0 ? void 0 : _b.enabled));
|
|
1092
1261
|
}
|
|
1262
|
+
hasHighIqSignal(body) {
|
|
1263
|
+
const messages = body === null || body === void 0 ? void 0 : body.messages;
|
|
1264
|
+
if (!Array.isArray(messages)) {
|
|
1265
|
+
return false;
|
|
1266
|
+
}
|
|
1267
|
+
for (const message of messages) {
|
|
1268
|
+
if ((message === null || message === void 0 ? void 0 : message.role) !== 'user')
|
|
1269
|
+
continue;
|
|
1270
|
+
const content = message === null || message === void 0 ? void 0 : message.content;
|
|
1271
|
+
// 处理字符串类型的 content
|
|
1272
|
+
if (typeof content === 'string') {
|
|
1273
|
+
if (content.trim().startsWith('!!')) {
|
|
1274
|
+
return true;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
// 处理数组类型的 content
|
|
1278
|
+
else if (Array.isArray(content)) {
|
|
1279
|
+
for (const block of content) {
|
|
1280
|
+
if ((block === null || block === void 0 ? void 0 : block.type) === 'text' && typeof block.text === 'string') {
|
|
1281
|
+
if (block.text.trim().startsWith('!!')) {
|
|
1282
|
+
return true;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
return false;
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* 检测用户消息中的高智商命令
|
|
1292
|
+
* @returns 'on' - 开启命令 (!!), 'off' - 关闭命令 (!x), null - 无命令
|
|
1293
|
+
*/
|
|
1294
|
+
detectHighIqCommand(body) {
|
|
1295
|
+
const messages = body === null || body === void 0 ? void 0 : body.messages;
|
|
1296
|
+
if (!Array.isArray(messages)) {
|
|
1297
|
+
return null;
|
|
1298
|
+
}
|
|
1299
|
+
// 只检查最后一条用户消息
|
|
1300
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1301
|
+
const message = messages[i];
|
|
1302
|
+
if ((message === null || message === void 0 ? void 0 : message.role) !== 'user')
|
|
1303
|
+
continue;
|
|
1304
|
+
const content = message === null || message === void 0 ? void 0 : message.content;
|
|
1305
|
+
if (typeof content === 'string') {
|
|
1306
|
+
const trimmed = content.trim();
|
|
1307
|
+
if (trimmed.startsWith('!x')) {
|
|
1308
|
+
return 'off';
|
|
1309
|
+
}
|
|
1310
|
+
if (trimmed.startsWith('!!')) {
|
|
1311
|
+
return 'on';
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
else if (Array.isArray(content)) {
|
|
1315
|
+
for (const block of content) {
|
|
1316
|
+
if ((block === null || block === void 0 ? void 0 : block.type) === 'text' && typeof block.text === 'string') {
|
|
1317
|
+
const trimmed = block.text.trim();
|
|
1318
|
+
if (trimmed.startsWith('!x')) {
|
|
1319
|
+
return 'off';
|
|
1320
|
+
}
|
|
1321
|
+
if (trimmed.startsWith('!!')) {
|
|
1322
|
+
return 'on';
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
break; // 只检查最后一条用户消息
|
|
1328
|
+
}
|
|
1329
|
+
return null;
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* 移除用户消息中的高智商命令前缀
|
|
1333
|
+
*/
|
|
1334
|
+
removeHighIqCommand(body) {
|
|
1335
|
+
if (!(body === null || body === void 0 ? void 0 : body.messages))
|
|
1336
|
+
return body;
|
|
1337
|
+
const processed = JSON.parse(JSON.stringify(body));
|
|
1338
|
+
for (let i = processed.messages.length - 1; i >= 0; i--) {
|
|
1339
|
+
const message = processed.messages[i];
|
|
1340
|
+
if ((message === null || message === void 0 ? void 0 : message.role) !== 'user')
|
|
1341
|
+
continue;
|
|
1342
|
+
const content = message === null || message === void 0 ? void 0 : message.content;
|
|
1343
|
+
if (typeof content === 'string') {
|
|
1344
|
+
const trimmed = content.trim();
|
|
1345
|
+
if (trimmed.startsWith('!x')) {
|
|
1346
|
+
message.content = trimmed.replace(/^!x\s*/, '').trim();
|
|
1347
|
+
}
|
|
1348
|
+
else if (trimmed.startsWith('!!')) {
|
|
1349
|
+
message.content = trimmed.replace(/^!!\s*/, '').trim();
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
else if (Array.isArray(content)) {
|
|
1353
|
+
for (const block of content) {
|
|
1354
|
+
if ((block === null || block === void 0 ? void 0 : block.type) === 'text' && typeof block.text === 'string') {
|
|
1355
|
+
const trimmed = block.text.trim();
|
|
1356
|
+
if (trimmed.startsWith('!x')) {
|
|
1357
|
+
block.text = trimmed.replace(/^!x\s*/, '').trim();
|
|
1358
|
+
}
|
|
1359
|
+
else if (trimmed.startsWith('!!')) {
|
|
1360
|
+
block.text = trimmed.replace(/^!!\s*/, '').trim();
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
break; // 只处理最后一条用户消息
|
|
1366
|
+
}
|
|
1367
|
+
return processed;
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* 查找可用的高智商规则
|
|
1371
|
+
*/
|
|
1372
|
+
findHighIqRule(routeId) {
|
|
1373
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1374
|
+
const rules = this.getRulesByRouteId(routeId);
|
|
1375
|
+
if (!rules || rules.length === 0)
|
|
1376
|
+
return undefined;
|
|
1377
|
+
const highIqRules = rules.filter(rule => rule.contentType === 'high-iq' && !rule.isDisabled);
|
|
1378
|
+
// 过滤黑名单和限制
|
|
1379
|
+
for (const rule of highIqRules) {
|
|
1380
|
+
const isBlacklisted = yield this.dbManager.isServiceBlacklisted(rule.targetServiceId, routeId, 'high-iq');
|
|
1381
|
+
if (isBlacklisted)
|
|
1382
|
+
continue;
|
|
1383
|
+
// 检查并重置到期的规则
|
|
1384
|
+
this.dbManager.checkAndResetRuleIfNeeded(rule.id);
|
|
1385
|
+
this.dbManager.checkAndResetRequestCountIfNeeded(rule.id);
|
|
1386
|
+
// 检查token限制
|
|
1387
|
+
if (rule.tokenLimit && rule.totalTokensUsed !== undefined &&
|
|
1388
|
+
rule.totalTokensUsed >= rule.tokenLimit * 1000) {
|
|
1389
|
+
continue;
|
|
1390
|
+
}
|
|
1391
|
+
// 检查请求次数限制
|
|
1392
|
+
if (rule.requestCountLimit && rule.totalRequestsUsed !== undefined &&
|
|
1393
|
+
rule.totalRequestsUsed >= rule.requestCountLimit) {
|
|
1394
|
+
continue;
|
|
1395
|
+
}
|
|
1396
|
+
// 检查频率限制
|
|
1397
|
+
if (this.isFrequencyLimitExceeded(rule)) {
|
|
1398
|
+
continue;
|
|
1399
|
+
}
|
|
1400
|
+
return rule;
|
|
1401
|
+
}
|
|
1402
|
+
return undefined;
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* 获取路由的目标类型
|
|
1407
|
+
*/
|
|
1408
|
+
getRouteTargetType(routeId) {
|
|
1409
|
+
const routes = this.dbManager.getRoutes();
|
|
1410
|
+
const route = routes.find(r => r.id === routeId);
|
|
1411
|
+
return (route === null || route === void 0 ? void 0 : route.targetType) || null;
|
|
1412
|
+
}
|
|
1093
1413
|
hasBackgroundSignal(body) {
|
|
1094
1414
|
var _a, _b, _c;
|
|
1095
1415
|
// 检测 count tokens 请求:messages 只有一条,role 为 "user",content 为 "count"
|
|
@@ -1211,15 +1531,23 @@ class ProxyServer {
|
|
|
1211
1531
|
}
|
|
1212
1532
|
/** 判断是否为 Claude 相关类型(使用 x-api-key 认证) */
|
|
1213
1533
|
isClaudeSource(sourceType) {
|
|
1214
|
-
|
|
1534
|
+
// 向下兼容:支持旧类型 'claude-code'
|
|
1535
|
+
const normalized = sourceType === 'claude-code' ? 'claude' : sourceType;
|
|
1536
|
+
return normalized === 'claude-chat' || normalized === 'claude';
|
|
1215
1537
|
}
|
|
1216
1538
|
isOpenAIChatSource(sourceType) {
|
|
1217
|
-
|
|
1539
|
+
// 向下兼容:支持旧类型 'openai-responses'
|
|
1540
|
+
const normalized = sourceType === 'openai-responses' ? 'openai' : sourceType;
|
|
1541
|
+
return normalized === 'openai-chat' || normalized === 'openai' || normalized === 'deepseek-reasoning-chat';
|
|
1218
1542
|
}
|
|
1219
1543
|
/** 判断是否为 Gemini 类型 */
|
|
1220
1544
|
isGeminiSource(sourceType) {
|
|
1221
1545
|
return sourceType === 'gemini';
|
|
1222
1546
|
}
|
|
1547
|
+
/** 判断是否为 Gemini Chat 类型 */
|
|
1548
|
+
isGeminiChatSource(sourceType) {
|
|
1549
|
+
return sourceType === 'gemini-chat';
|
|
1550
|
+
}
|
|
1223
1551
|
isChatType(sourceType) {
|
|
1224
1552
|
return sourceType.endsWith('-chat') || sourceType === 'gemini';
|
|
1225
1553
|
}
|
|
@@ -1341,8 +1669,8 @@ class ProxyServer {
|
|
|
1341
1669
|
// 向下兼容:检测旧数据的 'auto' 值
|
|
1342
1670
|
// TODO: 删除
|
|
1343
1671
|
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))) {
|
|
1672
|
+
// 使用 x-goog-api-key 认证(适用于 Google Gemini API 和 Gemini Chat)
|
|
1673
|
+
if (authType === types_1.AuthType.G_API_KEY || (isAuto && (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType)))) {
|
|
1346
1674
|
headers['x-goog-api-key'] = service.apiKey;
|
|
1347
1675
|
}
|
|
1348
1676
|
// 使用 x-api-key 认证(适用于 claude-chat, claude-code 及某些需要 x-api-key 的 openai-chat 兼容 API)
|
|
@@ -1604,7 +1932,9 @@ class ProxyServer {
|
|
|
1604
1932
|
var _a, _b, _c, _d, _e;
|
|
1605
1933
|
res.locals.skipLog = true;
|
|
1606
1934
|
const startTime = Date.now();
|
|
1607
|
-
const
|
|
1935
|
+
const rawSourceType = service.sourceType || 'openai-chat';
|
|
1936
|
+
// 标准化 sourceType,将旧类型转换为新类型(向下兼容)
|
|
1937
|
+
const sourceType = (0, type_migration_1.normalizeSourceType)(rawSourceType);
|
|
1608
1938
|
const targetType = route.targetType;
|
|
1609
1939
|
const failoverEnabled = (options === null || options === void 0 ? void 0 : options.failoverEnabled) === true;
|
|
1610
1940
|
const forwardedToServiceName = options === null || options === void 0 ? void 0 : options.forwardedToServiceName;
|
|
@@ -1907,7 +2237,7 @@ class ProxyServer {
|
|
|
1907
2237
|
else if (this.isOpenAIChatSource(sourceType)) {
|
|
1908
2238
|
requestBody = (0, claude_openai_1.transformClaudeRequestToOpenAIChat)(requestBody, rule.targetModel);
|
|
1909
2239
|
}
|
|
1910
|
-
else if (this.isGeminiSource(sourceType)) {
|
|
2240
|
+
else if (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType)) {
|
|
1911
2241
|
requestBody = (0, gemini_1.transformClaudeRequestToGemini)(requestBody);
|
|
1912
2242
|
}
|
|
1913
2243
|
else {
|
|
@@ -1923,7 +2253,7 @@ class ProxyServer {
|
|
|
1923
2253
|
else if (this.isClaudeSource(sourceType)) {
|
|
1924
2254
|
requestBody = (0, claude_openai_1.transformClaudeRequestToOpenAIChat)(requestBody, rule.targetModel);
|
|
1925
2255
|
}
|
|
1926
|
-
else if (this.isGeminiSource(sourceType)) {
|
|
2256
|
+
else if (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType)) {
|
|
1927
2257
|
requestBody = (0, gemini_1.transformOpenAIChatRequestToGemini)(requestBody);
|
|
1928
2258
|
}
|
|
1929
2259
|
else {
|
|
@@ -1952,7 +2282,8 @@ class ProxyServer {
|
|
|
1952
2282
|
const model = requestBody.model || rule.targetModel || 'gemini-pro';
|
|
1953
2283
|
upstreamUrl = this.buildGeminiUrl(service.apiUrl, model, streamRequested);
|
|
1954
2284
|
}
|
|
1955
|
-
else if (this.isChatType(sourceType)) {
|
|
2285
|
+
else if (this.isChatType(sourceType) || this.isGeminiChatSource(sourceType)) {
|
|
2286
|
+
// Chat 类型(包括 gemini-chat)直接使用用户配置的完整 URL
|
|
1956
2287
|
upstreamUrl = service.apiUrl;
|
|
1957
2288
|
}
|
|
1958
2289
|
else {
|
|
@@ -2235,8 +2566,8 @@ class ProxyServer {
|
|
|
2235
2566
|
}));
|
|
2236
2567
|
return;
|
|
2237
2568
|
}
|
|
2238
|
-
// Gemini -> Claude Code 流式转换
|
|
2239
|
-
if (targetType === 'claude-code' && this.isGeminiSource(sourceType)) {
|
|
2569
|
+
// Gemini / Gemini Chat -> Claude Code 流式转换
|
|
2570
|
+
if (targetType === 'claude-code' && (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType))) {
|
|
2240
2571
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
2241
2572
|
res.setHeader('Cache-Control', 'no-cache');
|
|
2242
2573
|
res.setHeader('Connection', 'keep-alive');
|
|
@@ -2331,8 +2662,8 @@ class ProxyServer {
|
|
|
2331
2662
|
}));
|
|
2332
2663
|
return;
|
|
2333
2664
|
}
|
|
2334
|
-
// Gemini -> Codex 流式转换
|
|
2335
|
-
if (targetType === 'codex' && this.isGeminiSource(sourceType)) {
|
|
2665
|
+
// Gemini / Gemini Chat -> Codex 流式转换
|
|
2666
|
+
if (targetType === 'codex' && (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType))) {
|
|
2336
2667
|
res.setHeader('Content-Type', 'text/event-stream');
|
|
2337
2668
|
res.setHeader('Cache-Control', 'no-cache');
|
|
2338
2669
|
res.setHeader('Connection', 'keep-alive');
|
|
@@ -2499,7 +2830,7 @@ class ProxyServer {
|
|
|
2499
2830
|
responseBodyForLog = JSON.stringify(converted);
|
|
2500
2831
|
res.status(response.status).json(converted);
|
|
2501
2832
|
}
|
|
2502
|
-
else if (targetType === 'claude-code' && this.isGeminiSource(sourceType)) {
|
|
2833
|
+
else if (targetType === 'claude-code' && (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType))) {
|
|
2503
2834
|
const converted = (0, gemini_1.transformGeminiResponseToClaude)(responseData, rule.targetModel);
|
|
2504
2835
|
usageForLog = (0, gemini_1.extractTokenUsageFromGeminiUsage)(responseData === null || responseData === void 0 ? void 0 : responseData.usageMetadata);
|
|
2505
2836
|
responseBodyForLog = JSON.stringify(converted);
|
|
@@ -2511,7 +2842,7 @@ class ProxyServer {
|
|
|
2511
2842
|
responseBodyForLog = JSON.stringify(converted);
|
|
2512
2843
|
res.status(response.status).json(converted);
|
|
2513
2844
|
}
|
|
2514
|
-
else if (targetType === 'codex' && this.isGeminiSource(sourceType)) {
|
|
2845
|
+
else if (targetType === 'codex' && (this.isGeminiSource(sourceType) || this.isGeminiChatSource(sourceType))) {
|
|
2515
2846
|
const converted = (0, gemini_1.transformGeminiResponseToOpenAIChat)(responseData, rule.targetModel);
|
|
2516
2847
|
usageForLog = (0, gemini_1.extractTokenUsageFromGeminiUsage)(responseData === null || responseData === void 0 ? void 0 : responseData.usageMetadata);
|
|
2517
2848
|
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
|
+
}
|