aicodeswitch 3.9.4 → 4.0.0

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.
@@ -1,4 +1,13 @@
1
1
  "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
2
11
  Object.defineProperty(exports, "__esModule", { value: true });
3
12
  exports.RulesStatusWS = exports.rulesStatusBroadcaster = exports.RulesStatusBroadcaster = void 0;
4
13
  exports.createRulesStatusWSServer = createRulesStatusWSServer;
@@ -25,9 +34,9 @@ class RulesStatusWS {
25
34
  });
26
35
  }
27
36
  /**
28
- * 发送规则状态消息到客户端
37
+ * 发送消息到客户端
29
38
  */
30
- sendStatus(message) {
39
+ sendMessage(message) {
31
40
  if (this.ws.readyState === ws_1.WebSocket.OPEN) {
32
41
  this.ws.send(JSON.stringify(message));
33
42
  }
@@ -46,24 +55,155 @@ class RulesStatusBroadcaster {
46
55
  writable: true,
47
56
  value: new Set()
48
57
  });
49
- Object.defineProperty(this, "activeRules", {
58
+ Object.defineProperty(this, "ruleStates", {
50
59
  enumerable: true,
51
60
  configurable: true,
52
61
  writable: true,
53
62
  value: new Map()
54
- }); // routeId -> Set of ruleIds
63
+ }); // ruleId -> RuleStatusData
55
64
  Object.defineProperty(this, "ruleTimeouts", {
56
65
  enumerable: true,
57
66
  configurable: true,
58
67
  writable: true,
59
68
  value: new Map()
60
69
  });
70
+ Object.defineProperty(this, "idleDebounceTimeouts", {
71
+ enumerable: true,
72
+ configurable: true,
73
+ writable: true,
74
+ value: new Map()
75
+ });
76
+ Object.defineProperty(this, "syncInterval", {
77
+ enumerable: true,
78
+ configurable: true,
79
+ writable: true,
80
+ value: null
81
+ });
82
+ Object.defineProperty(this, "blacklistChecker", {
83
+ enumerable: true,
84
+ configurable: true,
85
+ writable: true,
86
+ value: null
87
+ });
61
88
  Object.defineProperty(this, "INACTIVITY_TIMEOUT", {
89
+ enumerable: true,
90
+ configurable: true,
91
+ writable: true,
92
+ value: 10000
93
+ }); // 10秒无活动后标记为空闲
94
+ Object.defineProperty(this, "IDLE_DEBOUNCE_DELAY", {
95
+ enumerable: true,
96
+ configurable: true,
97
+ writable: true,
98
+ value: 3000
99
+ }); // idle 广播延迟3秒,避免对话快速进入 in_use 时闪烁
100
+ Object.defineProperty(this, "SYNC_INTERVAL", {
101
+ enumerable: true,
102
+ configurable: true,
103
+ writable: true,
104
+ value: 10000
105
+ }); // 每10秒全量同步一次
106
+ Object.defineProperty(this, "ERROR_RECOVERY_TIMEOUT", {
62
107
  enumerable: true,
63
108
  configurable: true,
64
109
  writable: true,
65
110
  value: 30000
66
- }); // 30秒无活动后标记为空闲
111
+ }); // error 状态30秒后自动恢复
112
+ // 启动全量状态同步定时器
113
+ this.startSyncInterval();
114
+ }
115
+ /**
116
+ * 设置黑名单检查函数
117
+ */
118
+ setBlacklistChecker(checker) {
119
+ this.blacklistChecker = checker;
120
+ }
121
+ /**
122
+ * 启动全量状态同步定时器
123
+ */
124
+ startSyncInterval() {
125
+ this.syncInterval = setInterval(() => {
126
+ this.checkSuspendedRulesAndBroadcast();
127
+ }, this.SYNC_INTERVAL);
128
+ }
129
+ /**
130
+ * 检查 suspended 和 error 状态的规则是否已恢复,然后广播全量状态
131
+ */
132
+ checkSuspendedRulesAndBroadcast() {
133
+ return __awaiter(this, void 0, void 0, function* () {
134
+ if (this.clients.size === 0) {
135
+ return;
136
+ }
137
+ const now = Date.now();
138
+ // 1. 检查所有 error 状态的规则,如果超过恢复时间则自动恢复为 idle
139
+ this.ruleStates.forEach((data, ruleId) => {
140
+ if (data.status === 'error' &&
141
+ data.timestamp &&
142
+ now - data.timestamp > this.ERROR_RECOVERY_TIMEOUT) {
143
+ console.log(`[RulesStatusBroadcaster] 规则 ${ruleId} 错误状态已超时,自动恢复为 idle 状态`);
144
+ this.ruleStates.set(ruleId, {
145
+ ruleId,
146
+ status: 'idle',
147
+ routeId: data.routeId,
148
+ timestamp: now,
149
+ });
150
+ }
151
+ });
152
+ // 2. 检查所有 suspended 状态的规则,如果黑名单已过期则恢复为 idle
153
+ if (this.blacklistChecker) {
154
+ const suspendedRules = [];
155
+ this.ruleStates.forEach((data, ruleId) => {
156
+ if (data.status === 'suspended' &&
157
+ data.serviceId &&
158
+ data.routeId &&
159
+ data.contentType) {
160
+ suspendedRules.push({
161
+ ruleId,
162
+ serviceId: data.serviceId,
163
+ routeId: data.routeId,
164
+ contentType: data.contentType,
165
+ });
166
+ }
167
+ });
168
+ // 检查每个 suspended 规则的黑名单状态
169
+ for (const { ruleId, serviceId, routeId, contentType } of suspendedRules) {
170
+ try {
171
+ const isBlacklisted = yield this.blacklistChecker(serviceId, routeId, contentType);
172
+ if (!isBlacklisted) {
173
+ // 黑名单已过期,恢复为 idle 状态
174
+ console.log(`[RulesStatusBroadcaster] 规则 ${ruleId} 黑名单已过期,恢复为 idle 状态`);
175
+ this.ruleStates.set(ruleId, {
176
+ ruleId,
177
+ status: 'idle',
178
+ routeId,
179
+ contentType,
180
+ timestamp: now,
181
+ });
182
+ }
183
+ }
184
+ catch (error) {
185
+ console.error(`[RulesStatusBroadcaster] 检查黑名单状态失败:`, error);
186
+ }
187
+ }
188
+ }
189
+ // 广播全量状态
190
+ this.broadcastAllRulesStatus();
191
+ });
192
+ }
193
+ /**
194
+ * 广播所有规则状态(全量同步)
195
+ */
196
+ broadcastAllRulesStatus() {
197
+ if (this.clients.size === 0) {
198
+ return;
199
+ }
200
+ const allStatuses = Array.from(this.ruleStates.values());
201
+ const message = {
202
+ type: 'all_rules_status',
203
+ data: allStatuses,
204
+ timestamp: Date.now(),
205
+ };
206
+ this.broadcastMessage(message);
67
207
  }
