dsh-plugin-subscription-oauth 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # dsh-plugin-subscription-oauth
2
2
 
3
3
  [![Listed on dsh-plugin.org](https://dsh-plugin.org/badges/listed.svg)](https://dsh-plugin.org/plugins/EricFL1998/dsh-subscription-oauth)
4
+ [![npm](https://img.shields.io/npm/v/dsh-plugin-subscription-oauth.svg)](https://www.npmjs.com/package/dsh-plugin-subscription-oauth)
4
5
  [![Licence: MIT](https://img.shields.io/badge/licence-MIT-blue.svg)](LICENSE)
5
6
 
6
7
  **Use the AI subscriptions you already pay for as LLM providers in DeepSeek
@@ -9,12 +10,14 @@ Subscriptions** (订阅) and that account's models join the session model picker
9
10
  with its plan quota shown next to the connection.
10
11
 
11
12
  ```sh
12
- dsh plugin --profile web add github:EricFL1998/dsh-subscription-oauth
13
+ dsh plugin --profile web add dsh-plugin-subscription-oauth
13
14
  ```
14
15
 
15
- The same package is published to npm as `dsh-plugin-subscription-oauth`, so
16
- `dsh plugin --profile web add dsh-plugin-subscription-oauth` installs it too; the
17
- git form above works without depending on a registry publish.
16
+ The same package installs straight from the repository, which needs no registry:
17
+
18
+ ```sh
19
+ dsh plugin --profile web add github:EricFL1998/dsh-subscription-oauth
20
+ ```
18
21
 
19
22
  | | |
20
23
  | --- | --- |
@@ -114,13 +117,22 @@ grip is focused) to re-order the list; that order is what the picker follows for
114
117
  that route.
115
118
 
116
119
  **`auto` is a provider of its own** (route `auto`), not a model inside each one: it
117
- stands in for the enabled models of *every* route in one list, each row badged with
118
- the route that owns it. It leads both the Settings section and the model picker,
119
- because it is the one entry that reaches every route. It has no account, so its card
120
- offers no connect or manage — only the list. A switch takes a model out of Auto
121
- **without** hiding it from the picker, drag re-orders what Auto tries first, and the
122
- arrangement is stored separately (`auto.order` / `auto.off`). Switch every model off
123
- and Auto is simply not offered in the picker.
120
+ stands in for the enabled models of *every* provider the harness serves in one list,
121
+ each row badged with the provider that owns it. That includes the routes this plugin
122
+ registers **and** the ones it does not — the built-in DeepSeek providers and any
123
+ provider you added yourself — because it enumerates the harness roster
124
+ (`ctx.llm.listProviders()` / `listModels()`) and dispatches through it rather than
125
+ looking only at its own routes. It leads both the Settings section and the model
126
+ picker, because it is the one entry that reaches every route. It has no account, so
127
+ its card offers no connect or manage — only the list. A switch takes a model out of
128
+ Auto **without** hiding it from the picker, drag re-orders what Auto tries first, and
129
+ the arrangement is stored separately (`auto.order` / `auto.off`). Switch every model
130
+ off and Auto is simply not offered in the picker.
131
+
132
+ A provider with no signed-in account contributes no rows: `listModels` answers with
133
+ nothing until a route has a session, so signing out removes that route's models from
134
+ the picker and from Auto at the same time, and its card hides the catalog instead of
135
+ offering models that cannot be called.
124
136
 
125
137
  Auto falls through to the next model when the provider refuses the preferred one
126
138
  (renamed or gone, rate-limited, out of allowance, or a transport failure). A
@@ -139,6 +151,13 @@ back to the per-model `quotaInfo` of `fetchAvailableModels` (worst-spent model p
139
151
  family, classified by id, display name and tier, so an `Opus` row counts as
140
152
  Claude). A provider that states nothing says so instead of inventing a figure.
141
153
 
154
+ A reset is stated by how far off it is, on every route: **minutes** within the hour
155
+ (`40 分钟后重置`), **hours** within the day (`4 小时后重置`), and the date beyond that
156
+ (`重置时间 2026/10/17 13:54`) — the count is what a reader acts on when deciding whether
157
+ there is room left, and a date is what they need when it is a week away. Because the
158
+ page re-reads the figures on a slow interval, the count follows along while it is
159
+ open.
160
+
142
161
  **Manage** opens a dialog with per-account token refresh, the credential-file
143
162
  path, and the resolved endpoints.
144
163
 
@@ -149,17 +168,18 @@ provider (Grok) instead shows a sign-in link and waits for the browser redirect.
149
168
  ## Install
150
169
 
151
170
  ```sh
152
- dsh plugin --profile web add github:EricFL1998/dsh-subscription-oauth
171
+ dsh plugin --profile web add dsh-plugin-subscription-oauth
153
172
  ```
154
173
 
155
174
  `web` is the profile that serves the Settings page; use `desktop` (or any other
156
175
  profile name) if that is the one you run.
157
176
 
158
- The CLI forwards its arguments to pnpm, so a registry version, a git URL and a local
177
+ The CLI forwards its arguments to pnpm, so a pinned version, a git URL and a local
159
178
  path are all spelled the same way:
160
179
 
161
180
  ```sh
162
- dsh plugin --profile web add dsh-plugin-subscription-oauth@0.1.0 # once published to npm
181
+ dsh plugin --profile web add dsh-plugin-subscription-oauth@0.2.1
182
+ dsh plugin --profile web add github:EricFL1998/dsh-subscription-oauth
163
183
  dsh plugin --profile web add /path/to/dsh-plugin-subscription-oauth
164
184
  ```
165
185
 
@@ -58,6 +58,34 @@ function formatTime(at) {
58
58
  return String(at);
59
59
  }
60
60
  }
61
+ /** How far ahead a reset is still stated as hours rather than a date. */
62
+ const RESET_RELATIVE_MS = 24 * 60 * 60 * 1000;
63
+ /**
64
+ * One allowance reset, in the form a reader acts on.
65
+ *
66
+ * How far off it is decides how it is said: minutes inside the hour, hours inside
67
+ * the day, and the date beyond that — "137 小时后" would be arithmetic rather than
68
+ * information, and a window that comes back in forty minutes is better described in
69
+ * minutes than as "1 小时后". Every route's windows go through here, so all of them
70
+ * read the same way.
71
+ *
72
+ * @param at - epoch milliseconds the window resets.
73
+ * @param t - translator, so the wording follows the page language.
74
+ * @returns the reset text.
75
+ */
76
+ function formatReset(at, t) {
77
+ const remaining = at - Date.now();
78
+ if (remaining <= 0)
79
+ return t('usageResetDue');
80
+ if (remaining >= RESET_RELATIVE_MS)
81
+ return t('usageReset', { time: formatTime(at) });
82
+ // Rounded, and never below one: "0 分钟后重置" would read as a bug rather than as
83
+ // "any moment now".
84
+ if (remaining < 60 * 60 * 1000) {
85
+ return t('usageResetsInMinutes', { minutes: Math.max(1, Math.round(remaining / 60_000)) });
86
+ }
87
+ return t('usageResetsInHours', { hours: Math.max(1, Math.round(remaining / 3_600_000)) });
88
+ }
61
89
  /** Compact token count for a context window. */
62
90
  function formatTokens(tokens) {
63
91
  if (tokens >= 1_000_000)
@@ -249,7 +277,7 @@ function windowAmount(window) {
249
277
  /** One account's plan and metered allowances. */
250
278
  function UsageBlock(props) {
251
279
  const { usage, error, t, busyAction, onRefresh } = props;
252
- return (_jsxs("div", { style: styles.usage, children: [_jsxs("div", { style: styles.usageHeader, children: [_jsx("span", { style: styles.catalogTitle, children: t('usageTitle') }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, disabled: busyAction, onClick: onRefresh, children: busyAction ? t('refreshing') : t('usageRefresh') })] }), error !== undefined && _jsx("p", { className: "dsh-subscription-error", children: t('usageError', { message: error }) }), usage.length === 0 && error === undefined && _jsx("p", { style: styles.statusLine, children: t('usageNone') }), usage.map((entry) => (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageLine, children: [_jsxs("span", { style: styles.modelMeta, children: [entry.plan === undefined ? entry.account : t('usagePlan', { plan: entry.plan }), entry.renewsAt === undefined ? '' : ` · ${t('usageRenews', { time: formatTime(entry.renewsAt) })}`] }), _jsx("span", { style: styles.modelMeta, children: t('usageFetched', { time: formatTime(entry.fetchedAt) }) })] }), entry.windows.map((window) => (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageLine, children: [_jsxs("span", { style: styles.modelName, children: [windowLabel(t, window), ' · ', windowAmount(window)] }), _jsx("span", { style: styles.modelMeta, children: window.resetsAt === undefined ? '' : t('usageReset', { time: formatTime(window.resetsAt) }) })] }), window.usedPercent !== undefined && (_jsx("div", { className: "dsh-subscription-meter", children: _jsx("span", { style: { width: `${String(Math.min(100, Math.max(0, window.usedPercent)))}%` }, "data-exhausted": window.usedPercent >= 90 ? 'true' : 'false' }) }))] }, `${entry.account}-${window.label}`)))] }, entry.account)))] }));
280
+ return (_jsxs("div", { style: styles.usage, children: [_jsxs("div", { style: styles.usageHeader, children: [_jsx("span", { style: styles.catalogTitle, children: t('usageTitle') }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, disabled: busyAction, onClick: onRefresh, children: busyAction ? t('refreshing') : t('usageRefresh') })] }), error !== undefined && _jsx("p", { className: "dsh-subscription-error", children: t('usageError', { message: error }) }), usage.length === 0 && error === undefined && _jsx("p", { style: styles.statusLine, children: t('usageNone') }), usage.map((entry) => (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageLine, children: [_jsxs("span", { style: styles.modelMeta, children: [entry.plan === undefined ? entry.account : t('usagePlan', { plan: entry.plan }), entry.renewsAt === undefined ? '' : ` · ${t('usageRenews', { time: formatTime(entry.renewsAt) })}`] }), _jsx("span", { style: styles.modelMeta, children: t('usageFetched', { time: formatTime(entry.fetchedAt) }) })] }), entry.windows.map((window) => (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageLine, children: [_jsxs("span", { style: styles.modelName, children: [windowLabel(t, window), ' · ', windowAmount(window)] }), _jsx("span", { style: styles.modelMeta, children: window.resetsAt === undefined ? '' : formatReset(window.resetsAt, t) })] }), window.usedPercent !== undefined && (_jsx("div", { className: "dsh-subscription-meter", children: _jsx("span", { style: { width: `${String(Math.min(100, Math.max(0, window.usedPercent)))}%` }, "data-exhausted": window.usedPercent >= 90 ? 'true' : 'false' }) }))] }, `${entry.account}-${window.label}`)))] }, entry.account)))] }));
253
281
  }
254
282
  /** The pending-login block: a device code, or a link to the sign-in page. */
255
283
  function PendingLogin(props) {
@@ -452,7 +480,7 @@ function ProviderCard(props) {
452
480
  ? t('statusConnected', { count: accounts.length })
453
481
  : t('statusSignedOut') }), connectable && !status.loggedIn && !busy && _jsx("p", { style: styles.statusLine, children: t('statusSignIn') }), status.lastError !== undefined && (_jsx("p", { className: "dsh-subscription-error", children: t('lastError', { message: status.lastError }) })), message?.tone === 'error' && _jsx("p", { className: "dsh-subscription-error", children: message.text }), message?.tone === 'success' && _jsx("p", { className: "dsh-subscription-success", children: message.text }), accounts.map((account) => (_jsx("div", { style: styles.accountRow, children: _jsxs("div", { style: styles.accountHeader, children: [_jsx("button", { type: "button", style: styles.starButton, title: account.isDefault ? t('defaultBadge') : t('makeDefault'), "aria-label": account.isDefault ? t('defaultBadge') : t('makeDefault'), disabled: busyAction || account.isDefault, onClick: () => { props.onSetDefault(account.account); }, children: account.isDefault ? '★' : '☆' }), _jsx("span", { style: styles.accountName, children: account.label }), account.isDefault && _jsx("span", { style: styles.badge, children: t('defaultBadge') }), account.needsLogin === true && (_jsx("span", { style: styles.badge, title: t('accountNeedsLoginHint'), children: t('accountNeedsLogin') })), _jsx("span", { style: styles.statusLine, children: account.expired
454
482
  ? t('accountExpired')
455
- : t('accountExpires', { time: formatTime(account.expiresAt) }) }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, disabled: busyAction, onClick: () => { props.onLogout(account.account); }, children: t('logout') })] }) }, account.account))), _jsxs("div", { style: styles.catalog, children: [_jsxs("div", { style: styles.catalogHeader, children: [_jsxs("button", { type: "button", className: "dsh-subscription-disclosure", style: styles.disclosure, "aria-expanded": modelsOpen, "aria-controls": `dsh-subscription-models-${status.provider}`, onClick: toggleModelsOpen, children: [_jsx("span", { style: styles.chevron, "aria-hidden": "true", children: modelsOpen ? '▾' : '▸' }), _jsx("span", { style: styles.catalogTitle, children: t('modelsTitle') })] }), _jsxs("span", { style: styles.catalogMeta, children: [t('modelsCount', { count: models.length }), ' · ', liveCatalog ? t('modelsLive') : t('modelsBuiltIn'), hiddenCount === 0 ? '' : ` ${t('modelsHiddenCount', { count: hiddenCount })}`] }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, disabled: busyAction, onClick: props.onRefreshModels, children: busyAction ? t('refreshing') : t('refresh') }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", disabled: busyAction || !connectable, title: t('modelsAddHint'), onClick: () => { setAdding((open) => !open); }, children: t('modelsAdd') })] }), adding && (_jsxs("form", { style: styles.addRow, onSubmit: (event) => {
483
+ : t('accountExpires', { time: formatTime(account.expiresAt) }) }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, disabled: busyAction, onClick: () => { props.onLogout(account.account); }, children: t('logout') })] }) }, account.account))), status.loggedIn && (_jsxs("div", { style: styles.catalog, children: [_jsxs("div", { style: styles.catalogHeader, children: [_jsxs("button", { type: "button", className: "dsh-subscription-disclosure", style: styles.disclosure, "aria-expanded": modelsOpen, "aria-controls": `dsh-subscription-models-${status.provider}`, onClick: toggleModelsOpen, children: [_jsx("span", { style: styles.chevron, "aria-hidden": "true", children: modelsOpen ? '▾' : '▸' }), _jsx("span", { style: styles.catalogTitle, children: t('modelsTitle') })] }), _jsxs("span", { style: styles.catalogMeta, children: [t('modelsCount', { count: models.length }), ' · ', liveCatalog ? t('modelsLive') : t('modelsBuiltIn'), hiddenCount === 0 ? '' : ` ${t('modelsHiddenCount', { count: hiddenCount })}`] }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, disabled: busyAction, onClick: props.onRefreshModels, children: busyAction ? t('refreshing') : t('refresh') }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", disabled: busyAction || !connectable, title: t('modelsAddHint'), onClick: () => { setAdding((open) => !open); }, children: t('modelsAdd') })] }), adding && (_jsxs("form", { style: styles.addRow, onSubmit: (event) => {
456
484
  event.preventDefault();
457
485
  const id = draft.trim();
458
486
  if (id.length === 0)
@@ -475,7 +503,7 @@ function ProviderCard(props) {
475
503
  ? { ...styles.modelRowGrow, cursor: 'pointer' }
476
504
  : { ...styles.modelRowGrow, cursor: 'pointer', color: 'var(--dsw-alias-label-dimmed)' }, title: model.visible ? t('modelsHidden') : t('modelsShow'), children: [_jsx("input", { type: "checkbox", className: "dsh-subscription-switch", role: "switch", checked: model.visible, disabled: busyAction, "aria-label": `${t('modelsShow')} · ${model.name}`, onChange: (event) => { props.onSetModelVisible(model.id, event.target.checked); } }), _jsxs("span", { style: styles.modelBody, children: [_jsxs("span", { style: styles.modelName, children: [model.name, model.source === undefined ? null : _jsx("span", { style: styles.badge, children: model.source })] }), _jsxs("span", { style: styles.modelMeta, children: [model.id, ' · ', t('modelsContext', { tokens: formatTokens(model.contextWindow) }), model.defaultEffort === undefined ? '' : ` · ${model.defaultEffort}`, model.inputModalities === undefined || model.inputModalities.length === 0
477
505
  ? ''
478
- : ` · ${model.inputModalities.join('/')}`] })] })] }), model.userAdded === true && (_jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.removeButton, disabled: busyAction, title: t('modelsRemove'), "aria-label": `${t('modelsRemove')} · ${model.name}`, onClick: () => { props.onRemoveModel(model.id); }, children: "\u00D7" }))] }, model.id))), models.length === 0 && (_jsx("p", { style: styles.statusLine, children: status.provider === 'auto' ? t('modelsAutoChainEmpty') : t('modelsEmpty') }))] }))] }), _jsx(UsageBlock, { usage: status.usage ?? [], ...status.usageError === undefined ? {} : { error: status.usageError }, t: t, busyAction: busyAction, onRefresh: props.onRefreshUsage }), connectable && (_jsxs("div", { style: styles.actions, children: [!busy && (_jsx("button", { type: "button", disabled: busyAction, onClick: props.onLogin, children: accounts.length === 0 ? t('connect') : t('addAccount') })), !busy && (_jsx("button", { type: "button", disabled: busyAction, onClick: () => { setManageOpen(true); }, children: t('manage') })), busy && (_jsx("button", { type: "button", disabled: busyAction, onClick: props.onCancel, children: t('cancel') }))] })), connectable && accounts.length > 0 && _jsx("p", { style: styles.statusLine, children: t('hint') }), busy && status.pending !== undefined && (_jsx(PendingLogin, { prompt: status.pending, t: t, copied: copied, onCopy: (code) => { void copyCode(code); } }))] }), _jsxs("dialog", { ref: dialog, className: "dsh-subscription-manager", "aria-labelledby": `dsh-subscription-manage-${status.provider}`,
506
+ : ` · ${model.inputModalities.join('/')}`] })] })] }), model.userAdded === true && (_jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.removeButton, disabled: busyAction, title: t('modelsRemove'), "aria-label": `${t('modelsRemove')} · ${model.name}`, onClick: () => { props.onRemoveModel(model.id); }, children: "\u00D7" }))] }, model.id))), models.length === 0 && (_jsx("p", { style: styles.statusLine, children: status.provider === 'auto' ? t('modelsAutoChainEmpty') : t('modelsEmpty') }))] }))] })), _jsx(UsageBlock, { usage: status.usage ?? [], ...status.usageError === undefined ? {} : { error: status.usageError }, t: t, busyAction: busyAction, onRefresh: props.onRefreshUsage }), connectable && (_jsxs("div", { style: styles.actions, children: [!busy && (_jsx("button", { type: "button", disabled: busyAction, onClick: props.onLogin, children: accounts.length === 0 ? t('connect') : t('addAccount') })), !busy && (_jsx("button", { type: "button", disabled: busyAction, onClick: () => { setManageOpen(true); }, children: t('manage') })), busy && (_jsx("button", { type: "button", disabled: busyAction, onClick: props.onCancel, children: t('cancel') }))] })), connectable && accounts.length > 0 && _jsx("p", { style: styles.statusLine, children: t('hint') }), busy && status.pending !== undefined && (_jsx(PendingLogin, { prompt: status.pending, t: t, copied: copied, onCopy: (code) => { void copyCode(code); } }))] }), _jsxs("dialog", { ref: dialog, className: "dsh-subscription-manager", "aria-labelledby": `dsh-subscription-manage-${status.provider}`,
479
507
  // A closed `<dialog>` is hidden by the user-agent stylesheet, and an
480
508
  // inline `display` would override that rule — so the open state has to
481
509
  // be restated here rather than left to `showModal()` alone.
@@ -64,6 +64,9 @@ export declare const en: {
64
64
  readonly usagePlan: "Plan: {plan}";
65
65
  readonly usageRenews: "renews {time}";
66
66
  readonly usageReset: "resets {time}";
67
+ readonly usageResetsInMinutes: "resets in {minutes}m";
68
+ readonly usageResetsInHours: "resets in {hours}h";
69
+ readonly usageResetDue: "resets shortly";
67
70
  readonly usageRefresh: "Refresh usage";
68
71
  readonly usageFetched: "read {time}";
69
72
  readonly usageNone: "This provider reports no plan quota.";
@@ -155,6 +158,9 @@ export declare const zh: {
155
158
  usagePlan: string;
156
159
  usageRenews: string;
157
160
  usageReset: string;
161
+ usageResetsInMinutes: string;
162
+ usageResetsInHours: string;
163
+ usageResetDue: string;
158
164
  usageRefresh: string;
159
165
  usageFetched: string;
160
166
  usageNone: string;
@@ -64,6 +64,9 @@ export const en = {
64
64
  usagePlan: 'Plan: {plan}',
65
65
  usageRenews: 'renews {time}',
66
66
  usageReset: 'resets {time}',
67
+ usageResetsInMinutes: 'resets in {minutes}m',
68
+ usageResetsInHours: 'resets in {hours}h',
69
+ usageResetDue: 'resets shortly',
67
70
  usageRefresh: 'Refresh usage',
68
71
  usageFetched: 'read {time}',
69
72
  usageNone: 'This provider reports no plan quota.',
@@ -153,6 +156,9 @@ export const zh = {
153
156
  usagePlan: '套餐:{plan}',
154
157
  usageRenews: '续费时间 {time}',
155
158
  usageReset: '重置时间 {time}',
159
+ usageResetsInMinutes: '{minutes} 分钟后重置',
160
+ usageResetsInHours: '{hours} 小时后重置',
161
+ usageResetDue: '即将重置',
156
162
  usageRefresh: '刷新额度',
157
163
  usageFetched: '读取于 {time}',
158
164
  usageNone: '该服务未提供套餐额度信息。',
package/lib/client.js CHANGED
@@ -95,6 +95,9 @@ const en = {
95
95
  usagePlan: "Plan: {plan}",
96
96
  usageRenews: "renews {time}",
97
97
  usageReset: "resets {time}",
98
+ usageResetsInMinutes: "resets in {minutes}m",
99
+ usageResetsInHours: "resets in {hours}h",
100
+ usageResetDue: "resets shortly",
98
101
  usageRefresh: "Refresh usage",
99
102
  usageFetched: "read {time}",
100
103
  usageNone: "This provider reports no plan quota.",
@@ -184,6 +187,9 @@ const zh = {
184
187
  usagePlan: "套餐:{plan}",
185
188
  usageRenews: "续费时间 {time}",
186
189
  usageReset: "重置时间 {time}",
190
+ usageResetsInMinutes: "{minutes} 分钟后重置",
191
+ usageResetsInHours: "{hours} 小时后重置",
192
+ usageResetDue: "即将重置",
187
193
  usageRefresh: "刷新额度",
188
194
  usageFetched: "读取于 {time}",
189
195
  usageNone: "该服务未提供套餐额度信息。",
@@ -565,6 +571,28 @@ function formatTime(at) {
565
571
  return String(at);
566
572
  }
567
573
  }
574
+ /** How far ahead a reset is still stated as hours rather than a date. */
575
+ const RESET_RELATIVE_MS = 1440 * 60 * 1e3;
576
+ /**
577
+ * One allowance reset, in the form a reader acts on.
578
+ *
579
+ * How far off it is decides how it is said: minutes inside the hour, hours inside
580
+ * the day, and the date beyond that — "137 小时后" would be arithmetic rather than
581
+ * information, and a window that comes back in forty minutes is better described in
582
+ * minutes than as "1 小时后". Every route's windows go through here, so all of them
583
+ * read the same way.
584
+ *
585
+ * @param at - epoch milliseconds the window resets.
586
+ * @param t - translator, so the wording follows the page language.
587
+ * @returns the reset text.
588
+ */
589
+ function formatReset(at, t) {
590
+ const remaining = at - Date.now();
591
+ if (remaining <= 0) return t("usageResetDue");
592
+ if (remaining >= RESET_RELATIVE_MS) return t("usageReset", { time: formatTime(at) });
593
+ if (remaining < 3600 * 1e3) return t("usageResetsInMinutes", { minutes: Math.max(1, Math.round(remaining / 6e4)) });
594
+ return t("usageResetsInHours", { hours: Math.max(1, Math.round(remaining / 36e5)) });
595
+ }
568
596
  /** Compact token count for a context window. */
569
597
  function formatTokens(tokens) {
570
598
  if (tokens >= 1e6) return `${String(Math.round(tokens / 1e5) / 10)}M`;
@@ -1023,7 +1051,7 @@ function UsageBlock(props) {
1023
1051
  ]
1024
1052
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1025
1053
  style: styles.modelMeta,
1026
- children: window$1.resetsAt === void 0 ? "" : t("usageReset", { time: formatTime(window$1.resetsAt) })
1054
+ children: window$1.resetsAt === void 0 ? "" : formatReset(window$1.resetsAt, t)
1027
1055
  })]
1028
1056
  }), window$1.usedPercent !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1029
1057
  className: "dsh-subscription-meter",
@@ -1346,7 +1374,7 @@ function ProviderCard(props) {
1346
1374
  ]
1347
1375
  })
1348
1376
  }, account.account)),
