@linxin666/dsh-usage 0.3.19 → 0.3.21
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.i18n.yaml +2 -2
- package/README.md +6 -1
- package/README.zh.md +6 -1
- package/lib/client.js +347 -34
- package/lib/client.js.map +1 -1
- package/lib/index.js +72 -17
- package/lib/types/client/UsageSectionCard.d.ts +4 -3
- package/lib/types/client/locales.d.ts +12 -0
- package/lib/types/client/voucher-art.generated.d.ts +9 -0
- package/lib/types/client/voucher.d.ts +56 -0
- package/lib/types/core/pricing.d.ts +14 -8
- package/lib/types/core/types.d.ts +28 -6
- package/lib/types/host/usage-service.d.ts +26 -0
- package/package.json +14 -14
- package/src/client/UsageSectionCard.tsx +141 -15
- package/src/client/index.ts +7 -4
- package/src/client/locales.ts +24 -0
- package/src/client/usage.module.css +13 -0
- package/src/client/voucher-art.generated.ts +10 -0
- package/src/client/voucher.ts +144 -0
- package/src/core/pricing.ts +31 -17
- package/src/core/types.ts +30 -6
- package/src/host/usage-service.ts +76 -4
package/lib/index.js
CHANGED
|
@@ -45,7 +45,7 @@ function mountOnce(packageName, fn) {
|
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
47
|
//#endregion
|
|
48
|
-
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-brand@0.1.
|
|
48
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-brand@0.1.5-rc.1_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-brand/lib/index.js
|
|
49
49
|
/**
|
|
50
50
|
* Duplicate-install-safe nominal primitive helpers.
|
|
51
51
|
*
|
|
@@ -68,7 +68,7 @@ function brandString(value) {
|
|
|
68
68
|
return value;
|
|
69
69
|
}
|
|
70
70
|
//#endregion
|
|
71
|
-
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-credentials@0.1.
|
|
71
|
+
//#region ../../node_modules/.pnpm/@deepseek-ai+dsh-credentials@0.1.5-rc.1_@deepseek-ai+cordis@4.0.2_@deepseek-ai+dsh-inva_8353d2c97c08a345a5c3047263f2fe4a/node_modules/@deepseek-ai/dsh-credentials/lib/index.js
|
|
72
72
|
/**
|
|
73
73
|
* Service Definition for the credential-reference capability seam (`ctx.credentials`). Settings and composition files carry
|
|
74
74
|
* *references* to secrets — environment-variable names — while providers own
|
|
@@ -585,22 +585,22 @@ const PEAK_WINDOWS = [{
|
|
|
585
585
|
}];
|
|
586
586
|
/** Beijing is UTC+8 year-round (no DST), so a fixed shift is exact. */
|
|
587
587
|
const BEIJING_UTC_OFFSET_MS = 8 * 36e5;
|
|
588
|
-
/** deepseek-
|
|
588
|
+
/** deepseek-flash (DeepSeek-V4.1-Flash), including the retired flash-class ids it now serves. */
|
|
589
589
|
const FLASH_PRICE = {
|
|
590
590
|
cacheHit: {
|
|
591
|
-
offPeak: .
|
|
592
|
-
peak: .
|
|
591
|
+
offPeak: .02,
|
|
592
|
+
peak: .04
|
|
593
593
|
},
|
|
594
594
|
inputMiss: {
|
|
595
|
-
offPeak: 1
|
|
596
|
-
peak:
|
|
595
|
+
offPeak: 1,
|
|
596
|
+
peak: 2
|
|
597
597
|
},
|
|
598
598
|
output: {
|
|
599
|
-
offPeak: 4
|
|
600
|
-
peak:
|
|
599
|
+
offPeak: 4,
|
|
600
|
+
peak: 8
|
|
601
601
|
}
|
|
602
602
|
};
|
|
603
|
-
/** deepseek-v4-pro. */
|
|
603
|
+
/** deepseek-v4-pro (DeepSeek-V4-Pro-0813); superseded by the flash row at the retirement instant. */
|
|
604
604
|
const PRO_PRICE = {
|
|
605
605
|
cacheHit: {
|
|
606
606
|
offPeak: .15,
|
|
@@ -615,6 +615,12 @@ const PRO_PRICE = {
|
|
|
615
615
|
peak: 27
|
|
616
616
|
}
|
|
617
617
|
};
|
|
618
|
+
/**
|
|
619
|
+
* The instant DeepSeek starts serving `deepseek-v4-pro` requests from
|
|
620
|
+
* V4.1-Flash and billing the flash row: 2026-09-14 12:00 Beijing (UTC+8).
|
|
621
|
+
* Drop the pro row's time fence once V4.1 Pro ships with a published row.
|
|
622
|
+
*/
|
|
623
|
+
const V4_PRO_FOLDED_INTO_FLASH_AT_MS = Date.UTC(2026, 8, 14, 4, 0);
|
|
618
624
|
function withinWindow(minuteOfDay) {
|
|
619
625
|
return PEAK_WINDOWS.find((window) => minuteOfDay >= window.from && minuteOfDay < window.to);
|
|
620
626
|
}
|
|
@@ -650,9 +656,9 @@ function deepseekPeriodAt(ms) {
|
|
|
650
656
|
boundaryMs: ms + 864e5
|
|
651
657
|
};
|
|
652
658
|
}
|
|
653
|
-
/** The price row for a model id
|
|
654
|
-
function priceFor(model) {
|
|
655
|
-
return model.includes("v4-pro") ? PRO_PRICE : FLASH_PRICE;
|
|
659
|
+
/** The price row for a model id at `atMs`; unknown ids take the flash-class row (documented estimate). */
|
|
660
|
+
function priceFor(model, atMs) {
|
|
661
|
+
return model.includes("v4-pro") && atMs < V4_PRO_FOLDED_INTO_FLASH_AT_MS ? PRO_PRICE : FLASH_PRICE;
|
|
656
662
|
}
|
|
657
663
|
/**
|
|
658
664
|
* Estimate one call's DeepSeek spend in CNY from its token totals, priced in
|
|
@@ -661,7 +667,7 @@ function priceFor(model) {
|
|
|
661
667
|
* by route family). Rounded to micro-CNY so the ledger stays readable.
|
|
662
668
|
*/
|
|
663
669
|
function deepseekModelSpend(model, totals, atMs) {
|
|
664
|
-
const price = priceFor(model);
|
|
670
|
+
const price = priceFor(model, atMs);
|
|
665
671
|
const column = deepseekPeriodAt(atMs).peak ? "peak" : "offPeak";
|
|
666
672
|
const spend = (totals.cacheReadTokens * price.cacheHit[column] + (totals.inputTokens + totals.cacheWriteTokens) * price.inputMiss[column] + totals.outputTokens * price.output[column]) / 1e6;
|
|
667
673
|
return Math.round(spend * 1e6) / 1e6;
|
|
@@ -1008,6 +1014,8 @@ var UsageService = class {
|
|
|
1008
1014
|
snapshotsPath;
|
|
1009
1015
|
ledger = createLedgerDocument();
|
|
1010
1016
|
snapshots = /* @__PURE__ */ new Map();
|
|
1017
|
+
/** The official DeepSeek family's real-spend watch (see SpendWatch). */
|
|
1018
|
+
spendWatch;
|
|
1011
1019
|
/** Per-live-session route attribution (WeakMap: disposed sessions age out). */
|
|
1012
1020
|
sessionRoutes = /* @__PURE__ */ new WeakMap();
|
|
1013
1021
|
/** The most recent route seen this boot; the pet bubble follows it. */
|
|
@@ -1083,8 +1091,10 @@ var UsageService = class {
|
|
|
1083
1091
|
});
|
|
1084
1092
|
}
|
|
1085
1093
|
providers.sort((a, b) => Number(b.supported) - Number(a.supported) || a.displayName.localeCompare(b.displayName));
|
|
1086
|
-
const
|
|
1094
|
+
const allKeys = ledgerDayKeys(this.ledger);
|
|
1095
|
+
const days = allKeys.slice(-30);
|
|
1087
1096
|
const range = summarizeDays(days.map((key) => this.ledger.days[key] ?? {}));
|
|
1097
|
+
const all = summarizeDays(allKeys.map((key) => this.ledger.days[key] ?? {}));
|
|
1088
1098
|
return {
|
|
1089
1099
|
updatedAt: Date.now(),
|
|
1090
1100
|
providers,
|
|
@@ -1102,7 +1112,17 @@ var UsageService = class {
|
|
|
1102
1112
|
to: days[days.length - 1] ?? todayKey,
|
|
1103
1113
|
totals: range.totals,
|
|
1104
1114
|
providers: range.providers
|
|
1105
|
-
}
|
|
1115
|
+
},
|
|
1116
|
+
all: {
|
|
1117
|
+
from: allKeys[0] ?? todayKey,
|
|
1118
|
+
to: allKeys[allKeys.length - 1] ?? todayKey,
|
|
1119
|
+
totals: all.totals,
|
|
1120
|
+
providers: all.providers
|
|
1121
|
+
},
|
|
1122
|
+
...this.spendWatch !== void 0 && this.spendWatch.accruedCny > 0 ? { observedSpend: {
|
|
1123
|
+
cny: this.spendWatch.accruedCny,
|
|
1124
|
+
since: this.spendWatch.since
|
|
1125
|
+
} } : {}
|
|
1106
1126
|
}
|
|
1107
1127
|
};
|
|
1108
1128
|
}
|
|
@@ -1128,6 +1148,33 @@ var UsageService = class {
|
|
|
1128
1148
|
return this.daySummary(localDateKey(Date.now())).providers.find((entry) => entry.provider === provider)?.totals ?? emptyTotals();
|
|
1129
1149
|
}
|
|
1130
1150
|
/**
|
|
1151
|
+
* Accrue the official DeepSeek family's real spend from observed CNY
|
|
1152
|
+
* balance decreases; a balance rise is a top-up and never counts. The
|
|
1153
|
+
* family's route ids can alias one account, so the watch follows the
|
|
1154
|
+
* largest balance seen this cycle and accrues only decreases of that
|
|
1155
|
+
* series — a failed probe keeps the stale balance and accrues nothing
|
|
1156
|
+
* until the next success reports the whole drop at once.
|
|
1157
|
+
*/
|
|
1158
|
+
accrueDeepSeekSpend() {
|
|
1159
|
+
let balanceCny;
|
|
1160
|
+
for (const [id, snapshot] of this.snapshots) {
|
|
1161
|
+
if (!isDeepSeekProviderRoute(id)) continue;
|
|
1162
|
+
const balance = snapshot.balance;
|
|
1163
|
+
if (balance === void 0 || balance.currency.toUpperCase() !== "CNY") continue;
|
|
1164
|
+
const value = Number(balance.totalBalance);
|
|
1165
|
+
if (!Number.isFinite(value)) continue;
|
|
1166
|
+
balanceCny = balanceCny === void 0 ? value : Math.max(balanceCny, value);
|
|
1167
|
+
}
|
|
1168
|
+
if (balanceCny === void 0) return;
|
|
1169
|
+
const watch = this.spendWatch ?? {
|
|
1170
|
+
accruedCny: 0,
|
|
1171
|
+
since: Date.now()
|
|
1172
|
+
};
|
|
1173
|
+
if (watch.lastBalanceCny !== void 0 && balanceCny < watch.lastBalanceCny) watch.accruedCny += watch.lastBalanceCny - balanceCny;
|
|
1174
|
+
watch.lastBalanceCny = balanceCny;
|
|
1175
|
+
this.spendWatch = watch;
|
|
1176
|
+
}
|
|
1177
|
+
/**
|
|
1131
1178
|
* The display name for a route key: the LLM runtime's first, then the
|
|
1132
1179
|
* adapter's, then the id itself — the snapshot may not exist for
|
|
1133
1180
|
* adapter-less routes, yet the bubble still needs a legible title.
|
|
@@ -1209,6 +1256,12 @@ var UsageService = class {
|
|
|
1209
1256
|
if (typeof parsed === "object" && parsed !== null && typeof parsed.providers === "object" && parsed.providers !== null) {
|
|
1210
1257
|
for (const [provider, snapshot] of Object.entries(parsed.providers)) if (typeof snapshot === "object" && snapshot !== null && typeof snapshot.provider === "string") this.snapshots.set(provider, snapshot);
|
|
1211
1258
|
}
|
|
1259
|
+
const watch = parsed.spendWatch;
|
|
1260
|
+
if (typeof watch === "object" && watch !== null && typeof watch.accruedCny === "number" && Number.isFinite(watch.accruedCny) && watch.accruedCny >= 0 && typeof watch.since === "number" && Number.isFinite(watch.since) && watch.since > 0) this.spendWatch = {
|
|
1261
|
+
accruedCny: watch.accruedCny,
|
|
1262
|
+
since: watch.since,
|
|
1263
|
+
...typeof watch.lastBalanceCny === "number" && Number.isFinite(watch.lastBalanceCny) ? { lastBalanceCny: watch.lastBalanceCny } : {}
|
|
1264
|
+
};
|
|
1212
1265
|
}
|
|
1213
1266
|
} catch {} finally {
|
|
1214
1267
|
this.loaded = true;
|
|
@@ -1259,7 +1312,8 @@ var UsageService = class {
|
|
|
1259
1312
|
await mkdir(dirname(this.snapshotsPath), { recursive: true });
|
|
1260
1313
|
await writeJsonAtomic(this.snapshotsPath, {
|
|
1261
1314
|
version: 1,
|
|
1262
|
-
providers: Object.fromEntries(this.snapshots)
|
|
1315
|
+
providers: Object.fromEntries(this.snapshots),
|
|
1316
|
+
...this.spendWatch !== void 0 ? { spendWatch: this.spendWatch } : {}
|
|
1263
1317
|
});
|
|
1264
1318
|
} catch {}
|
|
1265
1319
|
}
|
|
@@ -1294,6 +1348,7 @@ var UsageService = class {
|
|
|
1294
1348
|
}
|
|
1295
1349
|
for (const id of [...this.snapshots.keys()]) if (!seen.has(id)) this.snapshots.delete(id);
|
|
1296
1350
|
if (this.disposed) return;
|
|
1351
|
+
this.accrueDeepSeekSpend();
|
|
1297
1352
|
await this.persistSnapshots();
|
|
1298
1353
|
this.announceCurrent();
|
|
1299
1354
|
} finally {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The usage statistics settings section:
|
|
3
|
-
* balances, trend; 个人套餐: per-provider plan quota windows
|
|
4
|
-
*
|
|
2
|
+
* The usage statistics settings section: three tabs (用量: today's usage,
|
|
3
|
+
* balances, trend; 个人套餐: per-provider plan quota windows; Token 银行:
|
|
4
|
+
* the whale-yuan voucher minted from the DeepSeek official family's usage)
|
|
5
|
+
* plus a compact settings row. Data comes from the host's loopback-fenced
|
|
5
6
|
* /api/dsh-usage/overview document; polling runs only while the section is
|
|
6
7
|
* mounted and the tab is visible.
|
|
7
8
|
* @module @linxin666/dsh-usage/client/UsageSectionCard
|
|
@@ -10,11 +10,13 @@ export declare const zh: {
|
|
|
10
10
|
'usage.title': string;
|
|
11
11
|
'usage.tab.usage': string;
|
|
12
12
|
'usage.tab.plans': string;
|
|
13
|
+
'usage.tab.bank': string;
|
|
13
14
|
'usage.refresh': string;
|
|
14
15
|
'usage.refreshing': string;
|
|
15
16
|
'usage.updated': string;
|
|
16
17
|
'usage.loading': string;
|
|
17
18
|
'usage.error': string;
|
|
19
|
+
'usage.disabled': string;
|
|
18
20
|
'usage.current': string;
|
|
19
21
|
'usage.today': string;
|
|
20
22
|
'usage.today.cost': string;
|
|
@@ -48,6 +50,16 @@ export declare const zh: {
|
|
|
48
50
|
'usage.config.bubbleMode.always': string;
|
|
49
51
|
'usage.config.bubbleMode.change': string;
|
|
50
52
|
'usage.config.bubbleMode.off': string;
|
|
53
|
+
'usage.bank.title': string;
|
|
54
|
+
'usage.bank.hint': string;
|
|
55
|
+
'usage.bank.noUsage': string;
|
|
56
|
+
'usage.bank.minted': string;
|
|
57
|
+
'usage.bank.spend.observed': string;
|
|
58
|
+
'usage.bank.spend.estimated': string;
|
|
59
|
+
'usage.bank.window': string;
|
|
60
|
+
'usage.bank.save': string;
|
|
61
|
+
'usage.bank.share': string;
|
|
62
|
+
'usage.bank.drawError': string;
|
|
51
63
|
};
|
|
52
64
|
/** English mirror; every zh key present. */
|
|
53
65
|
export declare const en: Record<UsageKey, string>;
|