dsh-subscriptions 0.1.3 → 0.1.4

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 (58) hide show
  1. package/README.md +64 -0
  2. package/lib/auth/rpc.d.ts +3 -1
  3. package/lib/auth/rpc.js +10 -1
  4. package/lib/client/ProviderAccountManager.js +34 -41
  5. package/lib/client/ProviderModelEditor.js +5 -9
  6. package/lib/client/SpeedSelect.js +8 -7
  7. package/lib/client/SubscriptionUsageBadge.js +6 -5
  8. package/lib/client/SubscriptionsSection.d.ts +22 -0
  9. package/lib/client/SubscriptionsSection.js +128 -38
  10. package/lib/client/UsageBadgeDisplaySetting.js +4 -8
  11. package/lib/client/host-contract.d.ts +1 -1
  12. package/lib/client/host-contract.js +22 -0
  13. package/lib/client/locales.d.ts +58 -0
  14. package/lib/client/locales.js +58 -0
  15. package/lib/client/provider-settings-styles.d.ts +3 -0
  16. package/lib/client/provider-settings-styles.js +206 -0
  17. package/lib/client/reset-action.d.ts +14 -0
  18. package/lib/client/reset-action.js +15 -0
  19. package/lib/client/reset-credits-view.d.ts +53 -0
  20. package/lib/client/reset-credits-view.js +103 -0
  21. package/lib/client.js +937 -310
  22. package/lib/client.js.map +1 -1
  23. package/lib/index.d.ts +9 -0
  24. package/lib/index.js +1099 -608
  25. package/lib/providers/account-preferences.js +3 -2
  26. package/lib/providers/catalog-store.d.ts +3 -2
  27. package/lib/providers/catalog-store.js +57 -26
  28. package/lib/providers/claude.d.ts +22 -3
  29. package/lib/providers/claude.js +113 -15
  30. package/lib/providers/codex.d.ts +15 -2
  31. package/lib/providers/codex.js +72 -4
  32. package/lib/providers/common.d.ts +25 -0
  33. package/lib/providers/common.js +22 -18
  34. package/lib/providers/copilot.d.ts +2 -1
  35. package/lib/providers/copilot.js +10 -3
  36. package/lib/providers/grok.d.ts +2 -2
  37. package/lib/providers/grok.js +2 -2
  38. package/lib/providers/pool-health.d.ts +2 -0
  39. package/lib/providers/pool-health.js +9 -0
  40. package/lib/providers/pool-usage.d.ts +13 -1
  41. package/lib/providers/pool-usage.js +39 -7
  42. package/lib/providers/pool.d.ts +7 -5
  43. package/lib/providers/pool.js +89 -12
  44. package/lib/providers/rate-limit.d.ts +0 -14
  45. package/lib/providers/rate-limit.js +0 -26
  46. package/lib/providers/replay.d.ts +4 -0
  47. package/lib/providers/replay.js +50 -0
  48. package/lib/providers/reset-redemption.d.ts +51 -0
  49. package/lib/providers/reset-redemption.js +102 -0
  50. package/lib/translate/anthropic.d.ts +21 -2
  51. package/lib/translate/anthropic.js +35 -11
  52. package/lib/translate/antigravity.js +13 -6
  53. package/lib/translate/chat-completions.js +1 -1
  54. package/lib/translate/resolved.d.ts +21 -0
  55. package/lib/translate/resolved.js +63 -3
  56. package/lib/translate/responses.js +8 -6
  57. package/lib/translate/sse.js +8 -0
  58. package/package.json +1 -1
package/README.md CHANGED
@@ -97,6 +97,43 @@ in **Settings → Subscriptions**.
97
97
 
98
98
  ![Usage dialog with elapsed-time cursors](docs/images/usage-badge.png)
99
99
 
100
+ #### Codex usage limit resets
101
+
102
+ The Codex account card shows banked full-reset credits and their expirations
103
+ in a closed disclosure. A failed optional lookup reports an error without
104
+ hiding ordinary quota windows. **Use…** selects the earliest-expiring available
105
+ credit and requires a fresh unscoped weekly reading of at least 80%, then an
106
+ explicit confirmation and acknowledgment. Confirmation expires after 60
107
+ seconds; the server rechecks usage and credit availability before submission.
108
+
109
+ One reset consumes one credit and resets weekly and 5-hour limits. This
110
+ interface cannot undo it. An unconfirmed outcome blocks further redemption
111
+ for that account in the current host process. Restarting clears that block,
112
+ but must not be used to bypass it: check credits and limits in Codex before
113
+ retrying, because the first submission may already have spent a credit.
114
+ The private endpoint is covered by offline fakes, not live-account checks.
115
+ See the [manual reset decision](.agents/notes/implemented/provider/2026-10-04-codex-manual-reset.md)
116
+ for the retry boundary and verification limits.
117
+
118
+ #### Claude usage limit resets
119
+
120
+ The Claude account card shows banked limit resets from Anthropic's
121
+ `cedar_ember` program, in the same closed disclosure Codex uses. It lists one
122
+ row per grant, not one per credit, and the summary counts remaining resets by
123
+ summing `resetsLeft` over grants that are neither expired nor paused. Each row
124
+ states whether that grant is available, not yet usable, paused, exhausted, or
125
+ expired, with its remaining count and when it lapses. That time is the grant's
126
+ expiry: it is not a window reset and does not move the elapsed-time cursor.
127
+
128
+ This is **display only**. `claimable` is shown as data on a text row, and the
129
+ page has no control that spends a Claude reset, because the response shape is
130
+ not an Anthropic contract. An ineligible account, a grant-less block, or a
131
+ rejected optional query leaves ordinary session and weekly windows untouched
132
+ and shows no list; a failed lookup shows a fixed sentence rather than the
133
+ provider's text. No live account was used to verify what Anthropic returns. See
134
+ [the evidence and limits](docs/claude-banked-resets.md) and the
135
+ [display decision](.agents/notes/implemented/provider/2026-10-08-claude-banked-reset-display.md).
136
+
100
137
  ## Install
101
138
 
102
139
  Current source is tested with DSH `0.1.7-rc.2`, `0.2.0-rc.1`, and