68
208
  /**
69
209
  * 添加客户端
@@ -71,6 +211,16 @@ class RulesStatusBroadcaster {
71
211
  addClient(client) {
72
212
  this.clients.add(client);
73
213
  console.log(`[RulesStatusBroadcaster] 客户端已连接,当前客户端数: ${this.clients.size}`);
214
+ // 新客户端连接时,立即发送当前所有规则状态
215
+ const allStatuses = Array.from(this.ruleStates.values());
216
+ if (allStatuses.length > 0) {
217
+ const message = {
218
+ type: 'all_rules_status',
219
+ data: allStatuses,
220
+ timestamp: Date.now(),
221
+ };
222
+ client.sendMessage(message);
223
+ }
74
224
  }
75
225
  /**
76
226
  * 移除客户端
@@ -80,86 +230,158 @@ class RulesStatusBroadcaster {
80
230
  console.log(`[RulesStatusBroadcaster] 客户端已断开,当前客户端数: ${this.clients.size}`);
81
231
  }
82
232
  /**
83
- * 标记规则正在使用
233
+ * 清除规则的超时定时器
84
234
  */
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}`;
235
+ clearRuleTimeout(timeoutKey) {
93
236
  const existingTimeout = this.ruleTimeouts.get(timeoutKey);
94
237
  if (existingTimeout) {
95
238
  clearTimeout(existingTimeout);
239
+ this.ruleTimeouts.delete(timeoutKey);
240
+ }
241
+ }
242
+ /**
243
+ * 清除规则的 idle debounce 定时器
244
+ */
245
+ clearIdleDebounce(timeoutKey) {
246
+ const existing = this.idleDebounceTimeouts.get(timeoutKey);
247
+ if (existing) {
248
+ clearTimeout(existing);
249
+ this.idleDebounceTimeouts.delete(timeoutKey);
96
250
  }
97
- // 广播状态
98
- this.broadcastStatus({
251
+ }
252
+ /**
253
+ * 更新规则状态并广播
254
+ */
255
+ updateRuleStatus(data) {
256
+ // 更新本地状态
257
+ this.ruleStates.set(data.ruleId, data);
258
+ // 广播单个规则状态更新
259
+ const message = {
99
260
  type: 'rule_status',
100
- data: {
101
- ruleId,
102
- status: 'in_use',
103
- timestamp: Date.now(),
104
- },
261
+ data,
262
+ };
263
+ this.broadcastMessage(message);
264
+ }
265
+ /**
266
+ * 标记规则正在使用
267
+ */
268
+ markRuleInUse(routeId, ruleId) {
269
+ const timeoutKey = `${routeId}:${ruleId}`;
270
+ // 清除之前的超时定时器和 idle debounce
271
+ this.clearRuleTimeout(timeoutKey);
272
+ this.clearIdleDebounce(timeoutKey);
273
+ // 更新状态并广播
274
+ this.updateRuleStatus({
275
+ ruleId,
276
+ status: 'in_use',
277
+ routeId,
278
+ timestamp: Date.now(),
105
279
  });
106
- // 设置超时定时器,如果30秒内没有新活动则标记为空闲
280
+ // 设置超时定时器,如果10秒内没有新活动则标记为空闲
107
281
  const timeout = setTimeout(() => {
108
282
  this.markRuleIdle(routeId, ruleId);
109
283
  }, this.INACTIVITY_TIMEOUT);
110
284
  this.ruleTimeouts.set(timeoutKey, timeout);
111
285
  }
112
286
  /**
113
- * 标记规则空闲
287
+ * 标记规则空闲(带1秒 debounce,避免对话快速进入 in_use 时状态闪烁)
114
288
  */
115
289
  markRuleIdle(routeId, ruleId) {
116
290
  const timeoutKey = `${routeId}:${ruleId}`;
117
291
  // 清除超时定时器
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: {
292
+ this.clearRuleTimeout(timeoutKey);
293
+ // 清除已有的 idle debounce,重新计时
294
+ this.clearIdleDebounce(timeoutKey);
295
+ const debounce = setTimeout(() => {
296
+ this.idleDebounceTimeouts.delete(timeoutKey);
297
+ this.updateRuleStatus({
134
298
  ruleId,
135
299
  status: 'idle',
300
+ routeId,
136
301
  timestamp: Date.now(),
137
- },
302
+ });
303
+ }, this.IDLE_DEBOUNCE_DELAY);
304
+ this.idleDebounceTimeouts.set(timeoutKey, debounce);
305
+ }
306
+ /**
307
+ * 标记规则错误
308
+ */
309
+ markRuleError(routeId, ruleId, errorMessage) {
310
+ const timeoutKey = `${routeId}:${ruleId}`;
311
+ // 清除超时定时器和 idle debounce,避免 error 状态被延迟的 idle 覆盖
312
+ this.clearRuleTimeout(timeoutKey);
313
+ this.clearIdleDebounce(timeoutKey);
314
+ // 更新状态并广播
315
+ this.updateRuleStatus({
316
+ ruleId,
317
+ status: 'error',
318
+ routeId,
319
+ errorMessage,
320
+ timestamp: Date.now(),
321
+ });
322
+ }
323
+ /**
324
+ * 标记规则被挂起(进入黑名单)
325
+ */
326
+ markRuleSuspended(routeId, ruleId, serviceId, contentType, errorMessage, errorType) {
327
+ const timeoutKey = `${routeId}:${ruleId}`;
328
+ // 清除超时定时器和 idle debounce,避免 suspended 状态被延迟的 idle 覆盖
329
+ this.clearRuleTimeout(timeoutKey);
330
+ this.clearIdleDebounce(timeoutKey);
331
+ // 更新状态并广播,同时存储 routeId、serviceId 和 contentType 用于恢复检查
332
+ this.updateRuleStatus({
333
+ ruleId,
334
+ status: 'suspended',
335
+ routeId,
336
+ serviceId,
337
+ contentType,
338
+ errorMessage,
339
+ errorType,
340
+ timestamp: Date.now(),
138
341
  });
139
342
  }
140
343
  /**
141
344
  * 广播规则使用量更新
142
345
  */
143
346
  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
- },
347
+ // 获取当前状态,保留其他字段
348
+ const currentStatus = this.ruleStates.get(ruleId);
349
+ this.updateRuleStatus({
350
+ ruleId,
351
+ status: (currentStatus === null || currentStatus === void 0 ? void 0 : currentStatus.status) || 'in_use',
352
+ routeId: currentStatus === null || currentStatus === void 0 ? void 0 : currentStatus.routeId,
353
+ contentType: currentStatus === null || currentStatus === void 0 ? void 0 : currentStatus.contentType,
354
+ totalTokensUsed,
355
+ totalRequestsUsed,
356
+ timestamp: Date.now(),
153
357
  });
154
358
  }
359
+ /**
360
+ * 清除指定规则的状态
361
+ */
362
+ clearRuleStatus(ruleId) {
363
+ this.ruleStates.delete(ruleId);
364
+ }
365
+ /**
366
+ * 获取当前活动的规则列表
367
+ */
368
+ getActiveRules() {
369
+ const activeRuleIds = [];
370
+ this.ruleStates.forEach((data, ruleId) => {
371
+ if (data.status === 'in_use') {
372
+ activeRuleIds.push(ruleId);
373
+ }
374
+ });
375
+ return activeRuleIds;
376
+ }
155
377
  /**
156
378
  * 广播消息到所有客户端
157
379
  */
158
- broadcastStatus(message) {
380
+ broadcastMessage(message) {
159
381
  const deadClients = [];
160
382
  this.clients.forEach((client) => {
161
383
  try {
162
- client.sendStatus(message);
384
+ client.sendMessage(message);
163
385
  }
164
386
  catch (error) {
165
387
  console.error('[RulesStatusBroadcaster] 发送消息失败:', error);
@@ -172,10 +394,19 @@ class RulesStatusBroadcaster {
172
394
  });
173
395
  }
174
396
  /**
175
- * 获取当前活动的规则列表
397
+ * 销毁广播器(清理定时器)
176
398
  */
177
- getActiveRules() {
178
- return new Map(this.activeRules);
399
+ destroy() {
400
+ if (this.syncInterval) {
401
+ clearInterval(this.syncInterval);
402
+ this.syncInterval = null;
403
+ }
404
+ this.ruleTimeouts.forEach((timeout) => clearTimeout(timeout));
405
+ this.ruleTimeouts.clear();
406
+ this.idleDebounceTimeouts.forEach((timeout) => clearTimeout(timeout));
407
+ this.idleDebounceTimeouts.clear();
408
+ this.ruleStates.clear();
409
+ this.clients.clear();
179
410
  }
180
411
  }
181
412
  exports.RulesStatusBroadcaster = RulesStatusBroadcaster;
@@ -2,6 +2,17 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SSEEventCollectorTransform = exports.ChunkCollectorTransform = void 0;
4
4
  const stream_1 = require("stream");
5
+ /**
6
+ * 检测是否是客户端断开相关的错误(这些错误是正常的,不应记录为错误)
7
+ */
8
+ function isClientDisconnectError(error) {
9
+ const code = error === null || error === void 0 ? void 0 : error.code;
10
+ const message = typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' ? error.message.toLowerCase() : '';
11
+ return (code === 'ERR_STREAM_PREMATURE_CLOSE' ||
12
+ code === 'ERR_STREAM_UNABLE_TO_PIPE' ||
13
+ code === 'ERR_STREAM_DESTROYED' ||
14
+ message.includes('premature close'));
15
+ }
5
16
  /**
6
17
  * ChunkCollectorTransform - 收集stream chunks用于日志记录
7
18
  * 这个Transform会记录所有经过它的数据块,同时将数据原封不动地传递给下一个stream
@@ -22,7 +33,12 @@ class ChunkCollectorTransform extends stream_1.Transform {
22
33
  value: false
23
34
  });
24
35
  this.on('error', (err) => {
25
- console.error('[ChunkCollectorTransform] Stream error:', err);
36
+ if (isClientDisconnectError(err)) {
37
+ console.warn('[ChunkCollectorTransform] Stream closed (client disconnected)');
38
+ }
39
+ else {
40
+ console.error('[ChunkCollectorTransform] Stream error:', err);
41
+ }
26
42
  this.errorEmitted = true;
27
43
  });
28
44
  }
@@ -98,7 +114,12 @@ class SSEEventCollectorTransform extends stream_1.Transform {
98
114
  value: false
99
115
  });
100
116
  this.on('error', (err) => {
101
- console.error('[SSEEventCollectorTransform] Stream error:', err);
117
+ if (isClientDisconnectError(err)) {
118
+ console.warn('[SSEEventCollectorTransform] Stream closed (client disconnected)');
119
+ }
120
+ else {
121
+ console.error('[SSEEventCollectorTransform] Stream error:', err);
122
+ }
102
123
  this.errorEmitted = true;
103
124
  });
104
125
  }
@@ -108,7 +129,7 @@ class SSEEventCollectorTransform extends stream_1.Transform {
108
129
  return;
109
130
  }
110
131
  try {
111
- // 如果是对象(来自 SSEParserTransform),先转换为字符串格式进行处理
132
+ // 如果是对象(来自 SSEParserTransform 或上游转换器),先转换为字符串格式进行处理
112
133
  if (typeof chunk === 'object' && chunk !== null) {
113
134
  const sseEvent = chunk;
114
135
  const lines = [];
@@ -136,7 +157,7 @@ class SSEEventCollectorTransform extends stream_1.Transform {
136
157
  }
137
158
  this.flushEvent();
138
159
  }
139
- // 将原始对象传递给下一个stream
160
+ // 对象模式下保持原样透传,避免影响后续转换器读取 event/data 字段
140
161
  this.push(chunk);
141
162
  }
142
163
  else {
@@ -161,6 +182,7 @@ class SSEEventCollectorTransform extends stream_1.Transform {
161
182
  }
162
183
  // 刷新最后一个事件
163
184
  this.flushEvent();
185
+ // 不调用 this.end(),让 Node.js 自动管理流的生命周期
164
186
  callback();
165
187
  }
166
188
  catch (error) {