@kenz1117/dsh-ui-usage-billing 1.2.6 → 1.2.7
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/lib/client.js +5 -5
- package/lib/index.js +307 -53
- package/lib/types/aggregate.d.ts +60 -4
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -331,7 +331,7 @@ const MODEL_CATALOG = [
|
|
|
331
331
|
key: "flash",
|
|
332
332
|
name: "DeepSeek V4.1 Flash",
|
|
333
333
|
provider: "DeepSeek",
|
|
334
|
-
colorVar: "
|
|
334
|
+
colorVar: "ds-blue",
|
|
335
335
|
price: {
|
|
336
336
|
currency: "CNY",
|
|
337
337
|
input: 2,
|
|
@@ -349,7 +349,7 @@ const MODEL_CATALOG = [
|
|
|
349
349
|
key: "flash-vision-exp",
|
|
350
350
|
name: "DeepSeek V4 Flash Vision (Exp)",
|
|
351
351
|
provider: "DeepSeek",
|
|
352
|
-
colorVar: "
|
|
352
|
+
colorVar: "ds-blue",
|
|
353
353
|
retired: true,
|
|
354
354
|
price: {
|
|
355
355
|
currency: "CNY",
|
|
@@ -398,7 +398,7 @@ const MODEL_CATALOG = [
|
|
|
398
398
|
key: "glm-5.3",
|
|
399
399
|
name: "GLM-5.3",
|
|
400
400
|
provider: "智谱 AI",
|
|
401
|
-
colorVar: "
|
|
401
|
+
colorVar: "ds-blue",
|
|
402
402
|
price: {
|
|
403
403
|
currency: "CNY",
|
|
404
404
|
input: 8,
|
|
@@ -464,7 +464,7 @@ const MODEL_CATALOG = [
|
|
|
464
464
|
key: "glm-5-turbo",
|
|
465
465
|
name: "GLM-5-Turbo",
|
|
466
466
|
provider: "智谱 AI",
|
|
467
|
-
colorVar: "
|
|
467
|
+
colorVar: "ds-blue",
|
|
468
468
|
price: {
|
|
469
469
|
currency: "CNY",
|
|
470
470
|
input: 5,
|
|
@@ -606,7 +606,7 @@ const MODEL_CATALOG = [
|
|
|
606
606
|
key: "qwen-plus",
|
|
607
607
|
name: "Qwen3.5-Plus",
|
|
608
608
|
provider: "阿里通义",
|
|
609
|
-
colorVar: "
|
|
609
|
+
colorVar: "ds-blue",
|
|
610
610
|
price: {
|
|
611
611
|
currency: "CNY",
|
|
612
612
|
input: .8,
|
|
@@ -659,7 +659,7 @@ const MODEL_CATALOG = [
|
|
|
659
659
|
key: "doubao",
|
|
660
660
|
name: "Doubao Seed-2.0 Pro",
|
|
661
661
|
provider: "字节豆包",
|
|
662
|
-
colorVar: "
|
|
662
|
+
colorVar: "ds-red",
|
|
663
663
|
price: {
|
|
664
664
|
currency: "CNY",
|
|
665
665
|
input: 3.2,
|
|
@@ -695,7 +695,7 @@ const MODEL_CATALOG = [
|
|
|
695
695
|
key: "doubao-seed-evolving",
|
|
696
696
|
name: "Doubao-Seed-Evolving",
|
|
697
697
|
provider: "字节豆包",
|
|
698
|
-
colorVar: "
|
|
698
|
+
colorVar: "ds-red",
|
|
699
699
|
price: {
|
|
700
700
|
currency: "CNY",
|
|
701
701
|
input: 6,
|
|
@@ -803,7 +803,7 @@ const MODEL_CATALOG = [
|
|
|
803
803
|
key: "minimax",
|
|
804
804
|
name: "MiniMax-M3",
|
|
805
805
|
provider: "MiniMax",
|
|
806
|
-
colorVar: "
|
|
806
|
+
colorVar: "ds-amber",
|
|
807
807
|
price: {
|
|
808
808
|
currency: "CNY",
|
|
809
809
|
input: 2.1,
|
|
@@ -827,7 +827,7 @@ const MODEL_CATALOG = [
|
|
|
827
827
|
key: "minimax-m2.7-highspeed",
|
|
828
828
|
name: "MiniMax-M2.7-highspeed",
|
|
829
829
|
provider: "MiniMax",
|
|
830
|
-
colorVar: "
|
|
830
|
+
colorVar: "ds-amber",
|
|
831
831
|
price: {
|
|
832
832
|
currency: "CNY",
|
|
833
833
|
input: 4.2,
|
|
@@ -875,7 +875,7 @@ const MODEL_CATALOG = [
|
|
|
875
875
|
key: "yi",
|
|
876
876
|
name: "Yi-Lightning",
|
|
877
877
|
provider: "零一万物",
|
|
878
|
-
colorVar: "
|
|
878
|
+
colorVar: "ds-green",
|
|
879
879
|
price: {
|
|
880
880
|
currency: "CNY",
|
|
881
881
|
input: .99,
|
|
@@ -937,7 +937,7 @@ const MODEL_CATALOG = [
|
|
|
937
937
|
key: "gpt-6-astra",
|
|
938
938
|
name: "GPT-6 Astra",
|
|
939
939
|
provider: "OpenAI",
|
|
940
|
-
colorVar: "
|
|
940
|
+
colorVar: "ds-green",
|
|
941
941
|
price: {
|
|
942
942
|
currency: "USD",
|
|
943
943
|
input: 10,
|
|
@@ -949,7 +949,7 @@ const MODEL_CATALOG = [
|
|
|
949
949
|
key: "gpt-5.6-sol",
|
|
950
950
|
name: "GPT-5.6 Sol",
|
|
951
951
|
provider: "OpenAI",
|
|
952
|
-
colorVar: "
|
|
952
|
+
colorVar: "ds-green",
|
|
953
953
|
price: {
|
|
954
954
|
currency: "USD",
|
|
955
955
|
input: 5,
|
|
@@ -979,7 +979,7 @@ const MODEL_CATALOG = [
|
|
|
979
979
|
key: "gpt-5.6-luna",
|
|
980
980
|
name: "GPT-5.6 Luna",
|
|
981
981
|
provider: "OpenAI",
|
|
982
|
-
colorVar: "
|
|
982
|
+
colorVar: "ds-green",
|
|
983
983
|
price: {
|
|
984
984
|
currency: "USD",
|
|
985
985
|
input: .2,
|
|
@@ -1053,7 +1053,7 @@ const MODEL_CATALOG = [
|
|
|
1053
1053
|
key: "llama",
|
|
1054
1054
|
name: "Llama 4 Maverick",
|
|
1055
1055
|
provider: "Meta",
|
|
1056
|
-
colorVar: "
|
|
1056
|
+
colorVar: "ds-red",
|
|
1057
1057
|
price: {
|
|
1058
1058
|
currency: "USD",
|
|
1059
1059
|
input: .2,
|
|
@@ -1077,7 +1077,7 @@ const MODEL_CATALOG = [
|
|
|
1077
1077
|
key: "claude-opus-4-6",
|
|
1078
1078
|
name: "Claude Opus 4.6",
|
|
1079
1079
|
provider: "Anthropic",
|
|
1080
|
-
colorVar: "
|
|
1080
|
+
colorVar: "ds-red",
|
|
1081
1081
|
price: {
|
|
1082
1082
|
currency: "USD",
|
|
1083
1083
|
input: 5,
|
|
@@ -1113,7 +1113,7 @@ const MODEL_CATALOG = [
|
|
|
1113
1113
|
key: "claude-opus-5",
|
|
1114
1114
|
name: "Claude Opus 5",
|
|
1115
1115
|
provider: "Anthropic",
|
|
1116
|
-
colorVar: "
|
|
1116
|
+
colorVar: "ds-red",
|
|
1117
1117
|
price: {
|
|
1118
1118
|
currency: "USD",
|
|
1119
1119
|
input: 5,
|
|
@@ -1137,7 +1137,7 @@ const MODEL_CATALOG = [
|
|
|
1137
1137
|
key: "mistral-large-2512",
|
|
1138
1138
|
name: "Mistral Large 3",
|
|
1139
1139
|
provider: "Mistral AI",
|
|
1140
|
-
colorVar: "
|
|
1140
|
+
colorVar: "ds-violet",
|
|
1141
1141
|
price: {
|
|
1142
1142
|
currency: "USD",
|
|
1143
1143
|
input: .5,
|
|
@@ -1173,7 +1173,7 @@ const MODEL_CATALOG = [
|
|
|
1173
1173
|
key: "command-a-03-2025",
|
|
1174
1174
|
name: "Command A",
|
|
1175
1175
|
provider: "Cohere",
|
|
1176
|
-
colorVar: "
|
|
1176
|
+
colorVar: "ds-cyan",
|
|
1177
1177
|
price: {
|
|
1178
1178
|
currency: "USD",
|
|
1179
1179
|
input: 2.5,
|
|
@@ -1197,7 +1197,7 @@ const MODEL_CATALOG = [
|
|
|
1197
1197
|
key: "longcat-2.0",
|
|
1198
1198
|
name: "LongCat 2.0",
|
|
1199
1199
|
provider: "美团",
|
|
1200
|
-
colorVar: "
|
|
1200
|
+
colorVar: "ds-amber",
|
|
1201
1201
|
price: {
|
|
1202
1202
|
currency: "CNY",
|
|
1203
1203
|
input: 4,
|
|
@@ -1210,7 +1210,7 @@ const MODEL_CATALOG = [
|
|
|
1210
1210
|
key: "minicpm-v-4.5",
|
|
1211
1211
|
name: "MiniCPM-V 4.5",
|
|
1212
1212
|
provider: "面壁智能",
|
|
1213
|
-
colorVar: "
|
|
1213
|
+
colorVar: "ds-green",
|
|
1214
1214
|
price: {
|
|
1215
1215
|
currency: "CNY",
|
|
1216
1216
|
input: 1,
|
|
@@ -1236,7 +1236,7 @@ const MODEL_CATALOG = [
|
|
|
1236
1236
|
key: "dots-3-note-preview",
|
|
1237
1237
|
name: "Dots3-Note Preview",
|
|
1238
1238
|
provider: "小红书",
|
|
1239
|
-
colorVar: "
|
|
1239
|
+
colorVar: "ds-red",
|
|
1240
1240
|
price: {
|
|
1241
1241
|
currency: "CNY",
|
|
1242
1242
|
input: 2,
|
|
@@ -1403,7 +1403,7 @@ const MODEL_CATALOG = [
|
|
|
1403
1403
|
key: "doubao-seed-2.0-code",
|
|
1404
1404
|
name: "Doubao Seed-2.0 Code",
|
|
1405
1405
|
provider: "字节豆包",
|
|
1406
|
-
colorVar: "
|
|
1406
|
+
colorVar: "ds-red",
|
|
1407
1407
|
price: {
|
|
1408
1408
|
currency: "CNY",
|
|
1409
1409
|
input: 3.2,
|
|
@@ -2047,9 +2047,151 @@ function workspaceNameOf(cwd) {
|
|
|
2047
2047
|
}
|
|
2048
2048
|
/** TTFT 尖峰阈值(毫秒):超过计为一次尖峰样本,用于定位服务端抖动。 */
|
|
2049
2049
|
const PERF_SPIKE_MS = 1e4;
|
|
2050
|
+
/** 全零摘要(合并的恒等元)。 */
|
|
2051
|
+
const EMPTY_MODEL_DIGEST = {
|
|
2052
|
+
samples: 0,
|
|
2053
|
+
ttftSum: 0,
|
|
2054
|
+
ttftMax: 0,
|
|
2055
|
+
ttftSpikes: 0,
|
|
2056
|
+
ttftP50: 0,
|
|
2057
|
+
ttftP90: 0,
|
|
2058
|
+
tpsCount: 0,
|
|
2059
|
+
tpsSum: 0,
|
|
2060
|
+
latencyCount: 0,
|
|
2061
|
+
latencySum: 0,
|
|
2062
|
+
estimated: 0
|
|
2063
|
+
};
|
|
2064
|
+
/** 从样本集构建模型级摘要(样本侧精确:P50/P90 为精确插值)。 */
|
|
2065
|
+
function modelDigestOf(samples) {
|
|
2066
|
+
const ttfts = [];
|
|
2067
|
+
let ttftSum = 0;
|
|
2068
|
+
let ttftMax = 0;
|
|
2069
|
+
let ttftSpikes = 0;
|
|
2070
|
+
let tpsCount = 0;
|
|
2071
|
+
let tpsSum = 0;
|
|
2072
|
+
let latencyCount = 0;
|
|
2073
|
+
let latencySum = 0;
|
|
2074
|
+
let estimated = 0;
|
|
2075
|
+
for (const sample of samples) {
|
|
2076
|
+
ttfts.push(sample.ttftMs);
|
|
2077
|
+
ttftSum += sample.ttftMs;
|
|
2078
|
+
if (sample.ttftMs > ttftMax) ttftMax = sample.ttftMs;
|
|
2079
|
+
if (sample.ttftMs > 1e4) ttftSpikes += 1;
|
|
2080
|
+
if (sample.tps !== void 0) {
|
|
2081
|
+
tpsCount += 1;
|
|
2082
|
+
tpsSum += sample.tps;
|
|
2083
|
+
}
|
|
2084
|
+
if (sample.latencyMs !== void 0) {
|
|
2085
|
+
latencyCount += 1;
|
|
2086
|
+
latencySum += sample.latencyMs;
|
|
2087
|
+
}
|
|
2088
|
+
if (sample.estimated) estimated += 1;
|
|
2089
|
+
}
|
|
2090
|
+
return {
|
|
2091
|
+
samples: samples.length,
|
|
2092
|
+
ttftSum,
|
|
2093
|
+
ttftMax,
|
|
2094
|
+
ttftSpikes,
|
|
2095
|
+
ttftP50: percentile(ttfts, .5),
|
|
2096
|
+
ttftP90: percentile(ttfts, .9),
|
|
2097
|
+
tpsCount,
|
|
2098
|
+
tpsSum,
|
|
2099
|
+
latencyCount,
|
|
2100
|
+
latencySum,
|
|
2101
|
+
estimated
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
/** 合并两个模型级摘要:计数/求和/极值精确;分位数按样本数加权。 */
|
|
2105
|
+
function mergeModelDigest(a, b) {
|
|
2106
|
+
const n = a.samples + b.samples;
|
|
2107
|
+
const weighted = (x, y) => n === 0 ? 0 : (x * a.samples + y * b.samples) / n;
|
|
2108
|
+
return {
|
|
2109
|
+
samples: n,
|
|
2110
|
+
ttftSum: a.ttftSum + b.ttftSum,
|
|
2111
|
+
ttftMax: Math.max(a.ttftMax, b.ttftMax),
|
|
2112
|
+
ttftSpikes: a.ttftSpikes + b.ttftSpikes,
|
|
2113
|
+
ttftP50: weighted(a.ttftP50, b.ttftP50),
|
|
2114
|
+
ttftP90: weighted(a.ttftP90, b.ttftP90),
|
|
2115
|
+
tpsCount: a.tpsCount + b.tpsCount,
|
|
2116
|
+
tpsSum: a.tpsSum + b.tpsSum,
|
|
2117
|
+
latencyCount: a.latencyCount + b.latencyCount,
|
|
2118
|
+
latencySum: a.latencySum + b.latencySum,
|
|
2119
|
+
estimated: a.estimated + b.estimated
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
/** 从样本集构建小时级摘要(均值可无损重建)。 */
|
|
2123
|
+
function hourDigestOf(samples) {
|
|
2124
|
+
let ttftSum = 0;
|
|
2125
|
+
let tpsCount = 0;
|
|
2126
|
+
let tpsSum = 0;
|
|
2127
|
+
for (const sample of samples) {
|
|
2128
|
+
ttftSum += sample.ttftMs;
|
|
2129
|
+
if (sample.tps !== void 0) {
|
|
2130
|
+
tpsCount += 1;
|
|
2131
|
+
tpsSum += sample.tps;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
return {
|
|
2135
|
+
samples: samples.length,
|
|
2136
|
+
ttftSum,
|
|
2137
|
+
tpsCount,
|
|
2138
|
+
tpsSum
|
|
2139
|
+
};
|
|
2140
|
+
}
|
|
2141
|
+
/** 全零小时摘要(合并的恒等元)。 */
|
|
2142
|
+
const EMPTY_HOUR_DIGEST = {
|
|
2143
|
+
samples: 0,
|
|
2144
|
+
ttftSum: 0,
|
|
2145
|
+
tpsCount: 0,
|
|
2146
|
+
tpsSum: 0
|
|
2147
|
+
};
|
|
2148
|
+
/** 合并两个小时级摘要(纯字段加法,无损)。 */
|
|
2149
|
+
function mergeHourDigest(a, b) {
|
|
2150
|
+
return {
|
|
2151
|
+
samples: a.samples + b.samples,
|
|
2152
|
+
ttftSum: a.ttftSum + b.ttftSum,
|
|
2153
|
+
tpsCount: a.tpsCount + b.tpsCount,
|
|
2154
|
+
tpsSum: a.tpsSum + b.tpsSum
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
2157
|
+
/**
|
|
2158
|
+
* 样本集 → 账本摘要形态(模型 → 摘要;小时 → 模型 → 摘要)。serializeFold
|
|
2159
|
+
* 与账本迁移共用同一转换,两处口径不会漂移。
|
|
2160
|
+
* @param samples - 一个会话的全部性能样本。
|
|
2161
|
+
* @returns `{ byModel, byHourModel }` 的 JSON-safe 记录。
|
|
2162
|
+
*/
|
|
2163
|
+
function perfDigestsOf(samples) {
|
|
2164
|
+
const byModelSamples = /* @__PURE__ */ new Map();
|
|
2165
|
+
const byHourModelSamples = /* @__PURE__ */ new Map();
|
|
2166
|
+
for (const sample of samples) {
|
|
2167
|
+
let modelSamples = byModelSamples.get(sample.model);
|
|
2168
|
+
if (modelSamples === void 0) {
|
|
2169
|
+
modelSamples = [];
|
|
2170
|
+
byModelSamples.set(sample.model, modelSamples);
|
|
2171
|
+
}
|
|
2172
|
+
modelSamples.push(sample);
|
|
2173
|
+
let hourModels = byHourModelSamples.get(sample.hour);
|
|
2174
|
+
if (hourModels === void 0) {
|
|
2175
|
+
hourModels = /* @__PURE__ */ new Map();
|
|
2176
|
+
byHourModelSamples.set(sample.hour, hourModels);
|
|
2177
|
+
}
|
|
2178
|
+
let hourModelSamples = hourModels.get(sample.model);
|
|
2179
|
+
if (hourModelSamples === void 0) {
|
|
2180
|
+
hourModelSamples = [];
|
|
2181
|
+
hourModels.set(sample.model, hourModelSamples);
|
|
2182
|
+
}
|
|
2183
|
+
hourModelSamples.push(sample);
|
|
2184
|
+
}
|
|
2185
|
+
return {
|
|
2186
|
+
byModel: Object.fromEntries([...byModelSamples].map(([model, list]) => [model, modelDigestOf(list)])),
|
|
2187
|
+
byHourModel: Object.fromEntries([...byHourModelSamples].map(([hour, models]) => [hour, Object.fromEntries([...models].map(([model, list]) => [model, hourDigestOf(list)]))]))
|
|
2188
|
+
};
|
|
2189
|
+
}
|
|
2050
2190
|
/**
|
|
2051
2191
|
* 账本迁移注册表。首条迁移给 1.0.6 及更早的行回填 foldVersion = 1(它们全部出自
|
|
2052
|
-
* header
|
|
2192
|
+
* header 归因算法);perf 摘要化迁移把 v15 及更早行的逐样本 `perf` 数组转换为
|
|
2193
|
+
* `perfDigest`/`perfHourDigest` 摘要(日志已删的行不再被重折,迁移是它们唯一的
|
|
2194
|
+
* 收缩机会);此后新写入的行总带当前 {@link FOLD_VERSION} 与摘要形态。
|
|
2053
2195
|
*/
|
|
2054
2196
|
const LEDGER_MIGRATIONS = [{
|
|
2055
2197
|
id: "fold-version-backfill",
|
|
@@ -2061,6 +2203,26 @@ const LEDGER_MIGRATIONS = [{
|
|
|
2061
2203
|
}
|
|
2062
2204
|
return changed;
|
|
2063
2205
|
}
|
|
2206
|
+
}, {
|
|
2207
|
+
id: "perf-sample-to-digest",
|
|
2208
|
+
apply(document) {
|
|
2209
|
+
let changed = false;
|
|
2210
|
+
for (const session of document.sessions) {
|
|
2211
|
+
const fold = session.fold;
|
|
2212
|
+
if (Array.isArray(fold.perf)) {
|
|
2213
|
+
const { byModel, byHourModel } = perfDigestsOf(fold.perf);
|
|
2214
|
+
fold.perfDigest = byModel;
|
|
2215
|
+
fold.perfHourDigest = byHourModel;
|
|
2216
|
+
delete fold.perf;
|
|
2217
|
+
changed = true;
|
|
2218
|
+
} else if (fold.perfDigest === void 0 || fold.perfHourDigest === void 0) {
|
|
2219
|
+
fold.perfDigest ??= {};
|
|
2220
|
+
fold.perfHourDigest ??= {};
|
|
2221
|
+
changed = true;
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
return changed;
|
|
2225
|
+
}
|
|
2064
2226
|
}];
|
|
2065
2227
|
/**
|
|
2066
2228
|
* 在加载边界对账本文档应用未执行的迁移,并记录已应用 id 供写回。
|
|
@@ -2084,6 +2246,7 @@ function runLedgerMigrations(document, migrations = LEDGER_MIGRATIONS) {
|
|
|
2084
2246
|
}
|
|
2085
2247
|
/** Serialize Map/Set-heavy fold state into a JSON-safe ledger entry. */
|
|
2086
2248
|
function serializeFold(fold) {
|
|
2249
|
+
const { byModel: perfDigest, byHourModel: perfHourDigest } = perfDigestsOf(fold.perf);
|
|
2087
2250
|
return {
|
|
2088
2251
|
total: fold.total,
|
|
2089
2252
|
byModel: Object.fromEntries(fold.byModel),
|
|
@@ -2096,7 +2259,8 @@ function serializeFold(fold) {
|
|
|
2096
2259
|
unpricedModels: [...fold.unpricedModels],
|
|
2097
2260
|
planCalls: Object.fromEntries(fold.planCalls),
|
|
2098
2261
|
turns: fold.turns,
|
|
2099
|
-
|
|
2262
|
+
perfDigest,
|
|
2263
|
+
perfHourDigest,
|
|
2100
2264
|
roles: fold.roles,
|
|
2101
2265
|
lastActive: fold.lastActive,
|
|
2102
2266
|
...fold.title !== void 0 ? { title: fold.title } : {}
|
|
@@ -2116,7 +2280,9 @@ function deserializeFold(fold) {
|
|
|
2116
2280
|
unpricedModels: new Set(fold.unpricedModels),
|
|
2117
2281
|
planCalls: new Map(Object.entries(fold.planCalls)),
|
|
2118
2282
|
turns: fold.turns,
|
|
2119
|
-
perf:
|
|
2283
|
+
perf: [],
|
|
2284
|
+
perfDigest: new Map(Object.entries(fold.perfDigest)),
|
|
2285
|
+
perfHourDigest: new Map(Object.entries(fold.perfHourDigest).map(([hour, models]) => [hour, new Map(Object.entries(models))])),
|
|
2120
2286
|
roles: fold.roles,
|
|
2121
2287
|
lastActive: fold.lastActive,
|
|
2122
2288
|
...fold.title !== void 0 ? { title: fold.title } : {}
|
|
@@ -2132,7 +2298,7 @@ function ledgerSessionsOf(value) {
|
|
|
2132
2298
|
const row = entry;
|
|
2133
2299
|
if (typeof row.id !== "string" || row.id === "" || row.fold === null || typeof row.fold !== "object") return false;
|
|
2134
2300
|
const fold = row.fold;
|
|
2135
|
-
return fold.total !== void 0 && fold.byModel !== void 0 && fold.byDay !== void 0 && fold.byDayModels !== void 0 && fold.bySite !== void 0 && Array.isArray(fold.unpricedModels) && fold.planCalls !== void 0 && Array.isArray(fold.turns) &&
|
|
2301
|
+
return fold.total !== void 0 && fold.byModel !== void 0 && fold.byDay !== void 0 && fold.byDayModels !== void 0 && fold.bySite !== void 0 && Array.isArray(fold.unpricedModels) && fold.planCalls !== void 0 && Array.isArray(fold.turns) && fold.perfDigest !== void 0 && fold.perfHourDigest !== void 0 && fold.roles !== void 0 && typeof fold.lastActive === "number";
|
|
2136
2302
|
});
|
|
2137
2303
|
}
|
|
2138
2304
|
/**
|
|
@@ -2227,6 +2393,8 @@ function freshFold() {
|
|
|
2227
2393
|
planCalls: /* @__PURE__ */ new Map(),
|
|
2228
2394
|
turns: [],
|
|
2229
2395
|
perf: [],
|
|
2396
|
+
perfDigest: /* @__PURE__ */ new Map(),
|
|
2397
|
+
perfHourDigest: /* @__PURE__ */ new Map(),
|
|
2230
2398
|
roles: {
|
|
2231
2399
|
userChars: 0,
|
|
2232
2400
|
toolChars: 0,
|
|
@@ -2468,11 +2636,6 @@ function mergeUsageInto(acc, cell) {
|
|
|
2468
2636
|
acc.officialCost += cell.officialCost;
|
|
2469
2637
|
if (cell.searchCalls !== void 0) acc.searchCalls = (acc.searchCalls ?? 0) + cell.searchCalls;
|
|
2470
2638
|
}
|
|
2471
|
-
/** 均值(数组非空时调用;空数组按 0 兜底)。 */
|
|
2472
|
-
function mean(values) {
|
|
2473
|
-
if (values.length === 0) return 0;
|
|
2474
|
-
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
2475
|
-
}
|
|
2476
2639
|
/** 分位数(0..1):先拷贝排序,再线性插值;空数组返回 0。 */
|
|
2477
2640
|
function percentile(values, p) {
|
|
2478
2641
|
if (values.length === 0) return 0;
|
|
@@ -2489,6 +2652,53 @@ function percentile(values, p) {
|
|
|
2489
2652
|
if (a === void 0 || b === void 0) return 0;
|
|
2490
2653
|
return a + (b - a) * (idx - lo);
|
|
2491
2654
|
}
|
|
2655
|
+
/** 活样本累加器 → 模型级摘要(跨会话合并前先归一到摘要形态)。 */
|
|
2656
|
+
function accumToModelDigest(acc) {
|
|
2657
|
+
return {
|
|
2658
|
+
samples: acc.ttfts.length,
|
|
2659
|
+
ttftSum: acc.ttfts.reduce((sum, value) => sum + value, 0),
|
|
2660
|
+
ttftMax: acc.ttfts.length === 0 ? 0 : Math.max(...acc.ttfts),
|
|
2661
|
+
ttftSpikes: acc.ttfts.filter((ttft) => ttft > PERF_SPIKE_MS).length,
|
|
2662
|
+
ttftP50: percentile(acc.ttfts, .5),
|
|
2663
|
+
ttftP90: percentile(acc.ttfts, .9),
|
|
2664
|
+
tpsCount: acc.tps.length,
|
|
2665
|
+
tpsSum: acc.tps.reduce((sum, value) => sum + value, 0),
|
|
2666
|
+
latencyCount: acc.latencies.length,
|
|
2667
|
+
latencySum: acc.latencies.reduce((sum, value) => sum + value, 0),
|
|
2668
|
+
estimated: acc.estimated
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
/** 活样本小时累加器 → 小时级摘要。 */
|
|
2672
|
+
function accumToHourDigest(acc) {
|
|
2673
|
+
return {
|
|
2674
|
+
samples: acc.ttfts.length,
|
|
2675
|
+
ttftSum: acc.ttfts.reduce((sum, value) => sum + value, 0),
|
|
2676
|
+
tpsCount: acc.tps.length,
|
|
2677
|
+
tpsSum: acc.tps.reduce((sum, value) => sum + value, 0)
|
|
2678
|
+
};
|
|
2679
|
+
}
|
|
2680
|
+
/** 模型级摘要 → 面板行(均值由求和/计数派生)。 */
|
|
2681
|
+
function modelPerfOf(digest) {
|
|
2682
|
+
return {
|
|
2683
|
+
samples: digest.samples,
|
|
2684
|
+
ttftAvg: digest.samples === 0 ? 0 : digest.ttftSum / digest.samples,
|
|
2685
|
+
ttftP50: digest.ttftP50,
|
|
2686
|
+
ttftP90: digest.ttftP90,
|
|
2687
|
+
ttftMax: digest.ttftMax,
|
|
2688
|
+
ttftSpikes: digest.ttftSpikes,
|
|
2689
|
+
...digest.tpsCount === 0 ? {} : { tpsAvg: digest.tpsSum / digest.tpsCount },
|
|
2690
|
+
latencyAvg: digest.latencyCount === 0 ? 0 : digest.latencySum / digest.latencyCount,
|
|
2691
|
+
estimatedSamples: digest.estimated
|
|
2692
|
+
};
|
|
2693
|
+
}
|
|
2694
|
+
/** 小时级摘要 → 面板行(均值由求和/计数派生)。 */
|
|
2695
|
+
function hourPerfOf(digest) {
|
|
2696
|
+
return {
|
|
2697
|
+
samples: digest.samples,
|
|
2698
|
+
ttftAvg: digest.samples === 0 ? 0 : digest.ttftSum / digest.samples,
|
|
2699
|
+
...digest.tpsCount === 0 ? {} : { tpsAvg: digest.tpsSum / digest.tpsCount }
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2492
2702
|
/**
|
|
2493
2703
|
* 聚合配置指纹:影响折叠语义的全部配置(订阅豁免、官方名单、路由别名、搜索估值)
|
|
2494
2704
|
* 的稳定序列化。账本行的复用判定携带该指纹——用户改配置后(如为 grok build 加
|
|
@@ -2536,7 +2746,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2536
2746
|
id,
|
|
2537
2747
|
...cwd === void 0 ? {} : { cwd },
|
|
2538
2748
|
...stamp === null ? {} : { stamp },
|
|
2539
|
-
foldVersion:
|
|
2749
|
+
foldVersion: 16,
|
|
2540
2750
|
fingerprint: foldFingerprint,
|
|
2541
2751
|
fold: serializeFold(fold)
|
|
2542
2752
|
};
|
|
@@ -2633,7 +2843,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2633
2843
|
try {
|
|
2634
2844
|
const cwd = meta.cwd;
|
|
2635
2845
|
const ledgerRow = ledger.get(id);
|
|
2636
|
-
if (ledgerRow !== void 0 && ledgerRow.stamp !== void 0 && stamp !== null && ledgerRow.stamp === stamp && (ledgerRow.foldVersion ?? 1) ===
|
|
2846
|
+
if (ledgerRow !== void 0 && ledgerRow.stamp !== void 0 && stamp !== null && ledgerRow.stamp === stamp && (ledgerRow.foldVersion ?? 1) === 16 && ledgerRow.fingerprint === foldFingerprint) {
|
|
2637
2847
|
const fold = deserializeFold(ledgerRow.fold);
|
|
2638
2848
|
folds.push({
|
|
2639
2849
|
id,
|
|
@@ -2715,7 +2925,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2715
2925
|
for (const entry of ledger.values()) {
|
|
2716
2926
|
if (included.has(entry.id)) continue;
|
|
2717
2927
|
try {
|
|
2718
|
-
const stale = (entry.foldVersion ?? 1) <
|
|
2928
|
+
const stale = (entry.foldVersion ?? 1) < 16 || entry.fingerprint !== foldFingerprint;
|
|
2719
2929
|
folds.push({
|
|
2720
2930
|
id: entry.id,
|
|
2721
2931
|
...entry.cwd === void 0 ? {} : { cwd: entry.cwd },
|
|
@@ -2752,6 +2962,8 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2752
2962
|
};
|
|
2753
2963
|
const perfModel = /* @__PURE__ */ new Map();
|
|
2754
2964
|
const perfHourModel = /* @__PURE__ */ new Map();
|
|
2965
|
+
const perfModelDigest = /* @__PURE__ */ new Map();
|
|
2966
|
+
const perfHourDigest = /* @__PURE__ */ new Map();
|
|
2755
2967
|
for (const { id: sessionId, cwd, fold, staleLedger } of folds) {
|
|
2756
2968
|
mergeUsageInto(total, fold.total);
|
|
2757
2969
|
roles.userChars += fold.roles.userChars;
|
|
@@ -2798,6 +3010,15 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2798
3010
|
hourAccum.ttfts.push(sample.ttftMs);
|
|
2799
3011
|
if (sample.tps !== void 0) hourAccum.tps.push(sample.tps);
|
|
2800
3012
|
}
|
|
3013
|
+
for (const [model, digest] of fold.perfDigest) perfModelDigest.set(model, mergeModelDigest(perfModelDigest.get(model) ?? EMPTY_MODEL_DIGEST, digest));
|
|
3014
|
+
for (const [hour, models] of fold.perfHourDigest) {
|
|
3015
|
+
let hourModels = perfHourDigest.get(hour);
|
|
3016
|
+
if (hourModels === void 0) {
|
|
3017
|
+
hourModels = /* @__PURE__ */ new Map();
|
|
3018
|
+
perfHourDigest.set(hour, hourModels);
|
|
3019
|
+
}
|
|
3020
|
+
for (const [model, digest] of models) hourModels.set(model, mergeHourDigest(hourModels.get(model) ?? EMPTY_HOUR_DIGEST, digest));
|
|
3021
|
+
}
|
|
2801
3022
|
for (const row of fold.turns) turnRows.push({
|
|
2802
3023
|
sessionId,
|
|
2803
3024
|
...row
|
|
@@ -2841,23 +3062,26 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2841
3062
|
};
|
|
2842
3063
|
const toModelDayRecord = (map) => Object.fromEntries([...map].map(([day, models]) => [day, Object.fromEntries(models)]));
|
|
2843
3064
|
const toModelDaySiteRecord = (map) => Object.fromEntries([...map].map(([day, models]) => [day, Object.fromEntries([...models].map(([model, sites]) => [model, Object.fromEntries(sites)]))]));
|
|
2844
|
-
const
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
3065
|
+
const modelKeys = /* @__PURE__ */ new Set([...perfModel.keys(), ...perfModelDigest.keys()]);
|
|
3066
|
+
const hourKeys = /* @__PURE__ */ new Set([...perfHourModel.keys(), ...perfHourDigest.keys()]);
|
|
3067
|
+
const perf = modelKeys.size === 0 && hourKeys.size === 0 ? void 0 : {
|
|
3068
|
+
byModel: Object.fromEntries([...modelKeys].map((model) => {
|
|
3069
|
+
const live = perfModel.get(model);
|
|
3070
|
+
const restored = perfModelDigest.get(model);
|
|
3071
|
+
const digest = live === void 0 ? restored : mergeModelDigest(accumToModelDigest(live), restored ?? EMPTY_MODEL_DIGEST);
|
|
3072
|
+
return digest === void 0 ? null : [model, modelPerfOf(digest)];
|
|
3073
|
+
}).filter((entry) => entry !== null)),
|
|
3074
|
+
byHourModel: Object.fromEntries([...hourKeys].sort().map((hour) => {
|
|
3075
|
+
const liveModels = perfHourModel.get(hour);
|
|
3076
|
+
const digestModels = perfHourDigest.get(hour);
|
|
3077
|
+
const modelKeysInHour = /* @__PURE__ */ new Set([...liveModels?.keys() ?? [], ...digestModels?.keys() ?? []]);
|
|
3078
|
+
return [hour, Object.fromEntries([...modelKeysInHour].map((model) => {
|
|
3079
|
+
const live = liveModels?.get(model);
|
|
3080
|
+
const restored = digestModels?.get(model);
|
|
3081
|
+
const digest = live === void 0 ? restored : mergeHourDigest(accumToHourDigest(live), restored ?? EMPTY_HOUR_DIGEST);
|
|
3082
|
+
return digest === void 0 ? null : [model, hourPerfOf(digest)];
|
|
3083
|
+
}).filter((entry) => entry !== null))];
|
|
3084
|
+
}))
|
|
2861
3085
|
};
|
|
2862
3086
|
lastDoc = {
|
|
2863
3087
|
version: 4,
|
|
@@ -2903,6 +3127,29 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
2903
3127
|
});
|
|
2904
3128
|
return inflight;
|
|
2905
3129
|
},
|
|
3130
|
+
async sessionUsageOf(sessionId) {
|
|
3131
|
+
await this.aggregate();
|
|
3132
|
+
const hit = cache.get(sessionId);
|
|
3133
|
+
if (hit !== void 0) return {
|
|
3134
|
+
calls: hit.fold.total.calls,
|
|
3135
|
+
cost: hit.fold.total.cost,
|
|
3136
|
+
input: hit.fold.total.input,
|
|
3137
|
+
output: hit.fold.total.output
|
|
3138
|
+
};
|
|
3139
|
+
const row = ledger.get(sessionId);
|
|
3140
|
+
if (row === void 0) return void 0;
|
|
3141
|
+
try {
|
|
3142
|
+
const fold = deserializeFold(row.fold);
|
|
3143
|
+
return {
|
|
3144
|
+
calls: fold.total.calls,
|
|
3145
|
+
cost: fold.total.cost,
|
|
3146
|
+
input: fold.total.input,
|
|
3147
|
+
output: fold.total.output
|
|
3148
|
+
};
|
|
3149
|
+
} catch {
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
3152
|
+
},
|
|
2906
3153
|
async flush() {
|
|
2907
3154
|
if (inflight !== void 0) await inflight.catch(() => {});
|
|
2908
3155
|
await saveLedger(true);
|
|
@@ -5468,8 +5715,10 @@ const DEFAULT_BALANCE_API_KEY_ENV = "DEEPSEEK_API_KEY";
|
|
|
5468
5715
|
* 下发,供「设置 → 插件信息卡」展示。发布版 lib/index.js 相对包根解析。
|
|
5469
5716
|
*/
|
|
5470
5717
|
const PACKAGE_VERSION = createRequire(import.meta.url)("../package.json").version ?? "0.0.0";
|
|
5471
|
-
/**
|
|
5472
|
-
|
|
5718
|
+
/** 统计快照的落盘节流(毫秒):快照只服务聚合失败回退与重启首屏(展示级
|
|
5719
|
+
* 新鲜度即可),降到 5 分钟一次——原子写含上一版备份是两次全量 JSON 落盘,
|
|
5720
|
+
* 重度用户快照上百 KB,跟随前端轮询 30 秒写一次纯属写放大。 */
|
|
5721
|
+
const SNAPSHOT_INTERVAL_MS = 3e5;
|
|
5473
5722
|
/** usage-stats 响应的等待预算(毫秒):聚合在此预算内完成就回实时数据,
|
|
5474
5723
|
* 超预算立即回最近快照(stale-while-revalidate),后台折叠继续跑。
|
|
5475
5724
|
* 轻量用户暖缓存下的聚合是毫秒级,预算几乎不会触发。 */
|
|
@@ -6002,6 +6251,11 @@ function apply(ctx, config = {}) {
|
|
|
6002
6251
|
}
|
|
6003
6252
|
const sessionId = exec.agent?.id;
|
|
6004
6253
|
if (sessionId === void 0) throw new Error("usage_stats 的 session 范围需要 agent 会话上下文");
|
|
6254
|
+
const summary = await aggregator.sessionUsageOf(String(sessionId));
|
|
6255
|
+
if (summary !== void 0) return {
|
|
6256
|
+
range: args.range,
|
|
6257
|
+
...summary
|
|
6258
|
+
};
|
|
6005
6259
|
let cost = 0;
|
|
6006
6260
|
let calls = 0;
|
|
6007
6261
|
let input = 0;
|