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.
- package/README.md +6 -2
- package/UPGRADE.md +5 -4
- package/bin/utils/get-server.js +3 -3
- package/dist/server/access-keys/index.js +2 -2
- package/dist/server/access-keys/key-logger.js +63 -309
- package/dist/server/agent-map/activity-extractor.js +383 -0
- package/dist/server/agent-map/agent-map-service.js +707 -0
- package/dist/server/agent-map/index.js +10 -0
- package/dist/server/agent-map/routes.js +88 -0
- package/dist/server/agent-map/session-meta.js +300 -0
- package/dist/server/fs-database.js +166 -929
- package/dist/server/log-store/index.js +21 -0
- package/dist/server/log-store/log-store.js +1367 -0
- package/dist/server/log-store/types.js +2 -0
- package/dist/server/main.js +213 -18
- package/dist/server/notifier.js +100 -0
- package/dist/server/performance-tracker.js +377 -0
- package/dist/server/proxy-server.js +142 -39
- package/dist/server/rules-status-service.js +21 -6
- package/dist/server/transformers/stream-timing-transform.js +63 -0
- package/dist/ui/assets/index-4liE4bV8.css +1 -0
- package/dist/ui/assets/index-CJMAVkIN.js +813 -0
- package/dist/ui/index.html +2 -2
- package/package.json +2 -2
- package/scripts/dev.js +237 -0
- package/dist/ui/assets/index-DR6cZIa7.js +0 -799
- package/dist/ui/assets/index-MjMlew6J.css +0 -1
|
@@ -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 || ((
|
|
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 = ((
|
|
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:
|
|
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: (
|
|
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:
|
|
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:
|
|
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: (
|
|
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 = (
|
|
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:
|
|
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:
|
|
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:
|
|
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 = (
|
|
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 = (
|
|
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:
|
|
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: (
|
|
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 = (
|
|
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:
|
|
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: (
|
|
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:
|
|
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: (
|
|
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 = (
|
|
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 = (
|
|
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:
|
|
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: (
|
|
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
|
-
((
|
|
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:
|
|
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: (
|
|
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:
|
|
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: (
|
|
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:
|
|
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:
|
|
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: (
|
|
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:
|
|
56
|
-
}); //
|
|
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
|
-
//
|
|
267
|
-
if ((currentStatus === null || currentStatus === void 0 ? void 0 : currentStatus.status)
|
|
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;
|