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.
- package/README.md +37 -36
- package/UPGRADE.md +5 -3
- package/bin/restore.js +126 -22
- package/bin/start.js +29 -41
- package/bin/stop.js +3 -3
- package/bin/utils/config-helpers.js +198 -0
- package/dist/server/config-managed-fields.js +69 -0
- package/dist/server/config-merge.js +260 -0
- package/dist/server/database-factory.js +11 -181
- package/dist/server/fs-database.js +209 -72
- package/dist/server/main.js +365 -283
- package/dist/server/original-config-reader.js +154 -88
- package/dist/server/proxy-server.js +993 -387
- package/dist/server/rules-status-service.js +285 -54
- package/dist/server/transformers/chunk-collector.js +26 -4
- package/dist/server/transformers/streaming.js +2334 -280
- package/dist/server/transformers/transformers.js +1765 -0
- package/dist/ui/assets/index-GQBwe1Rm.js +514 -0
- package/dist/ui/index.html +1 -1
- package/package.json +4 -8
- package/schema/claude.schema.md +15 -14
- package/schema/deepseek-chat.schema.md +799 -0
- package/schema/{openai.schema.md → openai-chat-completions.schema.md} +9 -1083
- package/schema/openai-responses.schema.md +226196 -0
- package/schema/stream.md +2592 -0
- package/dist/server/database.js +0 -1609
- package/dist/server/migrate-to-fs.js +0 -353
- package/dist/server/transformers/claude-openai.js +0 -868
- package/dist/server/transformers/gemini.js +0 -625
- package/dist/ui/assets/index-BqSYpNgU.js +0 -511
|
@@ -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
|
-
|
|
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, "
|
|
58
|
+
Object.defineProperty(this, "ruleStates", {
|
|
50
59
|
enumerable: true,
|
|
51
60
|
configurable: true,
|
|
52
61
|
writable: true,
|
|
53
62
|
value: new Map()
|
|
54
|
-
}); //
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
// 设置超时定时器,如果
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
380
|
+
broadcastMessage(message) {
|
|
159
381
|
const deadClients = [];
|
|
160
382
|
this.clients.forEach((client) => {
|
|
161
383
|
try {
|
|
162
|
-
client.
|
|
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
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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) {
|