aicodeswitch 3.0.17 → 3.0.19
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 +4 -0
- package/dist/server/proxy-server.js +160 -19
- package/dist/ui/assets/index-BCdyCT1c.js +510 -0
- package/dist/ui/assets/index-C7G0whng.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-Bbt481dy.css +0 -1
- package/dist/ui/assets/index-CcN9H5O_.js +0 -506
package/README.md
CHANGED
|
@@ -111,6 +111,14 @@ class ProxyServer {
|
|
|
111
111
|
writable: true,
|
|
112
112
|
value: 60000
|
|
113
113
|
}); // 去重缓存1分钟过期
|
|
114
|
+
// 频率限制跟踪:用于跟踪每个规则在当前时间窗口内的请求数
|
|
115
|
+
// key: ruleId, value: { count: number, windowStart: number }
|
|
116
|
+
Object.defineProperty(this, "frequencyLimitTracker", {
|
|
117
|
+
enumerable: true,
|
|
118
|
+
configurable: true,
|
|
119
|
+
writable: true,
|
|
120
|
+
value: new Map()
|
|
121
|
+
});
|
|
114
122
|
this.dbManager = dbManager;
|
|
115
123
|
this.config = dbManager.getConfig();
|
|
116
124
|
this.app = app;
|
|
@@ -466,6 +474,9 @@ class ProxyServer {
|
|
|
466
474
|
const routeRules = this.dbManager.getRules(routeId);
|
|
467
475
|
return routeRules.sort((a, b) => (b.sortOrder || 0) - (a.sortOrder || 0));
|
|
468
476
|
}
|
|
477
|
+
getRuleById(ruleId) {
|
|
478
|
+
return this.dbManager.getRule(ruleId);
|
|
479
|
+
}
|
|
469
480
|
findMatchingRoute(req) {
|
|
470
481
|
// 根据请求路径确定目标类型
|
|
471
482
|
let targetType;
|
|
@@ -564,6 +575,32 @@ class ProxyServer {
|
|
|
564
575
|
}
|
|
565
576
|
}
|
|
566
577
|
}
|
|
578
|
+
/**
|
|
579
|
+
* 清理过期的频率限制跟踪数据
|
|
580
|
+
*/
|
|
581
|
+
cleanExpiredFrequencyTrackers() {
|
|
582
|
+
const now = Date.now();
|
|
583
|
+
const rules = this.dbManager.getRules();
|
|
584
|
+
const activeRuleIds = new Set(rules.map((r) => r.id));
|
|
585
|
+
for (const ruleId of this.frequencyLimitTracker.keys()) {
|
|
586
|
+
// 清理不再存在的规则的跟踪数据
|
|
587
|
+
if (!activeRuleIds.has(ruleId)) {
|
|
588
|
+
this.frequencyLimitTracker.delete(ruleId);
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
// 清理超时的跟踪数据
|
|
592
|
+
const tracker = this.frequencyLimitTracker.get(ruleId);
|
|
593
|
+
if (tracker) {
|
|
594
|
+
const rule = this.dbManager.getRule(ruleId);
|
|
595
|
+
if (rule && rule.frequencyWindow) {
|
|
596
|
+
const windowMs = rule.frequencyWindow * 1000;
|
|
597
|
+
if (now - tracker.windowStart > windowMs * 2) {
|
|
598
|
+
this.frequencyLimitTracker.delete(ruleId);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
567
604
|
/**
|
|
568
605
|
* 根据GLM计费逻辑判断请求是否应该计费
|
|
569
606
|
* 核心规则:
|
|
@@ -678,6 +715,10 @@ class ProxyServer {
|
|
|
678
715
|
if (rule.requestCountLimit && rule.totalRequestsUsed !== undefined && rule.totalRequestsUsed >= rule.requestCountLimit) {
|
|
679
716
|
continue; // 跳过超限规则
|
|
680
717
|
}
|
|
718
|
+
// 检查频率限制
|
|
719
|
+
if (this.isFrequencyLimitExceeded(rule)) {
|
|
720
|
+
continue; // 跳过达到频率限制的规则
|
|
721
|
+
}
|
|
681
722
|
return rule;
|
|
682
723
|
}
|
|
683
724
|
}
|
|
@@ -701,6 +742,10 @@ class ProxyServer {
|
|
|
701
742
|
if (rule.requestCountLimit && rule.totalRequestsUsed !== undefined && rule.totalRequestsUsed >= rule.requestCountLimit) {
|
|
702
743
|
continue; // 跳过超限规则
|
|
703
744
|
}
|
|
745
|
+
// 检查频率限制
|
|
746
|
+
if (this.isFrequencyLimitExceeded(rule)) {
|
|
747
|
+
continue; // 跳过达到频率限制的规则
|
|
748
|
+
}
|
|
704
749
|
return rule;
|
|
705
750
|
}
|
|
706
751
|
// 3. 最后返回 default 规则
|
|
@@ -722,6 +767,10 @@ class ProxyServer {
|
|
|
722
767
|
if (rule.requestCountLimit && rule.totalRequestsUsed !== undefined && rule.totalRequestsUsed >= rule.requestCountLimit) {
|
|
723
768
|
continue; // 跳过超限规则
|
|
724
769
|
}
|
|
770
|
+
// 检查频率限制
|
|
771
|
+
if (this.isFrequencyLimitExceeded(rule)) {
|
|
772
|
+
continue; // 跳过达到频率限制的规则
|
|
773
|
+
}
|
|
725
774
|
return rule;
|
|
726
775
|
}
|
|
727
776
|
return undefined;
|
|
@@ -772,6 +821,10 @@ class ProxyServer {
|
|
|
772
821
|
return false;
|
|
773
822
|
}
|
|
774
823
|
}
|
|
824
|
+
// 检查频率限制
|
|
825
|
+
if (this.isFrequencyLimitExceeded(rule)) {
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
775
828
|
return true; // 没有设置限制的规则总是可用
|
|
776
829
|
});
|
|
777
830
|
// 如果过滤后还有规则,使用过滤后的结果
|
|
@@ -781,6 +834,88 @@ class ProxyServer {
|
|
|
781
834
|
}
|
|
782
835
|
return candidates;
|
|
783
836
|
}
|
|
837
|
+
/**
|
|
838
|
+
* 检查规则是否达到频率限制
|
|
839
|
+
* 如果设置了频率限制(frequencyLimit)和时间窗口(frequencyWindow),
|
|
840
|
+
* 则跟踪当前时间窗口内的请求数,超过限制则返回true
|
|
841
|
+
*
|
|
842
|
+
* frequencyWindow = 0 表示"同一时刻",计数器不会按时间窗口重置,
|
|
843
|
+
* 持续累积直到达到 frequencyLimit
|
|
844
|
+
*/
|
|
845
|
+
isFrequencyLimitExceeded(rule) {
|
|
846
|
+
if (!rule.frequencyLimit || rule.frequencyLimit <= 0) {
|
|
847
|
+
return false; // 没有设置频率限制,不超过限制
|
|
848
|
+
}
|
|
849
|
+
// frequencyWindow 为 0 表示"同一时刻"(不按时间窗口重置)
|
|
850
|
+
const isZeroWindow = rule.frequencyWindow === 0;
|
|
851
|
+
if (!rule.frequencyWindow && !isZeroWindow) {
|
|
852
|
+
return false; // 没有设置时间窗口且不是0,不启用频率限制
|
|
853
|
+
}
|
|
854
|
+
const now = Date.now();
|
|
855
|
+
const existing = this.frequencyLimitTracker.get(rule.id);
|
|
856
|
+
if (!existing) {
|
|
857
|
+
// 首次请求,创建新记录
|
|
858
|
+
this.frequencyLimitTracker.set(rule.id, { count: 1, windowStart: now });
|
|
859
|
+
return false;
|
|
860
|
+
}
|
|
861
|
+
// 如果是零窗口(同一时刻),不按时间重置,持续累积
|
|
862
|
+
if (!isZeroWindow && rule.frequencyWindow) {
|
|
863
|
+
const windowMs = rule.frequencyWindow * 1000;
|
|
864
|
+
// 检查是否在当前时间窗口内
|
|
865
|
+
if (now - existing.windowStart >= windowMs) {
|
|
866
|
+
// 时间窗口已过,重置计数器
|
|
867
|
+
this.frequencyLimitTracker.set(rule.id, { count: 1, windowStart: now });
|
|
868
|
+
return false;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
// 检查是否超过限制
|
|
872
|
+
if (existing.count >= rule.frequencyLimit) {
|
|
873
|
+
return true; // 超过频率限制
|
|
874
|
+
}
|
|
875
|
+
// 增加计数
|
|
876
|
+
existing.count++;
|
|
877
|
+
this.frequencyLimitTracker.set(rule.id, existing);
|
|
878
|
+
return false;
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* 记录请求(增加频率计数)
|
|
882
|
+
* 在请求成功处理后调用
|
|
883
|
+
* frequencyWindow = 0 表示"同一时刻",计数器不会按时间窗口重置
|
|
884
|
+
*/
|
|
885
|
+
recordRequest(ruleId) {
|
|
886
|
+
const rule = this.getRuleById(ruleId);
|
|
887
|
+
if (!rule || !rule.frequencyLimit || rule.frequencyLimit <= 0) {
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
// frequencyWindow 为 0 表示"同一时刻"
|
|
891
|
+
const isZeroWindow = rule.frequencyWindow === 0;
|
|
892
|
+
// 如果 frequencyWindow 既不是 0 也不是正数,则不记录
|
|
893
|
+
if (!isZeroWindow && !rule.frequencyWindow) {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
const now = Date.now();
|
|
897
|
+
const existing = this.frequencyLimitTracker.get(ruleId);
|
|
898
|
+
if (!existing) {
|
|
899
|
+
this.frequencyLimitTracker.set(ruleId, { count: 1, windowStart: now });
|
|
900
|
+
}
|
|
901
|
+
else if (isZeroWindow) {
|
|
902
|
+
// 零窗口:持续累积,不按时间重置
|
|
903
|
+
existing.count++;
|
|
904
|
+
this.frequencyLimitTracker.set(ruleId, existing);
|
|
905
|
+
}
|
|
906
|
+
else if (rule.frequencyWindow) {
|
|
907
|
+
const windowMs = rule.frequencyWindow * 1000;
|
|
908
|
+
if (now - existing.windowStart < windowMs) {
|
|
909
|
+
// 在时间窗口内,增加计数
|
|
910
|
+
existing.count++;
|
|
911
|
+
this.frequencyLimitTracker.set(ruleId, existing);
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
// 时间窗口已过,重置
|
|
915
|
+
this.frequencyLimitTracker.set(ruleId, { count: 1, windowStart: now });
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
784
919
|
determineContentType(req) {
|
|
785
920
|
const body = req.body;
|
|
786
921
|
if (!body)
|
|
@@ -1426,7 +1561,7 @@ class ProxyServer {
|
|
|
1426
1561
|
}
|
|
1427
1562
|
proxyRequest(req, res, route, rule, service) {
|
|
1428
1563
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1429
|
-
var _a, _b, _c, _d;
|
|
1564
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1430
1565
|
res.locals.skipLog = true;
|
|
1431
1566
|
const startTime = Date.now();
|
|
1432
1567
|
const sourceType = (service.sourceType || 'openai-chat');
|
|
@@ -1595,7 +1730,7 @@ class ProxyServer {
|
|
|
1595
1730
|
targetType,
|
|
1596
1731
|
targetServiceId: service.id,
|
|
1597
1732
|
targetServiceName: service.name,
|
|
1598
|
-
targetModel: rule.targetModel,
|
|
1733
|
+
targetModel: rule.targetModel || requestModel,
|
|
1599
1734
|
vendorId: service.vendorId,
|
|
1600
1735
|
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
1601
1736
|
requestModel,
|
|
@@ -1643,6 +1778,8 @@ class ProxyServer {
|
|
|
1643
1778
|
// 检查是否是重复请求(如网络重试)
|
|
1644
1779
|
if (!this.isRequestProcessed(requestHash)) {
|
|
1645
1780
|
this.dbManager.incrementRuleRequestCount(rule.id, 1);
|
|
1781
|
+
// 更新频率限制跟踪
|
|
1782
|
+
this.recordRequest(rule.id);
|
|
1646
1783
|
// 获取更新后的规则数据并广播
|
|
1647
1784
|
const updatedRule = this.dbManager.getRule(rule.id);
|
|
1648
1785
|
if (updatedRule) {
|
|
@@ -1654,6 +1791,10 @@ class ProxyServer {
|
|
|
1654
1791
|
this.cleanExpiredDedupeCache();
|
|
1655
1792
|
}
|
|
1656
1793
|
}
|
|
1794
|
+
// 定期清理过期的频率限制跟踪数据
|
|
1795
|
+
if (Math.random() < 0.01) { // 1%概率清理
|
|
1796
|
+
this.cleanExpiredFrequencyTrackers();
|
|
1797
|
+
}
|
|
1657
1798
|
// 清理 MCP 临时图片文件
|
|
1658
1799
|
if (tempImageFiles.length > 0) {
|
|
1659
1800
|
(0, mcp_image_handler_1.cleanupTempImages)(tempImageFiles);
|
|
@@ -1832,7 +1973,7 @@ class ProxyServer {
|
|
|
1832
1973
|
console.error('[Proxy] Response stream error:', err);
|
|
1833
1974
|
});
|
|
1834
1975
|
(0, stream_1.pipeline)(response.data, parser, eventCollector, converter, serializer, res, (error) => __awaiter(this, void 0, void 0, function* () {
|
|
1835
|
-
var _a;
|
|
1976
|
+
var _a, _b;
|
|
1836
1977
|
if (error) {
|
|
1837
1978
|
console.error('[Proxy] Pipeline error for claude-code:', error);
|
|
1838
1979
|
// 记录到错误日志 - 包含请求详情和实际转发信息
|
|
@@ -1855,10 +1996,10 @@ class ProxyServer {
|
|
|
1855
1996
|
targetType,
|
|
1856
1997
|
targetServiceId: service.id,
|
|
1857
1998
|
targetServiceName: service.name,
|
|
1858
|
-
targetModel: rule.targetModel,
|
|
1999
|
+
targetModel: rule.targetModel || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
|
|
1859
2000
|
vendorId: service.vendorId,
|
|
1860
2001
|
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
1861
|
-
requestModel: (
|
|
2002
|
+
requestModel: (_b = req.body) === null || _b === void 0 ? void 0 : _b.model,
|
|
1862
2003
|
responseTime: Date.now() - startTime,
|
|
1863
2004
|
});
|
|
1864
2005
|
}
|
|
@@ -1934,7 +2075,7 @@ class ProxyServer {
|
|
|
1934
2075
|
console.error('[Proxy] Response stream error:', err);
|
|
1935
2076
|
});
|
|
1936
2077
|
(0, stream_1.pipeline)(response.data, parser, eventCollector, converter, serializer, res, (error) => __awaiter(this, void 0, void 0, function* () {
|
|
1937
|
-
var _a;
|
|
2078
|
+
var _a, _b;
|
|
1938
2079
|
if (error) {
|
|
1939
2080
|
console.error('[Proxy] Pipeline error for codex:', error);
|
|
1940
2081
|
// 记录到错误日志 - 包含请求详情和实际转发信息
|
|
@@ -1957,10 +2098,10 @@ class ProxyServer {
|
|
|
1957
2098
|
targetType,
|
|
1958
2099
|
targetServiceId: service.id,
|
|
1959
2100
|
targetServiceName: service.name,
|
|
1960
|
-
targetModel: rule.targetModel,
|
|
2101
|
+
targetModel: rule.targetModel || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
|
|
1961
2102
|
vendorId: service.vendorId,
|
|
1962
2103
|
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
1963
|
-
requestModel: (
|
|
2104
|
+
requestModel: (_b = req.body) === null || _b === void 0 ? void 0 : _b.model,
|
|
1964
2105
|
responseTime: Date.now() - startTime,
|
|
1965
2106
|
});
|
|
1966
2107
|
}
|
|
@@ -2030,7 +2171,7 @@ class ProxyServer {
|
|
|
2030
2171
|
console.error('[Proxy] Response stream error:', err);
|
|
2031
2172
|
});
|
|
2032
2173
|
(0, stream_1.pipeline)(response.data, parser, eventCollector, converter, serializer, res, (error) => __awaiter(this, void 0, void 0, function* () {
|
|
2033
|
-
var _a;
|
|
2174
|
+
var _a, _b;
|
|
2034
2175
|
if (error) {
|
|
2035
2176
|
console.error('[Proxy] Pipeline error for gemini->claude-code:', error);
|
|
2036
2177
|
try {
|
|
@@ -2050,10 +2191,10 @@ class ProxyServer {
|
|
|
2050
2191
|
targetType,
|
|
2051
2192
|
targetServiceId: service.id,
|
|
2052
2193
|
targetServiceName: service.name,
|
|
2053
|
-
targetModel: rule.targetModel,
|
|
2194
|
+
targetModel: rule.targetModel || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
|
|
2054
2195
|
vendorId: service.vendorId,
|
|
2055
2196
|
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
2056
|
-
requestModel: (
|
|
2197
|
+
requestModel: (_b = req.body) === null || _b === void 0 ? void 0 : _b.model,
|
|
2057
2198
|
responseTime: Date.now() - startTime,
|
|
2058
2199
|
});
|
|
2059
2200
|
}
|
|
@@ -2126,7 +2267,7 @@ class ProxyServer {
|
|
|
2126
2267
|
console.error('[Proxy] Response stream error:', err);
|
|
2127
2268
|
});
|
|
2128
2269
|
(0, stream_1.pipeline)(response.data, parser, eventCollector, converter, serializer, res, (error) => __awaiter(this, void 0, void 0, function* () {
|
|
2129
|
-
var _a;
|
|
2270
|
+
var _a, _b;
|
|
2130
2271
|
if (error) {
|
|
2131
2272
|
console.error('[Proxy] Pipeline error for gemini->codex:', error);
|
|
2132
2273
|
try {
|
|
@@ -2146,10 +2287,10 @@ class ProxyServer {
|
|
|
2146
2287
|
targetType,
|
|
2147
2288
|
targetServiceId: service.id,
|
|
2148
2289
|
targetServiceName: service.name,
|
|
2149
|
-
targetModel: rule.targetModel,
|
|
2290
|
+
targetModel: rule.targetModel || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
|
|
2150
2291
|
vendorId: service.vendorId,
|
|
2151
2292
|
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
2152
|
-
requestModel: (
|
|
2293
|
+
requestModel: (_b = req.body) === null || _b === void 0 ? void 0 : _b.model,
|
|
2153
2294
|
responseTime: Date.now() - startTime,
|
|
2154
2295
|
});
|
|
2155
2296
|
}
|
|
@@ -2280,10 +2421,10 @@ class ProxyServer {
|
|
|
2280
2421
|
targetType,
|
|
2281
2422
|
targetServiceId: service.id,
|
|
2282
2423
|
targetServiceName: service.name,
|
|
2283
|
-
targetModel: rule.targetModel,
|
|
2424
|
+
targetModel: rule.targetModel || ((_b = req.body) === null || _b === void 0 ? void 0 : _b.model),
|
|
2284
2425
|
vendorId: service.vendorId,
|
|
2285
2426
|
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
2286
|
-
requestModel: (
|
|
2427
|
+
requestModel: (_c = req.body) === null || _c === void 0 ? void 0 : _c.model,
|
|
2287
2428
|
upstreamRequest: upstreamRequestForLog,
|
|
2288
2429
|
responseTime: Date.now() - startTime,
|
|
2289
2430
|
});
|
|
@@ -2341,7 +2482,7 @@ class ProxyServer {
|
|
|
2341
2482
|
console.error('Proxy error:', error);
|
|
2342
2483
|
// 检测是否是 timeout 错误
|
|
2343
2484
|
const isTimeout = error.code === 'ECONNABORTED' ||
|
|
2344
|
-
((
|
|
2485
|
+
((_d = error.message) === null || _d === void 0 ? void 0 : _d.toLowerCase().includes('timeout')) ||
|
|
2345
2486
|
(error.errno && error.errno === 'ETIMEDOUT');
|
|
2346
2487
|
const errorMessage = isTimeout
|
|
2347
2488
|
? 'Request timeout - the upstream API took too long to respond'
|
|
@@ -2364,10 +2505,10 @@ class ProxyServer {
|
|
|
2364
2505
|
targetType,
|
|
2365
2506
|
targetServiceId: service.id,
|
|
2366
2507
|
targetServiceName: service.name,
|
|
2367
|
-
targetModel: rule.targetModel,
|
|
2508
|
+
targetModel: rule.targetModel || ((_e = req.body) === null || _e === void 0 ? void 0 : _e.model),
|
|
2368
2509
|
vendorId: service.vendorId,
|
|
2369
2510
|
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
2370
|
-
requestModel: (
|
|
2511
|
+
requestModel: (_f = req.body) === null || _f === void 0 ? void 0 : _f.model,
|
|
2371
2512
|
upstreamRequest: upstreamRequestForLog,
|
|
2372
2513
|
responseTime: Date.now() - startTime,
|
|
2373
2514
|
});
|