aicodeswitch 3.6.3 → 3.9.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/dist/server/database.js +1 -1
- package/dist/server/fs-database.js +20 -6
- package/dist/server/main.js +67 -2
- package/dist/server/original-config-reader.js +181 -0
- package/dist/server/proxy-server.js +377 -228
- package/dist/ui/assets/index-COkJEguF.js +511 -0
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/dist/ui/assets/index-Y_qNeXCt.js +0 -511
|
@@ -57,6 +57,7 @@ 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
59
|
const type_migration_1 = require("./type-migration");
|
|
60
|
+
const original_config_reader_1 = require("./original-config-reader");
|
|
60
61
|
const SUPPORTED_TARGETS = ['claude-code', 'codex'];
|
|
61
62
|
class ProxyServer {
|
|
62
63
|
constructor(dbManager, app) {
|
|
@@ -135,65 +136,21 @@ class ProxyServer {
|
|
|
135
136
|
try {
|
|
136
137
|
const route = this.findMatchingRoute(req);
|
|
137
138
|
if (!route) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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');
|
|
139
|
+
// 没有找到激活的路由,尝试使用原始配置
|
|
140
|
+
const fallbackResult = yield this.handleFallbackToOriginalConfig(req, res);
|
|
141
|
+
if (fallbackResult) {
|
|
142
|
+
return; // 成功使用原始配置处理请求
|
|
174
143
|
}
|
|
144
|
+
// 如果原始配置也不可用,返回错误
|
|
145
|
+
return res.status(404).json({ error: 'No matching route found and no original config available' });
|
|
175
146
|
}
|
|
176
|
-
|
|
177
|
-
|
|
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
|
-
}
|
|
147
|
+
// 高智商请求判定:从消息结构推断是否启用,不再使用 !x 显式关闭语法
|
|
148
|
+
const forcedContentType = yield this.prepareHighIqRouting(req, route, route.targetType);
|
|
192
149
|
// 检查是否启用故障切换
|
|
193
150
|
const enableFailover = ((_a = this.config) === null || _a === void 0 ? void 0 : _a.enableFailover) !== false; // 默认为 true
|
|
194
151
|
if (!enableFailover) {
|
|
195
152
|
// 故障切换已禁用,使用传统的单一规则匹配
|
|
196
|
-
const rule = yield this.findMatchingRule(route.id, req);
|
|
153
|
+
const rule = yield this.findMatchingRule(route.id, req, forcedContentType);
|
|
197
154
|
if (!rule) {
|
|
198
155
|
return res.status(404).json({ error: 'No matching rule found' });
|
|
199
156
|
}
|
|
@@ -205,7 +162,7 @@ class ProxyServer {
|
|
|
205
162
|
return;
|
|
206
163
|
}
|
|
207
164
|
// 启用故障切换:获取所有候选规则
|
|
208
|
-
const allRules = this.getAllMatchingRules(route.id, req);
|
|
165
|
+
const allRules = this.getAllMatchingRules(route.id, req, forcedContentType);
|
|
209
166
|
if (allRules.length === 0) {
|
|
210
167
|
return res.status(404).json({ error: 'No matching rule found' });
|
|
211
168
|
}
|
|
@@ -394,63 +351,13 @@ class ProxyServer {
|
|
|
394
351
|
if (!route) {
|
|
395
352
|
return res.status(404).json({ error: `No active route found for target type: ${targetType}` });
|
|
396
353
|
}
|
|
397
|
-
//
|
|
398
|
-
const
|
|
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
|
-
}
|
|
354
|
+
// 高智商请求判定:从消息结构推断是否启用,不再使用 !x 显式关闭语法
|
|
355
|
+
const forcedContentType = yield this.prepareHighIqRouting(req, route, targetType);
|
|
449
356
|
// 检查是否启用故障切换
|
|
450
357
|
const enableFailover = ((_a = this.config) === null || _a === void 0 ? void 0 : _a.enableFailover) !== false; // 默认为 true
|
|
451
358
|
if (!enableFailover) {
|
|
452
359
|
// 故障切换已禁用,使用传统的单一规则匹配
|
|
453
|
-
const rule = yield this.findMatchingRule(route.id, req);
|
|
360
|
+
const rule = yield this.findMatchingRule(route.id, req, forcedContentType);
|
|
454
361
|
if (!rule) {
|
|
455
362
|
return res.status(404).json({ error: 'No matching rule found' });
|
|
456
363
|
}
|
|
@@ -462,7 +369,7 @@ class ProxyServer {
|
|
|
462
369
|
return;
|
|
463
370
|
}
|
|
464
371
|
// 启用故障切换:获取所有候选规则
|
|
465
|
-
const allRules = this.getAllMatchingRules(route.id, req);
|
|
372
|
+
const allRules = this.getAllMatchingRules(route.id, req, forcedContentType);
|
|
466
373
|
if (allRules.length === 0) {
|
|
467
374
|
return res.status(404).json({ error: 'No matching rule found' });
|
|
468
375
|
}
|
|
@@ -646,6 +553,110 @@ class ProxyServer {
|
|
|
646
553
|
const activeRoutes = this.getActiveRoutes();
|
|
647
554
|
return activeRoutes.find(route => route.targetType === targetType && route.isActive);
|
|
648
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* 当没有激活的路由时,fallback 到原始配置
|
|
558
|
+
* @returns true 表示成功处理,false 表示无法处理
|
|
559
|
+
*/
|
|
560
|
+
handleFallbackToOriginalConfig(req, res) {
|
|
561
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
562
|
+
// 确定目标类型
|
|
563
|
+
let targetType;
|
|
564
|
+
if (req.path.startsWith('/claude-code/')) {
|
|
565
|
+
targetType = 'claude-code';
|
|
566
|
+
}
|
|
567
|
+
else if (req.path.startsWith('/codex/')) {
|
|
568
|
+
targetType = 'codex';
|
|
569
|
+
}
|
|
570
|
+
if (!targetType) {
|
|
571
|
+
return false;
|
|
572
|
+
}
|
|
573
|
+
// 读取原始配置
|
|
574
|
+
const originalConfig = (0, original_config_reader_1.readOriginalConfig)(targetType);
|
|
575
|
+
if (!originalConfig) {
|
|
576
|
+
console.log(`[FALLBACK] No original config available for ${targetType}`);
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
// 检查原始配置的 API URL 是否指向本系统(避免死循环)
|
|
580
|
+
if (this.isLocalProxyUrl(originalConfig.apiUrl, targetType)) {
|
|
581
|
+
console.error(`[FALLBACK] Original config points to local proxy, rejecting to avoid loop: ${originalConfig.apiUrl}`);
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
584
|
+
console.log(`[FALLBACK] Using original config for ${targetType}: ${originalConfig.apiUrl}`);
|
|
585
|
+
try {
|
|
586
|
+
// 创建临时的路由对象(用于传递给 proxyRequest)
|
|
587
|
+
const tempRoute = {
|
|
588
|
+
id: 'fallback-route',
|
|
589
|
+
name: 'Fallback to Original Config',
|
|
590
|
+
targetType: targetType,
|
|
591
|
+
isActive: true,
|
|
592
|
+
createdAt: Date.now(),
|
|
593
|
+
updatedAt: Date.now(),
|
|
594
|
+
};
|
|
595
|
+
// 创建临时的规则对象
|
|
596
|
+
const tempRule = {
|
|
597
|
+
id: 'fallback-rule',
|
|
598
|
+
routeId: 'fallback-route',
|
|
599
|
+
contentType: 'default',
|
|
600
|
+
targetServiceId: 'fallback-service',
|
|
601
|
+
createdAt: Date.now(),
|
|
602
|
+
updatedAt: Date.now(),
|
|
603
|
+
};
|
|
604
|
+
// 创建临时的服务对象
|
|
605
|
+
const tempService = {
|
|
606
|
+
id: 'fallback-service',
|
|
607
|
+
name: 'Original Config',
|
|
608
|
+
apiUrl: originalConfig.apiUrl,
|
|
609
|
+
apiKey: originalConfig.apiKey,
|
|
610
|
+
authType: originalConfig.authType,
|
|
611
|
+
sourceType: originalConfig.sourceType || (targetType === 'claude-code' ? 'claude' : 'openai'),
|
|
612
|
+
createdAt: Date.now(),
|
|
613
|
+
updatedAt: Date.now(),
|
|
614
|
+
};
|
|
615
|
+
// 调用 proxyRequest 处理请求,并标记使用原始配置
|
|
616
|
+
yield this.proxyRequest(req, res, tempRoute, tempRule, tempService, {
|
|
617
|
+
useOriginalConfig: true,
|
|
618
|
+
});
|
|
619
|
+
return true;
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
console.error('[FALLBACK] Failed to use original config:', error);
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* 检查 API URL 是否指向本系统的代理服务
|
|
629
|
+
* 用于避免 fallback 时的死循环
|
|
630
|
+
*/
|
|
631
|
+
isLocalProxyUrl(apiUrl, targetType) {
|
|
632
|
+
try {
|
|
633
|
+
const url = new URL(apiUrl);
|
|
634
|
+
// 检查是否是 localhost 或 127.0.0.1
|
|
635
|
+
const isLocalhost = url.hostname === 'localhost' ||
|
|
636
|
+
url.hostname === '127.0.0.1' ||
|
|
637
|
+
url.hostname === '::1' ||
|
|
638
|
+
url.hostname === '0.0.0.0';
|
|
639
|
+
if (!isLocalhost) {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
// 检查端口是否是本系统的端口(默认 4567)
|
|
643
|
+
const serverPort = process.env.PORT ? parseInt(process.env.PORT, 10) : 4567;
|
|
644
|
+
const urlPort = url.port ? parseInt(url.port, 10) : (url.protocol === 'https:' ? 443 : 80);
|
|
645
|
+
const isSamePort = urlPort === serverPort;
|
|
646
|
+
if (!isSamePort) {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
// 检查路径是否包含本系统的代理路径
|
|
650
|
+
const proxyPath = `/${targetType}`;
|
|
651
|
+
const hasProxyPath = url.pathname.startsWith(proxyPath) ||
|
|
652
|
+
url.pathname === proxyPath;
|
|
653
|
+
return hasProxyPath;
|
|
654
|
+
}
|
|
655
|
+
catch (error) {
|
|
656
|
+
// URL 解析失败,认为不是本地代理 URL
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
649
660
|
findRouteByTargetType(targetType) {
|
|
650
661
|
const activeRoutes = this.getActiveRoutes();
|
|
651
662
|
return activeRoutes.find(route => route.targetType === targetType && route.isActive);
|
|
@@ -865,7 +876,7 @@ class ProxyServer {
|
|
|
865
876
|
}
|
|
866
877
|
return service;
|
|
867
878
|
}
|
|
868
|
-
findMatchingRule(routeId, req) {
|
|
879
|
+
findMatchingRule(routeId, req, forcedContentType) {
|
|
869
880
|
return __awaiter(this, void 0, void 0, function* () {
|
|
870
881
|
const rules = this.getRulesByRouteId(routeId);
|
|
871
882
|
if (!rules || rules.length === 0)
|
|
@@ -876,27 +887,32 @@ class ProxyServer {
|
|
|
876
887
|
return undefined;
|
|
877
888
|
const body = req.body;
|
|
878
889
|
const requestModel = body === null || body === void 0 ? void 0 : body.model;
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
if (
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
const
|
|
885
|
-
if (
|
|
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`);
|
|
890
|
+
const contentType = forcedContentType || this.determineContentType(req);
|
|
891
|
+
// 高智商规则优先于 model-mapping,确保 !!/推断命中时不会被模型映射覆盖
|
|
892
|
+
if (contentType === 'high-iq') {
|
|
893
|
+
const highIqRules = enabledRules.filter(rule => rule.contentType === 'high-iq');
|
|
894
|
+
for (const rule of highIqRules) {
|
|
895
|
+
const isBlacklisted = yield this.dbManager.isServiceBlacklisted(rule.targetServiceId, routeId, rule.contentType);
|
|
896
|
+
if (isBlacklisted) {
|
|
897
|
+
continue;
|
|
894
898
|
}
|
|
899
|
+
this.dbManager.checkAndResetRuleIfNeeded(rule.id);
|
|
900
|
+
this.dbManager.checkAndResetRequestCountIfNeeded(rule.id);
|
|
901
|
+
if (rule.tokenLimit && rule.totalTokensUsed !== undefined && rule.totalTokensUsed >= rule.tokenLimit * 1000) {
|
|
902
|
+
continue;
|
|
903
|
+
}
|
|
904
|
+
if (rule.requestCountLimit && rule.totalRequestsUsed !== undefined && rule.totalRequestsUsed >= rule.requestCountLimit) {
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
if (this.isFrequencyLimitExceeded(rule)) {
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
return rule;
|
|
895
911
|
}
|
|
896
912
|
}
|
|
897
913
|
// 1. 首先查找 model-mapping 类型的规则,按 sortOrder 降序匹配
|
|
898
914
|
if (requestModel) {
|
|
899
|
-
const modelMappingRules =
|
|
915
|
+
const modelMappingRules = enabledRules.filter(rule => rule.contentType === 'model-mapping' &&
|
|
900
916
|
rule.replacedModel &&
|
|
901
917
|
requestModel.includes(rule.replacedModel));
|
|
902
918
|
// 过滤黑名单和token限制
|
|
@@ -924,8 +940,7 @@ class ProxyServer {
|
|
|
924
940
|
}
|
|
925
941
|
}
|
|
926
942
|
// 2. 查找其他内容类型的规则
|
|
927
|
-
const
|
|
928
|
-
const contentTypeRules = rules.filter(rule => rule.contentType === contentType);
|
|
943
|
+
const contentTypeRules = enabledRules.filter(rule => rule.contentType === contentType);
|
|
929
944
|
// 过滤黑名单和token限制
|
|
930
945
|
for (const rule of contentTypeRules) {
|
|
931
946
|
const isBlacklisted = yield this.dbManager.isServiceBlacklisted(rule.targetServiceId, routeId, contentType);
|
|
@@ -950,7 +965,7 @@ class ProxyServer {
|
|
|
950
965
|
return rule;
|
|
951
966
|
}
|
|
952
967
|
// 3. 最后返回 default 规则
|
|
953
|
-
const defaultRules =
|
|
968
|
+
const defaultRules = enabledRules.filter(rule => rule.contentType === 'default');
|
|
954
969
|
// 过滤黑名单和token限制
|
|
955
970
|
for (const rule of defaultRules) {
|
|
956
971
|
const isBlacklisted = yield this.dbManager.isServiceBlacklisted(rule.targetServiceId, routeId, 'default');
|
|
@@ -977,7 +992,7 @@ class ProxyServer {
|
|
|
977
992
|
return undefined;
|
|
978
993
|
});
|
|
979
994
|
}
|
|
980
|
-
getAllMatchingRules(routeId, req) {
|
|
995
|
+
getAllMatchingRules(routeId, req, forcedContentType) {
|
|
981
996
|
const rules = this.getRulesByRouteId(routeId);
|
|
982
997
|
if (!rules || rules.length === 0)
|
|
983
998
|
return [];
|
|
@@ -988,20 +1003,21 @@ class ProxyServer {
|
|
|
988
1003
|
const body = req.body;
|
|
989
1004
|
const requestModel = body === null || body === void 0 ? void 0 : body.model;
|
|
990
1005
|
const candidates = [];
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1006
|
+
const contentType = forcedContentType || this.determineContentType(req);
|
|
1007
|
+
const prioritizeContentType = contentType === 'high-iq';
|
|
1008
|
+
const modelMappingRules = requestModel
|
|
1009
|
+
? enabledRules.filter(rule => rule.contentType === 'model-mapping' &&
|
|
994
1010
|
rule.replacedModel &&
|
|
995
|
-
requestModel.includes(rule.replacedModel))
|
|
996
|
-
|
|
997
|
-
}
|
|
998
|
-
// 2. Content type specific rules
|
|
999
|
-
const contentType = this.determineContentType(req);
|
|
1011
|
+
requestModel.includes(rule.replacedModel))
|
|
1012
|
+
: [];
|
|
1000
1013
|
const contentTypeRules = enabledRules.filter(rule => rule.contentType === contentType);
|
|
1001
|
-
candidates.push(...contentTypeRules);
|
|
1002
|
-
// 3. Default rules
|
|
1003
1014
|
const defaultRules = enabledRules.filter(rule => rule.contentType === 'default');
|
|
1004
|
-
|
|
1015
|
+
if (prioritizeContentType) {
|
|
1016
|
+
candidates.push(...contentTypeRules, ...modelMappingRules, ...defaultRules);
|
|
1017
|
+
}
|
|
1018
|
+
else {
|
|
1019
|
+
candidates.push(...modelMappingRules, ...contentTypeRules, ...defaultRules);
|
|
1020
|
+
}
|
|
1005
1021
|
// 4. 检查并重置到期的规则
|
|
1006
1022
|
candidates.forEach(rule => {
|
|
1007
1023
|
this.dbManager.checkAndResetRuleIfNeeded(rule.id);
|
|
@@ -1260,112 +1276,245 @@ class ProxyServer {
|
|
|
1260
1276
|
((_b = body === null || body === void 0 ? void 0 : body.reasoning) === null || _b === void 0 ? void 0 : _b.enabled));
|
|
1261
1277
|
}
|
|
1262
1278
|
hasHighIqSignal(body) {
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
for (
|
|
1268
|
-
|
|
1279
|
+
return this.inferHighIqRouting(body, false).shouldUseHighIq;
|
|
1280
|
+
}
|
|
1281
|
+
inferHighIqRouting(body, previousMode) {
|
|
1282
|
+
const messages = this.extractConversationMessages(body);
|
|
1283
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1284
|
+
const message = messages[i];
|
|
1285
|
+
if ((message === null || message === void 0 ? void 0 : message.role) !== 'user') {
|
|
1269
1286
|
continue;
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
if (
|
|
1273
|
-
|
|
1274
|
-
|
|
1287
|
+
}
|
|
1288
|
+
const signal = this.analyzeUserMessageForHighIq(message);
|
|
1289
|
+
if (!signal.hasHumanText) {
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
if (signal.hasHighIqPrefix) {
|
|
1293
|
+
return {
|
|
1294
|
+
shouldUseHighIq: true,
|
|
1295
|
+
shouldStripPrefix: true,
|
|
1296
|
+
decisionSource: 'human',
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
return {
|
|
1300
|
+
shouldUseHighIq: false,
|
|
1301
|
+
shouldStripPrefix: false,
|
|
1302
|
+
decisionSource: 'human',
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
if (previousMode) {
|
|
1306
|
+
return {
|
|
1307
|
+
shouldUseHighIq: true,
|
|
1308
|
+
shouldStripPrefix: false,
|
|
1309
|
+
decisionSource: 'fallback',
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
return {
|
|
1313
|
+
shouldUseHighIq: false,
|
|
1314
|
+
shouldStripPrefix: false,
|
|
1315
|
+
decisionSource: 'none',
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
prepareHighIqRouting(req, route, targetType) {
|
|
1319
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1320
|
+
const sessionId = this.defaultExtractSessionId(req, targetType);
|
|
1321
|
+
const session = sessionId ? this.dbManager.getSession(sessionId) : null;
|
|
1322
|
+
const previousMode = (session === null || session === void 0 ? void 0 : session.highIqMode) === true;
|
|
1323
|
+
const inference = this.inferHighIqRouting(req.body, previousMode);
|
|
1324
|
+
if (inference.shouldStripPrefix) {
|
|
1325
|
+
req.body = this.removeHighIqPrefix(req.body);
|
|
1326
|
+
console.log('[HIGH-IQ] Removed "!!" prefix from user message');
|
|
1327
|
+
}
|
|
1328
|
+
if (!inference.shouldUseHighIq) {
|
|
1329
|
+
if (sessionId && (session === null || session === void 0 ? void 0 : session.highIqMode) && inference.decisionSource === 'human') {
|
|
1330
|
+
yield this.dbManager.updateSession(sessionId, {
|
|
1331
|
+
highIqMode: false,
|
|
1332
|
+
highIqRuleId: undefined,
|
|
1333
|
+
lastRequestAt: Date.now(),
|
|
1334
|
+
});
|
|
1335
|
+
console.log(`[HIGH-IQ] Session ${sessionId} auto-disabled by latest human message`);
|
|
1275
1336
|
}
|
|
1337
|
+
return undefined;
|
|
1276
1338
|
}
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
}
|
|
1339
|
+
const highIqRule = yield this.findHighIqRule(route.id);
|
|
1340
|
+
if (!highIqRule) {
|
|
1341
|
+
if (sessionId && (session === null || session === void 0 ? void 0 : session.highIqMode)) {
|
|
1342
|
+
yield this.dbManager.updateSession(sessionId, {
|
|
1343
|
+
highIqMode: false,
|
|
1344
|
+
highIqRuleId: undefined,
|
|
1345
|
+
lastRequestAt: Date.now(),
|
|
1346
|
+
});
|
|
1285
1347
|
}
|
|
1348
|
+
console.log('[HIGH-IQ] Inferred high-iq request but no available high-iq rule found');
|
|
1349
|
+
return undefined;
|
|
1286
1350
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1351
|
+
if (sessionId && (!(session === null || session === void 0 ? void 0 : session.highIqMode) || session.highIqRuleId !== highIqRule.id)) {
|
|
1352
|
+
yield this.dbManager.updateSession(sessionId, {
|
|
1353
|
+
highIqMode: true,
|
|
1354
|
+
highIqRuleId: highIqRule.id,
|
|
1355
|
+
highIqEnabledAt: (session === null || session === void 0 ? void 0 : session.highIqEnabledAt) || Date.now(),
|
|
1356
|
+
lastRequestAt: Date.now(),
|
|
1357
|
+
});
|
|
1358
|
+
console.log(`[HIGH-IQ] Session ${sessionId} inferred ON with rule ${highIqRule.id}`);
|
|
1359
|
+
}
|
|
1360
|
+
return 'high-iq';
|
|
1361
|
+
});
|
|
1289
1362
|
}
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
*/
|
|
1294
|
-
detectHighIqCommand(body) {
|
|
1295
|
-
const messages = body === null || body === void 0 ? void 0 : body.messages;
|
|
1296
|
-
if (!Array.isArray(messages)) {
|
|
1297
|
-
return null;
|
|
1363
|
+
extractConversationMessages(body) {
|
|
1364
|
+
if (!body || typeof body !== 'object') {
|
|
1365
|
+
return [];
|
|
1298
1366
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1367
|
+
if (Array.isArray(body.messages)) {
|
|
1368
|
+
return body.messages;
|
|
1369
|
+
}
|
|
1370
|
+
if (typeof body.input === 'string') {
|
|
1371
|
+
return [{ role: 'user', content: body.input }];
|
|
1372
|
+
}
|
|
1373
|
+
if (Array.isArray(body.input)) {
|
|
1374
|
+
const normalized = [];
|
|
1375
|
+
for (const item of body.input) {
|
|
1376
|
+
if (item && typeof item === 'object' && typeof item.role === 'string') {
|
|
1377
|
+
normalized.push(item);
|
|
1309
1378
|
}
|
|
1310
|
-
if (
|
|
1311
|
-
|
|
1379
|
+
else if (typeof item === 'string') {
|
|
1380
|
+
normalized.push({ role: 'user', content: item });
|
|
1312
1381
|
}
|
|
1313
1382
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1383
|
+
return normalized;
|
|
1384
|
+
}
|
|
1385
|
+
if (body.input && typeof body.input === 'object' && typeof body.input.role === 'string') {
|
|
1386
|
+
return [body.input];
|
|
1387
|
+
}
|
|
1388
|
+
return [];
|
|
1389
|
+
}
|
|
1390
|
+
analyzeUserMessageForHighIq(message) {
|
|
1391
|
+
let hasHumanText = false;
|
|
1392
|
+
let hasHighIqPrefix = false;
|
|
1393
|
+
const scanText = (text, treatAsHuman) => {
|
|
1394
|
+
const trimmed = text.trim();
|
|
1395
|
+
if (!trimmed) {
|
|
1396
|
+
return;
|
|
1397
|
+
}
|
|
1398
|
+
if (treatAsHuman) {
|
|
1399
|
+
hasHumanText = true;
|
|
1400
|
+
}
|
|
1401
|
+
if (treatAsHuman && trimmed.startsWith('!!')) {
|
|
1402
|
+
hasHighIqPrefix = true;
|
|
1403
|
+
}
|
|
1404
|
+
};
|
|
1405
|
+
const content = message === null || message === void 0 ? void 0 : message.content;
|
|
1406
|
+
if (typeof content === 'string') {
|
|
1407
|
+
scanText(content, true);
|
|
1408
|
+
return { hasHumanText, hasHighIqPrefix };
|
|
1409
|
+
}
|
|
1410
|
+
const blocks = Array.isArray(content) ? content : [content];
|
|
1411
|
+
for (const block of blocks) {
|
|
1412
|
+
if (typeof block === 'string') {
|
|
1413
|
+
scanText(block, true);
|
|
1414
|
+
continue;
|
|
1415
|
+
}
|
|
1416
|
+
if (!block || typeof block !== 'object') {
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1419
|
+
const type = typeof block.type === 'string' ? block.type : '';
|
|
1420
|
+
const toolGenerated = type === 'tool_result' || type === 'tool' || Boolean(block.tool_use_id || block.tool_call_id);
|
|
1421
|
+
if (typeof block.text === 'string') {
|
|
1422
|
+
scanText(block.text, !toolGenerated);
|
|
1423
|
+
}
|
|
1424
|
+
if (typeof block.content === 'string') {
|
|
1425
|
+
scanText(block.content, !toolGenerated);
|
|
1426
|
+
}
|
|
1427
|
+
if (Array.isArray(block.content)) {
|
|
1428
|
+
for (const nested of block.content) {
|
|
1429
|
+
if (typeof nested === 'string') {
|
|
1430
|
+
scanText(nested, !toolGenerated);
|
|
1431
|
+
continue;
|
|
1432
|
+
}
|
|
1433
|
+
if (nested && typeof nested === 'object') {
|
|
1434
|
+
if (typeof nested.text === 'string') {
|
|
1435
|
+
scanText(nested.text, !toolGenerated);
|
|
1320
1436
|
}
|
|
1321
|
-
if (
|
|
1322
|
-
|
|
1437
|
+
if (typeof nested.content === 'string') {
|
|
1438
|
+
scanText(nested.content, !toolGenerated);
|
|
1323
1439
|
}
|
|
1324
1440
|
}
|
|
1325
1441
|
}
|
|
1326
1442
|
}
|
|
1327
|
-
break; // 只检查最后一条用户消息
|
|
1328
1443
|
}
|
|
1329
|
-
return
|
|
1444
|
+
return { hasHumanText, hasHighIqPrefix };
|
|
1330
1445
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
*/
|
|
1334
|
-
removeHighIqCommand(body) {
|
|
1335
|
-
if (!(body === null || body === void 0 ? void 0 : body.messages))
|
|
1446
|
+
removeHighIqPrefix(body) {
|
|
1447
|
+
if (!body || typeof body !== 'object') {
|
|
1336
1448
|
return body;
|
|
1449
|
+
}
|
|
1337
1450
|
const processed = JSON.parse(JSON.stringify(body));
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
message.content = trimmed.replace(/^!x\s*/, '').trim();
|
|
1451
|
+
if (typeof processed.input === 'string') {
|
|
1452
|
+
processed.input = processed.input.replace(/^\s*!!\s*/, '');
|
|
1453
|
+
return processed;
|
|
1454
|
+
}
|
|
1455
|
+
if (Array.isArray(processed.input)) {
|
|
1456
|
+
for (let i = processed.input.length - 1; i >= 0; i--) {
|
|
1457
|
+
if (typeof processed.input[i] !== 'string') {
|
|
1458
|
+
continue;
|
|
1347
1459
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1460
|
+
const next = processed.input[i].replace(/^\s*!!\s*/, '');
|
|
1461
|
+
if (next !== processed.input[i]) {
|
|
1462
|
+
processed.input[i] = next;
|
|
1463
|
+
return processed;
|
|
1350
1464
|
}
|
|
1351
1465
|
}
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1466
|
+
}
|
|
1467
|
+
const messages = this.extractConversationMessages(processed);
|
|
1468
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1469
|
+
const message = messages[i];
|
|
1470
|
+
if ((message === null || message === void 0 ? void 0 : message.role) !== 'user') {
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1473
|
+
if (this.stripHighIqPrefixInMessage(message)) {
|
|
1474
|
+
break;
|
|
1364
1475
|
}
|
|
1365
|
-
break; // 只处理最后一条用户消息
|
|
1366
1476
|
}
|
|
1367
1477
|
return processed;
|
|
1368
1478
|
}
|
|
1479
|
+
stripHighIqPrefixInMessage(message) {
|
|
1480
|
+
const content = message === null || message === void 0 ? void 0 : message.content;
|
|
1481
|
+
if (typeof content === 'string') {
|
|
1482
|
+
const next = content.replace(/^\s*!!\s*/, '');
|
|
1483
|
+
if (next !== content) {
|
|
1484
|
+
message.content = next;
|
|
1485
|
+
return true;
|
|
1486
|
+
}
|
|
1487
|
+
return false;
|
|
1488
|
+
}
|
|
1489
|
+
if (!Array.isArray(content)) {
|
|
1490
|
+
return false;
|
|
1491
|
+
}
|
|
1492
|
+
for (const block of content) {
|
|
1493
|
+
if (!block || typeof block !== 'object') {
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
const type = typeof block.type === 'string' ? block.type : '';
|
|
1497
|
+
const toolGenerated = type === 'tool_result' || type === 'tool' || Boolean(block.tool_use_id || block.tool_call_id);
|
|
1498
|
+
if (toolGenerated) {
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
if (typeof block.text === 'string') {
|
|
1502
|
+
const next = block.text.replace(/^\s*!!\s*/, '');
|
|
1503
|
+
if (next !== block.text) {
|
|
1504
|
+
block.text = next;
|
|
1505
|
+
return true;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
if (typeof block.content === 'string') {
|
|
1509
|
+
const next = block.content.replace(/^\s*!!\s*/, '');
|
|
1510
|
+
if (next !== block.content) {
|
|
1511
|
+
block.content = next;
|
|
1512
|
+
return true;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
return false;
|
|
1517
|
+
}
|
|
1369
1518
|
/**
|
|
1370
1519
|
* 查找可用的高智商规则
|
|
1371
1520
|
*/
|
|
@@ -1402,14 +1551,6 @@ class ProxyServer {
|
|
|
1402
1551
|
return undefined;
|
|
1403
1552
|
});
|
|
1404
1553
|
}
|
|
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
|
-
}
|
|
1413
1554
|
hasBackgroundSignal(body) {
|
|
1414
1555
|
var _a, _b, _c;
|
|
1415
1556
|
// 检测 count tokens 请求:messages 只有一条,role 为 "user",content 为 "count"
|
|
@@ -1954,6 +2095,7 @@ class ProxyServer {
|
|
|
1954
2095
|
const targetType = route.targetType;
|
|
1955
2096
|
const failoverEnabled = (options === null || options === void 0 ? void 0 : options.failoverEnabled) === true;
|
|
1956
2097
|
const forwardedToServiceName = options === null || options === void 0 ? void 0 : options.forwardedToServiceName;
|
|
2098
|
+
const useOriginalConfig = (options === null || options === void 0 ? void 0 : options.useOriginalConfig) === true;
|
|
1957
2099
|
let requestBody = req.body || {};
|
|
1958
2100
|
let usageForLog;
|
|
1959
2101
|
let logged = false;
|
|
@@ -2122,6 +2264,7 @@ class ProxyServer {
|
|
|
2122
2264
|
vendorId: service.vendorId,
|
|
2123
2265
|
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
2124
2266
|
requestModel,
|
|
2267
|
+
tags: useOriginalConfig ? ['使用原始配置'] : undefined,
|
|
2125
2268
|
responseHeaders: responseHeadersForLog,
|
|
2126
2269
|
responseBody: responseBodyForLog,
|
|
2127
2270
|
streamChunks: streamChunksForLog,
|
|
@@ -2133,6 +2276,7 @@ class ProxyServer {
|
|
|
2133
2276
|
const totalTokens = ((usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.inputTokens) || 0) + ((usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.outputTokens) || 0) +
|
|
2134
2277
|
((usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.totalTokens) || 0);
|
|
2135
2278
|
const sessionTitle = this.defaultExtractSessionTitle(req, sessionId);
|
|
2279
|
+
const existingSession = this.dbManager.getSession(sessionId);
|
|
2136
2280
|
this.dbManager.upsertSession({
|
|
2137
2281
|
id: sessionId,
|
|
2138
2282
|
targetType,
|
|
@@ -2145,6 +2289,11 @@ class ProxyServer {
|
|
|
2145
2289
|
serviceName: service.name,
|
|
2146
2290
|
model: requestModel || rule.targetModel,
|
|
2147
2291
|
totalTokens,
|
|
2292
|
+
highIqMode: rule.contentType === 'high-iq' ? true : existingSession === null || existingSession === void 0 ? void 0 : existingSession.highIqMode,
|
|
2293
|
+
highIqRuleId: rule.contentType === 'high-iq' ? rule.id : existingSession === null || existingSession === void 0 ? void 0 : existingSession.highIqRuleId,
|
|
2294
|
+
highIqEnabledAt: rule.contentType === 'high-iq'
|
|
2295
|
+
? ((existingSession === null || existingSession === void 0 ? void 0 : existingSession.highIqEnabledAt) || Date.now())
|
|
2296
|
+
: existingSession === null || existingSession === void 0 ? void 0 : existingSession.highIqEnabledAt,
|
|
2148
2297
|
});
|
|
2149
2298
|
}
|
|
2150
2299
|
// 更新规则的token使用量(只在成功请求时更新)
|