@jxgame2020/dsh-token-quota 0.1.39 → 0.1.40

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/client.js CHANGED
@@ -7,14 +7,6 @@ window.__ModuleLoader__.load({
7
7
  let _deepseek_ai_dsh_client_store = require("@deepseek-ai/dsh-client-store");
8
8
  let react_jsx_runtime = require("react/jsx-runtime");
9
9
  let react = require("react");
10
- //#region lib/types/types.js
11
- /**
12
- * Provider route keys the host knows how to query for account balance. A
13
- * directory provider outside this list renders as "not integrated for query"
14
- * in the balance window.
15
- */
16
- const TOKEN_QUOTA_BALANCE_PROVIDERS = ["deepseek"];
17
- //#endregion
18
10
  //#region lib/types/client/store.js
19
11
  /**
20
12
  * Token-quota panel store: the shared, remount-surviving view state. The
@@ -123,7 +115,7 @@ window.__ModuleLoader__.load({
123
115
  d.musicOnlyCurrentSession = only;
124
116
  },
125
117
  setUpgrade: (d, upgrade) => {
126
- const normalized = upgrade !== null && upgrade.latestVersion !== "" && upgrade.latestVersion !== "0.1.39" ? upgrade : null;
118
+ const normalized = upgrade !== null && upgrade.latestVersion !== "" && upgrade.latestVersion !== "0.1.40" ? upgrade : null;
127
119
  if (d.upgrade === null || d.upgrade.latestVersion !== normalized?.latestVersion) d.upgradeDismissed = false;
128
120
  d.upgrade = normalized;
129
121
  },
@@ -253,142 +245,142 @@ window.__ModuleLoader__.load({
253
245
  document.head.appendChild(tag);
254
246
  }
255
247
  var TokenQuotaPanel_module_css_default = {
256
- "logPagerBtn": "NPLLIq_logPagerBtn",
257
- "selectBtn": "NPLLIq_selectBtn",
258
- "dialogOverlay": "NPLLIq_dialogOverlay",
259
- "fillIdle": "NPLLIq_fillIdle",
260
- "balanceBarText": "NPLLIq_balanceBarText",
261
- "balanceWinBody": "NPLLIq_balanceWinBody",
262
- "monitorRow": "NPLLIq_monitorRow",
263
- "resetSelect": "NPLLIq_resetSelect",
264
- "upgradeCommandRow": "NPLLIq_upgradeCommandRow",
265
- "rowDrag": "NPLLIq_rowDrag",
266
- "logClearDate": "NPLLIq_logClearDate",
267
- "headerText": "NPLLIq_headerText",
268
- "noteWinBtn": "NPLLIq_noteWinBtn",
269
- "fillOver": "NPLLIq_fillOver",
270
- "copyBtn": "NPLLIq_copyBtn",
271
- "balanceBarLow": "NPLLIq_balanceBarLow",
272
- "switch": "NPLLIq_switch",
273
- "dialogHeader": "NPLLIq_dialogHeader",
274
- "logSearch": "NPLLIq_logSearch",
248
+ "noteWinResize": "NPLLIq_noteWinResize",
275
249
  "dialogClose": "NPLLIq_dialogClose",
276
- "logPagerInfo": "NPLLIq_logPagerInfo",
277
- "balanceToggleOpen": "NPLLIq_balanceToggleOpen",
278
- "logPagerControls": "NPLLIq_logPagerControls",
279
- "versionNew": "NPLLIq_versionNew",
280
- "headerActions": "NPLLIq_headerActions",
281
- "logPager": "NPLLIq_logPager",
282
- "upgradeBannerFlash": "NPLLIq_upgradeBannerFlash",
283
- "logScroll": "NPLLIq_logScroll",
284
- "balanceRefreshAll": "NPLLIq_balanceRefreshAll",
285
- "noteWinTitle": "NPLLIq_noteWinTitle",
286
- "logDialog": "NPLLIq_logDialog",
287
- "upgradeCommands": "NPLLIq_upgradeCommands",
288
- "resetRow": "NPLLIq_resetRow",
289
- "noteWinText": "NPLLIq_noteWinText",
290
- "save": "NPLLIq_save",
291
- "balanceBarError": "NPLLIq_balanceBarError",
292
- "row": "NPLLIq_row",
293
- "checkBtn": "NPLLIq_checkBtn",
294
- "balanceValueLow": "NPLLIq_balanceValueLow",
295
- "notesBar": "NPLLIq_notesBar",
296
- "dialogActions": "NPLLIq_dialogActions",
297
- "logDayCol": "NPLLIq_logDayCol",
298
- "bar": "NPLLIq_bar",
299
- "upgradeBannerText": "NPLLIq_upgradeBannerText",
300
- "noteNew": "NPLLIq_noteNew",
301
- "logModelCol": "NPLLIq_logModelCol",
302
- "logTable": "NPLLIq_logTable",
250
+ "balanceStateHint": "NPLLIq_balanceStateHint",
251
+ "panel": "NPLLIq_panel",
303
252
  "noteMenuBackdrop": "NPLLIq_noteMenuBackdrop",
304
- "noteChip": "NPLLIq_noteChip",
305
- "noteWinCollapsed": "NPLLIq_noteWinCollapsed",
306
- "currentBadge": "NPLLIq_currentBadge",
307
- "input": "NPLLIq_input",
308
- "balanceSortBtn": "NPLLIq_balanceSortBtn",
253
+ "notesBar": "NPLLIq_notesBar",
254
+ "dialogTabActive": "NPLLIq_dialogTabActive",
255
+ "balanceWinHeader": "NPLLIq_balanceWinHeader",
256
+ "balanceRowValue": "NPLLIq_balanceRowValue",
257
+ "fullNotice": "NPLLIq_fullNotice",
258
+ "logUsedCol": "NPLLIq_logUsedCol",
259
+ "upgradeCommands": "NPLLIq_upgradeCommands",
260
+ "rowHeader": "NPLLIq_rowHeader",
309
261
  "fillWarn": "NPLLIq_fillWarn",
310
- "gearBtn": "NPLLIq_gearBtn",
311
- "monitorList": "NPLLIq_monitorList",
312
- "checkUpdatesRow": "NPLLIq_checkUpdatesRow",
313
- "dialogTab": "NPLLIq_dialogTab",
314
- "subtitle": "NPLLIq_subtitle",
315
- "dialog": "NPLLIq_dialog",
316
- "controls": "NPLLIq_controls",
317
- "noticeError": "NPLLIq_noticeError",
318
- "tabLabel": "NPLLIq_tabLabel",
319
- "checkErrorHint": "NPLLIq_checkErrorHint",
320
- "balanceStateError": "NPLLIq_balanceStateError",
321
- "logHeader": "NPLLIq_logHeader",
322
- "logClearDialog": "NPLLIq_logClearDialog",
323
- "logClearBtn": "NPLLIq_logClearBtn",
324
- "noteWinGripBar": "NPLLIq_noteWinGripBar",
325
- "noteWinResize": "NPLLIq_noteWinResize",
326
- "collapse": "NPLLIq_collapse",
327
- "monitorHint": "NPLLIq_monitorHint",
262
+ "logDialog": "NPLLIq_logDialog",
263
+ "balanceRowHeader": "NPLLIq_balanceRowHeader",
264
+ "monitorRow": "NPLLIq_monitorRow",
265
+ "logPager": "NPLLIq_logPager",
328
266
  "title": "NPLLIq_title",
329
- "noteChipOpen": "NPLLIq_noteChipOpen",
267
+ "logPageSizeSelect": "NPLLIq_logPageSizeSelect",
268
+ "balanceRowRefresh": "NPLLIq_balanceRowRefresh",
269
+ "balanceToggle": "NPLLIq_balanceToggle",
270
+ "upgradeBannerFlash": "NPLLIq_upgradeBannerFlash",
271
+ "upgradeBannerText": "NPLLIq_upgradeBannerText",
330
272
  "copyBtnDone": "NPLLIq_copyBtnDone",
273
+ "checkBtn": "NPLLIq_checkBtn",
274
+ "logPagerBtn": "NPLLIq_logPagerBtn",
275
+ "dialogOverlay": "NPLLIq_dialogOverlay",
331
276
  "noteWinTitleText": "NPLLIq_noteWinTitleText",
332
- "balanceLastLine": "NPLLIq_balanceLastLine",
333
- "noteNewPlus": "NPLLIq_noteNewPlus",
334
- "rowDropTarget": "NPLLIq_rowDropTarget",
335
- "balanceValueError": "NPLLIq_balanceValueError",
336
- "upgradeBanner": "NPLLIq_upgradeBanner",
337
- "logToolbar": "NPLLIq_logToolbar",
338
- "version": "NPLLIq_version",
339
- "balanceRowRefresh": "NPLLIq_balanceRowRefresh",
340
- "balanceStateHint": "NPLLIq_balanceStateHint",
341
- "noteWinBar": "NPLLIq_noteWinBar",
342
- "rowHeader": "NPLLIq_rowHeader",
343
- "balanceWin": "NPLLIq_balanceWin",
344
- "balanceWinTitle": "NPLLIq_balanceWinTitle",
345
- "balanceBar": "NPLLIq_balanceBar",
346
- "balanceRowName": "NPLLIq_balanceRowName",
347
- "dialogTabs": "NPLLIq_dialogTabs",
348
- "noteMenu": "NPLLIq_noteMenu",
277
+ "balanceBarBadge": "NPLLIq_balanceBarBadge",
278
+ "controls": "NPLLIq_controls",
279
+ "upgradeHint": "NPLLIq_upgradeHint",
280
+ "resetRow": "NPLLIq_resetRow",
281
+ "dialogHint": "NPLLIq_dialogHint",
282
+ "rowDrag": "NPLLIq_rowDrag",
283
+ "balanceBarLow": "NPLLIq_balanceBarLow",
284
+ "bar": "NPLLIq_bar",
285
+ "panelDim": "NPLLIq_panelDim",
286
+ "noteMenuItem": "NPLLIq_noteMenuItem",
287
+ "currentBadge": "NPLLIq_currentBadge",
349
288
  "rowDragging": "NPLLIq_rowDragging",
289
+ "upgradeCommand": "NPLLIq_upgradeCommand",
290
+ "rowMeta": "NPLLIq_rowMeta",
350
291
  "panelDimStrong": "NPLLIq_panelDimStrong",
351
- "dialogSection": "NPLLIq_dialogSection",
352
- "dialogLabel": "NPLLIq_dialogLabel",
353
- "monitorName": "NPLLIq_monitorName",
292
+ "checkErrorHint": "NPLLIq_checkErrorHint",
293
+ "logDayCol": "NPLLIq_logDayCol",
294
+ "balanceBarRefresh": "NPLLIq_balanceBarRefresh",
295
+ "header": "NPLLIq_header",
296
+ "checkUpdatesLabel": "NPLLIq_checkUpdatesLabel",
354
297
  "logPagerPages": "NPLLIq_logPagerPages",
355
- "balanceValueOk": "NPLLIq_balanceValueOk",
356
- "body": "NPLLIq_body",
298
+ "noteNewPlus": "NPLLIq_noteNewPlus",
299
+ "balanceToggleOpen": "NPLLIq_balanceToggleOpen",
300
+ "logTable": "NPLLIq_logTable",
301
+ "version": "NPLLIq_version",
302
+ "settingsBtn": "NPLLIq_settingsBtn",
357
303
  "settingsBtnActive": "NPLLIq_settingsBtnActive",
358
- "logPageSizeSelect": "NPLLIq_logPageSizeSelect",
359
- "panel": "NPLLIq_panel",
360
- "balanceRowHeader": "NPLLIq_balanceRowHeader",
361
- "balanceBarDim": "NPLLIq_balanceBarDim",
304
+ "balanceValueOk": "NPLLIq_balanceValueOk",
305
+ "resetSelect": "NPLLIq_resetSelect",
306
+ "noteWinTitle": "NPLLIq_noteWinTitle",
307
+ "logPagerInfo": "NPLLIq_logPagerInfo",
308
+ "noteMenu": "NPLLIq_noteMenu",
309
+ "upgradeCommandRow": "NPLLIq_upgradeCommandRow",
310
+ "dialogTabs": "NPLLIq_dialogTabs",
311
+ "balanceRow": "NPLLIq_balanceRow",
312
+ "logPagerControls": "NPLLIq_logPagerControls",
313
+ "monitorHint": "NPLLIq_monitorHint",
314
+ "noticeError": "NPLLIq_noticeError",
315
+ "tabLabel": "NPLLIq_tabLabel",
316
+ "tab": "NPLLIq_tab",
317
+ "dialog": "NPLLIq_dialog",
318
+ "balanceSortBtn": "NPLLIq_balanceSortBtn",
319
+ "fillOver": "NPLLIq_fillOver",
362
320
  "dialogBtn": "NPLLIq_dialogBtn",
363
- "panelDim": "NPLLIq_panelDim",
364
- "balanceWinHeader": "NPLLIq_balanceWinHeader",
365
- "radioRow": "NPLLIq_radioRow",
321
+ "noteWin": "NPLLIq_noteWin",
322
+ "save": "NPLLIq_save",
323
+ "dialogTab": "NPLLIq_dialogTab",
324
+ "noteWinText": "NPLLIq_noteWinText",
325
+ "balanceWin": "NPLLIq_balanceWin",
326
+ "logClearDate": "NPLLIq_logClearDate",
327
+ "monitorName": "NPLLIq_monitorName",
328
+ "tabIcon": "NPLLIq_tabIcon",
329
+ "logClearBtn": "NPLLIq_logClearBtn",
366
330
  "dialogBtnPrimary": "NPLLIq_dialogBtnPrimary",
367
- "header": "NPLLIq_header",
368
- "fullNotice": "NPLLIq_fullNotice",
369
- "upgradeCommand": "NPLLIq_upgradeCommand",
370
- "dialogTabActive": "NPLLIq_dialogTabActive",
371
- "balanceRowValue": "NPLLIq_balanceRowValue",
372
- "balanceToggle": "NPLLIq_balanceToggle",
373
- "rowMeta": "NPLLIq_rowMeta",
374
- "balanceBarRefresh": "NPLLIq_balanceBarRefresh",
375
- "checkUpdatesLabel": "NPLLIq_checkUpdatesLabel",
331
+ "body": "NPLLIq_body",
376
332
  "rowName": "NPLLIq_rowName",
377
- "logUsedCol": "NPLLIq_logUsedCol",
378
- "noteWin": "NPLLIq_noteWin",
379
- "notice": "NPLLIq_notice",
380
- "settingsDialog": "NPLLIq_settingsDialog",
381
- "dialogHint": "NPLLIq_dialogHint",
382
- "balanceBarBadge": "NPLLIq_balanceBarBadge",
383
- "noteMenuItem": "NPLLIq_noteMenuItem",
384
- "upgradeClose": "NPLLIq_upgradeClose",
385
- "tab": "NPLLIq_tab",
333
+ "collapse": "NPLLIq_collapse",
334
+ "dialogHeader": "NPLLIq_dialogHeader",
335
+ "balanceWinBody": "NPLLIq_balanceWinBody",
336
+ "balanceBarDim": "NPLLIq_balanceBarDim",
337
+ "noteWinCollapsed": "NPLLIq_noteWinCollapsed",
338
+ "switch": "NPLLIq_switch",
339
+ "rowDropTarget": "NPLLIq_rowDropTarget",
386
340
  "dialogTitle": "NPLLIq_dialogTitle",
387
- "balanceRow": "NPLLIq_balanceRow",
341
+ "checkUpdatesRow": "NPLLIq_checkUpdatesRow",
388
342
  "dialogBody": "NPLLIq_dialogBody",
389
- "settingsBtn": "NPLLIq_settingsBtn",
390
- "upgradeHint": "NPLLIq_upgradeHint",
391
- "tabIcon": "NPLLIq_tabIcon"
343
+ "balanceBarText": "NPLLIq_balanceBarText",
344
+ "settingsDialog": "NPLLIq_settingsDialog",
345
+ "logModelCol": "NPLLIq_logModelCol",
346
+ "logHeader": "NPLLIq_logHeader",
347
+ "balanceBar": "NPLLIq_balanceBar",
348
+ "upgradeBanner": "NPLLIq_upgradeBanner",
349
+ "balanceBarError": "NPLLIq_balanceBarError",
350
+ "monitorList": "NPLLIq_monitorList",
351
+ "balanceRefreshAll": "NPLLIq_balanceRefreshAll",
352
+ "dialogSection": "NPLLIq_dialogSection",
353
+ "balanceValueError": "NPLLIq_balanceValueError",
354
+ "balanceLastLine": "NPLLIq_balanceLastLine",
355
+ "upgradeClose": "NPLLIq_upgradeClose",
356
+ "notice": "NPLLIq_notice",
357
+ "balanceStateError": "NPLLIq_balanceStateError",
358
+ "fillIdle": "NPLLIq_fillIdle",
359
+ "noteChipOpen": "NPLLIq_noteChipOpen",
360
+ "versionNew": "NPLLIq_versionNew",
361
+ "balanceRowName": "NPLLIq_balanceRowName",
362
+ "headerText": "NPLLIq_headerText",
363
+ "row": "NPLLIq_row",
364
+ "input": "NPLLIq_input",
365
+ "balanceWinTitle": "NPLLIq_balanceWinTitle",
366
+ "headerActions": "NPLLIq_headerActions",
367
+ "noteWinBtn": "NPLLIq_noteWinBtn",
368
+ "radioRow": "NPLLIq_radioRow",
369
+ "balanceValueLow": "NPLLIq_balanceValueLow",
370
+ "gearBtn": "NPLLIq_gearBtn",
371
+ "dialogLabel": "NPLLIq_dialogLabel",
372
+ "logScroll": "NPLLIq_logScroll",
373
+ "noteWinGripBar": "NPLLIq_noteWinGripBar",
374
+ "noteWinBar": "NPLLIq_noteWinBar",
375
+ "subtitle": "NPLLIq_subtitle",
376
+ "copyBtn": "NPLLIq_copyBtn",
377
+ "selectBtn": "NPLLIq_selectBtn",
378
+ "logSearch": "NPLLIq_logSearch",
379
+ "logClearDialog": "NPLLIq_logClearDialog",
380
+ "noteNew": "NPLLIq_noteNew",
381
+ "dialogActions": "NPLLIq_dialogActions",
382
+ "logToolbar": "NPLLIq_logToolbar",
383
+ "noteChip": "NPLLIq_noteChip"
392
384
  };
393
385
  //#endregion
394
386
  //#region lib/types/client/TokenQuotaPanel.js
@@ -879,7 +871,7 @@ window.__ModuleLoader__.load({
879
871
  for (const provider of providers) {
880
872
  const bal = balances.find((b) => b.provider === provider);
881
873
  if (bal !== void 0) map.set(provider, bal);
882
- else if (TOKEN_QUOTA_BALANCE_PROVIDERS.includes(provider)) map.set(provider, "loading");
874
+ else if (balances.length === 0) map.set(provider, "loading");
883
875
  else map.set(provider, "unsupported");
884
876
  }
885
877
  return map;
@@ -1058,13 +1050,13 @@ window.__ModuleLoader__.load({
1058
1050
  title: t("upgradeAvailableHint"),
1059
1051
  children: [
1060
1052
  "v",
1061
- "0.1.39",
1053
+ "0.1.40",
1062
1054
  " → v",
1063
1055
  upgrade.latestVersion
1064
1056
  ]
1065
1057
  }) : (0, react_jsx_runtime.jsxs)("span", {
1066
1058
  className: TokenQuotaPanel_module_css_default.version,
1067
- children: ["v", "0.1.39"]
1059
+ children: ["v", "0.1.40"]
1068
1060
  })]
1069
1061
  })
1070
1062
  }),
package/lib/index.js CHANGED
@@ -276,23 +276,8 @@ function defaultStoragePath() {
276
276
  function usageTokens(usage) {
277
277
  return usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0) + usage.outputTokens;
278
278
  }
279
- /**
280
- * Daily token-quota service.
281
- *
282
- * Mount it beside the other rows (`@jxgame2020/dsh-token-quota`) and write
283
- * per-model limits through the `token-quota` settings namespace.
284
- */
285
- /**
286
- * Providers the host can query for account balance, keyed by route key. Each
287
- * entry names the credential reference (environment-variable name), the
288
- * balance endpoint base, and the optional `*_BASE_URL` override variable.
289
- * Directory providers outside this map render as "not integrated for query".
290
- */
291
- const BALANCE_SOURCES = { deepseek: {
292
- keyEnv: "DEEPSEEK_API_KEY",
293
- baseURL: "https://api.deepseek.com",
294
- baseEnv: "DEEPSEEK_BASE_URL"
295
- } };
279
+ /** DeepSeek's public API host; used to recognise DeepSeek-family providers. */
280
+ const DEEPSEEK_BALANCE_HOST = "api.deepseek.com";
296
281
  var TokenQuotaService = class TokenQuotaService extends Service {
297
282
  static Config = z.object({ storagePath: z.string().default("") });
298
283
  storagePath;
@@ -525,7 +510,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
525
510
  if (typeof parsed === "object" && parsed !== null && typeof parsed.provider === "string") provider = parsed.provider;
526
511
  }
527
512
  } catch {}
528
- (provider !== void 0 && provider in BALANCE_SOURCES ? this.refreshProviderBalance(provider) : this.refreshAllBalances()).finally(() => {
513
+ (provider !== void 0 ? this.refreshProviderBalance(provider) : this.refreshAllBalances()).finally(() => {
529
514
  const body = JSON.stringify(this.readSnapshot());
530
515
  res.writeHead(200, {
531
516
  "content-type": "application/json",
@@ -619,7 +604,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
619
604
  return this.upgradeCheck;
620
605
  }
621
606
  async doRefreshUpgrade() {
622
- const current = "0.1.39";
607
+ const current = "0.1.40";
623
608
  let latest;
624
609
  try {
625
610
  const response = await fetch(REGISTRY_LATEST_URL, {
@@ -692,9 +677,46 @@ var TokenQuotaService = class TokenQuotaService extends Service {
692
677
  };
693
678
  schedule();
694
679
  }
695
- /** Refresh every provider the host knows how to query. */
680
+ /** Refresh every DeepSeek-family provider found in the model directory. */
696
681
  refreshAllBalances() {
697
- return Promise.all(Object.keys(BALANCE_SOURCES).map((provider) => this.refreshProviderBalance(provider))).then(() => void 0);
682
+ return Promise.all([...this.discoverBalanceSources().values()].map((source) => this.refreshProviderBalance(source.provider, source))).then(() => void 0);
683
+ }
684
+ /**
685
+ * Discover every provider in the model directory that looks like DeepSeek,
686
+ * together with its own credential reference and endpoint base. This is what
687
+ * decides which providers are queried: a provider qualifies when its base URL
688
+ * points at DeepSeek's API host or its credential reference is named with the
689
+ * `DEEPSEEK` prefix, and it must declare an `apiKeyEnv`.
690
+ */
691
+ discoverBalanceSources() {
692
+ const found = /* @__PURE__ */ new Map();
693
+ const llm = this.ctx.get("llm");
694
+ const settings = this.ctx.get("settings");
695
+ const fallback = () => ({
696
+ provider: "deepseek",
697
+ keyEnv: "DEEPSEEK_API_KEY",
698
+ baseURL: process.env.DEEPSEEK_BASE_URL ?? `https://${DEEPSEEK_BALANCE_HOST}`
699
+ });
700
+ if (llm?.listConfigurableProviders === void 0) {
701
+ found.set("deepseek", fallback());
702
+ return found;
703
+ }
704
+ for (const entry of llm.listConfigurableProviders()) try {
705
+ let profile = settings?.get(entry.settingsNs);
706
+ for (const segment of entry.settingsPath ?? []) profile = profile !== null && typeof profile === "object" ? profile[segment] : void 0;
707
+ if (profile === null || typeof profile !== "object") continue;
708
+ const record = profile;
709
+ const keyEnv = typeof record.apiKeyEnv === "string" && record.apiKeyEnv !== "" ? record.apiKeyEnv : void 0;
710
+ const baseURL = typeof record.baseURL === "string" && record.baseURL !== "" ? record.baseURL : void 0;
711
+ if (!(baseURL !== void 0 && baseURL.includes(DEEPSEEK_BALANCE_HOST) || keyEnv !== void 0 && keyEnv.startsWith("DEEPSEEK") || entry.provider.startsWith("deepseek")) || keyEnv === void 0) continue;
712
+ found.set(entry.provider, {
713
+ provider: entry.provider,
714
+ keyEnv,
715
+ baseURL: baseURL ?? `https://${DEEPSEEK_BALANCE_HOST}`
716
+ });
717
+ } catch {}
718
+ if (found.size === 0) found.set("deepseek", fallback());
719
+ return found;
698
720
  }
699
721
  /**
700
722
  * Refresh one provider's cached account balance. The API key is resolved
@@ -702,9 +724,9 @@ var TokenQuotaService = class TokenQuotaService extends Service {
702
724
  * environment as a fallback, so the Models page key is used with nothing to
703
725
  * type. A failed fetch keeps the previous value and marks it as an error.
704
726
  */
705
- async refreshProviderBalance(provider) {
706
- const source = BALANCE_SOURCES[provider];
707
- if (source === void 0) return;
727
+ async refreshProviderBalance(provider, source) {
728
+ const resolved = source ?? this.discoverBalanceSources().get(provider);
729
+ if (resolved === void 0) return;
708
730
  const existing = this.balanceFetches.get(provider);
709
731
  if (existing !== void 0) return existing;
710
732
  const run = async () => {
@@ -712,9 +734,9 @@ var TokenQuotaService = class TokenQuotaService extends Service {
712
734
  const credentials = this.ctx.get("credentials");
713
735
  let key;
714
736
  if (credentials !== void 0) try {
715
- key = (await credentials.resolve(source.keyEnv))?.value;
737
+ key = (await credentials.resolve(resolved.keyEnv))?.value;
716
738
  } catch {}
717
- if (key === void 0 || key === "") key = process.env[source.keyEnv];
739
+ if (key === void 0 || key === "") key = process.env[resolved.keyEnv];
718
740
  if (key === void 0 || key === "") {
719
741
  this.balances.set(provider, {
720
742
  provider,
@@ -726,7 +748,7 @@ var TokenQuotaService = class TokenQuotaService extends Service {
726
748
  });
727
749
  return;
728
750
  }
729
- const base = source.baseEnv !== void 0 ? process.env[source.baseEnv] ?? source.baseURL : source.baseURL;
751
+ const base = resolved.baseURL;
730
752
  try {
731
753
  const controller = new AbortController();
732
754
  const timer = setTimeout(() => {
@@ -10,7 +10,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
10
10
  * callbacks.
11
11
  */
12
12
  import { useEffect, useMemo, useRef, useState } from 'react';
13
- import { TOKEN_QUOTA_BALANCE_PROVIDERS, TOKEN_QUOTA_MAX_NOTES } from '@jxgame2020/dsh-token-quota/types';
13
+ import { TOKEN_QUOTA_MAX_NOTES } from '@jxgame2020/dsh-token-quota/types';
14
14
  import { mergeModelRows, reorderKeys } from "./store.js";
15
15
  import css from './TokenQuotaPanel.module.css';
16
16
  /** Compact a token count for display. */
@@ -543,9 +543,10 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
543
543
  list.push('deepseek');
544
544
  return list;
545
545
  }, [groups]);
546
- // Balance by provider key, built from the snapshot (status ok/error/unconfigured)
547
- // plus a client-side "unsupported" sentinel for providers the Host does not
548
- // yet know how to query, and "loading" for supported ones not yet returned.
546
+ // Balance by provider key. The Host returns a balances entry for every
547
+ // provider it can query (DeepSeek-family), so a directory provider with no
548
+ // entry is "not integrated for query" — except during the very first fetch,
549
+ // when the whole list is still empty and reads as "loading" instead.
549
550
  const balanceByProvider = useMemo(() => {
550
551
  const map = new Map();
551
552
  for (const provider of providers) {
@@ -553,7 +554,7 @@ export function TokenQuotaPanel({ t, load, setLimit, selectModel, setMonitored,
553
554
  if (bal !== undefined) {
554
555
  map.set(provider, bal);
555
556
  }
556
- else if (TOKEN_QUOTA_BALANCE_PROVIDERS.includes(provider)) {
557
+ else if (balances.length === 0) {
557
558
  map.set(provider, 'loading');
558
559
  }
559
560
  else {
@@ -112,8 +112,16 @@ export declare class TokenQuotaService extends Service {
112
112
  * the enabled-transition in `onChange` fetch immediately).
113
113
  */
114
114
  private syncBalancePolling;
115
- /** Refresh every provider the host knows how to query. */
115
+ /** Refresh every DeepSeek-family provider found in the model directory. */
116
116
  private refreshAllBalances;
117
+ /**
118
+ * Discover every provider in the model directory that looks like DeepSeek,
119
+ * together with its own credential reference and endpoint base. This is what
120
+ * decides which providers are queried: a provider qualifies when its base URL
121
+ * points at DeepSeek's API host or its credential reference is named with the
122
+ * `DEEPSEEK` prefix, and it must declare an `apiKeyEnv`.
123
+ */
124
+ private discoverBalanceSources;
117
125
  /**
118
126
  * Refresh one provider's cached account balance. The API key is resolved
119
127
  * through the harness credential seam (`credentials.resolve`) with the
@@ -271,21 +271,8 @@ function usageTokens(usage) {
271
271
  + (usage.cacheWriteTokens ?? 0)
272
272
  + usage.outputTokens;
273
273
  }
274
- /**
275
- * Daily token-quota service.
276
- *
277
- * Mount it beside the other rows (`@jxgame2020/dsh-token-quota`) and write
278
- * per-model limits through the `token-quota` settings namespace.
279
- */
280
- /**
281
- * Providers the host can query for account balance, keyed by route key. Each
282
- * entry names the credential reference (environment-variable name), the
283
- * balance endpoint base, and the optional `*_BASE_URL` override variable.
284
- * Directory providers outside this map render as "not integrated for query".
285
- */
286
- const BALANCE_SOURCES = {
287
- deepseek: { keyEnv: 'DEEPSEEK_API_KEY', baseURL: 'https://api.deepseek.com', baseEnv: 'DEEPSEEK_BASE_URL' },
288
- };
274
+ /** DeepSeek's public API host; used to recognise DeepSeek-family providers. */
275
+ const DEEPSEEK_BALANCE_HOST = 'api.deepseek.com';
289
276
  export class TokenQuotaService extends Service {
290
277
  static Config = z.object({
291
278
  storagePath: z.string().default(''),
@@ -551,7 +538,7 @@ export class TokenQuotaService extends Service {
551
538
  catch {
552
539
  // Malformed body: fall back to refreshing everything.
553
540
  }
554
- const task = provider !== undefined && provider in BALANCE_SOURCES
541
+ const task = provider !== undefined
555
542
  ? this.refreshProviderBalance(provider)
556
543
  : this.refreshAllBalances();
557
544
  void task.finally(() => {
@@ -745,9 +732,66 @@ export class TokenQuotaService extends Service {
745
732
  };
746
733
  schedule();
747
734
  }
748
- /** Refresh every provider the host knows how to query. */
735
+ /** Refresh every DeepSeek-family provider found in the model directory. */
749
736
  refreshAllBalances() {
750
- return Promise.all(Object.keys(BALANCE_SOURCES).map(provider => this.refreshProviderBalance(provider))).then(() => undefined);
737
+ return Promise.all([...this.discoverBalanceSources().values()]
738
+ .map(source => this.refreshProviderBalance(source.provider, source))).then(() => undefined);
739
+ }
740
+ /**
741
+ * Discover every provider in the model directory that looks like DeepSeek,
742
+ * together with its own credential reference and endpoint base. This is what
743
+ * decides which providers are queried: a provider qualifies when its base URL
744
+ * points at DeepSeek's API host or its credential reference is named with the
745
+ * `DEEPSEEK` prefix, and it must declare an `apiKeyEnv`.
746
+ */
747
+ discoverBalanceSources() {
748
+ const found = new Map();
749
+ const llm = this.ctx.get('llm');
750
+ const settings = this.ctx.get('settings');
751
+ const fallback = () => ({
752
+ provider: 'deepseek',
753
+ keyEnv: 'DEEPSEEK_API_KEY',
754
+ baseURL: process.env.DEEPSEEK_BASE_URL ?? `https://${DEEPSEEK_BALANCE_HOST}`,
755
+ });
756
+ if (llm?.listConfigurableProviders === undefined) {
757
+ found.set('deepseek', fallback());
758
+ return found;
759
+ }
760
+ for (const entry of llm.listConfigurableProviders()) {
761
+ try {
762
+ let profile = settings?.get(entry.settingsNs);
763
+ for (const segment of entry.settingsPath ?? []) {
764
+ profile = profile !== null && typeof profile === 'object'
765
+ ? profile[segment]
766
+ : undefined;
767
+ }
768
+ if (profile === null || typeof profile !== 'object')
769
+ continue;
770
+ const record = profile;
771
+ const keyEnv = typeof record.apiKeyEnv === 'string' && record.apiKeyEnv !== ''
772
+ ? record.apiKeyEnv
773
+ : undefined;
774
+ const baseURL = typeof record.baseURL === 'string' && record.baseURL !== ''
775
+ ? record.baseURL
776
+ : undefined;
777
+ const isDeepSeek = (baseURL !== undefined && baseURL.includes(DEEPSEEK_BALANCE_HOST))
778
+ || (keyEnv !== undefined && keyEnv.startsWith('DEEPSEEK'))
779
+ || entry.provider.startsWith('deepseek');
780
+ if (!isDeepSeek || keyEnv === undefined)
781
+ continue;
782
+ found.set(entry.provider, {
783
+ provider: entry.provider,
784
+ keyEnv,
785
+ baseURL: baseURL ?? `https://${DEEPSEEK_BALANCE_HOST}`,
786
+ });
787
+ }
788
+ catch {
789
+ // Malformed provider entry — skip it and keep the others.
790
+ }
791
+ }
792
+ if (found.size === 0)
793
+ found.set('deepseek', fallback());
794
+ return found;
751
795
  }
752
796
  /**
753
797
  * Refresh one provider's cached account balance. The API key is resolved
@@ -755,9 +799,9 @@ export class TokenQuotaService extends Service {
755
799
  * environment as a fallback, so the Models page key is used with nothing to
756
800
  * type. A failed fetch keeps the previous value and marks it as an error.
757
801
  */
758
- async refreshProviderBalance(provider) {
759
- const source = BALANCE_SOURCES[provider];
760
- if (source === undefined)
802
+ async refreshProviderBalance(provider, source) {
803
+ const resolved = source ?? this.discoverBalanceSources().get(provider);
804
+ if (resolved === undefined)
761
805
  return;
762
806
  const existing = this.balanceFetches.get(provider);
763
807
  if (existing !== undefined)
@@ -769,14 +813,14 @@ export class TokenQuotaService extends Service {
769
813
  let key;
770
814
  if (credentials !== undefined) {
771
815
  try {
772
- key = (await credentials.resolve(source.keyEnv))?.value;
816
+ key = (await credentials.resolve(resolved.keyEnv))?.value;
773
817
  }
774
818
  catch {
775
819
  // Seam unreachable: fall through to the environment.
776
820
  }
777
821
  }
778
822
  if (key === undefined || key === '')
779
- key = process.env[source.keyEnv];
823
+ key = process.env[resolved.keyEnv];
780
824
  if (key === undefined || key === '') {
781
825
  this.balances.set(provider, {
782
826
  provider,
@@ -788,9 +832,7 @@ export class TokenQuotaService extends Service {
788
832
  });
789
833
  return;
790
834
  }
791
- const base = source.baseEnv !== undefined
792
- ? (process.env[source.baseEnv] ?? source.baseURL)
793
- : source.baseURL;
835
+ const base = resolved.baseURL;
794
836
  try {
795
837
  const controller = new AbortController();
796
838
  const timer = setTimeout(() => { controller.abort(); }, 10_000);
@@ -55,12 +55,6 @@ export interface TokenQuotaBalanceSettings {
55
55
  pollMinutes: number;
56
56
  }
57
57
  export declare const TOKEN_QUOTA_DEFAULT_BALANCE: TokenQuotaBalanceSettings;
58
- /**
59
- * Provider route keys the host knows how to query for account balance. A
60
- * directory provider outside this list renders as "not integrated for query"
61
- * in the balance window.
62
- */
63
- export declare const TOKEN_QUOTA_BALANCE_PROVIDERS: readonly string[];
64
58
  /** Fetch state of the provider account balance. */
65
59
  export type TokenQuotaBalanceStatus = 'ok' | 'unconfigured' | 'error';
66
60
  /**
@@ -33,12 +33,6 @@ export const TOKEN_QUOTA_DEFAULT_BALANCE = {
33
33
  enabled: true,
34
34
  pollMinutes: 5,
35
35
  };
36
- /**
37
- * Provider route keys the host knows how to query for account balance. A
38
- * directory provider outside this list renders as "not integrated for query"
39
- * in the balance window.
40
- */
41
- export const TOKEN_QUOTA_BALANCE_PROVIDERS = ['deepseek'];
42
36
  /** Default music settings: silent until the user flips the panel switch. */
43
37
  export const TOKEN_QUOTA_DEFAULT_MUSIC = {
44
38
  enabled: false,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jxgame2020/dsh-token-quota",
3
3
  "description": "Daily per-model token quota accounting, enforcement, and a floating quota panel for the DeepSeek Harness — a fully self-contained plugin (no core-Harness wiring, no generated RPC contract)",
4
- "version": "0.1.39",
4
+ "version": "0.1.40",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },