dsh-subscriptions 0.1.3 → 0.1.5

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.
Files changed (75) hide show
  1. package/README.md +64 -0
  2. package/lib/auth/device-flow.js +6 -3
  3. package/lib/auth/oauth-flow.js +41 -8
  4. package/lib/auth/rpc.d.ts +3 -1
  5. package/lib/auth/rpc.js +10 -1
  6. package/lib/client/ProviderAccountManager.js +34 -41
  7. package/lib/client/ProviderModelEditor.js +5 -9
  8. package/lib/client/SpeedSelect.js +8 -7
  9. package/lib/client/SubscriptionUsageBadge.js +6 -5
  10. package/lib/client/SubscriptionsSection.d.ts +21 -73
  11. package/lib/client/SubscriptionsSection.js +130 -104
  12. package/lib/client/UsageBadgeDisplaySetting.js +4 -8
  13. package/lib/client/host-contract.d.ts +1 -1
  14. package/lib/client/host-contract.js +22 -0
  15. package/lib/client/locales.d.ts +58 -0
  16. package/lib/client/locales.js +58 -0
  17. package/lib/client/provider-settings-styles.d.ts +3 -0
  18. package/lib/client/provider-settings-styles.js +206 -0
  19. package/lib/client/reset-action.d.ts +14 -0
  20. package/lib/client/reset-action.js +15 -0
  21. package/lib/client/reset-credits-view.d.ts +53 -0
  22. package/lib/client/reset-credits-view.js +103 -0
  23. package/lib/client.js +937 -310
  24. package/lib/client.js.map +1 -1
  25. package/lib/index.d.ts +9 -0
  26. package/lib/index.js +6058 -5298
  27. package/lib/model-defaults.d.ts +4 -5
  28. package/lib/model-defaults.js +18 -14
  29. package/lib/providers/account-preferences.js +3 -2
  30. package/lib/providers/antigravity.d.ts +0 -2
  31. package/lib/providers/antigravity.js +5 -9
  32. package/lib/providers/catalog-store.d.ts +3 -2
  33. package/lib/providers/catalog-store.js +57 -26
  34. package/lib/providers/claude.d.ts +22 -3
  35. package/lib/providers/claude.js +118 -20
  36. package/lib/providers/codex-search.js +5 -2
  37. package/lib/providers/codex.d.ts +15 -2
  38. package/lib/providers/codex.js +77 -9
  39. package/lib/providers/common.d.ts +38 -0
  40. package/lib/providers/common.js +44 -18
  41. package/lib/providers/copilot.d.ts +2 -1
  42. package/lib/providers/copilot.js +13 -6
  43. package/lib/providers/cursor-adapter.d.ts +35 -0
  44. package/lib/providers/cursor-adapter.js +245 -4
  45. package/lib/providers/cursor-auth.js +11 -4
  46. package/lib/providers/cursor-usage.js +2 -1
  47. package/lib/providers/external-usage.js +2 -1
  48. package/lib/providers/grok.d.ts +2 -2
  49. package/lib/providers/grok.js +16 -11
  50. package/lib/providers/minimax-usage.js +2 -1
  51. package/lib/providers/pool-health.d.ts +2 -0
  52. package/lib/providers/pool-health.js +9 -0
  53. package/lib/providers/pool-usage.d.ts +13 -1
  54. package/lib/providers/pool-usage.js +39 -7
  55. package/lib/providers/pool.d.ts +7 -5
  56. package/lib/providers/pool.js +89 -12
  57. package/lib/providers/rate-limit.d.ts +0 -14
  58. package/lib/providers/rate-limit.js +0 -26
  59. package/lib/providers/replay.d.ts +4 -0
  60. package/lib/providers/replay.js +50 -0
  61. package/lib/providers/reset-redemption.d.ts +51 -0
  62. package/lib/providers/reset-redemption.js +102 -0
  63. package/lib/tools/image-generate.js +2 -1
  64. package/lib/tools/video-generate.d.ts +0 -5
  65. package/lib/tools/video-generate.js +25 -14
  66. package/lib/tools/x-search.js +2 -2
  67. package/lib/translate/anthropic.d.ts +21 -2
  68. package/lib/translate/anthropic.js +35 -11
  69. package/lib/translate/antigravity.js +13 -6
  70. package/lib/translate/chat-completions.js +1 -1
  71. package/lib/translate/resolved.d.ts +24 -0
  72. package/lib/translate/resolved.js +66 -3
  73. package/lib/translate/responses.js +8 -6
  74. package/lib/translate/sse.js +8 -0
  75. package/package.json +1 -1
@@ -41,23 +41,28 @@ export interface ProviderUsage {
41
41
  supported: boolean;
42
42
  windows?: UsageWindow[];
43
43
  plan?: string;
44
+ /**
45
+ * Banked limit-reset grants. Codex entries carry id/expiry only. Claude
46
+ * entries are one row per grant: `expiresAt` is when the grant expires, not
47
+ * a window reset, and `resetsLeft` is the remaining credit count.
48
+ */
49
+ resetCredits?: ResetCreditView[];
50
+ resetCreditsError?: string;
44
51
  }
