dsh-ds-balance 2.1.1 → 2.1.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/lib/config.js CHANGED
@@ -135,7 +135,13 @@ export const Config = z.object({
135
135
  * 两边各存一份是因为**宿主与浏览器两个半体不许值导入**;约定本身由测试对着本表兜底。
136
136
  */
137
137
  export const THRESHOLD_PAIRS = [
138
- { currency: 'CNY', warn: 'cnyWarn', critical: 'cnyCritical', defaultWarn: 10, defaultCritical: 5 },
138
+ {
139
+ currency: 'CNY',
140
+ warn: 'cnyWarn',
141
+ critical: 'cnyCritical',
142
+ defaultWarn: 10,
143
+ defaultCritical: 5,
144
+ },
139
145
  { currency: 'USD', warn: 'usdWarn', critical: 'usdCritical', defaultWarn: 2, defaultCritical: 1 },
140
146
  ];
141
147
  /**
@@ -39,11 +39,16 @@ export class NetworkError extends AppError {
39
39
  /** 把 HTTP 状态码映射到错误码。 */
40
40
  export function upstreamCodeOf(status) {
41
41
  switch (status) {
42
- case 401: return 'UPSTREAM_401';
43
- case 402: return 'UPSTREAM_402';
44
- case 422: return 'UPSTREAM_422';
45
- case 429: return 'UPSTREAM_429';
46
- case 503: return 'UPSTREAM_503';
42
+ case 401:
43
+ return 'UPSTREAM_401';
44
+ case 402:
45
+ return 'UPSTREAM_402';
46
+ case 422:
47
+ return 'UPSTREAM_422';
48
+ case 429:
49
+ return 'UPSTREAM_429';
50
+ case 503:
51
+ return 'UPSTREAM_503';
47
52
  default:
48
53
  if (status >= 500)
49
54
  return 'UPSTREAM_5XX';
@@ -42,10 +42,10 @@ export function pickBalance(balances, preferred) {
42
42
  if (hit !== undefined)
43
43
  return hit;
44
44
  }
45
- return ordered.find((item) => item.currency === 'CNY' && !isZeroMoney(item.total))
46
- ?? ordered.find((item) => !isZeroMoney(item.total))
47
- ?? ordered.find((item) => item.currency === 'CNY')
48
- ?? ordered[0]
49
- ?? null;
45
+ return (ordered.find((item) => item.currency === 'CNY' && !isZeroMoney(item.total)) ??
46
+ ordered.find((item) => !isZeroMoney(item.total)) ??
47
+ ordered.find((item) => item.currency === 'CNY') ??
48
+ ordered[0] ??
49
+ null);
50
50
  }
51
51
  //# sourceMappingURL=select.js.map
@@ -96,7 +96,10 @@ export async function handleBalance(request, deps) {
96
96
  return json(toWireBalanceView(view, deps.service.accountTag8() ?? '', requestId));
97
97
  }
98
98
  catch (error) {
99
- deps.logger?.error('ds-balance: balance handler failed', { requestId, error: describeError(error) });
99
+ deps.logger?.error('ds-balance: balance handler failed', {
100
+ requestId,
101
+ error: describeError(error),
102
+ });
100
103
  return json(failureView(requestId, error));
101
104
  }
102
105
  }
@@ -111,7 +114,10 @@ export async function handleRefresh(request, deps) {
111
114
  return json({ requestId, schemaVersion: SCHEMA_VERSION, ...result, error: null });
112
115
  }
113
116
  catch (error) {
114
- deps.logger?.error('ds-balance: refresh handler failed', { requestId, error: describeError(error) });
117
+ deps.logger?.error('ds-balance: refresh handler failed', {
118
+ requestId,
119
+ error: describeError(error),
120
+ });
115
121
  return json({
116
122
  requestId,
117
123
  schemaVersion: SCHEMA_VERSION,
@@ -145,7 +151,10 @@ async function credentialInfo(deps, ref) {
145
151
  };
146
152
  }
147
153
  catch (error) {
148
- deps.logger?.debug('ds-balance: credential describe failed', { ref, error: describeError(error) });
154
+ deps.logger?.debug('ds-balance: credential describe failed', {
155
+ ref,
156
+ error: describeError(error),
157
+ });
149
158
  return null;
150
159
  }
151
160
  }
@@ -169,7 +178,10 @@ export async function handleConfigGet(_request, deps) {
169
178
  return json(await configBody(requestId, deps));
170
179
  }
171
180
  catch (error) {
172
- deps.logger?.error('ds-balance: config read handler failed', { requestId, error: describeError(error) });
181
+ deps.logger?.error('ds-balance: config read handler failed', {
182
+ requestId,
183
+ error: describeError(error),
184
+ });
173
185
  return json({ requestId, schemaVersion: SCHEMA_VERSION, error: toWireError(classify(error)) });
174
186
  }
175
187
  }
@@ -180,10 +192,20 @@ export async function handleConfigUpdate(request, deps) {
180
192
  try {
181
193
  const body = await readJsonObject(request);
182
194
  if (body === null)
183
- return json({ requestId, error: { code: 'VALIDATION', message: 'body must be a JSON object', retryable: false } }, 422);
195
+ return json({
196
+ requestId,
197
+ error: { code: 'VALIDATION', message: 'body must be a JSON object', retryable: false },
198
+ }, 422);
184
199
  const unknown = Object.keys(body).filter((key) => !CONFIG_FIELDS.some((field) => field === key));
185
200
  if (unknown.length > 0) {
186
- return json({ requestId, error: { code: 'VALIDATION', message: `unknown config field: ${unknown.join(', ')}`, retryable: false } }, 422);
201
+ return json({
202
+ requestId,
203
+ error: {
204
+ code: 'VALIDATION',
205
+ message: `unknown config field: ${unknown.join(', ')}`,
206
+ retryable: false,
207
+ },
208
+ }, 422);
187
209
  }
188
210
  patch = body;
189
211
  if (Object.keys(patch).length > 0) {
@@ -197,7 +219,10 @@ export async function handleConfigUpdate(request, deps) {
197
219
  }
198
220
  }
199
221
  catch (error) {
200
- deps.logger?.warn('ds-balance: config write rejected', { requestId, error: describeError(error) });
222
+ deps.logger?.warn('ds-balance: config write rejected', {
223
+ requestId,
224
+ error: describeError(error),
225
+ });
201
226
  return json({ requestId, error: toWireError(classify(error)) }, 422);
202
227
  }
203
228
  // 写完立刻回读,让调用方看到落盘后的真实值(掩码同上)。
@@ -205,7 +230,10 @@ export async function handleConfigUpdate(request, deps) {
205
230
  return json(await configBody(requestId, deps));
206
231
  }
207
232
  catch (error) {
208
- deps.logger?.error('ds-balance: config readback failed', { requestId, error: describeError(error) });
233
+ deps.logger?.error('ds-balance: config readback failed', {
234
+ requestId,
235
+ error: describeError(error),
236
+ });
209
237
  return json({ requestId, schemaVersion: SCHEMA_VERSION, error: toWireError(classify(error)) });
210
238
  }
211
239
  }
@@ -216,9 +244,13 @@ export async function handleTestConnection(request, deps) {
216
244
  const body = await readJsonObject(request);
217
245
  const config = deps.config.current();
218
246
  const rawBaseUrl = body?.baseUrl;
219
- const baseUrl = typeof rawBaseUrl === 'string' && rawBaseUrl.trim() !== '' ? rawBaseUrl.trim() : endpointOf(config);
247
+ const baseUrl = typeof rawBaseUrl === 'string' && rawBaseUrl.trim() !== ''
248
+ ? rawBaseUrl.trim()
249
+ : endpointOf(config);
220
250
  const rawTimeout = Number(body?.timeoutMs);
221
- const timeoutMs = Number.isFinite(rawTimeout) && rawTimeout >= TEST_TIMEOUT_RANGE.min && rawTimeout <= TEST_TIMEOUT_RANGE.max
251
+ const timeoutMs = Number.isFinite(rawTimeout) &&
252
+ rawTimeout >= TEST_TIMEOUT_RANGE.min &&
253
+ rawTimeout <= TEST_TIMEOUT_RANGE.max
222
254
  ? Math.trunc(rawTimeout)
223
255
  : deps.config.timeoutMs();
224
256
  const rawApiKey = body?.apiKey;
@@ -231,16 +263,26 @@ export async function handleTestConnection(request, deps) {
231
263
  return json({ requestId, schemaVersion: SCHEMA_VERSION, ...result });
232
264
  }
233
265
  catch (error) {
234
- deps.logger?.warn('ds-balance: test-connection handler failed', { requestId, error: describeError(error) });
266
+ deps.logger?.warn('ds-balance: test-connection handler failed', {
267
+ requestId,
268
+ error: describeError(error),
269
+ });
235
270
  const info = classify(error);
236
- return json({ requestId, schemaVersion: SCHEMA_VERSION, ok: false, latencyMs: 0, code: info.code, message: info.message });
271
+ return json({
272
+ requestId,
273
+ schemaVersion: SCHEMA_VERSION,
274
+ ok: false,
275
+ latencyMs: 0,
276
+ code: info.code,
277
+ message: info.message,
278
+ });
237
279
  }
238
280
  }
239
281
  /** `GET /api/v1/healthz`。 */
240
282
  export async function handleHealthz(_request, deps) {
241
283
  const requestId = newRequestId();
242
284
  const status = deps.service.status();
243
- let store = { ok: false, detail: 'health check failed' };
285
+ let store;
244
286
  try {
245
287
  const health = await deps.store.health();
246
288
  store = { ok: health.ok, detail: health.detail ?? null };
package/lib/index.js CHANGED
@@ -11,7 +11,7 @@ import { randomBytes } from 'node:crypto';
11
11
  import { credentialRef } from '@deepseek-ai/dsh-credentials';
12
12
  import { dshHomePath } from '@deepseek-ai/dsh-home-paths';
13
13
  import { ENTRY_ID, THRESHOLD_PAIRS, readConfig, resolveThresholdPairs, } from './config.js';
14
- import { DomainCoreStore } from './adapters/domain-core-store.js';
14
+ import { DomainCoreStore, } from './adapters/domain-core-store.js';
15
15
  import { HttpDeepSeekClient } from './adapters/http-deepseek-client.js';
16
16
  import { SALT_BYTES, SALT_ENCODING, loadOrCreateSalt } from './adapters/salt-file.js';
17
17
  import { createConsoleLogger } from './adapters/console-logger.js';
@@ -65,7 +65,9 @@ async function resolveSalt(logger) {
65
65
  return await loadOrCreateSalt({ path: dshHomePath(SALT_FILE_NAME) });
66
66
  }
67
67
  catch (error) {
68
- logger.error('ds-balance: salt file unavailable, using an ephemeral salt', { error: describeError(error) });
68
+ logger.error('ds-balance: salt file unavailable, using an ephemeral salt', {
69
+ error: describeError(error),
70
+ });
69
71
  return randomBytes(SALT_BYTES).toString(SALT_ENCODING);
70
72
  }
71
73
  }
@@ -97,7 +99,9 @@ function createConfigSource(ctx, refs, logger) {
97
99
  if (warned.has(pair.currency))
98
100
  continue;
99
101
  warned.add(pair.currency);
100
- logger.warn('ds-balance: ' + pair.currency + ' critical must stay below warn; this pair fell back to its defaults', {
102
+ logger.warn('ds-balance: ' +
103
+ pair.currency +
104
+ ' critical must stay below warn; this pair fell back to its defaults', {
101
105
  warn: pair.defaultWarn,
102
106
  critical: pair.defaultCritical,
103
107
  });
@@ -143,7 +147,9 @@ function credentialsPort(ctx) {
143
147
  async resolve(ref) {
144
148
  const resolved = await service.resolve(credentialRef(ref));
145
149
  // 未配置时官方回 `undefined`;端口的约定是「值可能为空串」,交给上游判空。
146
- return resolved === undefined ? { value: '', source: 'unset' } : { value: resolved.value, source: resolved.source };
150
+ return resolved === undefined
151
+ ? { value: '', source: 'unset' }
152
+ : { value: resolved.value, source: resolved.source };
147
153
  },
148
154
  describe: (ref) => service.describe(credentialRef(ref)),
149
155
  };
@@ -166,14 +172,16 @@ function connectStorage(ctx) {
166
172
  ctx.inject(['storageDomain'], (storageCtx) => {
167
173
  storageCtx.effect(() => {
168
174
  handle.facility = storageCtx.storageDomain;
169
- return () => { handle.facility = undefined; };
175
+ return () => {
176
+ handle.facility = undefined;
177
+ };
170
178
  }, 'ds-balance: storage facility');
171
179
  });
172
180
  return async (spec) => {
173
181
  const facility = handle.facility;
174
182
  if (facility === undefined)
175
183
  throw new Error('storageDomain service is unavailable');
176
- return await facility.open(spec);
184
+ return (await facility.open(spec));
177
185
  };
178
186
  }
179
187
  /**
@@ -207,7 +215,14 @@ export async function apply(ctx, refs) {
207
215
  // 由 healthz 的 metrics 段暴露(见 .agents/notes 的决策)。
208
216
  const metrics = new MemoryMetrics();
209
217
  const service = new BalanceService({
210
- client, store, keys, config: configService, clock: systemClock, salt, logger, metrics,
218
+ client,
219
+ store,
220
+ keys,
221
+ config: configService,
222
+ clock: systemClock,
223
+ salt,
224
+ logger,
225
+ metrics,
211
226
  });
212
227
  const scheduler = new Scheduler({ target: service, logger });
213
228
  ctx.effect(() => {
@@ -235,9 +250,19 @@ export async function apply(ctx, refs) {
235
250
  ctx.inject(['connection'], (connectionCtx) => {
236
251
  connectionCtx.effect(() => {
237
252
  const disposeRoutes = registerHttpRoutes(connectionCtx, {
238
- service, config: configService, keys, client, store, scheduler, logger, metrics, credentials,
253
+ service,
254
+ config: configService,
255
+ keys,
256
+ client,
257
+ store,
258
+ scheduler,
259
+ logger,
260
+ metrics,
261
+ credentials,
239
262
  });
240
- return () => { void disposeRoutes(); };
263
+ return () => {
264
+ void disposeRoutes();
265
+ };
241
266
  }, 'ds-balance: http routes');
242
267
  });
243
268
  }
@@ -5,7 +5,7 @@
5
5
  * **对外永不抛错**:一切失败都变成 `view.state` 与 `view.error`。
6
6
  * @module dsh-ds-balance/services/balance-service
7
7
  */
8
- import { classify, describeError, parseRetryAfter } from '../domain/errors.js';
8
+ import { classify, describeError, parseRetryAfter, } from '../domain/errors.js';
9
9
  import { normalize } from '../domain/normalize.js';
10
10
  import { pickBalance } from '../domain/select.js';
11
11
  import { severityOf, thresholdsFor } from '../domain/severity.js';
@@ -67,7 +67,9 @@ export class BalanceService {
67
67
  this.publishGauge();
68
68
  }
69
69
  catch (error) {
70
- this.options.logger?.debug('ds-balance: no snapshot restored', { error: describeError(error) });
70
+ this.options.logger?.debug('ds-balance: no snapshot restored', {
71
+ error: describeError(error),
72
+ });
71
73
  }
72
74
  }
73
75
  /**
@@ -80,7 +82,9 @@ export class BalanceService {
80
82
  return this.inflight;
81
83
  if (options.force !== true && this.canServeCache())
82
84
  return this.toView(options.currency);
83
- const run = this.fetchOnce(options.currency).finally(() => { this.inflight = null; });
85
+ const run = this.fetchOnce(options.currency).finally(() => {
86
+ this.inflight = null;
87
+ });
84
88
  this.inflight = run;
85
89
  return run;
86
90
  }
@@ -165,7 +169,9 @@ export class BalanceService {
165
169
  if (this.persistWarned)
166
170
  return;
167
171
  this.persistWarned = true;
168
- this.options.logger?.warn('ds-balance: snapshot not persisted, continuing in memory', { error: describeError(error) });
172
+ this.options.logger?.warn('ds-balance: snapshot not persisted, continuing in memory', {
173
+ error: describeError(error),
174
+ });
169
175
  }
170
176
  }
171
177
  /** 记录一次失败,并把状态推到 `stale` 或 `error`。 */
@@ -197,7 +203,9 @@ export class BalanceService {
197
203
  state: this.state,
198
204
  stale: this.state === 'stale',
199
205
  fetchedAt: this.snapshot?.fetchedAt ?? null,
200
- ageMs: this.snapshot === null ? null : Math.max(0, this.options.clock.now() - this.snapshot.fetchedAt),
206
+ ageMs: this.snapshot === null
207
+ ? null
208
+ : Math.max(0, this.options.clock.now() - this.snapshot.fetchedAt),
201
209
  isAvailable: this.snapshot?.isAvailable ?? null,
202
210
  balances: this.snapshot?.balances ?? [],
203
211
  selected: selected === null ? null : { currency: selected.currency, total: selected.total },
@@ -60,7 +60,9 @@ export function nextDelayMs(status, options = {}) {
60
60
  /** 真正的默认定时器。 */
61
61
  const defaultTimers = {
62
62
  set: (callback, ms) => setTimeout(callback, ms),
63
- clear: (handle) => { clearTimeout(handle); },
63
+ clear: (handle) => {
64
+ clearTimeout(handle);
65
+ },
64
66
  };
65
67
  /**
66
68
  * 刷新调度器。
@@ -118,7 +120,9 @@ export class Scheduler {
118
120
  if (!this.running)
119
121
  return;
120
122
  this.nextAt = this.now() + delay;
121
- this.handle = this.timers.set(() => { void this.tick(); }, delay);
123
+ this.handle = this.timers.set(() => {
124
+ void this.tick();
125
+ }, delay);
122
126
  }
123
127
  /** 跑一轮并按状态排下一轮。 */
124
128
  async tick() {
@@ -123,10 +123,10 @@ export interface DetailsGroupProps {
123
123
  children: ReactNode;
124
124
  }
125
125
  /**
126
- * 渲染一个原生 details 折叠块。
126
+ * 渲染二级折叠「自定义设置」。
127
127
  *
128
- * **受控但跟手**:`open` 由 state 持有,用户拨动时从 DOM 读回真实状态,
129
- * 所以卡片每次重渲染(每敲一个字都会)不会把用户展开的块弹回去。
128
+ * **受控但跟手**:`open` 由 state 持有,官方 DisclosureRow 的展开体只在 open 时条件渲染,
129
+ * 卡片每次重渲染(每敲一个字都会)不会把用户展开的块弹回去。
130
130
  * @param props - 标题、初始状态与内容。
131
131
  * @returns 折叠块元素。
132
132
  */
@@ -38,4 +38,4 @@ export interface PercentRingProps {
38
38
  * @param props - 状态、边长与可选提示。
39
39
  * @returns 圆环 svg;自身 aria-hidden,语义名由调用方的 aria-label 提供。
40
40
  */
41
- export declare function PercentRing({ state, marker, ratio, size, title }: PercentRingProps): JSX.Element;
41
+ export declare function PercentRing({ state, marker, ratio, size, title, }: PercentRingProps): JSX.Element;
package/lib/version.d.ts CHANGED
@@ -8,5 +8,5 @@
8
8
  /** 线上 schema 版本。响应里逐条回传,消费方据此判断兼容性。 */
9
9
  export declare const SCHEMA_VERSION = 1;
10
10
  /** 插件版本。与 `package.json` 的 `version` 一致(有测试兜底)。 */
11
- export declare const PLUGIN_VERSION = "2.1.1";
11
+ export declare const PLUGIN_VERSION = "2.1.2";
12
12
  //# sourceMappingURL=version.d.ts.map
package/lib/version.js CHANGED
@@ -8,5 +8,5 @@
8
8
  /** 线上 schema 版本。响应里逐条回传,消费方据此判断兼容性。 */
9
9
  export const SCHEMA_VERSION = 1;
10
10
  /** 插件版本。与 `package.json` 的 `version` 一致(有测试兜底)。 */
11
- export const PLUGIN_VERSION = '2.1.1';
11
+ export const PLUGIN_VERSION = '2.1.2';
12
12
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ds-balance",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "icon": "./icon.svg",
5
5
  "description": "DSH plugin: shows the DeepSeek account balance at the bottom of the sidebar, with a configuration card on the Plugins page.",
6
6
  "keywords": [
@@ -67,6 +67,9 @@
67
67
  "test": "npm run build && vitest run",
68
68
  "test:watch": "vitest",
69
69
  "test:contract": "vitest run --config vitest.contract.config.ts",
70
+ "lint": "eslint .",
71
+ "format": "prettier --write .",
72
+ "format:check": "prettier --check .",
70
73
  "check:release": "node scripts/check-release.mjs",
71
74
  "check:declaration": "node scripts/check-declaration.mjs"
72
75
  },
@@ -101,14 +104,20 @@
101
104
  "@deepseek-ai/dsh-settings": ">=0.1.7-alpha.1",
102
105
  "@deepseek-ai/dsh-storage-domain": ">=0.1.7-alpha.1",
103
106
  "@deepseek-ai/schemastery": "^3.18.3",
107
+ "@eslint/js": "^10.0.1",
104
108
  "@types/node": "^20.0.0",
105
109
  "@types/react": "~18.3.1",
106
110
  "@types/react-dom": "~18.3.1",
107
111
  "clsx": "^2.1.1",
108
112
  "esbuild": "^0.21.5",
113
+ "eslint": "^10.11.0",
114
+ "eslint-config-prettier": "^10.1.8",
115
+ "globals": "^17.12.0",
116
+ "prettier": "^3.9.9",
109
117
  "react": "^18.3.1",
110
118
  "react-dom": "^18.3.1",
111
119
  "typescript": "^5.4.0",
120
+ "typescript-eslint": "^8.70.1",
112
121
  "vitest": "^2.0.0"
113
122
  },
114
123
  "dependencies": {