@kenz1117/dsh-ui-usage-billing 0.9.2 → 0.9.4
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.en.md +20 -19
- package/README.md +20 -19
- package/lib/client.js +813 -257
- package/lib/index.js +146 -4
- package/lib/types/aggregate.d.ts +60 -0
- package/lib/types/client/plan-knowledge.d.ts +22 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -27,11 +27,23 @@ import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
|
27
27
|
const PLAN_KNOWLEDGE = {
|
|
28
28
|
"opencode-go": {
|
|
29
29
|
type: "code",
|
|
30
|
-
subscriptionCny: 70
|
|
30
|
+
subscriptionCny: 70,
|
|
31
|
+
tier: {
|
|
32
|
+
amount: 10,
|
|
33
|
+
currency: "USD",
|
|
34
|
+
periodDays: 7,
|
|
35
|
+
label: "周额度 $30"
|
|
36
|
+
}
|
|
31
37
|
},
|
|
32
38
|
opencode: {
|
|
33
39
|
type: "code",
|
|
34
|
-
subscriptionCny: 70
|
|
40
|
+
subscriptionCny: 70,
|
|
41
|
+
tier: {
|
|
42
|
+
amount: 10,
|
|
43
|
+
currency: "USD",
|
|
44
|
+
periodDays: 7,
|
|
45
|
+
label: "周额度 $30"
|
|
46
|
+
}
|
|
35
47
|
},
|
|
36
48
|
"kimi-coding": {
|
|
37
49
|
type: "code",
|
|
@@ -993,6 +1005,12 @@ function dayStamp(time) {
|
|
|
993
1005
|
const pad = (n) => String(n).padStart(2, "0");
|
|
994
1006
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
995
1007
|
}
|
|
1008
|
+
/** Local-time hour stamp `YYYY-MM-DDTHH` — the performance series bucket key. */
|
|
1009
|
+
function hourStamp(time) {
|
|
1010
|
+
const date = new Date(time);
|
|
1011
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
1012
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}`;
|
|
1013
|
+
}
|
|
996
1014
|
/** 工作区名:取 cwd 的末级目录名;无 cwd 时返回 {@link UNKNOWN_WORKSPACE_NAME}。 */
|
|
997
1015
|
function workspaceNameOf(cwd) {
|
|
998
1016
|
if (cwd === void 0 || cwd === "") return "—";
|
|
@@ -1066,6 +1084,7 @@ function foldSession(events, subscriptionProviders, officialProviderIds) {
|
|
|
1066
1084
|
byDayModels: /* @__PURE__ */ new Map(),
|
|
1067
1085
|
planCalls: /* @__PURE__ */ new Map(),
|
|
1068
1086
|
turns: [],
|
|
1087
|
+
perf: [],
|
|
1069
1088
|
roles: {
|
|
1070
1089
|
userChars: 0,
|
|
1071
1090
|
toolChars: 0,
|
|
@@ -1078,6 +1097,8 @@ function foldSession(events, subscriptionProviders, officialProviderIds) {
|
|
|
1078
1097
|
let subscription = false;
|
|
1079
1098
|
let official = false;
|
|
1080
1099
|
const turns = /* @__PURE__ */ new Map();
|
|
1100
|
+
const steps = /* @__PURE__ */ new Map();
|
|
1101
|
+
let lastOpenStepKey;
|
|
1081
1102
|
for (const event of events) {
|
|
1082
1103
|
fold.lastActive = Math.max(fold.lastActive, event.time);
|
|
1083
1104
|
if (event.type === "session/title") {
|
|
@@ -1104,11 +1125,39 @@ function foldSession(events, subscriptionProviders, officialProviderIds) {
|
|
|
1104
1125
|
if (state !== void 0) state.endedAt = event.time;
|
|
1105
1126
|
continue;
|
|
1106
1127
|
}
|
|
1128
|
+
if (event.type === "step/start") {
|
|
1129
|
+
const turn = event.data.turn;
|
|
1130
|
+
const step = event.data.step;
|
|
1131
|
+
if (typeof turn === "number" && typeof step === "number") {
|
|
1132
|
+
const stepKey = `${turn}:${step}`;
|
|
1133
|
+
steps.set(stepKey, { startTime: event.time });
|
|
1134
|
+
lastOpenStepKey = stepKey;
|
|
1135
|
+
}
|
|
1136
|
+
continue;
|
|
1137
|
+
}
|
|
1107
1138
|
if (event.type === "request/header") {
|
|
1108
1139
|
const { model, provider } = event.data.header.config;
|
|
1109
1140
|
key = resolveCatalogKey(model);
|
|
1110
1141
|
subscription = subscriptionProviders.has(provider);
|
|
1111
1142
|
official = officialProviderIds === void 0 ? isOfficialProvider(provider) : officialProviderIds.has(provider);
|
|
1143
|
+
if (lastOpenStepKey !== void 0) {
|
|
1144
|
+
const stepState = steps.get(lastOpenStepKey);
|
|
1145
|
+
if (stepState !== void 0 && stepState.requestTime === void 0) stepState.requestTime = event.time;
|
|
1146
|
+
}
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
if (event.type === "assistant/chunk") {
|
|
1150
|
+
const data = event.data;
|
|
1151
|
+
const turn = data.turn;
|
|
1152
|
+
const step = data.step;
|
|
1153
|
+
const chunk = data.chunk;
|
|
1154
|
+
if (typeof turn === "number" && typeof step === "number" && chunk !== void 0 && chunk.type !== "usage" && chunk.type !== "finish") {
|
|
1155
|
+
const state = steps.get(`${turn}:${step}`);
|
|
1156
|
+
if (state !== void 0) {
|
|
1157
|
+
if (state.firstContentTime === void 0) state.firstContentTime = event.time;
|
|
1158
|
+
state.lastContentTime = event.time;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1112
1161
|
continue;
|
|
1113
1162
|
}
|
|
1114
1163
|
if (event.type !== "assistant/message") continue;
|
|
@@ -1121,7 +1170,8 @@ function foldSession(events, subscriptionProviders, officialProviderIds) {
|
|
|
1121
1170
|
foldUsage(usageCell(fold.byDay, day), usage, modelKey, subscription, event.time, official);
|
|
1122
1171
|
foldUsage(modelDayCell(fold.byDayModels, day, modelKey), usage, modelKey, subscription, event.time, official);
|
|
1123
1172
|
if (subscription) fold.planCalls.set(modelKey, (fold.planCalls.get(modelKey) ?? 0) + 1);
|
|
1124
|
-
const
|
|
1173
|
+
const turn = event.data.turn ?? -1;
|
|
1174
|
+
const state = turnState(turns, turn);
|
|
1125
1175
|
state.model = modelKey;
|
|
1126
1176
|
state.input += usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0);
|
|
1127
1177
|
state.output += usage.outputTokens;
|
|
@@ -1146,6 +1196,15 @@ function foldSession(events, subscriptionProviders, officialProviderIds) {
|
|
|
1146
1196
|
fold.roles.inputCost += fullCost - outputCost;
|
|
1147
1197
|
}
|
|
1148
1198
|
if (state.startedAt === Number.MAX_SAFE_INTEGER) state.startedAt = event.time;
|
|
1199
|
+
const stepNum = event.data.step;
|
|
1200
|
+
if (typeof stepNum === "number") {
|
|
1201
|
+
const perfState = steps.get(`${turn}:${stepNum}`);
|
|
1202
|
+
if (perfState !== void 0) {
|
|
1203
|
+
const sample = perfSampleOf(perfState, modelKey, usage.outputTokens ?? 0, event.time);
|
|
1204
|
+
if (sample !== void 0) fold.perf.push(sample);
|
|
1205
|
+
steps.delete(`${turn}:${stepNum}`);
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1149
1208
|
}
|
|
1150
1209
|
fold.turns = [...turns.values()].filter((state) => state.input > 0 || state.output > 0).sort((a, b) => a.turn - b.turn).map((state) => ({
|
|
1151
1210
|
turn: state.turn,
|
|
@@ -1160,6 +1219,29 @@ function foldSession(events, subscriptionProviders, officialProviderIds) {
|
|
|
1160
1219
|
}));
|
|
1161
1220
|
return fold;
|
|
1162
1221
|
}
|
|
1222
|
+
/**
|
|
1223
|
+
* 生成一个 step 的性能样本;无效 / 超出 sane 上限(15 分钟)时返回 undefined,
|
|
1224
|
+
* 避免单条异常记录(时区错位 / 服务端抖动)拉偏均值。
|
|
1225
|
+
*/
|
|
1226
|
+
function perfSampleOf(state, model, outputTokens, endTime) {
|
|
1227
|
+
const start = state.requestTime ?? state.startTime;
|
|
1228
|
+
const first = state.firstContentTime;
|
|
1229
|
+
const last = state.lastContentTime;
|
|
1230
|
+
if (start === void 0 || first === void 0 || first < start) return void 0;
|
|
1231
|
+
const ttftMs = first - start;
|
|
1232
|
+
if (!Number.isFinite(ttftMs) || ttftMs < 0 || ttftMs > 9e5) return void 0;
|
|
1233
|
+
const genMs = last !== void 0 && last > first ? last - first : void 0;
|
|
1234
|
+
const latencyMs = endTime >= start ? endTime - start : void 0;
|
|
1235
|
+
const tps = genMs !== void 0 && genMs > 0 && outputTokens > 0 ? outputTokens / (genMs / 1e3) : void 0;
|
|
1236
|
+
return {
|
|
1237
|
+
model,
|
|
1238
|
+
hour: hourStamp(endTime),
|
|
1239
|
+
ttftMs,
|
|
1240
|
+
...tps === void 0 || !Number.isFinite(tps) || tps <= 0 ? {} : { tps },
|
|
1241
|
+
estimated: state.requestTime === void 0,
|
|
1242
|
+
...latencyMs === void 0 ? {} : { latencyMs }
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1163
1245
|
/** Accumulate one ModelUsage into another (merge step of the incremental aggregator). */
|
|
1164
1246
|
function mergeUsageInto(acc, cell) {
|
|
1165
1247
|
acc.calls += cell.calls;
|
|
@@ -1171,6 +1253,21 @@ function mergeUsageInto(acc, cell) {
|
|
|
1171
1253
|
acc.officialCalls += cell.officialCalls;
|
|
1172
1254
|
acc.officialCost += cell.officialCost;
|
|
1173
1255
|
}
|
|
1256
|
+
/** 均值(数组非空时调用;空数组按 0 兜底)。 */
|
|
1257
|
+
function mean(values) {
|
|
1258
|
+
if (values.length === 0) return 0;
|
|
1259
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
1260
|
+
}
|
|
1261
|
+
/** 分位数(0..1):先拷贝排序,再线性插值;空数组返回 0。 */
|
|
1262
|
+
function percentile(values, p) {
|
|
1263
|
+
if (values.length === 0) return 0;
|
|
1264
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
1265
|
+
const idx = (sorted.length - 1) * p;
|
|
1266
|
+
const lo = Math.floor(idx);
|
|
1267
|
+
const hi = Math.ceil(idx);
|
|
1268
|
+
if (lo === hi) return sorted[lo];
|
|
1269
|
+
return sorted[lo] + (sorted[hi] - sorted[lo]) * (idx - lo);
|
|
1270
|
+
}
|
|
1174
1271
|
/**
|
|
1175
1272
|
* Create the incremental usage aggregator.
|
|
1176
1273
|
* @param persistence - the session persistence service.
|
|
@@ -1245,6 +1342,8 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
1245
1342
|
inputCost: 0,
|
|
1246
1343
|
outputCost: 0
|
|
1247
1344
|
};
|
|
1345
|
+
const perfModel = /* @__PURE__ */ new Map();
|
|
1346
|
+
const perfHour = /* @__PURE__ */ new Map();
|
|
1248
1347
|
for (const { meta, fold } of folds) {
|
|
1249
1348
|
const sessionId = String(meta.id);
|
|
1250
1349
|
mergeUsageInto(total, fold.total);
|
|
@@ -1256,6 +1355,32 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
1256
1355
|
for (const [day, cell] of fold.byDay) mergeUsageInto(usageCell(byDay, day), cell);
|
|
1257
1356
|
for (const [day, models] of fold.byDayModels) for (const [modelKey, cell] of models) mergeUsageInto(modelDayCell(byDayModels, day, modelKey), cell);
|
|
1258
1357
|
for (const [modelKey, count] of fold.planCalls) planCalls.set(modelKey, (planCalls.get(modelKey) ?? 0) + count);
|
|
1358
|
+
for (const sample of fold.perf) {
|
|
1359
|
+
let modelAccum = perfModel.get(sample.model);
|
|
1360
|
+
if (modelAccum === void 0) {
|
|
1361
|
+
modelAccum = {
|
|
1362
|
+
ttfts: [],
|
|
1363
|
+
tps: [],
|
|
1364
|
+
latencies: [],
|
|
1365
|
+
estimated: 0
|
|
1366
|
+
};
|
|
1367
|
+
perfModel.set(sample.model, modelAccum);
|
|
1368
|
+
}
|
|
1369
|
+
modelAccum.ttfts.push(sample.ttftMs);
|
|
1370
|
+
if (sample.tps !== void 0) modelAccum.tps.push(sample.tps);
|
|
1371
|
+
if (sample.latencyMs !== void 0) modelAccum.latencies.push(sample.latencyMs);
|
|
1372
|
+
if (sample.estimated) modelAccum.estimated += 1;
|
|
1373
|
+
let hourAccum = perfHour.get(sample.hour);
|
|
1374
|
+
if (hourAccum === void 0) {
|
|
1375
|
+
hourAccum = {
|
|
1376
|
+
ttfts: [],
|
|
1377
|
+
tps: []
|
|
1378
|
+
};
|
|
1379
|
+
perfHour.set(sample.hour, hourAccum);
|
|
1380
|
+
}
|
|
1381
|
+
hourAccum.ttfts.push(sample.ttftMs);
|
|
1382
|
+
if (sample.tps !== void 0) hourAccum.tps.push(sample.tps);
|
|
1383
|
+
}
|
|
1259
1384
|
for (const row of fold.turns) turnRows.push({
|
|
1260
1385
|
sessionId,
|
|
1261
1386
|
...row
|
|
@@ -1297,6 +1422,22 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
1297
1422
|
return record;
|
|
1298
1423
|
};
|
|
1299
1424
|
const toModelDayRecord = (map) => Object.fromEntries([...map].map(([day, models]) => [day, Object.fromEntries(models)]));
|
|
1425
|
+
const perf = perfModel.size === 0 ? void 0 : {
|
|
1426
|
+
byModel: Object.fromEntries([...perfModel].map(([model, acc]) => [model, {
|
|
1427
|
+
samples: acc.ttfts.length,
|
|
1428
|
+
ttftAvg: mean(acc.ttfts),
|
|
1429
|
+
ttftP50: percentile(acc.ttfts, .5),
|
|
1430
|
+
ttftP90: percentile(acc.ttfts, .9),
|
|
1431
|
+
...acc.tps.length === 0 ? {} : { tpsAvg: mean(acc.tps) },
|
|
1432
|
+
latencyAvg: acc.latencies.length === 0 ? 0 : mean(acc.latencies),
|
|
1433
|
+
estimatedSamples: acc.estimated
|
|
1434
|
+
}])),
|
|
1435
|
+
byHour: Object.fromEntries([...perfHour].map(([hour, acc]) => [hour, {
|
|
1436
|
+
samples: acc.ttfts.length,
|
|
1437
|
+
ttftAvg: mean(acc.ttfts),
|
|
1438
|
+
...acc.tps.length === 0 ? {} : { tpsAvg: mean(acc.tps) }
|
|
1439
|
+
}]))
|
|
1440
|
+
};
|
|
1300
1441
|
lastDoc = {
|
|
1301
1442
|
version: 3,
|
|
1302
1443
|
updatedAt: now,
|
|
@@ -1308,6 +1449,7 @@ function createUsageAggregator(persistence, options = {}) {
|
|
|
1308
1449
|
bySession: sessionRows.slice(0, 100),
|
|
1309
1450
|
byTurn: turnRows.slice(0, 200),
|
|
1310
1451
|
byWorkspace: workspaces.slice(0, 100),
|
|
1452
|
+
...perf === void 0 ? {} : { perf },
|
|
1311
1453
|
byRole: (() => {
|
|
1312
1454
|
const chars = roles.userChars + roles.toolChars;
|
|
1313
1455
|
const userShare = chars > 0 ? roles.userChars / chars : .5;
|
|
@@ -1921,7 +2063,7 @@ const EMPTY_SUBSCRIPTION_KEYS = {
|
|
|
1921
2063
|
/** 订阅类 provider 的显示名(未命中的回退为 id 本身)。 */
|
|
1922
2064
|
const SUBSCRIPTION_DISPLAY_NAMES = {
|
|
1923
2065
|
"kimi-coding": "Kimi For Coding",
|
|
1924
|
-
"zai-coding-cn": "Z.ai Coding Plan",
|
|
2066
|
+
"zai-coding-cn": "Z.ai Coding Plan(国内)",
|
|
1925
2067
|
"zai-coding": "Z.ai Coding Plan",
|
|
1926
2068
|
"opencode": "OpenCode Plan",
|
|
1927
2069
|
"opencode-go": "OpenCode Go",
|
package/lib/types/aggregate.d.ts
CHANGED
|
@@ -65,6 +65,8 @@ export declare function emptyUsage(): ModelUsage;
|
|
|
65
65
|
export declare function foldUsage(acc: ModelUsage, usage: TokenUsage, key: string, subscription: boolean, timeMs: number, official?: boolean): void;
|
|
66
66
|
/** Local-time date stamp (the host runs in the user's timezone). */
|
|
67
67
|
export declare function dayStamp(time: number): string;
|
|
68
|
+
/** Local-time hour stamp `YYYY-MM-DDTHH` — the performance series bucket key. */
|
|
69
|
+
export declare function hourStamp(time: number): string;
|
|
68
70
|
/** cwd 未知时工作区聚合的占位名(UI 显示 em dash,保持语言无关)。 */
|
|
69
71
|
export declare const UNKNOWN_WORKSPACE_NAME = "\u2014";
|
|
70
72
|
/** 工作区名:取 cwd 的末级目录名;无 cwd 时返回 {@link UNKNOWN_WORKSPACE_NAME}。 */
|
|
@@ -98,6 +100,13 @@ export interface UsageStatsDocument {
|
|
|
98
100
|
* 属估算口径,UI 需标注)。旧快照可能缺失。
|
|
99
101
|
*/
|
|
100
102
|
byRole?: RoleCost;
|
|
103
|
+
/**
|
|
104
|
+
* 性能指标(TTFT / 生成速度 / 总延迟)按模型与按小时聚合;旧快照可能缺失。
|
|
105
|
+
* 口径:TTFT = request/header → 首个内容 chunk;生成速度 = 输出 token ÷ 生成时长;
|
|
106
|
+
* 总延迟 = request/header → assistant/message。工具续写步骤无独立请求头,
|
|
107
|
+
* 以 step/start 为起点估算并计 estimated。
|
|
108
|
+
*/
|
|
109
|
+
perf?: PerfStats;
|
|
101
110
|
}
|
|
102
111
|
/** 按角色费用归因:user / tool 为输入成本的启发式摊分,assistant 为输出成本实测。 */
|
|
103
112
|
export interface RoleCost {
|
|
@@ -105,6 +114,40 @@ export interface RoleCost {
|
|
|
105
114
|
assistant: number;
|
|
106
115
|
tool: number;
|
|
107
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* 性能指标(TTFT / 生成速度 / 总延迟):按模型与按小时聚合,供「性能」面板渲染。
|
|
119
|
+
* 旧快照(无 perf 字段)缺失时客户端按无数据兜底。
|
|
120
|
+
*/
|
|
121
|
+
export interface PerfStats {
|
|
122
|
+
/** 按模型聚合(键 = 计费目录键;未收录模型原样保留)。 */
|
|
123
|
+
byModel: Record<string, ModelPerf>;
|
|
124
|
+
/** 按小时聚合(键 = {@link hourStamp},北京时间)。 */
|
|
125
|
+
byHour: Record<string, HourPerf>;
|
|
126
|
+
}
|
|
127
|
+
/** 一个模型的性能统计:首字延时均值 / P50 / P90、生成速度均值、总延迟均值。 */
|
|
128
|
+
export interface ModelPerf {
|
|
129
|
+
/** 有效性能样本数(有可测 TTFT 的调用;不含损毁样本)。 */
|
|
130
|
+
samples: number;
|
|
131
|
+
/** 平均首字延时(毫秒)。 */
|
|
132
|
+
ttftAvg: number;
|
|
133
|
+
/** 首字延时 P50(毫秒)。 */
|
|
134
|
+
ttftP50: number;
|
|
135
|
+
/** 首字延时 P90(毫秒)。 */
|
|
136
|
+
ttftP90: number;
|
|
137
|
+
/** 平均生成速度(tokens/s);生成了有效输出且时长可测时存在。 */
|
|
138
|
+
tpsAvg?: number;
|
|
139
|
+
/** 平均总延迟(首次请求 → 响应完成,毫秒)。 */
|
|
140
|
+
latencyAvg: number;
|
|
141
|
+
/** 以 step/start 估算的样本数(工具续写步骤无独立 request/header)。 */
|
|
142
|
+
estimatedSamples: number;
|
|
143
|
+
}
|
|
144
|
+
/** 一个小时的性能统计(键 = {@link hourStamp})。 */
|
|
145
|
+
export interface HourPerf {
|
|
146
|
+
samples: number;
|
|
147
|
+
ttftAvg: number;
|
|
148
|
+
/** 平均生成速度(tokens/s);该小时无可测生成窗口时缺失。 */
|
|
149
|
+
tpsAvg?: number;
|
|
150
|
+
}
|
|
108
151
|
/** 会话明细行:仪表盘「会话明细」面板的数据源。 */
|
|
109
152
|
export interface SessionUsageRow {
|
|
110
153
|
/** 会话 id(字符串形式)。 */
|
|
@@ -156,6 +199,21 @@ export declare const SESSION_ROW_LIMIT = 100;
|
|
|
156
199
|
export declare const TURN_ROW_LIMIT = 200;
|
|
157
200
|
/** 聚合文档的短 TTL(毫秒):合并密集轮询,TTL 内直接复用上次的合并结果。 */
|
|
158
201
|
export declare const AGGREGATE_TTL_MS = 5000;
|
|
202
|
+
/** 单步性能样本(foldSession 的折叠产物;跨会话合并时按模型/小时再聚合)。 */
|
|
203
|
+
export interface PerfSample {
|
|
204
|
+
/** 计费目录键(模型;未收录模型原样保留)。 */
|
|
205
|
+
model: string;
|
|
206
|
+
/** 北京时间小时戳({@link hourStamp})——性能曲线的时间桶键。 */
|
|
207
|
+
hour: string;
|
|
208
|
+
/** 首字延时(毫秒);无效样本(超出 sane 上限)不入样本集。 */
|
|
209
|
+
ttftMs: number;
|
|
210
|
+
/** 生成速度(tokens/s);无有效生成窗口或无输出时缺失。 */
|
|
211
|
+
tps?: number;
|
|
212
|
+
/** 总延迟(首次请求 → 响应完成,毫秒);只测到内容但完成时刻优先于起点时缺失。 */
|
|
213
|
+
latencyMs?: number;
|
|
214
|
+
/** 无独立 request/header,以 step/start 起算(工具续写步骤)。 */
|
|
215
|
+
estimated: boolean;
|
|
216
|
+
}
|
|
159
217
|
/** One persisted session's folded usage plus drill-down metadata. */
|
|
160
218
|
interface SessionFold {
|
|
161
219
|
total: ModelUsage;
|
|
@@ -166,6 +224,8 @@ interface SessionFold {
|
|
|
166
224
|
planCalls: Map<string, number>;
|
|
167
225
|
/** 每轮费用明细(按轮次号升序,不含 sessionId);sessionId 在合并时补齐。 */
|
|
168
226
|
turns: SessionTurnRow[];
|
|
227
|
+
/** 性能样本(有可测 TTFT 的调用,按事件次序折叠)。 */
|
|
228
|
+
perf: PerfSample[];
|
|
169
229
|
/** 角色归因中间量:消息文本长度(user/tool)与输入/输出成本实测拆分。 */
|
|
170
230
|
roles: RoleFold;
|
|
171
231
|
/** 日志里最新的 session/title 文本(无标题事件时 undefined)。 */
|
|
@@ -15,11 +15,31 @@
|
|
|
15
15
|
*/
|
|
16
16
|
/** Plan type: code = subscription + quota, token = per-token usage. */
|
|
17
17
|
export type PlanType = 'code' | 'token';
|
|
18
|
+
/**
|
|
19
|
+
* 订阅档位知识(自动识别的「档位月费 + 周期额度口径」):供订阅卡片在
|
|
20
|
+
* 厂商官方未提供实时额度接口时展示参考口径。currency 为原生币种;费用与
|
|
21
|
+
* 额度按官方订阅周期(天/周/月)计量时,periodDays 表述该重置周期。
|
|
22
|
+
*/
|
|
23
|
+
export interface PlanTier {
|
|
24
|
+
/** 档位月费(原生币种值)。 */
|
|
25
|
+
amount: number;
|
|
26
|
+
currency: 'CNY' | 'USD';
|
|
27
|
+
/** 周期额度口径:每 periodDays 天重置的一个额度窗。 */
|
|
28
|
+
periodDays: number;
|
|
29
|
+
/** 周期请求额度(若有)。 */
|
|
30
|
+
requests?: number;
|
|
31
|
+
/** 周期 token 额度(若有)。 */
|
|
32
|
+
tokens?: number;
|
|
33
|
+
/** 额度口径的人话描述(官方未公布精确额度时)。 */
|
|
34
|
+
label?: string;
|
|
35
|
+
}
|
|
18
36
|
/** One plan row: provider id (after alias) → plan shape + optional subscription fee. */
|
|
19
37
|
export interface PlanKnowledgeEntry {
|
|
20
38
|
type: PlanType;
|
|
21
39
|
/** 订阅月费(人民币元);code 计划用,计入「本月预计」。 */
|
|
22
40
|
subscriptionCny?: number;
|
|
41
|
+
/** 自动识别的档位月费与周期额度口径(订阅卡片展示)。 */
|
|
42
|
+
tier?: PlanTier;
|
|
23
43
|
}
|
|
24
44
|
/**
|
|
25
45
|
* 订阅/计划 provider id → plan 知识(引用 dsh-spend 的 code/token 双口径)。
|
|
@@ -30,6 +50,8 @@ export declare const PLAN_KNOWLEDGE: Readonly<Record<string, PlanKnowledgeEntry>
|
|
|
30
50
|
export declare function planTypeOf(providerId: string): PlanType;
|
|
31
51
|
/** 订阅月费(人民币元);非 code 或未配置时为 0。 */
|
|
32
52
|
export declare function subscriptionCnyOf(providerId: string): number;
|
|
53
|
+
/** 自动识别的档位月费与周期额度口径(订阅卡片展示);无档位知识返回 undefined。 */
|
|
54
|
+
export declare function tierInfoOf(providerId: string): PlanTier | undefined;
|
|
33
55
|
export interface FallbackRate {
|
|
34
56
|
/** 归一化模型 id(计费键,与 catalogEntries 的 key 同口径)。 */
|
|
35
57
|
key: string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenz1117/dsh-ui-usage-billing",
|
|
3
3
|
"description": "Usage billing dashboard for DeepSeek Harness: sidebar cost metrics plus a full dashboard modal, priced from a current multi-provider catalog with real usage aggregated from session logs.",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.4",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|