aicodeswitch 3.9.4 → 4.0.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/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 +449 -78
- package/dist/server/main.js +392 -292
- package/dist/server/original-config-reader.js +154 -88
- package/dist/server/proxy-server.js +1108 -439
- package/dist/server/rules-status-service.js +241 -127
- package/dist/server/transformers/chunk-collector.js +64 -9
- package/dist/server/transformers/streaming.js +2357 -285
- package/dist/server/transformers/transformers.js +1767 -0
- package/dist/server/version-check.js +3 -2
- package/dist/ui/assets/index-D4Fimqi6.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,197 +1,311 @@
|
|
|
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
|
-
exports.
|
|
4
|
-
exports.createRulesStatusWSServer = createRulesStatusWSServer;
|
|
5
|
-
// @ts-ignore - ws 类型声明可能需要手动安装 @types/ws
|
|
6
|
-
const ws_1 = require("ws");
|
|
12
|
+
exports.rulesStatusBroadcaster = exports.RulesStatusBroadcaster = void 0;
|
|
7
13
|
/**
|
|
8
|
-
*
|
|
14
|
+
* 规则状态管理服务
|
|
15
|
+
* 负责管理所有规则的状态(使用中、空闲、错误、挂起)
|
|
9
16
|
*/
|
|
10
|
-
class
|
|
11
|
-
constructor(
|
|
12
|
-
Object.defineProperty(this, "
|
|
17
|
+
class RulesStatusBroadcaster {
|
|
18
|
+
constructor() {
|
|
19
|
+
Object.defineProperty(this, "ruleStates", {
|
|
13
20
|
enumerable: true,
|
|
14
21
|
configurable: true,
|
|
15
22
|
writable: true,
|
|
16
|
-
value:
|
|
17
|
-
});
|
|
18
|
-
this
|
|
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", {
|
|
23
|
+
value: new Map()
|
|
24
|
+
}); // ruleId -> RuleStatusData
|
|
25
|
+
Object.defineProperty(this, "ruleTimeouts", {
|
|
44
26
|
enumerable: true,
|
|
45
27
|
configurable: true,
|
|
46
28
|
writable: true,
|
|
47
|
-
value: new
|
|
29
|
+
value: new Map()
|
|
48
30
|
});
|
|
49
|
-
Object.defineProperty(this, "
|
|
31
|
+
Object.defineProperty(this, "idleDebounceTimeouts", {
|
|
50
32
|
enumerable: true,
|
|
51
33
|
configurable: true,
|
|
52
34
|
writable: true,
|
|
53
35
|
value: new Map()
|
|
54
|
-
});
|
|
55
|
-
Object.defineProperty(this, "
|
|
36
|
+
});
|
|
37
|
+
Object.defineProperty(this, "syncInterval", {
|
|
56
38
|
enumerable: true,
|
|
57
39
|
configurable: true,
|
|
58
40
|
writable: true,
|
|
59
|
-
value:
|
|
41
|
+
value: null
|
|
42
|
+
});
|
|
43
|
+
Object.defineProperty(this, "blacklistChecker", {
|
|
44
|
+
enumerable: true,
|
|
45
|
+
configurable: true,
|
|
46
|
+
writable: true,
|
|
47
|
+
value: null
|
|
60
48
|
});
|
|
61
49
|
Object.defineProperty(this, "INACTIVITY_TIMEOUT", {
|
|
50
|
+
enumerable: true,
|
|
51
|
+
configurable: true,
|
|
52
|
+
writable: true,
|
|
53
|
+
value: 10000
|
|
54
|
+
}); // 10秒无活动后标记为空闲
|
|
55
|
+
Object.defineProperty(this, "IDLE_DEBOUNCE_DELAY", {
|
|
56
|
+
enumerable: true,
|
|
57
|
+
configurable: true,
|
|
58
|
+
writable: true,
|
|
59
|
+
value: 3000
|
|
60
|
+
}); // idle 延迟3秒,避免对话快速进入 in_use 时闪烁
|
|
61
|
+
Object.defineProperty(this, "SYNC_INTERVAL", {
|
|
62
|
+
enumerable: true,
|
|
63
|
+
configurable: true,
|
|
64
|
+
writable: true,
|
|
65
|
+
value: 10000
|
|
66
|
+
}); // 每10秒检查一次 suspended 和 error 状态
|
|
67
|
+
Object.defineProperty(this, "ERROR_RECOVERY_TIMEOUT", {
|
|
62
68
|
enumerable: true,
|
|
63
69
|
configurable: true,
|
|
64
70
|
writable: true,
|
|
65
71
|
value: 30000
|
|
66
|
-
}); // 30
|
|
72
|
+
}); // error 状态30秒后自动恢复
|
|
73
|
+
// 启动定期状态检查定时器
|
|
74
|
+
this.startSyncInterval();
|
|
67
75
|
}
|
|
68
76
|
/**
|
|
69
|
-
*
|
|
77
|
+
* 设置黑名单检查函数
|
|
70
78
|
*/
|
|
71
|
-
|
|
72
|
-
this.
|
|
73
|
-
console.log(`[RulesStatusBroadcaster] 客户端已连接,当前客户端数: ${this.clients.size}`);
|
|
79
|
+
setBlacklistChecker(checker) {
|
|
80
|
+
this.blacklistChecker = checker;
|
|
74
81
|
}
|
|
75
82
|
/**
|
|
76
|
-
*
|
|
83
|
+
* 启动定期状态检查定时器
|
|
77
84
|
*/
|
|
78
|
-
|
|
79
|
-
this.
|
|
80
|
-
|
|
85
|
+
startSyncInterval() {
|
|
86
|
+
this.syncInterval = setInterval(() => {
|
|
87
|
+
this.checkSuspendedAndErrorRules();
|
|
88
|
+
}, this.SYNC_INTERVAL);
|
|
81
89
|
}
|
|
82
90
|
/**
|
|
83
|
-
*
|
|
91
|
+
* 检查 suspended 和 error 状态的规则是否已恢复
|
|
84
92
|
*/
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
checkSuspendedAndErrorRules() {
|
|
94
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
// 1. 检查所有 error 状态的规则,如果超过恢复时间则自动恢复为 idle
|
|
97
|
+
this.ruleStates.forEach((data, ruleId) => {
|
|
98
|
+
if (data.status === 'error' &&
|
|
99
|
+
data.timestamp &&
|
|
100
|
+
now - data.timestamp > this.ERROR_RECOVERY_TIMEOUT) {
|
|
101
|
+
console.log(`[RulesStatusBroadcaster] 规则 ${ruleId} 错误状态已超时,自动恢复为 idle 状态`);
|
|
102
|
+
this.ruleStates.set(ruleId, {
|
|
103
|
+
ruleId,
|
|
104
|
+
status: 'idle',
|
|
105
|
+
routeId: data.routeId,
|
|
106
|
+
timestamp: now,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
// 2. 检查所有 suspended 状态的规则,如果黑名单已过期则恢复为 idle
|
|
111
|
+
if (this.blacklistChecker) {
|
|
112
|
+
const suspendedRules = [];
|
|
113
|
+
this.ruleStates.forEach((data, ruleId) => {
|
|
114
|
+
if (data.status === 'suspended' &&
|
|
115
|
+
data.serviceId &&
|
|
116
|
+
data.routeId &&
|
|
117
|
+
data.contentType) {
|
|
118
|
+
suspendedRules.push({
|
|
119
|
+
ruleId,
|
|
120
|
+
serviceId: data.serviceId,
|
|
121
|
+
routeId: data.routeId,
|
|
122
|
+
contentType: data.contentType,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
// 检查每个 suspended 规则的黑名单状态
|
|
127
|
+
for (const { ruleId, serviceId, routeId, contentType } of suspendedRules) {
|
|
128
|
+
try {
|
|
129
|
+
const isBlacklisted = yield this.blacklistChecker(serviceId, routeId, contentType);
|
|
130
|
+
if (!isBlacklisted) {
|
|
131
|
+
// 黑名单已过期,恢复为 idle 状态
|
|
132
|
+
console.log(`[RulesStatusBroadcaster] 规则 ${ruleId} 黑名单已过期,恢复为 idle 状态`);
|
|
133
|
+
this.ruleStates.set(ruleId, {
|
|
134
|
+
ruleId,
|
|
135
|
+
status: 'idle',
|
|
136
|
+
routeId,
|
|
137
|
+
contentType,
|
|
138
|
+
timestamp: now,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error(`[RulesStatusBroadcaster] 检查黑名单状态失败:`, error);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 清除规则的超时定时器
|
|
151
|
+
*/
|
|
152
|
+
clearRuleTimeout(timeoutKey) {
|
|
93
153
|
const existingTimeout = this.ruleTimeouts.get(timeoutKey);
|
|
94
154
|
if (existingTimeout) {
|
|
95
155
|
clearTimeout(existingTimeout);
|
|
156
|
+
this.ruleTimeouts.delete(timeoutKey);
|
|
96
157
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* 清除规则的 idle debounce 定时器
|
|
161
|
+
*/
|
|
162
|
+
clearIdleDebounce(timeoutKey) {
|
|
163
|
+
const existing = this.idleDebounceTimeouts.get(timeoutKey);
|
|
164
|
+
if (existing) {
|
|
165
|
+
clearTimeout(existing);
|
|
166
|
+
this.idleDebounceTimeouts.delete(timeoutKey);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* 更新规则状态
|
|
171
|
+
*/
|
|
172
|
+
updateRuleStatus(data) {
|
|
173
|
+
// 更新本地状态
|
|
174
|
+
this.ruleStates.set(data.ruleId, data);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* 标记规则正在使用
|
|
178
|
+
*/
|
|
179
|
+
markRuleInUse(routeId, ruleId) {
|
|
180
|
+
const timeoutKey = `${routeId}:${ruleId}`;
|
|
181
|
+
// 清除之前的超时定时器和 idle debounce
|
|
182
|
+
this.clearRuleTimeout(timeoutKey);
|
|
183
|
+
this.clearIdleDebounce(timeoutKey);
|
|
184
|
+
// 更新状态
|
|
185
|
+
this.updateRuleStatus({
|
|
186
|
+
ruleId,
|
|
187
|
+
status: 'in_use',
|
|
188
|
+
routeId,
|
|
189
|
+
timestamp: Date.now(),
|
|
105
190
|
});
|
|
106
|
-
// 设置超时定时器,如果
|
|
191
|
+
// 设置超时定时器,如果10秒内没有新活动则标记为空闲
|
|
107
192
|
const timeout = setTimeout(() => {
|
|
108
193
|
this.markRuleIdle(routeId, ruleId);
|
|
109
194
|
}, this.INACTIVITY_TIMEOUT);
|
|
110
195
|
this.ruleTimeouts.set(timeoutKey, timeout);
|
|
111
196
|
}
|
|
112
197
|
/**
|
|
113
|
-
*
|
|
198
|
+
* 标记规则空闲(带3秒 debounce,避免对话快速进入 in_use 时状态闪烁)
|
|
114
199
|
*/
|
|
115
200
|
markRuleIdle(routeId, ruleId) {
|
|
116
201
|
const timeoutKey = `${routeId}:${ruleId}`;
|
|
117
202
|
// 清除超时定时器
|
|
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: {
|
|
203
|
+
this.clearRuleTimeout(timeoutKey);
|
|
204
|
+
// 清除已有的 idle debounce,重新计时
|
|
205
|
+
this.clearIdleDebounce(timeoutKey);
|
|
206
|
+
const debounce = setTimeout(() => {
|
|
207
|
+
this.idleDebounceTimeouts.delete(timeoutKey);
|
|
208
|
+
this.updateRuleStatus({
|
|
134
209
|
ruleId,
|
|
135
210
|
status: 'idle',
|
|
211
|
+
routeId,
|
|
136
212
|
timestamp: Date.now(),
|
|
137
|
-
}
|
|
138
|
-
});
|
|
213
|
+
});
|
|
214
|
+
}, this.IDLE_DEBOUNCE_DELAY);
|
|
215
|
+
this.idleDebounceTimeouts.set(timeoutKey, debounce);
|
|
139
216
|
}
|
|
140
217
|
/**
|
|
141
|
-
*
|
|
218
|
+
* 标记规则错误
|
|
142
219
|
*/
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
220
|
+
markRuleError(routeId, ruleId, errorMessage) {
|
|
221
|
+
const timeoutKey = `${routeId}:${ruleId}`;
|
|
222
|
+
// 清除超时定时器和 idle debounce,避免 error 状态被延迟的 idle 覆盖
|
|
223
|
+
this.clearRuleTimeout(timeoutKey);
|
|
224
|
+
this.clearIdleDebounce(timeoutKey);
|
|
225
|
+
// 更新状态
|
|
226
|
+
this.updateRuleStatus({
|
|
227
|
+
ruleId,
|
|
228
|
+
status: 'error',
|
|
229
|
+
routeId,
|
|
230
|
+
errorMessage,
|
|
231
|
+
timestamp: Date.now(),
|
|
153
232
|
});
|
|
154
233
|
}
|
|
155
234
|
/**
|
|
156
|
-
*
|
|
235
|
+
* 标记规则被挂起(进入黑名单)
|
|
157
236
|
*/
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
237
|
+
markRuleSuspended(routeId, ruleId, serviceId, contentType, errorMessage, errorType) {
|
|
238
|
+
const timeoutKey = `${routeId}:${ruleId}`;
|
|
239
|
+
// 清除超时定时器和 idle debounce,避免 suspended 状态被延迟的 idle 覆盖
|
|
240
|
+
this.clearRuleTimeout(timeoutKey);
|
|
241
|
+
this.clearIdleDebounce(timeoutKey);
|
|
242
|
+
// 更新状态,同时存储 routeId、serviceId 和 contentType 用于恢复检查
|
|
243
|
+
this.updateRuleStatus({
|
|
244
|
+
ruleId,
|
|
245
|
+
status: 'suspended',
|
|
246
|
+
routeId,
|
|
247
|
+
serviceId,
|
|
248
|
+
contentType,
|
|
249
|
+
errorMessage,
|
|
250
|
+
errorType,
|
|
251
|
+
timestamp: Date.now(),
|
|
168
252
|
});
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* 更新规则使用量
|
|
256
|
+
*/
|
|
257
|
+
updateRuleUsage(ruleId, totalTokensUsed, totalRequestsUsed) {
|
|
258
|
+
// 获取当前状态,保留其他字段
|
|
259
|
+
const currentStatus = this.ruleStates.get(ruleId);
|
|
260
|
+
this.updateRuleStatus({
|
|
261
|
+
ruleId,
|
|
262
|
+
status: (currentStatus === null || currentStatus === void 0 ? void 0 : currentStatus.status) || 'in_use',
|
|
263
|
+
routeId: currentStatus === null || currentStatus === void 0 ? void 0 : currentStatus.routeId,
|
|
264
|
+
contentType: currentStatus === null || currentStatus === void 0 ? void 0 : currentStatus.contentType,
|
|
265
|
+
totalTokensUsed,
|
|
266
|
+
totalRequestsUsed,
|
|
267
|
+
timestamp: Date.now(),
|
|
172
268
|
});
|
|
173
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* 清除指定规则的状态
|
|
272
|
+
*/
|
|
273
|
+
clearRuleStatus(ruleId) {
|
|
274
|
+
this.ruleStates.delete(ruleId);
|
|
275
|
+
}
|
|
174
276
|
/**
|
|
175
277
|
* 获取当前活动的规则列表
|
|
176
278
|
*/
|
|
177
279
|
getActiveRules() {
|
|
178
|
-
|
|
280
|
+
const activeRuleIds = [];
|
|
281
|
+
this.ruleStates.forEach((data, ruleId) => {
|
|
282
|
+
if (data.status === 'in_use') {
|
|
283
|
+
activeRuleIds.push(ruleId);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
return activeRuleIds;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* 获取所有规则的状态(用于 HTTP 轮询)
|
|
290
|
+
*/
|
|
291
|
+
getAllRuleStatuses() {
|
|
292
|
+
return Array.from(this.ruleStates.values());
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* 销毁广播器(清理定时器)
|
|
296
|
+
*/
|
|
297
|
+
destroy() {
|
|
298
|
+
if (this.syncInterval) {
|
|
299
|
+
clearInterval(this.syncInterval);
|
|
300
|
+
this.syncInterval = null;
|
|
301
|
+
}
|
|
302
|
+
this.ruleTimeouts.forEach((timeout) => clearTimeout(timeout));
|
|
303
|
+
this.ruleTimeouts.clear();
|
|
304
|
+
this.idleDebounceTimeouts.forEach((timeout) => clearTimeout(timeout));
|
|
305
|
+
this.idleDebounceTimeouts.clear();
|
|
306
|
+
this.ruleStates.clear();
|
|
179
307
|
}
|
|
180
308
|
}
|
|
181
309
|
exports.RulesStatusBroadcaster = RulesStatusBroadcaster;
|
|
182
310
|
// 全局单例
|
|
183
311
|
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
|
-
}
|
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SSEEventCollectorTransform = exports.ChunkCollectorTransform = void 0;
|
|
4
4
|
const stream_1 = require("stream");
|
|
5
|
+
const string_decoder_1 = require("string_decoder");
|
|
6
|
+
/**
|
|
7
|
+
* 检测是否是客户端断开相关的错误(这些错误是正常的,不应记录为错误)
|
|
8
|
+
*/
|
|
9
|
+
function isClientDisconnectError(error) {
|
|
10
|
+
const code = error === null || error === void 0 ? void 0 : error.code;
|
|
11
|
+
const message = typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' ? error.message.toLowerCase() : '';
|
|
12
|
+
return (code === 'ERR_STREAM_PREMATURE_CLOSE' ||
|
|
13
|
+
code === 'ERR_STREAM_UNABLE_TO_PIPE' ||
|
|
14
|
+
code === 'ERR_STREAM_DESTROYED' ||
|
|
15
|
+
message.includes('premature close'));
|
|
16
|
+
}
|
|
5
17
|
/**
|
|
6
18
|
* ChunkCollectorTransform - 收集stream chunks用于日志记录
|
|
7
19
|
* 这个Transform会记录所有经过它的数据块,同时将数据原封不动地传递给下一个stream
|
|
@@ -21,8 +33,19 @@ class ChunkCollectorTransform extends stream_1.Transform {
|
|
|
21
33
|
writable: true,
|
|
22
34
|
value: false
|
|
23
35
|
});
|
|
36
|
+
Object.defineProperty(this, "stringDecoder", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
configurable: true,
|
|
39
|
+
writable: true,
|
|
40
|
+
value: new string_decoder_1.StringDecoder('utf8')
|
|
41
|
+
});
|
|
24
42
|
this.on('error', (err) => {
|
|
25
|
-
|
|
43
|
+
if (isClientDisconnectError(err)) {
|
|
44
|
+
console.warn('[ChunkCollectorTransform] Stream closed (client disconnected)');
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.error('[ChunkCollectorTransform] Stream error:', err);
|
|
48
|
+
}
|
|
26
49
|
this.errorEmitted = true;
|
|
27
50
|
});
|
|
28
51
|
}
|
|
@@ -37,7 +60,8 @@ class ChunkCollectorTransform extends stream_1.Transform {
|
|
|
37
60
|
this.chunks.push(JSON.stringify(chunk));
|
|
38
61
|
}
|
|
39
62
|
else {
|
|
40
|
-
|
|
63
|
+
// 使用 StringDecoder 正确处理多字节字符边界,避免中文乱码
|
|
64
|
+
this.chunks.push(this.stringDecoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
|
41
65
|
}
|
|
42
66
|
// 将chunk传递给下一个stream
|
|
43
67
|
this.push(chunk);
|
|
@@ -48,6 +72,20 @@ class ChunkCollectorTransform extends stream_1.Transform {
|
|
|
48
72
|
callback();
|
|
49
73
|
}
|
|
50
74
|
}
|
|
75
|
+
_flush(callback) {
|
|
76
|
+
try {
|
|
77
|
+
// 处理 StringDecoder 中剩余的字节
|
|
78
|
+
const remaining = this.stringDecoder.end();
|
|
79
|
+
if (remaining) {
|
|
80
|
+
this.chunks.push(remaining);
|
|
81
|
+
}
|
|
82
|
+
callback();
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
console.error('[ChunkCollectorTransform] Error in _flush:', error);
|
|
86
|
+
callback();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
51
89
|
/**
|
|
52
90
|
* 获取收集的所有chunks
|
|
53
91
|
*/
|
|
@@ -97,8 +135,19 @@ class SSEEventCollectorTransform extends stream_1.Transform {
|
|
|
97
135
|
writable: true,
|
|
98
136
|
value: false
|
|
99
137
|
});
|
|
138
|
+
Object.defineProperty(this, "stringDecoder", {
|
|
139
|
+
enumerable: true,
|
|
140
|
+
configurable: true,
|
|
141
|
+
writable: true,
|
|
142
|
+
value: new string_decoder_1.StringDecoder('utf8')
|
|
143
|
+
});
|
|
100
144
|
this.on('error', (err) => {
|
|
101
|
-
|
|
145
|
+
if (isClientDisconnectError(err)) {
|
|
146
|
+
console.warn('[SSEEventCollectorTransform] Stream closed (client disconnected)');
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
console.error('[SSEEventCollectorTransform] Stream error:', err);
|
|
150
|
+
}
|
|
102
151
|
this.errorEmitted = true;
|
|
103
152
|
});
|
|
104
153
|
}
|
|
@@ -108,7 +157,7 @@ class SSEEventCollectorTransform extends stream_1.Transform {
|
|
|
108
157
|
return;
|
|
109
158
|
}
|
|
110
159
|
try {
|
|
111
|
-
// 如果是对象(来自 SSEParserTransform
|
|
160
|
+
// 如果是对象(来自 SSEParserTransform 或上游转换器),先转换为字符串格式进行处理
|
|
112
161
|
if (typeof chunk === 'object' && chunk !== null) {
|
|
113
162
|
const sseEvent = chunk;
|
|
114
163
|
const lines = [];
|
|
@@ -136,12 +185,12 @@ class SSEEventCollectorTransform extends stream_1.Transform {
|
|
|
136
185
|
}
|
|
137
186
|
this.flushEvent();
|
|
138
187
|
}
|
|
139
|
-
//
|
|
188
|
+
// 对象模式下保持原样透传,避免影响后续转换器读取 event/data 字段
|
|
140
189
|
this.push(chunk);
|
|
141
190
|
}
|
|
142
191
|
else {
|
|
143
|
-
// Buffer/string 模式
|
|
144
|
-
this.buffer += chunk.
|
|
192
|
+
// Buffer/string 模式 - 使用 StringDecoder 正确处理多字节字符边界
|
|
193
|
+
this.buffer += this.stringDecoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
145
194
|
this.processBuffer();
|
|
146
195
|
// 将chunk传递给下一个stream
|
|
147
196
|
this.push(chunk);
|
|
@@ -155,12 +204,18 @@ class SSEEventCollectorTransform extends stream_1.Transform {
|
|
|
155
204
|
}
|
|
156
205
|
_flush(callback) {
|
|
157
206
|
try {
|
|
207
|
+
// 处理 StringDecoder 中剩余的字节
|
|
208
|
+
const remaining = this.stringDecoder.end();
|
|
209
|
+
if (remaining) {
|
|
210
|
+
this.buffer += remaining;
|
|
211
|
+
}
|
|
158
212
|
// 处理剩余的buffer
|
|
159
213
|
if (this.buffer.trim()) {
|
|
160
214
|
this.processBuffer();
|
|
161
215
|
}
|
|
162
216
|
// 刷新最后一个事件
|
|
163
217
|
this.flushEvent();
|
|
218
|
+
// 不调用 this.end(),让 Node.js 自动管理流的生命周期
|
|
164
219
|
callback();
|
|
165
220
|
}
|
|
166
221
|
catch (error) {
|
|
@@ -259,8 +314,8 @@ class SSEEventCollectorTransform extends stream_1.Transform {
|
|
|
259
314
|
}
|
|
260
315
|
}
|
|
261
316
|
// 4. 直接在顶级的usage字段
|
|
262
|
-
if (data.input_tokens !== undefined || data.output_tokens !== undefined ||
|
|
263
|
-
data.prompt_tokens !== undefined || data.completion_tokens !== undefined) {
|
|
317
|
+
if ((data === null || data === void 0 ? void 0 : data.input_tokens) !== undefined || (data === null || data === void 0 ? void 0 : data.output_tokens) !== undefined ||
|
|
318
|
+
(data === null || data === void 0 ? void 0 : data.prompt_tokens) !== undefined || (data === null || data === void 0 ? void 0 : data.completion_tokens) !== undefined) {
|
|
264
319
|
return data;
|
|
265
320
|
}
|
|
266
321
|
}
|