aicodeswitch 5.2.11 → 5.2.13

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.
@@ -52,7 +52,9 @@ const crypto_1 = __importDefault(require("crypto"));
52
52
  const streaming_1 = require("./transformers/streaming");
53
53
  const model_rewrite_transform_1 = require("./transformers/model-rewrite-transform");
54
54
  const chunk_collector_1 = require("./transformers/chunk-collector");
55
+ const stream_timing_transform_1 = require("./transformers/stream-timing-transform");
55
56
  const rules_status_service_1 = require("./rules-status-service");
57
+ const agent_map_1 = require("./agent-map");
56
58
  const index_1 = require("./conversions/index");
57
59
  const stream_converter_adapter_1 = require("./conversions/stream-converter-adapter");
58
60
  const types_1 = require("../types");
@@ -219,6 +221,12 @@ class ProxyServer {
219
221
  writable: true,
220
222
  value: null
221
223
  });
224
+ Object.defineProperty(this, "performanceTracker", {
225
+ enumerable: true,
226
+ configurable: true,
227
+ writable: true,
228
+ value: null
229
+ });
222
230
  // 请求去重缓存:用于防止同一个请求被重复计数(如网络重试)
223
231
  // key: requestHash, value: timestamp
224
232
  Object.defineProperty(this, "requestDedupeCache", {
@@ -253,6 +261,43 @@ class ProxyServer {
253
261
  getAccessKeyModule() {
254
262
  return this.accessKeyModule;
255
263
  }
264
+ /** 设置服务性能统计 tracker(全局,与 AUTH 无关) */
265
+ setPerformanceTracker(tracker) {
266
+ this.performanceTracker = tracker;
267
+ }
268
+ /** 获取服务性能统计 tracker */
269
+ getPerformanceTracker() {
270
+ return this.performanceTracker;
271
+ }
272
+ /**
273
+ * 采集一次请求的服务性能数据点(全局,与 AUTH 无关)。
274
+ * 在两条转发路径的 finalizeLog 公共点调用,覆盖 AccessKey + 普通路由。
275
+ * 流式:依据 streamTiming 精确计算 TTFT 与生成阶段吞吐;非流式:端到端估算(estimated)。
276
+ */
277
+ emitPerformance(params) {
278
+ const tracker = this.performanceTracker;
279
+ if (!tracker)
280
+ return;
281
+ const { statusCode, startTime, usage, streamTiming, service, vendorId, vendorName, model } = params;
282
+ const isError = statusCode >= 400;
283
+ const outputTokens = usage === null || usage === void 0 ? void 0 : usage.outputTokens;
284
+ const responseMs = Date.now() - startTime;
285
+ let ttftMs;
286
+ let tokensPerSecond;
287
+ let timingAccuracy = 'estimated';
288
+ if (streamTiming && streamTiming.hasTiming()) {
289
+ timingAccuracy = 'precise';
290
+ ttftMs = streamTiming.firstEventAt - startTime;
291
+ const generationMs = streamTiming.lastEventAt - streamTiming.firstEventAt;
292
+ if (outputTokens && generationMs > 0) {
293
+ tokensPerSecond = outputTokens / (generationMs / 1000);
294
+ }
295
+ }
296
+ else if (outputTokens && responseMs > 0) {
297
+ tokensPerSecond = outputTokens / (responseMs / 1000);
298
+ }
299
+ tracker.recordPerformance(vendorId !== null && vendorId !== void 0 ? vendorId : service.vendorId, vendorName, service.id, service.name, model, { ttftMs, tokensPerSecond, outputTokens, timingAccuracy, isError });
300
+ }
256
301
  /**
257
302
  * 从请求中提取 API Key(支持三种 Header,按优先级依次尝试)
258
303
  */
@@ -705,6 +750,7 @@ class ProxyServer {
705
750
  responseTime: Date.now() - requestStartAt,
706
751
  // 添加最后失败的服务信息
707
752
  ruleId: lastFailedRule === null || lastFailedRule === void 0 ? void 0 : lastFailedRule.id,
753
+ routeId: route === null || route === void 0 ? void 0 : route.id,
708
754
  targetServiceId: lastFailedService === null || lastFailedService === void 0 ? void 0 : lastFailedService.id,
709
755
  targetServiceName: lastFailedService === null || lastFailedService === void 0 ? void 0 : lastFailedService.name,
710
756
  targetModel: (lastFailedRule === null || lastFailedRule === void 0 ? void 0 : lastFailedRule.targetModel) || ((_e = req.body) === null || _e === void 0 ? void 0 : _e.model),
@@ -3371,7 +3417,7 @@ class ProxyServer {
3371
3417
  }
3372
3418
  proxyRequest(req, res, route, rule, service, options) {
3373
3419
  return __awaiter(this, void 0, void 0, function* () {
3374
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
3420
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
3375
3421
  res.locals.skipLog = true;
3376
3422
  const startTime = Date.now();
3377
3423
  const rawSourceType = service.sourceType || 'openai-chat';
@@ -3379,8 +3425,15 @@ class ProxyServer {
3379
3425
  const sourceType = (0, type_migration_1.normalizeSourceType)(rawSourceType);
3380
3426
  const targetType = this.inferToolFromRequest(req);
3381
3427
  const sessionId = this.defaultExtractSessionId(req, targetType) || '-';
3428
+ // Agent Map:在途请求注册(active 状态判定依据)
3429
+ const _accessKeyCtxAtStart = req._accessKeyCtx;
3430
+ agent_map_1.agentMapService.startRequest(sessionId, targetType, {
3431
+ source: _accessKeyCtxAtStart ? 'access-key' : 'global',
3432
+ keyId: (_a = _accessKeyCtxAtStart === null || _accessKeyCtxAtStart === void 0 ? void 0 : _accessKeyCtxAtStart.accessKey) === null || _a === void 0 ? void 0 : _a.id,
3433
+ keyName: (_b = _accessKeyCtxAtStart === null || _accessKeyCtxAtStart === void 0 ? void 0 : _accessKeyCtxAtStart.accessKey) === null || _b === void 0 ? void 0 : _b.name,
3434
+ });
3382
3435
  const vendor = this.dbManager.getVendorByServiceId(service.id);
3383
- console.log(`\x1b[32m[Request Start]\x1b[0m client=${targetType}, session=${sessionId}, rule=${rule.id}(${rule.contentType}), vendor=${(vendor === null || vendor === void 0 ? void 0 : vendor.name) || '-'}, service=${service.name}, model=${rule.targetModel || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model) || '-'}`);
3436
+ console.log(`\x1b[32m[Request Start]\x1b[0m client=${targetType}, session=${sessionId}, rule=${rule.id}(${rule.contentType}), vendor=${(vendor === null || vendor === void 0 ? void 0 : vendor.name) || '-'}, service=${service.name}, model=${rule.targetModel || ((_c = req.body) === null || _c === void 0 ? void 0 : _c.model) || '-'}`);
3384
3437
  const failoverEnabled = (options === null || options === void 0 ? void 0 : options.failoverEnabled) === true;
3385
3438
  const forwardedToServiceName = options === null || options === void 0 ? void 0 : options.forwardedToServiceName;
3386
3439
  const useOriginalConfig = (options === null || options === void 0 ? void 0 : options.useOriginalConfig) === true;
@@ -3544,12 +3597,45 @@ class ProxyServer {
3544
3597
  let downstreamResponseBodyForLog;
3545
3598
  let upstreamRequestForLog;
3546
3599
  let actuallyUsedProxy = false; // 标记是否实际使用了代理
3600
+ // 服务性能打点:流式分支会创建实例并注入 pipeline;finalizeLog 据此判定 precise/estimated
3601
+ let streamTiming = null;
3547
3602
  // 标记规则正在使用
3548
3603
  rules_status_service_1.rulesStatusBroadcaster.markRuleInUse(route.id, rule.id);
3549
3604
  const finalizeLog = (statusCode, error) => __awaiter(this, void 0, void 0, function* () {
3550
- var _a, _b, _c, _d, _e, _f, _g;
3605
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
3551
3606
  if (logged)
3552
3607
  return;
3608
+ // 服务性能数据点采集(全局,与 AUTH 无关;独立于 enableLogging 开关)
3609
+ this.emitPerformance({
3610
+ statusCode, startTime, usage: usageForLog, streamTiming,
3611
+ service, vendorId: vendor === null || vendor === void 0 ? void 0 : vendor.id, vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
3612
+ model: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
3613
+ });
3614
+ // Agent Map:在途请求注销 + 活动/状态采集广播(独立于 enableLogging,仅执行一次)
3615
+ if (!res.locals._agentMapRecorded) {
3616
+ res.locals._agentMapRecorded = true;
3617
+ agent_map_1.agentMapService.endRequest(sessionId);
3618
+ const _akCtx = req._accessKeyCtx;
3619
+ const _tokensDelta = (usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.totalTokens) ||
3620
+ (((usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.inputTokens) || 0) + ((usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.outputTokens) || 0));
3621
+ agent_map_1.agentMapService.onFinalized({
3622
+ sessionId,
3623
+ agent: targetType,
3624
+ source: _akCtx ? 'access-key' : 'global',
3625
+ keyId: (_b = _akCtx === null || _akCtx === void 0 ? void 0 : _akCtx.accessKey) === null || _b === void 0 ? void 0 : _b.id,
3626
+ keyName: (_c = _akCtx === null || _akCtx === void 0 ? void 0 : _akCtx.accessKey) === null || _c === void 0 ? void 0 : _c.name,
3627
+ title: this.defaultExtractSessionTitle(req, sessionId),
3628
+ timestamp: Date.now(),
3629
+ statusCode,
3630
+ // 采用真正转发给上游的模型(转换+覆盖后的 requestBody.model),而非 rule.targetModel 预测值,
3631
+ // 避免规则未配置 targetModel 时回退成编程工具提交的 req.body.model
3632
+ model: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_d = req.body) === null || _d === void 0 ? void 0 : _d.model),
3633
+ tokensDelta: _tokensDelta,
3634
+ body: req.body,
3635
+ downstreamResponseBody: downstreamResponseBodyForLog !== null && downstreamResponseBodyForLog !== void 0 ? downstreamResponseBodyForLog : responseBodyForLog,
3636
+ responseBody: responseBodyForLog,
3637
+ });
3638
+ }
3553
3639
  const isError = statusCode >= 400;
3554
3640
  if (isError) {
3555
3641
  console.log(`\x1b[31m[Request Error]\x1b[0m client=${targetType}, session=${sessionId}, rule=${rule.id}(${rule.contentType}), vendor=${(vendor === null || vendor === void 0 ? void 0 : vendor.name) || '-'}, service=${service.name}, status=${statusCode}, time=${Date.now() - startTime}ms${error ? `, error=${error}` : ''}`);
@@ -3558,7 +3644,7 @@ class ProxyServer {
3558
3644
  console.log(`\x1b[33m[Request End]\x1b[0m client=${targetType}, session=${sessionId}, rule=${rule.id}(${rule.contentType}), vendor=${(vendor === null || vendor === void 0 ? void 0 : vendor.name) || '-'}, service=${service.name}, status=${statusCode}, time=${Date.now() - startTime}ms`);
3559
3645
  }
3560
3646
  // 检查是否启用日志记录(默认启用)
3561
- const enableLogging = ((_a = this.config) === null || _a === void 0 ? void 0 : _a.enableLogging) !== false; // 默认为 true
3647
+ const enableLogging = ((_e = this.config) === null || _e === void 0 ? void 0 : _e.enableLogging) !== false; // 默认为 true
3562
3648
  if (!enableLogging) {
3563
3649
  return;
3564
3650
  }
@@ -3582,13 +3668,14 @@ class ProxyServer {
3582
3668
  error,
3583
3669
  contentType: rule.contentType,
3584
3670
  ruleId: rule.id,
3671
+ routeId: route === null || route === void 0 ? void 0 : route.id,
3585
3672
  targetType,
3586
3673
  targetServiceId: service.id,
3587
3674
  targetServiceName: service.name,
3588
- targetModel: rule.targetModel || ((_b = req.body) === null || _b === void 0 ? void 0 : _b.model),
3675
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_f = req.body) === null || _f === void 0 ? void 0 : _f.model),
3589
3676
  vendorId: service.vendorId,
3590
3677
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
3591
- requestModel: (_c = req.body) === null || _c === void 0 ? void 0 : _c.model,
3678
+ requestModel: (_g = req.body) === null || _g === void 0 ? void 0 : _g.model,
3592
3679
  tags: this.buildRelayTags(relayedForLog, useOriginalConfig),
3593
3680
  responseHeaders: responseHeadersForLog,
3594
3681
  responseBody: responseBodyForLog,
@@ -3622,7 +3709,7 @@ class ProxyServer {
3622
3709
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
3623
3710
  serviceId: service.id,
3624
3711
  serviceName: service.name,
3625
- model: rule.targetModel || ((_d = req.body) === null || _d === void 0 ? void 0 : _d.model),
3712
+ model: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_h = req.body) === null || _h === void 0 ? void 0 : _h.model),
3626
3713
  totalTokens: sessionTokens,
3627
3714
  }).catch(err => console.error('[KeySession] upsert error:', err));
3628
3715
  }
@@ -3646,13 +3733,14 @@ class ProxyServer {
3646
3733
  error,
3647
3734
  contentType: rule.contentType,
3648
3735
  ruleId: rule.id,
3736
+ routeId: route === null || route === void 0 ? void 0 : route.id,
3649
3737
  targetType,
3650
3738
  targetServiceId: service.id,
3651
3739
  targetServiceName: service.name,
3652
- targetModel: rule.targetModel || ((_e = req.body) === null || _e === void 0 ? void 0 : _e.model),
3740
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_j = req.body) === null || _j === void 0 ? void 0 : _j.model),
3653
3741
  vendorId: service.vendorId,
3654
3742
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
3655
- requestModel: (_f = req.body) === null || _f === void 0 ? void 0 : _f.model,
3743
+ requestModel: (_k = req.body) === null || _k === void 0 ? void 0 : _k.model,
3656
3744
  tags: this.buildRelayTags(relayedForLog, useOriginalConfig),
3657
3745
  });
3658
3746
  }
@@ -3665,7 +3753,7 @@ class ProxyServer {
3665
3753
  const vendors = this.dbManager.getVendors();
3666
3754
  const vendorForLog = vendors.find(v => v.id === service.vendorId);
3667
3755
  // 从请求体中提取模型信息
3668
- const requestModel = (_g = req.body) === null || _g === void 0 ? void 0 : _g.model;
3756
+ const requestModel = (_l = req.body) === null || _l === void 0 ? void 0 : _l.model;
3669
3757
  const tagsForLog = this.buildRelayTags(relayedForLog, useOriginalConfig);
3670
3758
  if (extraTagsForLog.length > 0) {
3671
3759
  tagsForLog.push(...extraTagsForLog);
@@ -3684,10 +3772,11 @@ class ProxyServer {
3684
3772
  // 新增字段
3685
3773
  contentType: rule.contentType,
3686
3774
  ruleId: rule.id,
3775
+ routeId: route === null || route === void 0 ? void 0 : route.id,
3687
3776
  targetType,
3688
3777
  targetServiceId: service.id,
3689
3778
  targetServiceName: service.name,
3690
- targetModel: rule.targetModel || requestModel,
3779
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_m = req.body) === null || _m === void 0 ? void 0 : _m.model),
3691
3780
  vendorId: service.vendorId,
3692
3781
  vendorName: vendorForLog === null || vendorForLog === void 0 ? void 0 : vendorForLog.name,
3693
3782
  requestModel,
@@ -3715,7 +3804,7 @@ class ProxyServer {
3715
3804
  vendorName: vendorForLog === null || vendorForLog === void 0 ? void 0 : vendorForLog.name,
3716
3805
  serviceId: service.id,
3717
3806
  serviceName: service.name,
3718
- model: requestModel || rule.targetModel,
3807
+ model: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_o = req.body) === null || _o === void 0 ? void 0 : _o.model),
3719
3808
  totalTokens,
3720
3809
  highIqMode: rule.contentType === 'high-iq' ? true : existingSession === null || existingSession === void 0 ? void 0 : existingSession.highIqMode,
3721
3810
  highIqRuleId: rule.contentType === 'high-iq' ? rule.id : existingSession === null || existingSession === void 0 ? void 0 : existingSession.highIqRuleId,
@@ -3816,7 +3905,7 @@ class ProxyServer {
3816
3905
  targetType,
3817
3906
  targetServiceId: service.id,
3818
3907
  targetServiceName: service.name,
3819
- targetModel: rule.targetModel || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
3908
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
3820
3909
  vendorId: service.vendorId,
3821
3910
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
3822
3911
  requestModel: (_b = req.body) === null || _b === void 0 ? void 0 : _b.model,
@@ -3882,7 +3971,7 @@ class ProxyServer {
3882
3971
  // responses→responses 直连非 OpenAI 官方端点时,需降级兼容(剥离 custom/namespace 等私有工具与非标准字段)
3883
3972
  const sanitizeBody = clientFormat === 'responses' && (0, source_type_mapping_1.sourceTypeToFormat)(sourceType) === 'responses' && !(0, index_1.isOfficialOpenAiApi)(effectiveApiUrl || '');
3884
3973
  const transformedRequestBody = this.transformRequestToUpstream(targetType, sourceType, payloadForTransform, rule.targetModel, providerConfig, serverToolConfig, sanitizeBody);
3885
- requestBody = (_b = transformedRequestBody !== null && transformedRequestBody !== void 0 ? transformedRequestBody : this.cloneRequestBody(originalToolRequestBody)) !== null && _b !== void 0 ? _b : {};
3974
+ requestBody = (_d = transformedRequestBody !== null && transformedRequestBody !== void 0 ? transformedRequestBody : this.cloneRequestBody(originalToolRequestBody)) !== null && _d !== void 0 ? _d : {};
3886
3975
  // 对最终即将发送到上游的 Claude compact 请求再做一次兜底清理,
3887
3976
  // 避免中间转换/覆盖步骤重新引入未配对的 tool_use。
3888
3977
  if (rule.contentType === 'compact' && targetType === 'claude-code' && Array.isArray(requestBody === null || requestBody === void 0 ? void 0 : requestBody.messages)) {
@@ -3988,7 +4077,7 @@ class ProxyServer {
3988
4077
  let errorResponseData = response.data;
3989
4078
  if (streamRequested && response.data && typeof response.data.on === 'function') {
3990
4079
  const raw = yield this.readStreamBody(response.data);
3991
- errorResponseData = (_c = this.safeJsonParse(raw)) !== null && _c !== void 0 ? _c : raw;
4080
+ errorResponseData = (_e = this.safeJsonParse(raw)) !== null && _e !== void 0 ? _e : raw;
3992
4081
  }
3993
4082
  responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
3994
4083
  yield handleUpstreamHttpError(response.status, errorResponseData, responseHeaders, contentType);
@@ -4026,10 +4115,10 @@ class ProxyServer {
4026
4115
  targetType,
4027
4116
  targetServiceId: service.id,
4028
4117
  targetServiceName: service.name,
4029
- targetModel: rule.targetModel || ((_d = req.body) === null || _d === void 0 ? void 0 : _d.model),
4118
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_f = req.body) === null || _f === void 0 ? void 0 : _f.model),
4030
4119
  vendorId: service.vendorId,
4031
4120
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4032
- requestModel: (_e = req.body) === null || _e === void 0 ? void 0 : _e.model,
4121
+ requestModel: (_g = req.body) === null || _g === void 0 ? void 0 : _g.model,
4033
4122
  responseTime: Date.now() - startTime,
4034
4123
  });
4035
4124
  }
@@ -4061,11 +4150,13 @@ class ProxyServer {
4061
4150
  const downstreamChunkCollector = new chunk_collector_1.ChunkCollectorTransform(() => {
4062
4151
  rules_status_service_1.rulesStatusBroadcaster.refreshRuleInUse(route.id, rule.id);
4063
4152
  });
4153
+ // 服务性能打点:记录首/末 SSE 事件时间,用于 TTFT 与生成阶段吞吐
4154
+ streamTiming = new stream_timing_transform_1.StreamTimingTransform(startTime);
4064
4155
  const compactResponseSanitizer = rule.contentType === 'compact' && targetType === 'claude-code'
4065
4156
  ? new ClaudeCompactResponseSanitizer()
4066
4157
  : null;
4067
4158
  // 流式 model 回写:将上游返回的 model 改写为客户端请求时的原始模型名
4068
- const originalModel = (_f = req.body) === null || _f === void 0 ? void 0 : _f.model;
4159
+ const originalModel = (_h = req.body) === null || _h === void 0 ? void 0 : _h.model;
4069
4160
  const modelRewriter = originalModel ? new model_rewrite_transform_1.ModelRewriteTransform(originalModel) : null;
4070
4161
  responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
4071
4162
  // 使用 transformSSEToTool 方法选择转换器
@@ -4102,7 +4193,7 @@ class ProxyServer {
4102
4193
  ensureResponseWritable();
4103
4194
  return yield new Promise((resolve, reject) => {
4104
4195
  if (converter) {
4105
- const streamStages = [streamSource, parser, eventCollector, converter];
4196
+ const streamStages = [streamSource, parser, eventCollector, streamTiming, converter];
4106
4197
  if (compactResponseSanitizer) {
4107
4198
  streamStages.push(compactResponseSanitizer);
4108
4199
  }
@@ -4120,7 +4211,7 @@ class ProxyServer {
4120
4211
  });
4121
4212
  return;
4122
4213
  }
4123
- const streamStages = [streamSource, parser, eventCollector];
4214
+ const streamStages = [streamSource, parser, eventCollector, streamTiming];
4124
4215
  if (compactResponseSanitizer) {
4125
4216
  streamStages.push(compactResponseSanitizer);
4126
4217
  }
@@ -4165,10 +4256,10 @@ class ProxyServer {
4165
4256
  targetType,
4166
4257
  targetServiceId: service.id,
4167
4258
  targetServiceName: service.name,
4168
- targetModel: rule.targetModel || ((_g = req.body) === null || _g === void 0 ? void 0 : _g.model),
4259
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_j = req.body) === null || _j === void 0 ? void 0 : _j.model),
4169
4260
  vendorId: service.vendorId,
4170
4261
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4171
- requestModel: (_h = req.body) === null || _h === void 0 ? void 0 : _h.model,
4262
+ requestModel: (_k = req.body) === null || _k === void 0 ? void 0 : _k.model,
4172
4263
  responseTime: Date.now() - startTime,
4173
4264
  });
4174
4265
  }
@@ -4212,10 +4303,10 @@ class ProxyServer {
4212
4303
  targetType,
4213
4304
  targetServiceId: service.id,
4214
4305
  targetServiceName: service.name,
4215
- targetModel: rule.targetModel || ((_j = req.body) === null || _j === void 0 ? void 0 : _j.model),
4306
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_l = req.body) === null || _l === void 0 ? void 0 : _l.model),
4216
4307
  vendorId: service.vendorId,
4217
4308
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4218
- requestModel: (_k = req.body) === null || _k === void 0 ? void 0 : _k.model,
4309
+ requestModel: (_m = req.body) === null || _m === void 0 ? void 0 : _m.model,
4219
4310
  responseTime: Date.now() - startTime,
4220
4311
  });
4221
4312
  }
@@ -4234,7 +4325,7 @@ class ProxyServer {
4234
4325
  let responseData = response.data;
4235
4326
  if (streamRequested && response.data && typeof response.data.on === 'function' && !isEventStream) {
4236
4327
  const raw = yield this.readStreamBody(response.data);
4237
- responseData = (_l = this.safeJsonParse(raw)) !== null && _l !== void 0 ? _l : raw;
4328
+ responseData = (_o = this.safeJsonParse(raw)) !== null && _o !== void 0 ? _o : raw;
4238
4329
  }
4239
4330
  // 收集响应头
4240
4331
  responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
@@ -4256,7 +4347,7 @@ class ProxyServer {
4256
4347
  usageForLog = this.extractTokenUsageFromResponse(responseData, sourceType);
4257
4348
  console.log('[Proxy] Non-stream response: extracted usageForLog:', usageForLog);
4258
4349
  // 回写 model 字段:将上游返回的 model 改写为客户端请求时的原始模型名
4259
- const originalModel = (_m = req.body) === null || _m === void 0 ? void 0 : _m.model;
4350
+ const originalModel = (_p = req.body) === null || _p === void 0 ? void 0 : _p.model;
4260
4351
  (0, model_rewrite_transform_1.rewriteResponseModel)(normalizedConverted, originalModel);
4261
4352
  (0, model_rewrite_transform_1.rewriteResponseModel)(responseData, originalModel);
4262
4353
  this.copyResponseHeaders(responseHeaders, res);
@@ -4321,10 +4412,10 @@ class ProxyServer {
4321
4412
  targetType,
4322
4413
  targetServiceId: service.id,
4323
4414
  targetServiceName: service.name,
4324
- targetModel: rule.targetModel || ((_o = req.body) === null || _o === void 0 ? void 0 : _o.model),
4415
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_q = req.body) === null || _q === void 0 ? void 0 : _q.model),
4325
4416
  vendorId: service.vendorId,
4326
4417
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4327
- requestModel: (_p = req.body) === null || _p === void 0 ? void 0 : _p.model,
4418
+ requestModel: (_r = req.body) === null || _r === void 0 ? void 0 : _r.model,
4328
4419
  upstreamRequest: upstreamRequestForLog,
4329
4420
  responseTime: Date.now() - startTime,
4330
4421
  });