45
- /** One model's default-effort picker state as answered by `modelDefaults`. */
46
- export interface ModelDefaultView {
47
- id: string;
48
- name: string;
49
- /** Advertised effort levels, in catalog order (empty when the model has no reasoning). */
50
- efforts: {
51
- id: string;
52
- name: string;
53
- }[];
54
- /** The user-configured default effort, when set. */
55
- configured?: string;
56
- }
57
- /** `modelDefaults` endpoint value: one provider's picker state. */
58
- export interface ModelDefaultsCatalog {
59
- provider: SubscriptionProvider;
60
- models: ModelDefaultView[];
52
+ /** One banked reset grant as the usage RPC returns it. */
53
+ export interface ResetCreditView {
54
+ id?: string;
55
+ grantedAt?: number;
56
+ /** Epoch milliseconds when the grant expires. Never a periodic window reset. */
57
+ expiresAt?: number;
58
+ resetsTotal?: number;
59
+ resetsLeft?: number;
60
+ usableNow?: boolean;
61
+ paused?: boolean;
62
+ /** Server metadata only. Claude has no redemption control. */
63
+ claimable?: boolean;
64
+ /** Epoch milliseconds until which the grant is visible but not usable. */
65
+ cooldownUntil?: number;
61
66
  }
62
67
  /** Injected dependencies of {@link SubscriptionsSection} (slot `inject`). */
63
68
  export interface SubscriptionsSectionInjected {
@@ -80,63 +85,6 @@ export type SubscriptionsSectionProps = Partial<SubscriptionsSectionInjected>;
80
85
  export declare function clientVersionText(t: SubscriptionsSectionInjected['t'], clientVersion: ProviderStatus['clientVersion']): string;
81
86
  /** Meter fill color lives with the pace logic: fresh 90%+ is red, pace presets add a yellow lead threshold. */
82
87
  export { usageBarColor } from './usage-pace.js';
83
- /** What one provider's collapsible default-effort section renders. */
84
- export interface ModelDefaultsView {
85
- /** Models with reasoning levels, after the name filter — one row each. */
86
- shown: ModelDefaultView[];
87
- /** Models with reasoning levels before filtering (the header total). */
88
- total: number;
89
- /** How many of those carry a user override (the header count). */
90
- overridden: number;
91
- /** Models without reasoning levels: one count line, never a row each. */
92
- withoutEfforts: number;
93
- /** Whether the list is long enough to deserve a filter box. */
94
- showFilter: boolean;
95
- }
96
- /**
97
- * Derive one provider's default-effort section from its catalog and filter.
98
- * Pure so the collapsed-header counts and the filter stay testable without a
99
- * DOM: rows come only from models that advertise levels, the count of the rest
100
- * rides as one line, and the filter matches display name or model id.
101
- * @param models - the provider's catalog models, or undefined while loading.
102
- * @param filter - the raw filter input (trimmed and lowercased here).
103
- * @returns the section's rows and header counts.
104
- */
105
- export declare function deriveModelDefaultsView(models: readonly ModelDefaultView[] | undefined, filter: string): ModelDefaultsView;
106
- /** Inputs of the default-effort fetch decision (see {@link shouldFetchModelDefaults}). */
107
- export interface ModelDefaultsFetchInput {
108
- /** Providers that currently have at least one account. */
109
- loggedIn: readonly SubscriptionProvider[];
110
- /** Providers whose disclosure is open. */
111
- open: readonly SubscriptionProvider[];
112
- /** The account signature the last completed fetch was answered for. */
113
- loadedFor: string | undefined;
114
- /** The account signature of the current status snapshot. */
115
- signature: string;
116
- /** Whether the last attempt failed (a failure latches until Retry). */
117
- failed: boolean;
118
- }
119
- /**
120
- * Whether the default-effort catalog needs (re)fetching.
121
- *
122
- * Fetching is gated on an *attempt* signature rather than on the payload
123
- * being empty: an empty answer is a legitimate result (a narrowed
124
- * `config.providers`, or a catalog that is momentarily unavailable), and
125
- * treating it as "not loaded yet" re-ran this effect forever. The signature
126
- * also covers the accounts, so logging a second provider in refetches
127
- * instead of leaving that card on the previous answer.
128
- * @param input - the decision inputs.
129
- * @returns true when the caller should start a fetch.
130
- */
131
- export declare function shouldFetchModelDefaults(input: ModelDefaultsFetchInput): boolean;
132
- /**
133
- * Stable signature of the accounts a catalog answer depends on. A change
134
- * means a previous answer is stale (an account arrived or left), so the next
135
- * open disclosure refetches.
136
- * @param statuses - the per-provider status snapshot.
137
- * @returns a signature string, stable across renders with equal accounts.
138
- */
139
- export declare function modelDefaultsSignature(statuses: Partial<Record<SubscriptionProvider, ProviderStatus>>): string;
140
88
  /**
141
89
  * The Subscriptions settings page component.
142
90
  * @param props - the slot inject face ({@link SubscriptionsSectionInjected}).
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /**
3
3
  * Subscriptions settings section: one card per subscription provider with an
4
- * OAuth login/logout flow driven by the node half's `/subscriptions-auth` RPC
5
- * channel. Login state lives server-side; the page polls `status` only while
4
+ * OAuth login/logout flow driven by the node half's
5
+ * `/api/subscriptions-auth.<endpoint>` RPC routes. Login state lives server-side; the page polls `status` only while
6
6
  * a login attempt is busy, so an idle page never polls. All state is local
7
7
  * React state — the page has no store.
8
8
  *
@@ -10,15 +10,17 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
10
10
  * design-platform.css values flip under `body[data-ds-dark-theme]`), and
11
11
  * every user-visible string goes through the locale-bound `t` of the
12
12
  * 'settings.subscriptions' namespace. Buttons and inputs take the
13
- * ModelsSection vocabulary minus hover rules, which inline styles cannot
14
- * express.
13
+ * native settings vocabulary with shared, scoped control states.
15
14
  */
16
15
  import { useCallback, useEffect, useRef, useState } from 'react';
17
16
  import { en } from './locales.js';
17
+ import { resetActionBlock } from './reset-action.js';
18
18
  import { ProviderAccountManager } from './ProviderAccountManager.js';
19
+ import { providerSettingsCss } from './provider-settings-styles.js';
19
20
  import { ExternalUsageCards } from './ExternalUsageCards.js';
20
21
  import { CursorCard } from './CursorCard.js';
21
22
  import { UsageMeter } from './UsageMeter.js';
23
+ import { ResetCreditsDisclosure, ResetCreditsErrorLine, showsResetCredits } from './reset-credits-view.js';
22
24
  import { displayUsedPercent } from './usage-pace.js';
23
25
  import { UsageBadgeDisplaySetting } from './UsageBadgeDisplaySetting.js';
24
26
  import { USAGE_BADGE_REFRESH_EVENT } from './usage-badge-preferences.js';
@@ -27,11 +29,6 @@ import { callSubscriptionsAuth, SubscriptionsAuthError } from './subscriptions-r
27
29
  export { callSubscriptionsAuth } from './subscriptions-rpc.js';
28
30
  /** Poll cadence while a provider login attempt is busy. */
29
31
  const POLL_INTERVAL_MS = 2000;
30
- /**
31
- * Model count above which the expanded default-effort list also offers a name
32
- * filter; below it the list is short enough to scan.
33
- */
34
- const MODEL_FILTER_THRESHOLD = 8;
35
32
  const CLIENT_VERSION_SOURCES = {
36
33
  npm: 'clientVersionNpm',
37
34
  local: 'clientVersionLocal',
@@ -80,7 +77,10 @@ const styles = {
80
77
  color: 'var(--dsw-alias-label-primary)',
81
78
  },
82
79
  intro: { margin: 0, color: 'var(--dsw-alias-label-tertiary)', fontSize: 14, lineHeight: '22px' },
83
- card: cardStyles.card,
80
+ card: { ...cardStyles.card,
81
+ border: '0.5px solid var(--dsw-alias-settings-card-stroke, var(--dsw-alias-border-l4))',
82
+ borderRadius: 'var(--dsw-radius-xl, 20px)', background: 'var(--dsw-alias-settings-card-fill, var(--dsw-alias-bg-layer-2))',
83
+ },
84
84
  cardHeader: cardStyles.header,
85
85
  dot: cardStyles.dot,
86
86
  name: cardStyles.name,
@@ -88,44 +88,46 @@ const styles = {
88
88
  clientVersion: { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)', fontVariantNumeric: 'tabular-nums' },
89
89
  errorLine: cardStyles.error,
90
90
  actions: cardStyles.actions,
91
- button: cardStyles.button,
92
- usage: cardStyles.usage,
91
+ usage: { ...cardStyles.usage, borderTop: '0.5px solid var(--dsw-alias-border-l2)' },
93
92
  usageHeader: cardStyles.usageHeader,
94
93
  usageTitle: cardStyles.usageTitle,
95
94
  usagePlan: cardStyles.usagePlan,
96
- usageRefresh: cardStyles.usageRefresh,
95
+ usageRefresh: { minHeight: 22, height: 22, padding: '0 8px', marginLeft: 'auto' },
97
96
  usageRow: cardStyles.usageRow,
98
97
  usageMeta: cardStyles.usageMeta,
99
- accountRow: cardStyles.account,
98
+ accountRow: { ...cardStyles.account, border: '0.5px solid var(--dsw-alias-border-l2)', borderRadius: 'var(--dsw-radius-lg, 16px)' },
100
99
  accountHeader: cardStyles.accountHeader,
101
100
  accountName: cardStyles.accountName,
102
101
  starButton: {
103
- border: 'none', background: 'transparent', padding: 0,
102
+ border: 'none', padding: 0, width: 28,
104
103
  font: 'inherit', fontSize: 14, lineHeight: '20px', cursor: 'pointer',
105
104
  color: 'var(--dsw-alias-state-warn-label)',
106
105
  },
107
106
  deviceCode: {
108
107
  marginTop: 4, display: 'flex', flexDirection: 'column', gap: 6,
109
- border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
108
+ border: '0.5px solid var(--dsw-alias-border-l2)', borderRadius: 'var(--dsw-radius-lg, 16px)',
110
109
  padding: '10px 12px', background: 'var(--dsw-alias-bg-layer-1)',
111
110
  },
112
111
  deviceCodeText: {
113
112
  fontFamily: 'monospace', fontSize: 18, lineHeight: '24px', letterSpacing: 2,
114
113
  color: 'var(--dsw-alias-label-primary)', userSelect: 'all',
115
114
  },
115
+ manual: { fontSize: 13, lineHeight: '20px' },
116
+ manualRow: { display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center', marginTop: 8 },
117
+ manualInput: { flex: '1 1 200px', minWidth: 0 },
116
118
  modalOverlay: {
117
119
  position: 'fixed', inset: 0, zIndex: 1000,
118
120
  display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16,
119
- background: 'rgba(0, 0, 0, 0.45)',
121
+ background: 'var(--dsw-alias-bg-mask-1, #0006)', backdropFilter: 'var(--dsw-mask-blur, none)',
120
122
  },
121
123
  modal: {
122
124
  width: 460, maxWidth: '100%', maxHeight: '90vh', overflowY: 'auto',
123
125
  boxSizing: 'border-box', display: 'flex', flexDirection: 'column', gap: 12,
124
- padding: '16px 18px', borderRadius: 12,
125
- background: 'var(--dsw-alias-bg-layer-1)', border: '1px solid var(--dsw-alias-border-l2)',
126
+ padding: '16px 18px', borderRadius: 'var(--dsw-radius-panel, 28px)',
127
+ background: 'var(--dsw-alias-bg-layer-2)', border: 0, boxShadow: 'var(--dsw-elevation-prominent)',
126
128
  },
127
129
  modalHeader: { display: 'flex', alignItems: 'center', gap: 8 },
128
- modalTitle: { fontWeight: 600, fontSize: 15, lineHeight: '22px', color: 'var(--dsw-alias-label-primary)' },
130
+ modalTitle: { fontWeight: 500, fontSize: 16, lineHeight: '24px', color: 'var(--dsw-alias-label-primary)' },
129
131
  };
130
132
  /** Status dot color for one provider state. */
131
133
  function dotColor(status) {
@@ -185,65 +187,6 @@ function usageWindowLabel(t, window) {
185
187
  }
186
188
  /** Meter fill color lives with the pace logic: fresh 90%+ is red, pace presets add a yellow lead threshold. */
187
189
  export { usageBarColor } from './usage-pace.js';
188
- /**
189
- * Derive one provider's default-effort section from its catalog and filter.
190
- * Pure so the collapsed-header counts and the filter stay testable without a
191
- * DOM: rows come only from models that advertise levels, the count of the rest
192
- * rides as one line, and the filter matches display name or model id.
193
- * @param models - the provider's catalog models, or undefined while loading.
194
- * @param filter - the raw filter input (trimmed and lowercased here).
195
- * @returns the section's rows and header counts.
196
- */
197
- export function deriveModelDefaultsView(models, filter) {
198
- const all = models ?? [];
199
- const withEfforts = all.filter(model => model.efforts.length > 0);
200
- const query = filter.trim().toLowerCase();
201
- const shown = query === ''
202
- ? withEfforts
203
- : withEfforts.filter(model => model.name.toLowerCase().includes(query)
204
- || model.id.toLowerCase().includes(query));
205
- return {
206
- shown,
207
- total: withEfforts.length,
208
- overridden: withEfforts.filter(model => model.configured !== undefined).length,
209
- withoutEfforts: all.length - withEfforts.length,
210
- showFilter: withEfforts.length > MODEL_FILTER_THRESHOLD,
211
- };
212
- }
213
- /**
214
- * Whether the default-effort catalog needs (re)fetching.
215
- *
216
- * Fetching is gated on an *attempt* signature rather than on the payload
217
- * being empty: an empty answer is a legitimate result (a narrowed
218
- * `config.providers`, or a catalog that is momentarily unavailable), and
219
- * treating it as "not loaded yet" re-ran this effect forever. The signature
220
- * also covers the accounts, so logging a second provider in refetches
221
- * instead of leaving that card on the previous answer.
222
- * @param input - the decision inputs.
223
- * @returns true when the caller should start a fetch.
224
- */
225
- export function shouldFetchModelDefaults(input) {
226
- if (input.failed)
227
- return false;
228
- if (input.loggedIn.length === 0)
229
- return false;
230
- // Only an open disclosure pays for the per-model live resolve.
231
- if (!input.open.some(provider => input.loggedIn.includes(provider)))
232
- return false;
233
- return input.loadedFor !== input.signature;
234
- }
235
- /**
236
- * Stable signature of the accounts a catalog answer depends on. A change
237
- * means a previous answer is stale (an account arrived or left), so the next
238
- * open disclosure refetches.
239
- * @param statuses - the per-provider status snapshot.
240
- * @returns a signature string, stable across renders with equal accounts.
241
- */
242
- export function modelDefaultsSignature(statuses) {
243
- return PROVIDERS
244
- .map(({ id }) => `${id}:${(statuses[id]?.accounts ?? []).map(account => account.key).sort().join(',')}`)
245
- .join('|');
246
- }
247
190
  /**
248
191
  * The Subscriptions settings page component.
249
192
  * @param props - the slot inject face ({@link SubscriptionsSectionInjected}).
@@ -265,10 +208,24 @@ export function SubscriptionsSection(props) {
265
208
  const [observedAt, setObservedAt] = useState({});
266
209
  const [usageErrors, setUsageErrors] = useState({});
267
210
  const [usageLoading, setUsageLoading] = useState({});
211
+ const [resetConfirmation, setResetConfirmation] = useState();
212
+ const [resetBusy, setResetBusy] = useState(false);
213
+ const [resetAcknowledged, setResetAcknowledged] = useState(false);
214
+ const [resetMessage, setResetMessage] = useState('');
215
+ const [resetAccount, setResetAccount] = useState();
216
+ const resetDialogRef = useRef(null);
217
+ useEffect(() => {
218
+ if (resetConfirmation)
219
+ resetDialogRef.current?.showModal();
220
+ else
221
+ resetDialogRef.current?.close();
222
+ }, [resetConfirmation]);
223
+ const resetInflight = useRef(false);
268
224
  const mountedRef = useRef(true);
269
225
  const pollersRef = useRef(new Map());
270
226
  /** Accounts with a `usage` call in flight; guards the auto-fetch effect against re-entry. */
271
- const usageInflightRef = useRef(new Set());
227
+ /** In-flight usage reads per key, holding the promise so a forced read can wait one out. */
228
+ const usageInflightRef = useRef(new Map());
272
229
  const usageRosterSignatureRef = useRef();
273
230
  const [managedProvider, setManagedProvider] = useState();
274
231
  const setProviderError = useCallback((provider, message) => {
@@ -362,34 +319,91 @@ export function SubscriptionsSection(props) {
362
319
  };
363
320
  }, [refresh, startPolling]);
364
321
  const loadUsage = useCallback(async (provider, account, force = false) => {
322
+ if (rpc === undefined)
323
+ return;
365
324
  const key = `${provider}:${account}`;
325
+ const pending = usageInflightRef.current.get(key);
326
+ // A forced read must never be dropped behind a poll that started before the
327
+ // state changed. The refresh after a credit redemption is the only thing
328
+ // that picks up the new numbers, and the next automatic poll is minutes
329
+ // away, so waiting the earlier read out is cheaper than showing a stale
330
+ // percentage next to a success message.
331
+ if (pending !== undefined) {
332
+ if (!force)
333
+ return;
334
+ await pending;
335
+ }
366
336
  if (rpc === undefined || usageInflightRef.current.has(key))
367
337
  return;
368
- usageInflightRef.current.add(key);
369
- setUsageLoading(prev => ({ ...prev, [key]: true }));
338
+ const read = (async () => {
339
+ setUsageLoading(prev => ({ ...prev, [key]: true }));
340
+ try {
341
+ const usage = await callSubscriptionsAuth(rpc, 'usage', { provider, account, ...force ? { force: true } : {} });
342
+ if (!mountedRef.current)
343
+ return;
344
+ setUsages(prev => ({ ...prev, [key]: usage }));
345
+ setObservedAt(prev => ({ ...prev, [key]: usage.observedAt ?? Date.now() }));
346
+ window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
347
+ setUsageErrors((prev) => {
348
+ const next = { ...prev };
349
+ delete next[key];
350
+ return next;
351
+ });
352
+ }
353
+ catch (error) {
354
+ if (mountedRef.current)
355
+ setUsageErrors(prev => ({ ...prev, [key]: messageOf(error) }));
356
+ }
357
+ finally {
358
+ usageInflightRef.current.delete(key);
359
+ if (mountedRef.current)
360
+ setUsageLoading(prev => ({ ...prev, [key]: false }));
361
+ }
362
+ })();
363
+ usageInflightRef.current.set(key, read);
364
+ await read;
365
+ }, [rpc]);
366
+ async function prepareReset(account) {
367
+ if (!rpc || resetInflight.current)
368
+ return;
369
+ resetInflight.current = true;
370
+ setResetBusy(true);
371
+ setResetAccount(account);
372
+ setResetConfirmation(undefined);
373
+ setResetMessage('');
370
374
  try {
371
- const usage = await callSubscriptionsAuth(rpc, 'usage', { provider, account, ...force ? { force: true } : {} });
372
- if (!mountedRef.current)
373
- return;
374
- setUsages(prev => ({ ...prev, [key]: usage }));
375
- setObservedAt(prev => ({ ...prev, [key]: usage.observedAt ?? Date.now() }));
376
- window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
377
- setUsageErrors((prev) => {
378
- const next = { ...prev };
379
- delete next[key];
380
- return next;
381
- });
375
+ const result = await callSubscriptionsAuth(rpc, 'prepareReset', { account });
376
+ setResetAcknowledged(false);
377
+ setResetConfirmation({ ...result, account });
382
378
  }
383
379
  catch (error) {
384
- if (mountedRef.current)
385
- setUsageErrors(prev => ({ ...prev, [key]: messageOf(error) }));
380
+ setResetMessage(messageOf(error));
386
381
  }
387
382
  finally {
388
- usageInflightRef.current.delete(key);
389
- if (mountedRef.current)
390
- setUsageLoading(prev => ({ ...prev, [key]: false }));
383
+ resetInflight.current = false;
384
+ setResetBusy(false);
391
385
  }
392
- }, [rpc]);
386
+ }
387
+ async function consumeReset() {
388
+ if (!rpc || !resetConfirmation || !resetAcknowledged || resetInflight.current)
389
+ return;
390
+ const { account, ticket } = resetConfirmation;
391
+ resetInflight.current = true;
392
+ setResetBusy(true);
393
+ setResetConfirmation(undefined);
394
+ try {
395
+ await callSubscriptionsAuth(rpc, 'consumeReset', { account, ticket });
396
+ setResetMessage(t('resetUseSuccess'));
397
+ }
398
+ catch (error) {
399
+ setResetMessage(messageOf(error));
400
+ }
401
+ finally {
402
+ await loadUsage('codex', account, true);
403
+ resetInflight.current = false;
404
+ setResetBusy(false);
405
+ }
406
+ }
393
407
  // Fetch usage once an account is logged in; drop the snapshots of accounts
394
408
  // that vanished so a re-login refetches. A failed lookup does not auto-retry
395
409
  // — the per-account Refresh button is the retry path.
@@ -514,7 +528,7 @@ export function SubscriptionsSection(props) {
514
528
  if (rpc === undefined) {
515
529
  return _jsx("p", { style: styles.intro, children: t('unavailable') });
516
530
  }
517
- return (_jsxs("div", { style: styles.section, children: [_jsx("p", { style: styles.intro, children: t('intro') }), _jsx(UsageBadgeDisplaySetting, { t: t }), PROVIDERS.map(({ id, name }) => {
531
+ return (_jsxs("div", { className: "dsh-subscriptions-settings", style: styles.section, children: [_jsx("style", { children: providerSettingsCss }), _jsx("p", { style: styles.intro, children: t('intro') }), _jsx(UsageBadgeDisplaySetting, { t: t }), PROVIDERS.map(({ id, name }) => {
518
532
  const status = statuses[id];
519
533
  const busy = status?.busy === true;
520
534
  const deviceCode = deviceCodes[id];
@@ -524,17 +538,29 @@ export function SubscriptionsSection(props) {
524
538
  const usage = usages[usageKey];
525
539
  const usageError = usageErrors[usageKey];
526
540
  const display = account.account ?? account.key;
541
+ const sortedCredits = [...(usage?.resetCredits ?? [])].sort((a, b) => (a.expiresAt ?? Infinity) - (b.expiresAt ?? Infinity));
542
+ const nextReset = sortedCredits.find(c => c.expiresAt === undefined || c.expiresAt > Date.now());
543
+ const resetBlock = usage ? resetActionBlock(usage) : 'resetUseRefresh';
544
+ const resetDisabled = resetBusy || resetBlock !== undefined;
527
545
  // Providers without a usage endpoint answer supported:false — no block.
528
546
  const showUsage = usage?.supported !== false
529
547
  && (usage !== undefined || usageError !== undefined || usageLoading[usageKey] === true);
530
548
  return (_jsxs("div", { style: styles.accountRow, children: [_jsxs("div", { style: styles.accountHeader, children: [_jsx("button", { type: "button", style: styles.starButton, title: account.isDefault ? t('defaultBadge') : t('setDefault'), onClick: () => {
531
549
  if (!account.isDefault)
532
550
  void setDefault(id, account.key);
533
- }, children: account.isDefault ? '★' : '☆' }), _jsx("span", { style: styles.accountName, children: display }), account.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: account.plan })), account.expiresAt !== undefined && (_jsx("span", { style: styles.statusLine, children: t('accountExpires', { date: new Date(account.expiresAt).toLocaleString() }) })), _jsx("button", { type: "button", style: { ...styles.button, marginLeft: 'auto', flexShrink: 0 }, onClick: () => { void logout(id, account.key, display, name); }, children: t('logout') })] }), showUsage && (_jsxs("div", { style: styles.usage, children: [_jsxs("div", { style: styles.usageHeader, children: [_jsx("span", { style: styles.usageTitle, children: t('usageTitle') }), usage?.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: t('usagePlan', { plan: usage.plan }) })), _jsx("button", { type: "button", style: { ...styles.usageRefresh, ...usageLoading[usageKey] === true ? { opacity: 0.5, cursor: 'default' } : {} }, disabled: usageLoading[usageKey] === true, onClick: () => { void loadUsage(id, account.key, true); }, children: t('usageRefresh') })] }), usage === undefined && usageError === undefined && (_jsx("p", { style: styles.statusLine, children: t('usageLoading') })), usageError !== undefined && (_jsx("p", { style: styles.errorLine, children: t('usageError', { message: usageError }) })), usage?.windows !== undefined && usage.windows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageEmpty') })), (usage?.windows ?? []).map((window, index) => {
551
+ }, children: account.isDefault ? '★' : '☆' }), _jsx("span", { style: styles.accountName, children: display }), account.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: account.plan })), account.expiresAt !== undefined && (_jsx("span", { style: styles.statusLine, children: t('accountExpires', { date: new Date(account.expiresAt).toLocaleString() }) })), _jsx("button", { type: "button", style: { marginLeft: 'auto', flexShrink: 0 }, onClick: () => { void logout(id, account.key, display, name); }, children: t('logout') })] }), showUsage && (_jsxs("div", { style: styles.usage, children: [_jsxs("div", { style: styles.usageHeader, children: [_jsx("span", { style: styles.usageTitle, children: t('usageTitle') }), usage?.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: t('usagePlan', { plan: usage.plan }) })), _jsx("button", { type: "button", style: styles.usageRefresh, disabled: usageLoading[usageKey] === true, onClick: () => { void loadUsage(id, account.key, true); }, children: t('usageRefresh') })] }), usage === undefined && usageError === undefined && (_jsx("p", { style: styles.statusLine, children: t('usageLoading') })), usageError !== undefined && (_jsx("p", { style: styles.errorLine, children: t('usageError', { message: usageError }) })), usage?.windows !== undefined && usage.windows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageEmpty') })), (usage?.windows ?? []).map((window, index) => {
534
552
  const percent = displayUsedPercent(window.usedPercent);
535
553
  return (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageMeta, children: [_jsx("span", { children: usageWindowLabel(t, window) }), _jsxs("span", { children: [percent === undefined ? t('usageMeterInvalid') : `${percent}%`, window.resetsAt !== undefined
536
554
  && ` · ${t('usageResets', { date: new Date(window.resetsAt).toLocaleString() })}`] })] }), _jsx(UsageMeter, { window: window, t: t, observedAt: observedAt[usageKey], stale: usageError !== undefined || usage.stale === true })] }, index));
537
- })] }))] }, account.key));
538
- }), _jsxs("div", { style: styles.actions, children: [!busy && accounts.length === 0 && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id); }, children: t('login') })), !busy && accounts.length > 0 && id === 'claude' && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id, 'oauth'); }, children: t('addAccountOAuth') }), _jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id, 'keychain'); }, children: t('addAccountKeychain') })] })), !busy && accounts.length > 0 && id !== 'claude' && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id); }, children: t('addAccount') })), _jsx("button", { type: "button", style: styles.button, "aria-haspopup": "dialog", onClick: () => setManagedProvider({ id, name }), children: t('accountsManage') }), busy && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void cancel(id); }, children: t('cancel') }))] }), !busy && accounts.length > 0 && (_jsx("p", { style: styles.statusLine, children: t('addAccountHint') })), busy && deviceCode !== undefined && (_jsxs("div", { style: styles.deviceCode, children: [_jsx("span", { style: styles.statusLine, children: t('deviceCodePrompt') }), _jsx("span", { style: styles.deviceCodeText, children: deviceCode.userCode }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { type: "button", style: styles.button, onClick: () => { copyDeviceCode(id, deviceCode.userCode); }, children: copiedCode === id ? t('deviceCodeCopied') : t('deviceCodeCopy') }), _jsx("button", { type: "button", style: styles.button, onClick: () => { window.open(deviceCode.verificationUrl, '_blank', 'noopener'); }, children: t('deviceCodeOpenPage') })] })] })), busy && deviceCode === undefined && (_jsxs("details", { style: styles.manual, children: [_jsx("summary", { children: t('manualSummary') }), _jsxs("div", { style: styles.manualRow, children: [_jsx("input", { style: styles.manualInput, value: manualDrafts[id], placeholder: t('manualPlaceholder'), onChange: event => setManualDrafts(prev => ({ ...prev, [id]: event.target.value })) }), _jsx("button", { type: "button", style: styles.button, onClick: () => { void submitManual(id); }, children: t('submit') })] })] }))] }, id));
539
- }), _jsx(CursorCard, { rpc: rpc, t: t }), _jsx(ExternalUsageCards, { rpc: rpc, t: t }), managedProvider && _jsx(ProviderAccountManager, { provider: managedProvider.id, name: managedProvider.name, rpc: rpc, t: t, onClose: () => setManagedProvider(undefined) })] }));
555
+ }), (id === 'codex' || id === 'claude') && usage?.resetCreditsError !== undefined && (_jsx(ResetCreditsErrorLine, { provider: id, message: usage.resetCreditsError, t: t })), (id === 'codex' || id === 'claude') && showsResetCredits(id, usage?.resetCredits) && (_jsx(ResetCreditsDisclosure, { mode: id === 'claude' ? 'grants' : 'credits', credits: sortedCredits, t: t, ...nextReset !== undefined ? { nextCredit: nextReset } : {}, ...id === 'codex' ? { useAction: {
556
+ accountKey: account.key,
557
+ busy: resetBusy && resetAccount === account.key,
558
+ disabled: resetDisabled,
559
+ title: t(resetBusy ? 'resetUseChecking' : resetBlock ?? 'resetUseReady'),
560
+ label: t('resetUseButton'),
561
+ onUse: () => { if (!resetDisabled)
562
+ void prepareReset(account.key); },
563
+ } } : {}, ...resetAccount === account.key && resetMessage !== '' ? { statusMessage: resetMessage } : {} }))] }))] }, account.key));
564
+ }), _jsxs("div", { style: styles.actions, children: [!busy && accounts.length === 0 && (_jsx("button", { type: "button", onClick: () => { void login(id); }, children: t('login') })), !busy && accounts.length > 0 && id === 'claude' && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: () => { void login(id, 'oauth'); }, children: t('addAccountOAuth') }), _jsx("button", { type: "button", onClick: () => { void login(id, 'keychain'); }, children: t('addAccountKeychain') })] })), !busy && accounts.length > 0 && id !== 'claude' && (_jsx("button", { type: "button", onClick: () => { void login(id); }, children: t('addAccount') })), _jsx("button", { type: "button", "aria-haspopup": "dialog", onClick: () => setManagedProvider({ id, name }), children: t('accountsManage') }), busy && (_jsx("button", { type: "button", onClick: () => { void cancel(id); }, children: t('cancel') }))] }), !busy && accounts.length > 0 && (_jsx("p", { style: styles.statusLine, children: t('addAccountHint') })), busy && deviceCode !== undefined && (_jsxs("div", { style: styles.deviceCode, children: [_jsx("span", { style: styles.statusLine, children: t('deviceCodePrompt') }), _jsx("span", { style: styles.deviceCodeText, children: deviceCode.userCode }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { type: "button", onClick: () => { copyDeviceCode(id, deviceCode.userCode); }, children: copiedCode === id ? t('deviceCodeCopied') : t('deviceCodeCopy') }), _jsx("button", { type: "button", onClick: () => { window.open(deviceCode.verificationUrl, '_blank', 'noopener'); }, children: t('deviceCodeOpenPage') })] })] })), busy && deviceCode === undefined && (_jsxs("details", { style: styles.manual, children: [_jsx("summary", { children: t('manualSummary') }), _jsxs("div", { style: styles.manualRow, children: [_jsx("input", { style: styles.manualInput, value: manualDrafts[id], placeholder: t('manualPlaceholder'), onChange: event => setManualDrafts(prev => ({ ...prev, [id]: event.target.value })) }), _jsx("button", { type: "button", onClick: () => { void submitManual(id); }, children: t('submit') })] })] }))] }, id));
565
+ }), _jsx(CursorCard, { rpc: rpc, t: t }), _jsx(ExternalUsageCards, { rpc: rpc, t: t }), managedProvider && _jsx(ProviderAccountManager, { provider: managedProvider.id, name: managedProvider.name, rpc: rpc, t: t, onClose: () => setManagedProvider(undefined) }), _jsx("style", { children: '.subscriptions-reset-dialog::backdrop { background: var(--dsw-alias-bg-mask-1, #0006); backdrop-filter: var(--dsw-mask-blur, none); }' }), _jsx("dialog", { ref: resetDialogRef, className: "subscriptions-reset-dialog", "aria-labelledby": "subscriptions-reset-title", onCancel: () => setResetConfirmation(undefined), onClose: () => setResetConfirmation(undefined), style: { ...styles.modal, display: resetConfirmation ? 'flex' : 'none', margin: 'auto', color: 'var(--dsw-alias-label-primary)' }, children: resetConfirmation && _jsxs(_Fragment, { children: [_jsx("h3", { id: "subscriptions-reset-title", style: { ...styles.modalTitle, margin: 0 }, children: t('resetUseConfirmTitle') }), _jsx("p", { style: { ...styles.statusLine, overflowWrap: 'anywhere' }, children: statuses.codex?.accounts.find(a => a.key === resetConfirmation.account)?.account ?? resetConfirmation.account }), _jsxs("div", { style: { ...styles.usageMeta, gap: 12 }, children: [_jsx("span", { children: t('usageWeekly') }), _jsxs("strong", { children: [Math.round(resetConfirmation.weeklyUsedPercent), "%"] })] }), _jsx("div", { style: styles.usageMeta, children: _jsx("span", { children: resetConfirmation.creditExpiresAt === undefined ? t('resetCreditExpiryUnknown') : t('resetCreditExpires', { date: new Date(resetConfirmation.creditExpiresAt).toLocaleString() }) }) }), _jsx("p", { style: { ...styles.statusLine, margin: '4px 0' }, children: t('resetUseWarning') }), _jsxs("label", { style: { display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 12 }, children: [_jsx("input", { type: "checkbox", checked: resetAcknowledged, onChange: e => setResetAcknowledged(e.target.checked) }), t('resetUseAcknowledge')] }), _jsxs("div", { className: "dsh-subscription-dialog-actions", style: { ...styles.actions, justifyContent: 'flex-end', marginTop: 8 }, children: [_jsx("button", { type: "button", autoFocus: true, onClick: () => setResetConfirmation(undefined), children: t('cancel') }), _jsx("button", { type: "button", className: "dsh-subscription-primary", disabled: !resetAcknowledged || resetBusy, onClick: () => { void consumeReset(); }, children: t('resetUseConfirm') })] })] }) })] }));
540
566
  }
