aicodeswitch 5.2.12 → 6.0.0

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.
@@ -54,6 +54,7 @@ const model_rewrite_transform_1 = require("./transformers/model-rewrite-transfor
54
54
  const chunk_collector_1 = require("./transformers/chunk-collector");
55
55
  const stream_timing_transform_1 = require("./transformers/stream-timing-transform");
56
56
  const rules_status_service_1 = require("./rules-status-service");
57
+ const agent_map_1 = require("./agent-map");
57
58
  const index_1 = require("./conversions/index");
58
59
  const stream_converter_adapter_1 = require("./conversions/stream-converter-adapter");
59
60
  const types_1 = require("../types");
@@ -66,6 +67,13 @@ const coding_plan_1 = require("./coding-plan");
66
67
  const coding_plan_headers_1 = require("./coding-plan-headers");
67
68
  const auth_1 = require("./auth");
68
69
  const SUPPORTED_TARGETS = ['claude-code', 'codex'];
70
+ /**
71
+ * Fallback(回退原始配置)路径的虚拟供应商归属。
72
+ * 该路径转发的请求不属于任何用户配置的供应商/服务,故用一组固定 ID + 名称
73
+ * 把它纳入服务性能统计(service-performance.json),便于在测速面板单独呈现。
74
+ */
75
+ const FALLBACK_VENDOR_ID = 'fallback-vendor';
76
+ const FALLBACK_VENDOR_NAME = '原始配置 / 直连';
69
77
  /** 默认模型列表 */
70
78
  const DEFAULT_MODELS = [
71
79
  { id: 'claude-sonnet-4-20250514', owned_by: 'anthropic' },
@@ -274,12 +282,16 @@ class ProxyServer {
274
282
  * 流式:依据 streamTiming 精确计算 TTFT 与生成阶段吞吐;非流式:端到端估算(estimated)。
275
283
  */
276
284
  emitPerformance(params) {
285
+ var _a;
277
286
  const tracker = this.performanceTracker;
278
287
  if (!tracker)
279
288
  return;
280
289
  const { statusCode, startTime, usage, streamTiming, service, vendorId, vendorName, model } = params;
281
290
  const isError = statusCode >= 400;
291
+ const inputTokens = usage === null || usage === void 0 ? void 0 : usage.inputTokens;
282
292
  const outputTokens = usage === null || usage === void 0 ? void 0 : usage.outputTokens;
293
+ const computedTotal = (inputTokens || 0) + (outputTokens || 0);
294
+ const totalTokens = (_a = usage === null || usage === void 0 ? void 0 : usage.totalTokens) !== null && _a !== void 0 ? _a : (computedTotal > 0 ? computedTotal : undefined);
283
295
  const responseMs = Date.now() - startTime;
284
296
  let ttftMs;
285
297
  let tokensPerSecond;
@@ -295,7 +307,11 @@ class ProxyServer {
295
307
  else if (outputTokens && responseMs > 0) {
296
308
  tokensPerSecond = outputTokens / (responseMs / 1000);
297
309
  }
298
- tracker.recordPerformance(vendorId !== null && vendorId !== void 0 ? vendorId : service.vendorId, vendorName, service.id, service.name, model, { ttftMs, tokensPerSecond, outputTokens, timingAccuracy, isError });
310
+ // 解析最终归属:Fallback 临时服务没有真实 vendor,service.vendorId 兜底为虚拟供应商;
311
+ // 此时 vendorName 也为空,补上虚拟供应商名,确保不被 recordPerformance 的三元组校验丢弃。
312
+ const effectiveVendorId = vendorId !== null && vendorId !== void 0 ? vendorId : service.vendorId;
313
+ const effectiveVendorName = vendorName !== null && vendorName !== void 0 ? vendorName : (effectiveVendorId === FALLBACK_VENDOR_ID ? FALLBACK_VENDOR_NAME : undefined);
314
+ tracker.recordPerformance(effectiveVendorId, effectiveVendorName, service.id, service.name, model, { ttftMs, tokensPerSecond, outputTokens, inputTokens, totalTokens, timingAccuracy, isError });
299
315
  }
300
316
  /**
301
317
  * 从请求中提取 API Key(支持三种 Header,按优先级依次尝试)
@@ -749,6 +765,7 @@ class ProxyServer {
749
765
  responseTime: Date.now() - requestStartAt,
750
766
  // 添加最后失败的服务信息
751
767
  ruleId: lastFailedRule === null || lastFailedRule === void 0 ? void 0 : lastFailedRule.id,
768
+ routeId: route === null || route === void 0 ? void 0 : route.id,
752
769
  targetServiceId: lastFailedService === null || lastFailedService === void 0 ? void 0 : lastFailedService.id,
753
770
  targetServiceName: lastFailedService === null || lastFailedService === void 0 ? void 0 : lastFailedService.name,
754
771
  targetModel: (lastFailedRule === null || lastFailedRule === void 0 ? void 0 : lastFailedRule.targetModel) || ((_e = req.body) === null || _e === void 0 ? void 0 : _e.model),
@@ -1258,6 +1275,7 @@ class ProxyServer {
1258
1275
  const tempService = {
1259
1276
  id: 'fallback-service',
1260
1277
  name: 'Original Config',
1278
+ vendorId: FALLBACK_VENDOR_ID,
1261
1279
  apiUrl: originalConfig.apiUrl,
1262
1280
  apiKey: originalConfig.apiKey,
1263
1281
  authType: originalConfig.authType,
@@ -3345,8 +3363,28 @@ class ProxyServer {
3345
3363
  });
3346
3364
  return { converter: adapter, extractUsage };
3347
3365
  }
3366
+ /**
3367
+ * 将 SSEEventCollector 归一化后的 usage({input_tokens, output_tokens, total_tokens, cache_read_input_tokens})
3368
+ * 映射为 TokenUsage。collector 已遍历全部事件并合并 Anthropic message_start/message_delta + OpenAI/Gemini 字段,
3369
+ * 因此这里无需再区分上游格式。
3370
+ */
3371
+ tokenUsageFromCollected(extracted) {
3372
+ var _a;
3373
+ if (!extracted)
3374
+ return undefined;
3375
+ const inputTokens = extracted.input_tokens || 0;
3376
+ const outputTokens = extracted.output_tokens || 0;
3377
+ const computedTotal = inputTokens + outputTokens;
3378
+ const totalTokens = (_a = extracted.total_tokens) !== null && _a !== void 0 ? _a : (computedTotal > 0 ? computedTotal : undefined);
3379
+ return {
3380
+ inputTokens,
3381
+ outputTokens,
3382
+ totalTokens,
3383
+ cacheReadInputTokens: extracted.cache_read_input_tokens || 0,
3384
+ };
3385
+ }
3348
3386
  extractTokenUsageFromResponse(responseData, sourceType) {
3349
- var _a, _b, _c, _d, _e, _f;
3387
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
3350
3388
  if (!responseData)
3351
3389
  return undefined;
3352
3390
  const format = (0, source_type_mapping_1.sourceTypeToFormat)(sourceType);
@@ -3362,14 +3400,15 @@ class ProxyServer {
3362
3400
  };
3363
3401
  }
3364
3402
  if (format === 'completions' || format === 'responses') {
3365
- const usage = responseData === null || responseData === void 0 ? void 0 : responseData.usage;
3403
+ // 标准 Responses 非流式 usage 在顶层;个别上游/中转会嵌在 response.usage 下,做兜底
3404
+ const usage = (_a = responseData === null || responseData === void 0 ? void 0 : responseData.usage) !== null && _a !== void 0 ? _a : (_b = responseData === null || responseData === void 0 ? void 0 : responseData.response) === null || _b === void 0 ? void 0 : _b.usage;
3366
3405
  if (!usage)
3367
3406
  return undefined;
3368
3407
  return {
3369
3408
  inputTokens: (usage === null || usage === void 0 ? void 0 : usage.input_tokens) || (usage === null || usage === void 0 ? void 0 : usage.prompt_tokens) || 0,
3370
3409
  outputTokens: (usage === null || usage === void 0 ? void 0 : usage.output_tokens) || (usage === null || usage === void 0 ? void 0 : usage.completion_tokens) || 0,
3371
3410
  totalTokens: (usage === null || usage === void 0 ? void 0 : usage.total_tokens) || 0,
3372
- cacheReadInputTokens: (usage === null || usage === void 0 ? void 0 : usage.cached_tokens) || (usage === null || usage === void 0 ? void 0 : usage.cache_read_input_tokens) || 0,
3411
+ cacheReadInputTokens: (usage === null || usage === void 0 ? void 0 : usage.cached_tokens) || (usage === null || usage === void 0 ? void 0 : usage.cache_read_input_tokens) || ((_c = usage === null || usage === void 0 ? void 0 : usage.input_tokens_details) === null || _c === void 0 ? void 0 : _c.cached_tokens) || 0,
3373
3412
  };
3374
3413
  }
3375
3414
  if (format === 'claude') {
@@ -3377,7 +3416,7 @@ class ProxyServer {
3377
3416
  return {
3378
3417
  inputTokens: (responseData === null || responseData === void 0 ? void 0 : responseData.input_tokens) || 0,
3379
3418
  outputTokens: (responseData === null || responseData === void 0 ? void 0 : responseData.output_tokens) || 0,
3380
- totalTokens: ((_a = responseData === null || responseData === void 0 ? void 0 : responseData.input_tokens) !== null && _a !== void 0 ? _a : 0) + ((_b = responseData === null || responseData === void 0 ? void 0 : responseData.output_tokens) !== null && _b !== void 0 ? _b : 0) || undefined,
3419
+ totalTokens: ((_d = responseData === null || responseData === void 0 ? void 0 : responseData.input_tokens) !== null && _d !== void 0 ? _d : 0) + ((_e = responseData === null || responseData === void 0 ? void 0 : responseData.output_tokens) !== null && _e !== void 0 ? _e : 0) || undefined,
3381
3420
  cacheReadInputTokens: (responseData === null || responseData === void 0 ? void 0 : responseData.cache_read_input_tokens) || 0,
3382
3421
  };
3383
3422
  }
@@ -3387,7 +3426,7 @@ class ProxyServer {
3387
3426
  return {
3388
3427
  inputTokens: (usage === null || usage === void 0 ? void 0 : usage.input_tokens) || 0,
3389
3428
  outputTokens: (usage === null || usage === void 0 ? void 0 : usage.output_tokens) || 0,
3390
- totalTokens: ((_c = usage === null || usage === void 0 ? void 0 : usage.input_tokens) !== null && _c !== void 0 ? _c : 0) + ((_d = usage === null || usage === void 0 ? void 0 : usage.output_tokens) !== null && _d !== void 0 ? _d : 0) || undefined,
3429
+ totalTokens: ((_f = usage === null || usage === void 0 ? void 0 : usage.input_tokens) !== null && _f !== void 0 ? _f : 0) + ((_g = usage === null || usage === void 0 ? void 0 : usage.output_tokens) !== null && _g !== void 0 ? _g : 0) || undefined,
3391
3430
  cacheReadInputTokens: (usage === null || usage === void 0 ? void 0 : usage.cache_read_input_tokens) || 0,
3392
3431
  };
3393
3432
  }
@@ -3407,7 +3446,7 @@ class ProxyServer {
3407
3446
  return {
3408
3447
  inputTokens: (usage === null || usage === void 0 ? void 0 : usage.input_tokens) || 0,
3409
3448
  outputTokens: (usage === null || usage === void 0 ? void 0 : usage.output_tokens) || 0,
3410
- totalTokens: ((_e = usage === null || usage === void 0 ? void 0 : usage.input_tokens) !== null && _e !== void 0 ? _e : 0) + ((_f = usage === null || usage === void 0 ? void 0 : usage.output_tokens) !== null && _f !== void 0 ? _f : 0) || undefined,
3449
+ totalTokens: ((_h = usage === null || usage === void 0 ? void 0 : usage.input_tokens) !== null && _h !== void 0 ? _h : 0) + ((_j = usage === null || usage === void 0 ? void 0 : usage.output_tokens) !== null && _j !== void 0 ? _j : 0) || undefined,
3411
3450
  cacheReadInputTokens: (usage === null || usage === void 0 ? void 0 : usage.cache_read_input_tokens) || 0,
3412
3451
  };
3413
3452
  }
@@ -3415,7 +3454,7 @@ class ProxyServer {
3415
3454
  }
3416
3455
  proxyRequest(req, res, route, rule, service, options) {
3417
3456
  return __awaiter(this, void 0, void 0, function* () {
3418
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
3457
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
3419
3458
  res.locals.skipLog = true;
3420
3459
  const startTime = Date.now();
3421
3460
  const rawSourceType = service.sourceType || 'openai-chat';
@@ -3423,8 +3462,42 @@ class ProxyServer {
3423
3462
  const sourceType = (0, type_migration_1.normalizeSourceType)(rawSourceType);
3424
3463
  const targetType = this.inferToolFromRequest(req);
3425
3464
  const sessionId = this.defaultExtractSessionId(req, targetType) || '-';
3465
+ // Agent Map:在途请求注册(active 状态判定依据)
3466
+ const _accessKeyCtxAtStart = req._accessKeyCtx;
3467
+ // 后台类请求(count_tokens / compact / background 规则)不重置「本轮通知标记」,避免主任务结束后的
3468
+ // 续发请求重复触发结束通知
3469
+ const _isBgRequest = rule.contentType === 'background'
3470
+ || rule.contentType === 'compact'
3471
+ || this.isCountTokensPath(req.path)
3472
+ || this.isCountTokensPath(req.originalUrl);
3473
+ // 思考类请求:复用路由同款识别(rule.contentType==='thinking' 或请求体带 thinking/reasoning 信号)。
3474
+ // 思考期间用更宽的静默上限,避免思考静默被误判为「上游停滞」而弹通知。
3475
+ const _isThinking = rule.contentType === 'thinking' || this.hasThinkingSignal(req.body);
3476
+ res.locals._agentMapThinking = _isThinking;
3477
+ agent_map_1.agentMapService.startRequest(sessionId, targetType, {
3478
+ source: _accessKeyCtxAtStart ? 'access-key' : 'global',
3479
+ keyId: (_a = _accessKeyCtxAtStart === null || _accessKeyCtxAtStart === void 0 ? void 0 : _accessKeyCtxAtStart.accessKey) === null || _a === void 0 ? void 0 : _a.id,
3480
+ keyName: (_b = _accessKeyCtxAtStart === null || _accessKeyCtxAtStart === void 0 ? void 0 : _accessKeyCtxAtStart.accessKey) === null || _b === void 0 ? void 0 : _b.name,
3481
+ background: _isBgRequest,
3482
+ thinking: _isThinking,
3483
+ });
3484
+ // 安全网:无论走哪条分支(含编程套餐拒绝、配额拦截、异常等早退,导致 finalizeLog 未被调用),
3485
+ // 都在响应关闭时确保 endRequest 配对执行一次,杜绝在途计数泄漏(「永远卡在进行中」)。
3486
+ res.on('close', () => {
3487
+ // 仅保证 endRequest 配对执行一次(防在途计数泄漏)。用独立标记 _agentMapEnded,
3488
+ // 不再与 finalizeLog 的 onFinalized 共用标记 —— 否则 res 'close' 先于 finalizeLog 触发时
3489
+ // 会把标记置位,导致 finalizeLog 跳过 onFinalized(requestCount / model / token 永不更新)。
3490
+ if (res.locals._agentMapEnded)
3491
+ return;
3492
+ res.locals._agentMapEnded = true;
3493
+ agent_map_1.agentMapService.endRequest(sessionId, {
3494
+ isStream: !!res.locals._agentMapStream,
3495
+ thinking: !!res.locals._agentMapThinking,
3496
+ });
3497
+ agent_map_1.agentMapService.reevaluate(sessionId);
3498
+ });
3426
3499
  const vendor = this.dbManager.getVendorByServiceId(service.id);
3427
- 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) || '-'}`);
3500
+ 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) || '-'}`);
3428
3501
  const failoverEnabled = (options === null || options === void 0 ? void 0 : options.failoverEnabled) === true;
3429
3502
  const forwardedToServiceName = options === null || options === void 0 ? void 0 : options.forwardedToServiceName;
3430
3503
  const useOriginalConfig = (options === null || options === void 0 ? void 0 : options.useOriginalConfig) === true;
@@ -3593,15 +3666,52 @@ class ProxyServer {
3593
3666
  // 标记规则正在使用
3594
3667
  rules_status_service_1.rulesStatusBroadcaster.markRuleInUse(route.id, rule.id);
3595
3668
  const finalizeLog = (statusCode, error) => __awaiter(this, void 0, void 0, function* () {
3596
- var _a, _b, _c, _d, _e, _f, _g, _h;
3669
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
3597
3670
  if (logged)
3598
3671
  return;
3599
3672
  // 服务性能数据点采集(全局,与 AUTH 无关;独立于 enableLogging 开关)
3600
3673
  this.emitPerformance({
3601
3674
  statusCode, startTime, usage: usageForLog, streamTiming,
3602
3675
  service, vendorId: vendor === null || vendor === void 0 ? void 0 : vendor.id, vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
3603
- model: rule.targetModel || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
3676
+ model: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
3604
3677
  });
3678
+ // Agent Map:在途请求注销 + 活动/状态采集广播(独立于 enableLogging)
3679
+ // 注意:endRequest 与 onFinalized 用各自独立的标记,二者解耦。
3680
+ // 这样即便 res 'close' 安全网已先执行 endRequest(_agentMapEnded=true),onFinalized 仍会在此执行 ——
3681
+ // 修复「requestCount / model / token 不更新」:旧实现二者共用 _agentMapRecorded,'close' 抢先置位会让此处整块跳过。
3682
+ if (!res.locals._agentMapEnded) {
3683
+ res.locals._agentMapEnded = true;
3684
+ agent_map_1.agentMapService.endRequest(sessionId, {
3685
+ isStream: !!res.locals._agentMapStream,
3686
+ thinking: !!res.locals._agentMapThinking,
3687
+ });
3688
+ }
3689
+ if (!res.locals._agentMapFinalized) {
3690
+ res.locals._agentMapFinalized = true;
3691
+ const _akCtx = req._accessKeyCtx;
3692
+ const _tokensDelta = (usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.totalTokens) ||
3693
+ (((usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.inputTokens) || 0) + ((usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.outputTokens) || 0));
3694
+ agent_map_1.agentMapService.onFinalized({
3695
+ sessionId,
3696
+ agent: targetType,
3697
+ source: _akCtx ? 'access-key' : 'global',
3698
+ keyId: (_b = _akCtx === null || _akCtx === void 0 ? void 0 : _akCtx.accessKey) === null || _b === void 0 ? void 0 : _b.id,
3699
+ keyName: (_c = _akCtx === null || _akCtx === void 0 ? void 0 : _akCtx.accessKey) === null || _c === void 0 ? void 0 : _c.name,
3700
+ title: this.defaultExtractSessionTitle(req, sessionId),
3701
+ timestamp: Date.now(),
3702
+ statusCode,
3703
+ // 采用真正转发给上游的模型(转换+覆盖后的 requestBody.model),而非 rule.targetModel 预测值,
3704
+ // 避免规则未配置 targetModel 时回退成编程工具提交的 req.body.model
3705
+ model: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_d = req.body) === null || _d === void 0 ? void 0 : _d.model),
3706
+ tokensDelta: _tokensDelta,
3707
+ // 输入/输出拆分(3D 连线两段用);usageForLog 已规范化为驼峰字段
3708
+ inputTokensDelta: (usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.inputTokens) || 0,
3709
+ outputTokensDelta: (usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.outputTokens) || 0,
3710
+ body: req.body,
3711
+ downstreamResponseBody: downstreamResponseBodyForLog !== null && downstreamResponseBodyForLog !== void 0 ? downstreamResponseBodyForLog : responseBodyForLog,
3712
+ responseBody: responseBodyForLog,
3713
+ });
3714
+ }
3605
3715
  const isError = statusCode >= 400;
3606
3716
  if (isError) {
3607
3717
  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}` : ''}`);
@@ -3610,7 +3720,7 @@ class ProxyServer {
3610
3720
  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`);
3611
3721
  }
3612
3722
  // 检查是否启用日志记录(默认启用)
3613
- const enableLogging = ((_b = this.config) === null || _b === void 0 ? void 0 : _b.enableLogging) !== false; // 默认为 true
3723
+ const enableLogging = ((_e = this.config) === null || _e === void 0 ? void 0 : _e.enableLogging) !== false; // 默认为 true
3614
3724
  if (!enableLogging) {
3615
3725
  return;
3616
3726
  }
@@ -3634,13 +3744,14 @@ class ProxyServer {
3634
3744
  error,
3635
3745
  contentType: rule.contentType,
3636
3746
  ruleId: rule.id,
3747
+ routeId: route === null || route === void 0 ? void 0 : route.id,
3637
3748
  targetType,
3638
3749
  targetServiceId: service.id,
3639
3750
  targetServiceName: service.name,
3640
- targetModel: rule.targetModel || ((_c = req.body) === null || _c === void 0 ? void 0 : _c.model),
3751
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_f = req.body) === null || _f === void 0 ? void 0 : _f.model),
3641
3752
  vendorId: service.vendorId,
3642
3753
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
3643
- requestModel: (_d = req.body) === null || _d === void 0 ? void 0 : _d.model,
3754
+ requestModel: (_g = req.body) === null || _g === void 0 ? void 0 : _g.model,
3644
3755
  tags: this.buildRelayTags(relayedForLog, useOriginalConfig),
3645
3756
  responseHeaders: responseHeadersForLog,
3646
3757
  responseBody: responseBodyForLog,
@@ -3674,7 +3785,7 @@ class ProxyServer {
3674
3785
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
3675
3786
  serviceId: service.id,
3676
3787
  serviceName: service.name,
3677
- model: rule.targetModel || ((_e = req.body) === null || _e === void 0 ? void 0 : _e.model),
3788
+ model: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_h = req.body) === null || _h === void 0 ? void 0 : _h.model),
3678
3789
  totalTokens: sessionTokens,
3679
3790
  }).catch(err => console.error('[KeySession] upsert error:', err));
3680
3791
  }
@@ -3698,13 +3809,14 @@ class ProxyServer {
3698
3809
  error,
3699
3810
  contentType: rule.contentType,
3700
3811
  ruleId: rule.id,
3812
+ routeId: route === null || route === void 0 ? void 0 : route.id,
3701
3813
  targetType,
3702
3814
  targetServiceId: service.id,
3703
3815
  targetServiceName: service.name,
3704
- targetModel: rule.targetModel || ((_f = req.body) === null || _f === void 0 ? void 0 : _f.model),
3816
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_j = req.body) === null || _j === void 0 ? void 0 : _j.model),
3705
3817
  vendorId: service.vendorId,
3706
3818
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
3707
- requestModel: (_g = req.body) === null || _g === void 0 ? void 0 : _g.model,
3819
+ requestModel: (_k = req.body) === null || _k === void 0 ? void 0 : _k.model,
3708
3820
  tags: this.buildRelayTags(relayedForLog, useOriginalConfig),
3709
3821
  });
3710
3822
  }
@@ -3717,7 +3829,7 @@ class ProxyServer {
3717
3829
  const vendors = this.dbManager.getVendors();
3718
3830
  const vendorForLog = vendors.find(v => v.id === service.vendorId);
3719
3831
  // 从请求体中提取模型信息
3720
- const requestModel = (_h = req.body) === null || _h === void 0 ? void 0 : _h.model;
3832
+ const requestModel = (_l = req.body) === null || _l === void 0 ? void 0 : _l.model;
3721
3833
  const tagsForLog = this.buildRelayTags(relayedForLog, useOriginalConfig);
3722
3834
  if (extraTagsForLog.length > 0) {
3723
3835
  tagsForLog.push(...extraTagsForLog);
@@ -3736,10 +3848,11 @@ class ProxyServer {
3736
3848
  // 新增字段
3737
3849
  contentType: rule.contentType,
3738
3850
  ruleId: rule.id,
3851
+ routeId: route === null || route === void 0 ? void 0 : route.id,
3739
3852
  targetType,
3740
3853
  targetServiceId: service.id,
3741
3854
  targetServiceName: service.name,
3742
- targetModel: rule.targetModel || requestModel,
3855
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_m = req.body) === null || _m === void 0 ? void 0 : _m.model),
3743
3856
  vendorId: service.vendorId,
3744
3857
  vendorName: vendorForLog === null || vendorForLog === void 0 ? void 0 : vendorForLog.name,
3745
3858
  requestModel,
@@ -3767,8 +3880,11 @@ class ProxyServer {
3767
3880
  vendorName: vendorForLog === null || vendorForLog === void 0 ? void 0 : vendorForLog.name,
3768
3881
  serviceId: service.id,
3769
3882
  serviceName: service.name,
3770
- model: requestModel || rule.targetModel,
3883
+ model: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_o = req.body) === null || _o === void 0 ? void 0 : _o.model),
3771
3884
  totalTokens,
3885
+ // 输入/输出拆分持久化(与 totalTokens 同口径),供 Agent Map 重启后恢复
3886
+ inputTokens: (usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.inputTokens) || 0,
3887
+ outputTokens: (usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.outputTokens) || 0,
3772
3888
  highIqMode: rule.contentType === 'high-iq' ? true : existingSession === null || existingSession === void 0 ? void 0 : existingSession.highIqMode,
3773
3889
  highIqRuleId: rule.contentType === 'high-iq' ? rule.id : existingSession === null || existingSession === void 0 ? void 0 : existingSession.highIqRuleId,
3774
3890
  highIqEnabledAt: rule.contentType === 'high-iq'
@@ -3868,7 +3984,7 @@ class ProxyServer {
3868
3984
  targetType,
3869
3985
  targetServiceId: service.id,
3870
3986
  targetServiceName: service.name,
3871
- targetModel: rule.targetModel || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
3987
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
3872
3988
  vendorId: service.vendorId,
3873
3989
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
3874
3990
  requestModel: (_b = req.body) === null || _b === void 0 ? void 0 : _b.model,
@@ -3934,7 +4050,7 @@ class ProxyServer {
3934
4050
  // responses→responses 直连非 OpenAI 官方端点时,需降级兼容(剥离 custom/namespace 等私有工具与非标准字段)
3935
4051
  const sanitizeBody = clientFormat === 'responses' && (0, source_type_mapping_1.sourceTypeToFormat)(sourceType) === 'responses' && !(0, index_1.isOfficialOpenAiApi)(effectiveApiUrl || '');
3936
4052
  const transformedRequestBody = this.transformRequestToUpstream(targetType, sourceType, payloadForTransform, rule.targetModel, providerConfig, serverToolConfig, sanitizeBody);
3937
- requestBody = (_b = transformedRequestBody !== null && transformedRequestBody !== void 0 ? transformedRequestBody : this.cloneRequestBody(originalToolRequestBody)) !== null && _b !== void 0 ? _b : {};
4053
+ requestBody = (_d = transformedRequestBody !== null && transformedRequestBody !== void 0 ? transformedRequestBody : this.cloneRequestBody(originalToolRequestBody)) !== null && _d !== void 0 ? _d : {};
3938
4054
  // 对最终即将发送到上游的 Claude compact 请求再做一次兜底清理,
3939
4055
  // 避免中间转换/覆盖步骤重新引入未配对的 tool_use。
3940
4056
  if (rule.contentType === 'compact' && targetType === 'claude-code' && Array.isArray(requestBody === null || requestBody === void 0 ? void 0 : requestBody.messages)) {
@@ -4040,7 +4156,7 @@ class ProxyServer {
4040
4156
  let errorResponseData = response.data;
4041
4157
  if (streamRequested && response.data && typeof response.data.on === 'function') {
4042
4158
  const raw = yield this.readStreamBody(response.data);
4043
- errorResponseData = (_c = this.safeJsonParse(raw)) !== null && _c !== void 0 ? _c : raw;
4159
+ errorResponseData = (_e = this.safeJsonParse(raw)) !== null && _e !== void 0 ? _e : raw;
4044
4160
  }
4045
4161
  responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
4046
4162
  yield handleUpstreamHttpError(response.status, errorResponseData, responseHeaders, contentType);
@@ -4078,10 +4194,10 @@ class ProxyServer {
4078
4194
  targetType,
4079
4195
  targetServiceId: service.id,
4080
4196
  targetServiceName: service.name,
4081
- targetModel: rule.targetModel || ((_d = req.body) === null || _d === void 0 ? void 0 : _d.model),
4197
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_f = req.body) === null || _f === void 0 ? void 0 : _f.model),
4082
4198
  vendorId: service.vendorId,
4083
4199
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4084
- requestModel: (_e = req.body) === null || _e === void 0 ? void 0 : _e.model,
4200
+ requestModel: (_g = req.body) === null || _g === void 0 ? void 0 : _g.model,
4085
4201
  responseTime: Date.now() - startTime,
4086
4202
  });
4087
4203
  }
@@ -4112,18 +4228,28 @@ class ProxyServer {
4112
4228
  const serializer = new streaming_1.SSESerializerTransform();
4113
4229
  const downstreamChunkCollector = new chunk_collector_1.ChunkCollectorTransform(() => {
4114
4230
  rules_status_service_1.rulesStatusBroadcaster.refreshRuleInUse(route.id, rule.id);
4231
+ // Agent Map 心跳:每个流经的下游 chunk 都视为「会话仍活跃」,刷新活动时钟(节流由 ChunkCollector 内部 5s 上限控制)
4232
+ // 同时把当前流式请求的实时累计 usage(input/output)一并推送,使前端节点在流式过程中随 token 增长实时上移
4233
+ const u = eventCollector.extractUsage();
4234
+ agent_map_1.agentMapService.heartbeat(sessionId, u ? {
4235
+ inputTokens: u.input_tokens || 0,
4236
+ outputTokens: u.output_tokens || 0,
4237
+ } : undefined);
4115
4238
  });
4116
4239
  // 服务性能打点:记录首/末 SSE 事件时间,用于 TTFT 与生成阶段吞吐
4117
4240
  streamTiming = new stream_timing_transform_1.StreamTimingTransform(startTime);
4241
+ // Agent Map:SSE 流已建立 —— 记一个在途流,状态机据此区分「同步/流式」活跃判定
4242
+ res.locals._agentMapStream = true;
4243
+ agent_map_1.agentMapService.markStreaming(sessionId);
4118
4244
  const compactResponseSanitizer = rule.contentType === 'compact' && targetType === 'claude-code'
4119
4245
  ? new ClaudeCompactResponseSanitizer()
4120
4246
  : null;
4121
4247
  // 流式 model 回写:将上游返回的 model 改写为客户端请求时的原始模型名
4122
- const originalModel = (_f = req.body) === null || _f === void 0 ? void 0 : _f.model;
4248
+ const originalModel = (_h = req.body) === null || _h === void 0 ? void 0 : _h.model;
4123
4249
  const modelRewriter = originalModel ? new model_rewrite_transform_1.ModelRewriteTransform(originalModel) : null;
4124
4250
  responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
4125
4251
  // 使用 transformSSEToTool 方法选择转换器
4126
- const { converter, extractUsage } = this.transformSSEToTool(targetType, sourceType);
4252
+ const { converter } = this.transformSSEToTool(targetType, sourceType);
4127
4253
  this.copyResponseHeaders(responseHeaders, res);
4128
4254
  // 收集日志:responseBody/streamChunks 记录上游原始响应;downstreamResponseBody 记录实际下发内容
4129
4255
  const finalizeChunks = () => {
@@ -4140,13 +4266,7 @@ class ProxyServer {
4140
4266
  extractedUsage = converterUsage || extractedUsage;
4141
4267
  }
4142
4268
  }
4143
- // 如果有自定义的 extractUsage 函数,使用它
4144
- if (extractUsage && extractedUsage) {
4145
- usageForLog = extractUsage(extractedUsage);
4146
- }
4147
- else if (extractedUsage) {
4148
- usageForLog = this.extractTokenUsageFromResponse(extractedUsage, sourceType);
4149
- }
4269
+ usageForLog = this.tokenUsageFromCollected(extractedUsage);
4150
4270
  };
4151
4271
  // 监听 res 的错误事件
4152
4272
  res.on('error', (err) => {
@@ -4219,10 +4339,10 @@ class ProxyServer {
4219
4339
  targetType,
4220
4340
  targetServiceId: service.id,
4221
4341
  targetServiceName: service.name,
4222
- targetModel: rule.targetModel || ((_g = req.body) === null || _g === void 0 ? void 0 : _g.model),
4342
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_j = req.body) === null || _j === void 0 ? void 0 : _j.model),
4223
4343
  vendorId: service.vendorId,
4224
4344
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4225
- requestModel: (_h = req.body) === null || _h === void 0 ? void 0 : _h.model,
4345
+ requestModel: (_k = req.body) === null || _k === void 0 ? void 0 : _k.model,
4226
4346
  responseTime: Date.now() - startTime,
4227
4347
  });
4228
4348
  }
@@ -4266,10 +4386,10 @@ class ProxyServer {
4266
4386
  targetType,
4267
4387
  targetServiceId: service.id,
4268
4388
  targetServiceName: service.name,
4269
- targetModel: rule.targetModel || ((_j = req.body) === null || _j === void 0 ? void 0 : _j.model),
4389
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_l = req.body) === null || _l === void 0 ? void 0 : _l.model),
4270
4390
  vendorId: service.vendorId,
4271
4391
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4272
- requestModel: (_k = req.body) === null || _k === void 0 ? void 0 : _k.model,
4392
+ requestModel: (_m = req.body) === null || _m === void 0 ? void 0 : _m.model,
4273
4393
  responseTime: Date.now() - startTime,
4274
4394
  });
4275
4395
  }
@@ -4288,7 +4408,7 @@ class ProxyServer {
4288
4408
  let responseData = response.data;
4289
4409
  if (streamRequested && response.data && typeof response.data.on === 'function' && !isEventStream) {
4290
4410
  const raw = yield this.readStreamBody(response.data);
4291
- responseData = (_l = this.safeJsonParse(raw)) !== null && _l !== void 0 ? _l : raw;
4411
+ responseData = (_o = this.safeJsonParse(raw)) !== null && _o !== void 0 ? _o : raw;
4292
4412
  }
4293
4413
  // 收集响应头
4294
4414
  responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
@@ -4310,7 +4430,7 @@ class ProxyServer {
4310
4430
  usageForLog = this.extractTokenUsageFromResponse(responseData, sourceType);
4311
4431
  console.log('[Proxy] Non-stream response: extracted usageForLog:', usageForLog);
4312
4432
  // 回写 model 字段:将上游返回的 model 改写为客户端请求时的原始模型名
4313
- const originalModel = (_m = req.body) === null || _m === void 0 ? void 0 : _m.model;
4433
+ const originalModel = (_p = req.body) === null || _p === void 0 ? void 0 : _p.model;
4314
4434
  (0, model_rewrite_transform_1.rewriteResponseModel)(normalizedConverted, originalModel);
4315
4435
  (0, model_rewrite_transform_1.rewriteResponseModel)(responseData, originalModel);
4316
4436
  this.copyResponseHeaders(responseHeaders, res);
@@ -4375,10 +4495,10 @@ class ProxyServer {
4375
4495
  targetType,
4376
4496
  targetServiceId: service.id,
4377
4497
  targetServiceName: service.name,
4378
- targetModel: rule.targetModel || ((_o = req.body) === null || _o === void 0 ? void 0 : _o.model),
4498
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_q = req.body) === null || _q === void 0 ? void 0 : _q.model),
4379
4499
  vendorId: service.vendorId,
4380
4500
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4381
- requestModel: (_p = req.body) === null || _p === void 0 ? void 0 : _p.model,
4501
+ requestModel: (_r = req.body) === null || _r === void 0 ? void 0 : _r.model,
4382
4502
  upstreamRequest: upstreamRequestForLog,
4383
4503
  responseTime: Date.now() - startTime,
4384
4504
  });
@@ -4393,7 +4513,7 @@ class ProxyServer {
4393
4513
  console.error('Proxy error:', error);
4394
4514
  // 检测是否是 timeout 错误
4395
4515
  const isTimeout = error.code === 'ECONNABORTED' ||
4396
- ((_q = error.message) === null || _q === void 0 ? void 0 : _q.toLowerCase().includes('timeout')) ||
4516
+ ((_s = error.message) === null || _s === void 0 ? void 0 : _s.toLowerCase().includes('timeout')) ||
4397
4517
  (error.errno && error.errno === 'ETIMEDOUT');
4398
4518
  const statusCode = isTimeout ? 504 : this.getErrorStatusCode(error, 500);
4399
4519
  const baseErrorMessage = isTimeout
@@ -4419,10 +4539,10 @@ class ProxyServer {
4419
4539
  targetType,
4420
4540
  targetServiceId: service.id,
4421
4541
  targetServiceName: service.name,
4422
- targetModel: rule.targetModel || ((_r = req.body) === null || _r === void 0 ? void 0 : _r.model),
4542
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_t = req.body) === null || _t === void 0 ? void 0 : _t.model),
4423
4543
  vendorId: service.vendorId,
4424
4544
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4425
- requestModel: (_s = req.body) === null || _s === void 0 ? void 0 : _s.model,
4545
+ requestModel: (_u = req.body) === null || _u === void 0 ? void 0 : _u.model,
4426
4546
  upstreamRequest: upstreamRequestForLog,
4427
4547
  responseHeaders: responseHeadersForLog,
4428
4548
  responseTime: Date.now() - startTime,
@@ -4693,7 +4813,7 @@ class ProxyServer {
4693
4813
  this.emitPerformance({
4694
4814
  statusCode, startTime, usage: usageForLog, streamTiming,
4695
4815
  service, vendorId: vendor === null || vendor === void 0 ? void 0 : vendor.id, vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4696
- model: rule.targetModel || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
4816
+ model: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
4697
4817
  });
4698
4818
  // AccessKey 独立日志处理
4699
4819
  const accessKeyCtx = req._accessKeyCtx;
@@ -4711,9 +4831,10 @@ class ProxyServer {
4711
4831
  error,
4712
4832
  contentType: rule.contentType,
4713
4833
  ruleId: rule.id,
4834
+ routeId: route === null || route === void 0 ? void 0 : route.id,
4714
4835
  targetServiceId: service.id,
4715
4836
  targetServiceName: service.name,
4716
- targetModel: rule.targetModel || ((_b = req.body) === null || _b === void 0 ? void 0 : _b.model),
4837
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_b = req.body) === null || _b === void 0 ? void 0 : _b.model),
4717
4838
  vendorId: service.vendorId,
4718
4839
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4719
4840
  requestModel: (_c = req.body) === null || _c === void 0 ? void 0 : _c.model,
@@ -4742,7 +4863,7 @@ class ProxyServer {
4742
4863
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4743
4864
  serviceId: service.id,
4744
4865
  serviceName: service.name,
4745
- model: rule.targetModel || ((_d = req.body) === null || _d === void 0 ? void 0 : _d.model),
4866
+ model: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_d = req.body) === null || _d === void 0 ? void 0 : _d.model),
4746
4867
  totalTokens: sessionTokens,
4747
4868
  }).catch(err => console.error('[KeySession] upsert error:', err));
4748
4869
  }
@@ -4764,9 +4885,10 @@ class ProxyServer {
4764
4885
  error,
4765
4886
  contentType: rule.contentType,
4766
4887
  ruleId: rule.id,
4888
+ routeId: route === null || route === void 0 ? void 0 : route.id,
4767
4889
  targetServiceId: service.id,
4768
4890
  targetServiceName: service.name,
4769
- targetModel: rule.targetModel || ((_e = req.body) === null || _e === void 0 ? void 0 : _e.model),
4891
+ targetModel: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_e = req.body) === null || _e === void 0 ? void 0 : _e.model),
4770
4892
  vendorId: service.vendorId,
4771
4893
  vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
4772
4894
  requestModel: (_f = req.body) === null || _f === void 0 ? void 0 : _f.model,
@@ -4945,7 +5067,7 @@ class ProxyServer {
4945
5067
  // 流式 model 回写:将上游返回的 model 改写为客户端请求时的原始模型名
4946
5068
  const originalModel = (_d = req.body) === null || _d === void 0 ? void 0 : _d.model;
4947
5069
  const modelRewriter = originalModel ? new model_rewrite_transform_1.ModelRewriteTransform(originalModel) : null;
4948
- const { converter, extractUsage } = this.transformSSEByFormat(clientFormat, sourceType);
5070
+ const { converter } = this.transformSSEByFormat(clientFormat, sourceType);
4949
5071
  this.copyResponseHeaders(responseHeaders, res);
4950
5072
  res.status(response.status);
4951
5073
  const finalizeStreamChunks = () => {
@@ -4958,12 +5080,7 @@ class ProxyServer {
4958
5080
  if (converterUsage)
4959
5081
  extractedUsage = converterUsage;
4960
5082
  }
4961
- if (extractUsage && extractedUsage) {
4962
- usageForLog = extractUsage(extractedUsage);
4963
- }
4964
- else if (extractedUsage) {
4965
- usageForLog = this.extractTokenUsageFromResponse(extractedUsage, sourceType);
4966
- }
5083
+ usageForLog = this.tokenUsageFromCollected(extractedUsage);
4967
5084
  };
4968
5085
  try {
4969
5086
  yield new Promise((resolve, reject) => {