@kenz1117/dsh-ui-usage-billing 0.4.0 → 0.4.1
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 +14 -8
- package/lib/client.js +131 -131
- package/lib/index.js +107 -30
- package/lib/types/balance.d.ts +17 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ DeepSeek Harness 计费仪表盘插件。从持久化会话日志实时聚合模
|
|
|
6
6
|
[](https://github.com/kenz1117/dsh-ui-usage-billing)
|
|
7
7
|
[](https://github.com/kenz1117/dsh-ui-usage-billing)
|
|
8
8
|
[](https://www.npmjs.com/package/@kenz1117/dsh-ui-usage-billing)
|
|
9
|
+
[](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
|
-
-
|
|
21
|
+
- **余额查询**:厂商组头部显示该厂商余额(只显示一次,不再随每行重复)。DeepSeek 用官方余额 API、月之暗面(Kimi)用 Moonshot 余额接口**实时查询**;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
|
-
|
|
149
|
+
各接入厂商账户余额。API Key 复用 `llm-pi-ai` 设置的 `providers.<id>.apiKeyEnv`(DeepSeek 未在 llm-pi-ai 配置时回退到 `balanceApiKeyEnv`):
|
|
149
150
|
|
|
150
151
|
```json
|
|
151
152
|
{
|
|
@@ -153,11 +154,16 @@ 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
|
-
"
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
161
167
|
}
|
|
162
168
|
]
|
|
163
169
|
}
|
|
@@ -200,7 +206,7 @@ cost(CNY)= (missInput × p_input + cacheHit × p_cacheHit + output × p_outp
|
|
|
200
206
|
| 字段 | 默认 | 说明 |
|
|
201
207
|
| ----------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
|
|
202
208
|
| `statsPath` | 未设置 | 回退统计文件 `.dsh-usage-stats.json` 的绝对路径(`sessionPersistence` 不可用时生效) |
|
|
203
|
-
| `balanceApiKeyEnv` | `DEEPSEEK_API_KEY` |
|
|
209
|
+
| `balanceApiKeyEnv` | `DEEPSEEK_API_KEY` | DeepSeek 余额查询的凭据引用;仅在 llm-pi-ai 未配置 deepseek 的 `apiKeyEnv` 时兜底使用 |
|
|
204
210
|
| `subscriptionProviders` | `kimi-coding`、`xiaomi-token-plan-cn` | 订阅制(coding / token 套餐)provider id 列表,照常统计 token、费用记 0 |
|
|
205
211
|
| `monthlyBudget` | 未设置 | 月度预算默认金额(人民币元);随 usage-stats 下发,作为仪表盘预算条的初始金额(用户在界面上的设置优先并本地持久化) |
|
|
206
212
|
| `lowBalanceThreshold` | `50` | 余额不足告警阈值(人民币元);随 usage-stats 下发,任一厂商余额折算人民币低于此值时每天提醒一次 |
|
|
@@ -236,7 +242,7 @@ npm publish --access public
|
|
|
236
242
|
|
|
237
243
|
## Known Limitations and Deferred Work
|
|
238
244
|
|
|
239
|
-
-
|
|
245
|
+
- **余额查询已接入 DeepSeek / 月之暗面(Kimi)**:其他厂商因无公开余额 API 或需非 Bearer 鉴权,暂显示「未配置」;扩展点在 `src/balance.ts`(按厂商余额 API 增加查询器)。
|
|
240
246
|
- **超支通知依赖浏览器 Notification**:权限被拒绝或平台不支持时只有界面红色脉冲兜底,没有宿主级通知通道;通知上限为每天一次。
|
|
241
247
|
- **会话明细不可跳转**:点击会话行不会打开对应会话(跨插件导航需要宿主会话选择通道);会话数封顶 100 行、面板只显示前 20 行。
|
|
242
248
|
- **费用为目录价估算**:讯飞 / 商汤 / 小米等未公布按量单价的模型使用估算价(特性表脚注 ¹),正式定价以厂商账单为准。
|
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
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
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",
|
|
37
|
+
"budgetInputWrap": "VWh0dG_budgetInputWrap",
|
|
38
|
+
"roundsBarLabel": "VWh0dG_roundsBarLabel",
|
|
39
|
+
"heatmapLegend": "VWh0dG_heatmapLegend",
|
|
40
|
+
"triggerIcon": "VWh0dG_triggerIcon",
|
|
41
|
+
"chartLegendBar": "VWh0dG_chartLegendBar",
|
|
42
|
+
"pricingChevron": "VWh0dG_pricingChevron",
|
|
43
|
+
"heatmapGrid": "VWh0dG_heatmapGrid",
|
|
44
|
+
"heroLabel": "VWh0dG_heroLabel",
|
|
45
|
+
"headTitleRow": "VWh0dG_headTitleRow",
|
|
46
|
+
"subscriptionWindow": "VWh0dG_subscriptionWindow",
|
|
47
|
+
"modelDot": "VWh0dG_modelDot",
|
|
48
|
+
"healthBadgeOk": "VWh0dG_healthBadgeOk",
|
|
49
|
+
"providerGroupList": "VWh0dG_providerGroupList",
|
|
58
50
|
"subscriptionPlan": "VWh0dG_subscriptionPlan",
|
|
51
|
+
"bandPriceOff": "VWh0dG_bandPriceOff",
|
|
52
|
+
"triggerMonth": "VWh0dG_triggerMonth",
|
|
53
|
+
"dashboardSubtitle": "VWh0dG_dashboardSubtitle",
|
|
59
54
|
"rounds": "VWh0dG_rounds",
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
55
|
+
"bandTag": "VWh0dG_bandTag",
|
|
56
|
+
"panelHead": "VWh0dG_panelHead",
|
|
57
|
+
"rateBadge": "VWh0dG_rateBadge",
|
|
58
|
+
"heroMain": "VWh0dG_heroMain",
|
|
59
|
+
"chartWrap": "VWh0dG_chartWrap",
|
|
60
|
+
"subscriptionGrid": "VWh0dG_subscriptionGrid",
|
|
61
|
+
"switchKnob": "VWh0dG_switchKnob",
|
|
62
|
+
"bandTagOff": "VWh0dG_bandTagOff",
|
|
64
63
|
"modelTable": "VWh0dG_modelTable",
|
|
65
|
-
"kpiTile": "VWh0dG_kpiTile",
|
|
66
|
-
"budget": "VWh0dG_budget",
|
|
67
|
-
"hero": "VWh0dG_hero",
|
|
68
|
-
"subscriptionStatus": "VWh0dG_subscriptionStatus",
|
|
69
|
-
"dashboardRight": "VWh0dG_dashboardRight",
|
|
70
|
-
"kpiGreen": "VWh0dG_kpiGreen",
|
|
71
|
-
"emptyRow": "VWh0dG_emptyRow",
|
|
72
|
-
"heatmapLegend": "VWh0dG_heatmapLegend",
|
|
73
|
-
"chartAxisLabel": "VWh0dG_chartAxisLabel",
|
|
74
64
|
"providerGroupBalanceLabel": "VWh0dG_providerGroupBalanceLabel",
|
|
75
|
-
"budgetValue": "VWh0dG_budgetValue",
|
|
76
|
-
"deltaDown": "VWh0dG_deltaDown",
|
|
77
65
|
"currencyButton": "VWh0dG_currencyButton",
|
|
78
|
-
"
|
|
79
|
-
"
|
|
66
|
+
"subscriptionPct": "VWh0dG_subscriptionPct",
|
|
67
|
+
"budgetControls": "VWh0dG_budgetControls",
|
|
68
|
+
"healthBad": "VWh0dG_healthBad",
|
|
69
|
+
"heroSideLabel": "VWh0dG_heroSideLabel",
|
|
70
|
+
"planTag": "VWh0dG_planTag",
|
|
71
|
+
"balanceDaysLow": "VWh0dG_balanceDaysLow",
|
|
72
|
+
"bandPrice": "VWh0dG_bandPrice",
|
|
73
|
+
"dashboard": "VWh0dG_dashboard",
|
|
74
|
+
"closeButton": "VWh0dG_closeButton",
|
|
75
|
+
"dashboardRight": "VWh0dG_dashboardRight",
|
|
76
|
+
"currencyToggle": "VWh0dG_currencyToggle",
|
|
77
|
+
"roundsAxis": "VWh0dG_roundsAxis",
|
|
78
|
+
"modelCell": "VWh0dG_modelCell",
|
|
80
79
|
"budgetLabel": "VWh0dG_budgetLabel",
|
|
81
|
-
"
|
|
82
|
-
"
|
|
80
|
+
"pricingToggle": "VWh0dG_pricingToggle",
|
|
81
|
+
"emptyRow": "VWh0dG_emptyRow",
|
|
83
82
|
"heatmapFooter": "VWh0dG_heatmapFooter",
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
83
|
+
"chartLine": "VWh0dG_chartLine",
|
|
84
|
+
"hero": "VWh0dG_hero",
|
|
85
|
+
"dashboardModal": "VWh0dG_dashboardModal",
|
|
86
|
+
"switchOn": "VWh0dG_switchOn",
|
|
87
|
+
"roundsBars": "VWh0dG_roundsBars",
|
|
88
|
+
"triggerToday": "VWh0dG_triggerToday",
|
|
89
|
+
"uncataloguedTag": "VWh0dG_uncataloguedTag",
|
|
91
90
|
"currencyButtonActive": "VWh0dG_currencyButtonActive",
|
|
92
|
-
"
|
|
93
|
-
"
|
|
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",
|
|
103
|
-
"heatmapGrid": "VWh0dG_heatmapGrid",
|
|
104
|
-
"subscriptionGrid": "VWh0dG_subscriptionGrid",
|
|
105
|
-
"subscriptionWindow": "VWh0dG_subscriptionWindow",
|
|
106
|
-
"chartLegend": "VWh0dG_chartLegend",
|
|
91
|
+
"deltaDown": "VWh0dG_deltaDown",
|
|
92
|
+
"chartDot": "VWh0dG_chartDot",
|
|
107
93
|
"subscriptionFill": "VWh0dG_subscriptionFill",
|
|
108
|
-
"
|
|
109
|
-
"
|
|
110
|
-
"
|
|
111
|
-
"heroLabel": "VWh0dG_heroLabel",
|
|
94
|
+
"roundsBarWrap": "VWh0dG_roundsBarWrap",
|
|
95
|
+
"rangeButtonActive": "VWh0dG_rangeButtonActive",
|
|
96
|
+
"budget": "VWh0dG_budget",
|
|
112
97
|
"heroValue": "VWh0dG_heroValue",
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"
|
|
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",
|
|
98
|
+
"trendPanel": "VWh0dG_trendPanel",
|
|
99
|
+
"budgetUnit": "VWh0dG_budgetUnit",
|
|
100
|
+
"kpiTile": "VWh0dG_kpiTile",
|
|
101
|
+
"heatmapCell": "VWh0dG_heatmapCell",
|
|
102
|
+
"budgetTrack": "VWh0dG_budgetTrack",
|
|
103
|
+
"chartLegend": "VWh0dG_chartLegend",
|
|
104
|
+
"chartEmpty": "VWh0dG_chartEmpty",
|
|
105
|
+
"rateBadgeLive": "VWh0dG_rateBadgeLive",
|
|
129
106
|
"subscriptionCard": "VWh0dG_subscriptionCard",
|
|
130
|
-
"
|
|
107
|
+
"healthOk": "VWh0dG_healthOk",
|
|
108
|
+
"healthDot": "VWh0dG_healthDot",
|
|
109
|
+
"kpiGrid": "VWh0dG_kpiGrid",
|
|
131
110
|
"modelTableScroll": "VWh0dG_modelTableScroll",
|
|
132
|
-
"
|
|
111
|
+
"dashboardHead": "VWh0dG_dashboardHead",
|
|
112
|
+
"roundsFlagMark": "VWh0dG_roundsFlagMark",
|
|
113
|
+
"heatmapHover": "VWh0dG_heatmapHover",
|
|
114
|
+
"chartAxisLabel": "VWh0dG_chartAxisLabel",
|
|
115
|
+
"budgetValue": "VWh0dG_budgetValue",
|
|
116
|
+
"modelName": "VWh0dG_modelName",
|
|
117
|
+
"rangeToggle": "VWh0dG_rangeToggle",
|
|
118
|
+
"kpiGreen": "VWh0dG_kpiGreen",
|
|
119
|
+
"chartTooltipRow": "VWh0dG_chartTooltipRow",
|
|
120
|
+
"healthIdle": "VWh0dG_healthIdle",
|
|
121
|
+
"panelTitle": "VWh0dG_panelTitle",
|
|
122
|
+
"budgetOverPulse": "VWh0dG_budgetOverPulse",
|
|
123
|
+
"roundsBar": "VWh0dG_roundsBar",
|
|
124
|
+
"pricingToggleText": "VWh0dG_pricingToggleText",
|
|
133
125
|
"chartTooltipDate": "VWh0dG_chartTooltipDate",
|
|
134
|
-
"bandPrice": "VWh0dG_bandPrice",
|
|
135
|
-
"trendPanel": "VWh0dG_trendPanel",
|
|
136
126
|
"panel": "VWh0dG_panel",
|
|
137
|
-
"
|
|
138
|
-
"
|
|
139
|
-
"
|
|
140
|
-
"
|
|
141
|
-
"
|
|
142
|
-
"
|
|
143
|
-
"
|
|
144
|
-
"
|
|
127
|
+
"tableScroll": "VWh0dG_tableScroll",
|
|
128
|
+
"na": "VWh0dG_na",
|
|
129
|
+
"chartTooltip": "VWh0dG_chartTooltip",
|
|
130
|
+
"healthBadgeBad": "VWh0dG_healthBadgeBad",
|
|
131
|
+
"switch": "VWh0dG_switch",
|
|
132
|
+
"modelProvider": "VWh0dG_modelProvider",
|
|
133
|
+
"trigger": "VWh0dG_trigger",
|
|
134
|
+
"railButton": "VWh0dG_railButton",
|
|
135
|
+
"budgetHead": "VWh0dG_budgetHead",
|
|
145
136
|
"providerGroupBadge": "VWh0dG_providerGroupBadge",
|
|
137
|
+
"healthBadge": "VWh0dG_healthBadge",
|
|
138
|
+
"chartStack": "VWh0dG_chartStack",
|
|
139
|
+
"subscriptionWindowLabel": "VWh0dG_subscriptionWindowLabel",
|
|
140
|
+
"providerGroupBalance": "VWh0dG_providerGroupBalance",
|
|
141
|
+
"heroSideItem": "VWh0dG_heroSideItem",
|
|
142
|
+
"kpiDetail": "VWh0dG_kpiDetail",
|
|
143
|
+
"budgetFillOver": "VWh0dG_budgetFillOver",
|
|
144
|
+
"chartBar": "VWh0dG_chartBar",
|
|
145
|
+
"rateBadgeBuiltin": "VWh0dG_rateBadgeBuiltin",
|
|
146
|
+
"pricingChevronOpen": "VWh0dG_pricingChevronOpen",
|
|
147
|
+
"pricingTable": "VWh0dG_pricingTable",
|
|
146
148
|
"heroSideValue": "VWh0dG_heroSideValue",
|
|
147
|
-
"
|
|
149
|
+
"heatmapCellEmpty": "VWh0dG_heatmapCellEmpty",
|
|
150
|
+
"costCol": "VWh0dG_costCol",
|
|
151
|
+
"providerGroup": "VWh0dG_providerGroup",
|
|
152
|
+
"balanceCell": "VWh0dG_balanceCell",
|
|
153
|
+
"triggerMeta": "VWh0dG_triggerMeta",
|
|
154
|
+
"roundsBarCol": "VWh0dG_roundsBarCol",
|
|
155
|
+
"roundsEmpty": "VWh0dG_roundsEmpty",
|
|
156
|
+
"chartSvg": "VWh0dG_chartSvg",
|
|
157
|
+
"subscriptionReset": "VWh0dG_subscriptionReset",
|
|
158
|
+
"deltaUp": "VWh0dG_deltaUp",
|
|
159
|
+
"providerGroupName": "VWh0dG_providerGroupName",
|
|
160
|
+
"flatTag": "VWh0dG_flatTag",
|
|
161
|
+
"kpiLabel": "VWh0dG_kpiLabel",
|
|
148
162
|
"panelHint": "VWh0dG_panelHint",
|
|
149
|
-
"chartWrap": "VWh0dG_chartWrap",
|
|
150
|
-
"bandTag": "VWh0dG_bandTag",
|
|
151
|
-
"triggerDivider": "VWh0dG_triggerDivider",
|
|
152
|
-
"heatmapHover": "VWh0dG_heatmapHover",
|
|
153
|
-
"kpiValue": "VWh0dG_kpiValue",
|
|
154
|
-
"budgetUnit": "VWh0dG_budgetUnit",
|
|
155
|
-
"chartLegendBar": "VWh0dG_chartLegendBar",
|
|
156
|
-
"switch": "VWh0dG_switch",
|
|
157
|
-
"modelName": "VWh0dG_modelName",
|
|
158
|
-
"chartStack": "VWh0dG_chartStack",
|
|
159
|
-
"roundsBar": "VWh0dG_roundsBar",
|
|
160
163
|
"budgetFill": "VWh0dG_budgetFill",
|
|
161
|
-
"
|
|
162
|
-
"
|
|
164
|
+
"balanceDays": "VWh0dG_balanceDays",
|
|
165
|
+
"chartCrosshair": "VWh0dG_chartCrosshair",
|
|
166
|
+
"delta": "VWh0dG_delta",
|
|
163
167
|
"budgetInput": "VWh0dG_budgetInput",
|
|
164
|
-
"
|
|
165
|
-
"
|
|
166
|
-
"subscriptionPct": "VWh0dG_subscriptionPct",
|
|
167
|
-
"providerGroupTitle": "VWh0dG_providerGroupTitle",
|
|
168
|
-
"dashboardModal": "VWh0dG_dashboardModal",
|
|
168
|
+
"dashboardTitle": "VWh0dG_dashboardTitle",
|
|
169
|
+
"triggerAmount": "VWh0dG_triggerAmount",
|
|
169
170
|
"dashboardBody": "VWh0dG_dashboardBody",
|
|
171
|
+
"kpiValue": "VWh0dG_kpiValue",
|
|
172
|
+
"subscriptionName": "VWh0dG_subscriptionName",
|
|
173
|
+
"subscriptionStatus": "VWh0dG_subscriptionStatus",
|
|
174
|
+
"triggerDivider": "VWh0dG_triggerDivider",
|
|
170
175
|
"heroMeta": "VWh0dG_heroMeta",
|
|
171
|
-
"
|
|
172
|
-
"delta": "VWh0dG_delta",
|
|
173
|
-
"planTag": "VWh0dG_planTag",
|
|
174
|
-
"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",
|
|
176
|
+
"triggerAmountSub": "VWh0dG_triggerAmountSub",
|
|
185
177
|
"roundsFlagBadge": "VWh0dG_roundsFlagBadge",
|
|
186
|
-
"
|
|
178
|
+
"providerGroupTitle": "VWh0dG_providerGroupTitle",
|
|
179
|
+
"heatmap": "VWh0dG_heatmap",
|
|
180
|
+
"heroSide": "VWh0dG_heroSide",
|
|
181
|
+
"rangeButton": "VWh0dG_rangeButton",
|
|
182
|
+
"chartGrid": "VWh0dG_chartGrid",
|
|
183
|
+
"chartLegendLine": "VWh0dG_chartLegendLine",
|
|
184
|
+
"chartTooltipSwatch": "VWh0dG_chartTooltipSwatch",
|
|
185
|
+
"subscriptionTrack": "VWh0dG_subscriptionTrack",
|
|
186
|
+
"providerGroupMeta": "VWh0dG_providerGroupMeta",
|
|
187
187
|
"roundsBarFlagged": "VWh0dG_roundsBarFlagged",
|
|
188
|
-
"
|
|
189
|
-
"
|
|
190
|
-
"
|
|
188
|
+
"providerGroupHead": "VWh0dG_providerGroupHead",
|
|
189
|
+
"subscriptionHead": "VWh0dG_subscriptionHead",
|
|
190
|
+
"numCol": "VWh0dG_numCol"
|
|
191
191
|
};
|
|
192
192
|
//#endregion
|
|
193
193
|
//#region src/client/pricing.ts
|
package/lib/index.js
CHANGED
|
@@ -927,15 +927,23 @@ 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
|
|
931
|
-
*
|
|
932
|
-
*
|
|
933
|
-
*
|
|
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";
|
|
939
947
|
/** 数字归一化:接口返回的余额是字符串(如 `"110.00"`),统一转 number。 */
|
|
940
948
|
function toNumber(value) {
|
|
941
949
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
@@ -945,22 +953,28 @@ function toNumber(value) {
|
|
|
945
953
|
}
|
|
946
954
|
}
|
|
947
955
|
/**
|
|
948
|
-
*
|
|
956
|
+
* Fetch a Bearer-protected balance endpoint and normalize the HTTP outcome into
|
|
957
|
+
* a shared {@link ProviderBalance} row. Each provider supplies its own
|
|
958
|
+
* response parser for the success body.
|
|
949
959
|
* @param ctx - host context carrying the credentials seam.
|
|
950
|
-
* @param
|
|
960
|
+
* @param url - the balance endpoint.
|
|
961
|
+
* @param apiKeyEnv - credential reference resolving the API key.
|
|
962
|
+
* @param provider - the provider id (matches the model-table vendor display name).
|
|
963
|
+
* @param displayName - human-readable provider name.
|
|
964
|
+
* @param parse - maps a success JSON body to the balance row fields.
|
|
951
965
|
* @returns the balance row, or an error row when the key/endpoint misbehaves.
|
|
952
966
|
*/
|
|
953
|
-
async function
|
|
967
|
+
async function queryBearerBalance(ctx, url, apiKeyEnv, provider, displayName, parse) {
|
|
954
968
|
const hit = await ctx.credentials.resolve(credentialRef(apiKeyEnv));
|
|
955
969
|
if (hit === void 0) return {
|
|
956
|
-
provider
|
|
957
|
-
displayName
|
|
970
|
+
provider,
|
|
971
|
+
displayName,
|
|
958
972
|
error: "unconfigured"
|
|
959
973
|
};
|
|
960
974
|
const controller = new AbortController();
|
|
961
975
|
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS$1);
|
|
962
976
|
try {
|
|
963
|
-
const response = await fetch(
|
|
977
|
+
const response = await fetch(url, {
|
|
964
978
|
headers: {
|
|
965
979
|
accept: "application/json",
|
|
966
980
|
authorization: `Bearer ${hit.value}`
|
|
@@ -968,22 +982,41 @@ async function queryDeepSeek(ctx, apiKeyEnv) {
|
|
|
968
982
|
signal: controller.signal
|
|
969
983
|
});
|
|
970
984
|
if (response.status === 401 || response.status === 403) return {
|
|
971
|
-
provider
|
|
972
|
-
displayName
|
|
985
|
+
provider,
|
|
986
|
+
displayName,
|
|
973
987
|
error: "unauthorized"
|
|
974
988
|
};
|
|
975
989
|
if (!response.ok) return {
|
|
976
|
-
provider
|
|
977
|
-
displayName
|
|
990
|
+
provider,
|
|
991
|
+
displayName,
|
|
978
992
|
error: "unreachable"
|
|
979
993
|
};
|
|
980
|
-
|
|
981
|
-
|
|
994
|
+
return parse(await response.json());
|
|
995
|
+
} catch {
|
|
996
|
+
return {
|
|
997
|
+
provider,
|
|
998
|
+
displayName,
|
|
999
|
+
error: "unreachable"
|
|
1000
|
+
};
|
|
1001
|
+
} finally {
|
|
1002
|
+
clearTimeout(timer);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Query the DeepSeek account balance.
|
|
1007
|
+
* @param ctx - host context carrying the credentials seam.
|
|
1008
|
+
* @param apiKeyEnv - credential reference resolving the DeepSeek API key.
|
|
1009
|
+
* @returns the balance row, or an error row when the key/endpoint misbehaves.
|
|
1010
|
+
*/
|
|
1011
|
+
function queryDeepSeek(ctx, apiKeyEnv) {
|
|
1012
|
+
return queryBearerBalance(ctx, DEEPSEEK_BALANCE_URL, apiKeyEnv, "deepseek", "DeepSeek", (data) => {
|
|
1013
|
+
const doc = data;
|
|
1014
|
+
const info = (Array.isArray(doc.balance_infos) ? doc.balance_infos : [])[0];
|
|
982
1015
|
const currency = typeof info?.currency === "string" ? info.currency : void 0;
|
|
983
1016
|
const totalBalance = toNumber(info?.total_balance);
|
|
984
1017
|
const grantedBalance = toNumber(info?.granted_balance);
|
|
985
1018
|
const toppedUpBalance = toNumber(info?.topped_up_balance);
|
|
986
|
-
const isAvailable = typeof
|
|
1019
|
+
const isAvailable = typeof doc.is_available === "boolean" ? doc.is_available : void 0;
|
|
987
1020
|
return {
|
|
988
1021
|
provider: "deepseek",
|
|
989
1022
|
displayName: "DeepSeek",
|
|
@@ -993,28 +1026,57 @@ async function queryDeepSeek(ctx, apiKeyEnv) {
|
|
|
993
1026
|
...toppedUpBalance !== void 0 ? { toppedUpBalance } : {},
|
|
994
1027
|
...isAvailable !== void 0 ? { isAvailable } : {}
|
|
995
1028
|
};
|
|
996
|
-
}
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Query the Moonshot/Kimi account balance.
|
|
1033
|
+
* @param ctx - host context carrying the credentials seam.
|
|
1034
|
+
* @param apiKeyEnv - credential reference resolving the Moonshot API key.
|
|
1035
|
+
* @returns the balance row, or an error row when the key/endpoint misbehaves.
|
|
1036
|
+
*/
|
|
1037
|
+
function queryMoonshot(ctx, apiKeyEnv) {
|
|
1038
|
+
return queryBearerBalance(ctx, MOONSHOT_BALANCE_URL, apiKeyEnv, "月之暗面", "月之暗面", (data) => {
|
|
1039
|
+
const doc = data;
|
|
1040
|
+
const totalBalance = toNumber(doc.data?.available_balance);
|
|
1041
|
+
const grantedBalance = toNumber(doc.data?.voucher_balance);
|
|
1042
|
+
const toppedUpBalance = toNumber(doc.data?.cash_balance);
|
|
997
1043
|
return {
|
|
998
|
-
provider: "
|
|
999
|
-
displayName: "
|
|
1000
|
-
|
|
1044
|
+
provider: "月之暗面",
|
|
1045
|
+
displayName: "月之暗面",
|
|
1046
|
+
currency: "CNY",
|
|
1047
|
+
...totalBalance !== void 0 ? { totalBalance } : {},
|
|
1048
|
+
...grantedBalance !== void 0 ? { grantedBalance } : {},
|
|
1049
|
+
...toppedUpBalance !== void 0 ? { toppedUpBalance } : {}
|
|
1001
1050
|
};
|
|
1002
|
-
}
|
|
1003
|
-
clearTimeout(timer);
|
|
1004
|
-
}
|
|
1051
|
+
});
|
|
1005
1052
|
}
|
|
1006
1053
|
const QUERIERS = [{
|
|
1007
|
-
|
|
1054
|
+
route: "deepseek",
|
|
1055
|
+
displayName: "deepseek",
|
|
1008
1056
|
querier: queryDeepSeek
|
|
1057
|
+
}, {
|
|
1058
|
+
route: "moonshot",
|
|
1059
|
+
displayName: "月之暗面",
|
|
1060
|
+
querier: queryMoonshot
|
|
1009
1061
|
}];
|
|
1010
1062
|
/**
|
|
1011
|
-
* Query every configured provider's account balance.
|
|
1063
|
+
* Query every configured provider's account balance. A provider is queried only
|
|
1064
|
+
* when its llm-pi-ai route has an `apiKeyEnv`; absent routes answer
|
|
1065
|
+
* `unconfigured` so the dashboard shows a stable state instead of dropping the row.
|
|
1012
1066
|
* @param ctx - host context carrying the credentials seam.
|
|
1013
|
-
* @param
|
|
1067
|
+
* @param providers - the llm-pi-ai providers dict (`<route> → { apiKeyEnv? }`).
|
|
1014
1068
|
* @returns the balance rows (one per provider).
|
|
1015
1069
|
*/
|
|
1016
|
-
async function queryBalances(ctx,
|
|
1017
|
-
return await Promise.all(QUERIERS.map(({ querier }) =>
|
|
1070
|
+
async function queryBalances(ctx, providers) {
|
|
1071
|
+
return await Promise.all(QUERIERS.map(({ route, querier, displayName }) => {
|
|
1072
|
+
const env = providers[route]?.apiKeyEnv;
|
|
1073
|
+
if (typeof env !== "string" || env === "") return Promise.resolve({
|
|
1074
|
+
provider: displayName,
|
|
1075
|
+
displayName,
|
|
1076
|
+
error: "unconfigured"
|
|
1077
|
+
});
|
|
1078
|
+
return querier(ctx, env);
|
|
1079
|
+
}));
|
|
1018
1080
|
}
|
|
1019
1081
|
//#endregion
|
|
1020
1082
|
//#region lib/types/pricing-fetch.js
|
|
@@ -1609,6 +1671,19 @@ const SUBSCRIPTION_KEY_SOURCES = [
|
|
|
1609
1671
|
}
|
|
1610
1672
|
];
|
|
1611
1673
|
/**
|
|
1674
|
+
* 读取 llm-pi-ai 设置的 `providers` 字典(`<route> → { apiKeyEnv? }`)。
|
|
1675
|
+
* 余额查询复用同一份来源:部署为某个 provider 配一次 key,多个 surface 共享。
|
|
1676
|
+
* @param settings - the settings service (reads the llm-pi-ai namespace).
|
|
1677
|
+
* @returns the providers dict; empty when the namespace is unreadable.
|
|
1678
|
+
*/
|
|
1679
|
+
async function readPiAiProviders(settings) {
|
|
1680
|
+
try {
|
|
1681
|
+
return (settings.describe({ redactSecrets: true }).find((descriptor) => descriptor.ns === "llm-pi-ai")?.value)?.providers ?? {};
|
|
1682
|
+
} catch {
|
|
1683
|
+
return {};
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
/**
|
|
1612
1687
|
* 解析订阅适配器需要的 API Key:从 llm-pi-ai 设置的 `providers.<id>.apiKeyEnv`
|
|
1613
1688
|
* 读引用(如 kimi-coding → KIMI_CODING_API_KEY),再经凭据 seam 解析成实际值。
|
|
1614
1689
|
* 同时识别出用户配置了 key 的订阅套餐(供面板只显示已识别的)。
|
|
@@ -1680,7 +1755,9 @@ function apply(ctx, config = {}) {
|
|
|
1680
1755
|
path: "/api/billing/balance",
|
|
1681
1756
|
handler: async (_req, res) => {
|
|
1682
1757
|
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
1683
|
-
const
|
|
1758
|
+
const providers = { ...await readPiAiProviders(ctx.settings) };
|
|
1759
|
+
if (providers["deepseek"] === void 0) providers["deepseek"] = { apiKeyEnv: config.balanceApiKeyEnv ?? DEFAULT_BALANCE_API_KEY_ENV };
|
|
1760
|
+
const balances = await queryBalances(ctx, providers);
|
|
1684
1761
|
res.end(JSON.stringify({ balances }));
|
|
1685
1762
|
}
|
|
1686
1763
|
}), "usage-billing: balance route");
|
package/lib/types/balance.d.ts
CHANGED
|
@@ -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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
|
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,
|
|
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.
|
|
4
|
+
"version": "0.4.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|