dsh-desktop-statusbar 2.0.0 → 2.0.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.en.md CHANGED
@@ -4,7 +4,7 @@
4
4
  <a href="https://github.com/raphael-y7/dsh-desktop-statusbar/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/raphael-y7/dsh-desktop-statusbar?style=social"></a>
5
5
  <a href="https://www.npmjs.com/package/dsh-desktop-statusbar"><img alt="npm version" src="https://img.shields.io/npm/v/dsh-desktop-statusbar?color=red"></a>
6
6
  <a href="LICENSE"><img alt="license" src="https://img.shields.io/badge/license-PolyForm%20Noncommercial%201.0.0-green"></a>
7
- <a href="https://github.com/deepseek-ai/deepseek-harness"><img alt="DeepSeek Harness" src="https://img.shields.io/badge/DeepSeek%20Harness-0.1.7-%234d6bfe"></a>
7
+ <a href="https://github.com/deepseek-ai/deepseek-harness"><img alt="DeepSeek Harness" src="https://img.shields.io/badge/DeepSeek%20Harness-0.2.0--rc.1-%234d6bfe"></a>
8
8
  <a href="https://nodejs.org/"><img alt="node" src="https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-%235fa04e"></a>
9
9
  <img alt="language" src="https://img.shields.io/github/languages/top/raphael-y7/dsh-desktop-statusbar?color=yellow">
10
10
  <a href="https://dshfind.com/zh/plugins/raphael-y7/dsh-desktop-statusbar"><img alt="dshfind" src="https://img.shields.io/badge/dshfind-listed-%2300a884"></a>
@@ -27,9 +27,9 @@ Replaces the stats line under the message input in the DSH desktop app with a co
27
27
  | Field | Content |
28
28
  | --- | --- |
29
29
  | Session status | Context occupancy: system prompt / tool definitions / messages |
30
- | Combined hit | Highest / lowest cache hit rate |
31
- | TTFT | Fastest / slowest first-token delay |
32
- | TPS | Fastest / slowest output speed |
30
+ | Combined hit | This turn / highest / lowest cache hit rate |
31
+ | TTFT | This turn / fastest / slowest first-token delay |
32
+ | TPS | This turn / fastest / slowest output speed |
33
33
  | Run time | Model time / tool calls |
34
34
  | This turn cost | Token breakdown for this turn |
35
35
  | Session cost | Cumulative token breakdown for the whole session |
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  <a href="https://github.com/raphael-y7/dsh-desktop-statusbar/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/raphael-y7/dsh-desktop-statusbar?style=social"></a>
5
5
  <a href="https://www.npmjs.com/package/dsh-desktop-statusbar"><img alt="npm version" src="https://img.shields.io/npm/v/dsh-desktop-statusbar?color=red"></a>
6
6
  <a href="LICENSE"><img alt="license" src="https://img.shields.io/badge/license-PolyForm%20Noncommercial%201.0.0-green"></a>
7
- <a href="https://github.com/deepseek-ai/deepseek-harness"><img alt="DeepSeek Harness" src="https://img.shields.io/badge/DeepSeek%20Harness-0.1.7-%234d6bfe"></a>
7
+ <a href="https://github.com/deepseek-ai/deepseek-harness"><img alt="DeepSeek Harness" src="https://img.shields.io/badge/DeepSeek%20Harness-0.2.0--rc.1-%234d6bfe"></a>
8
8
  <a href="https://nodejs.org/"><img alt="node" src="https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-%235fa04e"></a>
9
9
  <img alt="language" src="https://img.shields.io/github/languages/top/raphael-y7/dsh-desktop-statusbar?color=yellow">
10
10
  <a href="https://dshfind.com/zh/plugins/raphael-y7/dsh-desktop-statusbar"><img alt="dshfind" src="https://img.shields.io/badge/dshfind-listed-%2300a884"></a>
@@ -27,9 +27,9 @@
27
27
  | 字段 | 内容 |
28
28
  | --- | --- |
29
29
  | 会话状态 | 上下文占用:系统提示词 / 工具定义 / 对话消息 |
30
- | 综合命中 | 最高 / 最低缓存命中率 |
31
- | 首字平均 | 最快 / 最慢首字延迟 |
32
- | 输出速度 | 最快 / 最慢输出速度 |
30
+ | 综合命中 | 本轮 / 最高 / 最低缓存命中率 |
31
+ | 首字平均 | 本轮 / 最快 / 最慢首字延迟 |
32
+ | 输出速度 | 本轮 / 最快 / 最慢输出速度 |
33
33
  | 运行用时 | 模型用时 / 工具调用 |