@@ -183,6 +220,33 @@ usage.
183
220
  The plugin follows DSH network settings. Its former proxy configuration and
184
221
  UI have been retired; an old plugin proxy config file is ignored.
185
222
 
223
+ ## Claude prompt-cache TTL
224
+
225
+ Set `claudePromptCacheTtl: '5m'` (default) or `'1h'` in the plugin config and
226
+ restart DSH. This affects Claude only; other providers keep their own caching.
227
+ Five minutes omits the wire `ttl` field, preserving the existing request. One
228
+ hour adds `ttl: "1h"` to the tools+system prefix and up to three conversation
229
+ breakpoints. All marks share one TTL: Anthropic rejects a one-hour mark after
230
+ a five-minute mark. Requests classified with `purpose` as compaction or
231
+ session-title keep five minutes. Unclassified calls, including subagents and
232
+ calls from hosts that do not supply `purpose`, follow the configured TTL.
233
+ The TTL setting does not require a newer host capability or dependency.
234
+
235
+ Cache reads have the same API price for either TTL. One-hour writes are priced
236
+ at 2× base input instead of 1.25×. They may pay off with reusable prefixes and
237
+ 5–60-minute pauses; benefits depend on the workload and do not establish
238
+ subscription quota savings. Frequent reuse within five minutes may not offset
239
+ the higher write price. Claude Code requests one hour for its main subscription
240
+ conversation; see
241
+ [Claude Code's prompt-caching notes](https://code.claude.com/docs/en/prompt-caching#which-ttl-each-request-gets).
242
+
243
+ Changing TTL on a cached conversation may rewrite the prefix. Upstream testing
244
+ found that the first one-hour request after five-minute writes rewrote a
245
+ conversation-tail prefix, while a system-only breakpoint could hit. A
246
+ five-minute request can read a one-hour entry; a one-hour-marked read does not
247
+ upgrade an existing five-minute entry's lifetime. These observations are not
248
+ live-account verification of this fork.
249
+
186
250
  ## Usage coloring
187
251
 
188
252
  **Settings → Subscriptions → Usage coloring** offers one browser-local dropdown:
package/lib/auth/rpc.d.ts CHANGED
@@ -23,7 +23,7 @@ export declare const SUBSCRIPTIONS_AUTH_PREFIX = "subscriptions-auth.";
23
23
  * Every endpoint {@link dispatch} answers; each gets one exact Fetch route.
24
24
  * Kept in one place so the route table and the switch cannot drift apart.
25
25
  */
26
- export declare const SUBSCRIPTIONS_AUTH_ENDPOINTS: readonly ["providerSettings", "setProviderSettings", "status", "login", "manual", "cancel", "logout", "setDefault", "usage", "externalStatus", "externalUsage", "cursorStatus", "cursorLogin", "cursorCancel", "cursorLogout", "cursorUsage", "cursorModels", "cursorSettings", "cursorSetSettings", "image", "video", "speed", "setSpeed", "modelDefaults", "setModelDefault"];
26
+ export declare const SUBSCRIPTIONS_AUTH_ENDPOINTS: readonly ["providerSettings", "setProviderSettings", "status", "login", "manual", "cancel", "logout", "setDefault", "usage", "externalStatus", "externalUsage", "cursorStatus", "cursorLogin", "cursorCancel", "cursorLogout", "cursorUsage", "cursorModels", "cursorSettings", "cursorSetSettings", "image", "video", "prepareReset", "consumeReset", "speed", "setSpeed", "modelDefaults", "setModelDefault"];
27
27
  /** Decoded image bytes returned by the `image` endpoint. */
28
28
  export interface ImageBytesResult {
29
29
  mediaType: string;
@@ -121,6 +121,8 @@ export interface ModelDefaultsController {
121
121
  }
122
122
  /** Provider-agnostic auth operations the RPC handler delegates to. */
123
123
  export interface AuthController {
124
+ prepareReset?(account: string, signal: AbortSignal): Promise<unknown>;
125
+ consumeReset?(account: string, ticket: string, signal: AbortSignal): Promise<void>;
124
126
  /** Current status of one provider. */
125
127
  status(provider: ProviderId): Promise<ProviderStatus>;
126
128
  /**
package/lib/auth/rpc.js CHANGED
@@ -25,7 +25,7 @@ export const SUBSCRIPTIONS_AUTH_ENDPOINTS = [
25
25
  'externalStatus', 'externalUsage',
26
26
  'cursorStatus', 'cursorLogin', 'cursorCancel', 'cursorLogout', 'cursorUsage', 'cursorModels',
27
27
  'cursorSettings', 'cursorSetSettings',
28
- 'image', 'video',
28
+ 'image', 'video', 'prepareReset', 'consumeReset',
29
29
  'speed', 'setSpeed',
30
30
  'modelDefaults', 'setModelDefault',
31
31
  ];
@@ -322,6 +322,15 @@ async function dispatch(controller, speed, modelDefaults, endpoint, payload, sig
322
322
  throw new BadRequest('payload must be an object');
323
323
  await cursor.setSettings(payload.settings);
324
324
  return ok({ ok: true });
325
+ case 'prepareReset':
326
+ if (!controller.prepareReset)
327
+ throw new BadRequest('Reset redemption unavailable');
328
+ return ok(await controller.prepareReset(readString(payload, 'account'), signal));
329
+ case 'consumeReset':
330
+ if (!controller.consumeReset)
331
+ throw new BadRequest('Reset redemption unavailable');
332
+ await controller.consumeReset(readString(payload, 'account'), readString(payload, 'ticket'), signal);
333
+ return ok({ ok: true });
325
334
  case 'image':
326
335
  return ok(await controller.readImage(readImageRef(payload), signal));
327
336
  case 'video':
@@ -3,13 +3,8 @@ import { useEffect, useId, useRef, useState } from 'react';
3
3
  import { callSubscriptionsAuth } from './subscriptions-rpc.js';
4
4
  import { accountModelRows, accountPoolSelection, mergeAccountChanges, mergeLatestAccounts } from './account-preferences.js';
5
5
  import { ProviderModelEditor } from './ProviderModelEditor.js';
6
- const border = '1px solid var(--dsw-alias-border-l2)';
7
- const control = {
8
- font: 'inherit', color: 'inherit', background: 'var(--dsw-alias-bg-layer-1)',
9
- border, borderRadius: 8, padding: '7px 12px', minWidth: 0,
10
- };
11
- const button = { ...control, cursor: 'pointer' };
12
- const hint = { margin: 0, fontSize: 12, lineHeight: 1.6, color: 'var(--dsw-alias-label-tertiary)' };
6
+ import { providerSettingsCss } from './provider-settings-styles.js';
7
+ const border = '0.5px solid var(--dsw-alias-border-l2)';
13
8
  const stack = { display: 'grid', gap: 12, minWidth: 0 };
14
9
  const actions = { display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' };
15
10
  /** Native top-layer dialog provides keyboard containment, Escape and focus restoration. */
@@ -110,38 +105,36 @@ export function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
110
105
  setSaving(false);
111
106
  }
112
107
  }
113
- return _jsx("dialog", { ref: dialog, "aria-labelledby": title, "aria-describedby": description, onCancel: event => { if (saveLock.current)
114
- event.preventDefault(); }, onClose: onClose, style: { width: 620, maxWidth: 'calc(100vw - 32px)', maxHeight: 'calc(100dvh - 32px)', boxSizing: 'border-box',
115
- padding: 0, border, borderRadius: 16, color: 'var(--dsw-alias-label-primary)',
116
- background: 'var(--dsw-alias-bg-layer-1)', boxShadow: '0 20px 70px #0004', overflow: 'auto' }, children: _jsxs("div", { style: { ...stack, padding: 20 }, children: [_jsxs("header", { style: { ...actions, justifyContent: 'space-between' }, children: [_jsx("h2", { id: title, style: { margin: 0, fontSize: 18 }, children: t('accountsTitle', { provider: name }) }), _jsx("button", { type: "button", autoFocus: true, style: button, disabled: saving, onClick: onClose, children: t('imageClose') })] }), _jsx("p", { id: description, style: hint, children: t(provider === 'cursor-subscription' ? 'cursorAccountsHint' : 'accountsHint') }), error && _jsx("p", { role: "alert", style: { ...hint, color: 'var(--dsw-alias-state-error-primary)' }, children: error }), loading && _jsx("p", { role: "status", style: hint, children: t('accountsLoading') }), !loading && !catalog && _jsx("button", { type: "button", style: button, onClick: () => setAttempt(value => value + 1), children: t('modelDefaultsRetry') }), catalog && _jsxs("fieldset", { disabled: saving || loading, style: { ...stack, border: 0, margin: 0, padding: 0 }, children: [catalog.accounts.length === 0 && _jsx("p", { style: hint, children: t(provider === 'cursor-subscription' ? 'cursorManageSignIn' : 'accountsEmpty') }), provider === 'cursor-subscription' && catalog.accounts.length > 0 &&
117
- _jsxs("fieldset", { style: { border, borderRadius: 12, padding: 14 }, children: [_jsx("legend", { style: { padding: '0 6px', fontWeight: 600, fontSize: 14 }, children: name }), _jsx("p", { style: hint, children: t('cursorConnected') })] }), provider !== 'cursor-subscription' && catalog.accounts.map(account => {
118
- const preferences = changes[account.key] ?? catalog.settings.accounts?.[account.key] ?? {};
119
- const selected = accountPoolSelection(preferences, account.models);
120
- const models = accountModelRows(account, preferences);
121
- return _jsxs("fieldset", { style: { ...stack, border, borderRadius: 12, padding: 14, margin: 0 }, children: [_jsx("legend", { style: { padding: '0 6px', fontWeight: 600, fontSize: 14, overflowWrap: 'anywhere', maxWidth: '100%' }, children: account.label }), account.unavailable && _jsx("p", { style: hint, children: t('accountsCatalogUnavailable') }), _jsxs("label", { style: { ...stack, gap: 6 }, children: [_jsx("span", { style: { fontSize: 12 }, children: t('accountsAlias') }), _jsx("input", { style: control, value: preferences.alias ?? '', placeholder: account.label, onChange: event => {
122
- const next = { ...preferences };
123
- if (event.target.value.trim())
124
- next.alias = event.target.value;
125
- else
126
- delete next.alias;
127
- edit(account.key, next);
128
- } })] }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: preferences.poolEnabled !== false, onChange: event => edit(account.key, { ...preferences, poolEnabled: event.target.checked }) }), " ", t('accountsPoolEnabled')] }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: preferences.independentEntry === true, onChange: event => edit(account.key, { ...preferences, independentEntry: event.target.checked }) }), " ", t('accountsIndependent')] }), _jsx("p", { style: hint, children: t('accountsIndependentHint') }), _jsxs("details", { children: [_jsxs("summary", { style: { cursor: 'pointer', fontSize: 13 }, children: [t('accountsModels'), " \u00B7 ", preferences.poolModels === undefined
129
- ? t('accountsModelsAll') : t('accountsModelsCount', { count: preferences.poolModels.length })] }), _jsxs("div", { style: { ...stack, marginTop: 12 }, children: [_jsx("p", { style: hint, children: t('accountsModelsHint') }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: preferences.poolModels === undefined, onChange: event => {
130
- const next = { ...preferences };
131
- if (event.target.checked)
132
- delete next.poolModels;
133
- else
134
- next.poolModels = account.models.map(model => model.id);
135
- edit(account.key, next);
136
- } }), " ", t('accountsModelsAutomatic')] }), _jsxs("div", { style: actions, children: [_jsx("button", { type: "button", style: button, onClick: () => edit(account.key, { ...preferences, poolModels: models.map(model => model.id) }), children: t('modelsSelectAll') }), _jsx("button", { type: "button", style: button, onClick: () => edit(account.key, { ...preferences, poolModels: [] }), children: t('modelsSelectNone') })] }), _jsxs("div", { style: { ...stack, gap: 8, maxHeight: 240, overflowY: 'auto' }, children: [models.map(model => _jsxs("label", { style: { fontSize: 13, overflowWrap: 'anywhere' }, children: [_jsx("input", { type: "checkbox", checked: selected.has(model.id), onChange: event => {
137
- const next = new Set(selected);
138
- if (event.target.checked)
139
- next.add(model.id);
140
- else
141
- next.delete(model.id);
142
- edit(account.key, { ...preferences, poolModels: [...next] });
143
- } }), " ", model.name, model.unavailable && ` (${t('modelsUnavailable')})`] }, model.id)), models.length === 0 && _jsx("p", { style: hint, children: t('accountsNoModels') })] })] })] })] }, account.key);
144
- })] }), (provider !== 'cursor-subscription' || (catalog?.accounts.length ?? 0) > 0) &&
145
- _jsx(ProviderModelEditor, { ref: editor, provider: provider, rpc: rpc, t: t, disabled: saving, onDirtyChange: setModelsDirty }), _jsxs("footer", { style: { ...stack, gap: 8, borderTop: border, padding: '14px 0 0',
146
- position: 'sticky', bottom: 0, background: 'var(--dsw-alias-bg-layer-1)' }, children: [saveError && _jsx("p", { role: "alert", style: { ...hint, color: 'var(--dsw-alias-state-error-primary)' }, children: saveError }), _jsxs("div", { style: { ...actions, justifyContent: 'flex-end' }, children: [_jsx("button", { type: "button", style: button, disabled: saving, onClick: onClose, children: t('cancel') }), _jsx("button", { type: "button", style: { ...button, fontWeight: 600 }, disabled: loading || saving || (!Object.keys(changes).length && !modelsDirty), onClick: () => { void save(); }, children: saving ? t('modelDefaultsSaving') : t('modelsSave') })] })] })] }) });
108
+ return _jsxs("dialog", { className: "dsh-subscription-manager", ref: dialog, "aria-labelledby": title, "aria-describedby": description, onCancel: event => { if (saveLock.current)
109
+ event.preventDefault(); }, onClose: onClose, style: { width: 620, maxWidth: 'calc(100vw - 32px)', maxHeight: 'calc(100dvh - 32px)', overflow: 'auto' }, children: [_jsx("style", { children: providerSettingsCss }), _jsxs("div", { style: { ...stack, padding: 20 }, children: [_jsxs("header", { style: { ...actions, justifyContent: 'space-between' }, children: [_jsx("h2", { id: title, style: { margin: 0 }, children: t('accountsTitle', { provider: name }) }), _jsx("button", { type: "button", autoFocus: true, disabled: saving, onClick: onClose, children: t('imageClose') })] }), _jsx("p", { id: description, className: "dsh-subscription-hint", children: t(provider === 'cursor-subscription' ? 'cursorAccountsHint' : 'accountsHint') }), error && _jsx("p", { role: "alert", children: error }), loading && _jsx("p", { role: "status", className: "dsh-subscription-hint", children: t('accountsLoading') }), !loading && !catalog && _jsx("button", { type: "button", onClick: () => setAttempt(value => value + 1), children: t('modelDefaultsRetry') }), catalog && _jsxs("fieldset", { disabled: saving || loading, style: { ...stack, border: 0, margin: 0, padding: 0 }, children: [catalog.accounts.length === 0 && _jsx("p", { className: "dsh-subscription-hint", children: t(provider === 'cursor-subscription' ? 'cursorManageSignIn' : 'accountsEmpty') }), provider === 'cursor-subscription' && catalog.accounts.length > 0 &&
110
+ _jsxs("fieldset", { style: { border, borderRadius: 'var(--dsw-radius-lg, 12px)', padding: 14 }, children: [_jsx("legend", { style: { padding: '0 6px' }, children: name }), _jsx("p", { className: "dsh-subscription-hint", children: t('cursorConnected') })] }), provider !== 'cursor-subscription' && catalog.accounts.map(account => {
111
+ const preferences = changes[account.key] ?? catalog.settings.accounts?.[account.key] ?? {};
112
+ const selected = accountPoolSelection(preferences, account.models);
113
+ const models = accountModelRows(account, preferences);
114
+ return _jsxs("fieldset", { style: { ...stack, border, borderRadius: 'var(--dsw-radius-lg, 12px)', padding: 14, margin: 0 }, children: [_jsx("legend", { style: { padding: '0 6px', overflowWrap: 'anywhere', maxWidth: '100%' }, children: account.label }), account.unavailable && _jsx("p", { className: "dsh-subscription-hint", children: t('accountsCatalogUnavailable') }), _jsxs("label", { style: { ...stack, gap: 6 }, children: [_jsx("span", { className: "dsh-subscription-label", children: t('accountsAlias') }), _jsx("input", { value: preferences.alias ?? '', placeholder: account.label, onChange: event => {
115
+ const next = { ...preferences };
116
+ if (event.target.value.trim())
117
+ next.alias = event.target.value;
118
+ else
119
+ delete next.alias;
120
+ edit(account.key, next);
121
+ } })] }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: preferences.poolEnabled !== false, onChange: event => edit(account.key, { ...preferences, poolEnabled: event.target.checked }) }), " ", t('accountsPoolEnabled')] }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: preferences.independentEntry === true, onChange: event => edit(account.key, { ...preferences, independentEntry: event.target.checked }) }), " ", t('accountsIndependent')] }), _jsx("p", { className: "dsh-subscription-hint", children: t('accountsIndependentHint') }), _jsxs("details", { children: [_jsxs("summary", { style: { cursor: 'pointer', fontSize: 13 }, children: [t('accountsModels'), " \u00B7 ", preferences.poolModels === undefined
122
+ ? t('accountsModelsAll') : t('accountsModelsCount', { count: preferences.poolModels.length })] }), _jsxs("div", { style: { ...stack, marginTop: 12 }, children: [_jsx("p", { className: "dsh-subscription-hint", children: t('accountsModelsHint') }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: preferences.poolModels === undefined, onChange: event => {
123
+ const next = { ...preferences };
124
+ if (event.target.checked)
125
+ delete next.poolModels;
126
+ else
127
+ next.poolModels = account.models.map(model => model.id);
128
+ edit(account.key, next);
129
+ } }), " ", t('accountsModelsAutomatic')] }), _jsxs("div", { style: actions, children: [_jsx("button", { type: "button", onClick: () => edit(account.key, { ...preferences, poolModels: models.map(model => model.id) }), children: t('modelsSelectAll') }), _jsx("button", { type: "button", onClick: () => edit(account.key, { ...preferences, poolModels: [] }), children: t('modelsSelectNone') })] }), _jsxs("div", { style: { ...stack, gap: 8, maxHeight: 240, overflowY: 'auto' }, children: [models.map(model => _jsxs("label", { style: { overflowWrap: 'anywhere' }, children: [_jsx("input", { type: "checkbox", checked: selected.has(model.id), onChange: event => {
130
+ const next = new Set(selected);
131
+ if (event.target.checked)
132
+ next.add(model.id);
133
+ else
134
+ next.delete(model.id);
135
+ edit(account.key, { ...preferences, poolModels: [...next] });
136
+ } }), " ", model.name, model.unavailable && ` (${t('modelsUnavailable')})`] }, model.id)), models.length === 0 && _jsx("p", { className: "dsh-subscription-hint", children: t('accountsNoModels') })] })] })] })] }, account.key);
137
+ })] }), (provider !== 'cursor-subscription' || (catalog?.accounts.length ?? 0) > 0) &&
138
+ _jsx(ProviderModelEditor, { ref: editor, provider: provider, rpc: rpc, t: t, disabled: saving, onDirtyChange: setModelsDirty }), _jsxs("footer", { style: { ...stack, gap: 8, borderTop: border, padding: '14px 0 8px',
139
+ position: 'sticky', bottom: 0, background: 'var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1))' }, children: [saveError && _jsx("p", { role: "alert", children: saveError }), _jsxs("div", { style: { ...actions, justifyContent: 'flex-end' }, children: [_jsx("button", { type: "button", disabled: saving, onClick: onClose, children: t('cancel') }), _jsx("button", { type: "button", className: "dsh-subscription-primary", disabled: loading || saving || (!Object.keys(changes).length && !modelsDirty), onClick: () => { void save(); }, children: saving ? t('modelDefaultsSaving') : t('modelsSave') })] })] })] })] });
147
140
  }