@@ -4339,7 +4430,7 @@ class ProxyServer {
4339
4430
  console.error('Proxy error:', error);
4340
4431
  // 检测是否是 timeout 错误
4341
4432
  const isTimeout = error.code === 'ECONNABORTED' ||
4342
- ((_q = error.message) === null || _q === void 0 ? void 0 : _q.toLowerCase().includes('timeout')) ||
4433
+ ((_s = error.message) === null || _s === void 0 ? void 0 : _s.toLowerCase().includes('timeout')) ||
4343
4434
  (error.errno && error.errno === 'ETIMEDOUT');
4344
4435
  const statusCode = isTimeout ? 504 : this.getErrorStatusCode(error, 500);
4345
4436
  const baseErrorMessage = isTimeout
@@ -4365,10 +4456,10 @@ class ProxyServer {
4365
4456
  targetType,
4366
4457
  targetServiceId: service.id,
4367
4458
  targetServiceName: service.name,
4368
- targetModel: rule.targetModel || ((_r = req.body) === null || _r === void 0 ? void 0 : _r.model),
4459
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_t = req.body) === null || _t === void 0 ? void 0 : _t.model),
4369
4460
  vendorId: service.vendorId,
4370
4461
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4371
- requestModel: (_s = req.body) === null || _s === void 0 ? void 0 : _s.model,
4462
+ requestModel: (_u = req.body) === null || _u === void 0 ? void 0 : _u.model,
4372
4463
  upstreamRequest: upstreamRequestForLog,