34
34
  | 本轮费用 | 本轮会话的 token 明细 |
35
35
  | 总计费用 | 整个会话累计的 token 明细 |
package/docs/settings.png CHANGED
Binary file
@@ -0,0 +1,224 @@
1
+ /**
2
+ * 多平台余额:把「当前会话用的模型/provider」映射到「该平台的余额接口 + 响应解析」。
3
+ *
4
+ * 只收录**已核实过官方文档**的平台;每家的端点、鉴权、返回字段都写在注释里,方便日后复核。
5
+ * 本模块只做纯逻辑(选适配器、拼请求、解析响应),真正发请求由 index.js 负责 —— 这样能单测。
6
+ *
7
+ * 核实时间:2026-09。未核实到的平台宁可不写,也不猜端点。
8
+ */
9
+
10
+ /* 余额响应统一成这个形状:
11
+ * { currency: 'CNY' | 'USD' | ..., total: '12.34', parts: [{ label, amount }] }
12
+ * currency 缺失时由调用方按站点/域名推断;parts 用来填气泡里的构成行(充值 / 赠金等)。 */
13
+
14
+ const adapters = [
15
+ {
16
+ id: 'deepseek',
17
+ label: 'DeepSeek',
18
+ /* 核实:GET https://api.deepseek.com/user/balance,Authorization: Bearer <key>
19
+ 响应 { balance_infos: [{ currency, total_balance, granted_balance, topped_up_balance }] } */
20
+ keys: ['deepseek', 'deepseek-official'],
21
+ refs: ['DEEPSEEK_API_KEY', 'deepseek-official', 'deepseek'],
22
+ request: (key) => ({
23
+ url: 'https://api.deepseek.com/user/balance',
24
+ headers: { authorization: 'Bearer ' + key, accept: 'application/json' }
25
+ }),
26
+ parse: (data) => {
27
+ const infos = Array.isArray(data && data.balance_infos) ? data.balance_infos : [];
28
+ if (infos.length === 0) return null;
29
+ const one = infos[0];
30
+ const total = typeof one.total_balance === 'string' ? one.total_balance : null;
31
+ if (total === null || total.length === 0) return null;
32
+ const parts = [];
33
+ if (typeof one.topped_up_balance === 'string') parts.push({ label: 'toppedUp', amount: one.topped_up_balance });
34
+ if (typeof one.granted_balance === 'string') parts.push({ label: 'granted', amount: one.granted_balance });
35
+ return { currency: typeof one.currency === 'string' ? one.currency : 'CNY', total, parts };
36
+ }
37
+ },
38
+ {
39
+ id: 'openrouter',
40
+ label: 'OpenRouter',
41
+ /* 核实:GET https://openrouter.ai/api/v1/credits,Authorization: Bearer <key>
42
+ 响应 { data: { total_credits, total_usage } },可用额度 = total_credits - total_usage */
43
+ keys: ['openrouter'],
44
+ refs: ['OPENROUTER_API_KEY'],
45
+ request: (key) => ({
46
+ url: 'https://openrouter.ai/api/v1/credits',
47
+ headers: { authorization: 'Bearer ' + key, accept: 'application/json' }
48
+ }),
49
+ parse: (data) => {
50
+ const inner = data && data.data ? data.data : null;
51
+ if (inner === null) return null;
52
+ const total = Number(inner.total_credits);
53
+ const used = Number(inner.total_usage);
54
+ if (Number.isFinite(total) !== true) return null;
55
+ const left = total - (Number.isFinite(used) ? used : 0);
56
+ return {
57
+ currency: 'USD',
58
+ total: left.toFixed(2),
59
+ parts: [
60
+ { label: 'toppedUp', amount: total.toFixed(2) },
61
+ { label: 'used', amount: (Number.isFinite(used) ? used : 0).toFixed(2) }
62
+ ]
63
+ };
64
+ }
65
+ },
66
+ {
67
+ id: 'bai',
68
+ label: 'B.AI',
69
+ /* 核实:文档 docs.b.ai/llmservice/api/balance/ 给出 GET /v1/balance,
70
+ 鉴权 Authorization: Bearer <api_key>,响应含 balance 字段。
71
+ 注:文档站是 docs.b.ai,API 域名按 api.b.ai 拼接,未经真实调用验证。 */
72
+ keys: ['b-ai', 'bai', 'b.ai'],
73
+ refs: ['B_AI_API_KEY', 'BAI_API_KEY'],
74
+ request: (key) => ({
75
+ url: 'https://api.b.ai/v1/balance',
76
+ headers: { authorization: 'Bearer ' + key, accept: 'application/json' }
77
+ }),
78
+ parse: (data) => {
79
+ const raw = data && (data.balance ?? (data.data ? data.data.balance : undefined));
80
+ if (raw === undefined || raw === null) return null;
81
+ const total = String(raw);
82
+ if (total.length === 0) return null;
83
+ return { currency: 'CNY', total, parts: [] };
84
+ }
85
+ },
86
+ {
87
+ id: 'siliconflow',
88
+ label: 'SiliconFlow',
89
+ /* 核实:GET https://api.siliconflow.com/v1/user/info(国内站 api.siliconflow.cn),
90
+ Authorization: Bearer <key>;响应 data.{balance, chargeBalance, totalBalance}(均为字符串)。
91
+ 官方未声明币种,国际站按 USD、国内站按 CNY 处理。 */
92
+ keys: ['siliconflow'],
93
+ refs: ['SILICONFLOW_API_KEY'],
94
+ request: (key, base) => ({
95
+ url: (base || 'https://api.siliconflow.com') + '/v1/user/info',
96
+ headers: { authorization: 'Bearer ' + key, accept: 'application/json' }
97
+ }),
98
+ parse: (data) => {
99
+ const inner = data && data.data ? data.data : null;
100
+ if (inner === null) return null;
101
+ const total = typeof inner.totalBalance === 'string' ? inner.totalBalance : null;
102
+ if (total === null || total.length === 0) return null;
103
+ const parts = [];
104
+ if (typeof inner.chargeBalance === 'string') parts.push({ label: 'toppedUp', amount: inner.chargeBalance });
105
+ if (typeof inner.balance === 'string') parts.push({ label: 'granted', amount: inner.balance });
106
+ return { currency: 'USD', total, parts };
107
+ }
108
+ },
109
+ {
110
+ id: 'moonshot',
111
+ label: 'Kimi',
112
+ /* 核实:GET https://api.moonshot.ai/v1/users/me/balance(国内站 api.moonshot.cn),
113
+ Authorization: Bearer <key>;响应 { code, data: { available_balance, voucher_balance, cash_balance } }。
114
+ 两站的 key 不通用,单位也不同:国际站 USD、国内站 CNY。 */
115
+ keys: ['moonshot', 'kimi'],
116
+ refs: ['MOONSHOT_API_KEY', 'KIMI_API_KEY'],
117
+ request: (key, base) => ({
118
+ url: (base || 'https://api.moonshot.ai') + '/v1/users/me/balance',
119
+ headers: { authorization: 'Bearer ' + key, accept: 'application/json' }
120
+ }),
121
+ parse: (data) => {
122
+ const inner = data && data.data ? data.data : null;
123
+ if (inner === null) return null;
124
+ const total = inner.available_balance;
125
+ if (typeof total !== 'number' || Number.isFinite(total) !== true) return null;
126
+ const parts = [];
127
+ if (typeof inner.cash_balance === 'number') parts.push({ label: 'toppedUp', amount: inner.cash_balance.toFixed(2) });
128
+ if (typeof inner.voucher_balance === 'number') parts.push({ label: 'granted', amount: inner.voucher_balance.toFixed(2) });
129
+ return { currency: 'USD', total: total.toFixed(2), parts };
130
+ }
131
+ },
132
+ {
133
+ id: 'zhipu',
134
+ label: 'GLM 余额',
135
+ /* 来源:cc-switch PR #6978 的自定义模板(截至 2026-09-27 未合并,社区实测方案)
136
+ GET https://open.bigmodel.cn/api/biz/account/query-customer-account-report
137
+ 鉴权:Authorization: Bearer <api_key> —— 注意与 coding plan 配额的「裸 key」不同
138
+ 响应 { code, msg, data: { availableBalance, balance, rechargeAmount, totalSpendAmount } }
139
+ code === 200 才算成功;可用余额 = availableBalance ?? balance,单位 CNY。 */
140
+ keys: ['zhipu', 'glm', 'bigmodel'],
141
+ refs: ['ZAI_API_KEY', 'ZHIPU_API_KEY', 'GLM_API_KEY', 'BIGMODEL_API_KEY'],
142
+ request: (key) => ({
143
+ /* 实测 2026-09-27:open.bigmodel.cn 与 api.z.ai 同后端,两个域名 + Bearer/裸 key 四种组合都返回 code=200 */
144
+ url: 'https://open.bigmodel.cn/api/biz/account/query-customer-account-report',
145
+ headers: { authorization: 'Bearer ' + key, accept: 'application/json' }
146
+ }),
147
+ parse: (data) => {
148
+ if (data === null || data === undefined) return null;
149
+ if (Number(data.code) !== 200) return null;
150
+ const inner = data.data ? data.data : {};
151
+ const raw = inner.availableBalance !== undefined && inner.availableBalance !== null ? inner.availableBalance : inner.balance;
152
+ const remaining = Number(raw);
153
+ if (Number.isFinite(remaining) !== true) return null;
154
+ const parts = [];
155
+ const recharge = Number(inner.rechargeAmount);
156
+ const gift = Number(inner.giveAmount);
157
+ const spent = Number(inner.totalSpendAmount);
158
+ if (Number.isFinite(recharge)) parts.push({ label: 'toppedUp', amount: recharge.toFixed(2) });
159
+ if (Number.isFinite(gift)) parts.push({ label: 'granted', amount: gift.toFixed(2) });
160
+ if (Number.isFinite(spent)) parts.push({ label: 'spent', amount: spent.toFixed(2) });
161
+ return { currency: 'CNY', total: remaining.toFixed(2), parts };
162
+ }
163
+ },
164
+ {
165
+ id: 'zhipu-quota',
166
+ label: 'GLM 配额',
167
+ /* 核实:cc-switch v3.20.4 的 src-tauri/src/services/coding_plan.rs:332-346
168
+ GET https://open.bigmodel.cn/api/monitor/usage/quota/limit(国际站 https://api.z.ai)
169
+ 鉴权:Authorization: <api_key> —— 智谱**不加 Bearer 前缀**(源码注释两次强调)
170
+ 响应 { success, data: { level, limits: [{ type: TOKENS_LIMIT|CREDIT_LIMIT, percentage, ... }] } }
171
+ percentage 就是配额使用率,直接当百分比显示,**不换算金额**。注意这是 coding plan
172
+ 配额,不是按量计费的现金余额 —— 现金余额只有控制台接口能给,需要网页登录态。 */
173
+ keys: ['zhipu', 'glm', 'bigmodel'],
174
+ refs: ['ZHIPU_API_KEY', 'GLM_API_KEY', 'BIGMODEL_API_KEY'],
175
+ request: (key, base) => ({
176
+ url: (base || 'https://open.bigmodel.cn') + '/api/monitor/usage/quota/limit',
177
+ headers: {
178
+ authorization: key,
179
+ 'content-type': 'application/json',
180
+ 'accept-language': 'en-US,en'
181
+ }
182
+ }),
183
+ parse: (data) => {
184
+ if (data === null || data === undefined || data.success === false) return null;
185
+ const inner = data.data ? data.data : null;
186
+ const limits = inner !== null && Array.isArray(inner.limits) ? inner.limits : [];
187
+ let percent = null;
188
+ for (let i = 0; i < limits.length; i += 1) {
189
+ const one = limits[i];
190
+ const kind = typeof one.type === 'string' ? one.type.toUpperCase() : '';
191
+ if (kind !== 'TOKENS_LIMIT' && kind !== 'CREDIT_LIMIT') continue;
192
+ const value = Number(one.percentage);
193
+ if (Number.isFinite(value) !== true) continue;
194
+ /* 取用得最紧的那个窗口,保守一点 */
195
+ if (percent === null || value > percent) percent = value;
196
+ }
197
+ if (percent === null) return null;
198
+ return { currency: '', total: percent.toFixed(2) + '%', parts: [], isPercent: true };
199
+ }
200
+ }
201
+ ];
202
+
203
+ /** 会话线索(provider + 模型名)里出现这些词就认为是哪一家。 */
204
+ function adapterFor(provider, model) {
205
+ const text = (String(provider === undefined || provider === null ? '' : provider) + ' '
206
+ + String(model === undefined || model === null ? '' : model)).toLowerCase();
207
+ if (text.trim().length === 0) return null;
208
+ for (let i = 0; i < adapters.length; i += 1) {
209
+ const one = adapters[i];
210
+ for (let k = 0; k < one.keys.length; k += 1) {
211
+ if (text.includes(one.keys[k])) return one;
212
+ }
213
+ }
214
+ return null;
215
+ }
216
+
217
+ /** 会话模型属于 DeepSeek 时,优先走官方账号通道(账号登录没有 API key 也能查)。 */
218
+ function isDeepSeek(provider, model) {
219
+ const text = (String(provider === undefined || provider === null ? '' : provider) + ' '
220
+ + String(model === undefined || model === null ? '' : model)).toLowerCase();
221
+ return text.includes('deepseek');
222
+ }
223
+
224
+ export { adapters, adapterFor, isDeepSeek };