aicodeswitch 3.0.18 → 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 CHANGED
@@ -30,6 +30,10 @@ AI Code Switch 是帮助你在本地管理 AI 编程工具接入大模型的工
30
30
  * 自定义API Key,支持B/S架构,让aicodeswitch成为在线服务,提供给团队使用
31
31
  * 数据完全本地,自主可控
32
32
 
33
+ ## 桌面客户端
34
+
35
+ [进入下载](https://github.com/tangshuang/aicodeswitch/releases)
36
+
33
37
  ## 命令行工具
34
38
 
35
39
  ### 安装
@@ -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: (_a = req.body) === null || _a === void 0 ? void 0 : _a.model,
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: (_a = req.body) === null || _a === void 0 ? void 0 : _a.model,
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: (_a = req.body) === null || _a === void 0 ? void 0 : _a.model,
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: (_a = req.body) === null || _a === void 0 ? void 0 : _a.model,
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: (_b = req.body) === null || _b === void 0 ? void 0 : _b.model,
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
- ((_c = error.message) === null || _c === void 0 ? void 0 : _c.toLowerCase().includes('timeout')) ||
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: (_d = req.body) === null || _d === void 0 ? void 0 : _d.model,
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
  });