aicodeswitch 4.0.1 → 4.0.3
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/dist/server/auth.js +12 -6
- package/dist/server/fs-database.js +30 -2
- package/dist/server/proxy-server.js +183 -17
- package/dist/ui/assets/index-Nl6yJxrc.js +514 -0
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/dist/ui/assets/index-D4Fimqi6.js +0 -514
package/dist/server/auth.js
CHANGED
|
@@ -10,14 +10,20 @@ exports.verifyToken = verifyToken;
|
|
|
10
10
|
exports.authMiddleware = authMiddleware;
|
|
11
11
|
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
12
12
|
const crypto_1 = __importDefault(require("crypto"));
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// 延迟读取 process.env.AUTH,避免模块加载时 dotenv 尚未执行导致值始终为空
|
|
14
|
+
function getAuthCode() {
|
|
15
|
+
return process.env.AUTH || '';
|
|
16
|
+
}
|
|
17
|
+
function getJwtSecret() {
|
|
18
|
+
const authCode = getAuthCode();
|
|
19
|
+
return process.env.JWT_SECRET || (authCode ? crypto_1.default.createHash('sha256').update(authCode).digest('hex') : '');
|
|
20
|
+
}
|
|
15
21
|
const TOKEN_EXPIRY = '7d'; // 7天有效期
|
|
16
22
|
/**
|
|
17
23
|
* 检查是否启用鉴权
|
|
18
24
|
*/
|
|
19
25
|
function isAuthEnabled() {
|
|
20
|
-
return
|
|
26
|
+
return getAuthCode().trim().length > 0;
|
|
21
27
|
}
|
|
22
28
|
/**
|
|
23
29
|
* 验证鉴权码
|
|
@@ -26,7 +32,7 @@ function verifyAuthCode(authCode) {
|
|
|
26
32
|
if (!isAuthEnabled()) {
|
|
27
33
|
return true; // 未启用鉴权,直接通过
|
|
28
34
|
}
|
|
29
|
-
return authCode ===
|
|
35
|
+
return authCode === getAuthCode();
|
|
30
36
|
}
|
|
31
37
|
/**
|
|
32
38
|
* 生成 JWT Token
|
|
@@ -35,14 +41,14 @@ function generateToken() {
|
|
|
35
41
|
const payload = {
|
|
36
42
|
authenticated: true,
|
|
37
43
|
};
|
|
38
|
-
return jsonwebtoken_1.default.sign(payload,
|
|
44
|
+
return jsonwebtoken_1.default.sign(payload, getJwtSecret(), { expiresIn: TOKEN_EXPIRY });
|
|
39
45
|
}
|
|
40
46
|
/**
|
|
41
47
|
* 验证 JWT Token
|
|
42
48
|
*/
|
|
43
49
|
function verifyToken(token) {
|
|
44
50
|
try {
|
|
45
|
-
jsonwebtoken_1.default.verify(token,
|
|
51
|
+
jsonwebtoken_1.default.verify(token, getJwtSecret());
|
|
46
52
|
return true;
|
|
47
53
|
}
|
|
48
54
|
catch (error) {
|
|
@@ -233,6 +233,12 @@ class FileSystemDatabaseManager {
|
|
|
233
233
|
writable: true,
|
|
234
234
|
value: 10 * 1024 * 1024
|
|
235
235
|
}); // 10MB
|
|
236
|
+
Object.defineProperty(this, "MAX_ERROR_LOG_FIELD_SIZE", {
|
|
237
|
+
enumerable: true,
|
|
238
|
+
configurable: true,
|
|
239
|
+
writable: true,
|
|
240
|
+
value: 256 * 1024
|
|
241
|
+
}); // 256KB 单个字段最大长度
|
|
236
242
|
Object.defineProperty(this, "LOG_RETENTION_DAYS", {
|
|
237
243
|
enumerable: true,
|
|
238
244
|
configurable: true,
|
|
@@ -899,7 +905,14 @@ class FileSystemDatabaseManager {
|
|
|
899
905
|
}
|
|
900
906
|
saveErrorLogs() {
|
|
901
907
|
return __awaiter(this, void 0, void 0, function* () {
|
|
902
|
-
|
|
908
|
+
try {
|
|
909
|
+
yield promises_1.default.writeFile(this.errorLogsFile, JSON.stringify(this.errorLogs, null, 2));
|
|
910
|
+
}
|
|
911
|
+
catch (e) {
|
|
912
|
+
console.error('[DB] Failed to save error logs, clearing to prevent crash:', e);
|
|
913
|
+
this.errorLogs = [];
|
|
914
|
+
yield promises_1.default.writeFile(this.errorLogsFile, '[]');
|
|
915
|
+
}
|
|
903
916
|
this.errorLogsCountCache = null;
|
|
904
917
|
});
|
|
905
918
|
}
|
|
@@ -973,6 +986,9 @@ class FileSystemDatabaseManager {
|
|
|
973
986
|
apiKey: (_d = current === null || current === void 0 ? void 0 : current.apiKey) !== null && _d !== void 0 ? _d : '',
|
|
974
987
|
enableFailover: (_e = current === null || current === void 0 ? void 0 : current.enableFailover) !== null && _e !== void 0 ? _e : true,
|
|
975
988
|
failoverRecoverySeconds: normalizeFailoverRecoverySeconds(current === null || current === void 0 ? void 0 : current.failoverRecoverySeconds),
|
|
989
|
+
ruleGlobalTimeout: typeof (current === null || current === void 0 ? void 0 : current.ruleGlobalTimeout) === 'number' && current.ruleGlobalTimeout > 0
|
|
990
|
+
? current.ruleGlobalTimeout
|
|
991
|
+
: undefined,
|
|
976
992
|
enableAgentTeams: (_f = current === null || current === void 0 ? void 0 : current.enableAgentTeams) !== null && _f !== void 0 ? _f : false,
|
|
977
993
|
enableBypassPermissionsSupport: (_g = current === null || current === void 0 ? void 0 : current.enableBypassPermissionsSupport) !== null && _g !== void 0 ? _g : false,
|
|
978
994
|
codexModelReasoningEffort: isCodexReasoningEffort(current === null || current === void 0 ? void 0 : current.codexModelReasoningEffort)
|
|
@@ -1724,11 +1740,20 @@ class FileSystemDatabaseManager {
|
|
|
1724
1740
|
}
|
|
1725
1741
|
return false;
|
|
1726
1742
|
}
|
|
1743
|
+
truncateForErrorLog(value) {
|
|
1744
|
+
if (value === undefined || value === null)
|
|
1745
|
+
return undefined;
|
|
1746
|
+
const str = typeof value === 'string' ? value : JSON.stringify(value);
|
|
1747
|
+
if (!str || str.length <= this.MAX_ERROR_LOG_FIELD_SIZE)
|
|
1748
|
+
return str || undefined;
|
|
1749
|
+
return str.substring(0, this.MAX_ERROR_LOG_FIELD_SIZE) + `\n...[truncated, original size: ${str.length} chars]`;
|
|
1750
|
+
}
|
|
1727
1751
|
// Error log operations
|
|
1728
1752
|
addErrorLog(log) {
|
|
1729
1753
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1730
1754
|
const id = crypto_1.default.randomUUID();
|
|
1731
|
-
this.
|
|
1755
|
+
const truncatedLog = Object.assign(Object.assign({}, log), { requestBody: this.truncateForErrorLog(log.requestBody), responseBody: this.truncateForErrorLog(log.responseBody), errorStack: this.truncateForErrorLog(log.errorStack), upstreamRequest: log.upstreamRequest ? Object.assign(Object.assign({}, log.upstreamRequest), { body: this.truncateForErrorLog(log.upstreamRequest.body) }) : undefined });
|
|
1756
|
+
this.errorLogs.push(Object.assign(Object.assign({}, truncatedLog), { id }));
|
|
1732
1757
|
yield this.saveErrorLogs();
|
|
1733
1758
|
});
|
|
1734
1759
|
}
|
|
@@ -1955,6 +1980,9 @@ class FileSystemDatabaseManager {
|
|
|
1955
1980
|
merged.codexModelReasoningEffort = DEFAULT_CODEX_REASONING_EFFORT;
|
|
1956
1981
|
}
|
|
1957
1982
|
merged.failoverRecoverySeconds = normalizeFailoverRecoverySeconds(merged.failoverRecoverySeconds);
|
|
1983
|
+
if (typeof merged.ruleGlobalTimeout !== 'number' || merged.ruleGlobalTimeout <= 0) {
|
|
1984
|
+
merged.ruleGlobalTimeout = undefined;
|
|
1985
|
+
}
|
|
1958
1986
|
this.config = merged;
|
|
1959
1987
|
yield this.saveConfig();
|
|
1960
1988
|
return true;
|
|
@@ -169,7 +169,7 @@ class ProxyServer {
|
|
|
169
169
|
initialize() {
|
|
170
170
|
// Dynamic proxy middleware
|
|
171
171
|
this.app.use((req, res, next) => __awaiter(this, void 0, void 0, function* () {
|
|
172
|
-
var _a, _b, _c, _d, _e;
|
|
172
|
+
var _a, _b, _c, _d, _e, _f;
|
|
173
173
|
// 仅处理支持的目标路径
|
|
174
174
|
if (!SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
|
|
175
175
|
return next();
|
|
@@ -340,20 +340,28 @@ class ProxyServer {
|
|
|
340
340
|
});
|
|
341
341
|
// 确定目标类型
|
|
342
342
|
const targetType = req.path.startsWith('/claude-code/') ? 'claude-code' : 'codex';
|
|
343
|
-
// 记录错误日志 -
|
|
343
|
+
// 记录错误日志 - 包含请求详情和最后失败的服务信息
|
|
344
|
+
const _lastFailedVendor = lastFailedService ? this.dbManager.getVendorByServiceId(lastFailedService.id) : undefined;
|
|
344
345
|
yield this.dbManager.addErrorLog({
|
|
345
346
|
timestamp: Date.now(),
|
|
346
347
|
method: req.method,
|
|
347
348
|
path: req.path,
|
|
348
349
|
statusCode: 503,
|
|
349
|
-
errorMessage: 'All services failed',
|
|
350
|
+
errorMessage: (lastError === null || lastError === void 0 ? void 0 : lastError.message) || 'All services failed',
|
|
350
351
|
errorStack: lastError === null || lastError === void 0 ? void 0 : lastError.stack,
|
|
351
352
|
requestHeaders: this.normalizeHeaders(req.headers),
|
|
352
353
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
353
354
|
// 添加请求详情
|
|
354
355
|
targetType,
|
|
355
356
|
requestModel: (_d = req.body) === null || _d === void 0 ? void 0 : _d.model,
|
|
356
|
-
responseTime:
|
|
357
|
+
responseTime: Date.now() - requestStartAt,
|
|
358
|
+
// 添加最后失败的服务信息
|
|
359
|
+
ruleId: lastFailedRule === null || lastFailedRule === void 0 ? void 0 : lastFailedRule.id,
|
|
360
|
+
targetServiceId: lastFailedService === null || lastFailedService === void 0 ? void 0 : lastFailedService.id,
|
|
361
|
+
targetServiceName: lastFailedService === null || lastFailedService === void 0 ? void 0 : lastFailedService.name,
|
|
362
|
+
targetModel: (lastFailedRule === null || lastFailedRule === void 0 ? void 0 : lastFailedRule.targetModel) || ((_e = req.body) === null || _e === void 0 ? void 0 : _e.model),
|
|
363
|
+
vendorId: lastFailedService === null || lastFailedService === void 0 ? void 0 : lastFailedService.vendorId,
|
|
364
|
+
vendorName: _lastFailedVendor === null || _lastFailedVendor === void 0 ? void 0 : _lastFailedVendor.name,
|
|
357
365
|
});
|
|
358
366
|
// 根据路径判断目标类型并返回适当的错误格式
|
|
359
367
|
const isClaudeCode = req.path.startsWith('/claude-code/');
|
|
@@ -399,8 +407,8 @@ class ProxyServer {
|
|
|
399
407
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
400
408
|
// 添加请求详情
|
|
401
409
|
targetType,
|
|
402
|
-
requestModel: (
|
|
403
|
-
responseTime:
|
|
410
|
+
requestModel: (_f = req.body) === null || _f === void 0 ? void 0 : _f.model,
|
|
411
|
+
responseTime: Date.now() - requestStartAt,
|
|
404
412
|
});
|
|
405
413
|
// 根据路径判断目标类型并返回适当的错误格式
|
|
406
414
|
const isClaudeCode = req.path.startsWith('/claude-code/');
|
|
@@ -432,7 +440,7 @@ class ProxyServer {
|
|
|
432
440
|
}
|
|
433
441
|
createFixedRouteHandler(targetType) {
|
|
434
442
|
return (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
435
|
-
var _a, _b, _c, _d, _e;
|
|
443
|
+
var _a, _b, _c, _d, _e, _f;
|
|
436
444
|
const requestStartAt = Date.now();
|
|
437
445
|
let hasRelayAttempt = false;
|
|
438
446
|
try {
|
|
@@ -605,20 +613,28 @@ class ProxyServer {
|
|
|
605
613
|
error: (lastError === null || lastError === void 0 ? void 0 : lastError.message) || 'All services failed',
|
|
606
614
|
tags: this.buildRelayTags(hasRelayAttempt),
|
|
607
615
|
});
|
|
608
|
-
// 记录错误日志 -
|
|
616
|
+
// 记录错误日志 - 包含请求详情和最后失败的服务信息(使用函数参数 targetType)
|
|
617
|
+
const _lastFailedVendor2 = lastFailedService ? this.dbManager.getVendorByServiceId(lastFailedService.id) : undefined;
|
|
609
618
|
yield this.dbManager.addErrorLog({
|
|
610
619
|
timestamp: Date.now(),
|
|
611
620
|
method: req.method,
|
|
612
621
|
path: req.path,
|
|
613
622
|
statusCode: 503,
|
|
614
|
-
errorMessage: 'All services failed',
|
|
623
|
+
errorMessage: (lastError === null || lastError === void 0 ? void 0 : lastError.message) || 'All services failed',
|
|
615
624
|
errorStack: lastError === null || lastError === void 0 ? void 0 : lastError.stack,
|
|
616
625
|
requestHeaders: this.normalizeHeaders(req.headers),
|
|
617
626
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
618
627
|
// 添加请求详情
|
|
619
628
|
targetType,
|
|
620
629
|
requestModel: (_d = req.body) === null || _d === void 0 ? void 0 : _d.model,
|
|
621
|
-
responseTime:
|
|
630
|
+
responseTime: Date.now() - requestStartAt,
|
|
631
|
+
// 添加最后失败的服务信息
|
|
632
|
+
ruleId: lastFailedRule === null || lastFailedRule === void 0 ? void 0 : lastFailedRule.id,
|
|
633
|
+
targetServiceId: lastFailedService === null || lastFailedService === void 0 ? void 0 : lastFailedService.id,
|
|
634
|
+
targetServiceName: lastFailedService === null || lastFailedService === void 0 ? void 0 : lastFailedService.name,
|
|
635
|
+
targetModel: (lastFailedRule === null || lastFailedRule === void 0 ? void 0 : lastFailedRule.targetModel) || ((_e = req.body) === null || _e === void 0 ? void 0 : _e.model),
|
|
636
|
+
vendorId: lastFailedService === null || lastFailedService === void 0 ? void 0 : lastFailedService.vendorId,
|
|
637
|
+
vendorName: _lastFailedVendor2 === null || _lastFailedVendor2 === void 0 ? void 0 : _lastFailedVendor2.name,
|
|
622
638
|
});
|
|
623
639
|
// 根据路径判断目标类型并返回适当的错误格式
|
|
624
640
|
const isClaudeCode = req.path.startsWith('/claude-code/');
|
|
@@ -663,8 +679,8 @@ class ProxyServer {
|
|
|
663
679
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
664
680
|
// 添加请求详情
|
|
665
681
|
targetType,
|
|
666
|
-
requestModel: (
|
|
667
|
-
responseTime:
|
|
682
|
+
requestModel: (_f = req.body) === null || _f === void 0 ? void 0 : _f.model,
|
|
683
|
+
responseTime: Date.now() - requestStartAt,
|
|
668
684
|
});
|
|
669
685
|
if (this.isResponseCommitted(res)) {
|
|
670
686
|
return;
|
|
@@ -882,6 +898,20 @@ class ProxyServer {
|
|
|
882
898
|
}
|
|
883
899
|
return `;已自动转发给 ${forwardedToServiceName} 服务继续处理`;
|
|
884
900
|
}
|
|
901
|
+
/**
|
|
902
|
+
* 解析规则的有效超时时间(毫秒)。
|
|
903
|
+
* 优先级:rule.timeout > config.ruleGlobalTimeout * 1000 > 300000(5分钟)
|
|
904
|
+
*/
|
|
905
|
+
resolveEffectiveTimeout(rule) {
|
|
906
|
+
if (rule.timeout && rule.timeout > 0) {
|
|
907
|
+
return rule.timeout;
|
|
908
|
+
}
|
|
909
|
+
const config = this.dbManager.getConfig();
|
|
910
|
+
if (config.ruleGlobalTimeout && config.ruleGlobalTimeout > 0) {
|
|
911
|
+
return config.ruleGlobalTimeout * 1000;
|
|
912
|
+
}
|
|
913
|
+
return 300000;
|
|
914
|
+
}
|
|
885
915
|
createFailoverError(message, statusCode, originalError) {
|
|
886
916
|
const failoverError = new Error(message);
|
|
887
917
|
failoverError.isFailoverCandidate = true;
|
|
@@ -2280,6 +2310,15 @@ class ProxyServer {
|
|
|
2280
2310
|
return null;
|
|
2281
2311
|
}
|
|
2282
2312
|
}
|
|
2313
|
+
isEmptyResponse(data) {
|
|
2314
|
+
if (data === null || data === undefined)
|
|
2315
|
+
return true;
|
|
2316
|
+
if (typeof data === 'string' && data.trim() === '')
|
|
2317
|
+
return true;
|
|
2318
|
+
if (typeof data === 'object' && !Array.isArray(data) && Object.keys(data).length === 0)
|
|
2319
|
+
return true;
|
|
2320
|
+
return false;
|
|
2321
|
+
}
|
|
2283
2322
|
/**
|
|
2284
2323
|
* 从请求中提取 session ID(默认方法)
|
|
2285
2324
|
* Claude Code: metadata.user_id
|
|
@@ -2692,7 +2731,7 @@ class ProxyServer {
|
|
|
2692
2731
|
}
|
|
2693
2732
|
proxyRequest(req, res, route, rule, service, options) {
|
|
2694
2733
|
return __awaiter(this, void 0, void 0, function* () {
|
|
2695
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
2734
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
2696
2735
|
res.locals.skipLog = true;
|
|
2697
2736
|
const startTime = Date.now();
|
|
2698
2737
|
const rawSourceType = service.sourceType || 'openai-chat';
|
|
@@ -3091,7 +3130,7 @@ class ProxyServer {
|
|
|
3091
3130
|
method: req.method,
|
|
3092
3131
|
url: upstreamUrl,
|
|
3093
3132
|
headers: this.buildUpstreamHeaders(req, service, sourceType, streamRequested, requestBody),
|
|
3094
|
-
timeout: rule
|
|
3133
|
+
timeout: this.resolveEffectiveTimeout(rule),
|
|
3095
3134
|
validateStatus: () => true,
|
|
3096
3135
|
responseType: streamRequested ? 'stream' : 'json',
|
|
3097
3136
|
signal: upstreamAbortController.signal,
|
|
@@ -3242,6 +3281,7 @@ class ProxyServer {
|
|
|
3242
3281
|
requestHeaders: this.normalizeHeaders(req.headers),
|
|
3243
3282
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
3244
3283
|
upstreamRequest: upstreamRequestForLog,
|
|
3284
|
+
responseHeaders: responseHeadersForLog,
|
|
3245
3285
|
// 添加请求详情
|
|
3246
3286
|
ruleId: rule.id,
|
|
3247
3287
|
targetType,
|
|
@@ -3347,6 +3387,7 @@ class ProxyServer {
|
|
|
3347
3387
|
requestHeaders: this.normalizeHeaders(req.headers),
|
|
3348
3388
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
3349
3389
|
upstreamRequest: upstreamRequestForLog,
|
|
3390
|
+
responseHeaders: responseHeadersForLog,
|
|
3350
3391
|
// 添加请求详情
|
|
3351
3392
|
ruleId: rule.id,
|
|
3352
3393
|
targetType,
|
|
@@ -3441,6 +3482,7 @@ class ProxyServer {
|
|
|
3441
3482
|
requestHeaders: this.normalizeHeaders(req.headers),
|
|
3442
3483
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
3443
3484
|
upstreamRequest: upstreamRequestForLog,
|
|
3485
|
+
responseHeaders: responseHeadersForLog,
|
|
3444
3486
|
ruleId: rule.id,
|
|
3445
3487
|
targetType,
|
|
3446
3488
|
targetServiceId: service.id,
|
|
@@ -3537,6 +3579,7 @@ class ProxyServer {
|
|
|
3537
3579
|
requestHeaders: this.normalizeHeaders(req.headers),
|
|
3538
3580
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
3539
3581
|
upstreamRequest: upstreamRequestForLog,
|
|
3582
|
+
responseHeaders: responseHeadersForLog,
|
|
3540
3583
|
ruleId: rule.id,
|
|
3541
3584
|
targetType,
|
|
3542
3585
|
targetServiceId: service.id,
|
|
@@ -3619,6 +3662,7 @@ class ProxyServer {
|
|
|
3619
3662
|
if (converter) {
|
|
3620
3663
|
ensureResponseWritable();
|
|
3621
3664
|
(0, stream_1.pipeline)(response.data, parser, eventCollector, converter, serializer, downstreamChunkCollector, res, (error) => __awaiter(this, void 0, void 0, function* () {
|
|
3665
|
+
var _a, _b;
|
|
3622
3666
|
if (error) {
|
|
3623
3667
|
if (this.isClientDisconnectError(error, res)) {
|
|
3624
3668
|
console.warn('[Proxy] Default stream pipeline closed because client disconnected');
|
|
@@ -3638,6 +3682,16 @@ class ProxyServer {
|
|
|
3638
3682
|
requestHeaders: this.normalizeHeaders(req.headers),
|
|
3639
3683
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
3640
3684
|
upstreamRequest: upstreamRequestForLog,
|
|
3685
|
+
responseHeaders: responseHeadersForLog,
|
|
3686
|
+
ruleId: rule.id,
|
|
3687
|
+
targetType,
|
|
3688
|
+
targetServiceId: service.id,
|
|
3689
|
+
targetServiceName: service.name,
|
|
3690
|
+
targetModel: rule.targetModel || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
|
|
3691
|
+
vendorId: service.vendorId,
|
|
3692
|
+
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
3693
|
+
requestModel: (_b = req.body) === null || _b === void 0 ? void 0 : _b.model,
|
|
3694
|
+
responseTime: Date.now() - startTime,
|
|
3641
3695
|
});
|
|
3642
3696
|
}
|
|
3643
3697
|
catch (logError) {
|
|
@@ -3650,6 +3704,7 @@ class ProxyServer {
|
|
|
3650
3704
|
else {
|
|
3651
3705
|
ensureResponseWritable();
|
|
3652
3706
|
(0, stream_1.pipeline)(response.data, parser, eventCollector, serializer, downstreamChunkCollector, res, (error) => __awaiter(this, void 0, void 0, function* () {
|
|
3707
|
+
var _a, _b;
|
|
3653
3708
|
if (error) {
|
|
3654
3709
|
if (this.isClientDisconnectError(error, res)) {
|
|
3655
3710
|
console.warn('[Proxy] Default stream pipeline closed because client disconnected');
|
|
@@ -3669,6 +3724,16 @@ class ProxyServer {
|
|
|
3669
3724
|
requestHeaders: this.normalizeHeaders(req.headers),
|
|
3670
3725
|
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
3671
3726
|
upstreamRequest: upstreamRequestForLog,
|
|
3727
|
+
responseHeaders: responseHeadersForLog,
|
|
3728
|
+
ruleId: rule.id,
|
|
3729
|
+
targetType,
|
|
3730
|
+
targetServiceId: service.id,
|
|
3731
|
+
targetServiceName: service.name,
|
|
3732
|
+
targetModel: rule.targetModel || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
|
|
3733
|
+
vendorId: service.vendorId,
|
|
3734
|
+
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
3735
|
+
requestModel: (_b = req.body) === null || _b === void 0 ? void 0 : _b.model,
|
|
3736
|
+
responseTime: Date.now() - startTime,
|
|
3672
3737
|
});
|
|
3673
3738
|
}
|
|
3674
3739
|
catch (logError) {
|
|
@@ -3687,6 +3752,59 @@ class ProxyServer {
|
|
|
3687
3752
|
}
|
|
3688
3753
|
// 收集响应头
|
|
3689
3754
|
responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
|
|
3755
|
+
// 检测上游空响应(HTTP 200 但 body 为空)
|
|
3756
|
+
if (this.isEmptyResponse(responseData)) {
|
|
3757
|
+
const emptyErrorMsg = 'Upstream API returned an empty response (HTTP 200)';
|
|
3758
|
+
console.warn(`[Proxy] ${emptyErrorMsg}`);
|
|
3759
|
+
if (failoverEnabled) {
|
|
3760
|
+
throw this.createFailoverError(emptyErrorMsg, 502);
|
|
3761
|
+
}
|
|
3762
|
+
responseBodyForLog = typeof responseData === 'string' ? responseData : JSON.stringify(responseData);
|
|
3763
|
+
const vendors = this.dbManager.getVendors();
|
|
3764
|
+
const vendor = vendors.find(v => v.id === service.vendorId);
|
|
3765
|
+
yield this.dbManager.addErrorLog({
|
|
3766
|
+
timestamp: Date.now(),
|
|
3767
|
+
method: req.method,
|
|
3768
|
+
path: req.path,
|
|
3769
|
+
statusCode: 502,
|
|
3770
|
+
errorMessage: emptyErrorMsg,
|
|
3771
|
+
requestHeaders: this.normalizeHeaders(req.headers),
|
|
3772
|
+
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
3773
|
+
ruleId: rule.id,
|
|
3774
|
+
targetType,
|
|
3775
|
+
targetServiceId: service.id,
|
|
3776
|
+
targetServiceName: service.name,
|
|
3777
|
+
targetModel: rule.targetModel || ((_e = req.body) === null || _e === void 0 ? void 0 : _e.model),
|
|
3778
|
+
vendorId: service.vendorId,
|
|
3779
|
+
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
3780
|
+
requestModel: (_f = req.body) === null || _f === void 0 ? void 0 : _f.model,
|
|
3781
|
+
upstreamRequest: upstreamRequestForLog,
|
|
3782
|
+
responseHeaders: responseHeadersForLog,
|
|
3783
|
+
responseTime: Date.now() - startTime,
|
|
3784
|
+
});
|
|
3785
|
+
yield finalizeLog(502, emptyErrorMsg);
|
|
3786
|
+
if (route.targetType === 'claude-code') {
|
|
3787
|
+
const claudeError = {
|
|
3788
|
+
type: 'error',
|
|
3789
|
+
error: { type: 'api_error', message: emptyErrorMsg }
|
|
3790
|
+
};
|
|
3791
|
+
if (streamRequested) {
|
|
3792
|
+
res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
|
|
3793
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
3794
|
+
res.setHeader('Connection', 'keep-alive');
|
|
3795
|
+
res.status(200);
|
|
3796
|
+
res.write(`event: error\ndata: ${JSON.stringify(claudeError)}\n\n`);
|
|
3797
|
+
res.end();
|
|
3798
|
+
}
|
|
3799
|
+
else {
|
|
3800
|
+
res.status(502).json(claudeError);
|
|
3801
|
+
}
|
|
3802
|
+
}
|
|
3803
|
+
else {
|
|
3804
|
+
res.status(502).json({ error: emptyErrorMsg });
|
|
3805
|
+
}
|
|
3806
|
+
return;
|
|
3807
|
+
}
|
|
3690
3808
|
// 使用统一的响应转换方法
|
|
3691
3809
|
const converted = this.transformResponseToTool(targetType, sourceType, responseData);
|
|
3692
3810
|
// 提取 token usage(从原始响应数据中提取)
|
|
@@ -3719,13 +3837,60 @@ class ProxyServer {
|
|
|
3719
3837
|
yield finalizeLog(499, 'Client disconnected');
|
|
3720
3838
|
return;
|
|
3721
3839
|
}
|
|
3840
|
+
// 特殊处理:count_tokens 请求无论如何都返回 200
|
|
3841
|
+
const isCountTokensRequest = this.isCountTokensPath(req.path) || this.isCountTokensPath(req.originalUrl);
|
|
3842
|
+
if (isCountTokensRequest) {
|
|
3843
|
+
console.warn('[Proxy] count_tokens request failed, falling back to local estimation:', error.message);
|
|
3844
|
+
// 使用本地估算返回结果
|
|
3845
|
+
const inputTokens = this.estimateClaudeCountTokens(requestBody);
|
|
3846
|
+
const localTokenResponse = { input_tokens: inputTokens };
|
|
3847
|
+
usageForLog = {
|
|
3848
|
+
inputTokens,
|
|
3849
|
+
outputTokens: 0,
|
|
3850
|
+
totalTokens: inputTokens,
|
|
3851
|
+
};
|
|
3852
|
+
responseHeadersForLog = {
|
|
3853
|
+
'content-type': 'application/json; charset=utf-8',
|
|
3854
|
+
};
|
|
3855
|
+
responseBodyForLog = JSON.stringify(localTokenResponse);
|
|
3856
|
+
streamChunksForLog = undefined;
|
|
3857
|
+
relayedForLog = false;
|
|
3858
|
+
extraTagsForLog.push('上游失败-本地计算Token');
|
|
3859
|
+
// 记录错误日志(但不影响响应)
|
|
3860
|
+
const vendors = this.dbManager.getVendors();
|
|
3861
|
+
const vendor = vendors.find(v => v.id === service.vendorId);
|
|
3862
|
+
yield this.dbManager.addErrorLog({
|
|
3863
|
+
timestamp: Date.now(),
|
|
3864
|
+
method: req.method,
|
|
3865
|
+
path: req.path,
|
|
3866
|
+
statusCode: 200, // 实际返回 200
|
|
3867
|
+
errorMessage: `count_tokens upstream failed, used local estimation: ${error.message}`,
|
|
3868
|
+
errorStack: error.stack,
|
|
3869
|
+
requestHeaders: this.normalizeHeaders(req.headers),
|
|
3870
|
+
requestBody: req.body ? JSON.stringify(req.body) : undefined,
|
|
3871
|
+
ruleId: rule.id,
|
|
3872
|
+
targetType,
|
|
3873
|
+
targetServiceId: service.id,
|
|
3874
|
+
targetServiceName: service.name,
|
|
3875
|
+
targetModel: rule.targetModel || ((_g = req.body) === null || _g === void 0 ? void 0 : _g.model),
|
|
3876
|
+
vendorId: service.vendorId,
|
|
3877
|
+
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
3878
|
+
requestModel: (_h = req.body) === null || _h === void 0 ? void 0 : _h.model,
|
|
3879
|
+
upstreamRequest: upstreamRequestForLog,
|
|
3880
|
+
responseTime: Date.now() - startTime,
|
|
3881
|
+
});
|
|
3882
|
+
// 返回 200 状态码和本地估算结果
|
|
3883
|
+
res.status(200).json(localTokenResponse);
|
|
3884
|
+
yield finalizeLog(200);
|
|
3885
|
+
return;
|
|
3886
|
+
}
|
|
3722
3887
|
if (failoverEnabled && (error === null || error === void 0 ? void 0 : error.isFailoverCandidate)) {
|
|
3723
3888
|
throw error;
|
|
3724
3889
|
}
|
|
3725
3890
|
console.error('Proxy error:', error);
|
|
3726
3891
|
// 检测是否是 timeout 错误
|
|
3727
3892
|
const isTimeout = error.code === 'ECONNABORTED' ||
|
|
3728
|
-
((
|
|
3893
|
+
((_j = error.message) === null || _j === void 0 ? void 0 : _j.toLowerCase().includes('timeout')) ||
|
|
3729
3894
|
(error.errno && error.errno === 'ETIMEDOUT');
|
|
3730
3895
|
const baseErrorMessage = isTimeout
|
|
3731
3896
|
? 'Request timeout - the upstream API took too long to respond'
|
|
@@ -3750,11 +3915,12 @@ class ProxyServer {
|
|
|
3750
3915
|
targetType,
|
|
3751
3916
|
targetServiceId: service.id,
|
|
3752
3917
|
targetServiceName: service.name,
|
|
3753
|
-
targetModel: rule.targetModel || ((
|
|
3918
|
+
targetModel: rule.targetModel || ((_k = req.body) === null || _k === void 0 ? void 0 : _k.model),
|
|
3754
3919
|
vendorId: service.vendorId,
|
|
3755
3920
|
vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
3756
|
-
requestModel: (
|
|
3921
|
+
requestModel: (_l = req.body) === null || _l === void 0 ? void 0 : _l.model,
|
|
3757
3922
|
upstreamRequest: upstreamRequestForLog,
|
|
3923
|
+
responseHeaders: responseHeadersForLog,
|
|
3758
3924
|
responseTime: Date.now() - startTime,
|
|
3759
3925
|
});
|
|
3760
3926
|
yield finalizeLog(isTimeout ? 504 : 500, errorMessage);
|