@kenz1117/dsh-ui-usage-billing 0.5.0 → 0.6.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/lib/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { readFile, stat } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
+ import { defineTool } from "@deepseek-ai/dsh-tools";
5
+ import { writeFileAtomic } from "@deepseek-ai/dsh-atomic-write";
4
6
  import { credentialRef } from "@deepseek-ai/dsh-credentials";
5
7
  /** 当前汇率:实时覆盖优先,缺省回退内置固定值。 */
6
8
  function currentRate() {
@@ -597,6 +599,28 @@ function computeCostAt(entry, buckets, timeMs, peakShare = DEFAULT_PEAK_SHARE) {
597
599
  if (timeMs === null || timeMs === void 0 || !Number.isFinite(timeMs)) return computeCost(entry, buckets, peakShare);
598
600
  return priceBandCost(tierAt(timeMs) === "peak" ? entry.price : entry.price.offPeak, buckets, entry.price.currency);
599
601
  }
602
+ /**
603
+ * Format an amount with adaptive precision and the given currency symbol.
604
+ * @param amount - the amount (CNY by default; pass `usd` for dollar display).
605
+ * @param currency - display currency; default `cny`.
606
+ */
607
+ function formatMoney(amount, currency = "cny") {
608
+ const value = Number(amount);
609
+ if (!Number.isFinite(value)) return currency === "cny" ? "¥0" : "$0";
610
+ const symbol = currency === "cny" ? "¥" : "$";
611
+ if (value <= 0) return `${symbol}0`;
612
+ if (value >= 1e3) return `${symbol}${value.toFixed(0)}`;
613
+ if (value >= 10) return `${symbol}${value.toFixed(1)}`;
614
+ if (value >= .1) return `${symbol}${value.toFixed(2)}`;
615
+ return `${symbol}${value.toFixed(3)}`;
616
+ }
617
+ /** Format a large token count with B/M/K suffix. */
618
+ function formatTokens(value) {
619
+ if (value >= 1e9) return `${(value / 1e9).toFixed(2)}B`;
620
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
621
+ if (value >= 1e3) return `${(value / 1e3).toFixed(0)}K`;
622
+ return String(value);
623
+ }
600
624
  //#endregion
601
625
  //#region lib/types/aggregate.js
602
626
  /**
@@ -675,6 +699,22 @@ function workspaceNameOf(cwd) {
675
699
  if (cwd === void 0 || cwd === "") return "—";
676
700
  return cwd.split(/[\\/]/).filter(Boolean).at(-1) ?? "—";
677
701
  }
702
+ /**
703
+ * 消息文本长度:user/tool 角色分摊输入成本的启发式依据。字符串内容取其
704
+ * 长度;内容块数组累计文本块长度;其余形状按 0 计(durable 边界收窄)。
705
+ */
706
+ function messageTextLength(message) {
707
+ if (message === null || typeof message !== "object") return 0;
708
+ const content = message.content;
709
+ if (typeof content === "string") return content.length;
710
+ if (!Array.isArray(content)) return 0;
711
+ let total = 0;
712
+ for (const block of content) {
713
+ const text = block?.text;
714
+ if (typeof text === "string") total += text.length;
715
+ }
716
+ return total;
717
+ }
678
718
  /** Get-or-create one model cell inside a usage map (avoids non-null assertions). */
679
719
  function usageCell(map, key) {
680
720
  const existing = map.get(key);
@@ -725,6 +765,12 @@ function foldSession(events, subscriptionProviders) {
725
765
  byDayModels: /* @__PURE__ */ new Map(),
726
766
  planCalls: /* @__PURE__ */ new Map(),
727
767
  turns: [],
768
+ roles: {
769
+ userChars: 0,
770
+ toolChars: 0,
771
+ inputCost: 0,
772
+ outputCost: 0
773
+ },
728
774
  lastActive: 0
729
775
  };
730
776
  let key = "other";
@@ -737,6 +783,14 @@ function foldSession(events, subscriptionProviders) {
737
783
  if (typeof title === "string" && title.length > 0) fold.title = title;
738
784
  continue;
739
785
  }
786
+ if (event.type === "user/message") {
787
+ fold.roles.userChars += messageTextLength(event.data.message);
788
+ continue;
789
+ }
790
+ if (event.type === "tool/result") {
791
+ fold.roles.toolChars += messageTextLength(event.data.message);
792
+ continue;
793
+ }
740
794
  if (event.type === "turn/start") {
741
795
  const state = turnState(turns, event.data.turn ?? -1);
742
796
  if (event.time < state.startedAt) state.startedAt = event.time;
@@ -770,12 +824,24 @@ function foldSession(events, subscriptionProviders) {
770
824
  state.output += usage.outputTokens;
771
825
  state.cacheHit += usage.cacheReadTokens ?? 0;
772
826
  state.cacheMiss += usage.inputTokens + (usage.cacheWriteTokens ?? 0);
773
- if (!subscription && MODEL_CATALOG.some((entry) => entry.key === modelKey)) state.cost += computeCostAt(modelOf(modelKey), {
774
- input: (usage.cacheReadTokens ?? 0) + usage.inputTokens + (usage.cacheWriteTokens ?? 0),
775
- cacheHit: usage.cacheReadTokens ?? 0,
776
- cacheMiss: usage.inputTokens + (usage.cacheWriteTokens ?? 0),
777
- output: usage.outputTokens
778
- }, event.time);
827
+ if (!subscription && MODEL_CATALOG.some((entry) => entry.key === modelKey)) {
828
+ const buckets = {
829
+ input: (usage.cacheReadTokens ?? 0) + usage.inputTokens + (usage.cacheWriteTokens ?? 0),
830
+ cacheHit: usage.cacheReadTokens ?? 0,
831
+ cacheMiss: usage.inputTokens + (usage.cacheWriteTokens ?? 0),
832
+ output: usage.outputTokens
833
+ };
834
+ const fullCost = computeCostAt(modelOf(modelKey), buckets, event.time);
835
+ state.cost += fullCost;
836
+ const outputCost = computeCostAt(modelOf(modelKey), {
837
+ input: 0,
838
+ cacheHit: 0,
839
+ cacheMiss: 0,
840
+ output: usage.outputTokens
841
+ }, event.time);
842
+ fold.roles.outputCost += outputCost;
843
+ fold.roles.inputCost += fullCost - outputCost;
844
+ }
779
845
  if (state.startedAt === Number.MAX_SAFE_INTEGER) state.startedAt = event.time;
780
846
  }
781
847
  fold.turns = [...turns.values()].filter((state) => state.input > 0 || state.output > 0).sort((a, b) => a.turn - b.turn).map((state) => ({
@@ -860,9 +926,19 @@ function createUsageAggregator(persistence, options = {}) {
860
926
  const sessionRows = [];
861
927
  const turnRows = [];
862
928
  const workspaceMap = /* @__PURE__ */ new Map();
929
+ const roles = {
930
+ userChars: 0,
931
+ toolChars: 0,
932
+ inputCost: 0,
933
+ outputCost: 0
934
+ };
863
935
  for (const { meta, fold } of folds) {
864
936
  const sessionId = String(meta.id);
865
937
  mergeUsageInto(total, fold.total);
938
+ roles.userChars += fold.roles.userChars;
939
+ roles.toolChars += fold.roles.toolChars;
940
+ roles.inputCost += fold.roles.inputCost;
941
+ roles.outputCost += fold.roles.outputCost;
866
942
  for (const [modelKey, cell] of fold.byModel) mergeUsageInto(usageCell(byModel, modelKey), cell);
867
943
  for (const [day, cell] of fold.byDay) mergeUsageInto(usageCell(byDay, day), cell);
868
944
  for (const [day, models] of fold.byDayModels) for (const [modelKey, cell] of models) mergeUsageInto(modelDayCell(byDayModels, day, modelKey), cell);
@@ -918,7 +994,16 @@ function createUsageAggregator(persistence, options = {}) {
918
994
  byDayModels: toModelDayRecord(byDayModels),
919
995
  bySession: sessionRows.slice(0, 100),
920
996
  byTurn: turnRows.slice(0, 200),
921
- byWorkspace: workspaces.slice(0, 100)
997
+ byWorkspace: workspaces.slice(0, 100),
998
+ byRole: (() => {
999
+ const chars = roles.userChars + roles.toolChars;
1000
+ const userShare = chars > 0 ? roles.userChars / chars : .5;
1001
+ return {
1002
+ user: roles.inputCost * userShare,
1003
+ assistant: roles.outputCost,
1004
+ tool: roles.inputCost * (1 - userShare)
1005
+ };
1006
+ })()
922
1007
  };
923
1008
  lastAt = now;
924
1009
  return lastDoc;
@@ -1676,6 +1761,8 @@ const PRICING_REFRESH_INTERVAL_MS = 360 * 60 * 1e3;
1676
1761
  const SUBSCRIPTION_CACHE_MS = 300 * 1e3;
1677
1762
  /** DeepSeek 余额查询的默认凭据引用(与 llm-deepseek 的默认引用一致)。 */
1678
1763
  const DEFAULT_BALANCE_API_KEY_ENV = "DEEPSEEK_API_KEY";
1764
+ /** 统计快照的落盘节流(毫秒):前端 30 秒轮询,快照最多每 30 秒写一次。 */
1765
+ const SNAPSHOT_INTERVAL_MS = 3e4;
1679
1766
  /** Required services: the web server, the persisted session log store, and user settings. */
1680
1767
  const inject = [
1681
1768
  "webServer",
@@ -1759,12 +1846,156 @@ async function resolveSubscriptionKeys(settings, credentials) {
1759
1846
  function apply(ctx, config = {}) {
1760
1847
  const aggregator = createUsageAggregator(ctx.sessionPersistence, { ...config.subscriptionProviders === void 0 ? {} : { subscriptionProviders: config.subscriptionProviders } });
1761
1848
  const cwd = process.cwd();
1849
+ const snapshotPath = join(homedir(), ".dsh/.dsh-usage-stats.json");
1762
1850
  const candidates = [
1763
1851
  config.statsPath,
1764
1852
  process.env.DSH_USAGE_STATS,
1765
1853
  join(cwd, ".dsh-usage-stats.json"),
1766
- join(homedir(), ".dsh/.dsh-usage-stats.json")
1854
+ snapshotPath
1767
1855
  ].filter((path) => typeof path === "string" && path.length > 0);
1856
+ let lastSnapshotAt = 0;
1857
+ const persistSnapshot = (doc) => {
1858
+ const now = Date.now();
1859
+ if (now - lastSnapshotAt < SNAPSHOT_INTERVAL_MS) return;
1860
+ lastSnapshotAt = now;
1861
+ writeFileAtomic(snapshotPath, JSON.stringify({
1862
+ ...doc,
1863
+ _writer: {
1864
+ pid: process.pid,
1865
+ at: now
1866
+ }
1867
+ }), {
1868
+ mode: 384,
1869
+ dirMode: 448
1870
+ }).catch(() => {});
1871
+ };
1872
+ (async () => {
1873
+ try {
1874
+ const text = await readFile(snapshotPath, "utf8");
1875
+ const writer = JSON.parse(text)._writer;
1876
+ if (writer?.pid !== void 0 && writer.pid !== process.pid && writer.at !== void 0 && Date.now() - writer.at < 6e4) console.warn(`[usage-billing] 检测到另一实例(pid ${writer.pid})正在提供用量统计,双实例可能导致提醒重复。`);
1877
+ } catch {}
1878
+ })();
1879
+ ctx.inject(["tools"], (toolsCtx) => {
1880
+ toolsCtx.tools.register(defineTool({
1881
+ name: "usage_stats",
1882
+ description: "查询本机 DeepSeek Harness 的模型用量与估算费用(人民币,按官方目录价估算,非账单)。range 取值:today=今天,month=本月,session=当前会话,all=累计。",
1883
+ parameters: { range: {
1884
+ type: "string",
1885
+ enum: [
1886
+ "today",
1887
+ "month",
1888
+ "session",
1889
+ "all"
1890
+ ],
1891
+ required: true,
1892
+ description: "统计范围:today / month / session / all"
1893
+ } },
1894
+ output: {
1895
+ schema: {
1896
+ type: "object",
1897
+ additionalProperties: false,
1898
+ properties: {
1899
+ range: {
1900
+ type: "string",
1901
+ required: true
1902
+ },
1903
+ cost: {
1904
+ type: "number",
1905
+ required: true,
1906
+ description: "估算费用(人民币元)"
1907
+ },
1908
+ calls: {
1909
+ type: "number",
1910
+ required: true
1911
+ },
1912
+ input: {
1913
+ type: "number",
1914
+ required: true,
1915
+ description: "输入 tokens"
1916
+ },
1917
+ output: {
1918
+ type: "number",
1919
+ required: true,
1920
+ description: "输出 tokens"
1921
+ }
1922
+ }
1923
+ },
1924
+ render: (_args, value) => [{
1925
+ type: "text",
1926
+ text: `用量(${value.range}):估算费用 ${formatMoney(value.cost)},调用 ${value.calls} 次,输入 ${formatTokens(value.input)} tokens,输出 ${formatTokens(value.output)} tokens`
1927
+ }]
1928
+ },
1929
+ async execute(args, exec) {
1930
+ const stats = await aggregator.aggregate();
1931
+ const zero = {
1932
+ range: args.range,
1933
+ cost: 0,
1934
+ calls: 0,
1935
+ input: 0,
1936
+ output: 0
1937
+ };
1938
+ if (args.range === "all") return {
1939
+ range: args.range,
1940
+ cost: stats.total.cost,
1941
+ calls: stats.total.calls,
1942
+ input: stats.total.input,
1943
+ output: stats.total.output
1944
+ };
1945
+ if (args.range === "today") {
1946
+ const day = stats.byDay[dayStamp(Date.now())];
1947
+ return day === void 0 ? zero : {
1948
+ range: args.range,
1949
+ cost: day.cost,
1950
+ calls: day.calls,
1951
+ input: day.input,
1952
+ output: day.output
1953
+ };
1954
+ }
1955
+ if (args.range === "month") {
1956
+ const prefix = dayStamp(Date.now()).slice(0, 7);
1957
+ let cost = 0;
1958
+ let calls = 0;
1959
+ let input = 0;
1960
+ let output = 0;
1961
+ for (const [date, day] of Object.entries(stats.byDay)) {
1962
+ if (!date.startsWith(prefix)) continue;
1963
+ cost += day.cost;
1964
+ calls += day.calls;
1965
+ input += day.input;
1966
+ output += day.output;
1967
+ }
1968
+ return {
1969
+ range: args.range,
1970
+ cost,
1971
+ calls,
1972
+ input,
1973
+ output
1974
+ };
1975
+ }
1976
+ const sessionId = exec.agent?.id;
1977
+ if (sessionId === void 0) throw new Error("usage_stats 的 session 范围需要 agent 会话上下文");
1978
+ let cost = 0;
1979
+ let calls = 0;
1980
+ let input = 0;
1981
+ let output = 0;
1982
+ for (const turn of stats.byTurn ?? []) {
1983
+ if (turn.sessionId !== String(sessionId)) continue;
1984
+ cost += turn.cost;
1985
+ calls += 1;
1986
+ input += turn.input;
1987
+ output += turn.output;
1988
+ }
1989
+ return {
1990
+ range: args.range,
1991
+ cost,
1992
+ calls,
1993
+ input,
1994
+ output
1995
+ };
1996
+ }
1997
+ }));
1998
+ });
1768
1999
  let live = { source: "builtin" };
1769
2000
  const refreshPricing = async () => {
1770
2001
  live = await fetchLivePricing();
@@ -1838,10 +2069,12 @@ function apply(ctx, config = {}) {
1838
2069
  ...config.monthlyBudget === void 0 ? {} : { budget: config.monthlyBudget },
1839
2070
  ...config.lowBalanceThreshold === void 0 ? {} : { lowBalanceThreshold: config.lowBalanceThreshold }
1840
2071
  };
1841
- res.end(JSON.stringify(Object.keys(injected).length === 0 ? stats : {
2072
+ const payload = Object.keys(injected).length === 0 ? stats : {
1842
2073
  ...stats,
1843
2074
  ...injected
1844
- }));
2075
+ };
2076
+ persistSnapshot(payload);
2077
+ res.end(JSON.stringify(payload));
1845
2078
  return;
1846
2079
  } catch {}
1847
2080
  for (const candidate of candidates) try {
@@ -79,6 +79,18 @@ export interface UsageStatsDocument {
79
79
  byTurn?: TurnUsageRow[];
80
80
  /** 工作区聚合:按 cwd 末级目录归并,按费用倒序;旧快照可能缺失。 */
81
81
  byWorkspace?: WorkspaceUsageRow[];
82
+ /**
83
+ * 按角色费用归因(人民币元):助手输出成本为实测计价;输入成本按会话内
84
+ * 用户消息 / 工具结果的文本长度占比启发式摊分(日志无角色级 token 实测,
85
+ * 属估算口径,UI 需标注)。旧快照可能缺失。
86
+ */
87
+ byRole?: RoleCost;
88
+ }
89
+ /** 按角色费用归因:user / tool 为输入成本的启发式摊分,assistant 为输出成本实测。 */
90
+ export interface RoleCost {
91
+ user: number;
92
+ assistant: number;
93
+ tool: number;
82
94
  }
83
95
  /** 会话明细行:仪表盘「会话明细」面板的数据源。 */
84
96
  export interface SessionUsageRow {
@@ -141,11 +153,27 @@ interface SessionFold {
141
153
  planCalls: Map<string, number>;
142
154
  /** 每轮费用明细(按轮次号升序,不含 sessionId);sessionId 在合并时补齐。 */
143
155
  turns: SessionTurnRow[];
156
+ /** 角色归因中间量:消息文本长度(user/tool)与输入/输出成本实测拆分。 */
157
+ roles: RoleFold;
144
158
  /** 日志里最新的 session/title 文本(无标题事件时 undefined)。 */
145
159
  title?: string;
146
160
  /** 最后一个事件的时间戳(毫秒);空日志为 0。 */
147
161
  lastActive: number;
148
162
  }
163
+ /** 角色归因的会话级中间量:字符占比用于把输入成本摊到 user/tool。 */
164
+ interface RoleFold {
165
+ userChars: number;
166
+ toolChars: number;
167
+ /** 输入侧成本(缓存命中 + 未命中 + 缓存写入,人民币元)。 */
168
+ inputCost: number;
169
+ /** 输出侧成本(人民币元)。 */
170
+ outputCost: number;
171
+ }
172
+ /**
173
+ * 消息文本长度:user/tool 角色分摊输入成本的启发式依据。字符串内容取其
174
+ * 长度;内容块数组累计文本块长度;其余形状按 0 计(durable 边界收窄)。
175
+ */
176
+ export declare function messageTextLength(message: unknown): number;
149
177
  /**
150
178
  * Fold one session's events into a {@link SessionFold}. 每个 LLM 调用归属到
151
179
  * 其前置 request/header 记录的模型;同时提取最新会话标题、最后活跃时间,
@@ -32,7 +32,7 @@ export interface ModelHealth {
32
32
  export type DashboardTab = 'overview' | 'trends' | 'providers' | 'details' | 'pricing';
33
33
  /**
34
34
  * Tab 定义(顺序即渲染顺序):概览=主数字/预算/KPI/热力图,趋势=趋势图/每轮费用,
35
- * 厂商=厂商计费与订阅,明细=工作区/会话明细,单价=模型单价表。导出供测试断言
35
+ * 明细=厂商计费与订阅,统计=工作区/会话明细,费率=模型单价表。导出供测试断言
36
36
  * tab 与文案 key 对齐、decor 锚点落在正确分区。
37
37
  */
38
38
  export declare const DASHBOARD_TABS: readonly {
@@ -67,6 +67,31 @@ export declare function providerFromModelKey(modelKey: string): string | undefin
67
67
  export declare function projectMonthCost(byDay: Record<string, {
68
68
  cost: number;
69
69
  }>, monthPrefix: string, today: string): number;
70
+ /**
71
+ * 峰谷时段费用分摊:按每轮的起始时刻(北京时间高峰 9-12 / 14-18)把费用
72
+ * 归入高峰 / 空闲两档。导出供测试:纯函数。
73
+ * @param turns - 每轮费用行(需带 startedAt 与 cost)。
74
+ * @returns 两档费用合计(人民币元)。
75
+ */
76
+ export declare function peakOffpeakCost(turns: readonly {
77
+ startedAt: number;
78
+ cost: number;
79
+ }[]): {
80
+ peak: number;
81
+ offPeak: number;
82
+ };
83
+ /**
84
+ * 近 7 天费用序列(含今天,缺日补 0):触发卡 hover 速览的迷你柱数据源。
85
+ * 导出供测试:纯函数(日期取本地时区)。
86
+ * @param byDay - 按日费用表。
87
+ * @returns 7 个 `{ date, cost }`,最旧在前。
88
+ */
89
+ export declare function lastSevenDays(byDay: Record<string, {
90
+ cost: number;
91
+ }>): readonly {
92
+ date: string;
93
+ cost: number;
94
+ }[];
70
95
  /** 组件注入面:探活 + 计费指标写入(billing 自身写入,主题插件经服务读取)。 */
71
96
  export interface UsageBillingInjected {
72
97
  checkModels: () => Promise<ModelHealth>;
@@ -12,8 +12,8 @@ export interface BudgetPrefsState {
12
12
  enabled: boolean;
13
13
  /** 用户设置的月度预算(人民币元);0 = 未设置(回退到宿主默认值)。 */
14
14
  amount: number;
15
- /** 最近一次超支通知的日期戳(YYYY-MM-DD):超支通知每天最多一次,跨重启生效。 */
16
- lastAlertDay: string;
15
+ /** 各档提醒的最后通知日期戳(档位百分比字符串 → YYYY-MM-DD):每档每天最多一次。 */
16
+ tierAlertDays: Record<string, string>;
17
17
  /** 最近一次余额不足通知的日期戳(YYYY-MM-DD):余额告警同样每天最多一次。 */
18
18
  lastBalanceAlertDay: string;
19
19
  }
@@ -21,7 +21,7 @@ export interface BudgetPrefsState {
21
21
  export type BudgetPrefsActions = {
22
22
  setEnabled: (d: BudgetPrefsState, on: boolean) => void;
23
23
  setAmount: (d: BudgetPrefsState, value: number) => void;
24
- markAlerted: (d: BudgetPrefsState, day: string) => void;
24
+ markTierAlerted: (d: BudgetPrefsState, tiers: readonly number[], day: string) => void;
25
25
  markBalanceAlerted: (d: BudgetPrefsState, day: string) => void;
26
26
  };
27
27
  /**
@@ -0,0 +1,33 @@
1
+ /**
2
+ * 数据导出:把用量统计导出为 CSV / JSON 供对账。
3
+ *
4
+ * 纯函数生成文本(按日 / 按会话两个视角),DOM 下载副作用单独隔离在
5
+ * `downloadText`;金额一律人民币元(与聚合口径一致),文件名带日期范围。
6
+ */
7
+ /** 按日聚合行(与 UsageStatsDocument.byDay 的行同形)。 */
8
+ export interface DayExportRow {
9
+ calls: number;
10
+ input: number;
11
+ output: number;
12
+ cacheHit: number;
13
+ cacheMiss: number;
14
+ cost: number;
15
+ }
16
+ /** 会话导出行(与 SessionUsageRow 同形,结构化声明避免跨文件引用)。 */
17
+ export interface SessionExportRow {
18
+ id: string;
19
+ title?: string;
20
+ cwd?: string;
21
+ calls: number;
22
+ cost: number;
23
+ lastActive: number;
24
+ }
25
+ /** 按日 CSV:日期,调用,输入,输出,缓存命中,缓存未命中,费用(元)。 */
26
+ export declare function dayRowsCsv(byDay: Record<string, DayExportRow>): string;
27
+ /** 按会话 CSV:会话 id,标题,项目,调用,费用(元),最后活跃(ISO)。 */
28
+ export declare function sessionRowsCsv(rows: readonly SessionExportRow[]): string;
29
+ /** 导出文件名:带日期范围(usage-2026-08-01_2026-08-22.csv);无日期时只带前缀。 */
30
+ export declare function exportFileName(prefix: string, ext: 'csv' | 'json', dates: readonly string[]): string;
31
+ /** 触发浏览器下载(唯一 DOM 副作用;调用方在 click 手势里使用)。 */
32
+ export declare function downloadText(filename: string, text: string, mime: string): void;
33
+ //# sourceMappingURL=export.d.ts.map
@@ -1,5 +1,5 @@
1
1
  /** Locale dictionaries for the usage billing surface. */
2
- export type UsageBillingKey = 'billing.title' | 'billing.subtitle' | 'billing.cost' | 'billing.todayCost' | 'billing.monthCost' | 'billing.yearCost' | 'billing.monthProjected' | 'billing.liveTurn' | 'billing.liveSession' | 'billing.totalCost' | 'billing.calls' | 'billing.cacheHitRate' | 'billing.tokens' | 'billing.inputTokens' | 'billing.outputTokens' | 'billing.avgCost' | 'billing.trend' | 'billing.trend7d' | 'billing.trend30d' | 'billing.trendEmpty' | 'billing.budget' | 'billing.sessions' | 'billing.project' | 'billing.lastActive' | 'billing.sessionOverflow' | 'billing.budgetOverBody' | 'billing.models' | 'billing.providerBilling' | 'billing.estimated' | 'billing.actual' | 'billing.pricing' | 'billing.showPricing' | 'billing.hidePricing' | 'billing.pricePerM' | 'billing.input' | 'billing.output' | 'billing.cacheHit' | 'billing.peak' | 'billing.offPeak' | 'billing.flat' | 'billing.peakHours' | 'billing.band' | 'billing.openDashboard' | 'billing.close' | 'billing.lastUpdated' | 'billing.noData' | 'billing.todayRate' | 'billing.rateLive' | 'billing.rateBuiltin' | 'billing.balance' | 'billing.balanceUnconfigured' | 'billing.balanceUnauthorized' | 'billing.balanceUnreachable' | 'billing.uncatalogued' | 'billing.estimatedPricing' | 'billing.balanceDays' | 'billing.balanceLowBody' | 'billing.subscriptions' | 'billing.subscriptionNotConfigured' | 'billing.subscriptionUnauthorized' | 'billing.subscriptionUnavailable' | 'billing.subscriptionInvalid' | 'billing.subscriptionRateLimited' | 'billing.subscriptionSession' | 'billing.subscriptionWeekly' | 'billing.subscriptionMonthly' | 'billing.subscriptionBilling' | 'billing.subscriptionRemaining' | 'billing.subscriptionReset' | 'billing.subscriptionNoApi' | 'billing.heatmapLess' | 'billing.heatmapMore' | 'billing.currency' | 'billing.currencyCny' | 'billing.currencyUsd' | 'billing.heatmap' | 'billing.rounds' | 'billing.anomaly' | 'billing.workspaces' | 'billing.plan' | 'billing.remaining' | 'billing.unknownModel' | 'billing.model' | 'billing.currentRound' | 'billing.costAbbr' | 'billing.tabOverview' | 'billing.tabTrends' | 'billing.tabProviders' | 'billing.tabDetails' | 'billing.tabPricing';
2
+ export type UsageBillingKey = 'billing.title' | 'billing.subtitle' | 'billing.cost' | 'billing.todayCost' | 'billing.monthCost' | 'billing.yearCost' | 'billing.monthProjected' | 'billing.liveTurn' | 'billing.liveSession' | 'billing.totalCost' | 'billing.calls' | 'billing.cacheHitRate' | 'billing.tokens' | 'billing.inputTokens' | 'billing.outputTokens' | 'billing.avgCost' | 'billing.trend' | 'billing.trend7d' | 'billing.trend30d' | 'billing.trendEmpty' | 'billing.budget' | 'billing.sessions' | 'billing.project' | 'billing.lastActive' | 'billing.sessionOverflow' | 'billing.budgetTierBody' | 'billing.models' | 'billing.providerBilling' | 'billing.estimated' | 'billing.actual' | 'billing.pricing' | 'billing.showPricing' | 'billing.hidePricing' | 'billing.pricePerM' | 'billing.input' | 'billing.output' | 'billing.cacheHit' | 'billing.peak' | 'billing.offPeak' | 'billing.flat' | 'billing.peakHours' | 'billing.band' | 'billing.openDashboard' | 'billing.close' | 'billing.lastUpdated' | 'billing.noData' | 'billing.todayRate' | 'billing.rateLive' | 'billing.rateBuiltin' | 'billing.balance' | 'billing.balanceUnconfigured' | 'billing.balanceUnauthorized' | 'billing.balanceUnreachable' | 'billing.uncatalogued' | 'billing.estimatedPricing' | 'billing.balanceDays' | 'billing.balanceLowBody' | 'billing.subscriptions' | 'billing.subscriptionNotConfigured' | 'billing.subscriptionUnauthorized' | 'billing.subscriptionUnavailable' | 'billing.subscriptionInvalid' | 'billing.subscriptionRateLimited' | 'billing.subscriptionSession' | 'billing.subscriptionWeekly' | 'billing.subscriptionMonthly' | 'billing.subscriptionBilling' | 'billing.subscriptionRemaining' | 'billing.subscriptionReset' | 'billing.subscriptionNoApi' | 'billing.heatmapLess' | 'billing.heatmapMore' | 'billing.currency' | 'billing.currencyCny' | 'billing.currencyUsd' | 'billing.heatmap' | 'billing.rounds' | 'billing.anomaly' | 'billing.workspaces' | 'billing.plan' | 'billing.remaining' | 'billing.unknownModel' | 'billing.model' | 'billing.currentRound' | 'billing.costAbbr' | 'billing.tabOverview' | 'billing.tabTrends' | 'billing.tabProviders' | 'billing.tabDetails' | 'billing.tabPricing' | 'billing.export' | 'billing.exportCsvDay' | 'billing.exportCsvSession' | 'billing.exportJson' | 'billing.peakShare' | 'billing.peakShareHint' | 'billing.weekCost' | 'billing.roleCost' | 'billing.roleUser' | 'billing.roleAssistant' | 'billing.roleTool' | 'billing.roleHint';
3
3
  export declare const NS = "usageBilling";
4
4
  export declare const zh: Record<UsageBillingKey, string>;
5
5
  export declare const en: Record<UsageBillingKey, 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.5.0",
4
+ "version": "0.6.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -48,6 +48,7 @@
48
48
  "license": "MIT",
49
49
  "peerDependencies": {
50
50
  "@deepseek-ai/dsh-api-remotes": "*",
51
+ "@deepseek-ai/dsh-atomic-write": "*",
51
52
  "@deepseek-ai/dsh-client-connection": "*",
52
53
  "@deepseek-ai/dsh-host-webserver": "*",
53
54
  "@deepseek-ai/dsh-client-locale": "*",
@@ -62,6 +63,7 @@
62
63
  "@deepseek-ai/dsh-session": "*",
63
64
  "@deepseek-ai/dsh-session-persistence": "*",
64
65
  "@deepseek-ai/dsh-settings": "*",
66
+ "@deepseek-ai/dsh-tools": "*",
65
67
  "@deepseek-ai/schemastery": "*",
66
68
  "@deepseek-ai/cordis": "*",
67
69
  "react": "^18.2.0"