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 +34 -14
- package/lib/client/SubscriptionsSection.js +31 -3
- package/lib/client/locales.d.ts +6 -0
- package/lib/client/locales.js +6 -0
- package/lib/client.js +30 -2
- package/lib/client.js.map +1 -1
- package/lib/index.js +22 -0
- package/lib/providers/antigravity.js +4 -0
- package/lib/providers/auto.d.ts +47 -1
- package/lib/providers/auto.js +52 -12
- package/lib/providers/glm.js +4 -0
- package/lib/providers/grok.js +4 -0
- package/lib/providers/kimi.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# dsh-plugin-subscription-oauth
|
|
2
2
|
|
|
3
3
|
[](https://dsh-plugin.org/plugins/EricFL1998/dsh-subscription-oauth)
|
|
4
|
+
[](https://www.npmjs.com/package/dsh-plugin-subscription-oauth)
|
|
4
5
|
[](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
|
|
13
|
+
dsh plugin --profile web add dsh-plugin-subscription-oauth
|
|
13
14
|
```
|
|
14
15
|
|
|
15
|
-
The same package
|
|
16
|
-
|
|
17
|
-
|
|
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*
|
|
118
|
-
the
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
|
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
|
|
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
|
|
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 ? '' :
|
|
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.
|
package/lib/client/locales.d.ts
CHANGED
|
@@ -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;
|
package/lib/client/locales.js
CHANGED
|
@@ -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 ? "" :
|
|
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) => ({
|
package/lib/providers/auto.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
}
|
package/lib/providers/auto.js
CHANGED
|
@@ -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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
/**
|
|
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)
|
package/lib/providers/glm.js
CHANGED
|
@@ -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,
|
package/lib/providers/grok.js
CHANGED
|
@@ -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) => ({
|
package/lib/providers/kimi.js
CHANGED
|
@@ -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
|
|
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",
|