1349
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1377
+ status.loggedIn && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1350
1378
  style: styles.catalog,
1351
1379
  children: [
1352
1380
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["code: string","messageOf","result: Awaited<ReturnType<ConnectionHandle['rpc']['call']>>","error: unknown","template: string","styles: Record<string, CSSProperties>","window","accounts: readonly AccountStatus[]","caught: unknown"],"sources":["../src/client/locales.ts","../src/client/styles.ts","../src/protocol.ts","../src/client/subscriptions-rpc.ts","../src/client/SubscriptionsSection.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * Copy dictionaries for the Subscriptions settings page.\n *\n * English is the key-set source of truth; the Chinese dictionary is typed\n * against it, so a key added to one and forgotten in the other is a compile\n * error rather than a silent fallback.\n */\n\n/** English strings. */\nexport const en = {\n nav: 'Subscriptions',\n intro: 'Sign in with a subscription account — Kimi Code, Grok (X Premium) or Google Antigravity. DeepSeek Harness keeps the tokens on this machine and streams every request through the provider\\'s own API.',\n loading: 'Checking subscriptions…',\n unavailable: 'The subscriptions page needs the web connection service, which this composition does not provide.',\n statusConnected: 'Connected · {count} account(s)',\n statusSignedOut: 'Not signed in',\n statusBusy: 'Waiting for browser approval',\n statusSignIn: 'Sign in to add this provider\\'s models to the model picker.',\n statusAuto: 'Follows {count} enabled model(s) from every provider',\n connect: 'Connect',\r\n edit: 'Edit',\r\n collapse: 'Collapse',\n addAccount: 'Add account',\n manage: 'Manage',\n cancel: 'Cancel',\n close: 'Close',\n logout: 'Sign out',\n makeDefault: 'Use by default',\n defaultBadge: 'Default',\n accountNeedsLogin: 'Sign in again',\n accountNeedsLoginHint: 'The provider refused this account\\'s grant, so its token cannot be renewed',\n accountExpired: 'token expired',\n accountExpires: 'expires {time}',\n deviceTitle: 'Approve this device',\n devicePrompt: 'Open the verification page and confirm the code below. This page updates by itself once the provider approves.',\n deviceOpen: 'Open verification page',\n deviceCopy: 'Copy code',\n deviceCopied: 'Copied',\n deviceExpires: 'The code expires at {time}.',\n openLogin: 'Open sign-in page',\n openLoginHint: 'Finish signing in in the browser tab that opened. This page updates by itself once the provider redirects back.',\n modelsTitle: 'Models',\n modelsCount: '{count} available',\n modelsLive: 'live',\n modelsBuiltIn: 'built-in',\n modelsShow: 'Show in picker',\n modelsHiddenCount: '· {count} hidden',\n modelsHidden: 'Hidden from the model picker',\n modelsEmpty: 'No models reported yet.',\n modelsAdd: 'Add model',\n modelsAddHint: 'Publish a model id this build does not know yet',\n modelsAddPlaceholder: 'model id, e.g. k3-next',\n modelsAddConfirm: 'Add',\n modelsRemove: 'Remove this hand-added model',\n modelsContext: '{tokens} context',\n modelsAuto: 'Auto',\n modelsAutoHint: 'Pinned first: moves to the next model when one is unavailable',\n modelsAutoOff: 'Off — the picker offers the models directly instead',\n modelsAutoChain: 'Auto tries these, in this order — drag to re-order',\n modelsDrag: 'Drag to re-order (or press ↑ / ↓ here)',\n modelsAutoChainOff: 'Take out of Auto',\n modelsAutoChainEmpty: 'No model is enabled, so Auto has nothing to try.',\n modelsPinned: 'pinned',\n usageTitle: 'Plan and usage',\n usagePlan: 'Plan: {plan}',\n usageRenews: 'renews {time}',\n usageReset: 'resets {time}',\n usageRefresh: 'Refresh usage',\n usageFetched: 'read {time}',\n usageNone: 'This provider reports no plan quota.',\n usageError: 'Could not read usage: {message}',\n usage_5h: '5-hour window',\n usage_weekly: 'Weekly window',\n usage_credits: 'Included credit',\n usage_gemini: 'Gemini allowance (rolling)',\n usage_gemini_weekly: 'Gemini allowance (weekly)',\n usage_claude: 'Claude allowance (rolling)',\n usage_claude_weekly: 'Claude allowance (weekly)',\n usage_other: 'Allowance',\n refresh: 'Refresh',\n refreshing: 'Refreshing…',\n modelsRefreshed: 'Model list refreshed.',\n refreshToken: 'Refresh token',\n tokenRefreshed: 'Token refreshed; valid until {time}.',\n hint: 'Authorization follows the account the browser is signed in with. To add a different account, switch accounts there first, or use a private window.',\n manageTitle: 'Manage {provider}',\n manageAccounts: 'Accounts',\n reload: 'Reload credentials',\n reloadHint: 'Use this after signing in from another DeepSeek Harness window.',\n detail_oauthHost: 'Authorization server',\n detail_apiBase: 'API base',\n detail_credentialFile: 'Credential file',\n detail_project: 'Cloud project',\n lastError: 'Last login attempt failed: {message}',\n loginFailed: 'Could not start the sign-in: {message}',\n actionFailed: 'Action failed: {message}',\n} as const\n\n/** The Subscriptions namespace key union. */\nexport type SubscriptionsKey = keyof typeof en\n\n/** Chinese strings. */\nexport const zh = {\n nav: '订阅',\n intro: '使用订阅账号登录 —— Kimi Code、Grok (X Premium) 或 Google Antigravity。DeepSeek Harness 将令牌保存在本机,并通过各服务自己的 API 发起请求。',\n loading: '正在检查订阅…',\n unavailable: '订阅页面需要 Web 连接服务,当前组合未提供该服务。',\n statusConnected: '已连接 {count} 个账号',\n statusSignedOut: '未登录',\n statusBusy: '等待浏览器确认',\n statusSignIn: '登录后即可在模型选择器中使用该服务的模型。',\n statusAuto: '依次使用来自各服务的 {count} 个已启用模型',\n connect: '连接',\r\n edit: '编辑',\r\n collapse: '收起',\n addAccount: '添加账号',\n manage: '管理',\n cancel: '取消',\n close: '关闭',\n logout: '退出登录',\n makeDefault: '设为默认',\n defaultBadge: '默认',\n accountNeedsLogin: '需重新登录',\n accountNeedsLoginHint: '服务方已拒绝该账号的授权,令牌无法自动续期',\n accountExpired: '令牌已过期',\n accountExpires: '过期时间 {time}',\n deviceTitle: '确认此设备',\n devicePrompt: '打开验证页面并确认下方代码,服务确认后本页面会自动更新。',\n deviceOpen: '打开验证页面',\n deviceCopy: '复制代码',\n deviceCopied: '已复制',\n deviceExpires: '代码将于 {time} 过期。',\n openLogin: '打开登录页面',\n openLoginHint: '请在打开的浏览器标签页中完成登录;服务回调后本页面会自动更新。',\n modelsTitle: '模型',\n modelsCount: '共 {count} 个',\n modelsLive: '实时',\n modelsBuiltIn: '内置',\n modelsShow: '在选择器中显示',\n modelsHiddenCount: '· 已隐藏 {count} 个',\n modelsHidden: '已从模型选择器隐藏',\n modelsEmpty: '暂未获取到模型。',\n modelsAdd: '添加模型',\n modelsAddHint: '手动添加一个本版本还不认识的模型 ID',\n modelsAddPlaceholder: '模型 ID,例如 k3-next',\n modelsAddConfirm: '添加',\n modelsRemove: '移除这个手动添加的模型',\n modelsContext: '{tokens} 上下文',\n modelsAuto: '自动',\n modelsAutoHint: '固定在首位:某个模型不可用时自动改用下一个',\n modelsAutoOff: '已关闭 —— 选择器将直接列出各个模型',\n modelsAutoChain: '自动会按这个顺序依次尝试(拖动可调整)',\n modelsDrag: '拖动调整顺序(也可在此按 ↑ / ↓)',\n modelsAutoChainOff: '从自动中移除',\n modelsAutoChainEmpty: '没有启用任何模型,自动没有可尝试的模型。',\n modelsPinned: '固定',\n usageTitle: '套餐与额度',\n usagePlan: '套餐:{plan}',\n usageRenews: '续费时间 {time}',\n usageReset: '重置时间 {time}',\n usageRefresh: '刷新额度',\n usageFetched: '读取于 {time}',\n usageNone: '该服务未提供套餐额度信息。',\n usageError: '额度读取失败:{message}',\n usage_5h: '5 小时窗口',\n usage_weekly: '每周窗口',\n usage_credits: '包含额度',\n usage_gemini: 'Gemini 模型额度(滚动)',\n usage_gemini_weekly: 'Gemini 模型额度(每周)',\n usage_claude: 'Claude 模型额度(滚动)',\n usage_claude_weekly: 'Claude 模型额度(每周)',\n usage_other: '额度',\n refresh: '刷新',\n refreshing: '正在刷新…',\n modelsRefreshed: '模型列表已刷新。',\n refreshToken: '刷新令牌',\n tokenRefreshed: '令牌已刷新,有效至 {time}。',\n hint: '授权以浏览器当前登录的账号为准;要添加不同账号,请先在浏览器里切换账号,或使用无痕窗口。',\n manageTitle: '管理 {provider}',\n manageAccounts: '账号',\n reload: '重新读取凭据',\n reloadHint: '在另一个 DeepSeek Harness 窗口中登录后,请使用此操作。',\n detail_oauthHost: '授权服务器',\n detail_apiBase: 'API 地址',\n detail_credentialFile: '凭据文件',\n detail_project: '云项目',\n lastError: '上次登录失败:{message}',\n loginFailed: '无法开始登录:{message}',\n actionFailed: '操作失败:{message}',\n} satisfies Record<SubscriptionsKey, string>\n","/**\n * Scoped stylesheet for the Subscriptions page and its manage dialog.\n *\n * The host loads plugin markup without its own control classes, and a bare\n * `<button>` therefore renders with the browser's default raised chrome — which\n * reads as a stray highlight in a dark theme. Rather than invent a look, this\n * follows the control vocabulary of the sibling `dsh-plugin-subscriptions`\n * plugin (same author-facing design as the shell's settings forms): a 6 px\n * radius small control, a transparent fill, a hairline border from the theme's\n * alias tokens, and the theme's hover/active/focus tokens for state.\n *\n * The tokens all carry fallbacks, so the page still renders correctly on a host\n * build that does not define them.\n *\n * @module dsh-plugin-subscription-oauth/client/styles\n */\n\n/**\n * Everything is scoped to this plugin's own roots, so no other surface in the\n * shell can be affected by a rule here.\n */\nconst scope = ':is(.dsh-subscriptions-settings, .dsh-subscription-manager)'\n\n/** The page's and dialog's stylesheet. */\nexport const subscriptionsCss = `\n.dsh-subscriptions-settings,\n.dsh-subscription-manager {\n font-family: inherit;\n font-size: 13px;\n line-height: 1.5;\n /* Native controls (checkbox, dialog) follow the host theme, not the OS. */\n color-scheme: light;\n}\nbody[data-ds-dark-theme] ${scope} {\n color-scheme: dark;\n}\n.dsh-subscription-manager {\n box-sizing: border-box;\n padding: 0;\n border: 0;\n border-radius: var(--dsw-radius-panel, 16px);\n background: var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1));\n color: var(--dsw-alias-label-primary);\n box-shadow: var(--dsw-elevation-prominent, 0 20px 70px #0004);\n}\n.dsh-subscription-manager::backdrop {\n background: var(--dsw-alias-bg-mask-1, #0006);\n backdrop-filter: var(--dsw-mask-blur, none);\n}\n${scope} h2 {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n}\n${scope} strong,\n${scope} .dsh-subscription-label {\n font-size: 13px;\n line-height: 1.5;\n font-weight: 500;\n}\n${scope} small,\n${scope} .dsh-subscription-hint,\n${scope} [role=\"status\"] {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n${scope} .dsh-subscription-error {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-error-primary, #b42318);\n}\n${scope} .dsh-subscription-success {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-success-primary, #067647);\n}\n${scope} label:has(> input[type=\"checkbox\"]) {\n cursor: pointer;\n}\n${scope} input[type=\"checkbox\"] {\n accent-color: var(--dsw-alias-brand-primary);\n}\n/*\n * A model's on/off control is a switch, not a checkbox: it takes effect\n * immediately and its state is what matters, not a tick. Drawn from the theme\n * tokens with appearance: none, so it follows the host palette in both themes\n * instead of the platform's checkbox chrome.\n */\n${scope} input[type=\"checkbox\"].dsh-subscription-switch {\n appearance: none;\n -webkit-appearance: none;\n position: relative;\n box-sizing: border-box;\n flex-shrink: 0;\n width: 34px;\n height: 20px;\n margin: 0;\n border: 0.5px solid var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-2));\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease;\n}\n${scope} input[type=\"checkbox\"].dsh-subscription-switch::after {\n content: '';\n position: absolute;\n top: 2px;\n left: 2px;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: var(--dsw-alias-label-primary-foreground, #fff);\n box-shadow: 0 1px 2px #0003;\n transition: transform 120ms ease;\n}\n${scope} input[type=\"checkbox\"].dsh-subscription-switch:checked {\n border-color: transparent;\n background: var(--dsw-alias-state-success-primary, var(--dsw-alias-brand-primary, #067647));\n}\n${scope} input[type=\"checkbox\"].dsh-subscription-switch:checked::after {\n transform: translateX(14px);\n}\n${scope} input[type=\"checkbox\"].dsh-subscription-switch:disabled {\n opacity: 0.4;\n cursor: default;\n}\n${scope} button {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n min-height: 28px;\n padding: 0 10px;\n border: 0.5px solid var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));\n border-radius: var(--dsw-radius-sm, 6px);\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n}\n${scope} button:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-3));\n}\n${scope} button:active:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-active, var(--dsw-alias-bg-layer-3));\n}\n${scope} button:disabled {\n opacity: 0.4;\n cursor: default;\n}\n/* A control that is present but should not read as a bordered button. */\n${scope} button.dsh-subscription-quiet {\n min-height: 22px;\n padding: 0 4px;\n border-color: transparent;\n color: var(--dsw-alias-label-secondary, var(--dsw-alias-label-primary));\n}\n/* The free-text field for a hand-added model id. */\n${scope} input.dsh-subscription-input {\n box-sizing: border-box;\n flex: 1 1 auto;\n min-width: 0;\n min-height: 28px;\n padding: 0 10px;\n border: 0.5px solid var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));\n border-radius: var(--dsw-radius-sm, 6px);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n line-height: 18px;\n}\n${scope} input.dsh-subscription-input::placeholder {\n color: var(--dsw-alias-label-tertiary);\n}\n${scope} input.dsh-subscription-input:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));\n outline-offset: 2px;\n}\n/* A drag handle: no border or fill, so it reads as a grip rather than a button. */\n${scope} button.dsh-subscription-handle {\n min-height: 18px;\n height: 18px;\n padding: 0 2px;\n border-color: transparent;\n border-radius: var(--dsw-radius-sm, 6px);\n background: transparent;\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n font-size: 12px;\n line-height: 18px;\n cursor: grab;\n}\n${scope} button.dsh-subscription-handle:hover:not(:disabled) {\n background: transparent;\n color: var(--dsw-alias-label-primary);\n}\n${scope} button.dsh-subscription-handle:active:not(:disabled) { cursor: grabbing; }\n/* Controls inside the manage dialog are one step larger, like a form footer. */\n${scope} .dsh-subscription-dialog-actions button {\n min-height: 34px;\n padding: 0 14px;\n border-radius: var(--dsw-radius-md, 8px);\n font-size: 13px;\n line-height: 1.5;\n}\n${scope} button.dsh-subscription-primary {\n border-color: transparent;\n background: var(--dsw-alias-button-primary-fill, var(--dsw-alias-label-primary));\n color: var(--dsw-alias-label-primary-foreground, var(--dsw-alias-bg-layer-1));\n font-weight: 500;\n}\n${scope} button.dsh-subscription-primary:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover, var(--dsw-alias-label-secondary));\n}\n${scope} button:focus-visible,\n${scope} input[type=\"checkbox\"]:focus-visible,\n${scope} .dsh-subscription-disclosure:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));\n outline-offset: 2px;\n}\n/* The catalog heading is a summary-style disclosure, not a bordered button. */\n${scope} .dsh-subscription-disclosure {\n border-color: transparent;\n border-radius: var(--dsw-radius-sm, 6px);\n background: transparent;\n padding: 0 4px;\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary, var(--dsw-alias-label-primary));\n}\n${scope} .dsh-subscription-disclosure:hover:not(:disabled) {\n background: transparent;\n color: var(--dsw-alias-label-primary);\n}\n/* A usage meter: the track, with the fill sized by the caller. */\n${scope} .dsh-subscription-meter {\n height: 6px;\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-2));\n overflow: hidden;\n}\n${scope} .dsh-subscription-meter > span {\n display: block;\n height: 100%;\n background: var(--dsw-alias-state-success-primary, #067647);\n}\n${scope} .dsh-subscription-meter > span[data-exhausted=\"true\"] {\n background: var(--dsw-alias-state-error-primary, #b42318);\n}\n`\n","/**\n * Wire contract shared by the plugin's node half (the RPC routes) and its\n * browser half (the Settings page).\n *\n * Only this module crosses the boundary: the client bundle imports the types\n * and the channel name, and every call travels as one JSON envelope over a\n * `POST /api/<prefix><endpoint>` route. One status document describes every\n * provider route the plugin serves, so the page renders one card per entry.\n *\n * @module dsh-plugin-subscription-oauth/protocol\n */\n\n/** Prefix of every RPC method this plugin serves. */\nexport const RPC_PREFIX = 'subscription-oauth.'\n\n/** Endpoints the Settings page may call. */\nexport const RPC_ENDPOINTS = [\n 'status',\n 'login',\n 'cancel',\n 'logout',\n 'setDefault',\n 'refresh',\n 'discover',\n 'setModelVisible',\n 'setModelOrder',\n 'addModel',\n 'removeModel',\n 'usage',\n 'reload',\n] as const\n\n/** One endpoint name. */\nexport type RpcEndpoint = typeof RPC_ENDPOINTS[number]\n\n/** Client → host envelope. */\nexport interface RpcRequest {\n type: 'client-request'\n rpcId: string\n method: string\n payload: unknown\n}\n\n/** Host → client envelope, carrying either the value or the failure. */\nexport interface RpcResponse {\n type: 'server-response'\n rpcId: string\n result: RpcResult\n}\n\n/** Discriminated result inside {@link RpcResponse}. */\nexport type RpcResult =\n | { ok: true, value: unknown }\n | { ok: false, error: RpcError }\n\n/** Failure detail, with the provider-neutral code when one applies. */\nexport interface RpcError {\n code: string\n message: string\n /**\n * Carrier-owned structured detail. The Connection transport models a failure\n * as `{code, message, details}`, so an empty object travels here rather than\n * an absent field the client would have to repair.\n */\n details: Record<string, unknown>\n}\n\n/**\n * One provider-owned connection fact the manage dialog shows.\n *\n * The key is a stable identifier (`oauthHost`, `apiBase`, …) that the browser\n * half localizes; the host never sends user-facing prose.\n */\nexport interface DetailRow {\n key: string\n value: string\n}\n\n/** How the browser should present one pending login. */\nexport type LoginMode =\n /** Open the URL in a new tab; the redirect completes the login. */\n | 'open-url'\n /** Show the code (and a link) while the host polls the token endpoint. */\n | 'device-code'\n\n/** What the Settings page has to show to finish a login. */\nexport interface LoginPrompt {\n /** URL the user opens (or that was opened for them). */\n readonly url: string\n /** Code the user confirms in the browser; device flows only. */\n readonly userCode?: string\n /** Epoch milliseconds after which the attempt lapses. */\n readonly expiresAt: number\n readonly mode: LoginMode\n}\n\n/** One signed-in account as the page displays it. */\nexport interface AccountStatus {\n /** Account key the store is keyed by. */\n account: string\n /** Display label (email, user id, or the key's digest). */\n label: string\n /** Epoch milliseconds when the access token lapses. */\n expiresAt: number\n /** Whether the access token is already past its expiry. */\n expired: boolean\n /** Whether this account serves requests by default. */\n isDefault: boolean\n /**\n * Whether the provider refused this account's grant outright.\n *\n * A refresh answered with `invalid_grant` means the authorization is gone — the\n * stored token can never be renewed — so the page asks for a new sign-in rather\n * than reporting the account as connected.\n */\n needsLogin?: boolean\n}\n\n/** One selectable model, as the page lists it. */\nexport interface ModelSummary {\n id: string\n name: string\n description?: string\n contextWindow: number\n inputModalities?: readonly string[]\n defaultEffort?: string\n /** Whether the session model picker offers this model. */\n visible: boolean\n /**\n * Route that owns this model, when the listing spans routes.\n *\n * The \u0007uto provider mixes models from every route, so its rows name the\n * owning route; a single-route catalog leaves it out.\n */\n source?: string\n /** Whether the user added this id by hand, so it can be removed again. */\n userAdded?: boolean\n /**\n * Whether this entry is fixed at the head of the list.\n *\n * The synthetic `auto` entry follows the user's own order, so it cannot be\n * hidden or moved; the page renders it without those controls.\n */\n pinned?: boolean\n}\n\n/** One metered allowance of a subscription plan. */\nexport interface UsageWindow {\n /** What the window meters, e.g. `weekly`, `5h`, `credits`. */\n label: string\n /** How much of the allowance is spent, when the provider states it. */\n usedPercent?: number\n /** Amount already spent, when the provider states an absolute figure. */\n used?: number\n /** Total allowance, when the provider states one. */\n limit?: number\n /** Epoch milliseconds when the window resets, when the provider states it. */\n resetsAt?: number\n}\n\n/** One account's plan and metered allowances. */\nexport interface ProviderUsage {\n /** Account the figures belong to. */\n account: string\n /** Plan name or tier, when the provider states one. */\n plan?: string\n /** Whether the plan renews on a date the provider states. */\n renewsAt?: number\n /** Metered allowances, most relevant first. */\n windows: readonly UsageWindow[]\n /** Epoch milliseconds these figures were read. */\n fetchedAt: number\n /**\n * Whether these figures are the route's substitute rather than its own accounting.\n *\n * A substitute is shown when the primary source cannot answer, and it is never\n * reused as if it were a reading: the next request asks again.\n */\n substitute?: boolean\n}\n\n/** Result of reading one route's usage. */\nexport interface UsageResult {\n provider: string\n /** Usage per account the endpoint could answer for. */\n usage: readonly ProviderUsage[]\n /** Why an account's usage is missing, when the read failed. */\n error?: string\n status: SubscriptionStatus\n}\n\n/** The full state of one provider route. */\nexport interface ProviderStatus {\n /** Provider route key, e.g. `kimi`. */\n provider: string\n displayName: string\n /** Where a user manages the underlying subscription. */\n codeUrl?: string\n /** Whether at least one account is stored. */\n loggedIn: boolean\n /**\n * Whether this route signs in at all.\n *\n * \u0007uto has no account of its own — it delegates to routes that do — so the\n * page hides the connect and manage controls for it.\n */\n connectable?: boolean\n /** Whether a login attempt is being polled. */\n busy: boolean\n /** The pending attempt, when `busy`. */\n pending?: LoginPrompt\n accounts: AccountStatus[]\n /** Account key serving requests, when one does. */\n activeAccount?: string\n /** Absolute path of the credential file, for support requests. */\n credentialFile: string\n /** Whether the live model list is consulted. */\n discovery: boolean\n /** Live catalog ids, when discovery has succeeded this session. */\n discovered?: readonly string[]\n models: readonly ModelSummary[]\n /** Model the picker preselects. */\n defaultModel?: string\n /** Last usage figures read for this route, when they were requested. */\n usage?: readonly ProviderUsage[]\n /** Why the last usage read failed, when it did. */\n usageError?: string\n /** Provider-owned connection facts shown in the manage dialog. */\n details: readonly DetailRow[]\n /** Last background failure (a login that could not complete). */\n lastError?: string\n}\n\n/** Every provider route's state, in display order. */\nexport interface SubscriptionStatus {\n providers: ProviderStatus[]\n}\n\n/** Result of starting a login. */\nexport interface LoginStart {\n prompt: LoginPrompt\n status: SubscriptionStatus\n}\n\n/** Result of a token refresh. */\nexport interface RefreshResult {\n account: string\n expiresAt: number\n status: SubscriptionStatus\n}\n\n/** Payload fields naming one provider route and optionally one account. */\nexport interface AccountRequest {\n provider: string\n account?: string\n}\n\n/** Payload of `setDefault`. */\nexport interface SetDefaultRequest {\n provider: string\n account: string\n}\n\n/** Payload of `discover`. */\nexport interface DiscoverRequest {\n provider: string\n /** Ask the endpoint for its live model list even when one is cached. */\n force?: boolean\n}\n\n/** Payload of `setModelVisible`. */\nexport interface SetModelVisibleRequest {\n provider: string\n /** Model id as the catalog reports it. */\n id: string\n /** Whether the session model picker should offer it. */\n visible: boolean\n}\n\n/** Payload of `addModel` and `removeModel`. */\nexport interface ModelIdRequest {\n provider: string\n /** Model id as the endpoint accepts it. */\n id: string\n}\n\n/** Payload of `setModelOrder`. */\nexport interface SetModelOrderRequest {\n provider: string\n /** Every catalog model id, in the order the user arranged them. */\n ids: readonly string[]\n}\n\n/** Payload naming only a provider route. */\nexport interface ProviderRequest {\n provider: string\n}\n\n/** Map an endpoint to its payload and value types. */\nexport interface RpcMap {\n status: { payload: undefined, value: SubscriptionStatus }\n login: { payload: AccountRequest, value: LoginStart }\n cancel: { payload: ProviderRequest, value: SubscriptionStatus }\n logout: { payload: AccountRequest, value: SubscriptionStatus }\n setDefault: { payload: SetDefaultRequest, value: SubscriptionStatus }\n refresh: { payload: AccountRequest, value: RefreshResult }\n discover: { payload: DiscoverRequest, value: SubscriptionStatus }\n setModelVisible: { payload: SetModelVisibleRequest, value: SubscriptionStatus }\n setModelOrder: { payload: SetModelOrderRequest, value: SubscriptionStatus }\n addModel: { payload: ModelIdRequest, value: SubscriptionStatus }\n removeModel: { payload: ModelIdRequest, value: SubscriptionStatus }\n usage: { payload: DiscoverRequest, value: UsageResult }\n reload: { payload: undefined, value: SubscriptionStatus }\n}\n","/**\n * Browser caller for the plugin's own RPC channel.\n *\n * The node half mounts one exact `POST /api/subscription-oauth.<endpoint>` route\n * per endpoint, so every call travels on the shared `/api` channel with a\n * prefixed endpoint name — the same wire the rest of the shell uses.\n *\n * @module dsh-plugin-subscription-oauth/client/subscriptions-rpc\n */\n\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client'\n\nimport { RPC_PREFIX } from '../protocol.js'\nimport type { RpcEndpoint, RpcMap } from '../protocol.js'\n\n/** The shared channel the node half's routes are mounted on. */\nconst CHANNEL = '/api'\n\n/** A failed call: either the transport rejected or the endpoint answered `ok: false`. */\nexport class SubscriptionsRpcError extends Error {\n constructor(message: string, readonly code: string) {\n super(message)\n this.name = 'SubscriptionsRpcError'\n }\n}\n\n/** Human-readable message for any thrown value. */\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/** Call one endpoint, unwrapping the transport result. */\nexport type SubscriptionsCall = <K extends RpcEndpoint>(\n endpoint: K,\n payload: RpcMap[K]['payload'],\n) => Promise<RpcMap[K]['value']>\n\n/**\n * Bind a caller to one connection's RPC transport.\n * @param rpc - the browser connection's RPC caller.\n * @returns a typed caller for this plugin's endpoints.\n */\nexport function createSubscriptionsCall(rpc: ConnectionHandle['rpc']): SubscriptionsCall {\n return async function call(endpoint, payload) {\n let result: Awaited<ReturnType<ConnectionHandle['rpc']['call']>>\n try {\n result = await rpc.call(CHANNEL, `${RPC_PREFIX}${endpoint}`, payload)\n } catch (error: unknown) {\n throw new SubscriptionsRpcError(messageOf(error), 'RPC_TRANSPORT')\n }\n if (!result.ok) throw new SubscriptionsRpcError(result.error.message, result.error.code)\n return result.value as never\n }\n}\n","/**\n * Subscriptions settings page.\n *\n * One card per provider route — Kimi Code, Grok (X Premium), Google Antigravity\n * — describing that route's connection, accounts, and model catalog. Every fact\n * arrives from the node half's `subscription-oauth` RPC endpoints, so this\n * component holds no credential state of its own. The status poll runs only\n * while a login is pending, so an idle page performs no requests.\n *\n * Colors resolve through `--dsw-alias-*` design tokens, so the page follows the\n * active theme; every user-visible string goes through the locale-bound `t`.\n *\n * @module dsh-plugin-subscription-oauth/client/SubscriptionsSection\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport type { CSSProperties } from 'react'\n\nimport { en } from './locales.js'\nimport type { SubscriptionsKey } from './locales.js'\nimport { subscriptionsCss } from './styles.js'\nimport { SubscriptionsRpcError } from './subscriptions-rpc.js'\nimport type { SubscriptionsCall } from './subscriptions-rpc.js'\nimport type {\n AccountStatus,\n DetailRow,\n LoginPrompt,\n ProviderStatus,\n ProviderUsage,\n SubscriptionStatus,\n UsageWindow,\n} from '../protocol.js'\n/** Poll cadence while a login attempt is pending. */\nconst POLL_INTERVAL_MS = 2000\n\n/**\n * How often an open page re-reads every signed-in route's allowance.\n *\n * Slow on purpose: it is fast enough that a figure shown on screen is current, and\n * slow enough that leaving the page open costs one small request a minute per\n * signed-in provider.\n */\nconst USAGE_POLL_INTERVAL_MS = 60_000\n\n/**\n * Disclosures start folded, every time the page is opened.\n *\n * Nothing is remembered between visits on purpose: the section is a list of\n * providers first, and a card left open last time should not decide what this\n * visit looks like. A fold is a gesture within one visit, not a stored setting.\n */\n\n/** Horizontal room the page takes inside the settings panel. */\nconst SECTION_WIDTH = 560\n\n/** What the node half injects into this section. */\nexport interface SubscriptionsSectionInjected {\n /** Typed caller for the plugin's RPC endpoints. */\n call: SubscriptionsCall\n /** Translate a key of this plugin's settings namespace. */\n t: (key: SubscriptionsKey, params?: Record<string, unknown>) => string\n}\n\n/** Component props: the shell supplies `close`, the plugin supplies the rest. */\nexport type SubscriptionsSectionProps = Partial<SubscriptionsSectionInjected> & {\n /** Close the settings panel (owned by the settings shell). */\n close?: () => void\n}\n\n/** One card's transient message. */\ninterface CardMessage {\n tone: 'error' | 'success'\n text: string\n}\n\n/** Minimal translator used when the locale seat is absent (standalone render). */\nfunction fallbackTranslate(key: SubscriptionsKey, params?: Record<string, unknown>): string {\n const template: string = en[key]\n if (params === undefined) return template\n return template.replace(/\\{(\\w+)\\}/g, (match, name: string) =>\n name in params ? String(params[name]) : match)\n}\n\n/** Human-readable message for any thrown value. */\nfunction messageOf(error: unknown): string {\n if (error instanceof SubscriptionsRpcError) return error.message\n return error instanceof Error ? error.message : String(error)\n}\n\n/** Local time for one epoch-millisecond instant. */\nfunction formatTime(at: number): string {\n try {\n return new Date(at).toLocaleString()\n } catch {\n return String(at)\n }\n}\n\n/** Compact token count for a context window. */\nfunction formatTokens(tokens: number): string {\n if (tokens >= 1_000_000) return `${String(Math.round(tokens / 100_000) / 10)}M`\n if (tokens >= 1_000) return `${String(Math.round(tokens / 1_000))}K`\n return String(tokens)\n}\n\n/**\n * Routes worth asking for allowances.\n *\n * A route with no account of its own — `auto` — reports itself as signed in so its\n * card reads as usable, but it holds no allowance to read: asking it every minute\n * spends a request to be told nothing. The routes it stands in for are each read\n * on their own.\n *\n * @param providers - the routes in the current status.\n * @returns the route keys to read usage for.\n */\nfunction usageTargets(providers: readonly ProviderStatus[]): string[] {\n return providers\n .filter((provider) => provider.loggedIn && provider.connectable !== false)\n .map((provider) => provider.provider)\n}\n\nconst styles: Record<string, CSSProperties> = {\n section: {\n display: 'flex', flexDirection: 'column', gap: 12, maxWidth: SECTION_WIDTH,\n color: 'var(--dsw-alias-label-primary)',\n },\n intro: { margin: 0, color: 'var(--dsw-alias-label-tertiary)', fontSize: 14, lineHeight: '22px' },\n card: {\n border: '0.5px solid var(--dsw-alias-settings-card-stroke, var(--dsw-alias-border-l4))',\n borderRadius: 'var(--dsw-radius-xl, 20px)',\n background: 'var(--dsw-alias-settings-card-fill, var(--dsw-alias-bg-layer-2))',\n padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 6,\n },\n cardHeader: { display: 'flex', alignItems: 'center', gap: 8 },\n /** Everything a folded card hides. */\n cardBody: { display: 'flex', flexDirection: 'column', gap: 6 },\n /** The one-line state a folded card shows in place of its body. */\n cardSummary: { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },\n dot: { width: 8, height: 8, borderRadius: '50%', flexShrink: 0 },\n name: { fontWeight: 500, fontSize: 14, lineHeight: '22px', color: 'var(--dsw-alias-label-primary)' },\n providerId: { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-dimmed)' },\n statusLine: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },\n errorLine: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' },\n successLine: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-success-primary)' },\n actions: { display: 'flex', gap: 8, marginTop: 4, alignItems: 'center', flexWrap: 'wrap' },\n accountRow: {\n display: 'flex', flexDirection: 'column', gap: 6,\n border: '0.5px solid var(--dsw-alias-border-l2)', borderRadius: 'var(--dsw-radius-lg, 16px)',\n padding: '8px 10px', marginTop: 4,\n },\n accountHeader: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' },\n starButton: {\n border: 'none', padding: 0, width: 20, background: 'transparent',\n font: 'inherit', fontSize: 14, lineHeight: '20px', cursor: 'pointer',\n color: 'var(--dsw-alias-state-warn-label)',\n },\n accountName: { fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)', userSelect: 'all' },\n badge: {\n fontSize: 11, lineHeight: '16px', padding: '0 6px', borderRadius: 8,\n background: 'var(--dsw-alias-bg-layer-1)', border: '0.5px solid var(--dsw-alias-border-l2)',\n color: 'var(--dsw-alias-label-secondary)',\n },\n pushRight: { marginLeft: 'auto', flexShrink: 0 },\n catalog: {\n display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4,\n borderTop: '0.5px solid var(--dsw-alias-border-l2)', paddingTop: 8,\n },\n catalogHeader: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' },\n catalogTitle: { fontSize: 12, lineHeight: '18px', fontWeight: 500, color: 'var(--dsw-alias-label-secondary)' },\n catalogMeta: { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },\n /**\n * Button surface geometry lives in the scoped stylesheet; only the layout a\n * rule cannot express stays inline.\n */\n pill: {},\n pillSmall: {},\n /** The catalog heading: a summary-style disclosure, not a bordered control. */\n disclosure: {\n display: 'inline-flex', alignItems: 'center', gap: 4, margin: 0,\n },\n chevron: { fontSize: 10, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },\n modelList: { display: 'flex', flexDirection: 'column', gap: 6, marginTop: 2 },\n /** One model line: a visibility toggle and the model's own metadata. */\n modelRowGrow: { display: 'flex', alignItems: 'flex-start', gap: 8, flex: '1 1 auto', minWidth: 0 },\n /** The pinned `auto` line: listed, switchable, and never part of its own chain. */\n autoRow: {\n display: 'flex', alignItems: 'flex-start', gap: 8, cursor: 'pointer',\n fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)',\n },\n /** The models `auto` will walk, indented under it. */\n chain: {\n display: 'flex', flexDirection: 'column', gap: 3,\n margin: '2px 0 4px 26px', paddingLeft: 8,\n borderLeft: '0.5px solid var(--dsw-alias-border-l2)',\n },\n chainTitle: { fontSize: 11, lineHeight: '16px', color: 'var(--dsw-alias-label-tertiary)' },\n /** The add-a-model-id row: the field, then confirm and cancel. */\n addRow: { display: 'flex', alignItems: 'center', gap: 6, marginTop: 2 },\n addInput: {},\n /** The remove control on a hand-added row: a bare glyph beside the row. */\n removeButton: { flexShrink: 0, alignSelf: 'center', padding: '0 6px' },\n /** One draggable chain row: the grip, then the remove-from-auto switch. */\n chainRowWrap: { display: 'flex', alignItems: 'center', gap: 4 },\n chainRow: {\n display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer', flex: '1 1 auto',\n fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-secondary)',\n },\n usage: {\n display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4,\n borderTop: '0.5px solid var(--dsw-alias-border-l2)', paddingTop: 8,\n },\n usageHeader: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' },\n usageRow: { display: 'flex', flexDirection: 'column', gap: 3 },\n usageLine: { display: 'flex', alignItems: 'baseline', gap: 8, justifyContent: 'space-between' },\n usageMeter: {\n height: 6, borderRadius: 999, background: 'var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-2))',\n overflow: 'hidden',\n },\n modelRow: {\n display: 'flex', alignItems: 'flex-start', gap: 8,\n cursor: 'pointer', fontSize: 13, lineHeight: '20px',\n color: 'var(--dsw-alias-label-primary)',\n },\n /** A hidden model stays listed and editable, but reads as inactive. */\n modelRowHidden: {\n display: 'flex', alignItems: 'flex-start', gap: 8,\n cursor: 'pointer', fontSize: 13, lineHeight: '20px',\n color: 'var(--dsw-alias-label-dimmed)',\n },\n modelBody: { display: 'flex', flexDirection: 'column', gap: 1, minWidth: 0 },\n modelName: { fontSize: 13, lineHeight: '20px' },\n modelMeta: { fontSize: 11, lineHeight: '16px', color: 'var(--dsw-alias-label-tertiary)', userSelect: 'all' },\n device: {\n marginTop: 4, display: 'flex', flexDirection: 'column', gap: 6,\n border: '0.5px solid var(--dsw-alias-border-l2)', borderRadius: 'var(--dsw-radius-lg, 16px)',\n padding: '10px 12px', background: 'var(--dsw-alias-bg-layer-1)',\n },\n deviceTitle: { fontWeight: 500, fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)' },\n deviceCode: {\n fontFamily: 'monospace', fontSize: 18, lineHeight: '24px', letterSpacing: 2,\n color: 'var(--dsw-alias-label-primary)', userSelect: 'all',\n },\n details: {\n display: 'flex', flexDirection: 'column', gap: 4,\n fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)',\n },\n detailRow: { display: 'flex', gap: 8, justifyContent: 'space-between', alignItems: 'baseline' },\n detailValue: { userSelect: 'all', overflowWrap: 'anywhere', textAlign: 'right' },\n /** Dialog layout only: its surface comes from `.dsh-subscription-manager`. */\n modal: {\n width: 440, maxWidth: '100%', maxHeight: '85vh', overflowY: 'auto',\n boxSizing: 'border-box', display: 'flex', flexDirection: 'column', gap: 10,\n padding: '16px 18px', margin: 'auto',\n },\n modalHeader: { display: 'flex', alignItems: 'center', gap: 8 },\n modalTitle: { fontWeight: 500, fontSize: 16, lineHeight: '24px', color: 'var(--dsw-alias-label-primary)' },\n modalActions: { display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 2 },\n divider: { borderTop: '0.5px solid var(--dsw-alias-border-l2)' },\n}\n\n/** Status dot color for one provider's connection state. */\nfunction dotColor(status: ProviderStatus): string {\n if (status.busy) return 'var(--dsw-alias-state-warn-label)'\n if (status.loggedIn) return 'var(--dsw-alias-state-success-primary)'\n return 'var(--dsw-alias-label-dimmed)'\n}\n\n/** Localized label for one provider-owned detail row. */\nfunction detailLabel(t: SubscriptionsSectionInjected['t'], row: DetailRow): string {\n const key = `detail_${row.key}` as SubscriptionsKey\n return key in en ? t(key) : row.key\n}\n\n/** Localized label for one metered window the provider states. */\nfunction windowLabel(t: SubscriptionsSectionInjected['t'], window: UsageWindow): string {\n const key = `usage_${window.label}` as SubscriptionsKey\n return key in en ? t(key) : window.label\n}\n\n/** How much of one window is spent, as a display string. */\nfunction windowAmount(window: UsageWindow): string {\n if (window.usedPercent !== undefined) return `${String(Math.round(window.usedPercent))}%`\n if (window.used !== undefined && window.limit !== undefined) return `${String(window.used)}/${String(window.limit)}`\n return '—'\n}\n\n/** One account's plan and metered allowances. */\nfunction UsageBlock(props: {\n usage: readonly ProviderUsage[]\n error?: string\n t: SubscriptionsSectionInjected['t']\n busyAction: boolean\n onRefresh(): void\n}) {\n const { usage, error, t, busyAction, onRefresh } = props\n return (\n <div style={styles.usage}>\n <div style={styles.usageHeader}>\n <span style={styles.catalogTitle}>{t('usageTitle')}</span>\n <button\n type=\"button\"\n className=\"dsh-subscription-quiet\"\n style={styles.pushRight}\n disabled={busyAction}\n onClick={onRefresh}\n >\n {busyAction ? t('refreshing') : t('usageRefresh')}\n </button>\n </div>\n {error !== undefined && <p className=\"dsh-subscription-error\">{t('usageError', { message: error })}</p>}\n {usage.length === 0 && error === undefined && <p style={styles.statusLine}>{t('usageNone')}</p>}\n {usage.map((entry) => (\n <div key={entry.account} style={styles.usageRow}>\n <div style={styles.usageLine}>\n <span style={styles.modelMeta}>\n {entry.plan === undefined ? entry.account : t('usagePlan', { plan: entry.plan })}\n {entry.renewsAt === undefined ? '' : ` · ${t('usageRenews', { time: formatTime(entry.renewsAt) })}`}\n </span>\n <span style={styles.modelMeta}>{t('usageFetched', { time: formatTime(entry.fetchedAt) })}</span>\n </div>\n {entry.windows.map((window) => (\n <div key={`${entry.account}-${window.label}`} style={styles.usageRow}>\n <div style={styles.usageLine}>\n <span style={styles.modelName}>\n {windowLabel(t, window)}\n {' · '}\n {windowAmount(window)}\n </span>\n <span style={styles.modelMeta}>\n {window.resetsAt === undefined ? '' : t('usageReset', { time: formatTime(window.resetsAt) })}\n </span>\n </div>\n {window.usedPercent !== undefined && (\n <div className=\"dsh-subscription-meter\">\n <span\n style={{ width: `${String(Math.min(100, Math.max(0, window.usedPercent)))}%` }}\n data-exhausted={window.usedPercent >= 90 ? 'true' : 'false'}\n />\n </div>\n )}\n </div>\n ))}\n </div>\n ))}\n </div>\n )\n}\n\n/** The pending-login block: a device code, or a link to the sign-in page. */\nfunction PendingLogin(props: {\n prompt: LoginPrompt\n t: SubscriptionsSectionInjected['t']\n copied: boolean\n onCopy(code: string): void\n}) {\n const { prompt, t, copied, onCopy } = props\n return (\n <div style={styles.device}>\n <span style={styles.deviceTitle}>\n {prompt.mode === 'device-code' ? t('deviceTitle') : t('openLogin')}\n </span>\n <span style={styles.statusLine}>\n {prompt.mode === 'device-code' ? t('devicePrompt') : t('openLoginHint')}\n </span>\n {prompt.userCode !== undefined && <span style={styles.deviceCode}>{prompt.userCode}</span>}\n <span style={styles.statusLine}>{t('deviceExpires', { time: formatTime(prompt.expiresAt) })}</span>\n <div style={styles.actions}>\n <button\n type=\"button\"\n \n onClick={() => { window.open(prompt.url, '_blank', 'noopener') }}\n >\n {prompt.mode === 'device-code' ? t('deviceOpen') : t('openLogin')}\n </button>\n {prompt.userCode !== undefined && (\n <button type=\"button\" onClick={() => { onCopy(prompt.userCode as string) }}>\n {copied ? t('deviceCopied') : t('deviceCopy')}\n </button>\n )}\n </div>\n </div>\n )\n}\n\n/** One provider's card. */\nfunction ProviderCard(props: {\n status: ProviderStatus\n t: SubscriptionsSectionInjected['t']\n busyAction: boolean\n message?: CardMessage\n onLogin(): void\n onCancel(): void\n onLogout(account: string): void\n onSetDefault(account: string): void\n onRefreshToken(account: string): void\n onRefreshModels(): void\n onSetModelVisible(id: string, visible: boolean): void\n /** Persist the chain order the user dropped into place. */\n onSetModelOrder(ids: readonly string[]): void\n /** Publish a model id the catalog does not know yet. */\n onAddModel(id: string): void\n /** Forget a hand-added model id. */\n onRemoveModel(id: string): void\n onRefreshUsage(): void\n onReload(): void\n}) {\n const { status, t, busyAction, message } = props\n const [copied, setCopied] = useState(false)\n // Both disclosures open folded, on every visit: the choice is a gesture, not a\n // stored setting. See the note beside the interval constants.\n const [modelsOpen, setModelsOpen] = useState(false)\n /** Whether this provider's whole card is unfolded. */\n const [cardOpen, setCardOpen] = useState(false)\n const [manageOpen, setManageOpen] = useState(false)\n const mounted = useRef(true)\n const dialog = useRef<HTMLDialogElement>(null)\n\n useEffect(() => {\n mounted.current = true\n return () => { mounted.current = false }\n }, [])\n\n // The dialog is a native modal, so the browser owns focus trapping and Esc.\n useEffect(() => {\n const element = dialog.current\n if (element === null || typeof element.showModal !== 'function') return\n if (manageOpen && !element.open) element.showModal()\n if (!manageOpen && element.open) element.close()\n }, [manageOpen])\n\n const copyCode = useCallback(async (code: string): Promise<void> => {\n try {\n await navigator.clipboard.writeText(code)\n if (mounted.current) {\n setCopied(true)\n setTimeout(() => { if (mounted.current) setCopied(false) }, 2000)\n }\n } catch {\n // A clipboard refusal is not worth an error banner: the code is on screen\n // and selectable, which is the fallback the user already has.\n }\n }, [])\n\n const toggleModelsOpen = useCallback((): void => {\n setModelsOpen((open) => !open)\n }, [])\n\n /** Fold or unfold this provider's card. */\n const toggleCardOpen = useCallback((): void => {\n setCardOpen((open) => !open)\n }, [])\n\n const accounts: readonly AccountStatus[] = status.accounts\n const models = status.models\n /** Whether this route signs in at all; the auto provider does not. */\n const connectable = status.connectable !== false\n const hiddenCount = models.filter((model) => !model.visible).length\n /** Whether the add-a-model-id field is open, and what has been typed. */\n const [adding, setAdding] = useState(false)\n const [draft, setDraft] = useState('')\n /** The row being dragged, so it can be dimmed while it moves. */\n const [dragFrom, setDragFrom] = useState<number | undefined>(undefined)\n /**\n * The dragged row, mirrored in a ref.\n *\n * The drop is handled by an ancestor whose handler was attached in an earlier\n * render, so it must not depend on state that was set during the drag.\n */\n const dragFromRef = useRef<number | undefined>(undefined)\n /** The row the pointer is currently over, so a drop lands where the user aims. */\n const dropTargetRef = useRef<number | undefined>(undefined)\n /** The same row in state, purely so it can be highlighted while dragging. */\n const [dropTarget, setDropTarget] = useState<number | undefined>(undefined)\n\n /**\n * Commit one dropped arrangement.\n *\n * The node half takes the full chain in its new order and appends whatever the\n * page left out, so sending just these ids is enough — and a model hidden in\n * another window is never dropped from the stored rank by a stale page.\n */\n const commitOrder = useCallback((ids: readonly string[]): void => {\n props.onSetModelOrder(ids)\n }, [props])\n\n /** Drag-and-drop plus its keyboard equivalent on the drag handle. */\n const drag = {\n start: (event: React.DragEvent, index: number): void => {\n setDragFrom(index)\n dragFromRef.current = index\n event.dataTransfer.effectAllowed = 'move'\n // Firefox refuses to start a drag without payload.\n event.dataTransfer.setData('text/plain', String(index))\n },\n over: (event: React.DragEvent, _index: number): void => {\n // A drop is only possible on a target that cancels the default.\n event.preventDefault()\n event.dataTransfer.dropEffect = 'move'\n const container = event.currentTarget as HTMLElement\n const to = indexAt(container, event.clientY)\n dropTargetRef.current = to\n if (to !== undefined && to !== dropTarget) setDropTarget(to)\n },\n drop: (event: React.DragEvent, index: number): void => {\n event.preventDefault()\n // The target comes from the last `dragover`, not from this event's own\n // coordinates: `drop` is not guaranteed to carry the pointer position, and a\n // zero there silently collapsed every drop onto the first row.\n const to = dropTargetRef.current ?? indexAt(event.currentTarget as HTMLElement, event.clientY) ?? index\n move(dragFromRef.current, to)\n dragFromRef.current = undefined\n dropTargetRef.current = undefined\n setDropTarget(undefined)\n setDragFrom(undefined)\n },\n /** A drop anywhere in the list lands on the row under the pointer. */\n dropAt: (event: React.DragEvent): void => {\n event.preventDefault()\n const container = event.currentTarget as HTMLElement\n const to = dropTargetRef.current ?? indexAt(container, event.clientY)\n if (to !== undefined) move(dragFromRef.current, to)\n dragFromRef.current = undefined\n dropTargetRef.current = undefined\n setDropTarget(undefined)\n setDragFrom(undefined)\n },\n end: (): void => {\n dragFromRef.current = undefined\n dropTargetRef.current = undefined\n setDropTarget(undefined)\n setDragFrom(undefined)\n },\n key: (event: React.KeyboardEvent, index: number): void => {\n if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return\n event.preventDefault()\n move(index, index + (event.key === 'ArrowUp' ? -1 : 1))\n },\n }\n\n /** Move one model row to another position and persist the result. */\n function move(from: number | undefined, to: number): void {\n if (from === undefined || from === to) return\n if (from < 0 || to < 0 || from >= models.length || to >= models.length) return\n const ids = models.map((model) => model.id)\n const [moved] = ids.splice(from, 1)\n if (moved === undefined) return\n ids.splice(to, 0, moved)\n commitOrder(ids)\n }\n\n /**\n * The row index a drop at this pointer position lands on.\n *\n * The drop is handled on the whole list rather than per row: rows are tall and\n * separated by gaps, so a drop between two of them hits no target at all and\n * silently does nothing. Comparing the pointer against each row's midpoint also\n * makes the gesture match what the user sees.\n */\n function indexAt(container: HTMLElement, clientY: number): number | undefined {\n const rows = Array.from(container.querySelectorAll<HTMLElement>('[data-model-index]'))\n if (rows.length === 0) return undefined\n let closest = 0\n let best = Number.POSITIVE_INFINITY\n for (const row of rows) {\n const box = row.getBoundingClientRect()\n const distance = Math.abs(clientY - (box.top + box.height / 2))\n if (distance >= best) continue\n best = distance\n closest = Number(row.dataset.modelIndex)\n }\n return closest\n }\n const busy = status.busy\n const liveCatalog = (status.discovered?.length ?? 0) > 0\n /**\n * What a folded card says about itself.\n *\n * The connection is the reason to open a card, so the folded row carries it;\n * `auto` has no account and counts the models it will actually try instead.\n */\n const summaryLine = !connectable\n ? t('statusAuto', { count: models.filter((model) => model.visible).length })\n : busy\n ? t('statusBusy')\n : status.loggedIn\n ? t('statusConnected', { count: accounts.length })\n : t('statusSignedOut')\n\n return (\n <div style={styles.card}>\n {/*\n The header is the disclosure: folded, the page is a list of providers, and\n the whole card is one row with its state and an edit affordance. The\n status dot stays visible either way, so a folded card still reports\n whether the route is signed in.\n */}\n <div style={styles.cardHeader}>\n <span style={{ ...styles.dot, background: dotColor(status) }} aria-hidden=\"true\" />\n <span style={styles.name}>{status.displayName}</span>\n <span style={styles.providerId}>{status.provider}</span>\n {!cardOpen && <span style={styles.cardSummary}>{summaryLine}</span>}\n <button\n type=\"button\"\n className=\"dsh-subscription-quiet\"\n style={styles.pushRight}\n aria-expanded={cardOpen}\n aria-controls={`dsh-subscription-card-${status.provider}`}\n onClick={toggleCardOpen}\n >\n {cardOpen ? t('collapse') : t('edit')}\n </button>\n </div>\n <div\n id={`dsh-subscription-card-${status.provider}`}\n style={cardOpen ? styles.cardBody : { ...styles.cardBody, display: 'none' }}\n >\n <p style={styles.statusLine}>\n {!connectable\n // `auto` has no account: it stands in for the models the other routes\n // signed in for, so it counts the candidates instead.\n ? t('statusAuto', { count: models.filter((model) => model.visible).length })\n : busy\n ? t('statusBusy')\n : status.loggedIn\n ? t('statusConnected', { count: accounts.length })\n : t('statusSignedOut')}\n </p>\n {connectable && !status.loggedIn && !busy && <p style={styles.statusLine}>{t('statusSignIn')}</p>}\n {status.lastError !== undefined && (\n <p className=\"dsh-subscription-error\">{t('lastError', { message: status.lastError })}</p>\n )}\n {message?.tone === 'error' && <p className=\"dsh-subscription-error\">{message.text}</p>}\n {message?.tone === 'success' && <p className=\"dsh-subscription-success\">{message.text}</p>}\n\n {accounts.map((account) => (\n <div key={account.account} style={styles.accountRow}>\n <div style={styles.accountHeader}>\n <button\n type=\"button\"\n style={styles.starButton}\n title={account.isDefault ? t('defaultBadge') : t('makeDefault')}\n aria-label={account.isDefault ? t('defaultBadge') : t('makeDefault')}\n disabled={busyAction || account.isDefault}\n onClick={() => { props.onSetDefault(account.account) }}\n >\n {account.isDefault ? '★' : '☆'}\n </button>\n <span style={styles.accountName}>{account.label}</span>\n {account.isDefault && <span style={styles.badge}>{t('defaultBadge')}</span>}\n {/* The provider refused this grant: only a new sign-in helps. */}\n {account.needsLogin === true && (\n <span style={styles.badge} title={t('accountNeedsLoginHint')}>{t('accountNeedsLogin')}</span>\n )}\n <span style={styles.statusLine}>\n {account.expired\n ? t('accountExpired')\n : t('accountExpires', { time: formatTime(account.expiresAt) })}\n </span>\n <button\n type=\"button\"\n className=\"dsh-subscription-quiet\"\n style={styles.pushRight}\n disabled={busyAction}\n onClick={() => { props.onLogout(account.account) }}\n >\n {t('logout')}\n </button>\n </div>\n </div>\n ))}\n\n <div style={styles.catalog}>\n <div style={styles.catalogHeader}>\n <button\n type=\"button\"\n className=\"dsh-subscription-disclosure\"\n style={styles.disclosure}\n aria-expanded={modelsOpen}\n aria-controls={`dsh-subscription-models-${status.provider}`}\n onClick={toggleModelsOpen}\n >\n <span style={styles.chevron} aria-hidden=\"true\">{modelsOpen ? '▾' : '▸'}</span>\n <span style={styles.catalogTitle}>{t('modelsTitle')}</span>\n </button>\n <span style={styles.catalogMeta}>\n {t('modelsCount', { count: models.length })}\n {' · '}\n {liveCatalog ? t('modelsLive') : t('modelsBuiltIn')}\n {hiddenCount === 0 ? '' : ` ${t('modelsHiddenCount', { count: hiddenCount })}`}\n </span>\n <button\n type=\"button\"\n className=\"dsh-subscription-quiet\"\n style={styles.pushRight}\n disabled={busyAction}\n onClick={props.onRefreshModels}\n >\n {busyAction ? t('refreshing') : t('refresh')}\n </button>\n {/* A model the endpoint serves but this build has never heard of: the\n id is published as a plain route, so a new release is usable at once. */}\n <button\n type=\"button\"\n className=\"dsh-subscription-quiet\"\n disabled={busyAction || !connectable}\n title={t('modelsAddHint')}\n onClick={() => { setAdding((open) => !open) }}\n >\n {t('modelsAdd')}\n </button>\n </div>\n {adding && (\n <form\n style={styles.addRow}\n onSubmit={(event) => {\n event.preventDefault()\n const id = draft.trim()\n if (id.length === 0) return\n props.onAddModel(id)\n setDraft('')\n setAdding(false)\n }}\n >\n <input\n type=\"text\"\n className=\"dsh-subscription-input\"\n style={styles.addInput}\n value={draft}\n autoFocus\n spellCheck={false}\n placeholder={t('modelsAddPlaceholder')}\n aria-label={t('modelsAdd')}\n disabled={busyAction}\n onChange={(event) => { setDraft(event.target.value) }}\n />\n <button type=\"submit\" disabled={busyAction || draft.trim().length === 0}>{t('modelsAddConfirm')}</button>\n <button type=\"button\" className=\"dsh-subscription-quiet\" onClick={() => { setAdding(false); setDraft('') }}>\n {t('cancel')}\n </button>\n </form>\n )}\n {modelsOpen && (\n <div\n id={`dsh-subscription-models-${status.provider}`}\n style={styles.modelList}\n // The whole list is the drop target, so a release between two rows still\n // lands somewhere instead of being cancelled unnoticed.\n onDragOver={(event) => { drag.over(event, 0) }}\n onDrop={drag.dropAt}\n >\n {status.provider === 'auto' && <span style={styles.chainTitle}>{t('modelsAutoChain')}</span>}\n {/* Every provider lists its models the same way: a switch in front,\n the model's own metadata, and a grip to drag it in the order both\n the picker list and Auto's fallback follow. */}\n {models.map((model, index) => (\n <div\n key={model.id}\n data-model-index={index}\n draggable={!busyAction}\n style={{\n ...styles.modelRowWrap,\n ...dragFrom === index ? { opacity: 0.5 } : {},\n // The row a drop would land on, so the gesture is visible while it\n // happens rather than only in its result.\n ...dropTarget === index && dragFrom !== undefined && dragFrom !== index\n ? { boxShadow: 'inset 0 2px 0 0 var(--dsw-alias-brand-primary)' }\n : {},\n }}\n onDragStart={(event) => { drag.start(event, index) }}\n onDragEnd={drag.end}\n >\n {/* The grip is the affordance and the keyboard path; the drag\n itself starts on the row, because a <button> does not start an\n HTML5 drag reliably in Chromium. */}\n <button\n type=\"button\"\n className=\"dsh-subscription-handle\"\n disabled={busyAction}\n title={t('modelsDrag')}\n aria-label={`${t('modelsDrag')} · ${model.name}`}\n onKeyDown={(event) => { drag.key(event, index) }}\n >\n ⠿\n </button>\n <label\n style={model.visible\n ? { ...styles.modelRowGrow, cursor: 'pointer' }\n : { ...styles.modelRowGrow, cursor: 'pointer', color: 'var(--dsw-alias-label-dimmed)' }}\n title={model.visible ? t('modelsHidden') : t('modelsShow')}\n >\n <input\n type=\"checkbox\"\n className=\"dsh-subscription-switch\"\n role=\"switch\"\n checked={model.visible}\n disabled={busyAction}\n aria-label={`${t('modelsShow')} · ${model.name}`}\n onChange={(event) => { props.onSetModelVisible(model.id, event.target.checked) }}\n />\n <span style={styles.modelBody}>\n <span style={styles.modelName}>\n {model.name}\n {model.source === undefined ? null : <span style={styles.badge}>{model.source}</span>}\n </span>\n <span style={styles.modelMeta}>\n {model.id}\n {' · '}\n {t('modelsContext', { tokens: formatTokens(model.contextWindow) })}\n {model.defaultEffort === undefined ? '' : ` · ${model.defaultEffort}`}\n {model.inputModalities === undefined || model.inputModalities.length === 0\n ? ''\n : ` · ${model.inputModalities.join('/')}`}\n </span>\n </span>\n </label>\n {/* A hand-added id can be removed again, so a typo does not linger. */}\n {model.userAdded === true && (\n <button\n type=\"button\"\n className=\"dsh-subscription-quiet\"\n style={styles.removeButton}\n disabled={busyAction}\n title={t('modelsRemove')}\n aria-label={`${t('modelsRemove')} · ${model.name}`}\n onClick={() => { props.onRemoveModel(model.id) }}\n >\n ×\n </button>\n )}\n </div>\n ))}\n {models.length === 0 && (\n <p style={styles.statusLine}>\n {status.provider === 'auto' ? t('modelsAutoChainEmpty') : t('modelsEmpty')}\n </p>\n )}\n </div>\n )}\n </div>\n\n <UsageBlock\n usage={status.usage ?? []}\n {...status.usageError === undefined ? {} : { error: status.usageError }}\n t={t}\n busyAction={busyAction}\n onRefresh={props.onRefreshUsage}\n />\n\n {/* `auto` owns no account, so it offers no connect, cancel or manage. */}\n {connectable && (\n <div style={styles.actions}>\n {!busy && (\n <button type=\"button\" disabled={busyAction} onClick={props.onLogin}>\n {accounts.length === 0 ? t('connect') : t('addAccount')}\n </button>\n )}\n {!busy && (\n <button type=\"button\" disabled={busyAction} onClick={() => { setManageOpen(true) }}>\n {t('manage')}\n </button>\n )}\n {busy && (\n <button type=\"button\" disabled={busyAction} onClick={props.onCancel}>\n {t('cancel')}\n </button>\n )}\n </div>\n )}\n {connectable && accounts.length > 0 && <p style={styles.statusLine}>{t('hint')}</p>}\n\n {busy && status.pending !== undefined && (\n <PendingLogin prompt={status.pending} t={t} copied={copied} onCopy={(code) => { void copyCode(code) }} />\n )}\n </div>\n {/* The dialog stays outside the collapsible body: a hidden ancestor would\n keep it out of the top layer even after `showModal()`. */}\n <dialog\n ref={dialog}\n className=\"dsh-subscription-manager\"\n aria-labelledby={`dsh-subscription-manage-${status.provider}`}\n // A closed `<dialog>` is hidden by the user-agent stylesheet, and an\n // inline `display` would override that rule — so the open state has to\n // be restated here rather than left to `showModal()` alone.\n style={manageOpen ? styles.modal : { ...styles.modal, display: 'none' }}\n onClose={() => { setManageOpen(false) }}\n onCancel={() => { setManageOpen(false) }}\n >\n <div style={styles.modalHeader}>\n <span style={styles.modalTitle} id={`dsh-subscription-manage-${status.provider}`}>\n {t('manageTitle', { provider: status.displayName })}\n </span>\n </div>\n\n <span style={styles.statusLine}>{t('manageAccounts')}</span>\n {accounts.map((account) => (\n <div key={account.account} style={styles.accountRow}>\n <div style={styles.accountHeader}>\n <span style={styles.accountName}>{account.label}</span>\n {account.isDefault && <span style={styles.badge}>{t('defaultBadge')}</span>}\n </div>\n <div style={styles.actions}>\n <button\n type=\"button\"\n \n disabled={busyAction}\n onClick={() => { props.onRefreshToken(account.account) }}\n >\n {t('refreshToken')}\n </button>\n {!account.isDefault && (\n <button\n type=\"button\"\n \n disabled={busyAction}\n onClick={() => { props.onSetDefault(account.account) }}\n >\n {t('makeDefault')}\n </button>\n )}\n <button\n type=\"button\"\n \n disabled={busyAction}\n onClick={() => { props.onLogout(account.account) }}\n >\n {t('logout')}\n </button>\n </div>\n </div>\n ))}\n {accounts.length === 0 && <p style={styles.statusLine}>{t('statusSignedOut')}</p>}\n\n <div style={styles.divider} />\n <div style={styles.actions}>\n <button type=\"button\" disabled={busyAction} onClick={props.onReload}>\n {t('reload')}\n </button>\n </div>\n <p style={styles.statusLine}>{t('reloadHint')}</p>\n\n <div style={styles.divider} />\n <div style={styles.details}>\n {status.details.map((row) => (\n <div key={row.key} style={styles.detailRow}>\n <span>{detailLabel(t, row)}</span>\n <span style={styles.detailValue}>{row.value}</span>\n </div>\n ))}\n {status.discovered !== undefined && status.discovered.length > 0 && (\n <div style={styles.detailRow}>\n <span>{t('modelsLive')}</span>\n <span style={styles.detailValue}>{status.discovered.join(', ')}</span>\n </div>\n )}\n </div>\n\n <div className=\"dsh-subscription-dialog-actions\" style={styles.modalActions}>\n <button type=\"button\" onClick={() => { setManageOpen(false) }}>{t('close')}</button>\n </div>\n </dialog>\n </div>\n )\n}\n\n/**\n * The Subscriptions settings section.\n * @param props - section props (inject face plus the shell's `close`).\n * @returns the section content.\n */\nexport function SubscriptionsSection(props: SubscriptionsSectionProps) {\n const { call } = props\n const t = props.t ?? fallbackTranslate\n const [status, setStatus] = useState<SubscriptionStatus | undefined>(undefined)\n const [error, setError] = useState<string | undefined>(undefined)\n const [messages, setMessages] = useState<Record<string, CardMessage>>({})\n const [busyAction, setBusyAction] = useState(false)\n const mounted = useRef(true)\n const timer = useRef<ReturnType<typeof setInterval> | undefined>(undefined)\n\n /** Fetch the current state; a failure is shown rather than swallowed. */\n const refresh = useCallback(async (): Promise<void> => {\n if (call === undefined) return\n try {\n const next = await call('status', undefined)\n if (!mounted.current) return\n setStatus(next)\n setError(undefined)\n } catch (caught: unknown) {\n if (mounted.current) setError(t('actionFailed', { message: messageOf(caught) }))\n }\n }, [call, t])\n\n const stopPolling = useCallback((): void => {\n if (timer.current !== undefined) {\n clearInterval(timer.current)\n timer.current = undefined\n }\n }, [])\n\n const startPolling = useCallback((): void => {\n if (timer.current !== undefined) return\n // The node half clears `busy` once the attempt settles; the effect below\n // observes that and stops this interval.\n timer.current = setInterval(() => { void refresh() }, POLL_INTERVAL_MS)\n }, [refresh])\n\n useEffect(() => {\n mounted.current = true\n void refresh()\n return () => {\n mounted.current = false\n stopPolling()\n }\n }, [refresh, stopPolling])\n\n /** Every route worth reading, as of the latest render, for the usage poll. */\n const signedInRef = useRef<readonly string[]>([])\n useEffect(() => {\n signedInRef.current = usageTargets(status?.providers ?? [])\n })\n\n /**\n * Read each named route's plan and allowance, applying every answer.\n *\n * A read is not forced: the node half reuses a figure only for a few seconds and\n * never reuses a substitute, so a steady interval refreshes the figures while\n * collapsing bursts into one request per route.\n */\n const readUsage = useCallback(async (providers: readonly string[]): Promise<void> => {\n if (call === undefined) return\n for (const provider of providers) {\n try {\n const result = await call('usage', { provider })\n if (!mounted.current) return\n setStatus(result.status)\n } catch {\n // An unreadable quota is already surfaced inside the card by the status it\n // carries; the page itself stays usable.\n }\n }\n }, [call])\n\n // The figures are read as soon as the first status lands...\n const usageRequested = useRef(false)\n useEffect(() => {\n if (call === undefined || status === undefined || usageRequested.current) return\n const signedIn = usageTargets(status.providers)\n if (signedIn.length === 0) return\n usageRequested.current = true\n void readUsage(signedIn)\n }, [call, status, readUsage])\n\n // ...and then kept current while the page is open. Allowances move as the account\n // is used, so a figure that is only read on mount reports the state of a page that\n // may have been open for hours.\n useEffect(() => {\n if (call === undefined) return\n const tick = (): void => {\n // Nothing is refreshed for a page nobody is looking at.\n if (globalThis.document?.visibilityState === 'hidden') return\n void readUsage(signedInRef.current)\n }\n const interval = setInterval(tick, USAGE_POLL_INTERVAL_MS)\n return () => { clearInterval(interval) }\n }, [call, readUsage])\n\n // Poll only while some provider is waiting for browser approval.\n const anyBusy = status?.providers.some((provider) => provider.busy) === true\n useEffect(() => {\n if (anyBusy) startPolling()\n else stopPolling()\n }, [anyBusy, startPolling, stopPolling])\n\n /** Run one action for one provider, reporting its own failure. */\n const act = useCallback(async (provider: string, run: () => Promise<void>): Promise<void> => {\n setBusyAction(true)\n setMessages((current) => {\n const next = { ...current }\n delete next[provider]\n return next\n })\n try {\n await run()\n } catch (caught: unknown) {\n if (mounted.current) {\n setMessages((current) => ({ ...current, [provider]: { tone: 'error', text: t('actionFailed', { message: messageOf(caught) }) } }))\n }\n } finally {\n if (mounted.current) setBusyAction(false)\n }\n }, [t])\n\n const login = useCallback((provider: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const started = await call('login', { provider })\n if (!mounted.current) return\n setStatus(started.status)\n // A loopback flow wants the browser opened; a blocked popup still leaves\n // the link in the card, which is the fallback the user already has.\n if (started.prompt.mode === 'open-url') {\n window.open(started.prompt.url, '_blank', 'noopener')\n }\n })\n }, [act, call])\n\n const cancel = useCallback((provider: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('cancel', { provider })\n if (mounted.current) setStatus(next)\n })\n }, [act, call])\n\n const logout = useCallback((provider: string, account: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('logout', { provider, account })\n if (mounted.current) setStatus(next)\n })\n }, [act, call])\n\n const setDefault = useCallback((provider: string, account: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('setDefault', { provider, account })\n if (mounted.current) setStatus(next)\n })\n }, [act, call])\n\n const refreshToken = useCallback((provider: string, account: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const result = await call('refresh', { provider, account })\n if (!mounted.current) return\n setStatus(result.status)\n setMessages((current) => ({\n ...current,\n [provider]: { tone: 'success', text: t('tokenRefreshed', { time: formatTime(result.expiresAt) }) },\n }))\n })\n }, [act, call, t])\n\n const refreshModels = useCallback((provider: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('discover', { provider, force: true })\n if (!mounted.current) return\n setStatus(next)\n setMessages((current) => ({ ...current, [provider]: { tone: 'success', text: t('modelsRefreshed') } }))\n })\n }, [act, call, t])\n\n const setModelVisible = useCallback((provider: string, id: string, visible: boolean): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('setModelVisible', { provider, id, visible })\n if (mounted.current) setStatus(next)\n })\n }, [act, call])\n\n const refreshUsage = useCallback((provider: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const result = await call('usage', { provider, force: true })\n if (mounted.current) setStatus(result.status)\n })\n }, [act, call])\n\n const setModelOrder = useCallback((provider: string, ids: readonly string[]): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('setModelOrder', { provider, ids: [...ids] })\n if (mounted.current) setStatus(next)\n })\n }, [act, call])\n\n const addModel = useCallback((provider: string, id: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('addModel', { provider, id })\n if (mounted.current) setStatus(next)\n })\n }, [act, call])\n\n const removeModel = useCallback((provider: string, id: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('removeModel', { provider, id })\n if (mounted.current) setStatus(next)\n })\n }, [act, call])\n\n const reload = useCallback((provider: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('reload', undefined)\n if (mounted.current) setStatus(next)\n })\n }, [act, call])\n\n if (call === undefined) return <p style={styles.intro}>{t('unavailable')}</p>\n\n return (\n <div className=\"dsh-subscriptions-settings\" style={styles.section}>\n <style>{subscriptionsCss}</style>\n <p style={styles.intro}>{t('intro')}</p>\n {error !== undefined && <p className=\"dsh-subscription-error\">{error}</p>}\n {status === undefined && <p style={styles.statusLine}>{t('loading')}</p>}\n {status?.providers.map((provider) => (\n <ProviderCard\n key={provider.provider}\n status={provider}\n t={t}\n busyAction={busyAction}\n {...messages[provider.provider] === undefined ? {} : { message: messages[provider.provider] as CardMessage }}\n onLogin={() => { login(provider.provider) }}\n onCancel={() => { cancel(provider.provider) }}\n onLogout={(account) => { logout(provider.provider, account) }}\n onSetDefault={(account) => { setDefault(provider.provider, account) }}\n onRefreshToken={(account) => { refreshToken(provider.provider, account) }}\n onRefreshModels={() => { refreshModels(provider.provider) }}\n onSetModelVisible={(id, visible) => { setModelVisible(provider.provider, id, visible) }}\n onSetModelOrder={(ids) => { setModelOrder(provider.provider, ids) }}\n onAddModel={(id) => { addModel(provider.provider, id) }}\n onRemoveModel={(id) => { removeModel(provider.provider, id) }}\n onRefreshUsage={() => { refreshUsage(provider.provider) }}\n onReload={() => { reload(provider.provider) }}\n />\n ))}\n </div>\n )\n}\n","/**\n * Subscriptions settings page, browser half.\n *\n * Registers one `settings.section` entry titled 订阅 / Subscriptions. Every\n * connection fact arrives through the node half's `subscription-oauth` RPC\n * endpoints, so this half holds no credential state of its own, and the copy\n * rides the client locale service under the `settings.subscriptions` namespace.\n *\n * @module dsh-plugin-subscription-oauth/client\n */\n\n// Type-only: pulls the `ctx.slots` Context merge and the `settings.section`\n// SlotMap entry. These imports are erased; no runtime binding is taken.\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client'\n\nimport { SubscriptionsSection } from './SubscriptionsSection.js'\nimport type { SubscriptionsSectionInjected } from './SubscriptionsSection.js'\nimport { createSubscriptionsCall } from './subscriptions-rpc.js'\nimport { en, zh } from './locales.js'\nimport type { SubscriptionsKey } from './locales.js'\n\nexport type { SubscriptionsSectionInjected, SubscriptionsSectionProps } from './SubscriptionsSection.js'\nexport type { SubscriptionsKey } from './locales.js'\nexport { createSubscriptionsCall, SubscriptionsRpcError } from './subscriptions-rpc.js'\nexport type { SubscriptionsCall } from './subscriptions-rpc.js'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The Subscriptions settings page copy. */\n 'settings.subscriptions': SubscriptionsKey\n }\n}\n\n/** Dictionary namespace owned by this plugin. */\nconst NS = 'settings.subscriptions'\n\n/** Settings nav position: after the shipped sections, before plugin tooling. */\nconst SECTION_ORDER = 92\n\n/**\n * Required services: `slots` carries the registration seat, `connection` the\n * RPC caller, and `locale` the copy dictionaries.\n */\nexport const inject = ['slots', 'connection', 'locale']\n\n/**\n * Register the Subscriptions settings section.\n *\n * Registration goes through `slots.inject`, so the shell's own apply order\n * relative to this one is unconstrained.\n *\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-plugin-subscription-oauth: copy dictionaries')\n // The shell's Context merge types `connection` as the host handle; in the\n // browser the same key holds the full client ConnectionHandle.\n const connection = ctx.get('connection') as unknown as ConnectionHandle\n const t = ctx.locale.bind(NS) as SubscriptionsSectionInjected['t']\n const call = createSubscriptionsCall(connection.rpc)\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'subscriptions',\n order: SECTION_ORDER,\n // A thunk re-evaluated per read, so the nav label follows the active locale.\n label: () => t('nav'),\n inject: (): SubscriptionsSectionInjected => ({ call, t }),\n }, SubscriptionsSection))\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["code: string","messageOf","result: Awaited<ReturnType<ConnectionHandle['rpc']['call']>>","error: unknown","template: string","styles: Record<string, CSSProperties>","window","accounts: readonly AccountStatus[]","caught: unknown"],"sources":["../src/client/locales.ts","../src/client/styles.ts","../src/protocol.ts","../src/client/subscriptions-rpc.ts","../src/client/SubscriptionsSection.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * Copy dictionaries for the Subscriptions settings page.\n *\n * English is the key-set source of truth; the Chinese dictionary is typed\n * against it, so a key added to one and forgotten in the other is a compile\n * error rather than a silent fallback.\n */\n\n/** English strings. */\nexport const en = {\n nav: 'Subscriptions',\n intro: 'Sign in with a subscription account — Kimi Code, Grok (X Premium) or Google Antigravity. DeepSeek Harness keeps the tokens on this machine and streams every request through the provider\\'s own API.',\n loading: 'Checking subscriptions…',\n unavailable: 'The subscriptions page needs the web connection service, which this composition does not provide.',\n statusConnected: 'Connected · {count} account(s)',\n statusSignedOut: 'Not signed in',\n statusBusy: 'Waiting for browser approval',\n statusSignIn: 'Sign in to add this provider\\'s models to the model picker.',\n statusAuto: 'Follows {count} enabled model(s) from every provider',\n connect: 'Connect',\n edit: 'Edit',\n collapse: 'Collapse',\n addAccount: 'Add account',\n manage: 'Manage',\n cancel: 'Cancel',\n close: 'Close',\n logout: 'Sign out',\n makeDefault: 'Use by default',\n defaultBadge: 'Default',\n accountNeedsLogin: 'Sign in again',\n accountNeedsLoginHint: 'The provider refused this account\\'s grant, so its token cannot be renewed',\n accountExpired: 'token expired',\n accountExpires: 'expires {time}',\n deviceTitle: 'Approve this device',\n devicePrompt: 'Open the verification page and confirm the code below. This page updates by itself once the provider approves.',\n deviceOpen: 'Open verification page',\n deviceCopy: 'Copy code',\n deviceCopied: 'Copied',\n deviceExpires: 'The code expires at {time}.',\n openLogin: 'Open sign-in page',\n openLoginHint: 'Finish signing in in the browser tab that opened. This page updates by itself once the provider redirects back.',\n modelsTitle: 'Models',\n modelsCount: '{count} available',\n modelsLive: 'live',\n modelsBuiltIn: 'built-in',\n modelsShow: 'Show in picker',\n modelsHiddenCount: '· {count} hidden',\n modelsHidden: 'Hidden from the model picker',\n modelsEmpty: 'No models reported yet.',\n modelsAdd: 'Add model',\n modelsAddHint: 'Publish a model id this build does not know yet',\n modelsAddPlaceholder: 'model id, e.g. k3-next',\n modelsAddConfirm: 'Add',\n modelsRemove: 'Remove this hand-added model',\n modelsContext: '{tokens} context',\n modelsAuto: 'Auto',\n modelsAutoHint: 'Pinned first: moves to the next model when one is unavailable',\n modelsAutoOff: 'Off — the picker offers the models directly instead',\n modelsAutoChain: 'Auto tries these, in this order — drag to re-order',\n modelsDrag: 'Drag to re-order (or press ↑ / ↓ here)',\n modelsAutoChainOff: 'Take out of Auto',\n modelsAutoChainEmpty: 'No model is enabled, so Auto has nothing to try.',\n modelsPinned: 'pinned',\n usageTitle: 'Plan and usage',\n usagePlan: 'Plan: {plan}',\n usageRenews: 'renews {time}',\n usageReset: 'resets {time}',\n usageResetsInMinutes: 'resets in {minutes}m',\r\n usageResetsInHours: 'resets in {hours}h',\n usageResetDue: 'resets shortly',\n usageRefresh: 'Refresh usage',\n usageFetched: 'read {time}',\n usageNone: 'This provider reports no plan quota.',\n usageError: 'Could not read usage: {message}',\n usage_5h: '5-hour window',\n usage_weekly: 'Weekly window',\n usage_credits: 'Included credit',\n usage_gemini: 'Gemini allowance (rolling)',\n usage_gemini_weekly: 'Gemini allowance (weekly)',\n usage_claude: 'Claude allowance (rolling)',\n usage_claude_weekly: 'Claude allowance (weekly)',\n usage_other: 'Allowance',\n refresh: 'Refresh',\n refreshing: 'Refreshing…',\n modelsRefreshed: 'Model list refreshed.',\n refreshToken: 'Refresh token',\n tokenRefreshed: 'Token refreshed; valid until {time}.',\n hint: 'Authorization follows the account the browser is signed in with. To add a different account, switch accounts there first, or use a private window.',\n manageTitle: 'Manage {provider}',\n manageAccounts: 'Accounts',\n reload: 'Reload credentials',\n reloadHint: 'Use this after signing in from another DeepSeek Harness window.',\n detail_oauthHost: 'Authorization server',\n detail_apiBase: 'API base',\n detail_credentialFile: 'Credential file',\n detail_project: 'Cloud project',\n lastError: 'Last login attempt failed: {message}',\n loginFailed: 'Could not start the sign-in: {message}',\n actionFailed: 'Action failed: {message}',\n} as const\n\n/** The Subscriptions namespace key union. */\nexport type SubscriptionsKey = keyof typeof en\n\n/** Chinese strings. */\nexport const zh = {\n nav: '订阅',\n intro: '使用订阅账号登录 —— Kimi Code、Grok (X Premium) 或 Google Antigravity。DeepSeek Harness 将令牌保存在本机,并通过各服务自己的 API 发起请求。',\n loading: '正在检查订阅…',\n unavailable: '订阅页面需要 Web 连接服务,当前组合未提供该服务。',\n statusConnected: '已连接 {count} 个账号',\n statusSignedOut: '未登录',\n statusBusy: '等待浏览器确认',\n statusSignIn: '登录后即可在模型选择器中使用该服务的模型。',\n statusAuto: '依次使用来自各服务的 {count} 个已启用模型',\n connect: '连接',\n edit: '编辑',\n collapse: '收起',\n addAccount: '添加账号',\n manage: '管理',\n cancel: '取消',\n close: '关闭',\n logout: '退出登录',\n makeDefault: '设为默认',\n defaultBadge: '默认',\n accountNeedsLogin: '需重新登录',\n accountNeedsLoginHint: '服务方已拒绝该账号的授权,令牌无法自动续期',\n accountExpired: '令牌已过期',\n accountExpires: '过期时间 {time}',\n deviceTitle: '确认此设备',\n devicePrompt: '打开验证页面并确认下方代码,服务确认后本页面会自动更新。',\n deviceOpen: '打开验证页面',\n deviceCopy: '复制代码',\n deviceCopied: '已复制',\n deviceExpires: '代码将于 {time} 过期。',\n openLogin: '打开登录页面',\n openLoginHint: '请在打开的浏览器标签页中完成登录;服务回调后本页面会自动更新。',\n modelsTitle: '模型',\n modelsCount: '共 {count} 个',\n modelsLive: '实时',\n modelsBuiltIn: '内置',\n modelsShow: '在选择器中显示',\n modelsHiddenCount: '· 已隐藏 {count} 个',\n modelsHidden: '已从模型选择器隐藏',\n modelsEmpty: '暂未获取到模型。',\n modelsAdd: '添加模型',\n modelsAddHint: '手动添加一个本版本还不认识的模型 ID',\n modelsAddPlaceholder: '模型 ID,例如 k3-next',\n modelsAddConfirm: '添加',\n modelsRemove: '移除这个手动添加的模型',\n modelsContext: '{tokens} 上下文',\n modelsAuto: '自动',\n modelsAutoHint: '固定在首位:某个模型不可用时自动改用下一个',\n modelsAutoOff: '已关闭 —— 选择器将直接列出各个模型',\n modelsAutoChain: '自动会按这个顺序依次尝试(拖动可调整)',\n modelsDrag: '拖动调整顺序(也可在此按 ↑ / ↓)',\n modelsAutoChainOff: '从自动中移除',\n modelsAutoChainEmpty: '没有启用任何模型,自动没有可尝试的模型。',\n modelsPinned: '固定',\n usageTitle: '套餐与额度',\n usagePlan: '套餐:{plan}',\n usageRenews: '续费时间 {time}',\n usageReset: '重置时间 {time}',\n usageResetsInMinutes: '{minutes} 分钟后重置',\r\n usageResetsInHours: '{hours} 小时后重置',\n usageResetDue: '即将重置',\n usageRefresh: '刷新额度',\n usageFetched: '读取于 {time}',\n usageNone: '该服务未提供套餐额度信息。',\n usageError: '额度读取失败:{message}',\n usage_5h: '5 小时窗口',\n usage_weekly: '每周窗口',\n usage_credits: '包含额度',\n usage_gemini: 'Gemini 模型额度(滚动)',\n usage_gemini_weekly: 'Gemini 模型额度(每周)',\n usage_claude: 'Claude 模型额度(滚动)',\n usage_claude_weekly: 'Claude 模型额度(每周)',\n usage_other: '额度',\n refresh: '刷新',\n refreshing: '正在刷新…',\n modelsRefreshed: '模型列表已刷新。',\n refreshToken: '刷新令牌',\n tokenRefreshed: '令牌已刷新,有效至 {time}。',\n hint: '授权以浏览器当前登录的账号为准;要添加不同账号,请先在浏览器里切换账号,或使用无痕窗口。',\n manageTitle: '管理 {provider}',\n manageAccounts: '账号',\n reload: '重新读取凭据',\n reloadHint: '在另一个 DeepSeek Harness 窗口中登录后,请使用此操作。',\n detail_oauthHost: '授权服务器',\n detail_apiBase: 'API 地址',\n detail_credentialFile: '凭据文件',\n detail_project: '云项目',\n lastError: '上次登录失败:{message}',\n loginFailed: '无法开始登录:{message}',\n actionFailed: '操作失败:{message}',\n} satisfies Record<SubscriptionsKey, string>\n","/**\n * Scoped stylesheet for the Subscriptions page and its manage dialog.\n *\n * The host loads plugin markup without its own control classes, and a bare\n * `<button>` therefore renders with the browser's default raised chrome — which\n * reads as a stray highlight in a dark theme. Rather than invent a look, this\n * follows the control vocabulary of the sibling `dsh-plugin-subscriptions`\n * plugin (same author-facing design as the shell's settings forms): a 6 px\n * radius small control, a transparent fill, a hairline border from the theme's\n * alias tokens, and the theme's hover/active/focus tokens for state.\n *\n * The tokens all carry fallbacks, so the page still renders correctly on a host\n * build that does not define them.\n *\n * @module dsh-plugin-subscription-oauth/client/styles\n */\n\n/**\n * Everything is scoped to this plugin's own roots, so no other surface in the\n * shell can be affected by a rule here.\n */\nconst scope = ':is(.dsh-subscriptions-settings, .dsh-subscription-manager)'\n\n/** The page's and dialog's stylesheet. */\nexport const subscriptionsCss = `\n.dsh-subscriptions-settings,\n.dsh-subscription-manager {\n font-family: inherit;\n font-size: 13px;\n line-height: 1.5;\n /* Native controls (checkbox, dialog) follow the host theme, not the OS. */\n color-scheme: light;\n}\nbody[data-ds-dark-theme] ${scope} {\n color-scheme: dark;\n}\n.dsh-subscription-manager {\n box-sizing: border-box;\n padding: 0;\n border: 0;\n border-radius: var(--dsw-radius-panel, 16px);\n background: var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1));\n color: var(--dsw-alias-label-primary);\n box-shadow: var(--dsw-elevation-prominent, 0 20px 70px #0004);\n}\n.dsh-subscription-manager::backdrop {\n background: var(--dsw-alias-bg-mask-1, #0006);\n backdrop-filter: var(--dsw-mask-blur, none);\n}\n${scope} h2 {\n margin: 0;\n font-size: 16px;\n line-height: 24px;\n font-weight: 500;\n}\n${scope} strong,\n${scope} .dsh-subscription-label {\n font-size: 13px;\n line-height: 1.5;\n font-weight: 500;\n}\n${scope} small,\n${scope} .dsh-subscription-hint,\n${scope} [role=\"status\"] {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary);\n}\n${scope} .dsh-subscription-error {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-error-primary, #b42318);\n}\n${scope} .dsh-subscription-success {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-success-primary, #067647);\n}\n${scope} label:has(> input[type=\"checkbox\"]) {\n cursor: pointer;\n}\n${scope} input[type=\"checkbox\"] {\n accent-color: var(--dsw-alias-brand-primary);\n}\n/*\n * A model's on/off control is a switch, not a checkbox: it takes effect\n * immediately and its state is what matters, not a tick. Drawn from the theme\n * tokens with appearance: none, so it follows the host palette in both themes\n * instead of the platform's checkbox chrome.\n */\n${scope} input[type=\"checkbox\"].dsh-subscription-switch {\n appearance: none;\n -webkit-appearance: none;\n position: relative;\n box-sizing: border-box;\n flex-shrink: 0;\n width: 34px;\n height: 20px;\n margin: 0;\n border: 0.5px solid var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-2));\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease;\n}\n${scope} input[type=\"checkbox\"].dsh-subscription-switch::after {\n content: '';\n position: absolute;\n top: 2px;\n left: 2px;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: var(--dsw-alias-label-primary-foreground, #fff);\n box-shadow: 0 1px 2px #0003;\n transition: transform 120ms ease;\n}\n${scope} input[type=\"checkbox\"].dsh-subscription-switch:checked {\n border-color: transparent;\n background: var(--dsw-alias-state-success-primary, var(--dsw-alias-brand-primary, #067647));\n}\n${scope} input[type=\"checkbox\"].dsh-subscription-switch:checked::after {\n transform: translateX(14px);\n}\n${scope} input[type=\"checkbox\"].dsh-subscription-switch:disabled {\n opacity: 0.4;\n cursor: default;\n}\n${scope} button {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n min-height: 28px;\n padding: 0 10px;\n border: 0.5px solid var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));\n border-radius: var(--dsw-radius-sm, 6px);\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n}\n${scope} button:hover:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-hover, var(--dsw-alias-bg-layer-3));\n}\n${scope} button:active:not(:disabled) {\n background: var(--dsw-alias-interactive-bg-active, var(--dsw-alias-bg-layer-3));\n}\n${scope} button:disabled {\n opacity: 0.4;\n cursor: default;\n}\n/* A control that is present but should not read as a bordered button. */\n${scope} button.dsh-subscription-quiet {\n min-height: 22px;\n padding: 0 4px;\n border-color: transparent;\n color: var(--dsw-alias-label-secondary, var(--dsw-alias-label-primary));\n}\n/* The free-text field for a hand-added model id. */\n${scope} input.dsh-subscription-input {\n box-sizing: border-box;\n flex: 1 1 auto;\n min-width: 0;\n min-height: 28px;\n padding: 0 10px;\n border: 0.5px solid var(--dsw-alias-border-l3, var(--dsw-alias-border-l2));\n border-radius: var(--dsw-radius-sm, 6px);\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font: inherit;\n font-size: 12px;\n line-height: 18px;\n}\n${scope} input.dsh-subscription-input::placeholder {\n color: var(--dsw-alias-label-tertiary);\n}\n${scope} input.dsh-subscription-input:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));\n outline-offset: 2px;\n}\n/* A drag handle: no border or fill, so it reads as a grip rather than a button. */\n${scope} button.dsh-subscription-handle {\n min-height: 18px;\n height: 18px;\n padding: 0 2px;\n border-color: transparent;\n border-radius: var(--dsw-radius-sm, 6px);\n background: transparent;\n color: var(--dsw-alias-label-tertiary, var(--dsw-alias-label-secondary));\n font-size: 12px;\n line-height: 18px;\n cursor: grab;\n}\n${scope} button.dsh-subscription-handle:hover:not(:disabled) {\n background: transparent;\n color: var(--dsw-alias-label-primary);\n}\n${scope} button.dsh-subscription-handle:active:not(:disabled) { cursor: grabbing; }\n/* Controls inside the manage dialog are one step larger, like a form footer. */\n${scope} .dsh-subscription-dialog-actions button {\n min-height: 34px;\n padding: 0 14px;\n border-radius: var(--dsw-radius-md, 8px);\n font-size: 13px;\n line-height: 1.5;\n}\n${scope} button.dsh-subscription-primary {\n border-color: transparent;\n background: var(--dsw-alias-button-primary-fill, var(--dsw-alias-label-primary));\n color: var(--dsw-alias-label-primary-foreground, var(--dsw-alias-bg-layer-1));\n font-weight: 500;\n}\n${scope} button.dsh-subscription-primary:hover:not(:disabled) {\n background: var(--dsw-alias-button-primary-hover, var(--dsw-alias-label-secondary));\n}\n${scope} button:focus-visible,\n${scope} input[type=\"checkbox\"]:focus-visible,\n${scope} .dsh-subscription-disclosure:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));\n outline-offset: 2px;\n}\n/* The catalog heading is a summary-style disclosure, not a bordered button. */\n${scope} .dsh-subscription-disclosure {\n border-color: transparent;\n border-radius: var(--dsw-radius-sm, 6px);\n background: transparent;\n padding: 0 4px;\n font-size: 12px;\n line-height: 18px;\n font-weight: 500;\n color: var(--dsw-alias-label-secondary, var(--dsw-alias-label-primary));\n}\n${scope} .dsh-subscription-disclosure:hover:not(:disabled) {\n background: transparent;\n color: var(--dsw-alias-label-primary);\n}\n/* A usage meter: the track, with the fill sized by the caller. */\n${scope} .dsh-subscription-meter {\n height: 6px;\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-2));\n overflow: hidden;\n}\n${scope} .dsh-subscription-meter > span {\n display: block;\n height: 100%;\n background: var(--dsw-alias-state-success-primary, #067647);\n}\n${scope} .dsh-subscription-meter > span[data-exhausted=\"true\"] {\n background: var(--dsw-alias-state-error-primary, #b42318);\n}\n`\n","/**\n * Wire contract shared by the plugin's node half (the RPC routes) and its\n * browser half (the Settings page).\n *\n * Only this module crosses the boundary: the client bundle imports the types\n * and the channel name, and every call travels as one JSON envelope over a\n * `POST /api/<prefix><endpoint>` route. One status document describes every\n * provider route the plugin serves, so the page renders one card per entry.\n *\n * @module dsh-plugin-subscription-oauth/protocol\n */\n\n/** Prefix of every RPC method this plugin serves. */\nexport const RPC_PREFIX = 'subscription-oauth.'\n\n/** Endpoints the Settings page may call. */\nexport const RPC_ENDPOINTS = [\n 'status',\n 'login',\n 'cancel',\n 'logout',\n 'setDefault',\n 'refresh',\n 'discover',\n 'setModelVisible',\n 'setModelOrder',\n 'addModel',\n 'removeModel',\n 'usage',\n 'reload',\n] as const\n\n/** One endpoint name. */\nexport type RpcEndpoint = typeof RPC_ENDPOINTS[number]\n\n/** Client → host envelope. */\nexport interface RpcRequest {\n type: 'client-request'\n rpcId: string\n method: string\n payload: unknown\n}\n\n/** Host → client envelope, carrying either the value or the failure. */\nexport interface RpcResponse {\n type: 'server-response'\n rpcId: string\n result: RpcResult\n}\n\n/** Discriminated result inside {@link RpcResponse}. */\nexport type RpcResult =\n | { ok: true, value: unknown }\n | { ok: false, error: RpcError }\n\n/** Failure detail, with the provider-neutral code when one applies. */\nexport interface RpcError {\n code: string\n message: string\n /**\n * Carrier-owned structured detail. The Connection transport models a failure\n * as `{code, message, details}`, so an empty object travels here rather than\n * an absent field the client would have to repair.\n */\n details: Record<string, unknown>\n}\n\n/**\n * One provider-owned connection fact the manage dialog shows.\n *\n * The key is a stable identifier (`oauthHost`, `apiBase`, …) that the browser\n * half localizes; the host never sends user-facing prose.\n */\nexport interface DetailRow {\n key: string\n value: string\n}\n\n/** How the browser should present one pending login. */\nexport type LoginMode =\n /** Open the URL in a new tab; the redirect completes the login. */\n | 'open-url'\n /** Show the code (and a link) while the host polls the token endpoint. */\n | 'device-code'\n\n/** What the Settings page has to show to finish a login. */\nexport interface LoginPrompt {\n /** URL the user opens (or that was opened for them). */\n readonly url: string\n /** Code the user confirms in the browser; device flows only. */\n readonly userCode?: string\n /** Epoch milliseconds after which the attempt lapses. */\n readonly expiresAt: number\n readonly mode: LoginMode\n}\n\n/** One signed-in account as the page displays it. */\nexport interface AccountStatus {\n /** Account key the store is keyed by. */\n account: string\n /** Display label (email, user id, or the key's digest). */\n label: string\n /** Epoch milliseconds when the access token lapses. */\n expiresAt: number\n /** Whether the access token is already past its expiry. */\n expired: boolean\n /** Whether this account serves requests by default. */\n isDefault: boolean\n /**\n * Whether the provider refused this account's grant outright.\n *\n * A refresh answered with `invalid_grant` means the authorization is gone — the\n * stored token can never be renewed — so the page asks for a new sign-in rather\n * than reporting the account as connected.\n */\n needsLogin?: boolean\n}\n\n/** One selectable model, as the page lists it. */\nexport interface ModelSummary {\n id: string\n name: string\n description?: string\n contextWindow: number\n inputModalities?: readonly string[]\n defaultEffort?: string\n /** Whether the session model picker offers this model. */\n visible: boolean\n /**\n * Route that owns this model, when the listing spans routes.\n *\n * The \u0007uto provider mixes models from every route, so its rows name the\n * owning route; a single-route catalog leaves it out.\n */\n source?: string\n /** Whether the user added this id by hand, so it can be removed again. */\n userAdded?: boolean\n /**\n * Whether this entry is fixed at the head of the list.\n *\n * The synthetic `auto` entry follows the user's own order, so it cannot be\n * hidden or moved; the page renders it without those controls.\n */\n pinned?: boolean\n}\n\n/** One metered allowance of a subscription plan. */\nexport interface UsageWindow {\n /** What the window meters, e.g. `weekly`, `5h`, `credits`. */\n label: string\n /** How much of the allowance is spent, when the provider states it. */\n usedPercent?: number\n /** Amount already spent, when the provider states an absolute figure. */\n used?: number\n /** Total allowance, when the provider states one. */\n limit?: number\n /** Epoch milliseconds when the window resets, when the provider states it. */\n resetsAt?: number\n}\n\n/** One account's plan and metered allowances. */\nexport interface ProviderUsage {\n /** Account the figures belong to. */\n account: string\n /** Plan name or tier, when the provider states one. */\n plan?: string\n /** Whether the plan renews on a date the provider states. */\n renewsAt?: number\n /** Metered allowances, most relevant first. */\n windows: readonly UsageWindow[]\n /** Epoch milliseconds these figures were read. */\n fetchedAt: number\n /**\n * Whether these figures are the route's substitute rather than its own accounting.\n *\n * A substitute is shown when the primary source cannot answer, and it is never\n * reused as if it were a reading: the next request asks again.\n */\n substitute?: boolean\n}\n\n/** Result of reading one route's usage. */\nexport interface UsageResult {\n provider: string\n /** Usage per account the endpoint could answer for. */\n usage: readonly ProviderUsage[]\n /** Why an account's usage is missing, when the read failed. */\n error?: string\n status: SubscriptionStatus\n}\n\n/** The full state of one provider route. */\nexport interface ProviderStatus {\n /** Provider route key, e.g. `kimi`. */\n provider: string\n displayName: string\n /** Where a user manages the underlying subscription. */\n codeUrl?: string\n /** Whether at least one account is stored. */\n loggedIn: boolean\n /**\n * Whether this route signs in at all.\n *\n * \u0007uto has no account of its own — it delegates to routes that do — so the\n * page hides the connect and manage controls for it.\n */\n connectable?: boolean\n /** Whether a login attempt is being polled. */\n busy: boolean\n /** The pending attempt, when `busy`. */\n pending?: LoginPrompt\n accounts: AccountStatus[]\n /** Account key serving requests, when one does. */\n activeAccount?: string\n /** Absolute path of the credential file, for support requests. */\n credentialFile: string\n /** Whether the live model list is consulted. */\n discovery: boolean\n /** Live catalog ids, when discovery has succeeded this session. */\n discovered?: readonly string[]\n models: readonly ModelSummary[]\n /** Model the picker preselects. */\n defaultModel?: string\n /** Last usage figures read for this route, when they were requested. */\n usage?: readonly ProviderUsage[]\n /** Why the last usage read failed, when it did. */\n usageError?: string\n /** Provider-owned connection facts shown in the manage dialog. */\n details: readonly DetailRow[]\n /** Last background failure (a login that could not complete). */\n lastError?: string\n}\n\n/** Every provider route's state, in display order. */\nexport interface SubscriptionStatus {\n providers: ProviderStatus[]\n}\n\n/** Result of starting a login. */\nexport interface LoginStart {\n prompt: LoginPrompt\n status: SubscriptionStatus\n}\n\n/** Result of a token refresh. */\nexport interface RefreshResult {\n account: string\n expiresAt: number\n status: SubscriptionStatus\n}\n\n/** Payload fields naming one provider route and optionally one account. */\nexport interface AccountRequest {\n provider: string\n account?: string\n}\n\n/** Payload of `setDefault`. */\nexport interface SetDefaultRequest {\n provider: string\n account: string\n}\n\n/** Payload of `discover`. */\nexport interface DiscoverRequest {\n provider: string\n /** Ask the endpoint for its live model list even when one is cached. */\n force?: boolean\n}\n\n/** Payload of `setModelVisible`. */\nexport interface SetModelVisibleRequest {\n provider: string\n /** Model id as the catalog reports it. */\n id: string\n /** Whether the session model picker should offer it. */\n visible: boolean\n}\n\n/** Payload of `addModel` and `removeModel`. */\nexport interface ModelIdRequest {\n provider: string\n /** Model id as the endpoint accepts it. */\n id: string\n}\n\n/** Payload of `setModelOrder`. */\nexport interface SetModelOrderRequest {\n provider: string\n /** Every catalog model id, in the order the user arranged them. */\n ids: readonly string[]\n}\n\n/** Payload naming only a provider route. */\nexport interface ProviderRequest {\n provider: string\n}\n\n/** Map an endpoint to its payload and value types. */\nexport interface RpcMap {\n status: { payload: undefined, value: SubscriptionStatus }\n login: { payload: AccountRequest, value: LoginStart }\n cancel: { payload: ProviderRequest, value: SubscriptionStatus }\n logout: { payload: AccountRequest, value: SubscriptionStatus }\n setDefault: { payload: SetDefaultRequest, value: SubscriptionStatus }\n refresh: { payload: AccountRequest, value: RefreshResult }\n discover: { payload: DiscoverRequest, value: SubscriptionStatus }\n setModelVisible: { payload: SetModelVisibleRequest, value: SubscriptionStatus }\n setModelOrder: { payload: SetModelOrderRequest, value: SubscriptionStatus }\n addModel: { payload: ModelIdRequest, value: SubscriptionStatus }\n removeModel: { payload: ModelIdRequest, value: SubscriptionStatus }\n usage: { payload: DiscoverRequest, value: UsageResult }\n reload: { payload: undefined, value: SubscriptionStatus }\n}\n","/**\n * Browser caller for the plugin's own RPC channel.\n *\n * The node half mounts one exact `POST /api/subscription-oauth.<endpoint>` route\n * per endpoint, so every call travels on the shared `/api` channel with a\n * prefixed endpoint name — the same wire the rest of the shell uses.\n *\n * @module dsh-plugin-subscription-oauth/client/subscriptions-rpc\n */\n\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client'\n\nimport { RPC_PREFIX } from '../protocol.js'\nimport type { RpcEndpoint, RpcMap } from '../protocol.js'\n\n/** The shared channel the node half's routes are mounted on. */\nconst CHANNEL = '/api'\n\n/** A failed call: either the transport rejected or the endpoint answered `ok: false`. */\nexport class SubscriptionsRpcError extends Error {\n constructor(message: string, readonly code: string) {\n super(message)\n this.name = 'SubscriptionsRpcError'\n }\n}\n\n/** Human-readable message for any thrown value. */\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/** Call one endpoint, unwrapping the transport result. */\nexport type SubscriptionsCall = <K extends RpcEndpoint>(\n endpoint: K,\n payload: RpcMap[K]['payload'],\n) => Promise<RpcMap[K]['value']>\n\n/**\n * Bind a caller to one connection's RPC transport.\n * @param rpc - the browser connection's RPC caller.\n * @returns a typed caller for this plugin's endpoints.\n */\nexport function createSubscriptionsCall(rpc: ConnectionHandle['rpc']): SubscriptionsCall {\n return async function call(endpoint, payload) {\n let result: Awaited<ReturnType<ConnectionHandle['rpc']['call']>>\n try {\n result = await rpc.call(CHANNEL, `${RPC_PREFIX}${endpoint}`, payload)\n } catch (error: unknown) {\n throw new SubscriptionsRpcError(messageOf(error), 'RPC_TRANSPORT')\n }\n if (!result.ok) throw new SubscriptionsRpcError(result.error.message, result.error.code)\n return result.value as never\n }\n}\n","/**\n * Subscriptions settings page.\n *\n * One card per provider route — Kimi Code, Grok (X Premium), Google Antigravity\n * — describing that route's connection, accounts, and model catalog. Every fact\n * arrives from the node half's `subscription-oauth` RPC endpoints, so this\n * component holds no credential state of its own. The status poll runs only\n * while a login is pending, so an idle page performs no requests.\n *\n * Colors resolve through `--dsw-alias-*` design tokens, so the page follows the\n * active theme; every user-visible string goes through the locale-bound `t`.\n *\n * @module dsh-plugin-subscription-oauth/client/SubscriptionsSection\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport type { CSSProperties } from 'react'\n\nimport { en } from './locales.js'\nimport type { SubscriptionsKey } from './locales.js'\nimport { subscriptionsCss } from './styles.js'\nimport { SubscriptionsRpcError } from './subscriptions-rpc.js'\nimport type { SubscriptionsCall } from './subscriptions-rpc.js'\nimport type {\n AccountStatus,\n DetailRow,\n LoginPrompt,\n ProviderStatus,\n ProviderUsage,\n SubscriptionStatus,\n UsageWindow,\n} from '../protocol.js'\n/** Poll cadence while a login attempt is pending. */\nconst POLL_INTERVAL_MS = 2000\n\n/**\n * How often an open page re-reads every signed-in route's allowance.\n *\n * Slow on purpose: it is fast enough that a figure shown on screen is current, and\n * slow enough that leaving the page open costs one small request a minute per\n * signed-in provider.\n */\nconst USAGE_POLL_INTERVAL_MS = 60_000\n\n/**\n * Disclosures start folded, every time the page is opened.\n *\n * Nothing is remembered between visits on purpose: the section is a list of\n * providers first, and a card left open last time should not decide what this\n * visit looks like. A fold is a gesture within one visit, not a stored setting.\n */\n\n/** Horizontal room the page takes inside the settings panel. */\nconst SECTION_WIDTH = 560\n\n/** What the node half injects into this section. */\nexport interface SubscriptionsSectionInjected {\n /** Typed caller for the plugin's RPC endpoints. */\n call: SubscriptionsCall\n /** Translate a key of this plugin's settings namespace. */\n t: (key: SubscriptionsKey, params?: Record<string, unknown>) => string\n}\n\n/** Component props: the shell supplies `close`, the plugin supplies the rest. */\nexport type SubscriptionsSectionProps = Partial<SubscriptionsSectionInjected> & {\n /** Close the settings panel (owned by the settings shell). */\n close?: () => void\n}\n\n/** One card's transient message. */\ninterface CardMessage {\n tone: 'error' | 'success'\n text: string\n}\n\n/** Minimal translator used when the locale seat is absent (standalone render). */\nfunction fallbackTranslate(key: SubscriptionsKey, params?: Record<string, unknown>): string {\n const template: string = en[key]\n if (params === undefined) return template\n return template.replace(/\\{(\\w+)\\}/g, (match, name: string) =>\n name in params ? String(params[name]) : match)\n}\n\n/** Human-readable message for any thrown value. */\nfunction messageOf(error: unknown): string {\n if (error instanceof SubscriptionsRpcError) return error.message\n return error instanceof Error ? error.message : String(error)\n}\n\n/** Local time for one epoch-millisecond instant. */\nfunction formatTime(at: number): string {\n try {\n return new Date(at).toLocaleString()\n } catch {\n return String(at)\n }\n}\n\n/** How far ahead a reset is still stated as hours rather than a date. */\nconst RESET_RELATIVE_MS = 24 * 60 * 60 * 1000\n\n/**\n * One allowance reset, in the form a reader acts on.\n *\n * How far off it is decides how it is said: minutes inside the hour, hours inside\n * the day, and the date beyond that — \"137 小时后\" would be arithmetic rather than\n * information, and a window that comes back in forty minutes is better described in\n * minutes than as \"1 小时后\". Every route's windows go through here, so all of them\n * read the same way.\n *\n * @param at - epoch milliseconds the window resets.\n * @param t - translator, so the wording follows the page language.\n * @returns the reset text.\n */\nfunction formatReset(\n at: number,\n t: (key: SubscriptionsKey, params?: Record<string, unknown>) => string,\n): string {\n const remaining = at - Date.now()\n if (remaining <= 0) return t('usageResetDue')\n if (remaining >= RESET_RELATIVE_MS) return t('usageReset', { time: formatTime(at) })\n // Rounded, and never below one: \"0 分钟后重置\" would read as a bug rather than as\n // \"any moment now\".\n if (remaining < 60 * 60 * 1000) {\n return t('usageResetsInMinutes', { minutes: Math.max(1, Math.round(remaining / 60_000)) })\n }\n return t('usageResetsInHours', { hours: Math.max(1, Math.round(remaining / 3_600_000)) })\n}\n\n/** Compact token count for a context window. */\nfunction formatTokens(tokens: number): string {\n if (tokens >= 1_000_000) return `${String(Math.round(tokens / 100_000) / 10)}M`\n if (tokens >= 1_000) return `${String(Math.round(tokens / 1_000))}K`\n return String(tokens)\n}\n\n/**\n * Routes worth asking for allowances.\n *\n * A route with no account of its own — `auto` — reports itself as signed in so its\n * card reads as usable, but it holds no allowance to read: asking it every minute\n * spends a request to be told nothing. The routes it stands in for are each read\n * on their own.\n *\n * @param providers - the routes in the current status.\n * @returns the route keys to read usage for.\n */\nfunction usageTargets(providers: readonly ProviderStatus[]): string[] {\n return providers\n .filter((provider) => provider.loggedIn && provider.connectable !== false)\n .map((provider) => provider.provider)\n}\n\nconst styles: Record<string, CSSProperties> = {\n section: {\n display: 'flex', flexDirection: 'column', gap: 12, maxWidth: SECTION_WIDTH,\n color: 'var(--dsw-alias-label-primary)',\n },\n intro: { margin: 0, color: 'var(--dsw-alias-label-tertiary)', fontSize: 14, lineHeight: '22px' },\n card: {\n border: '0.5px solid var(--dsw-alias-settings-card-stroke, var(--dsw-alias-border-l4))',\n borderRadius: 'var(--dsw-radius-xl, 20px)',\n background: 'var(--dsw-alias-settings-card-fill, var(--dsw-alias-bg-layer-2))',\n padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 6,\n },\n cardHeader: { display: 'flex', alignItems: 'center', gap: 8 },\n /** Everything a folded card hides. */\n cardBody: { display: 'flex', flexDirection: 'column', gap: 6 },\n /** The one-line state a folded card shows in place of its body. */\n cardSummary: { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },\n dot: { width: 8, height: 8, borderRadius: '50%', flexShrink: 0 },\n name: { fontWeight: 500, fontSize: 14, lineHeight: '22px', color: 'var(--dsw-alias-label-primary)' },\n providerId: { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-dimmed)' },\n statusLine: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },\n errorLine: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' },\n successLine: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-success-primary)' },\n actions: { display: 'flex', gap: 8, marginTop: 4, alignItems: 'center', flexWrap: 'wrap' },\n accountRow: {\n display: 'flex', flexDirection: 'column', gap: 6,\n border: '0.5px solid var(--dsw-alias-border-l2)', borderRadius: 'var(--dsw-radius-lg, 16px)',\n padding: '8px 10px', marginTop: 4,\n },\n accountHeader: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' },\n starButton: {\n border: 'none', padding: 0, width: 20, background: 'transparent',\n font: 'inherit', fontSize: 14, lineHeight: '20px', cursor: 'pointer',\n color: 'var(--dsw-alias-state-warn-label)',\n },\n accountName: { fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)', userSelect: 'all' },\n badge: {\n fontSize: 11, lineHeight: '16px', padding: '0 6px', borderRadius: 8,\n background: 'var(--dsw-alias-bg-layer-1)', border: '0.5px solid var(--dsw-alias-border-l2)',\n color: 'var(--dsw-alias-label-secondary)',\n },\n pushRight: { marginLeft: 'auto', flexShrink: 0 },\n catalog: {\n display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4,\n borderTop: '0.5px solid var(--dsw-alias-border-l2)', paddingTop: 8,\n },\n catalogHeader: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' },\n catalogTitle: { fontSize: 12, lineHeight: '18px', fontWeight: 500, color: 'var(--dsw-alias-label-secondary)' },\n catalogMeta: { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },\n /**\n * Button surface geometry lives in the scoped stylesheet; only the layout a\n * rule cannot express stays inline.\n */\n pill: {},\n pillSmall: {},\n /** The catalog heading: a summary-style disclosure, not a bordered control. */\n disclosure: {\n display: 'inline-flex', alignItems: 'center', gap: 4, margin: 0,\n },\n chevron: { fontSize: 10, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },\n modelList: { display: 'flex', flexDirection: 'column', gap: 6, marginTop: 2 },\n /** One model line: a visibility toggle and the model's own metadata. */\n modelRowGrow: { display: 'flex', alignItems: 'flex-start', gap: 8, flex: '1 1 auto', minWidth: 0 },\n /** The pinned `auto` line: listed, switchable, and never part of its own chain. */\n autoRow: {\n display: 'flex', alignItems: 'flex-start', gap: 8, cursor: 'pointer',\n fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)',\n },\n /** The models `auto` will walk, indented under it. */\n chain: {\n display: 'flex', flexDirection: 'column', gap: 3,\n margin: '2px 0 4px 26px', paddingLeft: 8,\n borderLeft: '0.5px solid var(--dsw-alias-border-l2)',\n },\n chainTitle: { fontSize: 11, lineHeight: '16px', color: 'var(--dsw-alias-label-tertiary)' },\n /** The add-a-model-id row: the field, then confirm and cancel. */\n addRow: { display: 'flex', alignItems: 'center', gap: 6, marginTop: 2 },\n addInput: {},\n /** The remove control on a hand-added row: a bare glyph beside the row. */\n removeButton: { flexShrink: 0, alignSelf: 'center', padding: '0 6px' },\n /** One draggable chain row: the grip, then the remove-from-auto switch. */\n chainRowWrap: { display: 'flex', alignItems: 'center', gap: 4 },\n chainRow: {\n display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer', flex: '1 1 auto',\n fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-secondary)',\n },\n usage: {\n display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4,\n borderTop: '0.5px solid var(--dsw-alias-border-l2)', paddingTop: 8,\n },\n usageHeader: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' },\n usageRow: { display: 'flex', flexDirection: 'column', gap: 3 },\n usageLine: { display: 'flex', alignItems: 'baseline', gap: 8, justifyContent: 'space-between' },\n usageMeter: {\n height: 6, borderRadius: 999, background: 'var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-2))',\n overflow: 'hidden',\n },\n modelRow: {\n display: 'flex', alignItems: 'flex-start', gap: 8,\n cursor: 'pointer', fontSize: 13, lineHeight: '20px',\n color: 'var(--dsw-alias-label-primary)',\n },\n /** A hidden model stays listed and editable, but reads as inactive. */\n modelRowHidden: {\n display: 'flex', alignItems: 'flex-start', gap: 8,\n cursor: 'pointer', fontSize: 13, lineHeight: '20px',\n color: 'var(--dsw-alias-label-dimmed)',\n },\n modelBody: { display: 'flex', flexDirection: 'column', gap: 1, minWidth: 0 },\n modelName: { fontSize: 13, lineHeight: '20px' },\n modelMeta: { fontSize: 11, lineHeight: '16px', color: 'var(--dsw-alias-label-tertiary)', userSelect: 'all' },\n device: {\n marginTop: 4, display: 'flex', flexDirection: 'column', gap: 6,\n border: '0.5px solid var(--dsw-alias-border-l2)', borderRadius: 'var(--dsw-radius-lg, 16px)',\n padding: '10px 12px', background: 'var(--dsw-alias-bg-layer-1)',\n },\n deviceTitle: { fontWeight: 500, fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)' },\n deviceCode: {\n fontFamily: 'monospace', fontSize: 18, lineHeight: '24px', letterSpacing: 2,\n color: 'var(--dsw-alias-label-primary)', userSelect: 'all',\n },\n details: {\n display: 'flex', flexDirection: 'column', gap: 4,\n fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)',\n },\n detailRow: { display: 'flex', gap: 8, justifyContent: 'space-between', alignItems: 'baseline' },\n detailValue: { userSelect: 'all', overflowWrap: 'anywhere', textAlign: 'right' },\n /** Dialog layout only: its surface comes from `.dsh-subscription-manager`. */\n modal: {\n width: 440, maxWidth: '100%', maxHeight: '85vh', overflowY: 'auto',\n boxSizing: 'border-box', display: 'flex', flexDirection: 'column', gap: 10,\n padding: '16px 18px', margin: 'auto',\n },\n modalHeader: { display: 'flex', alignItems: 'center', gap: 8 },\n modalTitle: { fontWeight: 500, fontSize: 16, lineHeight: '24px', color: 'var(--dsw-alias-label-primary)' },\n modalActions: { display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 2 },\n divider: { borderTop: '0.5px solid var(--dsw-alias-border-l2)' },\n}\n\n/** Status dot color for one provider's connection state. */\nfunction dotColor(status: ProviderStatus): string {\n if (status.busy) return 'var(--dsw-alias-state-warn-label)'\n if (status.loggedIn) return 'var(--dsw-alias-state-success-primary)'\n return 'var(--dsw-alias-label-dimmed)'\n}\n\n/** Localized label for one provider-owned detail row. */\nfunction detailLabel(t: SubscriptionsSectionInjected['t'], row: DetailRow): string {\n const key = `detail_${row.key}` as SubscriptionsKey\n return key in en ? t(key) : row.key\n}\n\n/** Localized label for one metered window the provider states. */\nfunction windowLabel(t: SubscriptionsSectionInjected['t'], window: UsageWindow): string {\n const key = `usage_${window.label}` as SubscriptionsKey\n return key in en ? t(key) : window.label\n}\n\n/** How much of one window is spent, as a display string. */\nfunction windowAmount(window: UsageWindow): string {\n if (window.usedPercent !== undefined) return `${String(Math.round(window.usedPercent))}%`\n if (window.used !== undefined && window.limit !== undefined) return `${String(window.used)}/${String(window.limit)}`\n return '—'\n}\n\n/** One account's plan and metered allowances. */\nfunction UsageBlock(props: {\n usage: readonly ProviderUsage[]\n error?: string\n t: SubscriptionsSectionInjected['t']\n busyAction: boolean\n onRefresh(): void\n}) {\n const { usage, error, t, busyAction, onRefresh } = props\n return (\n <div style={styles.usage}>\n <div style={styles.usageHeader}>\n <span style={styles.catalogTitle}>{t('usageTitle')}</span>\n <button\n type=\"button\"\n className=\"dsh-subscription-quiet\"\n style={styles.pushRight}\n disabled={busyAction}\n onClick={onRefresh}\n >\n {busyAction ? t('refreshing') : t('usageRefresh')}\n </button>\n </div>\n {error !== undefined && <p className=\"dsh-subscription-error\">{t('usageError', { message: error })}</p>}\n {usage.length === 0 && error === undefined && <p style={styles.statusLine}>{t('usageNone')}</p>}\n {usage.map((entry) => (\n <div key={entry.account} style={styles.usageRow}>\n <div style={styles.usageLine}>\n <span style={styles.modelMeta}>\n {entry.plan === undefined ? entry.account : t('usagePlan', { plan: entry.plan })}\n {entry.renewsAt === undefined ? '' : ` · ${t('usageRenews', { time: formatTime(entry.renewsAt) })}`}\n </span>\n <span style={styles.modelMeta}>{t('usageFetched', { time: formatTime(entry.fetchedAt) })}</span>\n </div>\n {entry.windows.map((window) => (\n <div key={`${entry.account}-${window.label}`} style={styles.usageRow}>\n <div style={styles.usageLine}>\n <span style={styles.modelName}>\n {windowLabel(t, window)}\n {' · '}\n {windowAmount(window)}\n </span>\n <span style={styles.modelMeta}>\n {window.resetsAt === undefined ? '' : formatReset(window.resetsAt, t)}\n </span>\n </div>\n {window.usedPercent !== undefined && (\n <div className=\"dsh-subscription-meter\">\n <span\n style={{ width: `${String(Math.min(100, Math.max(0, window.usedPercent)))}%` }}\n data-exhausted={window.usedPercent >= 90 ? 'true' : 'false'}\n />\n </div>\n )}\n </div>\n ))}\n </div>\n ))}\n </div>\n )\n}\n\n/** The pending-login block: a device code, or a link to the sign-in page. */\nfunction PendingLogin(props: {\n prompt: LoginPrompt\n t: SubscriptionsSectionInjected['t']\n copied: boolean\n onCopy(code: string): void\n}) {\n const { prompt, t, copied, onCopy } = props\n return (\n <div style={styles.device}>\n <span style={styles.deviceTitle}>\n {prompt.mode === 'device-code' ? t('deviceTitle') : t('openLogin')}\n </span>\n <span style={styles.statusLine}>\n {prompt.mode === 'device-code' ? t('devicePrompt') : t('openLoginHint')}\n </span>\n {prompt.userCode !== undefined && <span style={styles.deviceCode}>{prompt.userCode}</span>}\n <span style={styles.statusLine}>{t('deviceExpires', { time: formatTime(prompt.expiresAt) })}</span>\n <div style={styles.actions}>\n <button\n type=\"button\"\n \n onClick={() => { window.open(prompt.url, '_blank', 'noopener') }}\n >\n {prompt.mode === 'device-code' ? t('deviceOpen') : t('openLogin')}\n </button>\n {prompt.userCode !== undefined && (\n <button type=\"button\" onClick={() => { onCopy(prompt.userCode as string) }}>\n {copied ? t('deviceCopied') : t('deviceCopy')}\n </button>\n )}\n </div>\n </div>\n )\n}\n\n/** One provider's card. */\nfunction ProviderCard(props: {\n status: ProviderStatus\n t: SubscriptionsSectionInjected['t']\n busyAction: boolean\n message?: CardMessage\n onLogin(): void\n onCancel(): void\n onLogout(account: string): void\n onSetDefault(account: string): void\n onRefreshToken(account: string): void\n onRefreshModels(): void\n onSetModelVisible(id: string, visible: boolean): void\n /** Persist the chain order the user dropped into place. */\n onSetModelOrder(ids: readonly string[]): void\n /** Publish a model id the catalog does not know yet. */\n onAddModel(id: string): void\n /** Forget a hand-added model id. */\n onRemoveModel(id: string): void\n onRefreshUsage(): void\n onReload(): void\n}) {\n const { status, t, busyAction, message } = props\n const [copied, setCopied] = useState(false)\n // Both disclosures open folded, on every visit: the choice is a gesture, not a\n // stored setting. See the note beside the interval constants.\n const [modelsOpen, setModelsOpen] = useState(false)\n /** Whether this provider's whole card is unfolded. */\n const [cardOpen, setCardOpen] = useState(false)\n const [manageOpen, setManageOpen] = useState(false)\n const mounted = useRef(true)\n const dialog = useRef<HTMLDialogElement>(null)\n\n useEffect(() => {\n mounted.current = true\n return () => { mounted.current = false }\n }, [])\n\n // The dialog is a native modal, so the browser owns focus trapping and Esc.\n useEffect(() => {\n const element = dialog.current\n if (element === null || typeof element.showModal !== 'function') return\n if (manageOpen && !element.open) element.showModal()\n if (!manageOpen && element.open) element.close()\n }, [manageOpen])\n\n const copyCode = useCallback(async (code: string): Promise<void> => {\n try {\n await navigator.clipboard.writeText(code)\n if (mounted.current) {\n setCopied(true)\n setTimeout(() => { if (mounted.current) setCopied(false) }, 2000)\n }\n } catch {\n // A clipboard refusal is not worth an error banner: the code is on screen\n // and selectable, which is the fallback the user already has.\n }\n }, [])\n\n const toggleModelsOpen = useCallback((): void => {\n setModelsOpen((open) => !open)\n }, [])\n\n /** Fold or unfold this provider's card. */\n const toggleCardOpen = useCallback((): void => {\n setCardOpen((open) => !open)\n }, [])\n\n const accounts: readonly AccountStatus[] = status.accounts\n const models = status.models\n /** Whether this route signs in at all; the auto provider does not. */\n const connectable = status.connectable !== false\n const hiddenCount = models.filter((model) => !model.visible).length\n /** Whether the add-a-model-id field is open, and what has been typed. */\n const [adding, setAdding] = useState(false)\n const [draft, setDraft] = useState('')\n /** The row being dragged, so it can be dimmed while it moves. */\n const [dragFrom, setDragFrom] = useState<number | undefined>(undefined)\n /**\n * The dragged row, mirrored in a ref.\n *\n * The drop is handled by an ancestor whose handler was attached in an earlier\n * render, so it must not depend on state that was set during the drag.\n */\n const dragFromRef = useRef<number | undefined>(undefined)\n /** The row the pointer is currently over, so a drop lands where the user aims. */\n const dropTargetRef = useRef<number | undefined>(undefined)\n /** The same row in state, purely so it can be highlighted while dragging. */\n const [dropTarget, setDropTarget] = useState<number | undefined>(undefined)\n\n /**\n * Commit one dropped arrangement.\n *\n * The node half takes the full chain in its new order and appends whatever the\n * page left out, so sending just these ids is enough — and a model hidden in\n * another window is never dropped from the stored rank by a stale page.\n */\n const commitOrder = useCallback((ids: readonly string[]): void => {\n props.onSetModelOrder(ids)\n }, [props])\n\n /** Drag-and-drop plus its keyboard equivalent on the drag handle. */\n const drag = {\n start: (event: React.DragEvent, index: number): void => {\n setDragFrom(index)\n dragFromRef.current = index\n event.dataTransfer.effectAllowed = 'move'\n // Firefox refuses to start a drag without payload.\n event.dataTransfer.setData('text/plain', String(index))\n },\n over: (event: React.DragEvent, _index: number): void => {\n // A drop is only possible on a target that cancels the default.\n event.preventDefault()\n event.dataTransfer.dropEffect = 'move'\n const container = event.currentTarget as HTMLElement\n const to = indexAt(container, event.clientY)\n dropTargetRef.current = to\n if (to !== undefined && to !== dropTarget) setDropTarget(to)\n },\n drop: (event: React.DragEvent, index: number): void => {\n event.preventDefault()\n // The target comes from the last `dragover`, not from this event's own\n // coordinates: `drop` is not guaranteed to carry the pointer position, and a\n // zero there silently collapsed every drop onto the first row.\n const to = dropTargetRef.current ?? indexAt(event.currentTarget as HTMLElement, event.clientY) ?? index\n move(dragFromRef.current, to)\n dragFromRef.current = undefined\n dropTargetRef.current = undefined\n setDropTarget(undefined)\n setDragFrom(undefined)\n },\n /** A drop anywhere in the list lands on the row under the pointer. */\n dropAt: (event: React.DragEvent): void => {\n event.preventDefault()\n const container = event.currentTarget as HTMLElement\n const to = dropTargetRef.current ?? indexAt(container, event.clientY)\n if (to !== undefined) move(dragFromRef.current, to)\n dragFromRef.current = undefined\n dropTargetRef.current = undefined\n setDropTarget(undefined)\n setDragFrom(undefined)\n },\n end: (): void => {\n dragFromRef.current = undefined\n dropTargetRef.current = undefined\n setDropTarget(undefined)\n setDragFrom(undefined)\n },\n key: (event: React.KeyboardEvent, index: number): void => {\n if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return\n event.preventDefault()\n move(index, index + (event.key === 'ArrowUp' ? -1 : 1))\n },\n }\n\n /** Move one model row to another position and persist the result. */\n function move(from: number | undefined, to: number): void {\n if (from === undefined || from === to) return\n if (from < 0 || to < 0 || from >= models.length || to >= models.length) return\n const ids = models.map((model) => model.id)\n const [moved] = ids.splice(from, 1)\n if (moved === undefined) return\n ids.splice(to, 0, moved)\n commitOrder(ids)\n }\n\n /**\n * The row index a drop at this pointer position lands on.\n *\n * The drop is handled on the whole list rather than per row: rows are tall and\n * separated by gaps, so a drop between two of them hits no target at all and\n * silently does nothing. Comparing the pointer against each row's midpoint also\n * makes the gesture match what the user sees.\n */\n function indexAt(container: HTMLElement, clientY: number): number | undefined {\n const rows = Array.from(container.querySelectorAll<HTMLElement>('[data-model-index]'))\n if (rows.length === 0) return undefined\n let closest = 0\n let best = Number.POSITIVE_INFINITY\n for (const row of rows) {\n const box = row.getBoundingClientRect()\n const distance = Math.abs(clientY - (box.top + box.height / 2))\n if (distance >= best) continue\n best = distance\n closest = Number(row.dataset.modelIndex)\n }\n return closest\n }\n const busy = status.busy\n const liveCatalog = (status.discovered?.length ?? 0) > 0\n /**\n * What a folded card says about itself.\n *\n * The connection is the reason to open a card, so the folded row carries it;\n * `auto` has no account and counts the models it will actually try instead.\n */\n const summaryLine = !connectable\n ? t('statusAuto', { count: models.filter((model) => model.visible).length })\n : busy\n ? t('statusBusy')\n : status.loggedIn\n ? t('statusConnected', { count: accounts.length })\n : t('statusSignedOut')\n\n return (\n <div style={styles.card}>\n {/*\n The header is the disclosure: folded, the page is a list of providers, and\n the whole card is one row with its state and an edit affordance. The\n status dot stays visible either way, so a folded card still reports\n whether the route is signed in.\n */}\n <div style={styles.cardHeader}>\n <span style={{ ...styles.dot, background: dotColor(status) }} aria-hidden=\"true\" />\n <span style={styles.name}>{status.displayName}</span>\n <span style={styles.providerId}>{status.provider}</span>\n {!cardOpen && <span style={styles.cardSummary}>{summaryLine}</span>}\n <button\n type=\"button\"\n className=\"dsh-subscription-quiet\"\n style={styles.pushRight}\n aria-expanded={cardOpen}\n aria-controls={`dsh-subscription-card-${status.provider}`}\n onClick={toggleCardOpen}\n >\n {cardOpen ? t('collapse') : t('edit')}\n </button>\n </div>\n <div\n id={`dsh-subscription-card-${status.provider}`}\n style={cardOpen ? styles.cardBody : { ...styles.cardBody, display: 'none' }}\n >\n <p style={styles.statusLine}>\n {!connectable\n // `auto` has no account: it stands in for the models the other routes\n // signed in for, so it counts the candidates instead.\n ? t('statusAuto', { count: models.filter((model) => model.visible).length })\n : busy\n ? t('statusBusy')\n : status.loggedIn\n ? t('statusConnected', { count: accounts.length })\n : t('statusSignedOut')}\n </p>\n {connectable && !status.loggedIn && !busy && <p style={styles.statusLine}>{t('statusSignIn')}</p>}\n {status.lastError !== undefined && (\n <p className=\"dsh-subscription-error\">{t('lastError', { message: status.lastError })}</p>\n )}\n {message?.tone === 'error' && <p className=\"dsh-subscription-error\">{message.text}</p>}\n {message?.tone === 'success' && <p className=\"dsh-subscription-success\">{message.text}</p>}\n\n {accounts.map((account) => (\n <div key={account.account} style={styles.accountRow}>\n <div style={styles.accountHeader}>\n <button\n type=\"button\"\n style={styles.starButton}\n title={account.isDefault ? t('defaultBadge') : t('makeDefault')}\n aria-label={account.isDefault ? t('defaultBadge') : t('makeDefault')}\n disabled={busyAction || account.isDefault}\n onClick={() => { props.onSetDefault(account.account) }}\n >\n {account.isDefault ? '★' : '☆'}\n </button>\n <span style={styles.accountName}>{account.label}</span>\n {account.isDefault && <span style={styles.badge}>{t('defaultBadge')}</span>}\n {/* The provider refused this grant: only a new sign-in helps. */}\n {account.needsLogin === true && (\n <span style={styles.badge} title={t('accountNeedsLoginHint')}>{t('accountNeedsLogin')}</span>\n )}\n <span style={styles.statusLine}>\n {account.expired\n ? t('accountExpired')\n : t('accountExpires', { time: formatTime(account.expiresAt) })}\n </span>\n <button\n type=\"button\"\n className=\"dsh-subscription-quiet\"\n style={styles.pushRight}\n disabled={busyAction}\n onClick={() => { props.onLogout(account.account) }}\n >\n {t('logout')}\n </button>\n </div>\n </div>\n ))}\n\n {/* A signed-out route has nothing to configure here, and listing models it\n cannot call would only invite picker entries that fail. */}\n {status.loggedIn && (\n <div style={styles.catalog}>\n <div style={styles.catalogHeader}>\n <button\n type=\"button\"\n className=\"dsh-subscription-disclosure\"\n style={styles.disclosure}\n aria-expanded={modelsOpen}\n aria-controls={`dsh-subscription-models-${status.provider}`}\n onClick={toggleModelsOpen}\n >\n <span style={styles.chevron} aria-hidden=\"true\">{modelsOpen ? '▾' : '▸'}</span>\n <span style={styles.catalogTitle}>{t('modelsTitle')}</span>\n </button>\n <span style={styles.catalogMeta}>\n {t('modelsCount', { count: models.length })}\n {' · '}\n {liveCatalog ? t('modelsLive') : t('modelsBuiltIn')}\n {hiddenCount === 0 ? '' : ` ${t('modelsHiddenCount', { count: hiddenCount })}`}\n </span>\n <button\n type=\"button\"\n className=\"dsh-subscription-quiet\"\n style={styles.pushRight}\n disabled={busyAction}\n onClick={props.onRefreshModels}\n >\n {busyAction ? t('refreshing') : t('refresh')}\n </button>\n {/* A model the endpoint serves but this build has never heard of: the\n id is published as a plain route, so a new release is usable at once. */}\n <button\n type=\"button\"\n className=\"dsh-subscription-quiet\"\n disabled={busyAction || !connectable}\n title={t('modelsAddHint')}\n onClick={() => { setAdding((open) => !open) }}\n >\n {t('modelsAdd')}\n </button>\n </div>\n {adding && (\n <form\n style={styles.addRow}\n onSubmit={(event) => {\n event.preventDefault()\n const id = draft.trim()\n if (id.length === 0) return\n props.onAddModel(id)\n setDraft('')\n setAdding(false)\n }}\n >\n <input\n type=\"text\"\n className=\"dsh-subscription-input\"\n style={styles.addInput}\n value={draft}\n autoFocus\n spellCheck={false}\n placeholder={t('modelsAddPlaceholder')}\n aria-label={t('modelsAdd')}\n disabled={busyAction}\n onChange={(event) => { setDraft(event.target.value) }}\n />\n <button type=\"submit\" disabled={busyAction || draft.trim().length === 0}>{t('modelsAddConfirm')}</button>\n <button type=\"button\" className=\"dsh-subscription-quiet\" onClick={() => { setAdding(false); setDraft('') }}>\n {t('cancel')}\n </button>\n </form>\n )}\n {modelsOpen && (\n <div\n id={`dsh-subscription-models-${status.provider}`}\n style={styles.modelList}\n // The whole list is the drop target, so a release between two rows still\n // lands somewhere instead of being cancelled unnoticed.\n onDragOver={(event) => { drag.over(event, 0) }}\n onDrop={drag.dropAt}\n >\n {status.provider === 'auto' && <span style={styles.chainTitle}>{t('modelsAutoChain')}</span>}\n {/* Every provider lists its models the same way: a switch in front,\n the model's own metadata, and a grip to drag it in the order both\n the picker list and Auto's fallback follow. */}\n {models.map((model, index) => (\n <div\n key={model.id}\n data-model-index={index}\n draggable={!busyAction}\n style={{\n ...styles.modelRowWrap,\n ...dragFrom === index ? { opacity: 0.5 } : {},\n // The row a drop would land on, so the gesture is visible while it\n // happens rather than only in its result.\n ...dropTarget === index && dragFrom !== undefined && dragFrom !== index\n ? { boxShadow: 'inset 0 2px 0 0 var(--dsw-alias-brand-primary)' }\n : {},\n }}\n onDragStart={(event) => { drag.start(event, index) }}\n onDragEnd={drag.end}\n >\n {/* The grip is the affordance and the keyboard path; the drag\n itself starts on the row, because a <button> does not start an\n HTML5 drag reliably in Chromium. */}\n <button\n type=\"button\"\n className=\"dsh-subscription-handle\"\n disabled={busyAction}\n title={t('modelsDrag')}\n aria-label={`${t('modelsDrag')} · ${model.name}`}\n onKeyDown={(event) => { drag.key(event, index) }}\n >\n ⠿\n </button>\n <label\n style={model.visible\n ? { ...styles.modelRowGrow, cursor: 'pointer' }\n : { ...styles.modelRowGrow, cursor: 'pointer', color: 'var(--dsw-alias-label-dimmed)' }}\n title={model.visible ? t('modelsHidden') : t('modelsShow')}\n >\n <input\n type=\"checkbox\"\n className=\"dsh-subscription-switch\"\n role=\"switch\"\n checked={model.visible}\n disabled={busyAction}\n aria-label={`${t('modelsShow')} · ${model.name}`}\n onChange={(event) => { props.onSetModelVisible(model.id, event.target.checked) }}\n />\n <span style={styles.modelBody}>\n <span style={styles.modelName}>\n {model.name}\n {model.source === undefined ? null : <span style={styles.badge}>{model.source}</span>}\n </span>\n <span style={styles.modelMeta}>\n {model.id}\n {' · '}\n {t('modelsContext', { tokens: formatTokens(model.contextWindow) })}\n {model.defaultEffort === undefined ? '' : ` · ${model.defaultEffort}`}\n {model.inputModalities === undefined || model.inputModalities.length === 0\n ? ''\n : ` · ${model.inputModalities.join('/')}`}\n </span>\n </span>\n </label>\n {/* A hand-added id can be removed again, so a typo does not linger. */}\n {model.userAdded === true && (\n <button\n type=\"button\"\n className=\"dsh-subscription-quiet\"\n style={styles.removeButton}\n disabled={busyAction}\n title={t('modelsRemove')}\n aria-label={`${t('modelsRemove')} · ${model.name}`}\n onClick={() => { props.onRemoveModel(model.id) }}\n >\n ×\n </button>\n )}\n </div>\n ))}\n {models.length === 0 && (\n <p style={styles.statusLine}>\n {status.provider === 'auto' ? t('modelsAutoChainEmpty') : t('modelsEmpty')}\n </p>\n )}\n </div>\n )}\n </div>\n )}\n\n <UsageBlock\n usage={status.usage ?? []}\n {...status.usageError === undefined ? {} : { error: status.usageError }}\n t={t}\n busyAction={busyAction}\n onRefresh={props.onRefreshUsage}\n />\n\n {/* `auto` owns no account, so it offers no connect, cancel or manage. */}\n {connectable && (\n <div style={styles.actions}>\n {!busy && (\n <button type=\"button\" disabled={busyAction} onClick={props.onLogin}>\n {accounts.length === 0 ? t('connect') : t('addAccount')}\n </button>\n )}\n {!busy && (\n <button type=\"button\" disabled={busyAction} onClick={() => { setManageOpen(true) }}>\n {t('manage')}\n </button>\n )}\n {busy && (\n <button type=\"button\" disabled={busyAction} onClick={props.onCancel}>\n {t('cancel')}\n </button>\n )}\n </div>\n )}\n {connectable && accounts.length > 0 && <p style={styles.statusLine}>{t('hint')}</p>}\n\n {busy && status.pending !== undefined && (\n <PendingLogin prompt={status.pending} t={t} copied={copied} onCopy={(code) => { void copyCode(code) }} />\n )}\n </div>\n {/* The dialog stays outside the collapsible body: a hidden ancestor would\n keep it out of the top layer even after `showModal()`. */}\n <dialog\n ref={dialog}\n className=\"dsh-subscription-manager\"\n aria-labelledby={`dsh-subscription-manage-${status.provider}`}\n // A closed `<dialog>` is hidden by the user-agent stylesheet, and an\n // inline `display` would override that rule — so the open state has to\n // be restated here rather than left to `showModal()` alone.\n style={manageOpen ? styles.modal : { ...styles.modal, display: 'none' }}\n onClose={() => { setManageOpen(false) }}\n onCancel={() => { setManageOpen(false) }}\n >\n <div style={styles.modalHeader}>\n <span style={styles.modalTitle} id={`dsh-subscription-manage-${status.provider}`}>\n {t('manageTitle', { provider: status.displayName })}\n </span>\n </div>\n\n <span style={styles.statusLine}>{t('manageAccounts')}</span>\n {accounts.map((account) => (\n <div key={account.account} style={styles.accountRow}>\n <div style={styles.accountHeader}>\n <span style={styles.accountName}>{account.label}</span>\n {account.isDefault && <span style={styles.badge}>{t('defaultBadge')}</span>}\n </div>\n <div style={styles.actions}>\n <button\n type=\"button\"\n \n disabled={busyAction}\n onClick={() => { props.onRefreshToken(account.account) }}\n >\n {t('refreshToken')}\n </button>\n {!account.isDefault && (\n <button\n type=\"button\"\n \n disabled={busyAction}\n onClick={() => { props.onSetDefault(account.account) }}\n >\n {t('makeDefault')}\n </button>\n )}\n <button\n type=\"button\"\n \n disabled={busyAction}\n onClick={() => { props.onLogout(account.account) }}\n >\n {t('logout')}\n </button>\n </div>\n </div>\n ))}\n {accounts.length === 0 && <p style={styles.statusLine}>{t('statusSignedOut')}</p>}\n\n <div style={styles.divider} />\n <div style={styles.actions}>\n <button type=\"button\" disabled={busyAction} onClick={props.onReload}>\n {t('reload')}\n </button>\n </div>\n <p style={styles.statusLine}>{t('reloadHint')}</p>\n\n <div style={styles.divider} />\n <div style={styles.details}>\n {status.details.map((row) => (\n <div key={row.key} style={styles.detailRow}>\n <span>{detailLabel(t, row)}</span>\n <span style={styles.detailValue}>{row.value}</span>\n </div>\n ))}\n {status.discovered !== undefined && status.discovered.length > 0 && (\n <div style={styles.detailRow}>\n <span>{t('modelsLive')}</span>\n <span style={styles.detailValue}>{status.discovered.join(', ')}</span>\n </div>\n )}\n </div>\n\n <div className=\"dsh-subscription-dialog-actions\" style={styles.modalActions}>\n <button type=\"button\" onClick={() => { setManageOpen(false) }}>{t('close')}</button>\n </div>\n </dialog>\n </div>\n )\n}\n\n/**\n * The Subscriptions settings section.\n * @param props - section props (inject face plus the shell's `close`).\n * @returns the section content.\n */\nexport function SubscriptionsSection(props: SubscriptionsSectionProps) {\n const { call } = props\n const t = props.t ?? fallbackTranslate\n const [status, setStatus] = useState<SubscriptionStatus | undefined>(undefined)\n const [error, setError] = useState<string | undefined>(undefined)\n const [messages, setMessages] = useState<Record<string, CardMessage>>({})\n const [busyAction, setBusyAction] = useState(false)\n const mounted = useRef(true)\n const timer = useRef<ReturnType<typeof setInterval> | undefined>(undefined)\n\n /** Fetch the current state; a failure is shown rather than swallowed. */\n const refresh = useCallback(async (): Promise<void> => {\n if (call === undefined) return\n try {\n const next = await call('status', undefined)\n if (!mounted.current) return\n setStatus(next)\n setError(undefined)\n } catch (caught: unknown) {\n if (mounted.current) setError(t('actionFailed', { message: messageOf(caught) }))\n }\n }, [call, t])\n\n const stopPolling = useCallback((): void => {\n if (timer.current !== undefined) {\n clearInterval(timer.current)\n timer.current = undefined\n }\n }, [])\n\n const startPolling = useCallback((): void => {\n if (timer.current !== undefined) return\n // The node half clears `busy` once the attempt settles; the effect below\n // observes that and stops this interval.\n timer.current = setInterval(() => { void refresh() }, POLL_INTERVAL_MS)\n }, [refresh])\n\n useEffect(() => {\n mounted.current = true\n void refresh()\n return () => {\n mounted.current = false\n stopPolling()\n }\n }, [refresh, stopPolling])\n\n /** Every route worth reading, as of the latest render, for the usage poll. */\n const signedInRef = useRef<readonly string[]>([])\n useEffect(() => {\n signedInRef.current = usageTargets(status?.providers ?? [])\n })\n\n /**\n * Read each named route's plan and allowance, applying every answer.\n *\n * A read is not forced: the node half reuses a figure only for a few seconds and\n * never reuses a substitute, so a steady interval refreshes the figures while\n * collapsing bursts into one request per route.\n */\n const readUsage = useCallback(async (providers: readonly string[]): Promise<void> => {\n if (call === undefined) return\n for (const provider of providers) {\n try {\n const result = await call('usage', { provider })\n if (!mounted.current) return\n setStatus(result.status)\n } catch {\n // An unreadable quota is already surfaced inside the card by the status it\n // carries; the page itself stays usable.\n }\n }\n }, [call])\n\n // The figures are read as soon as the first status lands...\n const usageRequested = useRef(false)\n useEffect(() => {\n if (call === undefined || status === undefined || usageRequested.current) return\n const signedIn = usageTargets(status.providers)\n if (signedIn.length === 0) return\n usageRequested.current = true\n void readUsage(signedIn)\n }, [call, status, readUsage])\n\n // ...and then kept current while the page is open. Allowances move as the account\n // is used, so a figure that is only read on mount reports the state of a page that\n // may have been open for hours.\n useEffect(() => {\n if (call === undefined) return\n const tick = (): void => {\n // Nothing is refreshed for a page nobody is looking at.\n if (globalThis.document?.visibilityState === 'hidden') return\n void readUsage(signedInRef.current)\n }\n const interval = setInterval(tick, USAGE_POLL_INTERVAL_MS)\n return () => { clearInterval(interval) }\n }, [call, readUsage])\n\n // Poll only while some provider is waiting for browser approval.\n const anyBusy = status?.providers.some((provider) => provider.busy) === true\n useEffect(() => {\n if (anyBusy) startPolling()\n else stopPolling()\n }, [anyBusy, startPolling, stopPolling])\n\n /** Run one action for one provider, reporting its own failure. */\n const act = useCallback(async (provider: string, run: () => Promise<void>): Promise<void> => {\n setBusyAction(true)\n setMessages((current) => {\n const next = { ...current }\n delete next[provider]\n return next\n })\n try {\n await run()\n } catch (caught: unknown) {\n if (mounted.current) {\n setMessages((current) => ({ ...current, [provider]: { tone: 'error', text: t('actionFailed', { message: messageOf(caught) }) } }))\n }\n } finally {\n if (mounted.current) setBusyAction(false)\n }\n }, [t])\n\n const login = useCallback((provider: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const started = await call('login', { provider })\n if (!mounted.current) return\n setStatus(started.status)\n // A loopback flow wants the browser opened; a blocked popup still leaves\n // the link in the card, which is the fallback the user already has.\n if (started.prompt.mode === 'open-url') {\n window.open(started.prompt.url, '_blank', 'noopener')\n }\n })\n }, [act, call])\n\n const cancel = useCallback((provider: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('cancel', { provider })\n if (mounted.current) setStatus(next)\n })\n }, [act, call])\n\n const logout = useCallback((provider: string, account: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('logout', { provider, account })\n if (mounted.current) setStatus(next)\n })\n }, [act, call])\n\n const setDefault = useCallback((provider: string, account: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('setDefault', { provider, account })\n if (mounted.current) setStatus(next)\n })\n }, [act, call])\n\n const refreshToken = useCallback((provider: string, account: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const result = await call('refresh', { provider, account })\n if (!mounted.current) return\n setStatus(result.status)\n setMessages((current) => ({\n ...current,\n [provider]: { tone: 'success', text: t('tokenRefreshed', { time: formatTime(result.expiresAt) }) },\n }))\n })\n }, [act, call, t])\n\n const refreshModels = useCallback((provider: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('discover', { provider, force: true })\n if (!mounted.current) return\n setStatus(next)\n setMessages((current) => ({ ...current, [provider]: { tone: 'success', text: t('modelsRefreshed') } }))\n })\n }, [act, call, t])\n\n const setModelVisible = useCallback((provider: string, id: string, visible: boolean): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('setModelVisible', { provider, id, visible })\n if (mounted.current) setStatus(next)\n })\n }, [act, call])\n\n const refreshUsage = useCallback((provider: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const result = await call('usage', { provider, force: true })\n if (mounted.current) setStatus(result.status)\n })\n }, [act, call])\n\n const setModelOrder = useCallback((provider: string, ids: readonly string[]): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('setModelOrder', { provider, ids: [...ids] })\n if (mounted.current) setStatus(next)\n })\n }, [act, call])\n\n const addModel = useCallback((provider: string, id: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('addModel', { provider, id })\n if (mounted.current) setStatus(next)\n })\n }, [act, call])\n\n const removeModel = useCallback((provider: string, id: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('removeModel', { provider, id })\n if (mounted.current) setStatus(next)\n })\n }, [act, call])\n\n const reload = useCallback((provider: string): void => {\n if (call === undefined) return\n void act(provider, async () => {\n const next = await call('reload', undefined)\n if (mounted.current) setStatus(next)\n })\n }, [act, call])\n\n if (call === undefined) return <p style={styles.intro}>{t('unavailable')}</p>\n\n return (\n <div className=\"dsh-subscriptions-settings\" style={styles.section}>\n <style>{subscriptionsCss}</style>\n <p style={styles.intro}>{t('intro')}</p>\n {error !== undefined && <p className=\"dsh-subscription-error\">{error}</p>}\n {status === undefined && <p style={styles.statusLine}>{t('loading')}</p>}\n {status?.providers.map((provider) => (\n <ProviderCard\n key={provider.provider}\n status={provider}\n t={t}\n busyAction={busyAction}\n {...messages[provider.provider] === undefined ? {} : { message: messages[provider.provider] as CardMessage }}\n onLogin={() => { login(provider.provider) }}\n onCancel={() => { cancel(provider.provider) }}\n onLogout={(account) => { logout(provider.provider, account) }}\n onSetDefault={(account) => { setDefault(provider.provider, account) }}\n onRefreshToken={(account) => { refreshToken(provider.provider, account) }}\n onRefreshModels={() => { refreshModels(provider.provider) }}\n onSetModelVisible={(id, visible) => { setModelVisible(provider.provider, id, visible) }}\n onSetModelOrder={(ids) => { setModelOrder(provider.provider, ids) }}\n onAddModel={(id) => { addModel(provider.provider, id) }}\n onRemoveModel={(id) => { removeModel(provider.provider, id) }}\n onRefreshUsage={() => { refreshUsage(provider.provider) }}\n onReload={() => { reload(provider.provider) }}\n />\n ))}\n </div>\n )\n}\n","/**\n * Subscriptions settings page, browser half.\n *\n * Registers one `settings.section` entry titled 订阅 / Subscriptions. Every\n * connection fact arrives through the node half's `subscription-oauth` RPC\n * endpoints, so this half holds no credential state of its own, and the copy\n * rides the client locale service under the `settings.subscriptions` namespace.\n *\n * @module dsh-plugin-subscription-oauth/client\n */\n\n// Type-only: pulls the `ctx.slots` Context merge and the `settings.section`\n// SlotMap entry. These imports are erased; no runtime binding is taken.\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\nimport type { Context as ClientContext } from '@deepseek-ai/cordis'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client'\n\nimport { SubscriptionsSection } from './SubscriptionsSection.js'\nimport type { SubscriptionsSectionInjected } from './SubscriptionsSection.js'\nimport { createSubscriptionsCall } from './subscriptions-rpc.js'\nimport { en, zh } from './locales.js'\nimport type { SubscriptionsKey } from './locales.js'\n\nexport type { SubscriptionsSectionInjected, SubscriptionsSectionProps } from './SubscriptionsSection.js'\nexport type { SubscriptionsKey } from './locales.js'\nexport { createSubscriptionsCall, SubscriptionsRpcError } from './subscriptions-rpc.js'\nexport type { SubscriptionsCall } from './subscriptions-rpc.js'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** The Subscriptions settings page copy. */\n 'settings.subscriptions': SubscriptionsKey\n }\n}\n\n/** Dictionary namespace owned by this plugin. */\nconst NS = 'settings.subscriptions'\n\n/** Settings nav position: after the shipped sections, before plugin tooling. */\nconst SECTION_ORDER = 92\n\n/**\n * Required services: `slots` carries the registration seat, `connection` the\n * RPC caller, and `locale` the copy dictionaries.\n */\nexport const inject = ['slots', 'connection', 'locale']\n\n/**\n * Register the Subscriptions settings section.\n *\n * Registration goes through `slots.inject`, so the shell's own apply order\n * relative to this one is unconstrained.\n *\n * @param ctx - client root context.\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-plugin-subscription-oauth: copy dictionaries')\n // The shell's Context merge types `connection` as the host handle; in the\n // browser the same key holds the full client ConnectionHandle.\n const connection = ctx.get('connection') as unknown as ConnectionHandle\n const t = ctx.locale.bind(NS) as SubscriptionsSectionInjected['t']\n const call = createSubscriptionsCall(connection.rpc)\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'subscriptions',\n order: SECTION_ORDER,\n // A thunk re-evaluated per read, so the nav label follows the active locale.\n label: () => t('nav'),\n inject: (): SubscriptionsSectionInjected => ({ call, t }),\n }, SubscriptionsSection))\n}\n"],"mappings":"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"}
package/lib/index.js CHANGED
@@ -137,6 +137,28 @@ export function apply(ctx, config = {}) {
137
137
  const auto = createAutoProvider({
138
138
  preferences,
139
139
  registry,
140
+ // Auto spans every provider the harness serves — the built-in DeepSeek
141
+ // routes and any provider the user adds included — so it is enumerated
142
+ // through the runtime rather than through this plugin's own registry. The
143
+ // runtime is read per call, so a provider that registers later (another
144
+ // plugin, a route the user configures) joins the chain without a restart.
145
+ host: {
146
+ providers: () => ctx.llm.listProviders().map((entry) => ({ id: entry.id, name: entry.name })),
147
+ models: (provider) => ctx.llm.listModels(provider),
148
+ resolve: async (provider, model) => {
149
+ const resolved = await ctx.llm.resolveModelInfo(provider, model);
150
+ return {
151
+ ...resolved.context?.contextWindow === undefined ? {} : { contextWindow: resolved.context.contextWindow },
152
+ ...resolved.reasoning === undefined
153
+ ? {}
154
+ : { reasoningEfforts: resolved.reasoning.efforts.map((effort) => String(effort.id)) },
155
+ ...resolved.inputModalities === undefined ? {} : { inputModalities: resolved.inputModalities },
156
+ };
157
+ },
158
+ // Dispatch through the harness, which selects the adapter that owns the
159
+ // provider. Auto's own adapter is never asked to guess.
160
+ stream: (options) => ctx.llm.stream(options),
161
+ },
140
162
  onCatalogChanged: () => { reannounce(AUTO_PROVIDER_ID); },
141
163
  onWarn,
142
164
  });
@@ -349,6 +349,10 @@ export class AntigravityAdapter extends LlmAdapter {
349
349
  return undefined;
350
350
  }
351
351
  async listModels(_provider) {
352
+ // Nothing to offer without a signed-in account: a model that cannot be called
353
+ // must not appear in the picker, and Auto must not try it.
354
+ if (await this.options.tokens.session() === undefined)
355
+ return [];
352
356
  await this.syncUserModels();
353
357
  const hidden = await this.hidden();
354
358
  return resolveCatalog(this.catalog(), [], hidden).entries.map((entry) => ({
@@ -44,12 +44,50 @@ export declare function parseAutoKey(key: string): {
44
44
  provider: string;
45
45
  model: string;
46
46
  } | undefined;
47
+ /**
48
+ * The harness's own provider roster.
49
+ *
50
+ * `auto` spans everything the session can pick, not only the routes this plugin
51
+ * registered: the built-in DeepSeek providers and any provider the user added are
52
+ * models Auto is expected to fall back to. The runtime offers exactly the three
53
+ * facts that needs — which providers exist, what each one serves, and the call
54
+ * entry point that selects the owning adapter — so it is injected as a port rather
55
+ * than reached for directly.
56
+ */
57
+ export interface AutoHost {
58
+ /** Every provider the harness serves, in picker order. */
59
+ providers(): readonly {
60
+ id: string;
61
+ name: string;
62
+ }[];
63
+ /** Models one provider currently offers. */
64
+ models(provider: string): Promise<readonly {
65
+ id: string;
66
+ name?: string;
67
+ }[]>;
68
+ /** One model's context window, reasoning ladder and input kinds, when stated. */
69
+ resolve(provider: string, model: string): Promise<{
70
+ contextWindow?: number;
71
+ reasoningEfforts?: readonly string[];
72
+ inputModalities?: readonly ModelModality[];
73
+ }>;
74
+ /** Run one call, letting the harness select that provider's adapter. */
75
+ stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
76
+ }
47
77
  /** Everything the `auto` route needs. */
48
78
  export interface AutoProviderOptions {
49
79
  /** Shared model preferences, where `auto`'s own order and switches live. */
50
80
  preferences: ModelPreferences;
51
81
  /** Every other route, read per call so registration order stays free. */
52
82
  registry: SubscriptionRegistry;
83
+ /**
84
+ * The harness's own roster, when the route is mounted in a running harness.
85
+ *
86
+ * Present, this is what `auto` enumerates — it already contains this plugin's
87
+ * routes as well, so enumerating both would list every model twice. Absent (a
88
+ * unit test with no runtime), the registry alone is used.
89
+ */
90
+ host?: AutoHost;
53
91
  onWarn?: (message: string) => void;
54
92
  /** Invoked when the list changed, so the picker re-queries. */
55
93
  onCatalogChanged?: () => void;
@@ -86,8 +124,16 @@ export declare class AutoAdapter extends LlmAdapter {
86
124
  * instead would persist a reorder and then redraw the old arrangement.
87
125
  */
88
126
  private arranged;
89
- /** Every model the other routes currently offer the picker. */
127
+ /**
128
+ * Every model the picker currently offers, from every provider.
129
+ *
130
+ * In a running harness that means the whole roster, which is where the built-in
131
+ * DeepSeek providers and any provider the user added come from. Without a host —
132
+ * a unit test — this plugin's own routes are the roster.
133
+ */
90
134
  private candidates;
135
+ /** The same list built from this plugin's own routes, for a host-less build. */
136
+ private registryCandidates;
91
137
  /** The Settings card's rows: every candidate, switched-out ones included. */
92
138
  summaries(): Promise<ModelSummary[]>;
93
139
  }
@@ -109,22 +109,24 @@ export class AutoAdapter extends LlmAdapter {
109
109
  for (const [index, candidate] of chain.entries()) {
110
110
  const route = this.options.registry.get(candidate.provider);
111
111
  const next = chain[index + 1];
112
- if (route === undefined) {
113
- lastError = new LlmError(`Auto refers to the unknown provider ${candidate.provider}`, EMPTY_RESPONSE_CODE);
114
- if (next === undefined)
115
- break;
116
- continue;
117
- }
118
112
  // A chain may advance only while nothing has been produced: once a chunk
119
113
  // has been yielded the turn belongs to that model, and a later failure is
120
114
  // reported rather than papered over by a second model's text.
121
115
  let produced = false;
122
116
  try {
123
- for await (const chunk of route.adapter.stream({
124
- ...options,
125
- provider: candidate.provider,
126
- model: candidate.model,
127
- })) {
117
+ // A route this plugin owns streams through its own adapter. Anything else
118
+ // — a built-in provider, a provider the user added — goes through the
119
+ // harness, which is the only thing that knows which adapter serves it.
120
+ const source = route !== undefined
121
+ ? route.adapter.stream({ ...options, provider: candidate.provider, model: candidate.model })
122
+ : this.options.host?.stream({ ...options, provider: candidate.provider, model: candidate.model });
123
+ if (source === undefined) {
124
+ lastError = new LlmError(`Auto refers to the unknown provider ${candidate.provider}`, EMPTY_RESPONSE_CODE);
125
+ if (next === undefined)
126
+ break;
127
+ continue;
128
+ }
129
+ for await (const chunk of source) {
128
130
  produced = true;
129
131
  yield chunk;
130
132
  }
@@ -180,8 +182,46 @@ export class AutoAdapter extends LlmAdapter {
180
182
  }
181
183
  return out;
182
184
  }
183
- /** Every model the other routes currently offer the picker. */
185
+ /**
186
+ * Every model the picker currently offers, from every provider.
187
+ *
188
+ * In a running harness that means the whole roster, which is where the built-in
189
+ * DeepSeek providers and any provider the user added come from. Without a host —
190
+ * a unit test — this plugin's own routes are the roster.
191
+ */
184
192
  async candidates() {
193
+ const host = this.options.host;
194
+ if (host === undefined)
195
+ return await this.registryCandidates();
196
+ const out = [];
197
+ for (const entry of host.providers()) {
198
+ // Auto cannot chain to itself; that would be a loop, not a fallback.
199
+ if (entry.id === AUTO_PROVIDER_ID)
200
+ continue;
201
+ try {
202
+ for (const model of await host.models(entry.id)) {
203
+ const resolved = await host.resolve(entry.id, model.id);
204
+ out.push({
205
+ provider: entry.id,
206
+ model: model.id,
207
+ name: model.name ?? model.id,
208
+ contextWindow: resolved.contextWindow ?? AUTO_FALLBACK_CONTEXT_WINDOW,
209
+ ...resolved.reasoningEfforts === undefined || resolved.reasoningEfforts.length === 0
210
+ ? {}
211
+ : { reasoningEfforts: resolved.reasoningEfforts },
212
+ ...resolved.inputModalities === undefined ? {} : { inputModalities: resolved.inputModalities },
213
+ });
214
+ }
215
+ }
216
+ catch (error) {
217
+ // One unreachable provider must not empty Auto: the others still stand in.
218
+ this.options.onWarn?.(`auto could not read ${entry.id}'s models (${errorChain(error)})`);
219
+ }
220
+ }
221
+ return out;
222
+ }
223
+ /** The same list built from this plugin's own routes, for a host-less build. */
224
+ async registryCandidates() {
185
225
  const out = [];
186
226
  for (const route of this.options.registry.all()) {
187
227
  if (route.id === AUTO_PROVIDER_ID)
@@ -233,6 +233,10 @@ export class GlmAdapter extends LlmAdapter {
233
233
  return undefined;
234
234
  }
235
235
  async listModels(_provider) {
236
+ // Nothing to offer without a signed-in account: a model that cannot be called
237
+ // must not appear in the picker, and Auto must not try it.
238
+ if (await this.options.tokens.session() === undefined)
239
+ return [];
236
240
  const hidden = await this.hidden();
237
241
  return resolveCatalog(this.catalog(), [], hidden).entries.map((entry) => ({
238
242
  provider: GLM_PROVIDER_ID,
@@ -276,6 +276,10 @@ export class GrokAdapter extends LlmAdapter {
276
276
  return undefined;
277
277
  }
278
278
  async listModels(_provider) {
279
+ // Nothing to offer without a signed-in account: a model that cannot be called
280
+ // must not appear in the picker, and Auto must not try it.
281
+ if (await this.options.tokens.session() === undefined)
282
+ return [];
279
283
  await this.syncUserModels();
280
284
  const hidden = await this.hidden();
281
285
  return resolveCatalog(this.catalog(), [], hidden).entries.map((entry) => ({
@@ -143,6 +143,10 @@ export class KimiAdapter extends LlmAdapter {
143
143
  return undefined;
144
144
  }
145
145
  async listModels(_provider) {
146
+ // Nothing to offer without a signed-in account: a model that cannot be called
147
+ // must not appear in the picker, and Auto must not try it.
148
+ if (await this.options.tokens.session() === undefined)
149
+ return [];
146
150
  await this.syncUserModels();
147
151
  const hidden = await this.hidden();
148
152
  return resolveCatalog(this.catalog(), [], hidden).entries.map((entry) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-subscription-oauth",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Use subscription accounts as DeepSeek Harness LLM providers — Kimi Code (OAuth device flow), Grok / X Premium (OIDC + PKCE), Google Antigravity (Google OAuth + Cloud Code Assist) and GLM / BigModel (ZCode's browser sign-in) — with per-model visibility, drag-to-reorder Auto fallback and plan quotas from the web Settings page",
5
5
  "license": "MIT",
6
6
  "author": "EricFL1998",