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.
@@ -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
- const AUTH_CODE = process.env.AUTH || '';
14
- const JWT_SECRET = process.env.JWT_SECRET || (AUTH_CODE ? crypto_1.default.createHash('sha256').update(AUTH_CODE).digest('hex') : '');
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 AUTH_CODE.trim().length > 0;
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 === AUTH_CODE;
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, JWT_SECRET, { expiresIn: TOKEN_EXPIRY });
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, JWT_SECRET);
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
- yield promises_1.default.writeFile(this.errorLogsFile, JSON.stringify(this.errorLogs, null, 2));
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.errorLogs.push(Object.assign(Object.assign({}, log), { id }));
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: 0,
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: (_e = req.body) === null || _e === void 0 ? void 0 : _e.model,
403
- responseTime: 0,
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
- // 记录错误日志 - 包含请求详情(使用函数参数 targetType)
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: 0,
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: (_e = req.body) === null || _e === void 0 ? void 0 : _e.model,
667
- responseTime: 0,
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.timeout || 3000000, // 默认300秒
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
- ((_e = error.message) === null || _e === void 0 ? void 0 : _e.toLowerCase().includes('timeout')) ||
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 || ((_f = req.body) === null || _f === void 0 ? void 0 : _f.model),
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: (_g = req.body) === null || _g === void 0 ? void 0 : _g.model,
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);