@@ -1,11 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
3
3
  import { callSubscriptionsAuth } from './subscriptions-rpc.js';
4
- const border = '1px solid var(--dsw-alias-border-l2, #ddd)';
5
- const control = {
6
- font: 'inherit', color: 'inherit', background: 'transparent', border,
7
- borderRadius: 8, padding: '6px 10px', minWidth: 0,
8
- };
4
+ const border = '0.5px solid var(--dsw-alias-border-l2, #ddd)';
9
5
  const actions = { display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' };
10
6
  /**
11
7
  * Embedded section of the account manager dialog. It owns the model draft
@@ -83,25 +79,25 @@ export const ProviderModelEditor = forwardRef(function ProviderModelEditor({ pro
83
79
  const missing = (draft.visibleModels ?? []).filter(id => !known.has(id)).map(id => ({ id, name: id }));
84
80
  const models = [...allModels, ...missing].filter(model => `${model.name} ${model.id}`.toLowerCase().includes(query.trim().toLowerCase()));
85
81
  const selected = new Set(draft.visibleModels ?? allModels.map(model => model.id));
86
- return _jsxs("div", { style: { borderTop: border, marginTop: 12, paddingTop: 12 }, children: [_jsx("h3", { style: { margin: 0, fontSize: 15 }, children: t('modelsEdit') }), _jsxs("div", { style: { display: 'grid', gap: 12, marginTop: 12 }, children: [_jsx("p", { style: { margin: 0 }, children: t(provider === 'cursor-subscription' ? 'cursorManageHint' : 'modelsHint') }), error && _jsx("p", { role: "alert", style: { margin: 0, color: 'var(--dsw-alias-state-error-primary, #b42318)' }, children: error }), _jsxs("div", { style: actions, children: [_jsx("button", { type: "button", style: control, disabled: busy || disabled || dirty, onClick: () => { void load(true); }, children: t('usageRefresh') }), busy && _jsx("span", { role: "status", children: t('modelDefaultsLoading') })] }), catalog && _jsxs("fieldset", { disabled: busy || disabled, style: { border: 0, padding: 0, margin: 0, minWidth: 0, display: 'grid', gap: 12 }, children: [_jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: draft.visibleModels === undefined, onChange: event => {
82
+ return _jsxs("div", { style: { borderTop: border, marginTop: 12, paddingTop: 12 }, children: [_jsx("h3", { style: { margin: 0 }, children: t('modelsEdit') }), _jsxs("div", { style: { display: 'grid', gap: 12, marginTop: 12 }, children: [_jsx("p", { className: "dsh-subscription-hint", children: t(provider === 'cursor-subscription' ? 'cursorManageHint' : 'modelsHint') }), error && _jsx("p", { role: "alert", children: error }), _jsxs("div", { style: actions, children: [_jsx("button", { type: "button", className: "dsh-subscription-quiet", disabled: busy || disabled || dirty, onClick: () => { void load(true); }, children: t('usageRefresh') }), busy && _jsx("span", { role: "status", children: t('modelDefaultsLoading') })] }), catalog && _jsxs("fieldset", { disabled: busy || disabled, style: { border: 0, padding: 0, margin: 0, minWidth: 0, display: 'grid', gap: 12 }, children: [_jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: draft.visibleModels === undefined, onChange: event => {
87
83
  const next = { ...draft };
88
84
  if (event.target.checked)
89
85
  delete next.visibleModels;
90
86
  else
91
87
  next.visibleModels = allModels.map(model => model.id);
92
88
  edit(next);
93
- } }), " ", t('modelsAutomatic')] }), _jsxs("div", { style: actions, children: [_jsx("input", { style: { ...control, flex: '1 1 180px' }, value: query, onChange: event => setQuery(event.target.value), placeholder: t('modelDefaultsFilterPlaceholder'), "aria-label": t('modelDefaultsFilterPlaceholder') }), _jsx("button", { type: "button", style: control, onClick: () => edit({ ...draft, visibleModels: allModels.map(model => model.id) }), children: t('modelsSelectAll') }), _jsx("button", { type: "button", style: control, onClick: () => edit({ ...draft, visibleModels: [] }), children: t('modelsSelectNone') })] }), _jsxs("div", { style: { maxHeight: 360, overflowY: 'auto', display: 'grid', gap: 8 }, children: [models.map(model => _jsxs("div", { style: { borderBottom: border, padding: '8px 2px', display: 'grid', gap: 8 }, children: [_jsxs("label", { style: { overflowWrap: 'anywhere' }, children: [_jsx("input", { type: "checkbox", checked: selected.has(model.id), onChange: event => {
89
+ } }), " ", t('modelsAutomatic')] }), _jsxs("div", { style: actions, children: [_jsx("input", { style: { flex: '1 1 180px' }, value: query, onChange: event => setQuery(event.target.value), placeholder: t('modelDefaultsFilterPlaceholder'), "aria-label": t('modelDefaultsFilterPlaceholder') }), _jsx("button", { type: "button", onClick: () => edit({ ...draft, visibleModels: allModels.map(model => model.id) }), children: t('modelsSelectAll') }), _jsx("button", { type: "button", onClick: () => edit({ ...draft, visibleModels: [] }), children: t('modelsSelectNone') })] }), _jsxs("div", { style: { maxHeight: 360, overflowY: 'auto', display: 'grid', gap: 8 }, children: [models.map(model => _jsxs("div", { style: { borderBottom: border, padding: '8px 2px', display: 'grid', gap: 8 }, children: [_jsxs("label", { style: { overflowWrap: 'anywhere' }, children: [_jsx("input", { type: "checkbox", checked: selected.has(model.id), onChange: event => {
94
90
  const next = new Set(selected);
95
91
  if (event.target.checked)
96
92
  next.add(model.id);
97
93
  else
98
94
  next.delete(model.id);
99
95
  edit({ ...draft, visibleModels: [...next] });
100
- } }), " ", model.name, !known.has(model.id) && ` (${t('modelsUnavailable')})`] }), _jsxs("div", { style: actions, children: [(model.efforts?.length ?? 0) > 0 && _jsxs("label", { style: actions, children: [t('modelDefaultsTitle'), _jsxs("select", { style: control, "aria-label": `${model.name} ${t('modelDefaultsTitle')}`, value: efforts[model.id] ?? '', onChange: event => {
96
+ } }), " ", model.name, !known.has(model.id) && ` (${t('modelsUnavailable')})`] }), _jsxs("div", { style: actions, children: [(model.efforts?.length ?? 0) > 0 && _jsxs("label", { style: actions, children: [t('modelDefaultsTitle'), _jsxs("select", { "aria-label": `${model.name} ${t('modelDefaultsTitle')}`, value: efforts[model.id] ?? '', onChange: event => {
101
97
  setEfforts({ ...efforts, [model.id]: event.target.value });
102
98
  setDirty(true);
103
99
  }, children: [_jsx("option", { value: "", children: t('modelDefaultsFollowProvider') }), model.configured && !model.efforts?.some(effort => effort.id === model.configured) &&
104
- _jsxs("option", { value: model.configured, disabled: true, children: [model.configured, " (", t('modelsUnavailable'), ")"] }), model.efforts?.map(effort => _jsx("option", { value: effort.id, children: effort.name }, effort.id))] })] }), provider === 'codex' && model.maxContextWindow !== undefined && _jsxs("div", { style: actions, children: [_jsxs("label", { style: actions, children: [t('modelsContext'), _jsx("input", { style: { ...control, width: 140 }, inputMode: "numeric", value: contexts[model.id] ?? '', "aria-label": `${model.name} ${t('modelsContext')}`, placeholder: String(model.defaultContextWindow), onChange: event => { setContexts({ ...contexts, [model.id]: event.target.value }); setDirty(true); } })] }), _jsx("small", { children: t('modelsContextBounds', { default: model.defaultContextWindow, max: model.maxContextWindow }) })] })] })] }, model.id)), models.length === 0 && _jsx("span", { children: t('modelDefaultsFilterEmpty', { query }) })] }), provider === 'codex' && _jsx("small", { children: t('modelsContextHint') }), catalog.tools.length > 0 && _jsxs("div", { style: { display: 'grid', gap: 8 }, children: [_jsx("strong", { children: t('modelsTools') }), _jsx("small", { children: t('modelsToolsHint') }), catalog.tools.map(tool => _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: draft.tools?.[tool] !== false, onChange: event => edit({
100
+ _jsxs("option", { value: model.configured, disabled: true, children: [model.configured, " (", t('modelsUnavailable'), ")"] }), model.efforts?.map(effort => _jsx("option", { value: effort.id, children: effort.name }, effort.id))] })] }), provider === 'codex' && model.maxContextWindow !== undefined && _jsxs("div", { style: actions, children: [_jsxs("label", { style: actions, children: [t('modelsContext'), _jsx("input", { style: { width: 140 }, inputMode: "numeric", value: contexts[model.id] ?? '', "aria-label": `${model.name} ${t('modelsContext')}`, placeholder: String(model.defaultContextWindow), onChange: event => { setContexts({ ...contexts, [model.id]: event.target.value }); setDirty(true); } })] }), _jsx("small", { children: t('modelsContextBounds', { default: model.defaultContextWindow, max: model.maxContextWindow }) })] })] })] }, model.id)), models.length === 0 && _jsx("span", { children: t('modelDefaultsFilterEmpty', { query }) })] }), provider === 'codex' && _jsx("small", { children: t('modelsContextHint') }), catalog.tools.length > 0 && _jsxs("div", { style: { display: 'grid', gap: 8 }, children: [_jsx("strong", { children: t('modelsTools') }), _jsx("small", { children: t('modelsToolsHint') }), catalog.tools.map(tool => _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: draft.tools?.[tool] !== false, onChange: event => edit({
105
101
  ...draft, tools: { ...draft.tools, [tool]: event.target.checked },
106
102
  }) }), " ", t(tool === 'image_generate' ? 'modelsImage' : tool === 'video_generate' ? 'modelsVideo' : tool === 'web_search' ? 'modelsWebSearch' : 'modelsSearch')] }, tool))] })] })] })] });
107
103
  });
@@ -14,6 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
14
  */
15
15
  import { useEffect, useRef, useState } from 'react';
16
16
  import { callSubscriptionsAuth } from './SubscriptionsSection.js';
17
+ import { subscriptionChromeCss } from './provider-settings-styles.js';
17
18
  import { en } from './locales.js';
18
19
  /**
19
20
  * The `loadSpeed` half of the inject face: the plugin's own speed state plus
@@ -136,12 +137,12 @@ export function SpeedSelect({ loadSpeed, setSpeed, t }) {
136
137
  const tierName = (tier) => translate(tier === 'fast' ? 'speedFast' : 'speedStandard');
137
138
  const tierDescription = (tier) => translate(tier === 'fast' ? 'speedFastDescription' : 'speedStandardDescription');
138
139
  const triggerLabel = `${translate('speed')} · ${tierName(state.tier)}`;
139
- return (_jsxs("div", { ref: rootRef, style: styles.root, onKeyDown: (event) => {
140
+ return (_jsxs("div", { ref: rootRef, className: "dsh-subscription-speed", style: styles.root, onKeyDown: (event) => {
140
141
  if (event.key === 'Escape' && open) {
141
142
  event.preventDefault();
142
143
  setOpen(false);
143
144
  }
144
- }, children: [open && (_jsx("div", { style: styles.menu, role: "menu", "aria-label": translate('speed'), children: TIERS.map(tier => (_jsxs("button", { type: "button", role: "menuitemradio", "aria-checked": tier === state.tier, style: styles.item, disabled: busy, onClick: () => { choose(tier); }, children: [_jsx("span", { style: styles.itemCheck, children: tier === state.tier ? '✓' : '' }), _jsxs("span", { style: styles.itemText, children: [_jsx("span", { style: styles.itemName, children: tierName(tier) }), _jsx("span", { style: styles.itemDescription, children: tierDescription(tier) })] })] }, tier))) })), _jsx("button", { type: "button", style: styles.trigger, "aria-haspopup": "menu", "aria-expanded": open, title: triggerLabel, disabled: busy, onClick: () => {
145
+ }, children: [_jsx("style", { children: subscriptionChromeCss }), open && (_jsx("div", { style: styles.menu, role: "menu", "aria-label": translate('speed'), children: TIERS.map(tier => (_jsxs("button", { type: "button", role: "menuitemradio", "aria-checked": tier === state.tier, style: styles.item, disabled: busy, onClick: () => { choose(tier); }, children: [_jsx("span", { style: styles.itemCheck, children: tier === state.tier ? '✓' : '' }), _jsxs("span", { style: styles.itemText, children: [_jsx("span", { style: styles.itemName, children: tierName(tier) }), _jsx("span", { style: styles.itemDescription, children: tierDescription(tier) })] })] }, tier))) })), _jsx("button", { type: "button", style: styles.trigger, "aria-haspopup": "menu", "aria-expanded": open, title: triggerLabel, disabled: busy, onClick: () => {
145
146
  if (open)
146
147
  setOpen(false);
147
148
  else
@@ -151,20 +152,20 @@ export function SpeedSelect({ loadSpeed, setSpeed, t }) {
151
152
  const styles = {
152
153
  root: { position: 'relative', display: 'inline-flex' },
153
154
  trigger: {
154
- border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
155
- background: 'transparent', color: 'var(--dsw-alias-label-secondary)',
155
+ border: '0.5px solid var(--dsw-alias-border-l3)', borderRadius: 'var(--dsw-radius-sm, 8px)',
156
+ background: 'var(--dsh-subscription-speed-hover, transparent)', color: 'var(--dsw-alias-label-secondary)',
156
157
  font: 'inherit', fontSize: 12, lineHeight: '18px',
157
158
  padding: '2px 8px', cursor: 'pointer', whiteSpace: 'nowrap',
158
159
  },
159
160
  menu: {
160
161
  position: 'absolute', bottom: '100%', right: 0, marginBottom: 4,
161
162
  minWidth: 180, padding: 4, zIndex: 20,
162
- background: 'var(--dsw-alias-bg-layer-1)', border: '1px solid var(--dsw-alias-border-l2)',
163
- borderRadius: 8, display: 'flex', flexDirection: 'column', gap: 2,
163
+ background: 'var(--dsw-specific-menu, var(--dsw-alias-bg-layer-2))', border: 0,
164
+ boxShadow: 'var(--dsw-elevation-prominent)', borderRadius: 'var(--dsw-radius-lg, 16px)', display: 'flex', flexDirection: 'column', gap: 2,
164
165
  },
165
166
  item: {
166
167
  display: 'flex', alignItems: 'flex-start', gap: 6, width: '100%',
167
- border: 'none', borderRadius: 6, background: 'transparent',
168
+ border: 'none', borderRadius: 'var(--dsw-radius-sm, 8px)', background: 'var(--dsh-subscription-speed-hover, transparent)',
168
169
  padding: '6px 8px', cursor: 'pointer', font: 'inherit', textAlign: 'left',
169
170
  },
170
171
  itemCheck: {
@@ -24,6 +24,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
24
24
  * 'settings.subscriptions' namespace.
25
25
  */
26
26
  import { useCallback, useEffect, useRef, useState } from 'react';
27
+ import { subscriptionChromeCss } from './provider-settings-styles.js';
27
28
  import { createPortal } from 'react-dom';
28
29
  import * as primitives from '@deepseek-ai/dsh-client-ui-primitives';
29
30
  import { useAnchoredPosition, useDismissOnOutsidePointer } from '@deepseek-ai/dsh-client-ui-primitives';
@@ -513,7 +514,7 @@ export function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
513
514
  if (next)
514
515
  void refresh();
515
516
  };
516
- const pill = (_jsxs("span", { ref: rootRef, style: styles.anchor, children: [_jsxs("button", { type: "button", style: { ...styles.pill, ...(hover || open ? styles.pillActive : {}) }, "aria-haspopup": "dialog", "aria-expanded": open, "aria-label": `${title} · ${label}`, title: title, onMouseEnter: () => { setHover(true); }, onMouseLeave: () => { setHover(false); }, onClick: toggle, children: [_jsx(UsageBadgeIcon, {}), _jsx("span", { style: styles.label, children: label })] }), open && createPortal(_jsxs("div", { ref: panelRef, role: "dialog", "aria-label": title, style: { ...styles.panel, ...(pos ?? MEASURE_STYLE) }, children: [_jsx("div", { style: styles.title, children: _jsxs("span", { style: styles.titleLabel, children: [_jsx(UsageBadgeIcon, {}), title] }) }), _jsx("div", { style: styles.titleRule, "aria-hidden": true }), expanded.map((d, index) => (_jsxs("section", { style: index === 0 ? undefined : styles.section, children: [_jsxs("div", { style: styles.providerRow, children: [_jsxs("span", { style: styles.providerName, children: [d.name, d.provider === current && _jsx("span", { style: styles.currentTag, children: translate('usageBadgeCurrent') })] }), d.accounts.length === 1 && _jsx(AccountMeta, { account: d.accounts[0], translate: translate })] }), d.accounts.map((account, accountIndex) => (_jsxs("div", { style: accountIndex === 0 ? undefined : styles.accountBlock, children: [d.accounts.length > 1 && (_jsx("div", { style: styles.accountRow, children: _jsx(AccountMeta, { account: account, translate: translate }) })), _jsx(AccountWindows, { windows: account.windows, observedAt: account.observedAt, stale: account.stale, model: d.provider === dialogProvider ? dialogModel : undefined, provider: d.provider, translate: translate }, `${d.provider}:${dialogModel ?? ''}`)] }, account.key)))] }, d.provider)))] }), document.body)] }));
517
+ const pill = (_jsxs("span", { ref: rootRef, style: styles.anchor, children: [_jsx("style", { children: subscriptionChromeCss }), _jsxs("button", { type: "button", className: "dsh-subscription-usage-pill", style: { ...styles.pill, ...(hover || open ? styles.pillActive : {}) }, "aria-haspopup": "dialog", "aria-expanded": open, "aria-label": `${title} · ${label}`, title: title, onMouseEnter: () => { setHover(true); }, onMouseLeave: () => { setHover(false); }, onClick: toggle, children: [_jsx(UsageBadgeIcon, {}), _jsx("span", { style: styles.label, children: label })] }), open && createPortal(_jsxs("div", { ref: panelRef, role: "dialog", "aria-label": title, className: "dsh-subscription-usage-panel", style: { ...styles.panel, ...(pos ?? MEASURE_STYLE) }, children: [_jsx("div", { style: styles.title, children: _jsxs("span", { style: styles.titleLabel, children: [_jsx(UsageBadgeIcon, {}), title] }) }), _jsx("div", { style: styles.titleRule, "aria-hidden": true }), expanded.map((d, index) => (_jsxs("section", { style: index === 0 ? undefined : styles.section, children: [_jsxs("div", { style: styles.providerRow, children: [_jsxs("span", { style: styles.providerName, children: [d.name, d.provider === current && _jsx("span", { style: styles.currentTag, children: translate('usageBadgeCurrent') })] }), d.accounts.length === 1 && _jsx(AccountMeta, { account: d.accounts[0], translate: translate })] }), d.accounts.map((account, accountIndex) => (_jsxs("div", { style: accountIndex === 0 ? undefined : styles.accountBlock, children: [d.accounts.length > 1 && (_jsx("div", { style: styles.accountRow, children: _jsx(AccountMeta, { account: account, translate: translate }) })), _jsx(AccountWindows, { windows: account.windows, observedAt: account.observedAt, stale: account.stale, model: d.provider === dialogProvider ? dialogModel : undefined, provider: d.provider, translate: translate }, `${d.provider}:${dialogModel ?? ''}`)] }, account.key)))] }, d.provider)))] }), document.body)] }));
517
518
  return (_jsxs(_Fragment, { children: [seat, statsRow !== null && statsRow.isConnected ? createPortal(pill, statsRow) : pill] }));