@@ -39,15 +39,11 @@ export function UsageBadgeDisplaySetting({ t }) {
39
39
  }
40
40
  const styles = {
41
41
  card: {
42
- border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 12,
42
+ border: '0.5px solid var(--dsw-alias-settings-card-stroke, var(--dsw-alias-border-l4))',
43
+ borderRadius: 'var(--dsw-radius-xl, 20px)', background: 'var(--dsw-alias-settings-card-fill, var(--dsw-alias-bg-layer-2))',
43
44
  padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 6,
44
45
  },
45
- field: { display: 'flex', flexDirection: 'column', gap: 4, fontSize: 14, lineHeight: '22px' },
46
- select: {
47
- height: 32, width: '100%', boxSizing: 'border-box',
48
- border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
49
- padding: '0 10px', font: 'inherit',
50
- background: 'var(--dsw-alias-bg-layer-1)', color: 'var(--dsw-alias-label-primary)',
51
- },
46
+ field: { display: 'flex', flexDirection: 'column', gap: 6, fontSize: 13, fontWeight: 500, lineHeight: '20px' },
47
+ select: { width: '100%' },
52
48
  hint: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },
53
49
  };
@@ -83,7 +83,7 @@ export declare const HOST_CONTRACT: {
83
83
  /** Design tokens the client styles with; the host theme must define each one. */
84
84
  readonly tokens: {
85
85
  readonly package: "@deepseek-ai/dsh-client-ui-theme";
86
- readonly names: readonly ["--dsw-alias-bg-layer-1", "--dsw-alias-bg-skeleton", "--dsw-alias-border-l2", "--dsw-alias-border-l2-darkmode-thin", "--dsw-alias-interactive-bg-hover", "--dsw-alias-interactive-bg-hover-solid", "--dsw-alias-label-dimmed", "--dsw-alias-label-primary", "--dsw-alias-label-secondary", "--dsw-alias-label-tertiary", "--dsw-alias-state-error-primary", "--dsw-alias-state-success-primary", "--dsw-alias-state-warn-label", "--dsw-elevation-prominent", "--dsw-menu-backdrop-filter", "--dsw-specific-menu"];
86
+ readonly names: readonly ["--dsw-alias-bg-layer-1", "--dsw-alias-bg-layer-2", "--dsw-alias-bg-layer-3", "--dsw-alias-bg-mask-1", "--dsw-alias-bg-skeleton", "--dsw-alias-border-l1", "--dsw-alias-border-l2", "--dsw-alias-border-l2-darkmode-thin", "--dsw-alias-border-l3", "--dsw-alias-border-l4", "--dsw-alias-brand-primary", "--dsw-alias-button-primary-fill", "--dsw-alias-button-primary-hover", "--dsw-alias-interactive-bg-active", "--dsw-alias-interactive-bg-hover", "--dsw-alias-interactive-bg-hover-solid", "--dsw-alias-label-dimmed", "--dsw-alias-label-primary", "--dsw-alias-label-primary-foreground", "--dsw-alias-label-secondary", "--dsw-alias-label-tertiary", "--dsw-alias-settings-card-fill", "--dsw-alias-settings-card-stroke", "--dsw-alias-state-business-primary", "--dsw-alias-state-error-primary", "--dsw-alias-state-success-primary", "--dsw-alias-state-warn-label", "--dsw-elevation-prominent", "--dsw-focus-ring-color", "--dsw-focus-ring-width", "--dsw-mask-blur", "--dsw-menu-backdrop-filter", "--dsw-radius-lg", "--dsw-radius-md", "--dsw-radius-panel", "--dsw-radius-sm", "--dsw-radius-xl", "--dsw-specific-menu"];
87
87
  };
88
88
  };
