dsh-cost-meter 1.5.21 → 1.5.22

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
@@ -6,7 +6,7 @@
6
6
 
7
7
  Per-conversation cost · daily totals · OpenCode Go subscription quota display · budget with usage percentage · official account balance · custom provider balance · balance progress bar · history · peak/off-peak pricing hours display (peak hours UTC 01:00–04:00, 06:00–10:00) · pre-switch popup & system-notification alerts for peak/off-peak changes (position / lead time / alert type configurable) · one-click price sync from the official docs · Codex-style token usage heat grid · multi-vendor model pricing (built-in 90+ model price catalog with auto-matching) · mainstream Coding Plan quota queries & display (Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow / SCNet)
8
8
 
9
- [![version](https://img.shields.io/badge/version-1.5.21-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
9
+ [![version](https://img.shields.io/badge/version-1.5.22-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
10
10
  [![npm](https://img.shields.io/npm/v/dsh-cost-meter?label=npm)](https://www.npmjs.com/package/dsh-cost-meter)
11
11
  [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
12
12
  [![dsh](https://img.shields.io/badge/DeepSeek%20Harness-dsh--plugin-4176E6)](https://github.com/deepseek-ai/deepseek-harness)
@@ -45,6 +45,7 @@ English | [中文](README.md)
45
45
  | UI language | Settings → Display settings | Simplified Chinese / English / Follow browser (auto); switches instantly and auto-saves |
46
46
  | AI price sync | [prompt](docs/AI-PRICE-SYNC-PROMPT.en.md) | DeepSeek official sync; other providers use the verified official price catalog and manual configuration |
47
47
  | Model & Plan adaptation guide | [adaptation doc](docs/model-and-plan-adaptation.en.md) | Adaptation matrix for per-model billing and the 7 Coding Plan vendors, the auto-matching mechanism and price sources ([中文](docs/model-and-plan-adaptation.md)) |
48
+ | Peak/off-peak alert guide | [alert doc](docs/peak-alert.en.md) | Fully illustrated guide to the pre-switch popup and system notification: effect screenshots (EN/中文), settings reference and usage tips ([中文](docs/peak-alert.md)) |
48
49
  | Multi-provider billing | Settings / ledger | OpenAI, Anthropic, Google Gemini, Mistral and other providers with input/output, cache and reasoning-token pricing isolated by provider + model |
49
50
  | Model-name auto-matching | Settings / ledger | Unknown model ids are matched against the price table: case/spaces/hyphens/dots and bracket annotations (e.g. (go)) are ignored — a normalized-equal or containing name hits (e.g. `gpt5.6 luna(go)`); router providers (opencode/zen etc.) search across all vendors; can be restricted to exact match, and unmatched models can be pinned to a specific entry in Settings |
50
51
  | Extended price catalog | Settings → Extended price catalog | Built-in reference catalog grouped by vendor and model family (expandable; vendors collapsed by default); mount entries into billing with one click — mounted third-party models live inside the catalog and stay editable; a per-model “Show directly in Cost settings” toggle chooses which models (DeepSeek included) appear directly in the price table |
@@ -193,22 +194,22 @@ Real captures from an actual DSH sidebar of the period strip and collapsed verti
193
194
  dsh plugin --profile web add dsh-cost-meter
194
195
  ```
195
196
 
196
- **PowerShell one-click script** (copy the whole line, paste, press Enter; pnpm is provisioned automatically, git is auto-detected — no clone needed; the install chain is **pinned to the release tag `v1.5.21`** — review the script before running):
197
+ **PowerShell one-click script** (copy the whole line, paste, press Enter; pnpm is provisioned automatically, git is auto-detected — no clone needed; the install chain is **pinned to the release tag `v1.5.22`** — review the script before running):
197
198
 
198
199
  ```powershell
199
- irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.21/install.ps1 | iex
200
+ irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.22/install.ps1 | iex
200
201
  ```
201
202
 
202
203
  **Or a plain command line** (the machine must already have pnpm and git; also pinned to the tag):
203
204
 
204
205
  ```sh
205
- dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.21
206
+ dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.22
206
207
  ```
207
208
 
208
209
  Without git, use the GitHub tag archive:
209
210
 
210
211
  ```sh
211
- dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.21.tar.gz
212
+ dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.22.tar.gz
212
213
  ```
213
214
 
214
215
  After installing, **restart** `dsh web` (plugin rows, the Typert manifest and the client bundle are all scanned at startup):
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  本会话费用 · 当日费用 · OpenCode Go 订阅额度显示 · 预算与已用百分比 · 官方账户余额 · 自定义 Provider 余额查询(可配任意 HTTP 端点) · 余额三段进度条 · 历史记录 · 峰谷计价时段显示(UTC 01:00–04:00、06:00–10:00 为峰时段) · 峰/谷切换前弹窗与系统通知提醒(位置/提前量/提醒类型可配) · 官方价格一键同步 · 类 Codex Token 用量热图 · 多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配) · 主流 Coding Plan 额度查询与显示(Anthropic / Z.ai / MiniMax / Kimi / OpenRouter / SiliconFlow / SCNet 七家)
8
8
 
9
- [![version](https://img.shields.io/badge/version-1.5.21-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
9
+ [![version](https://img.shields.io/badge/version-1.5.22-4176E6)](https://github.com/Han-1413141/dsh-cost-meter)
10
10
  [![npm](https://img.shields.io/npm/v/dsh-cost-meter?label=npm)](https://www.npmjs.com/package/dsh-cost-meter)
11
11
  [![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
12
12
  [![dsh](https://img.shields.io/badge/DeepSeek%20Harness-dsh--plugin-4176E6)](https://github.com/deepseek-ai/deepseek-harness)
@@ -46,6 +46,7 @@
46
46
  | 界面语言 | 设置页 → 显示设置 | 简体中文 / English / 跟随浏览器(自动);切换即时生效并自动保存 |
47
47
  | AI 价格同步 | [提示词](docs/AI-PRICE-SYNC-PROMPT.md) | DeepSeek 官方同步;其他 provider 使用已核对的官方价格目录与手动配置 |
48
48
  | 模型与 Plan 适配说明 | [适配文档](docs/model-and-plan-adaptation.md) | 各厂商模型计费与 7 家 Coding Plan 的适配矩阵、自动匹配机制与价格来源([English](docs/model-and-plan-adaptation.en.md)) |
49
+ | 峰/谷切换提醒图解 | [提醒文档](docs/peak-alert.md) | 峰谷切换前弹窗与系统通知的完整图解:效果截图(中/英)、设置项说明与使用建议([English](docs/peak-alert.en.md)) |
49
50
  | 多 provider 计费 | 设置页 / 账本 | 支持 OpenAI、Anthropic、Google Gemini、Mistral 等 provider 的 input/output、缓存与 reasoning token 价格,按 provider+model 隔离计费 |
50
51
  | 模型名自动匹配 | 设置页 / 账本 | 未知模型 id 自动匹配价格表:忽略大小写/空格/横杠/点号与括号附注,归一化等价或请求名包含表内模型名即命中(如 `gpt5.6 luna(go)`);路由 provider(opencode/zen 等)下跨厂商全库查找;可关闭为仅精确;未命中模型可手动指定计费条目 |
51
52
  | 拓展价格表 | 设置页 → 拓展价格表 | 内置各厂商、按模型家族分类的参考价格目录(点开展开,厂商默认折叠);一键挂载参与计费,挂载的第三方模型默认收入表内可编辑;逐模型「在费用设置直接显示」开关自选哪些模型(含 DeepSeek)在「价格表」区直接显示 |
@@ -195,22 +196,22 @@
195
196
  dsh plugin --profile web add dsh-cost-meter
196
197
  ```
197
198
 
198
- **PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.21`**,建议先下载审阅再运行):
199
+ **PowerShell 一键脚本**(复制整行粘贴回车;自动补齐 pnpm、自动探测 git,无需克隆仓库;安装链**固定到发布 tag `v1.5.22`**,建议先下载审阅再运行):
199
200
 
200
201
  ```powershell
201
- irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.21/install.ps1 | iex
202
+ irm https://raw.githubusercontent.com/Han-1413141/dsh-cost-meter/v1.5.22/install.ps1 | iex
202
203
  ```
203
204
 
204
205
  **或直接命令行**(机器上需已有 pnpm 与 git;同样固定到 tag):
205
206
 
206
207
  ```sh
207
- dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.21
208
+ dsh plugin --profile web add github:Han-1413141/dsh-cost-meter#v1.5.22
208
209
  ```
209
210
 
210
211
  没有 git 时可用 GitHub tag 打包直链:
211
212
 
212
213
  ```sh
213
- dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.21.tar.gz
214
+ dsh plugin --profile web add https://github.com/Han-1413141/dsh-cost-meter/archive/refs/tags/v1.5.22.tar.gz
214
215
  ```
215
216
 
216
217
  安装后**重启** `dsh web`(插件行、Typert 清单与客户端 bundle 均在启动时扫描):
@@ -21,6 +21,8 @@
21
21
  * - 百炼 Coding Plan / OpenAI Codex / Gemini Code Assist / GitHub Copilot 个人版暂无 API-Key 化公开用量端点(仅控制台/组织级 API),不接入。
22
22
  */
23
23
 
24
+ import { fetchWithRetry } from './net.js'
25
+
24
26
  export const CODING_PLAN_PROVIDERS = {
25
27
  anthropic: {
26
28
  label: 'Anthropic (Claude Pro/Max)',
@@ -526,13 +528,13 @@ export async function queryCodingPlan(provider, key, locale, t) {
526
528
  for (const url of urls) {
527
529
  let response
528
530
  try {
529
- response = await fetch(url, {
531
+ // 瞬时网络错误先在单端点上重试(issue #28 同一封装),仍失败再换端点变体。
532
+ response = await fetchWithRetry(url, {
530
533
  headers: {
531
534
  authorization: `Bearer ${key.trim()}`,
532
535
  'user-agent': 'dsh-cost-meter/1.4 (DeepSeek Harness plugin)',
533
536
  },
534
- signal: AbortSignal.timeout(15000),
535
- })
537
+ }, { timeoutMs: 15000 })
536
538
  } catch (error) {
537
539
  lastError = error
538
540
  continue // 网络错误:尝试下一个端点变体
@@ -3,6 +3,7 @@
3
3
  * 与 coding-plans.js 固定端点 adapter 互补:共用 index.js 侧的 refresh/cache 模式。
4
4
  */
5
5
  import { credentialRef } from '@deepseek-ai/dsh-credentials'
6
+ import { fetchWithRetry } from './net.js'
6
7
 
7
8
  export const CUSTOM_BALANCE_ADAPTER_ID = 'custom'
8
9
 
@@ -102,14 +103,15 @@ export async function queryCustomBalance(ctx, config) {
102
103
  }
103
104
  const method = typeof request.method === 'string' ? request.method.toUpperCase() : 'GET'
104
105
  const headers = await resolveHeaders(request.headers ?? {}, ctx)
105
- const init = { method, headers, signal: AbortSignal.timeout(15000) }
106
+ const init = { method, headers }
106
107
  if (method !== 'GET' && method !== 'HEAD' && request.body !== undefined) {
107
108
  init.body = typeof request.body === 'string' ? request.body : JSON.stringify(request.body)
108
109
  if (!headers['content-type'] && !headers['Content-Type']) {
109
110
  init.headers = { ...headers, 'content-type': 'application/json' }
110
111
  }
111
112
  }
112
- const response = await fetch(request.url, init)
113
+ // 瞬时网络错误自动重试(issue #28 同一封装;body 为字符串可安全重放)
114
+ const response = await fetchWithRetry(request.url, init, { timeoutMs: 15000 })
113
115
  if (!response.ok) {
114
116
  throw new Error(`custom balance HTTP ${String(response.status)}`)
115
117
  }
package/lib/index.js CHANGED
@@ -22,6 +22,7 @@ import { Ledger, applyConfigPatch, localDayKey, pickBalanceInfo, reconcileBalanc
22
22
  import { backfillLegacyLedger, importLegacyHistory } from './backfill.js'
23
23
  import { OFFICIAL_PRICING_URL, normalizePrice, parsePricingHtml, costOf, priceEntryFor, providerPriceEntryFor, buildPriceCatalog } from './pricing.js'
24
24
  import { CODING_PLAN_PROVIDERS, CODING_PLAN_PROVIDER_IDS, queryCodingPlan, scnetTokenPlanWindows, emptyCustomBalance, queryCustomBalance } from './coding-plans.js'
25
+ import { fetchWithRetry } from './net.js'
25
26
  import { stateSchema } from './typert.host.js'
26
27
 
27
28
  export const name = 'cost-meter'
@@ -349,14 +350,15 @@ async function queryGoQuota(ctx, config, locale) {
349
350
  error.soft = true // 未登录/未配置 Key 属预期场景,面板以中性提示展示
350
351
  throw error
351
352
  }
352
- const response = await fetch(GO_QUOTA_URL, {
353
+ // Cloudflare 前置会间歇性重置连接(ECONNRESET fetch failed):瞬时网络错误
354
+ // 自动重试,401/403 等业务状态仍走下方原语义(issue #28)。
355
+ const response = await fetchWithRetry(GO_QUOTA_URL, {
353
356
  headers: {
354
357
  authorization: `Bearer ${key}`,
355
358
  // 浏览器 UA:避免被 opencode.ai 前置 Cloudflare 以 error 1010 拦截。
356
359
  'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36',
357
360
  },
358
- signal: AbortSignal.timeout(15000),
359
- })
361
+ }, { timeoutMs: 15000 })
360
362
  if (!response.ok) {
361
363
  if (response.status === 401 || response.status === 403) {
362
364
  const error = new Error(tmsg(locale, 'goQuotaNoSub', { code: String(response.status) }))
@@ -469,10 +471,10 @@ async function queryBalance(ctx, locale) {
469
471
  if (endpoint === null) {
470
472
  throw new Error(tmsg(locale, 'balanceEndpointNotOfficial', { url: String(baseURL ?? '') }))
471
473
  }
472
- const response = await fetch(endpoint, {
474
+ // 瞬时网络错误自动重试(issue #28 同一封装);非 2xx 状态仍按业务错误处理。
475
+ const response = await fetchWithRetry(endpoint, {
473
476
  headers: { authorization: `Bearer ${apiKey}` },
474
- signal: AbortSignal.timeout(15000),
475
- })
477
+ }, { timeoutMs: 15000 })
476
478
  if (!response.ok) throw new Error(tmsg(locale, 'balanceHttp', { code: String(response.status) }))
477
479
  const data = await response.json()
478
480
  // 多币种账号返回 CNY/USD 两条且顺序不稳定(#24/#25):按余额与币种挑选,不固定取首条。
@@ -548,12 +550,11 @@ function buildState(ledger, balance = emptyBalance(), goQuota = emptyGoQuota(),
548
550
  return state
549
551
  }
550
552
 
551
- /** 带超时抓取官方定价页。 */
553
+ /** 带超时抓取官方定价页(瞬时网络错误自动重试,issue #28 同一封装)。 */
552
554
  async function fetchPricingHtml(locale) {
553
- const response = await fetch(OFFICIAL_PRICING_URL, {
554
- signal: AbortSignal.timeout(20000),
555
+ const response = await fetchWithRetry(OFFICIAL_PRICING_URL, {
555
556
  headers: { 'user-agent': 'dsh-cost-meter/0.4 (DeepSeek Harness plugin)' },
556
- })
557
+ }, { timeoutMs: 20000 })
557
558
  if (!response.ok) throw new Error(`HTTP ${String(response.status)}`)
558
559
  const text = await response.text()
559
560
  if (text.length < 500) throw new Error(tmsg(locale, 'pageTooShort'))
package/lib/net.js ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * 对外 HTTP 请求的统一重试封装(issue #28)。
3
+ * opencode.ai 等端点部署在 Cloudflare 之后会间歇性重置连接(ECONNRESET),
4
+ * Node fetch 统一抛为 `fetch failed`;对这类瞬时网络错误做少量重试即可消除
5
+ * 面板偶发报错,而 401/403 等业务错误重试无意义,仍由调用方原样处理。
6
+ */
7
+
8
+ /** 视为瞬时的网络错误码(Node DNS/socket 层 + undici 内部码)。 */
9
+ const TRANSIENT_CODES = new Set([
10
+ 'ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND', 'EHOSTUNREACH',
11
+ 'ENETUNREACH', 'EPIPE', 'EAI_AGAIN',
12
+ 'UND_ERR_SOCKET', 'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_HEADERS_TIMEOUT',
13
+ 'UND_ERR_BODY_TIMEOUT', 'UND_ERR_READ_ERROR', 'UND_ERR_ABORTED',
14
+ ])
15
+
16
+ /**
17
+ * 判定 fetch 抛出的错误是否为瞬时网络错误(值得重试)。
18
+ * @param {unknown} error - fetch reject 的错误。
19
+ */
20
+ export function isTransientFetchError(error) {
21
+ if (typeof error !== 'object' || error === null) return false
22
+ // AbortSignal.timeout 触发的超时:DOMException TimeoutError(老版本 undici 为
23
+ // AbortError)。本插件对外请求只用超时信号、无手动取消,二者均可安全重试。
24
+ if (error.name === 'TimeoutError' || error.name === 'AbortError') return true
25
+ const code = error.cause?.code ?? error.code
26
+ // 有具体 code 时以白名单为准:证书过期(EPERM/CERT_*)等持久性错误不重试。
27
+ if (typeof code === 'string') return TRANSIENT_CODES.has(code)
28
+ // 无具体 code 的纯 'fetch failed'(TypeError)按瞬时处理。
29
+ return error instanceof TypeError && String(error.message ?? '').includes('fetch failed')
30
+ }
31
+
32
+ /** @param {number} ms */
33
+ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
34
+
35
+ /**
36
+ * 带重试的 fetch:仅对瞬时网络错误自动重试(默认共 4 次尝试,退避 300/600/1200ms,
37
+ * 上限 1500ms),其余错误与 HTTP 状态码原样交回调用方(业务语义各自判断)。
38
+ * 每次尝试用 timeoutMs 新建超时信号——重试若复用已中止的 AbortSignal.timeout
39
+ * 会立即再抛 AbortError,重试形同虚设。
40
+ * @param {string} url - 请求地址。
41
+ * @param {RequestInit} [init] - fetch init(headers/method/body 等)。
42
+ * @param {{ attempts?: number, backoffMs?: number, timeoutMs?: number }} [options]
43
+ * - attempts:总尝试次数(含首次),默认 4。
44
+ * - backoffMs:退避基数,默认 300(实际等待 backoffMs * 2^(尝试序-2))。
45
+ * - timeoutMs:单次尝试超时;>0 时覆盖 init.signal,默认 0(沿用调用方信号)。
46
+ */
47
+ export async function fetchWithRetry(url, init = {}, { attempts = 4, backoffMs = 300, timeoutMs = 0 } = {}) {
48
+ for (let attempt = 1; ; attempt++) {
49
+ if (attempt > 1) await sleep(Math.min(1500, backoffMs * 2 ** (attempt - 2)))
50
+ const perAttempt = timeoutMs > 0 ? { ...init, signal: AbortSignal.timeout(timeoutMs) } : init
51
+ try {
52
+ return await fetch(url, perAttempt)
53
+ } catch (error) {
54
+ if (!isTransientFetchError(error) || attempt >= attempts) throw error
55
+ }
56
+ }
57
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-cost-meter",
3
- "version": "1.5.21",
3
+ "version": "1.5.22",
4
4
  "description": "DeepSeek Harness 会话费用统计插件:本会话成本、当日费用、历史记录与官方价格同步,支持多厂商多模型价格计费(内置 90+ 模型价格目录与自动匹配)、主流 Coding Plan 订阅额度查询与显示(7 家,含 SCNet Token Plan 本地 Credits 计量)、自定义 Provider 余额查询(可配任意 HTTP 端点)与余额进度条、峰谷计价时段显示与切换前弹窗/系统通知提醒(位置/提前量/类型可配),界面中英双语。Session cost tracking plugin for DeepSeek Harness: per-conversation cost, daily totals, history and official price sync, with multi-vendor model pricing (built-in 90+ model catalog with auto-matching), Coding Plan quota queries & display for 7 vendors (incl. local credits metering for the SCNet Token Plan), custom provider balance lookup (configurable HTTP endpoint) and balance progress bar, plus peak/off-peak pricing display with pre-switch popup & system-notification alerts (position / lead time / type configurable), in a bilingual (Chinese/English) UI.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",