dsh-token-total 1.0.3 → 1.0.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/lib/client.js +332 -123
- package/lib/index.js +47 -4
- package/lib/types/client/format.d.ts +8 -4
- package/lib/types/client/locales.d.ts +1 -1
- package/package.json +1 -1
- package/src/client/TokenTotalPanel.module.css +259 -99
- package/src/client/TokenTotalPanel.tsx +197 -62
- package/src/client/format.ts +9 -7
- package/src/client/locales.ts +76 -0
- package/src/query.ts +53 -2
package/lib/index.js
CHANGED
|
@@ -80,6 +80,7 @@ function aggregateSamples(samples, filter, granularity = "day") {
|
|
|
80
80
|
const from = filter.from;
|
|
81
81
|
const to = filter.to;
|
|
82
82
|
const buckets = bucketsInRange(from, to, granularity);
|
|
83
|
+
const all = [...samples];
|
|
83
84
|
const out = {
|
|
84
85
|
from,
|
|
85
86
|
to,
|
|
@@ -116,7 +117,8 @@ function aggregateSamples(samples, filter, granularity = "day") {
|
|
|
116
117
|
const bucketTurns = /* @__PURE__ */ new Map();
|
|
117
118
|
const bucketCacheHit = /* @__PURE__ */ new Map();
|
|
118
119
|
const bucketCacheMiss = /* @__PURE__ */ new Map();
|
|
119
|
-
|
|
120
|
+
const modelAlias = aliasQualifiedModels(new Set(all.map((sample) => modelKeyOf(sample))));
|
|
121
|
+
for (const sample of all) {
|
|
120
122
|
const bucket = bucketOf(sample, from, to, granularity);
|
|
121
123
|
if (bucket === null) continue;
|
|
122
124
|
if (sample.turn) {
|
|
@@ -142,9 +144,9 @@ function aggregateSamples(samples, filter, granularity = "day") {
|
|
|
142
144
|
out.cacheHit += sample.cacheReadTokens;
|
|
143
145
|
const missSide = sample.inputTokens + sample.cacheWriteTokens;
|
|
144
146
|
out.cacheMiss += missSide;
|
|
145
|
-
const
|
|
146
|
-
const
|
|
147
|
-
const model =
|
|
147
|
+
const provider = providerOf(sample.model && sample.model !== "" ? sample.model : "(unknown)");
|
|
148
|
+
const modelBase = modelKeyOf(sample);
|
|
149
|
+
const model = modelAlias.get(modelBase) ?? modelBase;
|
|
148
150
|
const sources = modelProviders.get(model) ?? /* @__PURE__ */ new Set();
|
|
149
151
|
sources.add(provider);
|
|
150
152
|
modelProviders.set(model, sources);
|
|
@@ -219,6 +221,47 @@ function aggregateSamples(samples, filter, granularity = "day") {
|
|
|
219
221
|
}
|
|
220
222
|
return out;
|
|
221
223
|
}
|
|
224
|
+
/** The aggregation key of one sample: the resolved display name when the
|
|
225
|
+
* host resolved one, else the bare model ref — normalized like every
|
|
226
|
+
* display name so the key is stable across routes and refreshes. */
|
|
227
|
+
function modelKeyOf(sample) {
|
|
228
|
+
const modelRef = sample.model && sample.model !== "" ? sample.model : "(unknown)";
|
|
229
|
+
return normalizeModelName(sample.modelName?.trim() || modelNameOf(modelRef));
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Qualified-name aliases: "workbuddy-ai-plugin/deepseek-v4.1-flash" and
|
|
233
|
+
* "deepseek-v4.1-flash" are the same model reached through a routing
|
|
234
|
+
* namespace, so a name containing "/" merges onto the LONGEST suffix after a
|
|
235
|
+
* "/" that is itself a model key in this dataset (chains like a/b/c → b/c → c
|
|
236
|
+
* resolve transitively). The merge is dataset-relative on purpose: a slash
|
|
237
|
+
* that names no bare model in the data ("GPT/6.1 sol" beside "gpt-6.1-sol")
|
|
238
|
+
* is a genuinely different name and stays its own row — see the
|
|
239
|
+
* distinct-slashes case in tests/model-aggregation.test.mjs.
|
|
240
|
+
*/
|
|
241
|
+
function aliasQualifiedModels(names) {
|
|
242
|
+
const alias = /* @__PURE__ */ new Map();
|
|
243
|
+
for (const name of names) {
|
|
244
|
+
if (!name.includes("/")) continue;
|
|
245
|
+
const parts = name.split("/");
|
|
246
|
+
for (let i = 1; i < parts.length; i++) {
|
|
247
|
+
const suffix = parts.slice(i).join("/");
|
|
248
|
+
if (names.has(suffix)) {
|
|
249
|
+
alias.set(name, suffix);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
for (const name of [...alias.keys()]) {
|
|
255
|
+
let target = alias.get(name);
|
|
256
|
+
const seen = /* @__PURE__ */ new Set([name]);
|
|
257
|
+
while (alias.has(target) && !seen.has(target)) {
|
|
258
|
+
seen.add(target);
|
|
259
|
+
target = alias.get(target);
|
|
260
|
+
}
|
|
261
|
+
alias.set(name, target);
|
|
262
|
+
}
|
|
263
|
+
return alias;
|
|
264
|
+
}
|
|
222
265
|
function modelsSorted(totals, cache, sources, splits) {
|
|
223
266
|
const out = [];
|
|
224
267
|
for (const [model, tokens] of totals) {
|
|
@@ -25,10 +25,14 @@ export declare function localDay(offsetDays: number): string;
|
|
|
25
25
|
/** 0 = Monday … 6 = Sunday (GitHub-style heatmap rows). */
|
|
26
26
|
export declare function indexOfDay(day: string): number;
|
|
27
27
|
export declare function shortDay(day: string): string;
|
|
28
|
-
/** X-axis label for a trend bucket key.
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
|
|
28
|
+
/** X-axis label for a trend bucket key. `mode` is what the axis READS AS,
|
|
29
|
+
* chosen by the caller from the window it covers:
|
|
30
|
+
* - 'hour' — a single-day hourly span (今日): the date is constant, so the
|
|
31
|
+
* axis shows the bare clock ("8:00");
|
|
32
|
+
* - 'day' — anything spanning several days (3天 hourly, any daily range):
|
|
33
|
+
* the axis shows the short date ("10/22"); on multi-day hourly spans one
|
|
34
|
+
* label marks each day. The tooltip still carries the full date + hour. */
|
|
35
|
+
export declare function bucketAxisLabel(key: string, mode: 'day' | 'hour'): string;
|
|
32
36
|
/** Tooltip title for a trend bucket key: the date in day mode, date + hour
|
|
33
37
|
* in hour mode. */
|
|
34
38
|
export declare function bucketTitle(key: string, granularity: 'day' | 'hour'): string;
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
/** The locale namespace this plugin owns. */
|
|
8
8
|
export declare const LOCALE_NS = "tokenTotal";
|
|
9
9
|
/** The typed translator key union for the panel's locale seat. */
|
|
10
|
-
export type TokenTotalKey = 'nav' | 'back' | 'range' | 'rangePreset.today' | 'rangePreset.3' | 'rangePreset.7' | 'rangePreset.30' | 'rangePreset.90' | 'rangeCustom' | 'from' | 'to' | 'refresh' | 'loading' | 'tokens' | 'tokensHint' | 'tokensUnit' | 'inputTokens' | 'outputTokens' | 'cacheTokens' | 'cachedTokens' | 'sessions' | 'requests' | 'requestsFailed' | 'activeDays' | 'cacheRate' | 'cacheRateHint' | 'cacheHitRate' | 'hitRateLegend' | 'topModel' | 'topModelHint' | 'heatmap' | 'heatLess' | 'heatMore' | 'dailyTrend' | 'hourlyTrend' | 'trendLimited' | 'usageRanking' | 'byModel' | 'byProvider' | 'tokensColumn' | 'cacheRateColumn' | 'providerModels' | 'other' | 'total' | 'percent' | 'asOf' | 'empty' | 'stats.counts' | 'stats.tokensPerSecond' | 'stats.cacheHit' | 'stats.cacheMiss' | 'stats.dialog.title' | 'stats.dialog.usageTitle' | 'stats.dialog.llmTime' | 'stats.dialog.toolTime' | 'stats.dialog.ttft' | 'stats.dialog.speed' | 'stats.dialog.main' | 'stats.dialog.subagents' | 'stats.dialog.subagentCount' | 'stats.dialog.treeTotal' | 'message.turnUsage.count' | 'message.turnUsage.cacheHit' | 'message.turnUsage.input' | 'message.turnUsage.cacheRead' | 'message.turnUsage.cacheWrite' | 'message.turnUsage.output';
|
|
10
|
+
export type TokenTotalKey = 'nav' | 'back' | 'range' | 'rangePreset.today' | 'rangePreset.3' | 'rangePreset.7' | 'rangePreset.30' | 'rangePreset.90' | 'rangeCustom' | 'from' | 'to' | 'refresh' | 'loading' | 'tokens' | 'tokensHint' | 'tokensUnit' | 'inputTokens' | 'outputTokens' | 'cacheTokens' | 'cachedTokens' | 'sessions' | 'requests' | 'requestsFailed' | 'activeDays' | 'cacheRate' | 'cacheRateHint' | 'cacheHitRate' | 'hitRateLegend' | 'topModel' | 'topModelHint' | 'heatmap' | 'heatLess' | 'heatMore' | 'dow.mon' | 'dow.tue' | 'dow.wed' | 'dow.thu' | 'dow.fri' | 'dow.sat' | 'dow.sun' | 'month.jan' | 'month.feb' | 'month.mar' | 'month.apr' | 'month.may' | 'month.jun' | 'month.jul' | 'month.aug' | 'month.sep' | 'month.oct' | 'month.nov' | 'month.dec' | 'dailyTrend' | 'hourlyTrend' | 'trendLimited' | 'usageRanking' | 'byModel' | 'byProvider' | 'tokensColumn' | 'cacheRateColumn' | 'providerModels' | 'other' | 'total' | 'percent' | 'asOf' | 'empty' | 'stats.counts' | 'stats.tokensPerSecond' | 'stats.cacheHit' | 'stats.cacheMiss' | 'stats.dialog.title' | 'stats.dialog.usageTitle' | 'stats.dialog.llmTime' | 'stats.dialog.toolTime' | 'stats.dialog.ttft' | 'stats.dialog.speed' | 'stats.dialog.main' | 'stats.dialog.subagents' | 'stats.dialog.subagentCount' | 'stats.dialog.treeTotal' | 'message.turnUsage.count' | 'message.turnUsage.cacheHit' | 'message.turnUsage.input' | 'message.turnUsage.cacheRead' | 'message.turnUsage.cacheWrite' | 'message.turnUsage.output';
|
|
11
11
|
export declare const en: Record<TokenTotalKey, string>;
|
|
12
12
|
export declare const zh: Record<TokenTotalKey, string>;
|
|
13
13
|
export declare const zhTW: Record<TokenTotalKey, string>;
|
package/package.json
CHANGED