89
89
  /** A glyph name listed in {@link HOST_CONTRACT}; hostIcon() accepts no other. */
@@ -82,20 +82,42 @@ export const HOST_CONTRACT = {
82
82
  package: '@deepseek-ai/dsh-client-ui-theme',
83
83
  names: [
84
84
  '--dsw-alias-bg-layer-1',
85
+ '--dsw-alias-bg-layer-2',
86
+ '--dsw-alias-bg-layer-3',
87
+ '--dsw-alias-bg-mask-1',
85
88
  '--dsw-alias-bg-skeleton',
89
+ '--dsw-alias-border-l1',
86
90
  '--dsw-alias-border-l2',
87
91
  '--dsw-alias-border-l2-darkmode-thin',
92
+ '--dsw-alias-border-l3',
93
+ '--dsw-alias-border-l4',
94
+ '--dsw-alias-brand-primary',
95
+ '--dsw-alias-button-primary-fill',
96
+ '--dsw-alias-button-primary-hover',
97
+ '--dsw-alias-interactive-bg-active',
88
98
  '--dsw-alias-interactive-bg-hover',
89
99
  '--dsw-alias-interactive-bg-hover-solid',
90
100
  '--dsw-alias-label-dimmed',
91
101
  '--dsw-alias-label-primary',
102
+ '--dsw-alias-label-primary-foreground',
92
103
  '--dsw-alias-label-secondary',
93
104
  '--dsw-alias-label-tertiary',
105
+ '--dsw-alias-settings-card-fill',
106
+ '--dsw-alias-settings-card-stroke',
107
+ '--dsw-alias-state-business-primary',
94
108
  '--dsw-alias-state-error-primary',
95
109
  '--dsw-alias-state-success-primary',
96
110
  '--dsw-alias-state-warn-label',
97
111
  '--dsw-elevation-prominent',
112
+ '--dsw-focus-ring-color',
113
+ '--dsw-focus-ring-width',
114
+ '--dsw-mask-blur',
98
115
  '--dsw-menu-backdrop-filter',
116
+ '--dsw-radius-lg',
117
+ '--dsw-radius-md',
118
+ '--dsw-radius-panel',
119
+ '--dsw-radius-sm',
120
+ '--dsw-radius-xl',
99
121
  '--dsw-specific-menu',
100
122
  ],
101
123
  },
