deepseek-harness-wallet 0.3.9 → 0.3.11
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/CHANGELOG.md +12 -0
- package/README.md +4 -2
- package/index.js +28 -5
- package/lib/client.js +1 -5615
- package/package.json +19 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.3.11 - 2026-09-05
|
|
6
|
+
|
|
7
|
+
- 切换账户时清除旧余额并启动独立刷新,取消旧请求并忽略其迟到成功或失败;删除当前账户后重新读取宿主凭据对应的余额。 / Clear cached balance on account switches, start a fresh request, and discard stale successes or failures; refresh the host-credential balance after removing the active account.
|
|
8
|
+
- 设置备份按顺序发送,失败重试保留最新编辑,避免旧请求覆盖已保存的新设置,同时保留有限次数重试。 / Serialize preference backups and retain the newest queued edits across bounded retries so older requests cannot overwrite newer settings.
|
|
9
|
+
- 浏览器拒绝访问 localStorage 属性时,客户端仍可初始化并使用内存回退和宿主设置备份;桌面存储桥保持优先。 / Keep client initialization, memory storage, and host preference hydration working when access to the localStorage property is denied, preserving desktop storage bridge precedence.
|
|
10
|
+
|
|
11
|
+
## 0.3.10 - 2026-09-05
|
|
12
|
+
|
|
13
|
+
- 拆分可读客户端源码并提交可复现的精简加载产物,使每个文件低于 DSH STORE 的 256 KiB 自动审核上限;安装仍不需要构建或运行依赖。 / Split readable client sources and commit a reproducible compact loader artifact below DSH STORE's 256 KiB per-file review bound, retaining build-free installation and zero runtime dependencies.
|
|
14
|
+
- 声明 alpha.3、alpha.4、alpha.5、rc.1 的精确 DSH 兼容状态;alpha.4/alpha.5/rc.1 均完成隔离 Profile 安装、冷启动、钱包接口与客户端资源验证、卸载和回退。 / Declare exact DSH compatibility for alpha.3, alpha.4, alpha.5, and rc.1; the latter three passed disposable-profile installation, cold start, wallet endpoints/client resource delivery, removal, and rollback.
|
|
15
|
+
- 增加产物复现与文件体积检查,保留全部钱包、账户、套餐及分时计价回归。 / Add artifact reproducibility and byte-bound checks while retaining wallet, account, plan and time-of-use pricing regressions.
|
|
16
|
+
|
|
5
17
|
## 0.3.9 - 2026-09-04
|
|
6
18
|
|
|
7
19
|
- 第三方 API 自定义价格新增分时计价(Issue #37):每个精确 Provider/模型可在基础价之上配置多个 IANA 时区、适用星期和起止时间规则,并为输入、缓存读、缓存写、输出分别定价;支持跨午夜时段,当前命中的价格段会在设置和钱包详情中显示。 / Added time-of-use custom third-party API pricing (Issue #37): each exact Provider/model route can define multiple IANA-timezone, weekday, and start/end windows over a base rate, with separate input, cache-read, cache-write, and output prices; cross-midnight windows are supported and the active price segment is shown in Settings and wallet details.
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
> A local-first companion that keeps account status, per-conversation usage, completion reminders, official recharge, flexible layout, and host-gated session controls beside the DSH composer.
|
|
16
16
|
|
|
17
|
-
> **Version:** v0.3.
|
|
17
|
+
> **Version:** v0.3.11.
|
|
18
18
|
|
|
19
19
|
> If DeepSeek Harness Control Center helps you, please consider leaving a ⭐ Star. Thank you!
|
|
20
20
|
|
|
@@ -74,7 +74,7 @@ Details: [compatibility](#browser-desktop-and-os-compatibility) · [data and tru
|
|
|
74
74
|
|
|
75
75
|
## Install
|
|
76
76
|
|
|
77
|
-
From npm
|
|
77
|
+
From npm:
|
|
78
78
|
|
|
79
79
|
```sh
|
|
80
80
|
dsh plugin --profile web add deepseek-harness-wallet
|
|
@@ -145,6 +145,8 @@ For buildable DSH hosts, the npm package and repository include a versioned [Age
|
|
|
145
145
|
|
|
146
146
|
## Data & trust
|
|
147
147
|
|
|
148
|
+
Client development now uses five readable files under `src/client/` and a committed `lib/client.js` artifact; run `npm ci`, `npm run build:client`, and `npm run check:client` after source edits. Installation runs no build scripts. Exact-version smoke checks are documented in [0.3.10 compatibility evidence](https://github.com/feibi-mochi/deepseek-harness-control-center/blob/main/docs/compatibility-0.3.10.md).
|
|
149
|
+
|
|
148
150
|
| Item | Behavior |
|
|
149
151
|
| --- | --- |
|
|
150
152
|
| Token accounting | Listens to the `llm/stream` event and buckets per session and provider: `deepseek-official` plus explicitly opted-in wrapper routes use the official bucket; other providers stay third-party; each usage event also locks its contemporaneous official price, so multiple sessions and pricing windows never mix. |
|
package/index.js
CHANGED
|
@@ -34,7 +34,7 @@ const TRANSPARENT_PROVIDER_PREFIX = 'vision-toolkit-'
|
|
|
34
34
|
// DeepSeek's official paid-API bucket by the wrapper-provider alias control.
|
|
35
35
|
const PLAN_PROVIDER_IDS = new Set(PLAN_ADAPTERS.map((adapter) => adapter.provider))
|
|
36
36
|
const RECHARGE_URL = 'https://platform.deepseek.com/top_up'
|
|
37
|
-
const PLUGIN_VERSION = '0.3.
|
|
37
|
+
const PLUGIN_VERSION = '0.3.11'
|
|
38
38
|
const PRICING_SOURCE_URL = 'https://api-docs.deepseek.com/zh-cn/quick_start/pricing/'
|
|
39
39
|
const PRICING_SYNC_INTERVAL_MS = 6 * 60 * 60_000
|
|
40
40
|
const PRICING_SYNC_TIMEOUT_MS = 8_000
|
|
@@ -1913,7 +1913,10 @@ export function removeAccount(id) {
|
|
|
1913
1913
|
// Deliberately NOT unsetting the credentials seam: the key currently in
|
|
1914
1914
|
// .credentials.yaml keeps working for LLM billing; the UI just reports
|
|
1915
1915
|
// "no active account" and balance falls back to the seam key.
|
|
1916
|
-
if (accounts.activeId === id)
|
|
1916
|
+
if (accounts.activeId === id) {
|
|
1917
|
+
accounts.activeId = null
|
|
1918
|
+
invalidateBalance()
|
|
1919
|
+
}
|
|
1917
1920
|
return { ok: true, thresholdRemoved }
|
|
1918
1921
|
}
|
|
1919
1922
|
|
|
@@ -1971,6 +1974,7 @@ export async function activateAccount(ctx, id) {
|
|
|
1971
1974
|
}
|
|
1972
1975
|
accounts.activeId = account.id
|
|
1973
1976
|
scheduleAccountsSave(ctx.logger)
|
|
1977
|
+
invalidateBalance()
|
|
1974
1978
|
void refreshBalance(ctx)
|
|
1975
1979
|
return { ok: true, account: { id: account.id, name: account.name, maskedKey: maskKey(account.apiKey) } }
|
|
1976
1980
|
}
|
|
@@ -2043,8 +2047,18 @@ function recordUsage(options, usage, atMs = Date.now()) {
|
|
|
2043
2047
|
|
|
2044
2048
|
let balance = { fetchedAt: 0, available: false, balances: [], error: null }
|
|
2045
2049
|
let balanceRefresh = null
|
|
2050
|
+
let balanceGeneration = 0
|
|
2051
|
+
let balanceController = null
|
|
2052
|
+
|
|
2053
|
+
function invalidateBalance() {
|
|
2054
|
+
balanceGeneration += 1
|
|
2055
|
+
balance = { fetchedAt: 0, available: false, balances: [], error: null }
|
|
2056
|
+
balanceRefresh = null
|
|
2057
|
+
if (balanceController !== null) balanceController.abort()
|
|
2058
|
+
balanceController = null
|
|
2059
|
+
}
|
|
2046
2060
|
|
|
2047
|
-
async function performBalanceRefresh(ctx) {
|
|
2061
|
+
async function performBalanceRefresh(ctx, generation) {
|
|
2048
2062
|
try {
|
|
2049
2063
|
const credentials = ctx.get('credentials')
|
|
2050
2064
|
if (credentials === undefined) {
|
|
@@ -2052,11 +2066,13 @@ async function performBalanceRefresh(ctx) {
|
|
|
2052
2066
|
return
|
|
2053
2067
|
}
|
|
2054
2068
|
const key = await resolveBalanceKey(ctx)
|
|
2069
|
+
if (generation !== balanceGeneration) return
|
|
2055
2070
|
if (key === undefined || key === '') {
|
|
2056
2071
|
balance = { fetchedAt: Date.now(), available: false, balances: [], error: 'no-api-key' }
|
|
2057
2072
|
return
|
|
2058
2073
|
}
|
|
2059
2074
|
const controller = new AbortController()
|
|
2075
|
+
balanceController = controller
|
|
2060
2076
|
const timer = setTimeout(() => controller.abort(), 15000)
|
|
2061
2077
|
try {
|
|
2062
2078
|
const response = await fetch('https://api.deepseek.com/user/balance', {
|
|
@@ -2073,6 +2089,7 @@ async function performBalanceRefresh(ctx) {
|
|
|
2073
2089
|
throw error
|
|
2074
2090
|
}
|
|
2075
2091
|
const data = await response.json()
|
|
2092
|
+
if (generation !== balanceGeneration) return
|
|
2076
2093
|
const available = data !== null && typeof data === 'object' && data.is_available === true
|
|
2077
2094
|
balance = {
|
|
2078
2095
|
fetchedAt: Date.now(),
|
|
@@ -2082,8 +2099,10 @@ async function performBalanceRefresh(ctx) {
|
|
|
2082
2099
|
}
|
|
2083
2100
|
} finally {
|
|
2084
2101
|
clearTimeout(timer)
|
|
2102
|
+
if (balanceController === controller) balanceController = null
|
|
2085
2103
|
}
|
|
2086
2104
|
} catch (error) {
|
|
2105
|
+
if (generation !== balanceGeneration) return
|
|
2087
2106
|
const code = error && typeof error === 'object' && typeof error.code === 'string'
|
|
2088
2107
|
? error.code
|
|
2089
2108
|
: error && typeof error === 'object' && error.name === 'AbortError'
|
|
@@ -2100,11 +2119,13 @@ async function performBalanceRefresh(ctx) {
|
|
|
2100
2119
|
|
|
2101
2120
|
function refreshBalance(ctx) {
|
|
2102
2121
|
if (balanceRefresh !== null) return balanceRefresh
|
|
2122
|
+
const generation = balanceGeneration
|
|
2103
2123
|
// Never let a refresh escape as an unhandled rejection: every caller uses
|
|
2104
2124
|
// `void refreshBalance(ctx)`, so a throw here would take the host process
|
|
2105
2125
|
// down (Node exits on unhandled rejections). Absorb it into the error state.
|
|
2106
|
-
balanceRefresh = performBalanceRefresh(ctx)
|
|
2126
|
+
balanceRefresh = performBalanceRefresh(ctx, generation)
|
|
2107
2127
|
.catch((error) => {
|
|
2128
|
+
if (generation !== balanceGeneration) return
|
|
2108
2129
|
balance = {
|
|
2109
2130
|
fetchedAt: Date.now(),
|
|
2110
2131
|
available: false,
|
|
@@ -2116,7 +2137,7 @@ function refreshBalance(ctx) {
|
|
|
2116
2137
|
}
|
|
2117
2138
|
})
|
|
2118
2139
|
.finally(() => {
|
|
2119
|
-
balanceRefresh = null
|
|
2140
|
+
if (generation === balanceGeneration) balanceRefresh = null
|
|
2120
2141
|
})
|
|
2121
2142
|
return balanceRefresh
|
|
2122
2143
|
}
|
|
@@ -2668,10 +2689,12 @@ historyEventCount = store.history && store.history.events ? Object.keys(store.hi
|
|
|
2668
2689
|
if (accountStorageLocked) return json(res, 423, { ok: false, error: 'account-storage-locked' })
|
|
2669
2690
|
const body = await readBody(req)
|
|
2670
2691
|
if (body === null || typeof body.id !== 'string') return json(res, 400, { ok: false, error: 'id is required' })
|
|
2692
|
+
const wasActive = accounts.activeId === body.id
|
|
2671
2693
|
const result = removeAccount(body.id)
|
|
2672
2694
|
if (!result.ok) return json(res, 400, { ok: false, error: result.error })
|
|
2673
2695
|
scheduleAccountsSave(ctx.logger)
|
|
2674
2696
|
if (result.thresholdRemoved) scheduleSave(ctx.logger)
|
|
2697
|
+
if (wasActive) void refreshBalance(ctx)
|
|
2675
2698
|
return json(res, 200, { ok: true, ...accountListView() })
|
|
2676
2699
|
},
|
|
2677
2700
|
})
|