4373
4464
  responseHeaders: responseHeadersForLog,
4374
4465
  responseTime: Date.now() - startTime,
@@ -4608,6 +4699,8 @@ class ProxyServer {
4608
4699
  let responseBodyForLog;
4609
4700
  let downstreamResponseBodyForLog;
4610
4701
  let streamChunksForLog;
4702
+ // 服务性能打点:流式分支会创建实例并注入 pipeline
4703
+ let streamTiming = null;
4611
4704
  let responseHeadersForLog;
4612
4705
  let upstreamRequestForLog;
4613
4706
  let relayedForLog = true;
@@ -4629,10 +4722,16 @@ class ProxyServer {
4629
4722
  requestBody = (0, compact_1.normalizeClaudeCompactRequestBody)(requestBody);
4630
4723
  }
4631
4724
  const finalizeLog = (statusCode, error) => __awaiter(this, void 0, void 0, function* () {
4632
- var _a, _b, _c, _d, _e;
4725
+ var _a, _b, _c, _d, _e, _f;
4633
4726
  if (logged)
4634
4727
  return;
4635
4728
  logged = true;
4729
+ // 服务性能数据点采集(全局,与 AUTH 无关;独立于 enableLogging 开关)
4730
+ this.emitPerformance({
4731
+ statusCode, startTime, usage: usageForLog, streamTiming,
4732
+ service, vendorId: vendor === null || vendor === void 0 ? void 0 : vendor.id, vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4733
+ model: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
4734
+ });
4636
4735
  // AccessKey 独立日志处理
4637
4736
  const accessKeyCtx = req._accessKeyCtx;
4638
4737
  if (accessKeyCtx && this.accessKeyModule) {
@@ -4649,12 +4748,13 @@ class ProxyServer {
4649
4748
  error,
4650
4749
  contentType: rule.contentType,
4651
4750
  ruleId: rule.id,
4751
+ routeId: route === null || route === void 0 ? void 0 : route.id,
4652
4752
  targetServiceId: service.id,
4653
4753
  targetServiceName: service.name,
4654
- targetModel: rule.targetModel || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
4754
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_b = req.body) === null || _b === void 0 ? void 0 : _b.model),
4655
4755
  vendorId: service.vendorId,
4656
4756
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4657
- requestModel: (_b = req.body) === null || _b === void 0 ? void 0 : _b.model,
4757
+ requestModel: (_c = req.body) === null || _c === void 0 ? void 0 : _c.model,
4658
4758
  tags: this.buildRelayTags(relayedForLog),
4659
4759
  });
4660
4760
  if (usageForLog && statusCode < 400) {
@@ -4680,7 +4780,7 @@ class ProxyServer {
4680
4780
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4681
4781
  serviceId: service.id,
4682
4782
  serviceName: service.name,
4683
- model: rule.targetModel || ((_c = req.body) === null || _c === void 0 ? void 0 : _c.model),
4783
+ model: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_d = req.body) === null || _d === void 0 ? void 0 : _d.model),
4684
4784
  totalTokens: sessionTokens,
4685
4785
  }).catch(err => console.error('[KeySession] upsert error:', err));
