dsh-plugin-usage 0.2.2 → 0.4.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/README.md +43 -135
- package/icon.svg +11 -0
- package/lib/client/index.js +747 -31
- package/lib/host/query.js +24 -4
- package/lib/host/settings.js +39 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +8 -3
package/lib/host/query.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* by-model table and model candidate list) are owned here.
|
|
6
6
|
*/
|
|
7
7
|
import { dayKey, DAY_MS, bucketKeyFor, stepMs, alignFloor, stepDayKey, MAX_BUCKETS, } from './time.js';
|
|
8
|
+
import { currencyOf } from './settings.js';
|
|
8
9
|
function emptyBucket(key) {
|
|
9
10
|
return {
|
|
10
11
|
key,
|
|
@@ -52,7 +53,7 @@ export function query(state, args) {
|
|
|
52
53
|
const dailyMap = new Map();
|
|
53
54
|
const modelMap = new Map();
|
|
54
55
|
const total = emptyBucket('total');
|
|
55
|
-
const seenModels = new Map(); // composite key -> {provider, model} (first composite wins)
|
|
56
|
+
const seenModels = new Map(); // composite key -> {key, provider, model} (first composite wins)
|
|
56
57
|
let unpricedCalls = 0;
|
|
57
58
|
// Range-anchored day-step grid: blocks start at the range's first day.
|
|
58
59
|
const dayAnchor = Number.isFinite(from) ? alignFloor(from, 'day') : null;
|
|
@@ -60,14 +61,17 @@ export function query(state, args) {
|
|
|
60
61
|
for (const r of state.index ? state.index.recordsInRange(from, to) : state.records) {
|
|
61
62
|
if (r.time < from || r.time > to)
|
|
62
63
|
continue;
|
|
64
|
+
// Candidate list first, selection filter second: the dropdown must offer
|
|
65
|
+
// every model that has data in the range, not only the ones already ticked
|
|
66
|
+
// (ticking one used to collapse the list to that single entry).
|
|
67
|
+
const modelKey = compositeModelKey(r);
|
|
68
|
+
if (!seenModels.has(modelKey))
|
|
69
|
+
seenModels.set(modelKey, { key: modelKey, provider: r.provider, model: r.model });
|
|
63
70
|
if (!modelMatches(r))
|
|
64
71
|
continue;
|
|
65
72
|
const cost = state.prices.costOf(r);
|
|
66
73
|
if (r.totalTokens > 0 && !state.prices.hasPrice(r))
|
|
67
74
|
unpricedCalls += 1;
|
|
68
|
-
const modelKey = compositeModelKey(r);
|
|
69
|
-
if (!seenModels.has(modelKey))
|
|
70
|
-
seenModels.set(modelKey, { provider: r.provider, model: r.model });
|
|
71
75
|
const dk = useStepGrid ? stepDayKey(r, dayAnchor, stepDays) : bucketKeyFor(r.time, granularity, stepDays);
|
|
72
76
|
let db = dailyMap.get(dk);
|
|
73
77
|
if (db === undefined) {
|
|
@@ -145,12 +149,28 @@ export function query(state, args) {
|
|
|
145
149
|
heatmap.push(hmMap.get(k) || emptyBucket(k));
|
|
146
150
|
}
|
|
147
151
|
}
|
|
152
|
+
// Display currency: the price table is denominated in USD and stays as it is
|
|
153
|
+
// (it is shared, polled, and never mutated per request). Conversion happens
|
|
154
|
+
// here, once, at the output boundary — including the heatmap and the totals,
|
|
155
|
+
// so no two views of the same data disagree. Six decimals keep float tails
|
|
156
|
+
// out of the sums.
|
|
157
|
+
const money = currencyOf(args.currency, state.prices.currency);
|
|
158
|
+
const toDisplay = (v) => (money.perUsd === 1 ? v : Math.round(v * money.perUsd * 1e6) / 1e6);
|
|
159
|
+
for (const b of daily)
|
|
160
|
+
b.cost = toDisplay(b.cost);
|
|
161
|
+
for (const b of byModel)
|
|
162
|
+
b.cost = toDisplay(b.cost);
|
|
163
|
+
total.cost = toDisplay(total.cost);
|
|
164
|
+
if (heatmap !== null)
|
|
165
|
+
for (const b of heatmap)
|
|
166
|
+
b.cost = toDisplay(b.cost);
|
|
148
167
|
return {
|
|
149
168
|
daily,
|
|
150
169
|
byModel,
|
|
151
170
|
total,
|
|
152
171
|
models,
|
|
153
172
|
unpricedCalls,
|
|
173
|
+
currency: { code: money.code, symbol: money.symbol },
|
|
154
174
|
...heatmap === null ? {} : { heatmap },
|
|
155
175
|
backfill: { status: state.backfill.status, total: state.backfill.total, done: state.backfill.done },
|
|
156
176
|
};
|
package/lib/host/settings.js
CHANGED
|
@@ -12,6 +12,45 @@
|
|
|
12
12
|
* The store below stays the in-memory view the aggregation code reads.
|
|
13
13
|
*/
|
|
14
14
|
export const DEFAULT_CURRENCY = '¥';
|
|
15
|
+
/**
|
|
16
|
+
* Currency the price table is denominated in.
|
|
17
|
+
*
|
|
18
|
+
* DEFAULT_PRICES carries DeepSeek's USD list price per 1M tokens (0.27 input /
|
|
19
|
+
* 1.10 output / 0.07 cache-read for `deepseek-chat`, which is exactly 2 / 8 /
|
|
20
|
+
* 0.5 CNY at the fixed 7:1). The table is never rewritten at runtime: a display
|
|
21
|
+
* switch converts on the way out, so a query can never pollute the next one.
|
|
22
|
+
*/
|
|
23
|
+
export const PRICE_UNIT_CURRENCY = 'USD';
|
|
24
|
+
/** Display currencies; `perUsd` is how many units of it one USD buys. */
|
|
25
|
+
export const CURRENCIES = {
|
|
26
|
+
USD: { code: 'USD', symbol: '$', perUsd: 1 },
|
|
27
|
+
CNY: { code: 'CNY', symbol: '¥', perUsd: 7 },
|
|
28
|
+
};
|
|
29
|
+
function currencyCodeOf(value) {
|
|
30
|
+
const raw = typeof value === 'string' ? value.trim().toUpperCase() : '';
|
|
31
|
+
if (raw === 'USD' || raw === '$' || raw === 'US$')
|
|
32
|
+
return 'USD';
|
|
33
|
+
if (raw === 'CNY' || raw === '¥' || raw === 'RMB' || raw === 'CN¥')
|
|
34
|
+
return 'CNY';
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Resolve a configured currency (ISO code or the bare symbol the store has
|
|
39
|
+
* always held) to a CURRENCIES entry. Unknown values fall back to `fallback`,
|
|
40
|
+
* then to the default display currency — never throw on user input.
|
|
41
|
+
*
|
|
42
|
+
* @param {unknown} value
|
|
43
|
+
* @param {unknown} [fallback]
|
|
44
|
+
*/
|
|
45
|
+
export function currencyOf(value, fallback) {
|
|
46
|
+
const hit = CURRENCIES[currencyCodeOf(value)];
|
|
47
|
+
if (hit)
|
|
48
|
+
return hit;
|
|
49
|
+
const fb = CURRENCIES[currencyCodeOf(fallback)];
|
|
50
|
+
if (fb)
|
|
51
|
+
return fb;
|
|
52
|
+
return CURRENCIES[currencyCodeOf(DEFAULT_CURRENCY)] || CURRENCIES.CNY;
|
|
53
|
+
}
|
|
15
54
|
export const DEFAULT_PRICES = {
|
|
16
55
|
'deepseek-chat': { inputPerM: 0.27, outputPerM: 1.1, cacheReadPerM: 0.07, cacheWritePerM: 0.27 },
|
|
17
56
|
'deepseek-v3': { inputPerM: 0.27, outputPerM: 1.1, cacheReadPerM: 0.07, cacheWritePerM: 0.27 },
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-usage",
|
|
3
3
|
"description": "Token usage statistics for DeepSeek Harness — daily/custom-range views, model filters, call counts and cost, inspired by CC Switch statistics.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/bennett-dsh/dsh-plugin-usage.git"
|
|
9
9
|
},
|
|
10
10
|
"type": "module",
|
|
11
|
+
"icon": "./icon.svg",
|
|
11
12
|
"main": "lib/host/index.js",
|
|
12
13
|
"exports": {
|
|
13
14
|
".": {
|
|
@@ -17,11 +18,14 @@
|
|
|
17
18
|
"default": "./lib/client/index.js"
|
|
18
19
|
},
|
|
19
20
|
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
20
|
-
"./package.json": "./package.json"
|
|
21
|
+
"./package.json": "./package.json",
|
|
22
|
+
"./locale/*.json": "./locale/*.json"
|
|
21
23
|
},
|
|
22
24
|
"files": [
|
|
23
25
|
"lib",
|
|
24
26
|
"cordis.patch.yml",
|
|
27
|
+
"icon.svg",
|
|
28
|
+
"locale",
|
|
25
29
|
"image.png",
|
|
26
30
|
"image-1.png"
|
|
27
31
|
],
|
|
@@ -47,7 +51,8 @@
|
|
|
47
51
|
"inject": [
|
|
48
52
|
"@deepseek-ai/dsh-client-runtime",
|
|
49
53
|
"@deepseek-ai/dsh-client-ui-settings",
|
|
50
|
-
"@deepseek-ai/dsh-client-locale"
|
|
54
|
+
"@deepseek-ai/dsh-client-locale",
|
|
55
|
+
"@deepseek-ai/dsh-client-ui-conversation"
|
|
51
56
|
],
|
|
52
57
|
"platform": "web"
|
|
53
58
|
}
|