aicodeswitch 4.0.3 → 4.0.4

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.
@@ -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, _f;
172
+ var _a, _b, _c, _d, _e;
173
173
  // 仅处理支持的目标路径
174
174
  if (!SUPPORTED_TARGETS.some(target => req.path.startsWith(`/${target}/`))) {
175
175
  return next();
@@ -195,9 +195,8 @@ class ProxyServer {
195
195
  // 如果原始配置也不可用,返回错误
196
196
  return res.status(404).json({ error: 'No matching route found and no original config available' });
197
197
  }
198
- // 高智商请求判定:从消息结构推断是否启用,不再使用 !x 显式关闭语法
198
+ // 高智商请求判定:存在规则时从消息末尾往前搜索 [!]/[x] 标记
199
199
  const forcedContentType = yield this.prepareHighIqRouting(req, route, route.targetType);
200
- // 检查是否启用故障切换
201
200
  const enableFailover = ((_a = this.config) === null || _a === void 0 ? void 0 : _a.enableFailover) !== false; // 默认为 true
202
201
  if (!enableFailover) {
203
202
  // 故障切换已禁用,使用传统的单一规则匹配
@@ -294,7 +293,7 @@ class ProxyServer {
294
293
  }
295
294
  else {
296
295
  // HTTP错误,检查状态码
297
- const statusCode = ((_c = error.response) === null || _c === void 0 ? void 0 : _c.status) || 500;
296
+ const statusCode = this.getErrorStatusCode(error, 500);
298
297
  if (statusCode >= 400) {
299
298
  yield this.dbManager.addToBlacklist(service.id, route.id, rule.contentType, error.message, statusCode, 'http');
300
299
  console.log(`Service ${service.name} added to blacklist due to HTTP error ${statusCode} (${route.id}:${rule.contentType}:${service.id})`);
@@ -353,13 +352,13 @@ class ProxyServer {
353
352
  requestBody: req.body ? JSON.stringify(req.body) : undefined,
354
353
  // 添加请求详情
355
354
  targetType,
356
- requestModel: (_d = req.body) === null || _d === void 0 ? void 0 : _d.model,
355
+ requestModel: (_c = req.body) === null || _c === void 0 ? void 0 : _c.model,
357
356
  responseTime: Date.now() - requestStartAt,
358
357
  // 添加最后失败的服务信息
359
358
  ruleId: lastFailedRule === null || lastFailedRule === void 0 ? void 0 : lastFailedRule.id,
360
359
  targetServiceId: lastFailedService === null || lastFailedService === void 0 ? void 0 : lastFailedService.id,
361
360
  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),
361
+ targetModel: (lastFailedRule === null || lastFailedRule === void 0 ? void 0 : lastFailedRule.targetModel) || ((_d = req.body) === null || _d === void 0 ? void 0 : _d.model),
363
362
  vendorId: lastFailedService === null || lastFailedService === void 0 ? void 0 : lastFailedService.vendorId,
364
363
  vendorName: _lastFailedVendor === null || _lastFailedVendor === void 0 ? void 0 : _lastFailedVendor.name,
365
364
  });
@@ -407,7 +406,7 @@ class ProxyServer {
407
406
  requestBody: req.body ? JSON.stringify(req.body) : undefined,
408
407
  // 添加请求详情
409
408
  targetType,
410
- requestModel: (_f = req.body) === null || _f === void 0 ? void 0 : _f.model,
409
+ requestModel: (_e = req.body) === null || _e === void 0 ? void 0 : _e.model,
411
410
  responseTime: Date.now() - requestStartAt,
412
411
  });
413
412
  // 根据路径判断目标类型并返回适当的错误格式
@@ -440,7 +439,7 @@ class ProxyServer {
440
439
  }
441
440
  createFixedRouteHandler(targetType) {
442
441
  return (req, res) => __awaiter(this, void 0, void 0, function* () {
443
- var _a, _b, _c, _d, _e, _f;
442
+ var _a, _b, _c, _d, _e;
444
443
  const requestStartAt = Date.now();
445
444
  let hasRelayAttempt = false;
446
445
  try {
@@ -470,7 +469,7 @@ class ProxyServer {
470
469
  });
471
470
  return res.status(404).json({ error: `No active route found for target type: ${targetType}` });
472
471
  }
473
- // 高智商请求判定:从消息结构推断是否启用,不再使用 !x 显式关闭语法
472
+ // 高智商请求判定:存在规则时从消息末尾往前搜索 [!]/[x] 标记
474
473
  const forcedContentType = yield this.prepareHighIqRouting(req, route, targetType);
475
474
  // 检查是否启用故障切换
476
475
  const enableFailover = ((_a = this.config) === null || _a === void 0 ? void 0 : _a.enableFailover) !== false; // 默认为 true
@@ -569,7 +568,7 @@ class ProxyServer {
569
568
  }
570
569
  else {
571
570
  // HTTP错误,检查状态码
572
- const statusCode = ((_c = error.response) === null || _c === void 0 ? void 0 : _c.status) || 500;
571
+ const statusCode = this.getErrorStatusCode(error, 500);
573
572
  if (statusCode >= 400) {
574
573
  yield this.dbManager.addToBlacklist(service.id, route.id, rule.contentType, error.message, statusCode, 'http');
575
574
  console.log(`Service ${service.name} added to blacklist due to HTTP error ${statusCode} (${route.id}:${rule.contentType}:${service.id})`);
@@ -626,13 +625,13 @@ class ProxyServer {
626
625
  requestBody: req.body ? JSON.stringify(req.body) : undefined,
627
626
  // 添加请求详情
628
627
  targetType,
629
- requestModel: (_d = req.body) === null || _d === void 0 ? void 0 : _d.model,
628
+ requestModel: (_c = req.body) === null || _c === void 0 ? void 0 : _c.model,
630
629
  responseTime: Date.now() - requestStartAt,
631
630
  // 添加最后失败的服务信息
632
631
  ruleId: lastFailedRule === null || lastFailedRule === void 0 ? void 0 : lastFailedRule.id,
633
632
  targetServiceId: lastFailedService === null || lastFailedService === void 0 ? void 0 : lastFailedService.id,
634
633
  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),
634
+ targetModel: (lastFailedRule === null || lastFailedRule === void 0 ? void 0 : lastFailedRule.targetModel) || ((_d = req.body) === null || _d === void 0 ? void 0 : _d.model),
636
635
  vendorId: lastFailedService === null || lastFailedService === void 0 ? void 0 : lastFailedService.vendorId,
637
636
  vendorName: _lastFailedVendor2 === null || _lastFailedVendor2 === void 0 ? void 0 : _lastFailedVendor2.name,
638
637
  });
@@ -679,7 +678,7 @@ class ProxyServer {
679
678
  requestBody: req.body ? JSON.stringify(req.body) : undefined,
680
679
  // 添加请求详情
681
680
  targetType,
682
- requestModel: (_f = req.body) === null || _f === void 0 ? void 0 : _f.model,
681
+ requestModel: (_e = req.body) === null || _e === void 0 ? void 0 : _e.model,
683
682
  responseTime: Date.now() - requestStartAt,
684
683
  });
685
684
  if (this.isResponseCommitted(res)) {
@@ -915,12 +914,45 @@ class ProxyServer {
915
914
  createFailoverError(message, statusCode, originalError) {
916
915
  const failoverError = new Error(message);
917
916
  failoverError.isFailoverCandidate = true;
917
+ failoverError.statusCode = statusCode;
918
918
  failoverError.response = { status: statusCode };
919
919
  if (originalError === null || originalError === void 0 ? void 0 : originalError.stack) {
920
920
  failoverError.stack = originalError.stack;
921
921
  }
922
922
  return failoverError;
923
923
  }
924
+ getErrorStatusCode(error, fallbackStatusCode = 500) {
925
+ var _a, _b, _c;
926
+ const statusCode = (_c = (_b = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) !== null && _b !== void 0 ? _b : error === null || error === void 0 ? void 0 : error.statusCode) !== null && _c !== void 0 ? _c : error === null || error === void 0 ? void 0 : error.status;
927
+ if (typeof statusCode === 'number' && Number.isFinite(statusCode)) {
928
+ return statusCode;
929
+ }
930
+ return fallbackStatusCode;
931
+ }
932
+ detectStreamFailure(events) {
933
+ var _a, _b;
934
+ for (const event of events) {
935
+ const eventType = (_a = event.event) === null || _a === void 0 ? void 0 : _a.trim();
936
+ if (!eventType)
937
+ continue;
938
+ if (eventType !== 'response.failed' && eventType !== 'error') {
939
+ continue;
940
+ }
941
+ const parsed = event.data ? this.safeJsonParse(event.data) : null;
942
+ const errorObj = ((_b = parsed === null || parsed === void 0 ? void 0 : parsed.response) === null || _b === void 0 ? void 0 : _b.error) || (parsed === null || parsed === void 0 ? void 0 : parsed.error) || parsed;
943
+ const errorCode = errorObj === null || errorObj === void 0 ? void 0 : errorObj.code;
944
+ const errorMessage = (errorObj === null || errorObj === void 0 ? void 0 : errorObj.message)
945
+ || (parsed === null || parsed === void 0 ? void 0 : parsed.message)
946
+ || `Upstream stream returned ${eventType}`;
947
+ const normalizedMessage = `Upstream stream returned ${eventType}: ${errorMessage}`;
948
+ const statusCode = errorCode === 'server_is_overloaded' ? 503 : 502;
949
+ return {
950
+ statusCode,
951
+ errorMessage: normalizedMessage,
952
+ };
953
+ }
954
+ return null;
955
+ }
924
956
  isDownstreamClosed(res) {
925
957
  return res.destroyed || res.writableEnded || !res.writable;
926
958
  }
@@ -1420,7 +1452,7 @@ class ProxyServer {
1420
1452
  },
1421
1453
  {
1422
1454
  type: 'high-iq',
1423
- match: (_req, body) => this.hasHighIqSignal(body),
1455
+ match: (_req, body, _sessionId, routeId) => this.hasHighIqSignal(body, routeId),
1424
1456
  },
1425
1457
  {
1426
1458
  type: 'long-context',
@@ -1564,11 +1596,19 @@ class ProxyServer {
1564
1596
  ((_a = body === null || body === void 0 ? void 0 : body.reasoning) === null || _a === void 0 ? void 0 : _a.effort) ||
1565
1597
  ((_b = body === null || body === void 0 ? void 0 : body.reasoning) === null || _b === void 0 ? void 0 : _b.enabled));
1566
1598
  }
1567
- hasHighIqSignal(body) {
1599
+ hasHighIqRuleForRoute(routeId) {
1600
+ var _a;
1601
+ const rules = this.getRulesByRouteId(routeId);
1602
+ return (_a = rules === null || rules === void 0 ? void 0 : rules.some(rule => rule.contentType === 'high-iq' && !rule.isDisabled)) !== null && _a !== void 0 ? _a : false;
1603
+ }
1604
+ hasHighIqSignal(body, routeId) {
1605
+ if (routeId && !this.hasHighIqRuleForRoute(routeId))
1606
+ return false;
1568
1607
  return this.inferHighIqRouting(body, false).shouldUseHighIq;
1569
1608
  }
1570
1609
  inferHighIqRouting(body, previousMode) {
1571
1610
  const messages = this.extractConversationMessages(body);
1611
+ // 从消息列表末尾往前查找 [!] 或 [x] 标记,普通消息跳过继续搜索
1572
1612
  for (let i = messages.length - 1; i >= 0; i--) {
1573
1613
  const message = messages[i];
1574
1614
  if ((message === null || message === void 0 ? void 0 : message.role) !== 'user') {
@@ -1578,17 +1618,22 @@ class ProxyServer {
1578
1618
  if (!signal.hasHumanText) {
1579
1619
  continue;
1580
1620
  }
1621
+ // [x] 优先:同一消息中 [x] 覆盖 [!]
1622
+ if (signal.hasCancelPrefix) {
1623
+ return {
1624
+ shouldUseHighIq: false,
1625
+ decisionSource: 'human',
1626
+ };
1627
+ }
1581
1628
  if (signal.hasHighIqPrefix) {
1582
1629
  return {
1583
1630
  shouldUseHighIq: true,
1584
1631
  decisionSource: 'human',
1585
1632
  };
1586
1633
  }
1587
- return {
1588
- shouldUseHighIq: false,
1589
- decisionSource: 'human',
1590
- };
1634
+ // 普通消息(无 [!] 或 [x] 前缀),继续向前搜索
1591
1635
  }
1636
+ // 未找到 [!] 或 [x] 标记,回退到 session 持久化状态
1592
1637
  if (previousMode) {
1593
1638
  return {
1594
1639
  shouldUseHighIq: true,
@@ -1602,6 +1647,10 @@ class ProxyServer {
1602
1647
  }
1603
1648
  prepareHighIqRouting(req, route, targetType) {
1604
1649
  return __awaiter(this, void 0, void 0, function* () {
1650
+ // 无高智商规则时直接跳过,避免每次都检查消息前缀
1651
+ if (!this.hasHighIqRuleForRoute(route.id)) {
1652
+ return undefined;
1653
+ }
1605
1654
  const sessionId = this.defaultExtractSessionId(req, targetType);
1606
1655
  const session = sessionId ? this.dbManager.getSession(sessionId) : null;
1607
1656
  const previousMode = (session === null || session === void 0 ? void 0 : session.highIqMode) === true;
@@ -1613,7 +1662,7 @@ class ProxyServer {
1613
1662
  highIqRuleId: undefined,
1614
1663
  lastRequestAt: Date.now(),
1615
1664
  });
1616
- console.log(`[HIGH-IQ] Session ${sessionId} auto-disabled by latest human message`);
1665
+ console.log(`[HIGH-IQ] Session ${sessionId} cancelled by [x] prefix`);
1617
1666
  }
1618
1667
  return undefined;
1619
1668
  }
@@ -1671,6 +1720,7 @@ class ProxyServer {
1671
1720
  analyzeUserMessageForHighIq(message) {
1672
1721
  let hasHumanText = false;
1673
1722
  let hasHighIqPrefix = false;
1723
+ let hasCancelPrefix = false;
1674
1724
  const scanText = (text, treatAsHuman) => {
1675
1725
  const trimmed = text.trim();
1676
1726
  if (!trimmed) {
@@ -1682,11 +1732,14 @@ class ProxyServer {
1682
1732
  if (treatAsHuman && trimmed.startsWith('[!]')) {
1683
1733
  hasHighIqPrefix = true;
1684
1734
  }
1735
+ if (treatAsHuman && /^\[x]/i.test(trimmed)) {
1736
+ hasCancelPrefix = true;
1737
+ }
1685
1738
  };
1686
1739
  const content = message === null || message === void 0 ? void 0 : message.content;
1687
1740
  if (typeof content === 'string') {
1688
1741
  scanText(content, true);
1689
- return { hasHumanText, hasHighIqPrefix };
1742
+ return { hasHumanText, hasHighIqPrefix, hasCancelPrefix };
1690
1743
  }
1691
1744
  const blocks = Array.isArray(content) ? content : [content];
1692
1745
  for (const block of blocks) {
@@ -1722,7 +1775,7 @@ class ProxyServer {
1722
1775
  }
1723
1776
  }
1724
1777
  }
1725
- return { hasHumanText, hasHighIqPrefix };
1778
+ return { hasHumanText, hasHighIqPrefix, hasCancelPrefix };
1726
1779
  }
1727
1780
  /**
1728
1781
  * 查找可用的高智商规则
@@ -2164,7 +2217,7 @@ class ProxyServer {
2164
2217
  const headers = {};
2165
2218
  for (const [key, value] of Object.entries(req.headers)) {
2166
2219
  // 排除原始认证头,防止与代理设置的认证头冲突
2167
- if (['host', 'content-length', 'authorization', 'x-api-key', 'x-anthropic-api-key', 'anthropic-api-key', 'x-goog-api-key'].includes(key.toLowerCase())) {
2220
+ if (['host', 'content-length', 'authorization', 'x-api-key', 'x-anthropic-api-key', 'anthropic-api-key', 'x-goog-api-key', 'accept-encoding'].includes(key.toLowerCase())) {
2168
2221
  continue;
2169
2222
  }
2170
2223
  if (typeof value === 'string') {
@@ -2199,6 +2252,10 @@ class ProxyServer {
2199
2252
  if (streamRequested && !headers.accept) {
2200
2253
  headers.accept = 'text/event-stream';
2201
2254
  }
2255
+ // 流式场景显式禁用压缩,避免上游返回压缩字节流导致下游出现乱码
2256
+ if (streamRequested) {
2257
+ headers['accept-encoding'] = 'identity';
2258
+ }
2202
2259
  if (!headers.connection) {
2203
2260
  if (streamRequested) {
2204
2261
  headers.connection = 'keep-alive';
@@ -2310,6 +2367,17 @@ class ProxyServer {
2310
2367
  return null;
2311
2368
  }
2312
2369
  }
2370
+ cloneRequestBody(data) {
2371
+ if (data === null || data === undefined) {
2372
+ return data;
2373
+ }
2374
+ try {
2375
+ return JSON.parse(JSON.stringify(data));
2376
+ }
2377
+ catch (_a) {
2378
+ return data;
2379
+ }
2380
+ }
2313
2381
  isEmptyResponse(data) {
2314
2382
  if (data === null || data === undefined)
2315
2383
  return true;
@@ -2731,7 +2799,7 @@ class ProxyServer {
2731
2799
  }
2732
2800
  proxyRequest(req, res, route, rule, service, options) {
2733
2801
  return __awaiter(this, void 0, void 0, function* () {
2734
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
2802
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
2735
2803
  res.locals.skipLog = true;
2736
2804
  const startTime = Date.now();
2737
2805
  const rawSourceType = service.sourceType || 'openai-chat';
@@ -2745,7 +2813,8 @@ class ProxyServer {
2745
2813
  const forwardedToServiceName = options === null || options === void 0 ? void 0 : options.forwardedToServiceName;
2746
2814
  const useOriginalConfig = (options === null || options === void 0 ? void 0 : options.useOriginalConfig) === true;
2747
2815
  let relayedForLog = !useOriginalConfig;
2748
- let requestBody = req.body || {};
2816
+ const originalToolRequestBody = this.cloneRequestBody(req.body || {});
2817
+ let requestBody = this.cloneRequestBody(originalToolRequestBody) || {};
2749
2818
  let usageForLog;
2750
2819
  let logged = false;
2751
2820
  const extraTagsForLog = [];
@@ -3104,8 +3173,9 @@ class ProxyServer {
3104
3173
  res.once('close', onResponseClosed);
3105
3174
  try {
3106
3175
  // 使用统一的请求转换方法
3107
- const transformedRequestBody = this.transformRequestToUpstream(targetType, sourceType, requestBody, rule.targetModel);
3108
- requestBody = (_b = transformedRequestBody !== null && transformedRequestBody !== void 0 ? transformedRequestBody : requestBody) !== null && _b !== void 0 ? _b : {};
3176
+ const payloadForTransform = this.cloneRequestBody(originalToolRequestBody);
3177
+ const transformedRequestBody = this.transformRequestToUpstream(targetType, sourceType, payloadForTransform, rule.targetModel);
3178
+ requestBody = (_b = transformedRequestBody !== null && transformedRequestBody !== void 0 ? transformedRequestBody : this.cloneRequestBody(originalToolRequestBody)) !== null && _b !== void 0 ? _b : {};
3109
3179
  // 应用 max_output_tokens 限制
3110
3180
  requestBody = this.applyMaxOutputTokensLimit(requestBody, service);
3111
3181
  if (this.shouldDefaultStreamingForClaudeBridge(req, targetType, sourceType, requestBody)
@@ -3642,167 +3712,140 @@ class ProxyServer {
3642
3712
  else if (extractedUsage) {
3643
3713
  usageForLog = this.extractTokenUsageFromResponse(extractedUsage, sourceType);
3644
3714
  }
3645
- void finalizeLog(res.statusCode);
3646
3715
  };
3647
- // 在下游 chunk 收集器完成后执行,确保拿到真正下发给客户端的完整文本
3648
- downstreamChunkCollector.on('finish', () => {
3649
- finalizeChunks();
3650
- });
3651
- // 备用:如果eventCollector的finish没有触发,监听res的finish
3652
- res.on('finish', () => {
3653
- if (!streamChunksForLog) {
3654
- finalizeChunks();
3655
- }
3656
- });
3657
3716
  // 监听 res 的错误事件
3658
3717
  res.on('error', (err) => {
3659
3718
  console.error('[Proxy] Response stream error:', err);
3660
3719
  });
3661
- // 构建 pipeline,根据是否需要转换选择不同的处理链
3662
- if (converter) {
3720
+ const runStreamPipeline = () => __awaiter(this, void 0, void 0, function* () {
3663
3721
  ensureResponseWritable();
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;
3666
- if (error) {
3667
- if (this.isClientDisconnectError(error, res)) {
3668
- console.warn('[Proxy] Default stream pipeline closed because client disconnected');
3669
- yield finalizeLog(499, 'Client disconnected');
3670
- return;
3671
- }
3672
- console.error('[Proxy] Pipeline error (default stream with converter):', error);
3673
- // 记录到错误日志
3674
- try {
3675
- yield this.dbManager.addErrorLog({
3676
- timestamp: Date.now(),
3677
- method: req.method,
3678
- path: req.path,
3679
- statusCode: 500,
3680
- errorMessage: error.message || 'Stream processing error',
3681
- errorStack: error.stack,
3682
- requestHeaders: this.normalizeHeaders(req.headers),
3683
- requestBody: req.body ? JSON.stringify(req.body) : undefined,
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,
3695
- });
3696
- }
3697
- catch (logError) {
3698
- console.error('[Proxy] Failed to log error:', logError);
3699
- }
3700
- yield finalizeLog(500, error.message);
3722
+ return yield new Promise((resolve, reject) => {
3723
+ if (converter) {
3724
+ (0, stream_1.pipeline)(response.data, parser, eventCollector, converter, serializer, downstreamChunkCollector, res, (error) => {
3725
+ if (error) {
3726
+ reject(error);
3727
+ return;
3728
+ }
3729
+ resolve();
3730
+ });
3731
+ return;
3701
3732
  }
3702
- }));
3703
- }
3704
- else {
3705
- ensureResponseWritable();
3706
- (0, stream_1.pipeline)(response.data, parser, eventCollector, serializer, downstreamChunkCollector, res, (error) => __awaiter(this, void 0, void 0, function* () {
3707
- var _a, _b;
3708
- if (error) {
3709
- if (this.isClientDisconnectError(error, res)) {
3710
- console.warn('[Proxy] Default stream pipeline closed because client disconnected');
3711
- yield finalizeLog(499, 'Client disconnected');
3733
+ (0, stream_1.pipeline)(response.data, parser, eventCollector, serializer, downstreamChunkCollector, res, (error) => {
3734
+ if (error) {
3735
+ reject(error);
3712
3736
  return;
3713
3737
  }
3714
- console.error('[Proxy] Pipeline error (default stream):', error);
3715
- // 记录到错误日志
3716
- try {
3717
- yield this.dbManager.addErrorLog({
3718
- timestamp: Date.now(),
3719
- method: req.method,
3720
- path: req.path,
3721
- statusCode: 500,
3722
- errorMessage: error.message || 'Stream processing error',
3723
- errorStack: error.stack,
3724
- requestHeaders: this.normalizeHeaders(req.headers),
3725
- requestBody: req.body ? JSON.stringify(req.body) : undefined,
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,
3737
- });
3738
- }
3739
- catch (logError) {
3740
- console.error('[Proxy] Failed to log error:', logError);
3741
- }
3742
- yield finalizeLog(500, error.message);
3743
- }
3744
- }));
3738
+ resolve();
3739
+ });
3740
+ });
3741
+ });
3742
+ try {
3743
+ yield runStreamPipeline();
3744
+ }
3745
+ catch (error) {
3746
+ if (this.isClientDisconnectError(error, res)) {
3747
+ console.warn('[Proxy] Default stream pipeline closed because client disconnected');
3748
+ yield finalizeLog(499, 'Client disconnected');
3749
+ return;
3750
+ }
3751
+ console.error('[Proxy] Pipeline error (default stream):', error);
3752
+ // 记录到错误日志
3753
+ try {
3754
+ yield this.dbManager.addErrorLog({
3755
+ timestamp: Date.now(),
3756
+ method: req.method,
3757
+ path: req.path,
3758
+ statusCode: 500,
3759
+ errorMessage: error.message || 'Stream processing error',
3760
+ errorStack: error.stack,
3761
+ requestHeaders: this.normalizeHeaders(req.headers),
3762
+ requestBody: req.body ? JSON.stringify(req.body) : undefined,
3763
+ upstreamRequest: upstreamRequestForLog,
3764
+ responseHeaders: responseHeadersForLog,
3765
+ ruleId: rule.id,
3766
+ targetType,
3767
+ targetServiceId: service.id,
3768
+ targetServiceName: service.name,
3769
+ targetModel: rule.targetModel || ((_d = req.body) === null || _d === void 0 ? void 0 : _d.model),
3770
+ vendorId: service.vendorId,
3771
+ vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
3772
+ requestModel: (_e = req.body) === null || _e === void 0 ? void 0 : _e.model,
3773
+ responseTime: Date.now() - startTime,
3774
+ });
3775
+ }
3776
+ catch (logError) {
3777
+ console.error('[Proxy] Failed to log error:', logError);
3778
+ }
3779
+ yield finalizeLog(500, error.message);
3780
+ if (failoverEnabled && !this.isResponseCommitted(res)) {
3781
+ throw this.createFailoverError(error.message || 'Stream processing error', 500, error);
3782
+ }
3783
+ return;
3784
+ }
3785
+ finalizeChunks();
3786
+ // 检测空流:上游返回 SSE Content-Type 但没有发送任何事件数据
3787
+ const collectedEvents = eventCollector.getEvents();
3788
+ if (collectedEvents.length === 0) {
3789
+ const emptyStreamMsg = 'Upstream API returned an empty stream (HTTP 200, no SSE events)';
3790
+ console.warn(`[Proxy] ${emptyStreamMsg}`);
3791
+ yield finalizeLog(200, emptyStreamMsg);
3792
+ if (!res.writableEnded) {
3793
+ res.end();
3794
+ }
3795
+ return;
3796
+ }
3797
+ // 关键修复:识别 stream 内部的 response.failed / error 事件,归类为错误并触发 failover 交接
3798
+ const streamFailure = this.detectStreamFailure(collectedEvents);
3799
+ if (streamFailure) {
3800
+ try {
3801
+ yield this.dbManager.addErrorLog({
3802
+ timestamp: Date.now(),
3803
+ method: req.method,
3804
+ path: req.path,
3805
+ statusCode: streamFailure.statusCode,
3806
+ errorMessage: streamFailure.errorMessage,
3807
+ requestHeaders: this.normalizeHeaders(req.headers),
3808
+ requestBody: req.body ? JSON.stringify(req.body) : undefined,
3809
+ upstreamRequest: upstreamRequestForLog,
3810
+ responseHeaders: responseHeadersForLog,
3811
+ responseBody: responseBodyForLog,
3812
+ ruleId: rule.id,
3813
+ targetType,
3814
+ targetServiceId: service.id,
3815
+ targetServiceName: service.name,
3816
+ targetModel: rule.targetModel || ((_f = req.body) === null || _f === void 0 ? void 0 : _f.model),
3817
+ vendorId: service.vendorId,
3818
+ vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
3819
+ requestModel: (_g = req.body) === null || _g === void 0 ? void 0 : _g.model,
3820
+ responseTime: Date.now() - startTime,
3821
+ });
3822
+ }
3823
+ catch (logError) {
3824
+ console.error('[Proxy] Failed to log stream failure:', logError);
3825
+ }
3826
+ yield finalizeLog(streamFailure.statusCode, streamFailure.errorMessage);
3827
+ if (failoverEnabled && !this.isResponseCommitted(res)) {
3828
+ throw this.createFailoverError(streamFailure.errorMessage, streamFailure.statusCode);
3829
+ }
3830
+ return;
3745
3831
  }
3832
+ yield finalizeLog(res.statusCode);
3746
3833
  return;
3747
3834
  }
3748
3835
  let responseData = response.data;
3749
3836
  if (streamRequested && response.data && typeof response.data.on === 'function' && !isEventStream) {
3750
3837
  const raw = yield this.readStreamBody(response.data);
3751
- responseData = (_d = this.safeJsonParse(raw)) !== null && _d !== void 0 ? _d : raw;
3838
+ responseData = (_h = this.safeJsonParse(raw)) !== null && _h !== void 0 ? _h : raw;
3752
3839
  }
3753
3840
  // 收集响应头
3754
3841
  responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
3755
- // 检测上游空响应(HTTP 200 但 body 为空)
3842
+ // 检测上游空响应(HTTP 200 但 body 为空)— 透传 200
3756
3843
  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
- }
3844
+ const emptyInfoMsg = 'Upstream API returned an empty response (HTTP 200), passing through';
3845
+ console.warn(`[Proxy] ${emptyInfoMsg}`);
3762
3846
  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
- }
3847
+ yield finalizeLog(200, emptyInfoMsg);
3848
+ res.status(200).end();
3806
3849
  return;
3807
3850
  }
3808
3851
  // 使用统一的响应转换方法
@@ -3872,10 +3915,10 @@ class ProxyServer {
3872
3915
  targetType,
3873
3916
  targetServiceId: service.id,
3874
3917
  targetServiceName: service.name,
3875
- targetModel: rule.targetModel || ((_g = req.body) === null || _g === void 0 ? void 0 : _g.model),
3918
+ targetModel: rule.targetModel || ((_j = req.body) === null || _j === void 0 ? void 0 : _j.model),
3876
3919
  vendorId: service.vendorId,
3877
3920
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
3878
- requestModel: (_h = req.body) === null || _h === void 0 ? void 0 : _h.model,
3921
+ requestModel: (_k = req.body) === null || _k === void 0 ? void 0 : _k.model,
3879
3922
  upstreamRequest: upstreamRequestForLog,
3880
3923
  responseTime: Date.now() - startTime,
3881
3924
  });
@@ -3890,8 +3933,9 @@ class ProxyServer {
3890
3933
  console.error('Proxy error:', error);
3891
3934
  // 检测是否是 timeout 错误
3892
3935
  const isTimeout = error.code === 'ECONNABORTED' ||
3893
- ((_j = error.message) === null || _j === void 0 ? void 0 : _j.toLowerCase().includes('timeout')) ||
3936
+ ((_l = error.message) === null || _l === void 0 ? void 0 : _l.toLowerCase().includes('timeout')) ||
3894
3937
  (error.errno && error.errno === 'ETIMEDOUT');
3938
+ const statusCode = isTimeout ? 504 : this.getErrorStatusCode(error, 500);
3895
3939
  const baseErrorMessage = isTimeout
3896
3940
  ? 'Request timeout - the upstream API took too long to respond'
3897
3941
  : (error.message || 'Internal server error');
@@ -3905,7 +3949,7 @@ class ProxyServer {
3905
3949
  timestamp: Date.now(),
3906
3950
  method: req.method,
3907
3951
  path: req.path,
3908
- statusCode: isTimeout ? 504 : 500,
3952
+ statusCode,
3909
3953
  errorMessage: errorMessage,
3910
3954
  errorStack: error.stack,
3911
3955
  requestHeaders: this.normalizeHeaders(req.headers),
@@ -3915,17 +3959,17 @@ class ProxyServer {
3915
3959
  targetType,
3916
3960
  targetServiceId: service.id,
3917
3961
  targetServiceName: service.name,
3918
- targetModel: rule.targetModel || ((_k = req.body) === null || _k === void 0 ? void 0 : _k.model),
3962
+ targetModel: rule.targetModel || ((_m = req.body) === null || _m === void 0 ? void 0 : _m.model),
3919
3963
  vendorId: service.vendorId,
3920
3964
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
3921
- requestModel: (_l = req.body) === null || _l === void 0 ? void 0 : _l.model,
3965
+ requestModel: (_o = req.body) === null || _o === void 0 ? void 0 : _o.model,
3922
3966
  upstreamRequest: upstreamRequestForLog,
3923
3967
  responseHeaders: responseHeadersForLog,
3924
3968
  responseTime: Date.now() - startTime,
3925
3969
  });
3926
- yield finalizeLog(isTimeout ? 504 : 500, errorMessage);
3970
+ yield finalizeLog(statusCode, errorMessage);
3927
3971
  if (failoverEnabled) {
3928
- throw this.createFailoverError(errorMessage, isTimeout ? 504 : 500, error);
3972
+ throw this.createFailoverError(errorMessage, statusCode, error);
3929
3973
  }
3930
3974
  if (this.isResponseCommitted(res)) {
3931
3975
  return;
@@ -3954,12 +3998,12 @@ class ProxyServer {
3954
3998
  }
3955
3999
  else {
3956
4000
  // 非流式请求:返回 JSON 格式
3957
- res.status(500).json(claudeError);
4001
+ res.status(statusCode).json(claudeError);
3958
4002
  }
3959
4003
  }
3960
4004
  else {
3961
4005
  // 对于 Codex,返回 JSON 格式的错误响应
3962
- res.status(500).json({ error: errorMessage });
4006
+ res.status(statusCode).json({ error: errorMessage });
3963
4007
  }
3964
4008
  }
3965
4009
  finally {