dsh-subscriptions 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -10
- package/lib/auth/rpc.d.ts +3 -1
- package/lib/auth/rpc.js +10 -1
- package/lib/client/ExternalUsageCards.d.ts +11 -1
- package/lib/client/ExternalUsageCards.js +18 -3
- package/lib/client/ProviderAccountManager.js +34 -41
- package/lib/client/ProviderModelEditor.js +5 -9
- package/lib/client/SpeedSelect.js +8 -7
- package/lib/client/SubscriptionUsageBadge.d.ts +19 -2
- package/lib/client/SubscriptionUsageBadge.js +65 -9
- package/lib/client/SubscriptionsSection.d.ts +22 -0
- package/lib/client/SubscriptionsSection.js +128 -38
- package/lib/client/UsageBadgeDisplaySetting.js +6 -10
- package/lib/client/UsageMeter.js +11 -6
- package/lib/client/host-contract.d.ts +1 -1
- package/lib/client/host-contract.js +22 -0
- package/lib/client/locales.d.ts +60 -0
- package/lib/client/locales.js +62 -2
- package/lib/client/provider-settings-styles.d.ts +3 -0
- package/lib/client/provider-settings-styles.js +206 -0
- package/lib/client/reset-action.d.ts +14 -0
- package/lib/client/reset-action.js +15 -0
- package/lib/client/reset-credits-view.d.ts +53 -0
- package/lib/client/reset-credits-view.js +103 -0
- package/lib/client/usage-badge-preferences.d.ts +7 -2
- package/lib/client/usage-badge-preferences.js +3 -2
- package/lib/client/usage-pace.d.ts +14 -1
- package/lib/client/usage-pace.js +14 -1
- package/lib/client.js +1036 -334
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +9 -0
- package/lib/index.js +1120 -602
- package/lib/providers/account-preferences.js +3 -2
- package/lib/providers/catalog-store.d.ts +3 -2
- package/lib/providers/catalog-store.js +57 -26
- package/lib/providers/claude.d.ts +22 -3
- package/lib/providers/claude.js +113 -15
- package/lib/providers/codex.d.ts +15 -2
- package/lib/providers/codex.js +72 -4
- package/lib/providers/common.d.ts +25 -0
- package/lib/providers/common.js +22 -18
- package/lib/providers/copilot.d.ts +2 -1
- package/lib/providers/copilot.js +10 -3
- package/lib/providers/grok.d.ts +2 -2
- package/lib/providers/grok.js +2 -2
- package/lib/providers/minimax-usage.d.ts +22 -1
- package/lib/providers/minimax-usage.js +37 -7
- package/lib/providers/pool-health.d.ts +2 -0
- package/lib/providers/pool-health.js +9 -0
- package/lib/providers/pool-usage.d.ts +13 -1
- package/lib/providers/pool-usage.js +39 -7
- package/lib/providers/pool.d.ts +7 -5
- package/lib/providers/pool.js +89 -12
- package/lib/providers/rate-limit.d.ts +0 -14
- package/lib/providers/rate-limit.js +0 -26
- package/lib/providers/replay.d.ts +4 -0
- package/lib/providers/replay.js +50 -0
- package/lib/providers/reset-redemption.d.ts +51 -0
- package/lib/providers/reset-redemption.js +102 -0
- package/lib/translate/anthropic.d.ts +21 -2
- package/lib/translate/anthropic.js +35 -11
- package/lib/translate/antigravity.js +13 -6
- package/lib/translate/chat-completions.js +1 -1
- package/lib/translate/resolved.d.ts +21 -0
- package/lib/translate/resolved.js +63 -3
- package/lib/translate/responses.js +8 -6
- package/lib/translate/sse.js +8 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,13 +68,15 @@ and tool settings.
|
|
|
68
68
|
|
|
69
69
|
### Quota while you work
|
|
70
70
|
|
|
71
|
-
The composer's stats row gains a **
|
|
71
|
+
The composer's stats row gains a **provider usage** pill showing the
|
|
72
72
|
used percentage and reset window for the provider of the session's current
|
|
73
73
|
model. It shows at most one provider; switching to a non-subscription model
|
|
74
74
|
keeps the most recent subscription selected in the mounted conversation
|
|
75
|
-
view, or stays hidden if there is none.
|
|
75
|
+
view, or stays hidden if there is none. **Always show** instead rotates
|
|
76
|
+
through the providers that report usage every 10 seconds while the current
|
|
77
|
+
model has no quota source, so the pill never disappears.
|
|
76
78
|
|
|
77
|
-

|
|
78
80
|
|
|
79
81
|
Click the pill to expand every provider and account that reports usage —
|
|
80
82
|
the default account is starred, and the current provider is listed first.
|
|
@@ -83,7 +85,54 @@ neither do accounts without a usage window. Antigravity previews only the
|
|
|
83
85
|
current model's windows; the other model windows stay in a closed
|
|
84
86
|
disclosure.
|
|
85
87
|
|
|
86
|
-
|
|
88
|
+
Quota bars include a dark elapsed-time cursor whenever the provider's
|
|
89
|
+
timing places the window in time, including at 0% used and for stale or old
|
|
90
|
+
readings. Compare the colored fill (quota used) with the cursor (time
|
|
91
|
+
elapsed); hover for the percentages and reset countdown. Standard coloring
|
|
92
|
+
turns the fill orange when a fresh reading leads elapsed time by 10 points,
|
|
93
|
+
and Relaxed uses 15; 90% used turns it red and takes priority. Windows
|
|
94
|
+
without known timing omit the cursor, and a stale or old reading keeps its
|
|
95
|
+
cursor without a pace warning. Choose Standard, Relaxed, or Remaining colors
|
|
96
|
+
in **Settings → Subscriptions**.
|
|
97
|
+
|
|
98
|
+

|
|
99
|
+
|
|
100
|
+
#### Codex usage limit resets
|
|
101
|
+
|
|
102
|
+
The Codex account card shows banked full-reset credits and their expirations
|
|
103
|
+
in a closed disclosure. A failed optional lookup reports an error without
|
|
104
|
+
hiding ordinary quota windows. **Use…** selects the earliest-expiring available
|
|
105
|
+
credit and requires a fresh unscoped weekly reading of at least 80%, then an
|
|
106
|
+
explicit confirmation and acknowledgment. Confirmation expires after 60
|
|
107
|
+
seconds; the server rechecks usage and credit availability before submission.
|
|
108
|
+
|
|
109
|
+
One reset consumes one credit and resets weekly and 5-hour limits. This
|
|
110
|
+
interface cannot undo it. An unconfirmed outcome blocks further redemption
|
|
111
|
+
for that account in the current host process. Restarting clears that block,
|
|
112
|
+
but must not be used to bypass it: check credits and limits in Codex before
|
|
113
|
+
retrying, because the first submission may already have spent a credit.
|
|
114
|
+
The private endpoint is covered by offline fakes, not live-account checks.
|
|
115
|
+
See the [manual reset decision](.agents/notes/implemented/provider/2026-10-04-codex-manual-reset.md)
|
|
116
|
+
for the retry boundary and verification limits.
|
|
117
|
+
|
|
118
|
+
#### Claude usage limit resets
|
|
119
|
+
|
|
120
|
+
The Claude account card shows banked limit resets from Anthropic's
|
|
121
|
+
`cedar_ember` program, in the same closed disclosure Codex uses. It lists one
|
|
122
|
+
row per grant, not one per credit, and the summary counts remaining resets by
|
|
123
|
+
summing `resetsLeft` over grants that are neither expired nor paused. Each row
|
|
124
|
+
states whether that grant is available, not yet usable, paused, exhausted, or
|
|
125
|
+
expired, with its remaining count and when it lapses. That time is the grant's
|
|
126
|
+
expiry: it is not a window reset and does not move the elapsed-time cursor.
|
|
127
|
+
|
|
128
|
+
This is **display only**. `claimable` is shown as data on a text row, and the
|
|
129
|
+
page has no control that spends a Claude reset, because the response shape is
|
|
130
|
+
not an Anthropic contract. An ineligible account, a grant-less block, or a
|
|
131
|
+
rejected optional query leaves ordinary session and weekly windows untouched
|
|
132
|
+
and shows no list; a failed lookup shows a fixed sentence rather than the
|
|
133
|
+
provider's text. No live account was used to verify what Anthropic returns. See
|
|
134
|
+
[the evidence and limits](docs/claude-banked-resets.md) and the
|
|
135
|
+
[display decision](.agents/notes/implemented/provider/2026-10-08-claude-banked-reset-display.md).
|
|
87
136
|
|
|
88
137
|
## Install
|
|
89
138
|
|
|
@@ -171,6 +220,33 @@ usage.
|
|
|
171
220
|
The plugin follows DSH network settings. Its former proxy configuration and
|
|
172
221
|
UI have been retired; an old plugin proxy config file is ignored.
|
|
173
222
|
|
|
223
|
+
## Claude prompt-cache TTL
|
|
224
|
+
|
|
225
|
+
Set `claudePromptCacheTtl: '5m'` (default) or `'1h'` in the plugin config and
|
|
226
|
+
restart DSH. This affects Claude only; other providers keep their own caching.
|
|
227
|
+
Five minutes omits the wire `ttl` field, preserving the existing request. One
|
|
228
|
+
hour adds `ttl: "1h"` to the tools+system prefix and up to three conversation
|
|
229
|
+
breakpoints. All marks share one TTL: Anthropic rejects a one-hour mark after
|
|
230
|
+
a five-minute mark. Requests classified with `purpose` as compaction or
|
|
231
|
+
session-title keep five minutes. Unclassified calls, including subagents and
|
|
232
|
+
calls from hosts that do not supply `purpose`, follow the configured TTL.
|
|
233
|
+
The TTL setting does not require a newer host capability or dependency.
|
|
234
|
+
|
|
235
|
+
Cache reads have the same API price for either TTL. One-hour writes are priced
|
|
236
|
+
at 2× base input instead of 1.25×. They may pay off with reusable prefixes and
|
|
237
|
+
5–60-minute pauses; benefits depend on the workload and do not establish
|
|
238
|
+
subscription quota savings. Frequent reuse within five minutes may not offset
|
|
239
|
+
the higher write price. Claude Code requests one hour for its main subscription
|
|
240
|
+
conversation; see
|
|
241
|
+
[Claude Code's prompt-caching notes](https://code.claude.com/docs/en/prompt-caching#which-ttl-each-request-gets).
|
|
242
|
+
|
|
243
|
+
Changing TTL on a cached conversation may rewrite the prefix. Upstream testing
|
|
244
|
+
found that the first one-hour request after five-minute writes rewrote a
|
|
245
|
+
conversation-tail prefix, while a system-only breakpoint could hit. A
|
|
246
|
+
five-minute request can read a one-hour entry; a one-hour-marked read does not
|
|
247
|
+
upgrade an existing five-minute entry's lifetime. These observations are not
|
|
248
|
+
live-account verification of this fork.
|
|
249
|
+
|
|
174
250
|
## Usage coloring
|
|
175
251
|
|
|
176
252
|
**Settings → Subscriptions → Usage coloring** offers one browser-local dropdown:
|
|
@@ -183,7 +259,7 @@ Every preset shows reported usage of 90% or more in red, before considering pace
|
|
|
183
259
|
|
|
184
260
|
A vertical marker shows elapsed time only for an explicit valid start/end interval or a provider-verified fixed duration. A session/weekly label alone does not establish a fixed window; rolling or unknown intervals have no marker. Claude's recognized five-hour/seven-day buckets and Codex's reported quota durations supply fixed-window metadata. Unknown timing falls back to green below 90%, without a marker or yellow warning.
|
|
185
261
|
|
|
186
|
-
|
|
262
|
+
The marker depends on the window's timing alone, not on the reading: it stays at 0% used, for an invalid percentage, and when a refresh failed or the reading is more than five minutes old. Such a reading is not set against the current time, so it gets no yellow pace warning and its tooltip omits the ahead/behind comparison. It retains the last reported green/red color with an accessible stale label; refresh to confirm it. Once the reset time has passed the window has no position and the marker disappears. Invalid percentages show an unavailable state without a fabricated fill. Tooltips explain remaining quota, time progress and reset countdown where available. See the [decision record](.agents/notes/implemented/architecture/2026-10-01-usage-pace-colors.md) and the [cursor note](.agents/notes/implemented/architecture/2026-10-06-usage-cursor-timing-only.md) for rationale and verification limits.
|
|
187
263
|
|
|
188
264
|
### MiniMax subscription usage
|
|
189
265
|
|
|
@@ -193,11 +269,17 @@ The plugin reuses those credential references; no separate subscription ref is
|
|
|
193
269
|
required. Quota reads require subscription keys, not `sk-api-*` pay-as-you-go keys. The plugin reads the
|
|
194
270
|
region's `/v1/token_plan/remains` endpoint and displays finite per-model quotas
|
|
195
271
|
with API-provided start/end times. It does not add model routing or OAuth login.
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
272
|
+
Rows are labelled with the model name, and the plugin shows the windows the API
|
|
273
|
+
returns for each model. MiniMax documents a five-hour and a weekly window for
|
|
274
|
+
non-video models and only a weekly one for video. Standard (non-video) models
|
|
275
|
+
on time-based plans report an explicit remaining percentage beside zero counts,
|
|
276
|
+
and that percentage is enough for a row. Unlimited, boosted-above-100% and
|
|
277
|
+
unsupported quotas are omitted (a status-3 row also covers a model the plan
|
|
278
|
+
does not include); if none remain, the card reports unavailable rather than
|
|
279
|
+
inventing a percentage. Explicit remaining percentages take priority; older
|
|
280
|
+
responses follow the official CLI's remaining-count compatibility convention. No
|
|
281
|
+
live-account response was used in verification, so a missing model may still
|
|
282
|
+
come from the account or the plan rather than from this plugin.
|
|
201
283
|
|
|
202
284
|
## Development
|
|
203
285
|
|
package/lib/auth/rpc.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export declare const SUBSCRIPTIONS_AUTH_PREFIX = "subscriptions-auth.";
|
|
|
23
23
|
* Every endpoint {@link dispatch} answers; each gets one exact Fetch route.
|
|
24
24
|
* Kept in one place so the route table and the switch cannot drift apart.
|
|
25
25
|
*/
|
|
26
|
-
export declare const SUBSCRIPTIONS_AUTH_ENDPOINTS: readonly ["providerSettings", "setProviderSettings", "status", "login", "manual", "cancel", "logout", "setDefault", "usage", "externalStatus", "externalUsage", "cursorStatus", "cursorLogin", "cursorCancel", "cursorLogout", "cursorUsage", "cursorModels", "cursorSettings", "cursorSetSettings", "image", "video", "speed", "setSpeed", "modelDefaults", "setModelDefault"];
|
|
26
|
+
export declare const SUBSCRIPTIONS_AUTH_ENDPOINTS: readonly ["providerSettings", "setProviderSettings", "status", "login", "manual", "cancel", "logout", "setDefault", "usage", "externalStatus", "externalUsage", "cursorStatus", "cursorLogin", "cursorCancel", "cursorLogout", "cursorUsage", "cursorModels", "cursorSettings", "cursorSetSettings", "image", "video", "prepareReset", "consumeReset", "speed", "setSpeed", "modelDefaults", "setModelDefault"];
|
|
27
27
|
/** Decoded image bytes returned by the `image` endpoint. */
|
|
28
28
|
export interface ImageBytesResult {
|
|
29
29
|
mediaType: string;
|
|
@@ -121,6 +121,8 @@ export interface ModelDefaultsController {
|
|
|
121
121
|
}
|
|
122
122
|
/** Provider-agnostic auth operations the RPC handler delegates to. */
|
|
123
123
|
export interface AuthController {
|
|
124
|
+
prepareReset?(account: string, signal: AbortSignal): Promise<unknown>;
|
|
125
|
+
consumeReset?(account: string, ticket: string, signal: AbortSignal): Promise<void>;
|
|
124
126
|
/** Current status of one provider. */
|
|
125
127
|
status(provider: ProviderId): Promise<ProviderStatus>;
|
|
126
128
|
/**
|
package/lib/auth/rpc.js
CHANGED
|
@@ -25,7 +25,7 @@ export const SUBSCRIPTIONS_AUTH_ENDPOINTS = [
|
|
|
25
25
|
'externalStatus', 'externalUsage',
|
|
26
26
|
'cursorStatus', 'cursorLogin', 'cursorCancel', 'cursorLogout', 'cursorUsage', 'cursorModels',
|
|
27
27
|
'cursorSettings', 'cursorSetSettings',
|
|
28
|
-
'image', 'video',
|
|
28
|
+
'image', 'video', 'prepareReset', 'consumeReset',
|
|
29
29
|
'speed', 'setSpeed',
|
|
30
30
|
'modelDefaults', 'setModelDefault',
|
|
31
31
|
];
|
|
@@ -322,6 +322,15 @@ async function dispatch(controller, speed, modelDefaults, endpoint, payload, sig
|
|
|
322
322
|
throw new BadRequest('payload must be an object');
|
|
323
323
|
await cursor.setSettings(payload.settings);
|
|
324
324
|
return ok({ ok: true });
|
|
325
|
+
case 'prepareReset':
|
|
326
|
+
if (!controller.prepareReset)
|
|
327
|
+
throw new BadRequest('Reset redemption unavailable');
|
|
328
|
+
return ok(await controller.prepareReset(readString(payload, 'account'), signal));
|
|
329
|
+
case 'consumeReset':
|
|
330
|
+
if (!controller.consumeReset)
|
|
331
|
+
throw new BadRequest('Reset redemption unavailable');
|
|
332
|
+
await controller.consumeReset(readString(payload, 'account'), readString(payload, 'ticket'), signal);
|
|
333
|
+
return ok({ ok: true });
|
|
325
334
|
case 'image':
|
|
326
335
|
return ok(await controller.readImage(readImageRef(payload), signal));
|
|
327
336
|
case 'video':
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client';
|
|
2
|
-
import type { SubscriptionsSectionInjected } from './SubscriptionsSection.js';
|
|
2
|
+
import type { SubscriptionsSectionInjected, UsageWindow } from './SubscriptionsSection.js';
|
|
3
3
|
type Translate = SubscriptionsSectionInjected['t'];
|
|
4
|
+
/**
|
|
5
|
+
* Row label of one window on an external usage card. A session or weekly
|
|
6
|
+
* window appends the model it belongs to when the provider names one (MiniMax
|
|
7
|
+
* reports a window per model), so two models' weekly rows stay distinguishable.
|
|
8
|
+
* The monthly pool is a scope on an `other` window, not a model.
|
|
9
|
+
* @param t - section translate.
|
|
10
|
+
* @param window - the reported window.
|
|
11
|
+
* @returns the localized label, e.g. "Weekly · general".
|
|
12
|
+
*/
|
|
13
|
+
export declare function externalUsageWindowLabel(t: Translate, window: UsageWindow): string;
|
|
4
14
|
/** Quota cards for API-key providers already available through DSH itself. */
|
|
5
15
|
export declare function ExternalUsageCards({ rpc, t }: {
|
|
6
16
|
rpc: ConnectionHandle['rpc'];
|
|
@@ -14,6 +14,23 @@ const SOURCES = [
|
|
|
14
14
|
function errorText(error) {
|
|
15
15
|
return error instanceof Error ? error.message : String(error);
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Row label of one window on an external usage card. A session or weekly
|
|
19
|
+
* window appends the model it belongs to when the provider names one (MiniMax
|
|
20
|
+
* reports a window per model), so two models' weekly rows stay distinguishable.
|
|
21
|
+
* The monthly pool is a scope on an `other` window, not a model.
|
|
22
|
+
* @param t - section translate.
|
|
23
|
+
* @param window - the reported window.
|
|
24
|
+
* @returns the localized label, e.g. "Weekly · general".
|
|
25
|
+
*/
|
|
26
|
+
export function externalUsageWindowLabel(t, window) {
|
|
27
|
+
const named = window.scope !== undefined && window.scope !== '';
|
|
28
|
+
if (window.kind === 'other') {
|
|
29
|
+
return window.scope === 'Monthly' ? t('usageMonthly') : named ? window.scope : t('usageWindow');
|
|
30
|
+
}
|
|
31
|
+
const base = window.kind === 'session' ? t('usageSession') : t('usageWeekly');
|
|
32
|
+
return named ? `${base} · ${window.scope}` : base;
|
|
33
|
+
}
|
|
17
34
|
/** Quota cards for API-key providers already available through DSH itself. */
|
|
18
35
|
export function ExternalUsageCards({ rpc, t }) {
|
|
19
36
|
const [status, setStatus] = useState();
|
|
@@ -62,9 +79,7 @@ export function ExternalUsageCards({ rpc, t }) {
|
|
|
62
79
|
? 'var(--dsw-alias-state-success-primary)' : 'var(--dsw-alias-label-dimmed)' } }), _jsx("span", { style: styles.name, children: name })] }), !(status === undefined && statusError !== undefined) && _jsx("p", { style: styles.status, children: status === undefined ? t('checking')
|
|
63
80
|
: configured ? t('externalUsageConnected') : t('externalUsageNotConfigured') }), statusError !== undefined && _jsx("p", { style: styles.error, children: t('externalUsageUnavailable', { message: statusError }) }), status !== undefined && !configured && _jsx("p", { style: styles.status, children: t('externalUsageConfigure', { ref }) }), configured && _jsxs("div", { style: styles.account, children: [_jsxs("div", { style: styles.accountHeader, children: [_jsx("span", { style: styles.defaultStar, title: t('defaultBadge'), children: "\u2605" }), _jsx("span", { style: styles.accountName, children: t('externalUsageApiKey') })] }), _jsxs("div", { style: styles.usage, children: [_jsxs("div", { style: styles.usageHeader, children: [_jsx("span", { style: styles.usageTitle, children: t('usageTitle') }), snapshot?.plan !== undefined && _jsx("span", { style: styles.usagePlan, children: t('usagePlan', { plan: snapshot.plan }) }), _jsx("button", { type: "button", style: styles.usageRefresh, disabled: loading[id] === true, onClick: () => { void refresh(id); }, children: t('usageRefresh') })] }), loading[id] === true && snapshot === undefined && _jsx("p", { style: styles.status, children: t('usageLoading') }), errors[id] !== undefined && _jsx("p", { style: styles.error, children: t('usageError', { message: errors[id] }) }), snapshot?.windows?.length === 0 && _jsx("p", { style: styles.status, children: t('usageEmpty') }), snapshot?.windows?.map((window, index) => {
|
|
64
81
|
const percent = displayUsedPercent(window.usedPercent);
|
|
65
|
-
const label = window
|
|
66
|
-
: window.kind === 'weekly' ? t('usageWeekly')
|
|
67
|
-
: window.scope === 'Monthly' ? t('usageMonthly') : window.scope ?? t('usageWindow');
|
|
82
|
+
const label = externalUsageWindowLabel(t, window);
|
|
68
83
|
return _jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageMeta, children: [_jsx("span", { children: label }), _jsxs("span", { children: [percent === undefined ? t('usageMeterInvalid') : `${percent}%`, window.resetsAt === undefined ? ''
|
|
69
84
|
: ` · ${t('usageResets', { date: new Date(window.resetsAt).toLocaleString() })}`] })] }), _jsx(UsageMeter, { window: window, t: t, observedAt: observedAt[id], stale: errors[id] !== undefined || snapshot?.stale === true })] }, index);
|
|
70
85
|
})] })] }), !configured && _jsx("div", { style: styles.actions, children: _jsx("button", { type: "button", style: styles.button, onClick: () => { void refreshStatus(); }, children: t('usageRefresh') }) })] }, id);
|
|
@@ -3,13 +3,8 @@ import { useEffect, useId, useRef, useState } from 'react';
|
|
|
3
3
|
import { callSubscriptionsAuth } from './subscriptions-rpc.js';
|
|
4
4
|
import { accountModelRows, accountPoolSelection, mergeAccountChanges, mergeLatestAccounts } from './account-preferences.js';
|
|
5
5
|
import { ProviderModelEditor } from './ProviderModelEditor.js';
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
font: 'inherit', color: 'inherit', background: 'var(--dsw-alias-bg-layer-1)',
|
|
9
|
-
border, borderRadius: 8, padding: '7px 12px', minWidth: 0,
|
|
10
|
-
};
|
|
11
|
-
const button = { ...control, cursor: 'pointer' };
|
|
12
|
-
const hint = { margin: 0, fontSize: 12, lineHeight: 1.6, color: 'var(--dsw-alias-label-tertiary)' };
|
|
6
|
+
import { providerSettingsCss } from './provider-settings-styles.js';
|
|
7
|
+
const border = '0.5px solid var(--dsw-alias-border-l2)';
|
|
13
8
|
const stack = { display: 'grid', gap: 12, minWidth: 0 };
|
|
14
9
|
const actions = { display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' };
|
|
15
10
|
/** Native top-layer dialog provides keyboard containment, Escape and focus restoration. */
|
|
@@ -110,38 +105,36 @@ export function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
110
105
|
setSaving(false);
|
|
111
106
|
}
|
|
112
107
|
}
|
|
113
|
-
return
|
|
114
|
-
event.preventDefault(); }, onClose: onClose, style: { width: 620, maxWidth: 'calc(100vw - 32px)', maxHeight: 'calc(100dvh - 32px)',
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
_jsx(ProviderModelEditor, { ref: editor, provider: provider, rpc: rpc, t: t, disabled: saving, onDirtyChange: setModelsDirty }), _jsxs("footer", { style: { ...stack, gap: 8, borderTop: border, padding: '14px 0 0',
|
|
146
|
-
position: 'sticky', bottom: 0, background: 'var(--dsw-alias-bg-layer-1)' }, children: [saveError && _jsx("p", { role: "alert", style: { ...hint, color: 'var(--dsw-alias-state-error-primary)' }, children: saveError }), _jsxs("div", { style: { ...actions, justifyContent: 'flex-end' }, children: [_jsx("button", { type: "button", style: button, disabled: saving, onClick: onClose, children: t('cancel') }), _jsx("button", { type: "button", style: { ...button, fontWeight: 600 }, disabled: loading || saving || (!Object.keys(changes).length && !modelsDirty), onClick: () => { void save(); }, children: saving ? t('modelDefaultsSaving') : t('modelsSave') })] })] })] }) });
|
|
108
|
+
return _jsxs("dialog", { className: "dsh-subscription-manager", ref: dialog, "aria-labelledby": title, "aria-describedby": description, onCancel: event => { if (saveLock.current)
|
|
109
|
+
event.preventDefault(); }, onClose: onClose, style: { width: 620, maxWidth: 'calc(100vw - 32px)', maxHeight: 'calc(100dvh - 32px)', overflow: 'auto' }, children: [_jsx("style", { children: providerSettingsCss }), _jsxs("div", { style: { ...stack, padding: 20 }, children: [_jsxs("header", { style: { ...actions, justifyContent: 'space-between' }, children: [_jsx("h2", { id: title, style: { margin: 0 }, children: t('accountsTitle', { provider: name }) }), _jsx("button", { type: "button", autoFocus: true, disabled: saving, onClick: onClose, children: t('imageClose') })] }), _jsx("p", { id: description, className: "dsh-subscription-hint", children: t(provider === 'cursor-subscription' ? 'cursorAccountsHint' : 'accountsHint') }), error && _jsx("p", { role: "alert", children: error }), loading && _jsx("p", { role: "status", className: "dsh-subscription-hint", children: t('accountsLoading') }), !loading && !catalog && _jsx("button", { type: "button", onClick: () => setAttempt(value => value + 1), children: t('modelDefaultsRetry') }), catalog && _jsxs("fieldset", { disabled: saving || loading, style: { ...stack, border: 0, margin: 0, padding: 0 }, children: [catalog.accounts.length === 0 && _jsx("p", { className: "dsh-subscription-hint", children: t(provider === 'cursor-subscription' ? 'cursorManageSignIn' : 'accountsEmpty') }), provider === 'cursor-subscription' && catalog.accounts.length > 0 &&
|
|
110
|
+
_jsxs("fieldset", { style: { border, borderRadius: 'var(--dsw-radius-lg, 12px)', padding: 14 }, children: [_jsx("legend", { style: { padding: '0 6px' }, children: name }), _jsx("p", { className: "dsh-subscription-hint", children: t('cursorConnected') })] }), provider !== 'cursor-subscription' && catalog.accounts.map(account => {
|
|
111
|
+
const preferences = changes[account.key] ?? catalog.settings.accounts?.[account.key] ?? {};
|
|
112
|
+
const selected = accountPoolSelection(preferences, account.models);
|
|
113
|
+
const models = accountModelRows(account, preferences);
|
|
114
|
+
return _jsxs("fieldset", { style: { ...stack, border, borderRadius: 'var(--dsw-radius-lg, 12px)', padding: 14, margin: 0 }, children: [_jsx("legend", { style: { padding: '0 6px', overflowWrap: 'anywhere', maxWidth: '100%' }, children: account.label }), account.unavailable && _jsx("p", { className: "dsh-subscription-hint", children: t('accountsCatalogUnavailable') }), _jsxs("label", { style: { ...stack, gap: 6 }, children: [_jsx("span", { className: "dsh-subscription-label", children: t('accountsAlias') }), _jsx("input", { value: preferences.alias ?? '', placeholder: account.label, onChange: event => {
|
|
115
|
+
const next = { ...preferences };
|
|
116
|
+
if (event.target.value.trim())
|
|
117
|
+
next.alias = event.target.value;
|
|
118
|
+
else
|
|
119
|
+
delete next.alias;
|
|
120
|
+
edit(account.key, next);
|
|
121
|
+
} })] }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: preferences.poolEnabled !== false, onChange: event => edit(account.key, { ...preferences, poolEnabled: event.target.checked }) }), " ", t('accountsPoolEnabled')] }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: preferences.independentEntry === true, onChange: event => edit(account.key, { ...preferences, independentEntry: event.target.checked }) }), " ", t('accountsIndependent')] }), _jsx("p", { className: "dsh-subscription-hint", children: t('accountsIndependentHint') }), _jsxs("details", { children: [_jsxs("summary", { style: { cursor: 'pointer', fontSize: 13 }, children: [t('accountsModels'), " \u00B7 ", preferences.poolModels === undefined
|
|
122
|
+
? t('accountsModelsAll') : t('accountsModelsCount', { count: preferences.poolModels.length })] }), _jsxs("div", { style: { ...stack, marginTop: 12 }, children: [_jsx("p", { className: "dsh-subscription-hint", children: t('accountsModelsHint') }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: preferences.poolModels === undefined, onChange: event => {
|
|
123
|
+
const next = { ...preferences };
|
|
124
|
+
if (event.target.checked)
|
|
125
|
+
delete next.poolModels;
|
|
126
|
+
else
|
|
127
|
+
next.poolModels = account.models.map(model => model.id);
|
|
128
|
+
edit(account.key, next);
|
|
129
|
+
} }), " ", t('accountsModelsAutomatic')] }), _jsxs("div", { style: actions, children: [_jsx("button", { type: "button", onClick: () => edit(account.key, { ...preferences, poolModels: models.map(model => model.id) }), children: t('modelsSelectAll') }), _jsx("button", { type: "button", onClick: () => edit(account.key, { ...preferences, poolModels: [] }), children: t('modelsSelectNone') })] }), _jsxs("div", { style: { ...stack, gap: 8, maxHeight: 240, overflowY: 'auto' }, children: [models.map(model => _jsxs("label", { style: { overflowWrap: 'anywhere' }, children: [_jsx("input", { type: "checkbox", checked: selected.has(model.id), onChange: event => {
|
|
130
|
+
const next = new Set(selected);
|
|
131
|
+
if (event.target.checked)
|
|
132
|
+
next.add(model.id);
|
|
133
|
+
else
|
|
134
|
+
next.delete(model.id);
|
|
135
|
+
edit(account.key, { ...preferences, poolModels: [...next] });
|
|
136
|
+
} }), " ", model.name, model.unavailable && ` (${t('modelsUnavailable')})`] }, model.id)), models.length === 0 && _jsx("p", { className: "dsh-subscription-hint", children: t('accountsNoModels') })] })] })] })] }, account.key);
|
|
137
|
+
})] }), (provider !== 'cursor-subscription' || (catalog?.accounts.length ?? 0) > 0) &&
|
|
138
|
+
_jsx(ProviderModelEditor, { ref: editor, provider: provider, rpc: rpc, t: t, disabled: saving, onDirtyChange: setModelsDirty }), _jsxs("footer", { style: { ...stack, gap: 8, borderTop: border, padding: '14px 0 8px',
|
|
139
|
+
position: 'sticky', bottom: 0, background: 'var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1))' }, children: [saveError && _jsx("p", { role: "alert", children: saveError }), _jsxs("div", { style: { ...actions, justifyContent: 'flex-end' }, children: [_jsx("button", { type: "button", disabled: saving, onClick: onClose, children: t('cancel') }), _jsx("button", { type: "button", className: "dsh-subscription-primary", disabled: loading || saving || (!Object.keys(changes).length && !modelsDirty), onClick: () => { void save(); }, children: saving ? t('modelDefaultsSaving') : t('modelsSave') })] })] })] })] });
|
|
147
140
|
}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
3
3
|
import { callSubscriptionsAuth } from './subscriptions-rpc.js';
|
|
4
|
-
const border = '
|
|
5
|
-
const control = {
|
|
6
|
-
font: 'inherit', color: 'inherit', background: 'transparent', border,
|
|
7
|
-
borderRadius: 8, padding: '6px 10px', minWidth: 0,
|
|
8
|
-
};
|
|
4
|
+
const border = '0.5px solid var(--dsw-alias-border-l2, #ddd)';
|
|
9
5
|
const actions = { display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center' };
|
|
10
6
|
/**
|
|
11
7
|
* Embedded section of the account manager dialog. It owns the model draft
|
|
@@ -83,25 +79,25 @@ export const ProviderModelEditor = forwardRef(function ProviderModelEditor({ pro
|
|
|
83
79
|
const missing = (draft.visibleModels ?? []).filter(id => !known.has(id)).map(id => ({ id, name: id }));
|
|
84
80
|
const models = [...allModels, ...missing].filter(model => `${model.name} ${model.id}`.toLowerCase().includes(query.trim().toLowerCase()));
|
|
85
81
|
const selected = new Set(draft.visibleModels ?? allModels.map(model => model.id));
|
|
86
|
-
return _jsxs("div", { style: { borderTop: border, marginTop: 12, paddingTop: 12 }, children: [_jsx("h3", { style: { margin: 0
|
|
82
|
+
return _jsxs("div", { style: { borderTop: border, marginTop: 12, paddingTop: 12 }, children: [_jsx("h3", { style: { margin: 0 }, children: t('modelsEdit') }), _jsxs("div", { style: { display: 'grid', gap: 12, marginTop: 12 }, children: [_jsx("p", { className: "dsh-subscription-hint", children: t(provider === 'cursor-subscription' ? 'cursorManageHint' : 'modelsHint') }), error && _jsx("p", { role: "alert", children: error }), _jsxs("div", { style: actions, children: [_jsx("button", { type: "button", className: "dsh-subscription-quiet", disabled: busy || disabled || dirty, onClick: () => { void load(true); }, children: t('usageRefresh') }), busy && _jsx("span", { role: "status", children: t('modelDefaultsLoading') })] }), catalog && _jsxs("fieldset", { disabled: busy || disabled, style: { border: 0, padding: 0, margin: 0, minWidth: 0, display: 'grid', gap: 12 }, children: [_jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: draft.visibleModels === undefined, onChange: event => {
|
|
87
83
|
const next = { ...draft };
|
|
88
84
|
if (event.target.checked)
|
|
89
85
|
delete next.visibleModels;
|
|
90
86
|
else
|
|
91
87
|
next.visibleModels = allModels.map(model => model.id);
|
|
92
88
|
edit(next);
|
|
93
|
-
} }), " ", t('modelsAutomatic')] }), _jsxs("div", { style: actions, children: [_jsx("input", { style: {
|
|
89
|
+
} }), " ", t('modelsAutomatic')] }), _jsxs("div", { style: actions, children: [_jsx("input", { style: { flex: '1 1 180px' }, value: query, onChange: event => setQuery(event.target.value), placeholder: t('modelDefaultsFilterPlaceholder'), "aria-label": t('modelDefaultsFilterPlaceholder') }), _jsx("button", { type: "button", onClick: () => edit({ ...draft, visibleModels: allModels.map(model => model.id) }), children: t('modelsSelectAll') }), _jsx("button", { type: "button", onClick: () => edit({ ...draft, visibleModels: [] }), children: t('modelsSelectNone') })] }), _jsxs("div", { style: { maxHeight: 360, overflowY: 'auto', display: 'grid', gap: 8 }, children: [models.map(model => _jsxs("div", { style: { borderBottom: border, padding: '8px 2px', display: 'grid', gap: 8 }, children: [_jsxs("label", { style: { overflowWrap: 'anywhere' }, children: [_jsx("input", { type: "checkbox", checked: selected.has(model.id), onChange: event => {
|
|
94
90
|
const next = new Set(selected);
|
|
95
91
|
if (event.target.checked)
|
|
96
92
|
next.add(model.id);
|
|
97
93
|
else
|
|
98
94
|
next.delete(model.id);
|
|
99
95
|
edit({ ...draft, visibleModels: [...next] });
|
|
100
|
-
} }), " ", model.name, !known.has(model.id) && ` (${t('modelsUnavailable')})`] }), _jsxs("div", { style: actions, children: [(model.efforts?.length ?? 0) > 0 && _jsxs("label", { style: actions, children: [t('modelDefaultsTitle'), _jsxs("select", {
|
|
96
|
+
} }), " ", model.name, !known.has(model.id) && ` (${t('modelsUnavailable')})`] }), _jsxs("div", { style: actions, children: [(model.efforts?.length ?? 0) > 0 && _jsxs("label", { style: actions, children: [t('modelDefaultsTitle'), _jsxs("select", { "aria-label": `${model.name} ${t('modelDefaultsTitle')}`, value: efforts[model.id] ?? '', onChange: event => {
|
|
101
97
|
setEfforts({ ...efforts, [model.id]: event.target.value });
|
|
102
98
|
setDirty(true);
|
|
103
99
|
}, children: [_jsx("option", { value: "", children: t('modelDefaultsFollowProvider') }), model.configured && !model.efforts?.some(effort => effort.id === model.configured) &&
|
|
104
|
-
_jsxs("option", { value: model.configured, disabled: true, children: [model.configured, " (", t('modelsUnavailable'), ")"] }), model.efforts?.map(effort => _jsx("option", { value: effort.id, children: effort.name }, effort.id))] })] }), provider === 'codex' && model.maxContextWindow !== undefined && _jsxs("div", { style: actions, children: [_jsxs("label", { style: actions, children: [t('modelsContext'), _jsx("input", { style: {
|
|
100
|
+
_jsxs("option", { value: model.configured, disabled: true, children: [model.configured, " (", t('modelsUnavailable'), ")"] }), model.efforts?.map(effort => _jsx("option", { value: effort.id, children: effort.name }, effort.id))] })] }), provider === 'codex' && model.maxContextWindow !== undefined && _jsxs("div", { style: actions, children: [_jsxs("label", { style: actions, children: [t('modelsContext'), _jsx("input", { style: { width: 140 }, inputMode: "numeric", value: contexts[model.id] ?? '', "aria-label": `${model.name} ${t('modelsContext')}`, placeholder: String(model.defaultContextWindow), onChange: event => { setContexts({ ...contexts, [model.id]: event.target.value }); setDirty(true); } })] }), _jsx("small", { children: t('modelsContextBounds', { default: model.defaultContextWindow, max: model.maxContextWindow }) })] })] })] }, model.id)), models.length === 0 && _jsx("span", { children: t('modelDefaultsFilterEmpty', { query }) })] }), provider === 'codex' && _jsx("small", { children: t('modelsContextHint') }), catalog.tools.length > 0 && _jsxs("div", { style: { display: 'grid', gap: 8 }, children: [_jsx("strong", { children: t('modelsTools') }), _jsx("small", { children: t('modelsToolsHint') }), catalog.tools.map(tool => _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: draft.tools?.[tool] !== false, onChange: event => edit({
|
|
105
101
|
...draft, tools: { ...draft.tools, [tool]: event.target.checked },
|
|
106
102
|
}) }), " ", t(tool === 'image_generate' ? 'modelsImage' : tool === 'video_generate' ? 'modelsVideo' : tool === 'web_search' ? 'modelsWebSearch' : 'modelsSearch')] }, tool))] })] })] })] });
|
|
107
103
|
});
|
|
@@ -14,6 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
14
14
|
*/
|
|
15
15
|
import { useEffect, useRef, useState } from 'react';
|
|
16
16
|
import { callSubscriptionsAuth } from './SubscriptionsSection.js';
|
|
17
|
+
import { subscriptionChromeCss } from './provider-settings-styles.js';
|
|
17
18
|
import { en } from './locales.js';
|
|
18
19
|
/**
|
|
19
20
|
* The `loadSpeed` half of the inject face: the plugin's own speed state plus
|
|
@@ -136,12 +137,12 @@ export function SpeedSelect({ loadSpeed, setSpeed, t }) {
|
|
|
136
137
|
const tierName = (tier) => translate(tier === 'fast' ? 'speedFast' : 'speedStandard');
|
|
137
138
|
const tierDescription = (tier) => translate(tier === 'fast' ? 'speedFastDescription' : 'speedStandardDescription');
|
|
138
139
|
const triggerLabel = `${translate('speed')} · ${tierName(state.tier)}`;
|
|
139
|
-
return (_jsxs("div", { ref: rootRef, style: styles.root, onKeyDown: (event) => {
|
|
140
|
+
return (_jsxs("div", { ref: rootRef, className: "dsh-subscription-speed", style: styles.root, onKeyDown: (event) => {
|
|
140
141
|
if (event.key === 'Escape' && open) {
|
|
141
142
|
event.preventDefault();
|
|
142
143
|
setOpen(false);
|
|
143
144
|
}
|
|
144
|
-
}, children: [open && (_jsx("div", { style: styles.menu, role: "menu", "aria-label": translate('speed'), children: TIERS.map(tier => (_jsxs("button", { type: "button", role: "menuitemradio", "aria-checked": tier === state.tier, style: styles.item, disabled: busy, onClick: () => { choose(tier); }, children: [_jsx("span", { style: styles.itemCheck, children: tier === state.tier ? '✓' : '' }), _jsxs("span", { style: styles.itemText, children: [_jsx("span", { style: styles.itemName, children: tierName(tier) }), _jsx("span", { style: styles.itemDescription, children: tierDescription(tier) })] })] }, tier))) })), _jsx("button", { type: "button", style: styles.trigger, "aria-haspopup": "menu", "aria-expanded": open, title: triggerLabel, disabled: busy, onClick: () => {
|
|
145
|
+
}, children: [_jsx("style", { children: subscriptionChromeCss }), open && (_jsx("div", { style: styles.menu, role: "menu", "aria-label": translate('speed'), children: TIERS.map(tier => (_jsxs("button", { type: "button", role: "menuitemradio", "aria-checked": tier === state.tier, style: styles.item, disabled: busy, onClick: () => { choose(tier); }, children: [_jsx("span", { style: styles.itemCheck, children: tier === state.tier ? '✓' : '' }), _jsxs("span", { style: styles.itemText, children: [_jsx("span", { style: styles.itemName, children: tierName(tier) }), _jsx("span", { style: styles.itemDescription, children: tierDescription(tier) })] })] }, tier))) })), _jsx("button", { type: "button", style: styles.trigger, "aria-haspopup": "menu", "aria-expanded": open, title: triggerLabel, disabled: busy, onClick: () => {
|
|
145
146
|
if (open)
|
|
146
147
|
setOpen(false);
|
|
147
148
|
else
|
|
@@ -151,20 +152,20 @@ export function SpeedSelect({ loadSpeed, setSpeed, t }) {
|
|
|
151
152
|
const styles = {
|
|
152
153
|
root: { position: 'relative', display: 'inline-flex' },
|
|
153
154
|
trigger: {
|
|
154
|
-
border: '
|
|
155
|
-
background: 'transparent', color: 'var(--dsw-alias-label-secondary)',
|
|
155
|
+
border: '0.5px solid var(--dsw-alias-border-l3)', borderRadius: 'var(--dsw-radius-sm, 8px)',
|
|
156
|
+
background: 'var(--dsh-subscription-speed-hover, transparent)', color: 'var(--dsw-alias-label-secondary)',
|
|
156
157
|
font: 'inherit', fontSize: 12, lineHeight: '18px',
|
|
157
158
|
padding: '2px 8px', cursor: 'pointer', whiteSpace: 'nowrap',
|
|
158
159
|
},
|
|
159
160
|
menu: {
|
|
160
161
|
position: 'absolute', bottom: '100%', right: 0, marginBottom: 4,
|
|
161
162
|
minWidth: 180, padding: 4, zIndex: 20,
|
|
162
|
-
background: 'var(--dsw-
|
|
163
|
-
borderRadius:
|
|
163
|
+
background: 'var(--dsw-specific-menu, var(--dsw-alias-bg-layer-2))', border: 0,
|
|
164
|
+
boxShadow: 'var(--dsw-elevation-prominent)', borderRadius: 'var(--dsw-radius-lg, 16px)', display: 'flex', flexDirection: 'column', gap: 2,
|
|
164
165
|
},
|
|
165
166
|
item: {
|
|
166
167
|
display: 'flex', alignItems: 'flex-start', gap: 6, width: '100%',
|
|
167
|
-
border: 'none', borderRadius:
|
|
168
|
+
border: 'none', borderRadius: 'var(--dsw-radius-sm, 8px)', background: 'var(--dsh-subscription-speed-hover, transparent)',
|
|
168
169
|
padding: '6px 8px', cursor: 'pointer', font: 'inherit', textAlign: 'left',
|
|
169
170
|
},
|
|
170
171
|
itemCheck: {
|
|
@@ -20,7 +20,11 @@ export interface SubscriptionUsageBadgeInjected {
|
|
|
20
20
|
} | undefined>;
|
|
21
21
|
}
|
|
22
22
|
/** Props delivered by the slot outlet + inject + the locale seat. */
|
|
23
|
-
export type SubscriptionUsageBadgeProps = PropsRuntime<'conversation.composer.dock'> & Partial<SubscriptionUsageBadgeInjected> & Partial<PropsLocale<'settings.subscriptions'
|
|
23
|
+
export type SubscriptionUsageBadgeProps = PropsRuntime<'conversation.composer.dock'> & Partial<SubscriptionUsageBadgeInjected> & Partial<PropsLocale<'settings.subscriptions'>>
|
|
24
|
+
/** Override the rotation cadence (tests only; the slot never passes it). */
|
|
25
|
+
& {
|
|
26
|
+
rotationMs?: number;
|
|
27
|
+
};
|
|
24
28
|
/** One logged-in account's usage windows, as listed in the expanded dialog. */
|
|
25
29
|
export interface AccountUsageDisplay {
|
|
26
30
|
/** Account key (the `usage` endpoint's `account` argument). */
|
|
@@ -98,6 +102,19 @@ type ModelSelection = {
|
|
|
98
102
|
export declare function retainSubscriptionSelection(previous: ModelSelection | undefined, current: ModelSelection | undefined): ModelSelection | undefined;
|
|
99
103
|
/** At most one provider; no known selection or no usage means no badge. */
|
|
100
104
|
export declare function collapsedDisplays(displays: readonly ProviderUsageDisplay[], current: string | undefined): readonly ProviderUsageDisplay[];
|
|
105
|
+
/**
|
|
106
|
+
* `always` mode: the current model's provider while it reports usage,
|
|
107
|
+
* otherwise the rotation steps through the providers that do, in display
|
|
108
|
+
* order, wrapping around. The index is taken modulo the current provider
|
|
109
|
+
* count, so providers appearing or dropping out stay inside range; the
|
|
110
|
+
* sequence itself may shift when the roster changes. Unlike `recent`, the
|
|
111
|
+
* retained subscription does not pin the pill — that retention is exactly
|
|
112
|
+
* what this mode exists to escape.
|
|
113
|
+
* @param displays - providers with usage, in display order.
|
|
114
|
+
* @param current - the provider of the session's current model.
|
|
115
|
+
* @param step - rotation tick; consecutive ticks advance one provider.
|
|
116
|
+
*/
|
|
117
|
+
export declare function rotatingDisplay(displays: readonly ProviderUsageDisplay[], current: string | undefined, step: number): readonly ProviderUsageDisplay[];
|
|
101
118
|
/** Order for the expanded dialog: the current provider first, the rest in poll order. */
|
|
102
119
|
export declare function expandedDisplays(displays: readonly ProviderUsageDisplay[], current: string | undefined): readonly ProviderUsageDisplay[];
|
|
103
120
|
type Translate = (key: SubscriptionsKey, params?: Record<string, unknown>) => string;
|
|
@@ -107,7 +124,7 @@ type Translate = (key: SubscriptionsKey, params?: Record<string, unknown>) => st
|
|
|
107
124
|
* every provider's accounts and their windows. Returns null when no data is
|
|
108
125
|
* available.
|
|
109
126
|
*/
|
|
110
|
-
export declare function SubscriptionUsageBadge({ rpc, currentModel, t }: SubscriptionUsageBadgeProps): import("react").JSX.Element;
|
|
127
|
+
export declare function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }: SubscriptionUsageBadgeProps): import("react").JSX.Element;
|
|
111
128
|
/** Preview each account independently; all remaining quotas stay accessible. */
|
|
112
129
|
export declare function AccountWindows({ windows, model, provider, translate, observedAt, stale }: {
|
|
113
130
|
windows: readonly UsageWindow[];
|