@@ -104,6 +104,35 @@ export declare const en: {
104
104
  usageColorPaceHint: string;
105
105
  usageColorRemainingHint: string;
106
106
  usagePlan: string;
107
+ resetCreditsTitle: string;
108
+ resetCreditsAvailable: string;
109
+ resetCreditsError: string;
110
+ resetCreditsErrorFixed: string;
111
+ resetCreditReadOnly: string;
112
+ resetCreditReadOnlyLabel: string;
113
+ resetCreditAvailable: string;
114
+ resetCreditNotUsable: string;
115
+ resetCreditPaused: string;
116
+ resetCreditExhausted: string;
117
+ resetCreditExpired: string;
118
+ resetCreditRemaining: string;
119
+ resetCreditLeft: string;
120
+ resetCreditRemainingUnknown: string;
121
+ resetCreditCooldownUntil: string;
122
+ resetUseButton: string;
123
+ resetUseChecking: string;
124
+ resetUseGuard: string;
125
+ resetUseRestart: string;
126
+ resetUseRefresh: string;
127
+ resetUseReady: string;
128
+ resetUseConfirmTitle: string;
129
+ resetUseWarning: string;
130
+ resetUseAcknowledge: string;
131
+ resetUseConfirm: string;
132
+ resetUseSuccess: string;
133
+ resetCreditFull: string;
134
+ resetCreditExpires: string;
135
+ resetCreditExpiryUnknown: string;
107
136
  externalUsageTitle: string;
108
137
  externalUsageConnected: string;
109
138
  externalUsageNotConfigured: string;
@@ -276,6 +305,35 @@ export declare const zh: {
276
305
  usageColorPaceHint: string;
277
306
  usageColorRemainingHint: string;
278
307
  usagePlan: string;
308
+ resetCreditsTitle: string;
309
+ resetCreditsAvailable: string;
310
+ resetCreditsError: string;
311
+ resetCreditsErrorFixed: string;
312
+ resetCreditReadOnly: string;
313
+ resetCreditReadOnlyLabel: string;
314
+ resetCreditAvailable: string;
315
+ resetCreditNotUsable: string;
316
+ resetCreditPaused: string;
317
+ resetCreditExhausted: string;
318
+ resetCreditExpired: string;
319
+ resetCreditRemaining: string;
320
+ resetCreditLeft: string;
321
+ resetCreditRemainingUnknown: string;
322
+ resetCreditCooldownUntil: string;
323
+ resetUseButton: string;
324
+ resetUseChecking: string;
325
+ resetUseGuard: string;
326
+ resetUseRestart: string;
327
+ resetUseRefresh: string;
328
+ resetUseReady: string;
329
+ resetUseConfirmTitle: string;
330
+ resetUseWarning: string;
331
+ resetUseAcknowledge: string;
332
+ resetUseConfirm: string;
333
+ resetUseSuccess: string;
334
+ resetCreditFull: string;
335
+ resetCreditExpires: string;
336
+ resetCreditExpiryUnknown: string;
279
337
  externalUsageTitle: string;
280
338
  externalUsageConnected: string;
281
339
  externalUsageNotConfigured: string;