518
519
  }
519
520
  /**
@@ -564,9 +565,9 @@ const styles = {
564
565
  pill: {
565
566
  boxSizing: 'border-box', maxWidth: '100%',
566
567
  color: 'var(--dsw-alias-label-tertiary)',
567
- font: 'inherit', fontSize: 'var(--dsh-content-font-size-secondary, 13px)',
568
- fontVariantNumeric: 'tabular-nums', lineHeight: '20px', whiteSpace: 'nowrap',
569
- background: 'transparent', border: 'none', borderRadius: 24,
568
+ font: 'inherit', fontSize: 'calc(var(--dsh-content-font-size-secondary, 13px) - 1px)',
569
+ fontVariantNumeric: 'tabular-nums', lineHeight: 'calc(20px + var(--dsh-content-font-delta-secondary, 0px))', whiteSpace: 'nowrap',
570
+ background: 'transparent', border: 'none', borderRadius: 999,
570
571
  alignItems: 'center', gap: 6, padding: '1px 8px', display: 'inline-flex', cursor: 'pointer',
571
572
  },
572
573
  pillActive: {
@@ -586,7 +587,7 @@ const styles = {
586
587
  maxHeight: 'min(560px, 100dvh - 24px)', overflowY: 'auto', overscrollBehavior: 'contain',
587
588
  boxShadow: 'var(--dsw-elevation-prominent)',
588
589
  color: 'var(--dsw-alias-label-secondary)', cursor: 'default',
589
- border: 0, borderRadius: 12, padding: 16, fontSize: 12, lineHeight: '18px',
590
+ border: 0, borderRadius: 'var(--dsw-radius-lg, 16px)', padding: 16, fontSize: 12, lineHeight: '18px',
590
591
  },
591
592
  title: {
592
593
  color: 'var(--dsw-alias-label-primary)', display: 'flex',
@@ -41,6 +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;
51
+ }
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;
44
66
  }
45
67
  /** One model's default-effort picker state as answered by `modelDefaults`. */
46
68
  export interface ModelDefaultView {