aicodeswitch 2.0.10 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/CLAUDE.md +32 -2
- package/TECH.md +292 -170
- package/dist/server/main.js +38 -0
- package/dist/server/proxy-server.js +116 -14
- package/dist/server/rules-status-service.js +197 -0
- package/dist/server/tools-service.js +202 -0
- package/dist/server/transformers/claude-openai.js +554 -48
- package/dist/server/transformers/streaming.js +169 -2
- package/dist/server/websocket-service.js +148 -0
- package/dist/ui/assets/index-B5wnowv6.css +1 -0
- package/dist/ui/assets/index-DXvVJfvE.js +452 -0
- package/dist/ui/index.html +2 -2
- package/package.json +3 -2
- package/schemes/claude.schema.md +945 -0
- package/schemes/openai.schema.md +2162 -0
- package/dist/ui/assets/index-BYeFnkER.js +0 -431
- package/dist/ui/assets/index-DQ2LJr-O.css +0 -1
|
@@ -51,6 +51,7 @@ const stream_1 = require("stream");
|
|
|
51
51
|
const crypto_1 = __importDefault(require("crypto"));
|
|
52
52
|
const streaming_1 = require("./transformers/streaming");
|
|
53
53
|
const chunk_collector_1 = require("./transformers/chunk-collector");
|
|
54
|
+
const rules_status_service_1 = require("./rules-status-service");
|
|
54
55
|
const claude_openai_1 = require("./transformers/claude-openai");
|
|
55
56
|
const SUPPORTED_TARGETS = ['claude-code', 'codex'];
|
|
56
57
|
class ProxyServer {
|
|
@@ -114,7 +115,7 @@ class ProxyServer {
|
|
|
114
115
|
initialize() {
|
|
115
116
|
// Dynamic proxy middleware
|
|
116
117
|
this.app.use((req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
117
|
-
var _a, _b, _c, _d, _e;
|
|
118
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
118
119
|
// 仅处理支持的目标路径
|
|
119
120
|
if (!SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
|
|
120
121
|
return next();
|
|
@@ -200,7 +201,9 @@ class ProxyServer {
|
|
|
200
201
|
error: (lastError === null || lastError === void 0 ? void 0 : lastError.message) || 'All services failed',
|
|
201
202
|
});
|
|
202
203
|
}
|
|
203
|
-
//
|
|
204
|
+
// 确定目标类型
|
|
205
|
+
const targetType = req.path.startsWith('/claude-code/') ? 'claude-code' : 'codex';
|
|
206
|
+
// 记录错误日志 - 包含请求详情
|
|
204
207
|
yield this.dbManager.addErrorLog({
|
|
205
208
|
timestamp: Date.now(),
|
|
206
209
|
method: req.method,
|
|
@@ -210,6 +213,10 @@ class ProxyServer {
|
|
|
210
213
|
errorStack: lastError === null || lastError === void 0 ? void 0 : lastError.stack,
|
|
211
214
|
requestHeaders: this.normalizeHeaders(req.headers),
|
|
212
215
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
216
|
+
// 添加请求详情
|
|
217
|
+
targetType,
|
|
218
|
+
requestModel: (_e = req.body) === null || _e === void 0 ? void 0 : _e.model,
|
|
219
|
+
responseTime: 0,
|
|
213
220
|
});
|
|
214
221
|
// 根据路径判断目标类型并返回适当的错误格式
|
|
215
222
|
const isClaudeCode = req.path.startsWith('/claude-code/');
|
|
@@ -232,7 +239,7 @@ class ProxyServer {
|
|
|
232
239
|
}
|
|
233
240
|
catch (error) {
|
|
234
241
|
console.error('Proxy error:', error);
|
|
235
|
-
if (((
|
|
242
|
+
if (((_f = this.config) === null || _f === void 0 ? void 0 : _f.enableLogging) !== false && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
|
|
236
243
|
yield this.dbManager.addLog({
|
|
237
244
|
timestamp: Date.now(),
|
|
238
245
|
method: req.method,
|
|
@@ -242,7 +249,8 @@ class ProxyServer {
|
|
|
242
249
|
error: error.message,
|
|
243
250
|
});
|
|
244
251
|
}
|
|
245
|
-
// Add error log
|
|
252
|
+
// Add error log - 包含请求详情
|
|
253
|
+
const targetType = req.path.startsWith('/claude-code/') ? 'claude-code' : 'codex';
|
|
246
254
|
yield this.dbManager.addErrorLog({
|
|
247
255
|
timestamp: Date.now(),
|
|
248
256
|
method: req.method,
|
|
@@ -252,6 +260,10 @@ class ProxyServer {
|
|
|
252
260
|
errorStack: error.stack,
|
|
253
261
|
requestHeaders: this.normalizeHeaders(req.headers),
|
|
254
262
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
263
|
+
// 添加请求详情
|
|
264
|
+
targetType,
|
|
265
|
+
requestModel: (_g = req.body) === null || _g === void 0 ? void 0 : _g.model,
|
|
266
|
+
responseTime: 0,
|
|
255
267
|
});
|
|
256
268
|
// 根据路径判断目标类型并返回适当的错误格式
|
|
257
269
|
const isClaudeCode = req.path.startsWith('/claude-code/');
|
|
@@ -280,7 +292,7 @@ class ProxyServer {
|
|
|
280
292
|
}
|
|
281
293
|
createFixedRouteHandler(targetType) {
|
|
282
294
|
return (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
283
|
-
var _a, _b, _c, _d, _e;
|
|
295
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
284
296
|
try {
|
|
285
297
|
// 检查API Key验证
|
|
286
298
|
if (this.config.apiKey) {
|
|
@@ -370,7 +382,7 @@ class ProxyServer {
|
|
|
370
382
|
error: (lastError === null || lastError === void 0 ? void 0 : lastError.message) || 'All services failed',
|
|
371
383
|
});
|
|
372
384
|
}
|
|
373
|
-
// 记录错误日志
|
|
385
|
+
// 记录错误日志 - 包含请求详情(使用函数参数 targetType)
|
|
374
386
|
yield this.dbManager.addErrorLog({
|
|
375
387
|
timestamp: Date.now(),
|
|
376
388
|
method: req.method,
|
|
@@ -380,6 +392,10 @@ class ProxyServer {
|
|
|
380
392
|
errorStack: lastError === null || lastError === void 0 ? void 0 : lastError.stack,
|
|
381
393
|
requestHeaders: this.normalizeHeaders(req.headers),
|
|
382
394
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
395
|
+
// 添加请求详情
|
|
396
|
+
targetType,
|
|
397
|
+
requestModel: (_e = req.body) === null || _e === void 0 ? void 0 : _e.model,
|
|
398
|
+
responseTime: 0,
|
|
383
399
|
});
|
|
384
400
|
// 根据路径判断目标类型并返回适当的错误格式
|
|
385
401
|
const isClaudeCode = req.path.startsWith('/claude-code/');
|
|
@@ -402,7 +418,7 @@ class ProxyServer {
|
|
|
402
418
|
}
|
|
403
419
|
catch (error) {
|
|
404
420
|
console.error(`Fixed route error for ${targetType}:`, error);
|
|
405
|
-
if (((
|
|
421
|
+
if (((_f = this.config) === null || _f === void 0 ? void 0 : _f.enableLogging) !== false && SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
|
|
406
422
|
yield this.dbManager.addLog({
|
|
407
423
|
timestamp: Date.now(),
|
|
408
424
|
method: req.method,
|
|
@@ -412,7 +428,7 @@ class ProxyServer {
|
|
|
412
428
|
error: error.message,
|
|
413
429
|
});
|
|
414
430
|
}
|
|
415
|
-
// Add error log
|
|
431
|
+
// Add error log - 包含请求详情(使用函数参数 targetType)
|
|
416
432
|
yield this.dbManager.addErrorLog({
|
|
417
433
|
timestamp: Date.now(),
|
|
418
434
|
method: req.method,
|
|
@@ -422,6 +438,10 @@ class ProxyServer {
|
|
|
422
438
|
errorStack: error.stack,
|
|
423
439
|
requestHeaders: this.normalizeHeaders(req.headers),
|
|
424
440
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
441
|
+
// 添加请求详情
|
|
442
|
+
targetType,
|
|
443
|
+
requestModel: (_g = req.body) === null || _g === void 0 ? void 0 : _g.model,
|
|
444
|
+
responseTime: 0,
|
|
425
445
|
});
|
|
426
446
|
res.status(500).json({ error: error.message });
|
|
427
447
|
}
|
|
@@ -762,6 +782,10 @@ class ProxyServer {
|
|
|
762
782
|
const body = req.body;
|
|
763
783
|
if (!body)
|
|
764
784
|
return 'default';
|
|
785
|
+
// 检查是否为 count_tokens 请求(后台类型)
|
|
786
|
+
if (req.path.includes('/count_tokens')) {
|
|
787
|
+
return 'background';
|
|
788
|
+
}
|
|
765
789
|
const explicitType = this.getExplicitContentType(req, body);
|
|
766
790
|
if (explicitType) {
|
|
767
791
|
return explicitType;
|
|
@@ -890,6 +914,17 @@ class ProxyServer {
|
|
|
890
914
|
}
|
|
891
915
|
hasBackgroundSignal(body) {
|
|
892
916
|
var _a, _b, _c;
|
|
917
|
+
// 检测 count tokens 请求:messages 只有一条,role 为 "user",content 为 "count"
|
|
918
|
+
const messages = body === null || body === void 0 ? void 0 : body.messages;
|
|
919
|
+
if (Array.isArray(messages) && messages.length === 1) {
|
|
920
|
+
const firstMessage = messages[0];
|
|
921
|
+
if ((firstMessage === null || firstMessage === void 0 ? void 0 : firstMessage.role) === 'user' &&
|
|
922
|
+
((firstMessage === null || firstMessage === void 0 ? void 0 : firstMessage.content) === 'count' ||
|
|
923
|
+
(typeof (firstMessage === null || firstMessage === void 0 ? void 0 : firstMessage.content) === 'string' && firstMessage.content.trim() === 'count'))) {
|
|
924
|
+
return true;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
// 检测其他后台信号
|
|
893
928
|
const candidates = [
|
|
894
929
|
body === null || body === void 0 ? void 0 : body.background,
|
|
895
930
|
(_a = body === null || body === void 0 ? void 0 : body.metadata) === null || _a === void 0 ? void 0 : _a.background,
|
|
@@ -1345,7 +1380,7 @@ class ProxyServer {
|
|
|
1345
1380
|
}
|
|
1346
1381
|
proxyRequest(req, res, route, rule, service) {
|
|
1347
1382
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1348
|
-
var _a, _b;
|
|
1383
|
+
var _a, _b, _c, _d;
|
|
1349
1384
|
res.locals.skipLog = true;
|
|
1350
1385
|
const startTime = Date.now();
|
|
1351
1386
|
const sourceType = (service.sourceType || 'openai-chat');
|
|
@@ -1359,6 +1394,8 @@ class ProxyServer {
|
|
|
1359
1394
|
let streamChunksForLog;
|
|
1360
1395
|
let upstreamRequestForLog;
|
|
1361
1396
|
let actuallyUsedProxy = false; // 标记是否实际使用了代理
|
|
1397
|
+
// 标记规则正在使用
|
|
1398
|
+
rules_status_service_1.rulesStatusBroadcaster.markRuleInUse(route.id, rule.id);
|
|
1362
1399
|
const finalizeLog = (statusCode, error) => __awaiter(this, void 0, void 0, function* () {
|
|
1363
1400
|
var _a, _b;
|
|
1364
1401
|
if (logged)
|
|
@@ -1428,6 +1465,11 @@ class ProxyServer {
|
|
|
1428
1465
|
const totalTokens = (usageForLog.inputTokens || 0) + (usageForLog.outputTokens || 0);
|
|
1429
1466
|
if (totalTokens > 0) {
|
|
1430
1467
|
this.dbManager.incrementRuleTokenUsage(rule.id, totalTokens);
|
|
1468
|
+
// 获取更新后的规则数据并广播
|
|
1469
|
+
const updatedRule = this.dbManager.getRule(rule.id);
|
|
1470
|
+
if (updatedRule) {
|
|
1471
|
+
rules_status_service_1.rulesStatusBroadcaster.broadcastUsageUpdate(rule.id, updatedRule.totalTokensUsed || 0, updatedRule.totalRequestsUsed || 0);
|
|
1472
|
+
}
|
|
1431
1473
|
}
|
|
1432
1474
|
}
|
|
1433
1475
|
// 更新规则的请求次数(只在成功请求时更新)
|
|
@@ -1437,6 +1479,11 @@ class ProxyServer {
|
|
|
1437
1479
|
// 检查是否是重复请求(如网络重试)
|
|
1438
1480
|
if (!this.isRequestProcessed(requestHash)) {
|
|
1439
1481
|
this.dbManager.incrementRuleRequestCount(rule.id, 1);
|
|
1482
|
+
// 获取更新后的规则数据并广播
|
|
1483
|
+
const updatedRule = this.dbManager.getRule(rule.id);
|
|
1484
|
+
if (updatedRule) {
|
|
1485
|
+
rules_status_service_1.rulesStatusBroadcaster.broadcastUsageUpdate(rule.id, updatedRule.totalTokensUsed || 0, updatedRule.totalRequestsUsed || 0);
|
|
1486
|
+
}
|
|
1440
1487
|
}
|
|
1441
1488
|
// 定期清理过期缓存
|
|
1442
1489
|
if (Math.random() < 0.01) { // 1%概率清理,避免每次都清理
|
|
@@ -1580,10 +1627,14 @@ class ProxyServer {
|
|
|
1580
1627
|
console.error('[Proxy] Response stream error:', err);
|
|
1581
1628
|
});
|
|
1582
1629
|
(0, stream_1.pipeline)(response.data, parser, eventCollector, converter, serializer, res, (error) => __awaiter(this, void 0, void 0, function* () {
|
|
1630
|
+
var _a;
|
|
1583
1631
|
if (error) {
|
|
1584
1632
|
console.error('[Proxy] Pipeline error for claude-code:', error);
|
|
1585
|
-
// 记录到错误日志
|
|
1633
|
+
// 记录到错误日志 - 包含请求详情和实际转发信息
|
|
1586
1634
|
try {
|
|
1635
|
+
// 获取供应商信息
|
|
1636
|
+
const vendors = this.dbManager.getVendors();
|
|
1637
|
+
const vendor = vendors.find(v => v.id === service.vendorId);
|
|
1587
1638
|
yield this.dbManager.addErrorLog({
|
|
1588
1639
|
timestamp: Date.now(),
|
|
1589
1640
|
method: req.method,
|
|
@@ -1594,6 +1645,16 @@ class ProxyServer {
|
|
|
1594
1645
|
requestHeaders: this.normalizeHeaders(req.headers),
|
|
1595
1646
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
1596
1647
|
upstreamRequest: upstreamRequestForLog,
|
|
1648
|
+
// 添加请求详情
|
|
1649
|
+
ruleId: rule.id,
|
|
1650
|
+
targetType,
|
|
1651
|
+
targetServiceId: service.id,
|
|
1652
|
+
targetServiceName: service.name,
|
|
1653
|
+
targetModel: rule.targetModel,
|
|
1654
|
+
vendorId: service.vendorId,
|
|
1655
|
+
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
1656
|
+
requestModel: (_a = req.body) === null || _a === void 0 ? void 0 : _a.model,
|
|
1657
|
+
responseTime: Date.now() - startTime,
|
|
1597
1658
|
});
|
|
1598
1659
|
}
|
|
1599
1660
|
catch (logError) {
|
|
@@ -1651,10 +1712,14 @@ class ProxyServer {
|
|
|
1651
1712
|
console.error('[Proxy] Response stream error:', err);
|
|
1652
1713
|
});
|
|
1653
1714
|
(0, stream_1.pipeline)(response.data, parser, eventCollector, converter, serializer, res, (error) => __awaiter(this, void 0, void 0, function* () {
|
|
1715
|
+
var _a;
|
|
1654
1716
|
if (error) {
|
|
1655
1717
|
console.error('[Proxy] Pipeline error for codex:', error);
|
|
1656
|
-
// 记录到错误日志
|
|
1718
|
+
// 记录到错误日志 - 包含请求详情和实际转发信息
|
|
1657
1719
|
try {
|
|
1720
|
+
// 获取供应商信息
|
|
1721
|
+
const vendors = this.dbManager.getVendors();
|
|
1722
|
+
const vendor = vendors.find(v => v.id === service.vendorId);
|
|
1658
1723
|
yield this.dbManager.addErrorLog({
|
|
1659
1724
|
timestamp: Date.now(),
|
|
1660
1725
|
method: req.method,
|
|
@@ -1665,6 +1730,16 @@ class ProxyServer {
|
|
|
1665
1730
|
requestHeaders: this.normalizeHeaders(req.headers),
|
|
1666
1731
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
1667
1732
|
upstreamRequest: upstreamRequestForLog,
|
|
1733
|
+
// 添加请求详情
|
|
1734
|
+
ruleId: rule.id,
|
|
1735
|
+
targetType,
|
|
1736
|
+
targetServiceId: service.id,
|
|
1737
|
+
targetServiceName: service.name,
|
|
1738
|
+
targetModel: rule.targetModel,
|
|
1739
|
+
vendorId: service.vendorId,
|
|
1740
|
+
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
1741
|
+
requestModel: (_a = req.body) === null || _a === void 0 ? void 0 : _a.model,
|
|
1742
|
+
responseTime: Date.now() - startTime,
|
|
1668
1743
|
});
|
|
1669
1744
|
}
|
|
1670
1745
|
catch (logError) {
|
|
@@ -1698,7 +1773,6 @@ class ProxyServer {
|
|
|
1698
1773
|
});
|
|
1699
1774
|
res.on('finish', () => {
|
|
1700
1775
|
streamChunksForLog = eventCollector.getChunks();
|
|
1701
|
-
console.log('[Proxy] Default stream request finished, collected chunks:', (streamChunksForLog === null || streamChunksForLog === void 0 ? void 0 : streamChunksForLog.length) || 0);
|
|
1702
1776
|
// 尝试从event collector中提取usage信息
|
|
1703
1777
|
const extractedUsage = eventCollector.extractUsage();
|
|
1704
1778
|
if (extractedUsage) {
|
|
@@ -1757,6 +1831,9 @@ class ProxyServer {
|
|
|
1757
1831
|
else {
|
|
1758
1832
|
errorDetail = JSON.stringify(responseData);
|
|
1759
1833
|
}
|
|
1834
|
+
// 获取供应商信息
|
|
1835
|
+
const vendors = this.dbManager.getVendors();
|
|
1836
|
+
const vendor = vendors.find(v => v.id === service.vendorId);
|
|
1760
1837
|
yield this.dbManager.addErrorLog({
|
|
1761
1838
|
timestamp: Date.now(),
|
|
1762
1839
|
method: req.method,
|
|
@@ -1768,6 +1845,17 @@ class ProxyServer {
|
|
|
1768
1845
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
1769
1846
|
responseHeaders: responseHeadersForLog,
|
|
1770
1847
|
responseBody: responseBodyForLog,
|
|
1848
|
+
// 添加请求详情和实际转发信息
|
|
1849
|
+
ruleId: rule.id,
|
|
1850
|
+
targetType,
|
|
1851
|
+
targetServiceId: service.id,
|
|
1852
|
+
targetServiceName: service.name,
|
|
1853
|
+
targetModel: rule.targetModel,
|
|
1854
|
+
vendorId: service.vendorId,
|
|
1855
|
+
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
1856
|
+
requestModel: (_b = req.body) === null || _b === void 0 ? void 0 : _b.model,
|
|
1857
|
+
upstreamRequest: upstreamRequestForLog,
|
|
1858
|
+
responseTime: Date.now() - startTime,
|
|
1771
1859
|
});
|
|
1772
1860
|
this.copyResponseHeaders(responseHeaders, res);
|
|
1773
1861
|
if (contentType.includes('application/json')) {
|
|
@@ -1811,12 +1899,15 @@ class ProxyServer {
|
|
|
1811
1899
|
console.error('Proxy error:', error);
|
|
1812
1900
|
// 检测是否是 timeout 错误
|
|
1813
1901
|
const isTimeout = error.code === 'ECONNABORTED' ||
|
|
1814
|
-
((
|
|
1902
|
+
((_c = error.message) === null || _c === void 0 ? void 0 : _c.toLowerCase().includes('timeout')) ||
|
|
1815
1903
|
(error.errno && error.errno === 'ETIMEDOUT');
|
|
1816
1904
|
const errorMessage = isTimeout
|
|
1817
1905
|
? 'Request timeout - the upstream API took too long to respond'
|
|
1818
1906
|
: (error.message || 'Internal server error');
|
|
1819
|
-
// 将错误记录到错误日志
|
|
1907
|
+
// 将错误记录到错误日志 - 包含请求详情和实际转发信息
|
|
1908
|
+
// 获取供应商信息
|
|
1909
|
+
const vendors = this.dbManager.getVendors();
|
|
1910
|
+
const vendor = vendors.find(v => v.id === service.vendorId);
|
|
1820
1911
|
yield this.dbManager.addErrorLog({
|
|
1821
1912
|
timestamp: Date.now(),
|
|
1822
1913
|
method: req.method,
|
|
@@ -1826,6 +1917,17 @@ class ProxyServer {
|
|
|
1826
1917
|
errorStack: error.stack,
|
|
1827
1918
|
requestHeaders: this.normalizeHeaders(req.headers),
|
|
1828
1919
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
1920
|
+
// 添加请求详情和实际转发信息
|
|
1921
|
+
ruleId: rule.id,
|
|
1922
|
+
targetType,
|
|
1923
|
+
targetServiceId: service.id,
|
|
1924
|
+
targetServiceName: service.name,
|
|
1925
|
+
targetModel: rule.targetModel,
|
|
1926
|
+
vendorId: service.vendorId,
|
|
1927
|
+
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
1928
|
+
requestModel: (_d = req.body) === null || _d === void 0 ? void 0 : _d.model,
|
|
1929
|
+
upstreamRequest: upstreamRequestForLog,
|
|
1930
|
+
responseTime: Date.now() - startTime,
|
|
1829
1931
|
});
|
|
1830
1932
|
yield finalizeLog(isTimeout ? 504 : 500, errorMessage);
|
|
1831
1933
|
// 根据请求类型返回适当格式的错误响应
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RulesStatusWS = exports.rulesStatusBroadcaster = exports.RulesStatusBroadcaster = void 0;
|
|
4
|
+
exports.createRulesStatusWSServer = createRulesStatusWSServer;
|
|
5
|
+
// @ts-ignore - ws 类型声明可能需要手动安装 @types/ws
|
|
6
|
+
const ws_1 = require("ws");
|
|
7
|
+
/**
|
|
8
|
+
* 规则状态 WebSocket 连接管理
|
|
9
|
+
*/
|
|
10
|
+
class RulesStatusWS {
|
|
11
|
+
constructor(ws, req) {
|
|
12
|
+
Object.defineProperty(this, "ws", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
configurable: true,
|
|
15
|
+
writable: true,
|
|
16
|
+
value: void 0
|
|
17
|
+
});
|
|
18
|
+
this.ws = ws;
|
|
19
|
+
console.log(`[RulesStatusWS] 新的 WebSocket 连接: ${req.socket.remoteAddress}`);
|
|
20
|
+
this.ws.on('close', () => {
|
|
21
|
+
console.log(`[RulesStatusWS] WebSocket 连接关闭`);
|
|
22
|
+
});
|
|
23
|
+
this.ws.on('error', (err) => {
|
|
24
|
+
console.error(`[RulesStatusWS] WebSocket 错误:`, err);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 发送规则状态消息到客户端
|
|
29
|
+
*/
|
|
30
|
+
sendStatus(message) {
|
|
31
|
+
if (this.ws.readyState === ws_1.WebSocket.OPEN) {
|
|
32
|
+
this.ws.send(JSON.stringify(message));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.RulesStatusWS = RulesStatusWS;
|
|
37
|
+
/**
|
|
38
|
+
* 规则状态广播服务
|
|
39
|
+
* 管理所有连接的客户端,负责广播规则使用状态
|
|
40
|
+
*/
|
|
41
|
+
class RulesStatusBroadcaster {
|
|
42
|
+
constructor() {
|
|
43
|
+
Object.defineProperty(this, "clients", {
|
|
44
|
+
enumerable: true,
|
|
45
|
+
configurable: true,
|
|
46
|
+
writable: true,
|
|
47
|
+
value: new Set()
|
|
48
|
+
});
|
|
49
|
+
Object.defineProperty(this, "activeRules", {
|
|
50
|
+
enumerable: true,
|
|
51
|
+
configurable: true,
|
|
52
|
+
writable: true,
|
|
53
|
+
value: new Map()
|
|
54
|
+
}); // routeId -> Set of ruleIds
|
|
55
|
+
Object.defineProperty(this, "ruleTimeouts", {
|
|
56
|
+
enumerable: true,
|
|
57
|
+
configurable: true,
|
|
58
|
+
writable: true,
|
|
59
|
+
value: new Map()
|
|
60
|
+
});
|
|
61
|
+
Object.defineProperty(this, "INACTIVITY_TIMEOUT", {
|
|
62
|
+
enumerable: true,
|
|
63
|
+
configurable: true,
|
|
64
|
+
writable: true,
|
|
65
|
+
value: 30000
|
|
66
|
+
}); // 30秒无活动后标记为空闲
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 添加客户端
|
|
70
|
+
*/
|
|
71
|
+
addClient(client) {
|
|
72
|
+
this.clients.add(client);
|
|
73
|
+
console.log(`[RulesStatusBroadcaster] 客户端已连接,当前客户端数: ${this.clients.size}`);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 移除客户端
|
|
77
|
+
*/
|
|
78
|
+
removeClient(client) {
|
|
79
|
+
this.clients.delete(client);
|
|
80
|
+
console.log(`[RulesStatusBroadcaster] 客户端已断开,当前客户端数: ${this.clients.size}`);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* 标记规则正在使用
|
|
84
|
+
*/
|
|
85
|
+
markRuleInUse(routeId, ruleId) {
|
|
86
|
+
// 添加到活动规则集合
|
|
87
|
+
if (!this.activeRules.has(routeId)) {
|
|
88
|
+
this.activeRules.set(routeId, new Set());
|
|
89
|
+
}
|
|
90
|
+
this.activeRules.get(routeId).add(ruleId);
|
|
91
|
+
// 清除之前的超时定时器
|
|
92
|
+
const timeoutKey = `${routeId}:${ruleId}`;
|
|
93
|
+
const existingTimeout = this.ruleTimeouts.get(timeoutKey);
|
|
94
|
+
if (existingTimeout) {
|
|
95
|
+
clearTimeout(existingTimeout);
|
|
96
|
+
}
|
|
97
|
+
// 广播状态
|
|
98
|
+
this.broadcastStatus({
|
|
99
|
+
type: 'rule_status',
|
|
100
|
+
data: {
|
|
101
|
+
ruleId,
|
|
102
|
+
status: 'in_use',
|
|
103
|
+
timestamp: Date.now(),
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
// 设置超时定时器,如果30秒内没有新活动则标记为空闲
|
|
107
|
+
const timeout = setTimeout(() => {
|
|
108
|
+
this.markRuleIdle(routeId, ruleId);
|
|
109
|
+
}, this.INACTIVITY_TIMEOUT);
|
|
110
|
+
this.ruleTimeouts.set(timeoutKey, timeout);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 标记规则空闲
|
|
114
|
+
*/
|
|
115
|
+
markRuleIdle(routeId, ruleId) {
|
|
116
|
+
const timeoutKey = `${routeId}:${ruleId}`;
|
|
117
|
+
// 清除超时定时器
|
|
118
|
+
const existingTimeout = this.ruleTimeouts.get(timeoutKey);
|
|
119
|
+
if (existingTimeout) {
|
|
120
|
+
clearTimeout(existingTimeout);
|
|
121
|
+
this.ruleTimeouts.delete(timeoutKey);
|
|
122
|
+
}
|
|
123
|
+
// 从活动规则集合中移除
|
|
124
|
+
if (this.activeRules.has(routeId)) {
|
|
125
|
+
this.activeRules.get(routeId).delete(ruleId);
|
|
126
|
+
if (this.activeRules.get(routeId).size === 0) {
|
|
127
|
+
this.activeRules.delete(routeId);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// 广播空闲状态
|
|
131
|
+
this.broadcastStatus({
|
|
132
|
+
type: 'rule_status',
|
|
133
|
+
data: {
|
|
134
|
+
ruleId,
|
|
135
|
+
status: 'idle',
|
|
136
|
+
timestamp: Date.now(),
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 广播规则使用量更新
|
|
142
|
+
*/
|
|
143
|
+
broadcastUsageUpdate(ruleId, totalTokensUsed, totalRequestsUsed) {
|
|
144
|
+
this.broadcastStatus({
|
|
145
|
+
type: 'rule_status',
|
|
146
|
+
data: {
|
|
147
|
+
ruleId,
|
|
148
|
+
status: 'in_use',
|
|
149
|
+
totalTokensUsed,
|
|
150
|
+
totalRequestsUsed,
|
|
151
|
+
timestamp: Date.now(),
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* 广播消息到所有客户端
|
|
157
|
+
*/
|
|
158
|
+
broadcastStatus(message) {
|
|
159
|
+
const deadClients = [];
|
|
160
|
+
this.clients.forEach((client) => {
|
|
161
|
+
try {
|
|
162
|
+
client.sendStatus(message);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
console.error('[RulesStatusBroadcaster] 发送消息失败:', error);
|
|
166
|
+
deadClients.push(client);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
// 清理断开的客户端
|
|
170
|
+
deadClients.forEach((client) => {
|
|
171
|
+
this.removeClient(client);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* 获取当前活动的规则列表
|
|
176
|
+
*/
|
|
177
|
+
getActiveRules() {
|
|
178
|
+
return new Map(this.activeRules);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
exports.RulesStatusBroadcaster = RulesStatusBroadcaster;
|
|
182
|
+
// 全局单例
|
|
183
|
+
exports.rulesStatusBroadcaster = new RulesStatusBroadcaster();
|
|
184
|
+
/**
|
|
185
|
+
* 创建 WebSocket 服务器用于规则状态
|
|
186
|
+
*/
|
|
187
|
+
function createRulesStatusWSServer() {
|
|
188
|
+
const wss = new ws_1.WebSocketServer({ noServer: true });
|
|
189
|
+
wss.on('connection', (ws, req) => {
|
|
190
|
+
const wsHandler = new RulesStatusWS(ws, req);
|
|
191
|
+
exports.rulesStatusBroadcaster.addClient(wsHandler);
|
|
192
|
+
ws.on('close', () => {
|
|
193
|
+
exports.rulesStatusBroadcaster.removeClient(wsHandler);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
return wss;
|
|
197
|
+
}
|