4686
4786
  }
@@ -4702,12 +4802,13 @@ class ProxyServer {
4702
4802
  error,
4703
4803
  contentType: rule.contentType,
4704
4804
  ruleId: rule.id,
4805
+ routeId: route === null || route === void 0 ? void 0 : route.id,
4705
4806
  targetServiceId: service.id,
4706
4807
  targetServiceName: service.name,
4707
- targetModel: rule.targetModel || ((_d = req.body) === null || _d === void 0 ? void 0 : _d.model),
4808
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_e = req.body) === null || _e === void 0 ? void 0 : _e.model),
4708
4809
  vendorId: service.vendorId,
4709
4810
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4710
- requestModel: (_e = req.body) === null || _e === void 0 ? void 0 : _e.model,
4811
+ requestModel: (_f = req.body) === null || _f === void 0 ? void 0 : _f.model,
4711
4812
  tags: this.buildRelayTags(relayedForLog),
4712
4813
  });
4713
4814
  }
@@ -4877,6 +4978,8 @@ class ProxyServer {
4877
4978
  const downstreamChunkCollector = new chunk_collector_1.ChunkCollectorTransform(() => {
4878
4979
  rules_status_service_1.rulesStatusBroadcaster.refreshRuleInUse(route.id, rule.id);
4879
4980
  });
4981
+ // 服务性能打点:记录首/末 SSE 事件时间
4982
+ streamTiming = new stream_timing_transform_1.StreamTimingTransform(startTime);
4880
4983
  responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
4881
4984
  // 流式 model 回写:将上游返回的 model 改写为客户端请求时的原始模型名
4882
4985
  const originalModel = (_d = req.body) === null || _d === void 0 ? void 0 : _d.model;
@@ -4911,7 +5014,7 @@ class ProxyServer {
4911
5014
  return stages;
4912
5015
  };
4913
5016
  if (converter) {
4914
- const stages = buildStages(streamSource, parser, eventCollector, converter);
5017
+ const stages = buildStages(streamSource, parser, eventCollector, streamTiming, converter);
4915
5018
  stream_1.pipeline(...stages, (error) => {
4916
5019
  if (error) {
4917
5020
  reject(error);
@@ -4921,7 +5024,7 @@ class ProxyServer {
4921
5024
  });
4922
5025
  }
4923
5026
  else {
4924
- const stages = buildStages(streamSource, parser, eventCollector);
5027
+ const stages = buildStages(streamSource, parser, eventCollector, streamTiming);
4925
5028
  stream_1.pipeline(...stages, (error) => {
4926
5029
  if (error) {
4927
5030
  reject(error);
@@ -52,8 +52,8 @@ class RulesStatusBroadcaster extends events_1.EventEmitter {
52
52
  enumerable: true,
53
53
  configurable: true,
54
54
  writable: true,
55
- value: 10000
56
- }); // 10秒无活动后标记为空闲
55
+ value: 120000
56
+ }); // 120秒无活动后标记为空闲(兜底安全网,覆盖 thinking hold 等长静默场景)
57
57
  Object.defineProperty(this, "IDLE_DEBOUNCE_DELAY", {
58
58
  enumerable: true,
59
59
  configurable: true,
@@ -258,16 +258,31 @@ class RulesStatusBroadcaster extends events_1.EventEmitter {
258
258
  });
259
259
  }
260
260
  /**
261
- * 刷新规则使用中的不活动定时器(轻量级,仅重置定时器,不修改状态)
262
- * 用于 streaming 过程中持续保持 in_use 状态
261
+ * 刷新规则使用中的不活动定时器(轻量级,仅重置定时器,通常不修改状态)
262
+ * 用于 streaming 过程中持续保持 in_use 状态。
263
+ *
264
+ * 行为:
265
+ * - status === 'in_use':重置不活动定时器,并清除可能 pending 的 idle debounce,
266
+ * 避免已触发的 idle 经 SSE 推送出去(thinking hold 场景的关键修复)。
267
+ * - status === 'idle':说明此前已被错误判空闲,但请求仍在出流——重新标记为 in_use
268
+ * 以便经 SSE 把状态推回"使用中",实现前端自愈。
269
+ * - status === 'error' / 'suspended':早退,这两种终态有独立恢复机制,不应被流式刷新覆盖。
263
270
  */
264
271
  refreshRuleInUse(routeId, ruleId) {
265
272
  const currentStatus = this.ruleStates.get(ruleId);
266
- // 仅当状态已经是 in_use 时才刷新定时器
267
- if ((currentStatus === null || currentStatus === void 0 ? void 0 : currentStatus.status) !== 'in_use')
273
+ // 终态有独立恢复机制,刷新不应覆盖
274
+ if ((currentStatus === null || currentStatus === void 0 ? void 0 : currentStatus.status) === 'error' || (currentStatus === null || currentStatus === void 0 ? void 0 : currentStatus.status) === 'suspended') {
268
275
  return;
276
+ }
269
277
  const timeoutKey = `${routeId}:${ruleId}`;
278
+ // 已被错误判空闲:重新标记为 in_use(内部会清旧定时器/debounce 并 emit statusChanged → SSE 推回使用中)
279
+ if ((currentStatus === null || currentStatus === void 0 ? void 0 : currentStatus.status) === 'idle') {
280
+ this.markRuleInUse(routeId, ruleId);
281
+ return;
282
+ }
283
+ // in_use:重置不活动定时器,并清除 pending 的 idle debounce(阻止已触发的 idle 经 SSE 推送)
270
284
  this.clearRuleTimeout(timeoutKey);
285
+ this.clearIdleDebounce(timeoutKey);
271
286
  const timeout = setTimeout(() => {
272
287
  this.markRuleIdle(routeId, ruleId);
273
288
  }, this.INACTIVITY_TIMEOUT);
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StreamTimingTransform = void 0;
4
+ const stream_1 = require("stream");
5
+ /**
6
+ * StreamTimingTransform - 流式打点 Transform(服务性能统计专用)
7
+ *
8
+ * 透传所有数据(对象模式 / Buffer / string 均可),仅记录:
9
+ * - firstEventAt:首个被解析出的 SSE 事件流经本 Transform 的时刻(≈ 首 Token 返回时刻)
10
+ * - lastEventAt:最后一个事件流经的时刻(≈ 整个返回结束时刻)
11
+ *
12
+ * 由调用方在转发开始前注入 startTime(请求发起时刻),即可派生:
13
+ * - ttftMs = firstEventAt - startTime
14
+ * - generationMs = lastEventAt - firstEventAt
15
+ *
16
+ * 该 Transform 仅做时间记录,不修改任何数据内容,对转发链路零影响。
17
+ */
18
+ class StreamTimingTransform extends stream_1.Transform {
19
+ constructor(startTime) {
20
+ super({ writableObjectMode: true, readableObjectMode: true });
21
+ /** 请求发起时刻(由外部注入) */
22
+ Object.defineProperty(this, "startTime", {
23
+ enumerable: true,
24
+ configurable: true,
25
+ writable: true,
26
+ value: void 0
27
+ });
28
+ /** 首个事件到达时刻(未收到则为 0) */
29
+ Object.defineProperty(this, "firstEventAt", {
30
+ enumerable: true,
31
+ configurable: true,
32
+ writable: true,
33
+ value: 0
34
+ });
35
+ /** 最后一个事件到达时刻 */
36
+ Object.defineProperty(this, "lastEventAt", {
37
+ enumerable: true,
38
+ configurable: true,
39
+ writable: true,
40
+ value: 0
41
+ });
42
+ this.startTime = startTime;
43
+ }
44
+ _transform(chunk, _encoding, callback) {
45
+ try {
46
+ const now = Date.now();
47
+ if (this.firstEventAt === 0) {
48
+ this.firstEventAt = now;
49
+ }
50
+ this.lastEventAt = now;
51
+ this.push(chunk);
52
+ }
53
+ catch (error) {
54
+ console.error('[StreamTimingTransform] Error in _transform:', error);
55
+ }
56
+ callback();
57
+ }
58
+ /** 是否采集到至少一个事件(用于判定精确口径可用性) */
59
+ hasTiming() {
60
+ return this.firstEventAt > 0 && this.lastEventAt > 0;
61
+ }
62
+ }
63
+ exports.StreamTimingTransform = StreamTimingTransform;