@kenz1117/dsh-ui-usage-billing 0.4.0 → 0.4.2

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 CHANGED
@@ -6,6 +6,7 @@ DeepSeek Harness 计费仪表盘插件。从持久化会话日志实时聚合模
6
6
  [![GitHub stars](https://img.shields.io/github/stars/kenz1117/dsh-ui-usage-billing)](https://github.com/kenz1117/dsh-ui-usage-billing)
7
7
  [![GitHub last commit](https://img.shields.io/github/last-commit/kenz1117/dsh-ui-usage-billing)](https://github.com/kenz1117/dsh-ui-usage-billing)
8
8
  [![npm version](https://img.shields.io/npm/v/@kenz1117/dsh-ui-usage-billing)](https://www.npmjs.com/package/@kenz1117/dsh-ui-usage-billing)
9
+ [![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
9
10
 
10
11
  ## 特性
11
12
 
@@ -17,7 +18,7 @@ DeepSeek Harness 计费仪表盘插件。从持久化会话日志实时聚合模
17
18
  - **真实用量**:服务端从会话日志实时聚合,无需手工维护统计文件;增量缓存保证只有写过的日志会被重新折叠,重度使用下轮询依然轻量。
18
19
  - **模型健康探测**:模型行的圆点反映各厂商接入状态(正常绿 / 异常红 / 未接入灰)。
19
20
  - **订阅计划豁免**:走 coding / token plan / opencode 订阅通道的模型照常统计 token、费用记 0(按**通道**判定,同一模型走按量通道时正常计费)。
20
- - **余额查询**:厂商组头部显示该厂商余额(只显示一次,不再随每行重复),DeepSeek 通过官方余额 API 实时查询;未配置 / Key 无效 / 服务不可达均有状态提示,扩展点可接更多厂商。
21
+ - **余额查询**:厂商组头部显示该厂商余额(只显示一次,不再随每行重复)。DeepSeek、月之暗面(Kimi)、阶跃星辰(StepFun)用官方余额接口**实时查询**;API Key 复用 `llm-pi-ai` 设置的 `apiKeyEnv`(DeepSeek 另有 `balanceApiKeyEnv` 特例),未配置 / Key 无效 / 服务不可达均有状态提示,扩展点可接更多厂商。
21
22
  - **可用天数估算**:余额列按最近 7 天日均消耗折算「约可撑 N 天」,剩余不足 3 天红色强调。
22
23
  - **余额不足告警**:任一厂商余额(折算人民币)低于阈值时每天弹一次系统通知;阈值经宿主配置 `lowBalanceThreshold`(人民币元)下发,未配置默认 50 元。
23
24
  - **未收录模型标注**:真实模型 id 不在计费目录时,明细行显示真实 id 并标注「未收录」,费用按兜底档估算;厂商可从模型 id 自动推断(如 `mi-mimo-2.5` → 小米),健康圆点据此点亮。
@@ -145,7 +146,7 @@ cost(CNY)= (missInput × p_input + cacheHit × p_cacheHit + output × p_outp
145
146
 
146
147
  ### `GET /api/billing/balance`
147
148
 
148
- 各接入厂商账户余额(凭据 seam `balanceApiKeyEnv` key):
149
+ 各接入厂商账户余额。API Key 复用 `llm-pi-ai` 设置的 `providers.<id>.apiKeyEnv`(DeepSeek 未在 llm-pi-ai 配置时回退到 `balanceApiKeyEnv`):
149
150
 
150
151
  ```json
151
152
  {
@@ -153,11 +154,24 @@ cost(CNY)= (missInput × p_input + cacheHit × p_cacheHit + output × p_outp
153
154
  {
154
155
  "provider": "deepseek",
155
156
  "displayName": "DeepSeek",
156
- "ok": true,
157
157
  "currency": "CNY",
158
- "total": 12.34,
159
- "available": 10.56,
160
- "granted": 1.78
158
+ "totalBalance": 12.34
159
+ },
160
+ {
161
+ "provider": "月之暗面",
162
+ "displayName": "月之暗面",
163
+ "currency": "CNY",
164
+ "totalBalance": 49.59,
165
+ "grantedBalance": 46.59,
166
+ "toppedUpBalance": 3.0
167
+ },
168
+ {
169
+ "provider": "阶跃星辰",
170
+ "displayName": "阶跃星辰",
171
+ "currency": "CNY",
172
+ "totalBalance": 150.0,
173
+ "toppedUpBalance": 200.0,
174
+ "grantedBalance": 50.0
161
175
  }
162
176
  ]
163
177
  }
@@ -200,7 +214,7 @@ cost(CNY)= (missInput × p_input + cacheHit × p_cacheHit + output × p_outp
200
214
  | 字段 | 默认 | 说明 |
201
215
  | ----------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
202
216
  | `statsPath` | 未设置 | 回退统计文件 `.dsh-usage-stats.json` 的绝对路径(`sessionPersistence` 不可用时生效) |
203
- | `balanceApiKeyEnv` | `DEEPSEEK_API_KEY` | 余额查询使用的 DeepSeek 凭据引用(环境变量名),经 `ctx.credentials` 解析 |
217
+ | `balanceApiKeyEnv` | `DEEPSEEK_API_KEY` | DeepSeek 余额查询的凭据引用;仅在 llm-pi-ai 未配置 deepseek 的 `apiKeyEnv` 时兜底使用 |
204
218
  | `subscriptionProviders` | `kimi-coding`、`xiaomi-token-plan-cn` | 订阅制(coding / token 套餐)provider id 列表,照常统计 token、费用记 0 |
205
219
  | `monthlyBudget` | 未设置 | 月度预算默认金额(人民币元);随 usage-stats 下发,作为仪表盘预算条的初始金额(用户在界面上的设置优先并本地持久化) |
206
220
  | `lowBalanceThreshold` | `50` | 余额不足告警阈值(人民币元);随 usage-stats 下发,任一厂商余额折算人民币低于此值时每天提醒一次 |
@@ -236,7 +250,7 @@ npm publish --access public
236
250
 
237
251
  ## Known Limitations and Deferred Work
238
252
 
239
- - **余额查询仅接入 DeepSeek**:其他厂商一律显示「未配置」;扩展点在 `src/balance.ts`(按厂商余额 API 增加查询器)。
253
+ - **余额查询已接入 DeepSeek / 月之暗面(Kimi)/ 阶跃星辰(StepFun)**:这三家用标准 Bearer API key 即可查询。其余厂商因无公开余额接口或需非 Bearer 鉴权(小米 MiMo 走控制台 Cookie、商汤走 AccessKey 签名、MiniMax/字节豆包走额度制或 AK/SK),暂显示「未配置」;扩展点在 `src/balance.ts`(按厂商余额 API 增加查询器)。
240
254
  - **超支通知依赖浏览器 Notification**:权限被拒绝或平台不支持时只有界面红色脉冲兜底,没有宿主级通知通道;通知上限为每天一次。
241
255
  - **会话明细不可跳转**:点击会话行不会打开对应会话(跨插件导航需要宿主会话选择通道);会话数封顶 100 行、面板只显示前 20 行。
242
256
  - **费用为目录价估算**:讯飞 / 商汤 / 小米等未公布按量单价的模型使用估算价(特性表脚注 ¹),正式定价以厂商账单为准。
package/lib/client.js CHANGED
@@ -34,160 +34,160 @@ window.__ModuleLoader__.load({
34
34
  document.head.appendChild(tag);
35
35
  }
36
36
  var UsageBilling_module_css_default = {
37
- "providerGroupBalance": "VWh0dG_providerGroupBalance",
38
- "chartCrosshair": "VWh0dG_chartCrosshair",
39
- "panelHead": "VWh0dG_panelHead",
40
- "triggerAmountSub": "VWh0dG_triggerAmountSub",
41
- "heatmapCellEmpty": "VWh0dG_heatmapCellEmpty",
42
- "subscriptionTrack": "VWh0dG_subscriptionTrack",
43
- "numCol": "VWh0dG_numCol",
44
- "rangeButton": "VWh0dG_rangeButton",
37
+ "rateBadge": "VWh0dG_rateBadge",
38
+ "balanceCell": "VWh0dG_balanceCell",
45
39
  "healthOk": "VWh0dG_healthOk",
46
- "chartLegendLine": "VWh0dG_chartLegendLine",
40
+ "railButton": "VWh0dG_railButton",
41
+ "trendPanel": "VWh0dG_trendPanel",
42
+ "pricingChevron": "VWh0dG_pricingChevron",
43
+ "dashboardTitle": "VWh0dG_dashboardTitle",
44
+ "modelProvider": "VWh0dG_modelProvider",
45
+ "bandPrice": "VWh0dG_bandPrice",
46
+ "triggerToday": "VWh0dG_triggerToday",
47
+ "panelTitle": "VWh0dG_panelTitle",
48
+ "modelName": "VWh0dG_modelName",
49
+ "triggerDivider": "VWh0dG_triggerDivider",
50
+ "kpiGreen": "VWh0dG_kpiGreen",
47
51
  "switchOn": "VWh0dG_switchOn",
52
+ "chartEmpty": "VWh0dG_chartEmpty",
53
+ "subscriptionReset": "VWh0dG_subscriptionReset",
54
+ "costCol": "VWh0dG_costCol",
55
+ "budgetLabel": "VWh0dG_budgetLabel",
56
+ "roundsFlagBadge": "VWh0dG_roundsFlagBadge",
57
+ "budgetUnit": "VWh0dG_budgetUnit",
58
+ "budgetInput": "VWh0dG_budgetInput",
59
+ "subscriptionWindow": "VWh0dG_subscriptionWindow",
60
+ "heroSideItem": "VWh0dG_heroSideItem",
61
+ "subscriptionHead": "VWh0dG_subscriptionHead",
62
+ "roundsBarFlagged": "VWh0dG_roundsBarFlagged",
63
+ "currencyButton": "VWh0dG_currencyButton",
48
64
  "rangeButtonActive": "VWh0dG_rangeButtonActive",
49
- "rateBadgeBuiltin": "VWh0dG_rateBadgeBuiltin",
50
- "flatTag": "VWh0dG_flatTag",
51
- "balanceDays": "VWh0dG_balanceDays",
52
- "triggerMeta": "VWh0dG_triggerMeta",
53
- "kpiLabel": "VWh0dG_kpiLabel",
54
- "bandPriceOff": "VWh0dG_bandPriceOff",
55
- "triggerAmount": "VWh0dG_triggerAmount",
56
- "budgetFillOver": "VWh0dG_budgetFillOver",
57
- "bandTagOff": "VWh0dG_bandTagOff",
58
- "subscriptionPlan": "VWh0dG_subscriptionPlan",
59
- "rounds": "VWh0dG_rounds",
60
- "dashboardHead": "VWh0dG_dashboardHead",
65
+ "heatmapCell": "VWh0dG_heatmapCell",
61
66
  "chartSvg": "VWh0dG_chartSvg",
62
- "pricingTable": "VWh0dG_pricingTable",
67
+ "roundsBarWrap": "VWh0dG_roundsBarWrap",
68
+ "providerGroupTitle": "VWh0dG_providerGroupTitle",
69
+ "chartTooltipSwatch": "VWh0dG_chartTooltipSwatch",
70
+ "budgetFill": "VWh0dG_budgetFill",
71
+ "chartStack": "VWh0dG_chartStack",
72
+ "currencyToggle": "VWh0dG_currencyToggle",
73
+ "subscriptionGrid": "VWh0dG_subscriptionGrid",
63
74
  "pricingToggle": "VWh0dG_pricingToggle",
75
+ "chartLegendBar": "VWh0dG_chartLegendBar",
76
+ "balanceDaysLow": "VWh0dG_balanceDaysLow",
64
77
  "modelTable": "VWh0dG_modelTable",
65
- "kpiTile": "VWh0dG_kpiTile",
66
- "budget": "VWh0dG_budget",
78
+ "dashboardBody": "VWh0dG_dashboardBody",
79
+ "roundsAxis": "VWh0dG_roundsAxis",
80
+ "dashboardSubtitle": "VWh0dG_dashboardSubtitle",
81
+ "trigger": "VWh0dG_trigger",
82
+ "chartBar": "VWh0dG_chartBar",
83
+ "chartLine": "VWh0dG_chartLine",
84
+ "roundsFlagMark": "VWh0dG_roundsFlagMark",
85
+ "kpiGrid": "VWh0dG_kpiGrid",
86
+ "modelTableScroll": "VWh0dG_modelTableScroll",
87
+ "bandTagOff": "VWh0dG_bandTagOff",
88
+ "closeButton": "VWh0dG_closeButton",
89
+ "headTitleRow": "VWh0dG_headTitleRow",
90
+ "chartWrap": "VWh0dG_chartWrap",
91
+ "chartGrid": "VWh0dG_chartGrid",
92
+ "rateBadgeBuiltin": "VWh0dG_rateBadgeBuiltin",
93
+ "uncataloguedTag": "VWh0dG_uncataloguedTag",
94
+ "subscriptionPct": "VWh0dG_subscriptionPct",
67
95
  "hero": "VWh0dG_hero",
68
- "subscriptionStatus": "VWh0dG_subscriptionStatus",
69
- "dashboardRight": "VWh0dG_dashboardRight",
70
- "kpiGreen": "VWh0dG_kpiGreen",
96
+ "providerGroupBalanceLabel": "VWh0dG_providerGroupBalanceLabel",
97
+ "heroMeta": "VWh0dG_heroMeta",
98
+ "balanceDays": "VWh0dG_balanceDays",
71
99
  "emptyRow": "VWh0dG_emptyRow",
100
+ "subscriptionWindowLabel": "VWh0dG_subscriptionWindowLabel",
101
+ "subscriptionTrack": "VWh0dG_subscriptionTrack",
102
+ "panel": "VWh0dG_panel",
72
103
  "heatmapLegend": "VWh0dG_heatmapLegend",
73
- "chartAxisLabel": "VWh0dG_chartAxisLabel",
74
- "providerGroupBalanceLabel": "VWh0dG_providerGroupBalanceLabel",
75
- "budgetValue": "VWh0dG_budgetValue",
76
- "deltaDown": "VWh0dG_deltaDown",
77
- "currencyButton": "VWh0dG_currencyButton",
78
- "pricingChevron": "VWh0dG_pricingChevron",
79
- "rangeToggle": "VWh0dG_rangeToggle",
80
- "budgetLabel": "VWh0dG_budgetLabel",
81
- "budgetOverPulse": "VWh0dG_budgetOverPulse",
82
- "pricingChevronOpen": "VWh0dG_pricingChevronOpen",
83
- "heatmapFooter": "VWh0dG_heatmapFooter",
84
- "triggerMonth": "VWh0dG_triggerMonth",
85
- "heroMain": "VWh0dG_heroMain",
104
+ "heroSide": "VWh0dG_heroSide",
105
+ "roundsBarLabel": "VWh0dG_roundsBarLabel",
106
+ "budgetFillOver": "VWh0dG_budgetFillOver",
86
107
  "budgetInputWrap": "VWh0dG_budgetInputWrap",
87
- "modelProvider": "VWh0dG_modelProvider",
88
- "dashboardTitle": "VWh0dG_dashboardTitle",
89
- "kpiDetail": "VWh0dG_kpiDetail",
90
- "chartTooltipRow": "VWh0dG_chartTooltipRow",
91
- "currencyButtonActive": "VWh0dG_currencyButtonActive",
92
- "closeButton": "VWh0dG_closeButton",
93
- "deltaUp": "VWh0dG_deltaUp",
94
- "chartTooltip": "VWh0dG_chartTooltip",
95
- "subscriptionHead": "VWh0dG_subscriptionHead",
96
- "roundsBarWrap": "VWh0dG_roundsBarWrap",
97
- "costCol": "VWh0dG_costCol",
98
- "healthDot": "VWh0dG_healthDot",
99
- "headTitleRow": "VWh0dG_headTitleRow",
100
- "roundsBarCol": "VWh0dG_roundsBarCol",
101
- "healthBadgeBad": "VWh0dG_healthBadgeBad",
102
- "chartEmpty": "VWh0dG_chartEmpty",
108
+ "triggerMonth": "VWh0dG_triggerMonth",
109
+ "rateBadgeLive": "VWh0dG_rateBadgeLive",
110
+ "heatmapHover": "VWh0dG_heatmapHover",
111
+ "healthBadgeOk": "VWh0dG_healthBadgeOk",
112
+ "healthBad": "VWh0dG_healthBad",
113
+ "bandTag": "VWh0dG_bandTag",
103
114
  "heatmapGrid": "VWh0dG_heatmapGrid",
104
- "subscriptionGrid": "VWh0dG_subscriptionGrid",
105
- "subscriptionWindow": "VWh0dG_subscriptionWindow",
106
115
  "chartLegend": "VWh0dG_chartLegend",
107
- "subscriptionFill": "VWh0dG_subscriptionFill",
108
- "panelTitle": "VWh0dG_panelTitle",
109
- "roundsBarLabel": "VWh0dG_roundsBarLabel",
110
- "heatmapCell": "VWh0dG_heatmapCell",
111
- "heroLabel": "VWh0dG_heroLabel",
112
- "heroValue": "VWh0dG_heroValue",
113
- "heroSideItem": "VWh0dG_heroSideItem",
114
116
  "modelDot": "VWh0dG_modelDot",
115
- "providerGroupMeta": "VWh0dG_providerGroupMeta",
116
- "healthBadgeOk": "VWh0dG_healthBadgeOk",
117
- "triggerIcon": "VWh0dG_triggerIcon",
118
- "healthBadge": "VWh0dG_healthBadge",
119
- "budgetHead": "VWh0dG_budgetHead",
120
- "balanceCell": "VWh0dG_balanceCell",
121
- "heroSide": "VWh0dG_heroSide",
122
- "currencyToggle": "VWh0dG_currencyToggle",
123
- "railButton": "VWh0dG_railButton",
124
- "roundsEmpty": "VWh0dG_roundsEmpty",
125
- "heatmap": "VWh0dG_heatmap",
126
- "subscriptionWindowLabel": "VWh0dG_subscriptionWindowLabel",
127
- "chartTooltipSwatch": "VWh0dG_chartTooltipSwatch",
128
- "chartBar": "VWh0dG_chartBar",
129
- "subscriptionCard": "VWh0dG_subscriptionCard",
130
- "heroSideLabel": "VWh0dG_heroSideLabel",
131
- "modelTableScroll": "VWh0dG_modelTableScroll",
132
- "trigger": "VWh0dG_trigger",
133
- "chartTooltipDate": "VWh0dG_chartTooltipDate",
134
- "bandPrice": "VWh0dG_bandPrice",
135
- "trendPanel": "VWh0dG_trendPanel",
136
- "panel": "VWh0dG_panel",
137
- "providerGroupList": "VWh0dG_providerGroupList",
138
- "roundsBars": "VWh0dG_roundsBars",
139
- "dashboardSubtitle": "VWh0dG_dashboardSubtitle",
140
- "healthIdle": "VWh0dG_healthIdle",
141
- "roundsAxis": "VWh0dG_roundsAxis",
142
117
  "budgetControls": "VWh0dG_budgetControls",
143
- "chartLine": "VWh0dG_chartLine",
144
- "balanceDaysLow": "VWh0dG_balanceDaysLow",
145
- "providerGroupBadge": "VWh0dG_providerGroupBadge",
146
- "heroSideValue": "VWh0dG_heroSideValue",
147
- "pricingToggleText": "VWh0dG_pricingToggleText",
148
118
  "panelHint": "VWh0dG_panelHint",
149
- "chartWrap": "VWh0dG_chartWrap",
150
- "bandTag": "VWh0dG_bandTag",
151
- "triggerDivider": "VWh0dG_triggerDivider",
152
- "heatmapHover": "VWh0dG_heatmapHover",
119
+ "dashboard": "VWh0dG_dashboard",
120
+ "currencyButtonActive": "VWh0dG_currencyButtonActive",
121
+ "tableScroll": "VWh0dG_tableScroll",
122
+ "bandPriceOff": "VWh0dG_bandPriceOff",
123
+ "pricingToggleText": "VWh0dG_pricingToggleText",
124
+ "modelCell": "VWh0dG_modelCell",
125
+ "pricingChevronOpen": "VWh0dG_pricingChevronOpen",
126
+ "providerGroupBalance": "VWh0dG_providerGroupBalance",
127
+ "roundsBars": "VWh0dG_roundsBars",
128
+ "heroSideLabel": "VWh0dG_heroSideLabel",
129
+ "roundsEmpty": "VWh0dG_roundsEmpty",
130
+ "deltaDown": "VWh0dG_deltaDown",
131
+ "subscriptionFill": "VWh0dG_subscriptionFill",
132
+ "rangeToggle": "VWh0dG_rangeToggle",
133
+ "chartCrosshair": "VWh0dG_chartCrosshair",
134
+ "chartTooltipRow": "VWh0dG_chartTooltipRow",
135
+ "numCol": "VWh0dG_numCol",
153
136
  "kpiValue": "VWh0dG_kpiValue",
154
- "budgetUnit": "VWh0dG_budgetUnit",
155
- "chartLegendBar": "VWh0dG_chartLegendBar",
156
- "switch": "VWh0dG_switch",
157
- "modelName": "VWh0dG_modelName",
158
- "chartStack": "VWh0dG_chartStack",
137
+ "chartTooltipDate": "VWh0dG_chartTooltipDate",
138
+ "triggerAmountSub": "VWh0dG_triggerAmountSub",
139
+ "providerGroupMeta": "VWh0dG_providerGroupMeta",
140
+ "triggerAmount": "VWh0dG_triggerAmount",
141
+ "dashboardModal": "VWh0dG_dashboardModal",
142
+ "kpiDetail": "VWh0dG_kpiDetail",
143
+ "kpiTile": "VWh0dG_kpiTile",
144
+ "subscriptionStatus": "VWh0dG_subscriptionStatus",
145
+ "budgetOverPulse": "VWh0dG_budgetOverPulse",
146
+ "subscriptionName": "VWh0dG_subscriptionName",
159
147
  "roundsBar": "VWh0dG_roundsBar",
160
- "budgetFill": "VWh0dG_budgetFill",
161
- "roundsFlagMark": "VWh0dG_roundsFlagMark",
162
- "dashboard": "VWh0dG_dashboard",
163
- "budgetInput": "VWh0dG_budgetInput",
148
+ "budgetTrack": "VWh0dG_budgetTrack",
149
+ "switch": "VWh0dG_switch",
150
+ "roundsBarCol": "VWh0dG_roundsBarCol",
151
+ "chartLegendLine": "VWh0dG_chartLegendLine",
152
+ "healthIdle": "VWh0dG_healthIdle",
153
+ "deltaUp": "VWh0dG_deltaUp",
154
+ "panelHead": "VWh0dG_panelHead",
155
+ "dashboardRight": "VWh0dG_dashboardRight",
156
+ "providerGroupBadge": "VWh0dG_providerGroupBadge",
157
+ "pricingTable": "VWh0dG_pricingTable",
158
+ "triggerMeta": "VWh0dG_triggerMeta",
159
+ "chartAxisLabel": "VWh0dG_chartAxisLabel",
160
+ "rounds": "VWh0dG_rounds",
164
161
  "na": "VWh0dG_na",
165
- "uncataloguedTag": "VWh0dG_uncataloguedTag",
166
- "subscriptionPct": "VWh0dG_subscriptionPct",
167
- "providerGroupTitle": "VWh0dG_providerGroupTitle",
168
- "dashboardModal": "VWh0dG_dashboardModal",
169
- "dashboardBody": "VWh0dG_dashboardBody",
170
- "heroMeta": "VWh0dG_heroMeta",
171
- "triggerToday": "VWh0dG_triggerToday",
162
+ "heroValue": "VWh0dG_heroValue",
163
+ "chartDot": "VWh0dG_chartDot",
164
+ "healthBadge": "VWh0dG_healthBadge",
165
+ "rangeButton": "VWh0dG_rangeButton",
166
+ "heatmapCellEmpty": "VWh0dG_heatmapCellEmpty",
167
+ "heatmapFooter": "VWh0dG_heatmapFooter",
168
+ "providerGroupHead": "VWh0dG_providerGroupHead",
169
+ "chartTooltip": "VWh0dG_chartTooltip",
170
+ "healthBadgeBad": "VWh0dG_healthBadgeBad",
172
171
  "delta": "VWh0dG_delta",
173
- "planTag": "VWh0dG_planTag",
174
172
  "providerGroup": "VWh0dG_providerGroup",
175
- "modelCell": "VWh0dG_modelCell",
176
- "tableScroll": "VWh0dG_tableScroll",
177
- "subscriptionReset": "VWh0dG_subscriptionReset",
178
- "providerGroupHead": "VWh0dG_providerGroupHead",
179
- "chartDot": "VWh0dG_chartDot",
180
- "budgetTrack": "VWh0dG_budgetTrack",
181
- "kpiGrid": "VWh0dG_kpiGrid",
182
- "rateBadge": "VWh0dG_rateBadge",
183
- "chartGrid": "VWh0dG_chartGrid",
184
- "providerGroupName": "VWh0dG_providerGroupName",
185
- "roundsFlagBadge": "VWh0dG_roundsFlagBadge",
186
- "healthBad": "VWh0dG_healthBad",
187
- "roundsBarFlagged": "VWh0dG_roundsBarFlagged",
173
+ "heatmap": "VWh0dG_heatmap",
174
+ "triggerIcon": "VWh0dG_triggerIcon",
175
+ "dashboardHead": "VWh0dG_dashboardHead",
176
+ "heroSideValue": "VWh0dG_heroSideValue",
177
+ "heroMain": "VWh0dG_heroMain",
178
+ "budgetValue": "VWh0dG_budgetValue",
179
+ "healthDot": "VWh0dG_healthDot",
180
+ "subscriptionCard": "VWh0dG_subscriptionCard",
181
+ "budgetHead": "VWh0dG_budgetHead",
182
+ "planTag": "VWh0dG_planTag",
183
+ "heroLabel": "VWh0dG_heroLabel",
184
+ "budget": "VWh0dG_budget",
185
+ "kpiLabel": "VWh0dG_kpiLabel",
188
186
  "switchKnob": "VWh0dG_switchKnob",
189
- "rateBadgeLive": "VWh0dG_rateBadgeLive",
190
- "subscriptionName": "VWh0dG_subscriptionName"
187
+ "subscriptionPlan": "VWh0dG_subscriptionPlan",
188
+ "flatTag": "VWh0dG_flatTag",
189
+ "providerGroupName": "VWh0dG_providerGroupName",
190
+ "providerGroupList": "VWh0dG_providerGroupList"
191
191
  };
192
192
  //#endregion
193
193
  //#region src/client/pricing.ts
package/lib/index.js CHANGED
@@ -927,15 +927,25 @@ function createUsageAggregator(persistence, options = {}) {
927
927
  * Account-balance queries for the billing dashboard.
928
928
  *
929
929
  * Only providers with a public balance endpoint can report one. Today that is
930
- * DeepSeek (`GET https://api.deepseek.com/user/balance`, Bearer 鉴权); the
931
- * other mainstream providers (OpenAI, 智谱, 通义, Kimi…) expose no standard
932
- * balance API, so their rows in the model table show an unavailable state.
933
- * The lookup map below is the extension point for future providers.
930
+ * DeepSeek (`GET https://api.deepseek.com/user/balance`) and Moonshot/Kimi
931
+ * (`GET https://api.moonshot.cn/v1/users/me/balance`), both Bearer 鉴权 with a
932
+ * documented JSON shape; the other mainstream providers expose no standard
933
+ * balance API (or require a non-Bearer auth flow), so their rows in the model
934
+ * table show an unavailable state. The lookup map below is the extension point
935
+ * for future providers.
936
+ *
937
+ * API keys are read from the `llm-pi-ai` settings namespace (`providers.<id>.apiKeyEnv`),
938
+ * the same source the subscription adapter uses, so a deployment configures a
939
+ * provider's key once and every surface reuses it.
934
940
  */
935
941
  /** Abort a balance fetch when the upstream hangs beyond this budget. */
936
942
  const FETCH_TIMEOUT_MS$1 = 8e3;
937
943
  /** DeepSeek 官方余额接口(官方文档 api-docs.deepseek.com/api/get-user-balance)。 */
938
944
  const DEEPSEEK_BALANCE_URL = "https://api.deepseek.com/user/balance";
945
+ /** Moonshot/Kimi 官方余额接口(platform.kimi.com/docs/api/balance)。 */
946
+ const MOONSHOT_BALANCE_URL = "https://api.moonshot.cn/v1/users/me/balance";
947
+ /** 阶跃星辰 StepFun 官方账户信息接口(platform.stepfun.com/docs/api-reference/accounts/get)。 */
948
+ const STEPFUN_BALANCE_URL = "https://api.stepfun.com/v1/accounts";
939
949
  /** 数字归一化:接口返回的余额是字符串(如 `"110.00"`),统一转 number。 */
940
950
  function toNumber(value) {
941
951
  if (typeof value === "number" && Number.isFinite(value)) return value;
@@ -945,22 +955,28 @@ function toNumber(value) {
945
955
  }
946
956
  }
947
957
  /**
948
- * Query the DeepSeek account balance through the configured credential.
958
+ * Fetch a Bearer-protected balance endpoint and normalize the HTTP outcome into
959
+ * a shared {@link ProviderBalance} row. Each provider supplies its own
960
+ * response parser for the success body.
949
961
  * @param ctx - host context carrying the credentials seam.
950
- * @param apiKeyEnv - credential reference resolving the DeepSeek API key.
962
+ * @param url - the balance endpoint.
963
+ * @param apiKeyEnv - credential reference resolving the API key.
964
+ * @param provider - the provider id (matches the model-table vendor display name).
965
+ * @param displayName - human-readable provider name.
966
+ * @param parse - maps a success JSON body to the balance row fields.
951
967
  * @returns the balance row, or an error row when the key/endpoint misbehaves.
952
968
  */
953
- async function queryDeepSeek(ctx, apiKeyEnv) {
969
+ async function queryBearerBalance(ctx, url, apiKeyEnv, provider, displayName, parse) {
954
970
  const hit = await ctx.credentials.resolve(credentialRef(apiKeyEnv));
955
971
  if (hit === void 0) return {
956
- provider: "deepseek",
957
- displayName: "DeepSeek",
972
+ provider,
973
+ displayName,
958
974
  error: "unconfigured"
959
975
  };
960
976
  const controller = new AbortController();
961
977
  const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS$1);
962
978
  try {
963
- const response = await fetch(DEEPSEEK_BALANCE_URL, {
979
+ const response = await fetch(url, {
964
980
  headers: {
965
981
  accept: "application/json",
966
982
  authorization: `Bearer ${hit.value}`
@@ -968,22 +984,41 @@ async function queryDeepSeek(ctx, apiKeyEnv) {
968
984
  signal: controller.signal
969
985
  });
970
986
  if (response.status === 401 || response.status === 403) return {
971
- provider: "deepseek",
972
- displayName: "DeepSeek",
987
+ provider,
988
+ displayName,
973
989
  error: "unauthorized"
974
990
  };
975
991
  if (!response.ok) return {
976
- provider: "deepseek",
977
- displayName: "DeepSeek",
992
+ provider,
993
+ displayName,
994
+ error: "unreachable"
995
+ };
996
+ return parse(await response.json());
997
+ } catch {
998
+ return {
999
+ provider,
1000
+ displayName,
978
1001
  error: "unreachable"
979
1002
  };
980
- const data = await response.json();
981
- const info = (Array.isArray(data.balance_infos) ? data.balance_infos : [])[0];
1003
+ } finally {
1004
+ clearTimeout(timer);
1005
+ }
1006
+ }
1007
+ /**
1008
+ * Query the DeepSeek account balance.
1009
+ * @param ctx - host context carrying the credentials seam.
1010
+ * @param apiKeyEnv - credential reference resolving the DeepSeek API key.
1011
+ * @returns the balance row, or an error row when the key/endpoint misbehaves.
1012
+ */
1013
+ function queryDeepSeek(ctx, apiKeyEnv) {
1014
+ return queryBearerBalance(ctx, DEEPSEEK_BALANCE_URL, apiKeyEnv, "deepseek", "DeepSeek", (data) => {
1015
+ const doc = data;
1016
+ const info = (Array.isArray(doc.balance_infos) ? doc.balance_infos : [])[0];
982
1017
  const currency = typeof info?.currency === "string" ? info.currency : void 0;
983
1018
  const totalBalance = toNumber(info?.total_balance);
984
1019
  const grantedBalance = toNumber(info?.granted_balance);
985
1020
  const toppedUpBalance = toNumber(info?.topped_up_balance);
986
- const isAvailable = typeof data.is_available === "boolean" ? data.is_available : void 0;
1021
+ const isAvailable = typeof doc.is_available === "boolean" ? doc.is_available : void 0;
987
1022
  return {
988
1023
  provider: "deepseek",
989
1024
  displayName: "DeepSeek",
@@ -993,28 +1028,87 @@ async function queryDeepSeek(ctx, apiKeyEnv) {
993
1028
  ...toppedUpBalance !== void 0 ? { toppedUpBalance } : {},
994
1029
  ...isAvailable !== void 0 ? { isAvailable } : {}
995
1030
  };
996
- } catch {
1031
+ });
1032
+ }
1033
+ /**
1034
+ * Query the Moonshot/Kimi account balance.
1035
+ * @param ctx - host context carrying the credentials seam.
1036
+ * @param apiKeyEnv - credential reference resolving the Moonshot API key.
1037
+ * @returns the balance row, or an error row when the key/endpoint misbehaves.
1038
+ */
1039
+ function queryMoonshot(ctx, apiKeyEnv) {
1040
+ return queryBearerBalance(ctx, MOONSHOT_BALANCE_URL, apiKeyEnv, "月之暗面", "月之暗面", (data) => {
1041
+ const doc = data;
1042
+ const totalBalance = toNumber(doc.data?.available_balance);
1043
+ const grantedBalance = toNumber(doc.data?.voucher_balance);
1044
+ const toppedUpBalance = toNumber(doc.data?.cash_balance);
997
1045
  return {
998
- provider: "deepseek",
999
- displayName: "DeepSeek",
1000
- error: "unreachable"
1046
+ provider: "月之暗面",
1047
+ displayName: "月之暗面",
1048
+ currency: "CNY",
1049
+ ...totalBalance !== void 0 ? { totalBalance } : {},
1050
+ ...grantedBalance !== void 0 ? { grantedBalance } : {},
1051
+ ...toppedUpBalance !== void 0 ? { toppedUpBalance } : {}
1001
1052
  };
1002
- } finally {
1003
- clearTimeout(timer);
1004
- }
1053
+ });
1005
1054
  }
1006
- const QUERIERS = [{
1007
- provider: "deepseek",
1008
- querier: queryDeepSeek
1009
- }];
1010
1055
  /**
1011
- * Query every configured provider's account balance.
1056
+ * Query the StepFun (阶跃星辰) account balance.
1057
+ * @param ctx - host context carrying the credentials seam.
1058
+ * @param apiKeyEnv - credential reference resolving the StepFun API key.
1059
+ * @returns the balance row, or an error row when the key/endpoint misbehaves.
1060
+ */
1061
+ function queryStepFun(ctx, apiKeyEnv) {
1062
+ return queryBearerBalance(ctx, STEPFUN_BALANCE_URL, apiKeyEnv, "阶跃星辰", "阶跃星辰", (data) => {
1063
+ const doc = data;
1064
+ const totalBalance = toNumber(doc.balance);
1065
+ const toppedUpBalance = toNumber(doc.total_cash_balance);
1066
+ const grantedBalance = toNumber(doc.total_voucher_balance);
1067
+ return {
1068
+ provider: "阶跃星辰",
1069
+ displayName: "阶跃星辰",
1070
+ currency: "CNY",
1071
+ ...totalBalance !== void 0 ? { totalBalance } : {},
1072
+ ...toppedUpBalance !== void 0 ? { toppedUpBalance } : {},
1073
+ ...grantedBalance !== void 0 ? { grantedBalance } : {}
1074
+ };
1075
+ });
1076
+ }
1077
+ const QUERIERS = [
1078
+ {
1079
+ route: "deepseek",
1080
+ displayName: "deepseek",
1081
+ querier: queryDeepSeek
1082
+ },
1083
+ {
1084
+ route: "moonshot",
1085
+ displayName: "月之暗面",
1086
+ querier: queryMoonshot
1087
+ },
1088
+ {
1089
+ route: "stepfun",
1090
+ displayName: "阶跃星辰",
1091
+ querier: queryStepFun
1092
+ }
1093
+ ];
1094
+ /**
1095
+ * Query every configured provider's account balance. A provider is queried only
1096
+ * when its llm-pi-ai route has an `apiKeyEnv`; absent routes answer
1097
+ * `unconfigured` so the dashboard shows a stable state instead of dropping the row.
1012
1098
  * @param ctx - host context carrying the credentials seam.
1013
- * @param balanceApiKeyEnv - credential reference for the DeepSeek key.
1099
+ * @param providers - the llm-pi-ai providers dict (`<route> → { apiKeyEnv? }`).
1014
1100
  * @returns the balance rows (one per provider).
1015
1101
  */
1016
- async function queryBalances(ctx, balanceApiKeyEnv) {
1017
- return await Promise.all(QUERIERS.map(({ querier }) => querier(ctx, balanceApiKeyEnv)));
1102
+ async function queryBalances(ctx, providers) {
1103
+ return await Promise.all(QUERIERS.map(({ route, querier, displayName }) => {
1104
+ const env = providers[route]?.apiKeyEnv;
1105
+ if (typeof env !== "string" || env === "") return Promise.resolve({
1106
+ provider: displayName,
1107
+ displayName,
1108
+ error: "unconfigured"
1109
+ });
1110
+ return querier(ctx, env);
1111
+ }));
1018
1112
  }
1019
1113
  //#endregion
1020
1114
  //#region lib/types/pricing-fetch.js
@@ -1609,6 +1703,19 @@ const SUBSCRIPTION_KEY_SOURCES = [
1609
1703
  }
1610
1704
  ];
1611
1705
  /**
1706
+ * 读取 llm-pi-ai 设置的 `providers` 字典(`<route> → { apiKeyEnv? }`)。
1707
+ * 余额查询复用同一份来源:部署为某个 provider 配一次 key,多个 surface 共享。
1708
+ * @param settings - the settings service (reads the llm-pi-ai namespace).
1709
+ * @returns the providers dict; empty when the namespace is unreadable.
1710
+ */
1711
+ async function readPiAiProviders(settings) {
1712
+ try {
1713
+ return (settings.describe({ redactSecrets: true }).find((descriptor) => descriptor.ns === "llm-pi-ai")?.value)?.providers ?? {};
1714
+ } catch {
1715
+ return {};
1716
+ }
1717
+ }
1718
+ /**
1612
1719
  * 解析订阅适配器需要的 API Key:从 llm-pi-ai 设置的 `providers.<id>.apiKeyEnv`
1613
1720
  * 读引用(如 kimi-coding → KIMI_CODING_API_KEY),再经凭据 seam 解析成实际值。
1614
1721
  * 同时识别出用户配置了 key 的订阅套餐(供面板只显示已识别的)。
@@ -1680,7 +1787,9 @@ function apply(ctx, config = {}) {
1680
1787
  path: "/api/billing/balance",
1681
1788
  handler: async (_req, res) => {
1682
1789
  res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
1683
- const balances = await queryBalances(ctx, config.balanceApiKeyEnv ?? DEFAULT_BALANCE_API_KEY_ENV);
1790
+ const providers = { ...await readPiAiProviders(ctx.settings) };
1791
+ if (providers["deepseek"] === void 0) providers["deepseek"] = { apiKeyEnv: config.balanceApiKeyEnv ?? DEFAULT_BALANCE_API_KEY_ENV };
1792
+ const balances = await queryBalances(ctx, providers);
1684
1793
  res.end(JSON.stringify({ balances }));
1685
1794
  }
1686
1795
  }), "usage-billing: balance route");
@@ -2,18 +2,28 @@
2
2
  * Account-balance queries for the billing dashboard.
3
3
  *
4
4
  * Only providers with a public balance endpoint can report one. Today that is
5
- * DeepSeek (`GET https://api.deepseek.com/user/balance`, Bearer 鉴权); the
6
- * other mainstream providers (OpenAI, 智谱, 通义, Kimi…) expose no standard
7
- * balance API, so their rows in the model table show an unavailable state.
8
- * The lookup map below is the extension point for future providers.
5
+ * DeepSeek (`GET https://api.deepseek.com/user/balance`) and Moonshot/Kimi
6
+ * (`GET https://api.moonshot.cn/v1/users/me/balance`), both Bearer 鉴权 with a
7
+ * documented JSON shape; the other mainstream providers expose no standard
8
+ * balance API (or require a non-Bearer auth flow), so their rows in the model
9
+ * table show an unavailable state. The lookup map below is the extension point
10
+ * for future providers.
11
+ *
12
+ * API keys are read from the `llm-pi-ai` settings namespace (`providers.<id>.apiKeyEnv`),
13
+ * the same source the subscription adapter uses, so a deployment configures a
14
+ * provider's key once and every surface reuses it.
9
15
  */
10
16
  import type { Context } from '@deepseek-ai/cordis';
11
17
  import type { ProviderBalance } from './pricing-shared.ts';
12
18
  /**
13
- * Query every configured provider's account balance.
19
+ * Query every configured provider's account balance. A provider is queried only
20
+ * when its llm-pi-ai route has an `apiKeyEnv`; absent routes answer
21
+ * `unconfigured` so the dashboard shows a stable state instead of dropping the row.
14
22
  * @param ctx - host context carrying the credentials seam.
15
- * @param balanceApiKeyEnv - credential reference for the DeepSeek key.
23
+ * @param providers - the llm-pi-ai providers dict (`<route> → { apiKeyEnv? }`).
16
24
  * @returns the balance rows (one per provider).
17
25
  */
18
- export declare function queryBalances(ctx: Context, balanceApiKeyEnv: string): Promise<readonly ProviderBalance[]>;
26
+ export declare function queryBalances(ctx: Context, providers: Readonly<Record<string, {
27
+ apiKeyEnv?: string;
28
+ }>>): Promise<readonly ProviderBalance[]>;
19
29
  //# sourceMappingURL=balance.d.ts.map
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.4.0",
4
+ "version": "0.4.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },