dsh-balance-widget 0.6.0 → 0.6.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 +24 -5
- package/README.md +24 -5
- package/lib/index.js +98 -1
- package/package.json +2 -2
package/README.en.md
CHANGED
|
@@ -32,7 +32,7 @@ A balance & cost widget for the [DeepSeek Harness](https://github.com/deepseek-a
|
|
|
32
32
|
|
|
33
33
|
## Features
|
|
34
34
|
|
|
35
|
-
- **Account balance** —
|
|
35
|
+
- **Account balance** — The balance comes from the host account service `deepseekAccount`, provided by `@deepseek-ai/dsh-deepseek-account-platform` — the official "Settings → Account & balance" page's own source, so both surfaces show the same number and **no API key is needed**; unsigned hosts and older runtimes (0.1.x) fall back to DeepSeek's official `GET /user/balance`. The `¥` balance is color-coded by threshold (healthy / amber below `lowThreshold` / red below `criticalThreshold`). The API key is resolved through the host credentials service and never leaves the host process; the browser only talks to same-origin routes.
|
|
36
36
|
- **Last prompt cost (estimate)** — Parses the most recent session file and prices the last turn's token usage, answering "how much did that last prompt cost", with the **session name** labeled underneath.
|
|
37
37
|
- **Today · this session cost (estimate)** — The current session's usage today (calendar day) × DeepSeek's official peak/off-peak price table. Follows the configured model (default `deepseek-v4-flash`, switchable to `deepseek-v4-pro`) and the Beijing-time peak/off-peak windows automatically.
|
|
38
38
|
- **Today · this workspace cost (estimate)** — Sums today's token usage × price across every session in the current workspace (anchored by the current session).
|
|
@@ -167,19 +167,20 @@ This section is for the DSH Store / plugin audit: dependencies, runtime permissi
|
|
|
167
167
|
|
|
168
168
|
**Dependencies & compatibility**
|
|
169
169
|
- Zero runtime dependencies: imports no `@deepseek-ai/*` packages; no third-party host deps
|
|
170
|
-
- `peerDependencies["@deepseek-ai/dsh"]`: `>=0.1.2-rc.1 <0.
|
|
170
|
+
- `peerDependencies["@deepseek-ai/dsh"]`: `>=0.1.2-rc.1 <0.3.0` (DSH compatibility range; 0.1.5 / 0.1.7 / 0.2.0 verified)
|
|
171
171
|
- `engines.node`: `^22.19.0 || >=24.0.0`
|
|
172
172
|
- `peerDependencies["react"]`: `^18.2.0` (browser rendering only)
|
|
173
173
|
|
|
174
174
|
**Runtime permissions**
|
|
175
175
|
- `files`: reads only `~/.dsh/sessions/` session JSONL (cost stats); never writes or mutates any session file
|
|
176
|
-
- `network`: only the DeepSeek official endpoints — `api.deepseek.com` (`GET /user/balance`) and `api-docs.deepseek.com` pricing page (fetched every 12h); no third-party proxy
|
|
176
|
+
- `network`: only the DeepSeek official endpoints — `api.deepseek.com` (`GET /user/balance`, used only when unsigned or on an older host; a signed-in runtime reads the host's `deepseekAccount` service instead, so no plugin request leaves the process) and `api-docs.deepseek.com` pricing page (fetched every 12h); no third-party proxy
|
|
177
177
|
- `commands`: **none**. Session logs are decompressed with Node's built-in zstd (`node:zlib`, needs Node >=22.15), so no `zstd` binary and no `PATH` dependency
|
|
178
|
-
- `credentials`: reads `DEEPSEEK_API_KEY` (resolved via the host credentials service), used only in the host process behind a loopback-only route guard; the browser never sees the key
|
|
178
|
+
- `credentials`: reads `DEEPSEEK_API_KEY` on the fallback path only (resolved via the host credentials service), used only in the host process behind a loopback-only route guard; the browser never sees the key. A signed-in account reads no key at all
|
|
179
179
|
- All host routes are bound to the loopback address and unreachable externally
|
|
180
180
|
|
|
181
181
|
**External services**
|
|
182
|
-
-
|
|
182
|
+
- Host account service `deepseekAccount` (DSH 0.2.0+ with a signed-in account; the balance is the official page's own value, truncated to cents the way that page displays it)
|
|
183
|
+
- DeepSeek official balance endpoint `GET /user/balance` (fallback: unsigned or older host; on click / 60s refresh)
|
|
183
184
|
- DeepSeek official pricing page (on startup + every 12h, for peak/off-peak rates)
|
|
184
185
|
|
|
185
186
|
**Failure bounds**
|
|
@@ -191,6 +192,24 @@ This section is for the DSH Store / plugin audit: dependencies, runtime permissi
|
|
|
191
192
|
|
|
192
193
|
## Changelog
|
|
193
194
|
|
|
195
|
+
### v0.6.2 — the balance now prefers the official account service (same source as the settings page, no API key needed)
|
|
196
|
+
- ✨ **The balance is read from the host account service `deepseekAccount` (provided by `@deepseek-ai/dsh-deepseek-account-platform`)** whenever DSH >= 0.2.0 ships it **and a DeepSeek account is signed in**. That service is the official "Settings → Account & balance" page's own source, so the card and the settings page show the same number. Amounts follow the official rule — **positive values are truncated to cents** (`55.6787307800000000` → `55.67`, matching the page) — with the purchased wallet from `value[]`, the granted wallet from `bonusWallets[]`, their sum as the total, and one entry per currency
|
|
197
|
+
- 🚪 **Every fallback is unchanged**: when signed out, when the host has no such service (DSH 0.1.x), or when the account read fails (expired token and friends), the balance comes from the original `GET /user/balance` + `DEEPSEEK_API_KEY`, exactly as in 0.6.1; a failed account read logs one warning saying why
|
|
198
|
+
- 🔑 **Signed in, no key is read at all**: the balance no longer hard-depends on `DEEPSEEK_API_KEY`, so an account-only install still shows a balance
|
|
199
|
+
- 🔎 **Auditable**: the balance response carries a `balanceSource` field (`deepseekAccount` or the fallback path), so it is always clear where a number came from
|
|
200
|
+
- 🧪 **Verified**: a fake cordis ctx drives the **real routes** through 13 assertions — account mapping, multi-currency and bonus summation, cent truncation, signed-out fallback, missing-service fallback, throwing-account fallback with a warning, account winning over an API key, the agent billing tool reading the same source, and a guard that the credential key's spelling is never mistaken for the service name
|
|
201
|
+
- 📏 **Measured same-source** (2026-09-29, after a real restart): the official `account/getBalance` returned `¥55.0685054200000000` while the card showed `¥55.06` — identical to the cent
|
|
202
|
+
|
|
203
|
+
> **Why this release, said out loud.**
|
|
204
|
+
> After desktop 0.2.0 shipped, the official app put a signed-in balance page under Settings → Account & balance. **That means the platform now recognises the pain point** — people need to see their balance. It just takes several clicks to reach, which for everyday use still loses to a card that sits in the sidebar and reads at a glance.
|
|
205
|
+
>
|
|
206
|
+
> So this release adapts anyway: the balance is read from the official account service, identical to the settings page to the cent, and no API key is needed. But we are not pretending otherwise — now that the platform has taken the need over, **this plugin will be replaced sooner or later**. A little wistful, yet a tool's job is to make itself unnecessary: **we are entering the farewell period.** From here we only keep compatibility maintenance in step with DSH, with no new features. Thanks for letting it watch your balance all this time.
|
|
207
|
+
|
|
208
|
+
### v0.6.1 — compatibility range widened to DSH 0.2.0 (upgrading would otherwise drop the plugin silently)
|
|
209
|
+
- 🐛 **Fixed**: DSH 0.2.0 adds a plugin compatibility gate (`evaluatePluginCompatibility` in `dsh-app-boot`). It matches every declared `@deepseek-ai/dsh` / `@deepseek-ai/dsh-*` peer range against the running version — **prereleases included** (`semver.satisfies(..., { includePrerelease: true })`) — and a mismatching, non-exempted bundle is **skipped silently at startup** (`loadProfileDirectory` files it under `skippedBundles`: no error, no manifest change). The old declaration `>=0.1.2-rc.1 <0.2.0` happened to cover `0.2.0-rc.1` (which is why the widget works on the current 0.2.0-rc.1 desktop, verified live) but **not 0.2.0 final** — so the first stable-desktop upgrade would have made the sidebar card vanish, with the plugin manager demanding a manual `dsh plugin allow-version` exemption. The range is now `<0.3.0` (verified to cover 0.2.0 / 0.2.1 / 0.3.0-rc.1)
|
|
210
|
+
- ✅ **Live 0.2.0 compatibility check** (against the 0.2.0-rc.1 desktop app, driven in a real browser): all five host routes answer; session logs are still `SESSION_FORMAT_VERSION = 4` and `totalTokens = inputTokens + outputTokens + cacheReadTokens` holds for all 394 usage events (pricing math unchanged); the `sidebar.footer.action` slot, the `dsh.client.platform === "web"` loading gate and the `IconRefreshOutlineRegular` name are all unchanged; every one of the 19 `--dsw-*` tokens the plugin uses still exists (15 of them redefined under `body[data-ds-dark-theme]`, and the popover portalled into `document.body` still inherits the theme); React is still 18.3.1; the 36×36 collapsed-rail button and the popover anchoring behave as before
|
|
211
|
+
- 📦 **Scope**: the compatibility range in `package.json` plus documentation; no runtime code changed
|
|
212
|
+
|
|
194
213
|
### v0.6.0 — desktop app support, and a UI that matches the desktop shell
|
|
195
214
|
- 🖥️ **Desktop app support (DSH 0.1.7 / DeepSeek Harness.app)**
|
|
196
215
|
- **Session logs are no longer decompressed by the `zstd` CLI** — Node's built-in zstd (`node:zlib`) does it instead. The desktop app is launched by the GUI, so its PATH is only `/usr/bin:/bin:/usr/sbin:/sbin` and the Homebrew `zstd` is invisible: every cost tier (today's spend, last prompt) failed outright there
|
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ DeepSeek Harness (DSH) **Web GUI 与桌面版**通用的余额与成本小部件
|
|
|
32
32
|
|
|
33
33
|
## 功能
|
|
34
34
|
|
|
35
|
-
- **账户余额** —
|
|
35
|
+
- **账户余额** — 余额优先取自宿主账号服务 `deepseekAccount`(由 `@deepseek-ai/dsh-deepseek-account-platform` 提供,即官方「设置 → 账号与余额」页的同一来源,因此两处显示同一个数字,且**不需要 API Key**);未登录或旧版宿主(0.1.x)时回退到 DeepSeek 官方 `GET /user/balance`。展示 `¥` 余额,按阈值自动变色(充足 / 低于 `lowThreshold` 变黄 / 低于 `criticalThreshold` 变红);API key 只在宿主进程内读取(凭据服务),浏览器不接触密钥。
|
|
36
36
|
- **最近一次提问成本(估算)** — 从最近活跃会话文件解析最后一个 turn 的 token 用量 × 单价,回答"刚才那条提问花了多少";下方标注该会话的**会话名**。
|
|
37
37
|
- **今日·本会话成本(估算)** — 当前会话今天(自然日)产生的 token 用量 × DeepSeek 官方峰谷定价表计算,随当前会话模型(默认 `deepseek-v4-flash`,可在配置中改为 `deepseek-v4-pro`)与北京时间高峰/空闲时段自动切换。
|
|
38
38
|
- **今日·本工作区成本(估算)** — 遍历当前工作区(由当前会话锚定)下的所有会话,累加今天的 token 用量 × 单价。
|
|
@@ -164,19 +164,20 @@ DSH 的插件配置统一放在这个文件里:
|
|
|
164
164
|
|
|
165
165
|
**依赖与兼容**
|
|
166
166
|
- 零运行时依赖:不 import 任何 `@deepseek-ai/*` 包,宿主端无第三方依赖
|
|
167
|
-
- `peerDependencies["@deepseek-ai/dsh"]`: `>=0.1.2-rc.1 <0.
|
|
167
|
+
- `peerDependencies["@deepseek-ai/dsh"]`: `>=0.1.2-rc.1 <0.3.0`(DSH 兼容范围;0.1.5 / 0.1.7 / 0.2.0 实测通过)
|
|
168
168
|
- `engines.node`: `^22.19.0 || >=24.0.0`
|
|
169
169
|
- `peerDependencies["react"]`: `^18.2.0`(仅浏览器端渲染)
|
|
170
170
|
|
|
171
171
|
**运行时权限**
|
|
172
172
|
- `files`:只读 `~/.dsh/sessions/` 下的会话 JSONL(成本统计);不写入、不修改任何会话文件
|
|
173
|
-
- `network`:仅请求 DeepSeek 官方端点——`api.deepseek.com`(`GET /user/balance`
|
|
173
|
+
- `network`:仅请求 DeepSeek 官方端点——`api.deepseek.com`(`GET /user/balance`,仅在未登录/旧版宿主时使用;已登录时余额走宿主的 `deepseekAccount` 服务,不产生本插件的网络请求)与 `api-docs.deepseek.com` 定价页(每 12h 抓取);不走任何第三方代理
|
|
174
174
|
- `commands`:**不执行任何命令**。会话解压改用 Node 内置 zstd(`node:zlib`,需 Node ≥22.15),不再依赖 `zstd` 可执行文件与 `PATH`
|
|
175
|
-
- `credentials
|
|
175
|
+
- `credentials`:仅在回退路径上读取 `DEEPSEEK_API_KEY`(经宿主凭据服务解析),仅宿主进程使用、loopback-only 路由守卫;浏览器不接触密钥。已登录账号时余额不读任何密钥
|
|
176
176
|
- 所有 host 路由均绑定 load 回环地址,外部不可达
|
|
177
177
|
|
|
178
178
|
**外部服务**
|
|
179
|
-
-
|
|
179
|
+
- 宿主账号服务 `deepseekAccount`(DSH 0.2.0+ 且已登录账号时;余额与官方账号页同源,金额按官方规则截断到分)
|
|
180
|
+
- DeepSeek 官方余额接口 `GET /user/balance`(回退路径:未登录或旧版宿主;点击/60s 刷新时调用)
|
|
180
181
|
- DeepSeek 官方定价页(启动时 + 每 12h 抓取,用于峰谷单价)
|
|
181
182
|
|
|
182
183
|
**失败边界**
|
|
@@ -188,6 +189,24 @@ DSH 的插件配置统一放在这个文件里:
|
|
|
188
189
|
|
|
189
190
|
## 版本历史
|
|
190
191
|
|
|
192
|
+
### v0.6.2 — 余额优先读官方账号服务(与设置页同源,不再需要 API Key)
|
|
193
|
+
- ✨ **余额优先取自宿主账号服务 `deepseekAccount`**:当 DSH ≥ 0.2.0 **且已登录 DeepSeek 账号**时,余额改读这个服务——它就是官方「设置 → 账号与余额」页的数据来源,所以卡片与设置页显示同一个数字。金额按官方规则处理:**正金额截断至分**(`55.6787307800000000` → `55.67`,与设置页一致),充值余额 = 官方 `value[]`、赠金余额 = `bonusWallets[]`,总额为两者之和,多币种各自成组
|
|
194
|
+
- 🚪 **降级路径完全不变**:未登录、宿主无该服务(DSH 0.1.x)、或账号读取失败(token 过期等)时,一律回退到原来的 `GET /user/balance` + `DEEPSEEK_API_KEY`,行为与 0.6.1 相同;账号读取失败会记一条 warn 说明原因
|
|
195
|
+
- 🔑 **登录态下余额不读任何密钥**:不再强制依赖 `DEEPSEEK_API_KEY`,纯账号模式(不配 API Key)也能显示余额
|
|
196
|
+
- 🔎 **可审计**:余额响应新增 `balanceSource` 字段(`deepseekAccount` 或回退路径),便于确认这一次的数字从哪来
|
|
197
|
+
- 🧪 **验证**:用假 cordis ctx 驱动**真实路由**跑 13 项断言全通过——账号映射 / 多币种与赠金求和 / 截断到分、未登录回退、服务缺失回退、账号抛错回退并告警、账号优先于 API Key、agent 计费工具同源,以及"凭据 key 的拼写不能被误当成服务名"这条防回归
|
|
198
|
+
- 📏 **实测同源**(2026-09-29,真机重启后):官方 `account/getBalance` 返回 `¥55.0685054200000000`,插件卡片显示 `¥55.06` —— 逐分一致
|
|
199
|
+
|
|
200
|
+
> **关于这个版本的背景,也说给我们自己听。**
|
|
201
|
+
> 桌面版 0.2.0 上线后,官方在「设置 → 账号与余额」里放出了登录后的余额页。**这说明官方已经承认"用户需要随时知道余额"这个痛点**——只是它要多点几下才能看到,日常用起来仍然不如侧边栏常驻、一眼可读。
|
|
202
|
+
>
|
|
203
|
+
> 所以这一版还是做了适配:余额改读官方账号服务,与设置页逐分一致,并且不再需要 API Key。但我们心里清楚,官方既然已经把这个需求接过去了,**这个插件被替代只是时间问题**。有点不舍,不过工具的价值本来就应该由平台自己长出来——**接下来进入告别期**:后续只跟随 DSH 版本做必要的兼容性维护,不再扩展新功能。谢谢一路用它看住余额的每一天。
|
|
204
|
+
|
|
205
|
+
### v0.6.1 — 兼容范围放宽到 DSH 0.2.0(否则升级后插件会被静默摘掉)
|
|
206
|
+
- 🐛 **修复**:DSH 0.2.0 新增了插件兼容性闸门(`dsh-app-boot` 的 `evaluatePluginCompatibility`),它把 `peerDependencies` 里每个 `@deepseek-ai/dsh` / `@deepseek-ai/dsh-*` 范围与运行时版本比对,**且预发布版本参与范围匹配**(`semver.satisfies(..., { includePrerelease: true })`);不匹配且未被豁免的 bundle 在启动时被**静默跳过**(`loadProfileDirectory` 收进 `skippedBundles`,不报错、也不改 manifest)。旧声明 `>=0.1.2-rc.1 <0.2.0` 恰好覆盖 `0.2.0-rc.1`(所以在 0.2.0-rc.1 桌面上一切正常,实测确认),但**不覆盖 0.2.0 正式版**——桌面版一升级,侧边栏卡片就会直接消失,插件管理器还会要求 `dsh plugin allow-version` 手动豁免。现放宽为 `<0.3.0`(实测覆盖 0.2.0 / 0.2.1 / 0.3.0-rc.1)
|
|
207
|
+
- ✅ **0.2.0 兼容性实测**(在 0.2.0-rc.1 桌面版 + 真实浏览器上运行):宿主五条路由全部正常;会话日志仍为 `SESSION_FORMAT_VERSION = 4`,`totalTokens = inputTokens + outputTokens + cacheReadTokens` 在 394 条 usage 事件上全部成立(计价公式未变);`sidebar.footer.action` 槽位、`dsh.client.platform === "web"` 加载闸门、`IconRefreshOutlineRegular` 图标名均未变;插件用到的 19 个 `--dsw-*` token 全部存在(其中 15 个在 `body[data-ds-dark-theme]` 下重定义,弹层 portal 到 `document.body` 仍能继承主题);React 仍为 18.3.1;收起态 36×36 图标按钮与弹层定位照常
|
|
208
|
+
- 📦 **范围**:仅 `package.json` 的兼容范围声明与文档,运行代码无变化
|
|
209
|
+
|
|
191
210
|
### v0.6.0 — 适配 DSH 桌面版,界面与桌面端视觉对齐
|
|
192
211
|
- 🖥️ **适配桌面版(DSH 0.1.7 / DeepSeek Harness.app)**
|
|
193
212
|
- **会话解压不再依赖 `zstd` 命令行**,改用 Node 内置 zstd(`node:zlib`)。桌面版由 GUI 启动,进程 PATH 只有 `/usr/bin:/bin:/usr/sbin:/sbin`,找不到 Homebrew 的 zstd——此前在桌面端「今日花费 / 最近一次提问」等所有成本档位都会直接报错
|
package/lib/index.js
CHANGED
|
@@ -180,10 +180,107 @@ async function resolveApiKey(ctx, config) {
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
/**
|
|
183
|
-
*
|
|
183
|
+
* The host account service behind the official "Settings → Account & balance"
|
|
184
|
+
* page. `@deepseek-ai/dsh-deepseek-account-platform` registers it as
|
|
185
|
+
* `deepseekAccount` (its `super(ctx, "deepseekAccount")`, and the account
|
|
186
|
+
* controller's `inject = ["deepseekAccount", ...]`), shipped from DSH 0.2.0 on;
|
|
187
|
+
* on older hosts and profiles without an account backend ctx.get() returns
|
|
188
|
+
* undefined. The package's credential key happens to be spelled differently
|
|
189
|
+
* (`deepseek-account-platform`) and is deliberately not used here.
|
|
190
|
+
*/
|
|
191
|
+
const ACCOUNT_SERVICE = "deepseekAccount";
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* A platform wallet amount as integer cents, truncated below a cent — the same
|
|
195
|
+
* rule the official page uses for positive amounts ("正金额截断至分").
|
|
196
|
+
* @returns BigInt cents, or null when the value is not a decimal string.
|
|
197
|
+
*/
|
|
198
|
+
function centsOf(value) {
|
|
199
|
+
const match = /^(-?)(\d+)(?:\.(\d+))?$/.exec(String(value ?? "").trim());
|
|
200
|
+
if (match === null) return null;
|
|
201
|
+
const cents = BigInt(match[2]) * 100n + BigInt((match[3] ?? "").slice(0, 2).padEnd(2, "0"));
|
|
202
|
+
return match[1] === "-" ? -cents : cents;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Render integer cents as a two-decimal string. */
|
|
206
|
+
function formatCents(cents) {
|
|
207
|
+
const negative = cents < 0n;
|
|
208
|
+
const absolute = negative ? -cents : cents;
|
|
209
|
+
const text = `${absolute / 100n}.${String(absolute % 100n).padStart(2, "0")}`;
|
|
210
|
+
return negative ? `-${text}` : text;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Reshape an account balance view into the /user/balance payload the client
|
|
215
|
+
* already reads (purchased + granted wallets per currency).
|
|
216
|
+
*/
|
|
217
|
+
function accountBalancePayload(view) {
|
|
218
|
+
const wallets = Array.isArray(view.value) ? view.value : [];
|
|
219
|
+
const bonuses = Array.isArray(view.bonusWallets) ? view.bonusWallets : [];
|
|
220
|
+
const currencies = [];
|
|
221
|
+
for (const wallet of [...wallets, ...bonuses]) {
|
|
222
|
+
if (typeof wallet?.currency === "string" && !currencies.includes(wallet.currency)) currencies.push(wallet.currency);
|
|
223
|
+
}
|
|
224
|
+
const sumCents = (list, currency) => list.reduce((total, wallet) => {
|
|
225
|
+
if (wallet?.currency !== currency) return total;
|
|
226
|
+
const cents = centsOf(wallet.balance);
|
|
227
|
+
return cents === null ? total : total + cents;
|
|
228
|
+
}, 0n);
|
|
229
|
+
const balance_infos = currencies.map((currency) => {
|
|
230
|
+
const toppedUp = sumCents(wallets, currency);
|
|
231
|
+
const granted = sumCents(bonuses, currency);
|
|
232
|
+
return {
|
|
233
|
+
currency,
|
|
234
|
+
total_balance: formatCents(toppedUp + granted),
|
|
235
|
+
granted_balance: formatCents(granted),
|
|
236
|
+
topped_up_balance: formatCents(toppedUp)
|
|
237
|
+
};
|
|
238
|
+
});
|
|
239
|
+
return {
|
|
240
|
+
is_available: balance_infos.some((info) => Number(info.total_balance) > 0),
|
|
241
|
+
balance_infos,
|
|
242
|
+
balanceSource: ACCOUNT_SERVICE
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Read the balance from the host account service — the official page's own
|
|
248
|
+
* source, so both surfaces show the same number, and no API key is involved.
|
|
249
|
+
* @returns the /user/balance-shaped payload, or undefined when the service is
|
|
250
|
+
* absent, the user is not signed in, or the read fails (caller falls back).
|
|
251
|
+
*/
|
|
252
|
+
async function fetchAccountBalance(ctx) {
|
|
253
|
+
let account;
|
|
254
|
+
try {
|
|
255
|
+
account = ctx.get(ACCOUNT_SERVICE);
|
|
256
|
+
} catch {
|
|
257
|
+
return void 0;
|
|
258
|
+
}
|
|
259
|
+
if (account === void 0 || typeof account.getBalance !== "function") return void 0;
|
|
260
|
+
try {
|
|
261
|
+
const view = await account.getBalance({
|
|
262
|
+
version: "dsh-balance-widget",
|
|
263
|
+
locale: Intl.DateTimeFormat().resolvedOptions().locale,
|
|
264
|
+
timezoneOffsetSeconds: -new Date().getTimezoneOffset() * 60
|
|
265
|
+
});
|
|
266
|
+
if (view?.status !== "ready") return void 0;
|
|
267
|
+
const payload = accountBalancePayload(view);
|
|
268
|
+
return payload.balance_infos.length === 0 ? void 0 : payload;
|
|
269
|
+
} catch (error) {
|
|
270
|
+
ctx.logger?.warn?.("[dsh-balance-widget] account balance read failed: %s", error instanceof Error ? error.message : String(error));
|
|
271
|
+
return void 0;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Query the account balance: the host's account service when the user signed in
|
|
277
|
+
* with a DeepSeek account, else the official /user/balance endpoint with the API
|
|
278
|
+
* key.
|
|
184
279
|
* @returns { ok: true, ...payload } | { ok: false, error }
|
|
185
280
|
*/
|
|
186
281
|
async function fetchBalance(ctx, config) {
|
|
282
|
+
const account = await fetchAccountBalance(ctx);
|
|
283
|
+
if (account !== void 0) return { ok: true, ...account };
|
|
187
284
|
let apiKey;
|
|
188
285
|
try {
|
|
189
286
|
apiKey = await resolveApiKey(ctx, config);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-balance-widget",
|
|
3
3
|
"description": "Balance & cost widget for the DeepSeek Harness GUI (web and desktop app): a sidebar footer card shows the DeepSeek account balance and today's costs; clicking opens a popover with a five-tier cost breakdown (last prompt with session name / today-this-session / today-this-workspace / today-all-workspaces), peak/off-peak status tag, term explanations, and a one-click top-up link. Zero external dependencies — works on Node 24.",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"files": [
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"react": "^18.2.0",
|
|
34
|
-
"@deepseek-ai/dsh": ">=0.1.2-rc.1 <0.
|
|
34
|
+
"@deepseek-ai/dsh": ">=0.1.2-rc.1 <0.3.0"
|
|
35
35
|
},
|
|
36
36
|
"keywords": [
|
|
37
37
|
"deepseek",
|