@linxin666/dsh-usage 0.4.1 → 0.4.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/index.js CHANGED
@@ -45,7 +45,7 @@ function mountOnce(packageName, fn) {
45
45
  });
46
46
  }
47
47
  //#endregion
48
- //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-brand@0.1.7-rc.1_@deepseek-ai+cordis@4.0.4/node_modules/@deepseek-ai/dsh-brand/lib/index.js
48
+ //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-brand@0.1.7-rc.2_@deepseek-ai+cordis@4.0.4/node_modules/@deepseek-ai/dsh-brand/lib/index.js
49
49
  /**
50
50
  * Duplicate-install-safe nominal primitive helpers.
51
51
  *
@@ -68,7 +68,7 @@ function brandString(value) {
68
68
  return value;
69
69
  }
70
70
  //#endregion
71
- //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-credentials@0.1.7-rc.1_@deepseek-ai+cordis@4.0.4_@deepseek-ai+dsh-inva_9b80248df36b741e5eb76c73b242287a/node_modules/@deepseek-ai/dsh-credentials/lib/index.js
71
+ //#region ../../node_modules/.pnpm/@deepseek-ai+dsh-credentials@0.1.7-rc.2_@deepseek-ai+cordis@4.0.4_@deepseek-ai+dsh-inva_98fa173416f048996704a605a96a2b0e/node_modules/@deepseek-ai/dsh-credentials/lib/index.js
72
72
  /**
73
73
  * Service Definition for the credential-reference capability seam (`ctx.credentials`). Settings and composition files carry
74
74
  * *references* to secrets — environment-variable names — while providers own
@@ -235,16 +235,11 @@ function usedPercent(used, limit) {
235
235
  function bearer(apiKey) {
236
236
  return { authorization: `Bearer ${apiKey}` };
237
237
  }
238
- /**
239
- * The official pay-as-you-go balance. `deepseek` is the configurable-catalog
240
- * route key; `deepseek-official` is the live provider route the llm-deepseek
241
- * adapter registers (sessions and agent-default-model carry it), so both ids
242
- * must resolve here or the current provider would never be probed.
243
- */
244
238
  const DEEPSEEK = {
245
239
  ids: ["deepseek", "deepseek-official"],
246
240
  displayName: "DeepSeek",
247
241
  balance: {
242
+ origin: "https://api.deepseek.com",
248
243
  build: ({ apiKey }) => ({
249
244
  url: "https://api.deepseek.com/user/balance",
250
245
  headers: bearer(apiKey)
@@ -624,6 +619,40 @@ const PROVIDER_ADAPTERS = [
624
619
  function adapterFor(provider) {
625
620
  return PROVIDER_ADAPTERS.find((adapter) => adapter.ids.includes(provider));
626
621
  }
622
+ /** Origin of a configured base URL, or undefined when it cannot be parsed. */
623
+ function originOf(baseURL) {
624
+ if (baseURL === void 0 || baseURL.trim() === "") return void 0;
625
+ try {
626
+ return new URL(baseURL).origin;
627
+ } catch {
628
+ return;
629
+ }
630
+ }
631
+ /**
632
+ * Whether an adapter's balance endpoint reports THIS route's account.
633
+ *
634
+ * Some adapter families share one route key across providers that merely
635
+ * speak the same API: `deepseek` is both the official DeepSeek catalog entry
636
+ * and a common id for an OpenAI-compatible reseller (Alibaba Bailian, ...)
637
+ * pointed at another host by the profile's `baseURL`. The balance endpoint is
638
+ * per ORIGIN, not per route key, so probing the official endpoint for a
639
+ * reseller profile reports the official account's money under the reseller's
640
+ * name (issue #1688). An adapter published with a fixed `origin` therefore
641
+ * only applies to a route whose configured base URL is that origin, or to a
642
+ * route that pins no base URL at all (the catalog alias of the official
643
+ * route, which resolves the family's own credential).
644
+ *
645
+ * @param adapter - the matched adapter.
646
+ * @param baseURL - the route's configured base URL (pi-ai profile), if any.
647
+ * @returns true when the adapter may probe its balance endpoint for the route.
648
+ */
649
+ function balanceAppliesToRoute(adapter, baseURL) {
650
+ const expected = adapter.balance?.origin;
651
+ if (expected === void 0) return true;
652
+ const configured = originOf(baseURL);
653
+ if (configured === void 0) return true;
654
+ return configured === expected;
655
+ }
627
656
  /**
628
657
  * Whether a provider route belongs to the official DeepSeek family: the only
629
658
  * family with a spend price book and a settings-section-owned env credential
@@ -1086,7 +1115,8 @@ var UsageService = class {
1086
1115
  displayName: route.displayName || adapter?.displayName || route.id,
1087
1116
  credential: snapshot?.credential ?? "none",
1088
1117
  supported: adapter !== void 0 && (adapter.balance !== void 0 || adapter.plan !== void 0),
1089
- ...adapter?.balance !== void 0 ? { balanceSupported: true } : {},
1118
+ ...adapter?.balance !== void 0 && balanceAppliesToRoute(adapter, this.piAiProfile(route.id)?.baseURL) ? { balanceSupported: true } : {},
1119
+ ...adapter?.balance !== void 0 && !balanceAppliesToRoute(adapter, this.piAiProfile(route.id)?.baseURL) ? { balanceSupported: false } : {},
1090
1120
  ...adapter?.plan !== void 0 ? { planSupported: true } : {},
1091
1121
  ...snapshot?.balance !== void 0 ? { balance: snapshot.balance } : {},
1092
1122
  ...snapshot?.plan !== void 0 ? { plan: snapshot.plan } : {},
@@ -1415,6 +1445,7 @@ var UsageService = class {
1415
1445
  async probeRoute(route, adapter) {
1416
1446
  const credential = await this.resolveCredential(route.id);
1417
1447
  const previous = this.snapshots.get(route.id);
1448
+ const balanceApplies = balanceAppliesToRoute(adapter, this.piAiProfile(route.id)?.baseURL);
1418
1449
  if (credential.key === void 0) {
1419
1450
  this.snapshots.set(route.id, {
1420
1451
  provider: route.id,
@@ -1430,11 +1461,13 @@ var UsageService = class {
1430
1461
  displayName: route.displayName || adapter.displayName,
1431
1462
  credential: credential.kind,
1432
1463
  supported: true,
1464
+ ...adapter.balance !== void 0 && !balanceApplies ? { balanceSupported: false } : {},
1433
1465
  updatedAt: Date.now()
1434
1466
  };
1435
1467
  const runProbe = async (kind) => {
1436
1468
  const half = adapter[kind];
1437
1469
  if (half === void 0) return void 0;
1470
+ if (kind === "balance" && !balanceApplies) return void 0;
1438
1471
  try {
1439
1472
  const spec = half.build({
1440
1473
  apiKey: credential.key,
@@ -19,6 +19,14 @@ export interface AdapterProbeContext {
19
19
  apiKey: string;
20
20
  /** Provider account id riding a dedicated header, when the credential carries one (Codex). */
21
21
  accountId?: string;
22
+ /**
23
+ * The route's configured base URL (the pi-ai profile's baseURL), when it
24
+ * declares one. An adapter whose endpoint belongs to one provider origin
25
+ * uses it to refuse an account that merely shares the route key, so a
26
+ * differently-hosted model of the same family is never reported with the
27
+ * other account's number.
28
+ */
29
+ baseURL?: string;
22
30
  }
23
31
  /** A parsed balance fact. */
24
32
  export interface BalanceParse {
@@ -37,6 +45,14 @@ export interface ProviderAdapter {
37
45
  /** Fallback display name when the LLM runtime has none. */
38
46
  displayName: string;
39
47
  balance?: {
48
+ /**
49
+ * Origin (scheme + host) whose account this balance endpoint reports.
50
+ * Omitted when the endpoint is per-route by construction (a profile
51
+ * supplies the host) or when the provider has only one origin. When set,
52
+ * a route whose configured baseURL resolves elsewhere is not this
53
+ * origin's account and must not be probed with this adapter.
54
+ */
55
+ origin?: string;
40
56
  build(context: AdapterProbeContext): ProbeSpec;
41
57
  parse(status: number, body: unknown): BalanceParse | undefined;
42
58
  };
@@ -45,6 +61,14 @@ export interface ProviderAdapter {
45
61
  parse(status: number, body: unknown): PlanParse | undefined;
46
62
  };
47
63
  }
64
+ /**
65
+ * The official pay-as-you-go balance. `deepseek` is the configurable-catalog
66
+ * route key; `deepseek-official` is the live provider route the llm-deepseek
67
+ * adapter registers (sessions and agent-default-model carry it), so both ids
68
+ * must resolve here or the current provider would never be probed.
69
+ */
70
+ /** The official DeepSeek API origin: the only account its balance endpoint reports. */
71
+ export declare const DEEPSEEK_API_ORIGIN = "https://api.deepseek.com";
48
72
  /**
49
73
  * The adapter registry, in no particular order. Route keys come from the
50
74
  * pi-ai provider catalog plus the routes this deployment observed in user
@@ -53,6 +77,27 @@ export interface ProviderAdapter {
53
77
  export declare const PROVIDER_ADAPTERS: readonly ProviderAdapter[];
54
78
  /** Find the adapter serving a provider route key, if any. */
55
79
  export declare function adapterFor(provider: string): ProviderAdapter | undefined;
80
+ /** Origin of a configured base URL, or undefined when it cannot be parsed. */
81
+ export declare function originOf(baseURL: string | undefined): string | undefined;
82
+ /**
83
+ * Whether an adapter's balance endpoint reports THIS route's account.
84
+ *
85
+ * Some adapter families share one route key across providers that merely
86
+ * speak the same API: `deepseek` is both the official DeepSeek catalog entry
87
+ * and a common id for an OpenAI-compatible reseller (Alibaba Bailian, ...)
88
+ * pointed at another host by the profile's `baseURL`. The balance endpoint is
89
+ * per ORIGIN, not per route key, so probing the official endpoint for a
90
+ * reseller profile reports the official account's money under the reseller's
91
+ * name (issue #1688). An adapter published with a fixed `origin` therefore
92
+ * only applies to a route whose configured base URL is that origin, or to a
93
+ * route that pins no base URL at all (the catalog alias of the official
94
+ * route, which resolves the family's own credential).
95
+ *
96
+ * @param adapter - the matched adapter.
97
+ * @param baseURL - the route's configured base URL (pi-ai profile), if any.
98
+ * @returns true when the adapter may probe its balance endpoint for the route.
99
+ */
100
+ export declare function balanceAppliesToRoute(adapter: ProviderAdapter, baseURL: string | undefined): boolean;
56
101
  /**
57
102
  * Whether a provider route belongs to the official DeepSeek family: the only
58
103
  * family with a spend price book and a settings-section-owned env credential
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@linxin666/dsh-usage",
3
3
  "description": "Usage statistics plugin for the dsh web GUI: per-provider balance and coding-plan quota detection plus a live token usage ledger, with a compact price glance card above the sidebar's Settings row",
4
- "version": "0.4.1",
4
+ "version": "0.4.2",
5
5
  "icon": "icon.svg",
6
6
  "type": "module",
7
7
  "engines": {
@@ -33,7 +33,7 @@
33
33
  "license": "Apache-2.0",
34
34
  "dsh": {
35
35
  "engines": {
36
- "dsh": ">=0.1.7-rc.1"
36
+ "dsh": ">=0.1.7-rc.2"
37
37
  },
38
38
  "bundle": {
39
39
  "patch": "./cordis.patch.yml"
@@ -53,22 +53,22 @@
53
53
  "schemastery": "^3.18.0"
54
54
  },
55
55
  "peerDependencies": {
56
- "@deepseek-ai/dsh": ">=0.1.7-rc.1",
56
+ "@deepseek-ai/dsh": ">=0.1.7-rc.2",
57
57
  "react": "^18.2.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@deepseek-ai/cordis": "^4.0.4",
61
- "@deepseek-ai/dsh-credentials": "^0.1.7-rc.1",
62
- "@deepseek-ai/dsh-client-connection": "^0.1.7-rc.1",
63
- "@deepseek-ai/dsh-client-locale": "^0.1.7-rc.1",
64
- "@deepseek-ai/dsh-client-store": "^0.1.7-rc.1",
65
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-rc.1",
66
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.7-rc.1",
67
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.1",
68
- "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.1",
69
- "@deepseek-ai/dsh-llm": "^0.1.7-rc.1",
70
- "@deepseek-ai/dsh-session": "^0.1.7-rc.1",
71
- "@deepseek-ai/dsh-settings": "^0.1.7-rc.1",
61
+ "@deepseek-ai/dsh-credentials": "^0.1.7-rc.2",
62
+ "@deepseek-ai/dsh-client-connection": "^0.1.7-rc.2",
63
+ "@deepseek-ai/dsh-client-locale": "^0.1.7-rc.2",
64
+ "@deepseek-ai/dsh-client-store": "^0.1.7-rc.2",
65
+ "@deepseek-ai/dsh-client-ui-renderer": "^0.1.7-rc.2",
66
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.7-rc.2",
67
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.7-rc.2",
68
+ "@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2",
69
+ "@deepseek-ai/dsh-llm": "^0.1.7-rc.2",
70
+ "@deepseek-ai/dsh-session": "^0.1.7-rc.2",
71
+ "@deepseek-ai/dsh-settings": "^0.1.7-rc.2",
72
72
  "@testing-library/dom": "^10.4.1",
73
73
  "@testing-library/react": "^16.3.2",
74
74
  "@types/node": "^22.20.0",
@@ -128,6 +128,10 @@ function balanceLine(provider: ProviderSnapshotView): ReactNode {
128
128
  }
129
129
  if (provider.credential === 'oauth') return <span className={styles.muted}>{t('usage.oauth')}</span>
130
130
  if (provider.credential === 'none') return <span className={styles.muted}>{t('usage.balance.noCredential')}</span>
131
+ // balanceSupported === false is the origin gate: the adapter has a balance
132
+ // endpoint but it belongs to another provider's account (issue #1688), so
133
+ // the row says so instead of silently vanishing from the card.
134
+ if (provider.balanceSupported === false) return <span className={styles.muted}>{t('usage.balance.unsupported')}</span>
131
135
  if (!provider.supported) return <span className={styles.muted}>{t('usage.balance.unsupported')}</span>
132
136
  return null
133
137
  }
@@ -298,7 +302,10 @@ export function UsageSectionCard(props: UsageSectionProps): ReactNode {
298
302
  <span className={styles.cardTitle}>{t('usage.balance')}</span>
299
303
  {(() => {
300
304
  const configured = snapshot.providers.filter(isConfigured)
301
- const rows = configured.filter((provider) => provider.balanceSupported === true || (provider.balanceSupported === undefined && provider.supported))
305
+ // Include rows whose balance is deliberately not applicable
306
+ // (another origin's endpoint): the row itself explains why, which
307
+ // a single generic line cannot (#1688).
308
+ const rows = configured.filter((provider) => provider.balanceSupported === true || provider.balanceSupported === false || (provider.balanceSupported === undefined && provider.supported))
302
309
  if (rows.length === 0) {
303
310
  return <span className={styles.muted}>{configured.length === 0 ? t('usage.balance.noneConfigured') : t('usage.balance.unsupported')}</span>
304
311
  }
@@ -42,8 +42,11 @@ async function usageFetch<T>(path: string, method: 'GET' | 'POST'): Promise<T> {
42
42
  }
43
43
 
44
44
  const usageApi: UsageHttpApi = {
45
- overview: () => usageFetch('/api/dsh-usage/overview', 'GET'),
46
- refresh: () => usageFetch('/api/dsh-usage/refresh', 'POST'),
45
+ // DOCUMENT-RELATIVE routes (issue #1707): the GUI is served with
46
+ // `<base href="./">`, so a sub-path deployment resolves these against its
47
+ // entry directory instead of escaping to the origin root.
48
+ overview: () => usageFetch('api/dsh-usage/overview', 'GET'),
49
+ refresh: () => usageFetch('api/dsh-usage/refresh', 'POST'),
47
50
  }
48
51
 
49
52
  /** Settings namespace the section edits (dsh-web-settings maps it onto this row's profile entry id). */
@@ -22,6 +22,14 @@ export interface AdapterProbeContext {
22
22
  apiKey: string
23
23
  /** Provider account id riding a dedicated header, when the credential carries one (Codex). */
24
24
  accountId?: string
25
+ /**
26
+ * The route's configured base URL (the pi-ai profile's baseURL), when it
27
+ * declares one. An adapter whose endpoint belongs to one provider origin
28
+ * uses it to refuse an account that merely shares the route key, so a
29
+ * differently-hosted model of the same family is never reported with the
30
+ * other account's number.
31
+ */
32
+ baseURL?: string
25
33
  }
26
34
 
27
35
  /** A parsed balance fact. */
@@ -43,6 +51,14 @@ export interface ProviderAdapter {
43
51
  /** Fallback display name when the LLM runtime has none. */
44
52
  displayName: string
45
53
  balance?: {
54
+ /**
55
+ * Origin (scheme + host) whose account this balance endpoint reports.
56
+ * Omitted when the endpoint is per-route by construction (a profile
57
+ * supplies the host) or when the provider has only one origin. When set,
58
+ * a route whose configured baseURL resolves elsewhere is not this
59
+ * origin's account and must not be probed with this adapter.
60
+ */
61
+ origin?: string
46
62
  build(context: AdapterProbeContext): ProbeSpec
47
63
  parse(status: number, body: unknown): BalanceParse | undefined
48
64
  }
@@ -113,10 +129,14 @@ function bearer(apiKey: string): Record<string, string> {
113
129
  * adapter registers (sessions and agent-default-model carry it), so both ids
114
130
  * must resolve here or the current provider would never be probed.
115
131
  */
132
+ /** The official DeepSeek API origin: the only account its balance endpoint reports. */
133
+ export const DEEPSEEK_API_ORIGIN = 'https://api.deepseek.com'
134
+
116
135
  const DEEPSEEK: ProviderAdapter = {
117
136
  ids: ['deepseek', 'deepseek-official'],
118
137
  displayName: 'DeepSeek',
119
138
  balance: {
139
+ origin: DEEPSEEK_API_ORIGIN,
120
140
  build: ({ apiKey }) => ({ url: 'https://api.deepseek.com/user/balance', headers: bearer(apiKey) }),
121
141
  parse: (status, body) => {
122
142
  if (status !== 200 || typeof body !== 'object' || body === null) return undefined
@@ -459,6 +479,42 @@ export function adapterFor(provider: string): ProviderAdapter | undefined {
459
479
  return PROVIDER_ADAPTERS.find((adapter) => adapter.ids.includes(provider))
460
480
  }
461
481
 
482
+ /** Origin of a configured base URL, or undefined when it cannot be parsed. */
483
+ export function originOf(baseURL: string | undefined): string | undefined {
484
+ if (baseURL === undefined || baseURL.trim() === '') return undefined
485
+ try {
486
+ return new URL(baseURL).origin
487
+ } catch {
488
+ return undefined
489
+ }
490
+ }
491
+
492
+ /**
493
+ * Whether an adapter's balance endpoint reports THIS route's account.
494
+ *
495
+ * Some adapter families share one route key across providers that merely
496
+ * speak the same API: `deepseek` is both the official DeepSeek catalog entry
497
+ * and a common id for an OpenAI-compatible reseller (Alibaba Bailian, ...)
498
+ * pointed at another host by the profile's `baseURL`. The balance endpoint is
499
+ * per ORIGIN, not per route key, so probing the official endpoint for a
500
+ * reseller profile reports the official account's money under the reseller's
501
+ * name (issue #1688). An adapter published with a fixed `origin` therefore
502
+ * only applies to a route whose configured base URL is that origin, or to a
503
+ * route that pins no base URL at all (the catalog alias of the official
504
+ * route, which resolves the family's own credential).
505
+ *
506
+ * @param adapter - the matched adapter.
507
+ * @param baseURL - the route's configured base URL (pi-ai profile), if any.
508
+ * @returns true when the adapter may probe its balance endpoint for the route.
509
+ */
510
+ export function balanceAppliesToRoute(adapter: ProviderAdapter, baseURL: string | undefined): boolean {
511
+ const expected = adapter.balance?.origin
512
+ if (expected === undefined) return true
513
+ const configured = originOf(baseURL)
514
+ if (configured === undefined) return true
515
+ return configured === expected
516
+ }
517
+
462
518
  /**
463
519
  * Whether a provider route belongs to the official DeepSeek family: the only
464
520
  * family with a spend price book and a settings-section-owned env credential
@@ -15,7 +15,7 @@ import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
15
15
  import type { TokenUsage } from '@deepseek-ai/dsh-llm'
16
16
  import { dshHome } from '../dsh-home.ts'
17
17
  import { withIdentityEncoding } from './http.ts'
18
- import { adapterFor, isDeepSeekProviderRoute, providerErrorMessage } from '../core/adapters.ts'
18
+ import { adapterFor, balanceAppliesToRoute, isDeepSeekProviderRoute, providerErrorMessage } from '../core/adapters.ts'
19
19
  import type { BalanceParse, PlanParse } from '../core/adapters.ts'
20
20
  import { foldAliasRoutes } from '../core/provider-routes.ts'
21
21
  import { deepseekModelSpend } from '../core/pricing.ts'
@@ -202,7 +202,16 @@ export class UsageService {
202
202
  displayName: route.displayName || adapter?.displayName || route.id,
203
203
  credential: snapshot?.credential ?? 'none',
204
204
  supported: adapter !== undefined && (adapter.balance !== undefined || adapter.plan !== undefined),
205
- ...(adapter?.balance !== undefined ? { balanceSupported: true } : {}),
205
+ // A balance endpoint that belongs to another origin does not report
206
+ // THIS route's account: it is supported by the adapter but not
207
+ // applicable here, and the UI renders it as unsupported rather than
208
+ // showing the other account's number (issue #1688).
209
+ ...(adapter?.balance !== undefined && balanceAppliesToRoute(adapter, this.piAiProfile(route.id)?.baseURL)
210
+ ? { balanceSupported: true }
211
+ : {}),
212
+ ...(adapter?.balance !== undefined && !balanceAppliesToRoute(adapter, this.piAiProfile(route.id)?.baseURL)
213
+ ? { balanceSupported: false }
214
+ : {}),
206
215
  ...(adapter?.plan !== undefined ? { planSupported: true } : {}),
207
216
  ...(snapshot?.balance !== undefined ? { balance: snapshot.balance } : {}),
208
217
  ...(snapshot?.plan !== undefined ? { plan: snapshot.plan } : {}),
@@ -573,6 +582,12 @@ export class UsageService {
573
582
  private async probeRoute(route: ProviderRoute, adapter: NonNullable<ReturnType<typeof adapterFor>>): Promise<void> {
574
583
  const credential = await this.resolveCredential(route.id)
575
584
  const previous = this.snapshots.get(route.id)
585
+ // The balance endpoint is per ORIGIN, not per route key: a profile that
586
+ // points a shared route id (`deepseek`) at another host is a different
587
+ // account, and probing the official endpoint would print this account's
588
+ // money under that route's name (issue #1688). Such a route reports the
589
+ // balance as unsupported instead.
590
+ const balanceApplies = balanceAppliesToRoute(adapter, this.piAiProfile(route.id)?.baseURL)
576
591
  if (credential.key === undefined) {
577
592
  // Nothing to probe with (oauth grant, no credential): retained facts
578
593
  // would never refresh again, so the row resets to its credential
@@ -591,11 +606,13 @@ export class UsageService {
591
606
  displayName: route.displayName || adapter.displayName,
592
607
  credential: credential.kind,
593
608
  supported: true,
609
+ ...(adapter.balance !== undefined && !balanceApplies ? { balanceSupported: false } : {}),
594
610
  updatedAt: Date.now(),
595
611
  }
596
612
  const runProbe = async (kind: 'balance' | 'plan'): Promise<BalanceView | PlanView | undefined> => {
597
613
  const half = adapter[kind]
598
614
  if (half === undefined) return undefined
615
+ if (kind === 'balance' && !balanceApplies) return undefined
599
616
  try {
600
617
  const spec = half.build({ apiKey: credential.key as string, ...(credential.accountId !== undefined ? { accountId: credential.accountId } : {}) })
601
618
  const response = await fetch(spec.url, withIdentityEncoding({