aicodeswitch 5.2.13 → 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.
- package/dist/server/agent-map/agent-map-service.js +355 -51
- package/dist/server/conversions/index.js +10 -2
- package/dist/server/conversions/thinking/providers.js +12 -7
- package/dist/server/fs-database.js +6 -0
- package/dist/server/main.js +7 -30
- package/dist/server/performance-tracker.js +78 -8
- package/dist/server/proxy-server.js +104 -26
- package/dist/server/transformers/chunk-collector.js +112 -25
- package/dist/ui/assets/{index-4liE4bV8.css → index-CQVhBlBB.css} +1 -1
- package/dist/ui/assets/index-QZBX5HHX.js +1186 -0
- package/dist/ui/assets/three-CFpmPosW.js +3802 -0
- package/dist/ui/index.html +3 -2
- package/package.json +3 -1
- package/scripts/dev.js +76 -30
- package/dist/server/tools-service.js +0 -203
- package/dist/server/websocket-service.js +0 -148
- package/dist/ui/assets/index-CJMAVkIN.js +0 -813
|
@@ -2323,6 +2323,8 @@ class FileSystemDatabaseManager {
|
|
|
2323
2323
|
lastRequestAt: now,
|
|
2324
2324
|
requestCount: 1,
|
|
2325
2325
|
totalTokens: 0,
|
|
2326
|
+
inputTokens: 0,
|
|
2327
|
+
outputTokens: 0,
|
|
2326
2328
|
};
|
|
2327
2329
|
this.sessions.push(session);
|
|
2328
2330
|
yield this.saveSessions();
|
|
@@ -2478,6 +2480,8 @@ class FileSystemDatabaseManager {
|
|
|
2478
2480
|
existing.lastRequestAt = now;
|
|
2479
2481
|
existing.requestCount++;
|
|
2480
2482
|
existing.totalTokens += session.totalTokens || 0;
|
|
2483
|
+
existing.inputTokens = (existing.inputTokens || 0) + (session.inputTokens || 0);
|
|
2484
|
+
existing.outputTokens = (existing.outputTokens || 0) + (session.outputTokens || 0);
|
|
2481
2485
|
if (session.vendorId !== undefined)
|
|
2482
2486
|
existing.vendorId = session.vendorId;
|
|
2483
2487
|
if (session.vendorName !== undefined)
|
|
@@ -2510,6 +2514,8 @@ class FileSystemDatabaseManager {
|
|
|
2510
2514
|
lastRequestAt: now,
|
|
2511
2515
|
requestCount: 1,
|
|
2512
2516
|
totalTokens: session.totalTokens || 0,
|
|
2517
|
+
inputTokens: session.inputTokens || 0,
|
|
2518
|
+
outputTokens: session.outputTokens || 0,
|
|
2513
2519
|
vendorId: session.vendorId,
|
|
2514
2520
|
vendorName: session.vendorName,
|
|
2515
2521
|
serviceId: session.serviceId,
|
package/dist/server/main.js
CHANGED
|
@@ -32,8 +32,6 @@ const os_1 = __importDefault(require("os"));
|
|
|
32
32
|
const auth_1 = require("./auth");
|
|
33
33
|
const version_check_1 = require("./version-check");
|
|
34
34
|
const utils_1 = require("./utils");
|
|
35
|
-
const tools_service_1 = require("./tools-service");
|
|
36
|
-
const websocket_service_1 = require("./websocket-service");
|
|
37
35
|
const rules_status_service_1 = require("./rules-status-service");
|
|
38
36
|
const type_migration_1 = require("./type-migration");
|
|
39
37
|
const config_metadata_1 = require("./config-metadata");
|
|
@@ -2514,19 +2512,6 @@ ${instruction}
|
|
|
2514
2512
|
res.json({ success: false });
|
|
2515
2513
|
}
|
|
2516
2514
|
})));
|
|
2517
|
-
// 工具安装检测相关路由
|
|
2518
|
-
app.get('/api/tools/status', asyncHandler((_req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
2519
|
-
console.log('[API] GET /api/tools/status - 获取工具安装状态');
|
|
2520
|
-
try {
|
|
2521
|
-
const status = yield (0, tools_service_1.getToolsInstallationStatus)();
|
|
2522
|
-
console.log('[API] 工具安装状态:', status);
|
|
2523
|
-
res.json(status);
|
|
2524
|
-
}
|
|
2525
|
-
catch (error) {
|
|
2526
|
-
console.error('[API] 获取工具状态失败:', error);
|
|
2527
|
-
res.status(500).json({ error: '获取工具状态失败' });
|
|
2528
|
-
}
|
|
2529
|
-
})));
|
|
2530
2515
|
// MCP 工具管理相关路由
|
|
2531
2516
|
app.get('/api/mcps', (_req, res) => {
|
|
2532
2517
|
res.json(dbManager.getMCPs());
|
|
@@ -3563,26 +3548,12 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
3563
3548
|
}
|
|
3564
3549
|
setImmediate(() => process.exit(1));
|
|
3565
3550
|
});
|
|
3566
|
-
// 创建 WebSocket 服务器用于工具安装
|
|
3567
|
-
const toolInstallWss = (0, websocket_service_1.createToolInstallationWSServer)();
|
|
3568
3551
|
// 设置黑名单检查函数,用于在规则状态同步时检查黑名单是否已过期
|
|
3569
3552
|
rules_status_service_1.rulesStatusBroadcaster.setBlacklistChecker((serviceId, routeId, contentType) => __awaiter(void 0, void 0, void 0, function* () {
|
|
3570
3553
|
// 检查服务��否在黑名单中
|
|
3571
3554
|
const isBlacklisted = yield dbManager.isServiceBlacklisted(serviceId, routeId, contentType);
|
|
3572
3555
|
return isBlacklisted;
|
|
3573
3556
|
}));
|
|
3574
|
-
// 将 WebSocket 服务器附加到 HTTP 服务器(仅用于工具安装)
|
|
3575
|
-
server.on('upgrade', (request, socket, head) => {
|
|
3576
|
-
if (request.url === '/api/tools/install') {
|
|
3577
|
-
toolInstallWss.handleUpgrade(request, socket, head, (ws) => {
|
|
3578
|
-
toolInstallWss.emit('connection', ws, request);
|
|
3579
|
-
});
|
|
3580
|
-
}
|
|
3581
|
-
else {
|
|
3582
|
-
socket.destroy();
|
|
3583
|
-
}
|
|
3584
|
-
});
|
|
3585
|
-
console.log(`WebSocket server for tool installation attached to ws://${clientHost}:${port}/api/tools/install`);
|
|
3586
3557
|
let isShuttingDown = false;
|
|
3587
3558
|
let shutdownPromise = null;
|
|
3588
3559
|
const shutdown = (signal) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -3594,10 +3565,16 @@ const start = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
3594
3565
|
console.log(`[Server] Received ${signal}, shutting down...`);
|
|
3595
3566
|
// 立即停止监听以释放端口(同步关闭监听句柄,端口马上可用),
|
|
3596
3567
|
// 避免在漫长的清理流程期间端口仍被占用,导致此时重启产生 EADDRINUSE 冲突。
|
|
3597
|
-
// 现有连接会继续处理直到完成或超时;其回调与下面的清理流程并行等待。
|
|
3598
3568
|
const serverClosedPromise = new Promise((resolve) => {
|
|
3599
3569
|
server.close(() => resolve());
|
|
3600
3570
|
});
|
|
3571
|
+
// 强制断开所有现存连接(含浏览器长连的 SSE:agent-map stream、rules-status
|
|
3572
|
+
// 广播等)。否则这些连接永不自行关闭,server.close 的回调要等满下面 5s 超时
|
|
3573
|
+
// 才触发,表现为 Ctrl+C 后「卡很久才打印 Server stopped.」。closeAllConnections
|
|
3574
|
+
// 立即销毁所有 socket,让 close 回调几乎瞬时完成。
|
|
3575
|
+
if (typeof server.closeAllConnections === 'function') {
|
|
3576
|
+
server.closeAllConnections();
|
|
3577
|
+
}
|
|
3601
3578
|
// 服务终止前恢复配置文件(适用于 aicos stop 与 Ctrl+C)
|
|
3602
3579
|
try {
|
|
3603
3580
|
const claudeRestored = yield restoreClaudeConfig();
|
|
@@ -68,8 +68,12 @@ class ServicePerformanceTracker {
|
|
|
68
68
|
try {
|
|
69
69
|
const raw = yield promises_1.default.readFile(this.filePath, 'utf-8');
|
|
70
70
|
const parsed = JSON.parse(raw);
|
|
71
|
-
if (parsed && parsed.vendors)
|
|
71
|
+
if (parsed && parsed.vendors) {
|
|
72
72
|
this.file = parsed;
|
|
73
|
+
// 旧数据文件可能缺新增的 token 字段(sumInputTokens/sumTotalTokens),
|
|
74
|
+
// 直接累加会得到 undefined+N=NaN,污染派生值。这里补齐为 0。
|
|
75
|
+
this.migrateBuckets(this.file);
|
|
76
|
+
}
|
|
73
77
|
}
|
|
74
78
|
catch (_a) {
|
|
75
79
|
// 首次启动或文件损坏,使用空桶
|
|
@@ -77,6 +81,42 @@ class ServicePerformanceTracker {
|
|
|
77
81
|
}
|
|
78
82
|
});
|
|
79
83
|
}
|
|
84
|
+
/** 加载后补齐旧数据缺失的桶字段,避免 undefined 累加成 NaN */
|
|
85
|
+
migrateBuckets(file) {
|
|
86
|
+
for (const v of Object.values(file.vendors)) {
|
|
87
|
+
if (v.vendorRollup)
|
|
88
|
+
this.normalizeAggregate(v.vendorRollup);
|
|
89
|
+
for (const s of Object.values(v.services || {})) {
|
|
90
|
+
if (s.serviceRollup)
|
|
91
|
+
this.normalizeAggregate(s.serviceRollup);
|
|
92
|
+
for (const agg of Object.values(s.models || {})) {
|
|
93
|
+
this.normalizeAggregate(agg);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
normalizeAggregate(agg) {
|
|
99
|
+
this.normalizeBucket(agg.precise);
|
|
100
|
+
this.normalizeBucket(agg.estimated);
|
|
101
|
+
for (const b of Object.values(agg.hourly || {}))
|
|
102
|
+
this.normalizeBucket(b);
|
|
103
|
+
}
|
|
104
|
+
normalizeBucket(b) {
|
|
105
|
+
if (!b)
|
|
106
|
+
return;
|
|
107
|
+
if (typeof b.count !== 'number')
|
|
108
|
+
b.count = 0;
|
|
109
|
+
if (typeof b.sumTtftMs !== 'number')
|
|
110
|
+
b.sumTtftMs = 0;
|
|
111
|
+
if (typeof b.sumTps !== 'number')
|
|
112
|
+
b.sumTps = 0;
|
|
113
|
+
if (typeof b.totalOutputTokens !== 'number')
|
|
114
|
+
b.totalOutputTokens = 0;
|
|
115
|
+
if (typeof b.sumInputTokens !== 'number')
|
|
116
|
+
b.sumInputTokens = 0;
|
|
117
|
+
if (typeof b.sumTotalTokens !== 'number')
|
|
118
|
+
b.sumTotalTokens = 0;
|
|
119
|
+
}
|
|
80
120
|
/**
|
|
81
121
|
* 记录一次请求的性能数据点(三级同步聚合)。
|
|
82
122
|
* 纯内存操作,不阻塞调用方。
|
|
@@ -257,7 +297,7 @@ class ServicePerformanceTracker {
|
|
|
257
297
|
};
|
|
258
298
|
}
|
|
259
299
|
emptyBucket() {
|
|
260
|
-
return { count: 0, sumTtftMs: 0, sumTps: 0, totalOutputTokens: 0 };
|
|
300
|
+
return { count: 0, sumTtftMs: 0, sumTps: 0, totalOutputTokens: 0, sumInputTokens: 0, sumTotalTokens: 0 };
|
|
261
301
|
}
|
|
262
302
|
accumulate(agg, m, hour, withExtremes) {
|
|
263
303
|
var _a;
|
|
@@ -268,6 +308,7 @@ class ServicePerformanceTracker {
|
|
|
268
308
|
const bucket = m.timingAccuracy === 'precise' ? agg.precise : agg.estimated;
|
|
269
309
|
const hasTtft = m.timingAccuracy === 'precise' && typeof m.ttftMs === 'number';
|
|
270
310
|
const hasTps = typeof m.tokensPerSecond === 'number';
|
|
311
|
+
// token 量与计时精度无关,precise/estimated 桶都累加
|
|
271
312
|
bucket.count += 1;
|
|
272
313
|
if (hasTtft)
|
|
273
314
|
bucket.sumTtftMs += m.ttftMs;
|
|
@@ -275,18 +316,37 @@ class ServicePerformanceTracker {
|
|
|
275
316
|
bucket.sumTps += m.tokensPerSecond;
|
|
276
317
|
if (m.outputTokens)
|
|
277
318
|
bucket.totalOutputTokens += m.outputTokens;
|
|
278
|
-
|
|
319
|
+
if (m.inputTokens)
|
|
320
|
+
bucket.sumInputTokens += m.inputTokens;
|
|
321
|
+
if (m.totalTokens)
|
|
322
|
+
bucket.sumTotalTokens += m.totalTokens;
|
|
323
|
+
// 小时走势桶:
|
|
324
|
+
// - count/sumTtftMs/sumTps 仅精确样本计入(避免估算样本污染 TTFT/TPM 均值)
|
|
325
|
+
// - token 三项与精度无关,所有样本都计入走势(含非流式)
|
|
326
|
+
const hb = (_a = agg.hourly[hour]) !== null && _a !== void 0 ? _a : (agg.hourly[hour] = this.emptyBucket());
|
|
327
|
+
let touchedHourly = false;
|
|
279
328
|
if (m.timingAccuracy === 'precise') {
|
|
280
|
-
const hb = (_a = agg.hourly[hour]) !== null && _a !== void 0 ? _a : (agg.hourly[hour] = this.emptyBucket());
|
|
281
329
|
hb.count += 1;
|
|
282
330
|
if (hasTtft)
|
|
283
331
|
hb.sumTtftMs += m.ttftMs;
|
|
284
332
|
if (hasTps)
|
|
285
333
|
hb.sumTps += m.tokensPerSecond;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
334
|
+
touchedHourly = true;
|
|
335
|
+
}
|
|
336
|
+
if (m.outputTokens) {
|
|
337
|
+
hb.totalOutputTokens += m.outputTokens;
|
|
338
|
+
touchedHourly = true;
|
|
339
|
+
}
|
|
340
|
+
if (m.inputTokens) {
|
|
341
|
+
hb.sumInputTokens += m.inputTokens;
|
|
342
|
+
touchedHourly = true;
|
|
289
343
|
}
|
|
344
|
+
if (m.totalTokens) {
|
|
345
|
+
hb.sumTotalTokens += m.totalTokens;
|
|
346
|
+
touchedHourly = true;
|
|
347
|
+
}
|
|
348
|
+
if (touchedHourly)
|
|
349
|
+
this.trimHourly(agg.hourly);
|
|
290
350
|
// 极值(仅模型级、仅精确样本)
|
|
291
351
|
if (withExtremes && m.timingAccuracy === 'precise') {
|
|
292
352
|
if (hasTtft) {
|
|
@@ -315,9 +375,14 @@ class ServicePerformanceTracker {
|
|
|
315
375
|
// ---------------- 内部:派生 ----------------
|
|
316
376
|
derive(agg) {
|
|
317
377
|
const p = agg.precise;
|
|
378
|
+
const e = agg.estimated;
|
|
318
379
|
const count = p.count;
|
|
319
380
|
const avgTtftMs = count > 0 ? p.sumTtftMs / count : 0;
|
|
320
381
|
const avgTps = count > 0 ? p.sumTps / count : 0;
|
|
382
|
+
// token 量跨 precise+estimated 求和(含非流式样本)
|
|
383
|
+
const totalOutputTokens = p.totalOutputTokens + e.totalOutputTokens;
|
|
384
|
+
const totalInputTokens = p.sumInputTokens + e.sumInputTokens;
|
|
385
|
+
const totalTokens = p.sumTotalTokens + e.sumTotalTokens;
|
|
321
386
|
return {
|
|
322
387
|
count,
|
|
323
388
|
avgTtftMs,
|
|
@@ -327,7 +392,9 @@ class ServicePerformanceTracker {
|
|
|
327
392
|
minTps: agg.minTps,
|
|
328
393
|
maxTps: agg.maxTps,
|
|
329
394
|
errorCount: agg.errorCount,
|
|
330
|
-
totalOutputTokens
|
|
395
|
+
totalOutputTokens,
|
|
396
|
+
totalInputTokens,
|
|
397
|
+
totalTokens,
|
|
331
398
|
successRate: count + agg.errorCount > 0 ? count / (count + agg.errorCount) : 0,
|
|
332
399
|
};
|
|
333
400
|
}
|
|
@@ -355,6 +422,9 @@ class ServicePerformanceTracker {
|
|
|
355
422
|
count: b.count,
|
|
356
423
|
avgTtftMs: b.count > 0 ? b.sumTtftMs / b.count : 0,
|
|
357
424
|
avgTpm: b.count > 0 ? (b.sumTps / b.count) * 60 : 0,
|
|
425
|
+
inputTokens: b.sumInputTokens,
|
|
426
|
+
outputTokens: b.totalOutputTokens,
|
|
427
|
+
totalTokens: b.sumTotalTokens,
|
|
358
428
|
}));
|
|
359
429
|
}
|
|
360
430
|
locateService(serviceId) {
|
|
@@ -67,6 +67,13 @@ const coding_plan_1 = require("./coding-plan");
|
|
|
67
67
|
const coding_plan_headers_1 = require("./coding-plan-headers");
|
|
68
68
|
const auth_1 = require("./auth");
|
|
69
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 = '原始配置 / 直连';
|
|
70
77
|
/** 默认模型列表 */
|
|
71
78
|
const DEFAULT_MODELS = [
|
|
72
79
|
{ id: 'claude-sonnet-4-20250514', owned_by: 'anthropic' },
|
|
@@ -275,12 +282,16 @@ class ProxyServer {
|
|
|
275
282
|
* 流式:依据 streamTiming 精确计算 TTFT 与生成阶段吞吐;非流式:端到端估算(estimated)。
|
|
276
283
|
*/
|
|
277
284
|
emitPerformance(params) {
|
|
285
|
+
var _a;
|
|
278
286
|
const tracker = this.performanceTracker;
|
|
279
287
|
if (!tracker)
|
|
280
288
|
return;
|
|
281
289
|
const { statusCode, startTime, usage, streamTiming, service, vendorId, vendorName, model } = params;
|
|
282
290
|
const isError = statusCode >= 400;
|
|
291
|
+
const inputTokens = usage === null || usage === void 0 ? void 0 : usage.inputTokens;
|
|
283
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);
|
|
284
295
|
const responseMs = Date.now() - startTime;
|
|
285
296
|
let ttftMs;
|
|
286
297
|
let tokensPerSecond;
|
|
@@ -296,7 +307,11 @@ class ProxyServer {
|
|
|
296
307
|
else if (outputTokens && responseMs > 0) {
|
|
297
308
|
tokensPerSecond = outputTokens / (responseMs / 1000);
|
|
298
309
|
}
|
|
299
|
-
|
|
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 });
|
|
300
315
|
}
|
|
301
316
|
/**
|
|
302
317
|
* 从请求中提取 API Key(支持三种 Header,按优先级依次尝试)
|
|
@@ -1260,6 +1275,7 @@ class ProxyServer {
|
|
|
1260
1275
|
const tempService = {
|
|
1261
1276
|
id: 'fallback-service',
|
|
1262
1277
|
name: 'Original Config',
|
|
1278
|
+
vendorId: FALLBACK_VENDOR_ID,
|
|
1263
1279
|
apiUrl: originalConfig.apiUrl,
|
|
1264
1280
|
apiKey: originalConfig.apiKey,
|
|
1265
1281
|
authType: originalConfig.authType,
|
|
@@ -3347,8 +3363,28 @@ class ProxyServer {
|
|
|
3347
3363
|
});
|
|
3348
3364
|
return { converter: adapter, extractUsage };
|
|
3349
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
|
+
}
|
|
3350
3386
|
extractTokenUsageFromResponse(responseData, sourceType) {
|
|
3351
|
-
var _a, _b, _c, _d, _e, _f;
|
|
3387
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
3352
3388
|
if (!responseData)
|
|
3353
3389
|
return undefined;
|
|
3354
3390
|
const format = (0, source_type_mapping_1.sourceTypeToFormat)(sourceType);
|
|
@@ -3364,14 +3400,15 @@ class ProxyServer {
|
|
|
3364
3400
|
};
|
|
3365
3401
|
}
|
|
3366
3402
|
if (format === 'completions' || format === 'responses') {
|
|
3367
|
-
|
|
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;
|
|
3368
3405
|
if (!usage)
|
|
3369
3406
|
return undefined;
|
|
3370
3407
|
return {
|
|
3371
3408
|
inputTokens: (usage === null || usage === void 0 ? void 0 : usage.input_tokens) || (usage === null || usage === void 0 ? void 0 : usage.prompt_tokens) || 0,
|
|
3372
3409
|
outputTokens: (usage === null || usage === void 0 ? void 0 : usage.output_tokens) || (usage === null || usage === void 0 ? void 0 : usage.completion_tokens) || 0,
|
|
3373
3410
|
totalTokens: (usage === null || usage === void 0 ? void 0 : usage.total_tokens) || 0,
|
|
3374
|
-
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,
|
|
3375
3412
|
};
|
|
3376
3413
|
}
|
|
3377
3414
|
if (format === 'claude') {
|
|
@@ -3379,7 +3416,7 @@ class ProxyServer {
|
|
|
3379
3416
|
return {
|
|
3380
3417
|
inputTokens: (responseData === null || responseData === void 0 ? void 0 : responseData.input_tokens) || 0,
|
|
3381
3418
|
outputTokens: (responseData === null || responseData === void 0 ? void 0 : responseData.output_tokens) || 0,
|
|
3382
|
-
totalTokens: ((
|
|
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,
|
|
3383
3420
|
cacheReadInputTokens: (responseData === null || responseData === void 0 ? void 0 : responseData.cache_read_input_tokens) || 0,
|
|
3384
3421
|
};
|
|
3385
3422
|
}
|
|
@@ -3389,7 +3426,7 @@ class ProxyServer {
|
|
|
3389
3426
|
return {
|
|
3390
3427
|
inputTokens: (usage === null || usage === void 0 ? void 0 : usage.input_tokens) || 0,
|
|
3391
3428
|
outputTokens: (usage === null || usage === void 0 ? void 0 : usage.output_tokens) || 0,
|
|
3392
|
-
totalTokens: ((
|
|
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,
|
|
3393
3430
|
cacheReadInputTokens: (usage === null || usage === void 0 ? void 0 : usage.cache_read_input_tokens) || 0,
|
|
3394
3431
|
};
|
|
3395
3432
|
}
|
|
@@ -3409,7 +3446,7 @@ class ProxyServer {
|
|
|
3409
3446
|
return {
|
|
3410
3447
|
inputTokens: (usage === null || usage === void 0 ? void 0 : usage.input_tokens) || 0,
|
|
3411
3448
|
outputTokens: (usage === null || usage === void 0 ? void 0 : usage.output_tokens) || 0,
|
|
3412
|
-
totalTokens: ((
|
|
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,
|
|
3413
3450
|
cacheReadInputTokens: (usage === null || usage === void 0 ? void 0 : usage.cache_read_input_tokens) || 0,
|
|
3414
3451
|
};
|
|
3415
3452
|
}
|
|
@@ -3427,10 +3464,37 @@ class ProxyServer {
|
|
|
3427
3464
|
const sessionId = this.defaultExtractSessionId(req, targetType) || '-';
|
|
3428
3465
|
// Agent Map:在途请求注册(active 状态判定依据)
|
|
3429
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;
|
|
3430
3477
|
agent_map_1.agentMapService.startRequest(sessionId, targetType, {
|
|
3431
3478
|
source: _accessKeyCtxAtStart ? 'access-key' : 'global',
|
|
3432
3479
|
keyId: (_a = _accessKeyCtxAtStart === null || _accessKeyCtxAtStart === void 0 ? void 0 : _accessKeyCtxAtStart.accessKey) === null || _a === void 0 ? void 0 : _a.id,
|
|
3433
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);
|
|
3434
3498
|
});
|
|
3435
3499
|
const vendor = this.dbManager.getVendorByServiceId(service.id);
|
|
3436
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) || '-'}`);
|
|
@@ -3611,10 +3675,19 @@ class ProxyServer {
|
|
|
3611
3675
|
service, vendorId: vendor === null || vendor === void 0 ? void 0 : vendor.id, vendorName: vendor === null || vendor === void 0 ? void 0 : vendor.name,
|
|
3612
3676
|
model: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_a = req.body) === null || _a === void 0 ? void 0 : _a.model),
|
|
3613
3677
|
});
|
|
3614
|
-
// Agent Map:在途请求注销 + 活动/状态采集广播(独立于 enableLogging
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
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;
|
|
3618
3691
|
const _akCtx = req._accessKeyCtx;
|
|
3619
3692
|
const _tokensDelta = (usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.totalTokens) ||
|
|
3620
3693
|
(((usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.inputTokens) || 0) + ((usageForLog === null || usageForLog === void 0 ? void 0 : usageForLog.outputTokens) || 0));
|
|
@@ -3631,6 +3704,9 @@ class ProxyServer {
|
|
|
3631
3704
|
// 避免规则未配置 targetModel 时回退成编程工具提交的 req.body.model
|
|
3632
3705
|
model: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_d = req.body) === null || _d === void 0 ? void 0 : _d.model),
|
|
3633
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,
|
|
3634
3710
|
body: req.body,
|
|
3635
3711
|
downstreamResponseBody: downstreamResponseBodyForLog !== null && downstreamResponseBodyForLog !== void 0 ? downstreamResponseBodyForLog : responseBodyForLog,
|
|
3636
3712
|
responseBody: responseBodyForLog,
|
|
@@ -3806,6 +3882,9 @@ class ProxyServer {
|
|
|
3806
3882
|
serviceName: service.name,
|
|
3807
3883
|
model: (requestBody === null || requestBody === void 0 ? void 0 : requestBody.model) || ((_o = req.body) === null || _o === void 0 ? void 0 : _o.model),
|
|
3808
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,
|
|
3809
3888
|
highIqMode: rule.contentType === 'high-iq' ? true : existingSession === null || existingSession === void 0 ? void 0 : existingSession.highIqMode,
|
|
3810
3889
|
highIqRuleId: rule.contentType === 'high-iq' ? rule.id : existingSession === null || existingSession === void 0 ? void 0 : existingSession.highIqRuleId,
|
|
3811
3890
|
highIqEnabledAt: rule.contentType === 'high-iq'
|
|
@@ -4149,9 +4228,19 @@ class ProxyServer {
|
|
|
4149
4228
|
const serializer = new streaming_1.SSESerializerTransform();
|
|
4150
4229
|
const downstreamChunkCollector = new chunk_collector_1.ChunkCollectorTransform(() => {
|
|
4151
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);
|
|
4152
4238
|
});
|
|
4153
4239
|
// 服务性能打点:记录首/末 SSE 事件时间,用于 TTFT 与生成阶段吞吐
|
|
4154
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);
|
|
4155
4244
|
const compactResponseSanitizer = rule.contentType === 'compact' && targetType === 'claude-code'
|
|
4156
4245
|
? new ClaudeCompactResponseSanitizer()
|
|
4157
4246
|
: null;
|
|
@@ -4160,7 +4249,7 @@ class ProxyServer {
|
|
|
4160
4249
|
const modelRewriter = originalModel ? new model_rewrite_transform_1.ModelRewriteTransform(originalModel) : null;
|
|
4161
4250
|
responseHeadersForLog = this.normalizeResponseHeaders(responseHeaders);
|
|
4162
4251
|
// 使用 transformSSEToTool 方法选择转换器
|
|
4163
|
-
const { converter
|
|
4252
|
+
const { converter } = this.transformSSEToTool(targetType, sourceType);
|
|
4164
4253
|
this.copyResponseHeaders(responseHeaders, res);
|
|
4165
4254
|
// 收集日志:responseBody/streamChunks 记录上游原始响应;downstreamResponseBody 记录实际下发内容
|
|
4166
4255
|
const finalizeChunks = () => {
|
|
@@ -4177,13 +4266,7 @@ class ProxyServer {
|
|
|
4177
4266
|
extractedUsage = converterUsage || extractedUsage;
|
|
4178
4267
|
}
|
|
4179
4268
|
}
|
|
4180
|
-
|
|
4181
|
-
if (extractUsage && extractedUsage) {
|
|
4182
|
-
usageForLog = extractUsage(extractedUsage);
|
|
4183
|
-
}
|
|
4184
|
-
else if (extractedUsage) {
|
|
4185
|
-
usageForLog = this.extractTokenUsageFromResponse(extractedUsage, sourceType);
|
|
4186
|
-
}
|
|
4269
|
+
usageForLog = this.tokenUsageFromCollected(extractedUsage);
|
|
4187
4270
|
};
|
|
4188
4271
|
// 监听 res 的错误事件
|
|
4189
4272
|
res.on('error', (err) => {
|
|
@@ -4984,7 +5067,7 @@ class ProxyServer {
|
|
|
4984
5067
|
// 流式 model 回写:将上游返回的 model 改写为客户端请求时的原始模型名
|
|
4985
5068
|
const originalModel = (_d = req.body) === null || _d === void 0 ? void 0 : _d.model;
|
|
4986
5069
|
const modelRewriter = originalModel ? new model_rewrite_transform_1.ModelRewriteTransform(originalModel) : null;
|
|
4987
|
-
const { converter
|
|
5070
|
+
const { converter } = this.transformSSEByFormat(clientFormat, sourceType);
|
|
4988
5071
|
this.copyResponseHeaders(responseHeaders, res);
|
|
4989
5072
|
res.status(response.status);
|
|
4990
5073
|
const finalizeStreamChunks = () => {
|
|
@@ -4997,12 +5080,7 @@ class ProxyServer {
|
|
|
4997
5080
|
if (converterUsage)
|
|
4998
5081
|
extractedUsage = converterUsage;
|
|
4999
5082
|
}
|
|
5000
|
-
|
|
5001
|
-
usageForLog = extractUsage(extractedUsage);
|
|
5002
|
-
}
|
|
5003
|
-
else if (extractedUsage) {
|
|
5004
|
-
usageForLog = this.extractTokenUsageFromResponse(extractedUsage, sourceType);
|
|
5005
|
-
}
|
|
5083
|
+
usageForLog = this.tokenUsageFromCollected(extractedUsage);
|
|
5006
5084
|
};
|
|
5007
5085
|
try {
|
|
5008
5086
|
yield new Promise((resolve, reject) => {
|