dsh-subscriptions 0.2.2 → 0.2.3
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 +21 -13
- package/lib/auth/rpc.d.ts +8 -4
- package/lib/auth/rpc.js +15 -2
- package/lib/client/ProviderModelEditor.js +59 -37
- package/lib/client/SpeedSelect.js +13 -12
- package/lib/client/SubscriptionsSection.d.ts +2 -0
- package/lib/client/SubscriptionsSection.js +10 -3
- package/lib/client/locales.d.ts +12 -0
- package/lib/client/locales.js +14 -2
- package/lib/client/model-filter.d.ts +37 -0
- package/lib/client/model-filter.js +45 -0
- package/lib/client/usage-window-filter.d.ts +7 -0
- package/lib/client/usage-window-filter.js +12 -0
- package/lib/client.js +199 -95
- package/lib/client.js.map +1 -1
- package/lib/index.js +49 -12
- package/lib/provider-settings.d.ts +5 -0
- package/lib/provider-settings.js +21 -0
- package/lib/translate/antigravity.js +10 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -77,10 +77,17 @@ the composer's tool row: Standard or Fast, per session.
|
|
|
77
77
|

|
|
78
78
|
|
|
79
79
|
**Settings → Subscriptions → provider → Manage** edits account aliases, pool
|
|
80
|
-
participation, model allowlists, and the per-model effort,
|
|
81
|
-
settings. A context value only lowers the window a session
|
|
82
|
-
above what the provider advertises; Codex and Claude
|
|
83
|
-
enlarge the API's capacity.
|
|
80
|
+
participation, model allowlists, and the per-model effort, subagent speed,
|
|
81
|
+
context, and tool settings. A context value only lowers the window a session
|
|
82
|
+
plans against, never above what the provider advertises; Codex and Claude
|
|
83
|
+
support it, and it does not enlarge the API's capacity.
|
|
84
|
+
|
|
85
|
+
For Codex, **Subagent default speed** applies to subsequent subagent requests
|
|
86
|
+
using that model, regardless of the parent model. An explicit Speed choice in
|
|
87
|
+
the subagent session overrides the saved default, and a parent's choice never
|
|
88
|
+
propagates. The default survives restart; session overrides do not. Fast still
|
|
89
|
+
requires current model support, and requests without live subagent metadata use
|
|
90
|
+
Standard.
|
|
84
91
|
|
|
85
92
|

|
|
86
93
|
|
|
@@ -154,15 +161,16 @@ text. No live account was used to verify what Anthropic returns. See
|
|
|
154
161
|
|
|
155
162
|
## Install
|
|
156
163
|
|
|
157
|
-
Current source is tested with DSH `0.1.7-rc.2`, `0.2.0-rc.2`,
|
|
158
|
-
The package's peers admit `>=0.1.7-rc.2 <0.3.0-0`: untested
|
|
159
|
-
range install but are unsupported, and versions outside it
|
|
160
|
-
`dsh plugin allow-version` exemption. `dsh plugin add` checks
|
|
161
|
-
`includePrerelease` semantics, so the admitted set covers
|
|
162
|
-
`0.1.7-rc.2` through the whole `0.2.x` line, prereleases
|
|
163
|
-
the `0.3.0` line. See
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
Current source is tested with DSH `0.1.7-rc.2`, `0.2.0-rc.2`, `0.2.1-rc.2`, and
|
|
165
|
+
`0.2.1-rc.5`. The package's peers admit `>=0.1.7-rc.2 <0.3.0-0`: untested
|
|
166
|
+
versions inside that range install but are unsupported, and versions outside it
|
|
167
|
+
need an explicit `dsh plugin allow-version` exemption. `dsh plugin add` checks
|
|
168
|
+
those ranges with `includePrerelease` semantics, so the admitted set covers
|
|
169
|
+
every host build from `0.1.7-rc.2` through the whole `0.2.x` line, prereleases
|
|
170
|
+
included, and blocks the `0.3.0` line. See
|
|
171
|
+
[docs/compatibility.md](docs/compatibility.md) for the support window and
|
|
172
|
+
[docs/testing.md](docs/testing.md) for the test layers. With `dsh` available,
|
|
173
|
+
install the plugin into the web profile from npm:
|
|
166
174
|
|
|
167
175
|
```sh
|
|
168
176
|
dsh plugin --profile web add dsh-subscriptions
|
package/lib/auth/rpc.d.ts
CHANGED
|
@@ -38,15 +38,15 @@ export interface VideoBytesResult {
|
|
|
38
38
|
export type SpeedTier = 'standard' | 'fast';
|
|
39
39
|
/** `speed` endpoint value: the session's choice plus the visibility list. */
|
|
40
40
|
export interface SpeedState {
|
|
41
|
-
/**
|
|
41
|
+
/** Explicit session speed, or the subagent's model default when supplied. */
|
|
42
42
|
tier: SpeedTier;
|
|
43
43
|
/** Codex model ids whose catalog advertises a fast tier. */
|
|
44
44
|
fastModels: string[];
|
|
45
45
|
}
|
|
46
|
-
/**
|
|
46
|
+
/** Session overrides plus persisted subagent model defaults. */
|
|
47
47
|
export interface SpeedController {
|
|
48
|
-
/**
|
|
49
|
-
speed(sessionId: string): Promise<SpeedState>;
|
|
48
|
+
/** Pass the current Codex model to resolve a subagent's default speed. */
|
|
49
|
+
speed(sessionId: string, model?: string): Promise<SpeedState>;
|
|
50
50
|
/** Set one session's speed tier. */
|
|
51
51
|
setSpeed(sessionId: string, tier: SpeedTier): Promise<void>;
|
|
52
52
|
}
|
|
@@ -65,6 +65,8 @@ export interface AccountStatus {
|
|
|
65
65
|
}
|
|
66
66
|
/** Login state of one provider, as rendered by the Settings page. */
|
|
67
67
|
export interface ProviderStatus {
|
|
68
|
+
/** Saved picker visibility, attached by the status RPC from local preferences. */
|
|
69
|
+
visibleModels?: string[];
|
|
68
70
|
/** Whether a login attempt is currently waiting for its code. */
|
|
69
71
|
busy: boolean;
|
|
70
72
|
/** Logged-in accounts, default first. */
|
|
@@ -110,6 +112,8 @@ export interface ProviderAccountCatalog {
|
|
|
110
112
|
}
|
|
111
113
|
/** Provider and account preference operations behind providerSettings/setProviderSettings. */
|
|
112
114
|
export interface ProviderSettingsController {
|
|
115
|
+
/** Local picker selection, without triggering model discovery. */
|
|
116
|
+
visibleModels?(provider: ProviderId): string[] | undefined;
|
|
113
117
|
get(provider: ProviderId, force: boolean): Promise<unknown>;
|
|
114
118
|
set(provider: ProviderId, settings: unknown): Promise<void>;
|
|
115
119
|
}
|
package/lib/auth/rpc.js
CHANGED
|
@@ -309,7 +309,18 @@ async function dispatch(controller, speed, modelDefaults, endpoint, payload, sig
|
|
|
309
309
|
detail: error instanceof Error ? error.message : String(error),
|
|
310
310
|
})),
|
|
311
311
|
]));
|
|
312
|
-
return ok({
|
|
312
|
+
return ok({
|
|
313
|
+
providers: Object.fromEntries(entries.map(([provider, status]) => {
|
|
314
|
+
const visibleModels = providerSettings?.visibleModels?.(provider);
|
|
315
|
+
return [
|
|
316
|
+
provider,
|
|
317
|
+
{
|
|
318
|
+
...status,
|
|
319
|
+
...(visibleModels === undefined ? {} : { visibleModels }),
|
|
320
|
+
},
|
|
321
|
+
];
|
|
322
|
+
})),
|
|
323
|
+
});
|
|
313
324
|
}
|
|
314
325
|
case 'login': {
|
|
315
326
|
const provider = readProvider(payload);
|
|
@@ -433,7 +444,9 @@ async function dispatch(controller, speed, modelDefaults, endpoint, payload, sig
|
|
|
433
444
|
case 'video':
|
|
434
445
|
return ok(await controller.readVideo(readVideoName(payload), signal));
|
|
435
446
|
case 'speed':
|
|
436
|
-
return ok(await speed.speed(readSessionId(payload)
|
|
447
|
+
return ok(await speed.speed(readSessionId(payload), payload.model === undefined
|
|
448
|
+
? undefined
|
|
449
|
+
: readString(payload, 'model')));
|
|
437
450
|
case 'setSpeed':
|
|
438
451
|
await speed.setSpeed(readSessionId(payload), readSpeedTier(payload));
|
|
439
452
|
return ok({ ok: true });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { forwardRef, useEffect, useImperativeHandle, useRef, useState, } from 'react';
|
|
3
3
|
import { callSubscriptionsAuth } from './subscriptions-rpc.js';
|
|
4
|
+
import { modelEditorState, savedModelIds } from './model-filter.js';
|
|
4
5
|
/** Providers whose runtime honours a local context-window override. */
|
|
5
6
|
const supportsContext = (provider) => provider === 'codex' || provider === 'claude';
|
|
6
7
|
const border = '0.5px solid var(--dsw-alias-border-l2, #ddd)';
|
|
@@ -108,18 +109,26 @@ export const ProviderModelEditor = forwardRef(function ProviderModelEditor({ pro
|
|
|
108
109
|
}), [catalog, dirty, draft, contexts, efforts, provider, t]);
|
|
109
110
|
const allModels = catalog?.models ?? [];
|
|
110
111
|
const known = new Set(allModels.map((model) => model.id));
|
|
111
|
-
const
|
|
112
|
+
const savedIds = savedModelIds(draft.visibleModels, draft.subagentSpeeds);
|
|
113
|
+
const missing = savedIds
|
|
112
114
|
.filter((id) => !known.has(id))
|
|
113
115
|
.map((id) => ({ id, name: id }));
|
|
114
116
|
const models = [...allModels, ...missing].filter((model) => `${model.name} ${model.id}`
|
|
115
117
|
.toLowerCase()
|
|
116
118
|
.includes(query.trim().toLowerCase()));
|
|
117
|
-
const
|
|
119
|
+
const editorState = modelEditorState({
|
|
120
|
+
discovered: allModels.length,
|
|
121
|
+
saved: missing.length,
|
|
122
|
+
matched: models.length,
|
|
123
|
+
accounts: catalog?.accounts,
|
|
124
|
+
});
|
|
125
|
+
const selected = new Set(draft.visibleModels ??
|
|
126
|
+
[...allModels, ...missing].map((model) => model.id));
|
|
118
127
|
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'
|
|
119
128
|
? 'cursorManageHint'
|
|
120
129
|
: '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: () => {
|
|
121
130
|
void load(true);
|
|
122
|
-
}, children: t('usageRefresh') }), busy && _jsx("span", { role: "status", children: t('modelDefaultsLoading') })] }), catalog && (_jsxs("fieldset", { disabled: busy || disabled, style: {
|
|
131
|
+
}, children: t('usageRefresh') }), busy && _jsx("span", { role: "status", children: t('modelDefaultsLoading') })] }), !busy && catalog && (_jsxs("fieldset", { disabled: busy || disabled, style: {
|
|
123
132
|
border: 0,
|
|
124
133
|
padding: 0,
|
|
125
134
|
margin: 0,
|
|
@@ -133,44 +142,57 @@ export const ProviderModelEditor = forwardRef(function ProviderModelEditor({ pro
|
|
|
133
142
|
else
|
|
134
143
|
next.visibleModels = allModels.map((model) => model.id);
|
|
135
144
|
edit(next);
|
|
136
|
-
} }), ' ', 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({
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
display: 'grid',
|
|
143
|
-
gap: 8,
|
|
144
|
-
}, children: [models.map((model) => (_jsxs("div", { style: {
|
|
145
|
-
borderBottom: border,
|
|
146
|
-
padding: '8px 2px',
|
|
145
|
+
} }), ' ', t('modelsAutomatic')] }), editorState.listState === 'provider-empty' ? (_jsx("p", { className: "dsh-subscription-hint", children: t(editorState.noticeKey) })) : (_jsxs(_Fragment, { children: [_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({
|
|
146
|
+
...draft,
|
|
147
|
+
visibleModels: allModels.map((model) => model.id),
|
|
148
|
+
}), children: t('modelsSelectAll') }), _jsx("button", { type: "button", onClick: () => edit({ ...draft, visibleModels: [] }), children: t('modelsSelectNone') })] }), _jsxs("div", { style: {
|
|
149
|
+
maxHeight: 360,
|
|
150
|
+
overflowY: 'auto',
|
|
147
151
|
display: 'grid',
|
|
148
152
|
gap: 8,
|
|
149
|
-
}, children: [
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
}, children: [
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
setContexts({
|
|
166
|
-
...contexts,
|
|
153
|
+
}, children: [models.map((model) => (_jsxs("div", { style: {
|
|
154
|
+
borderBottom: border,
|
|
155
|
+
padding: '8px 2px',
|
|
156
|
+
display: 'grid',
|
|
157
|
+
gap: 8,
|
|
158
|
+
}, children: [_jsxs("label", { style: { overflowWrap: 'anywhere' }, children: [_jsx("input", { type: "checkbox", checked: selected.has(model.id), onChange: (event) => {
|
|
159
|
+
const next = new Set(selected);
|
|
160
|
+
if (event.target.checked)
|
|
161
|
+
next.add(model.id);
|
|
162
|
+
else
|
|
163
|
+
next.delete(model.id);
|
|
164
|
+
edit({ ...draft, visibleModels: [...next] });
|
|
165
|
+
} }), ' ', model.name, !known.has(model.id) &&
|
|
166
|
+
` (${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) => {
|
|
167
|
+
setEfforts({
|
|
168
|
+
...efforts,
|
|
167
169
|
[model.id]: event.target.value,
|
|
168
170
|
});
|
|
169
171
|
setDirty(true);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
}, children: [_jsx("option", { value: "", children: t('modelDefaultsFollowProvider') }), model.configured &&
|
|
173
|
+
!model.efforts?.some((effort) => effort.id === model.configured) && (_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)))] })] })), supportsContext(provider) &&
|
|
174
|
+
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) => {
|
|
175
|
+
setContexts({
|
|
176
|
+
...contexts,
|
|
177
|
+
[model.id]: event.target.value,
|
|
178
|
+
});
|
|
179
|
+
setDirty(true);
|
|
180
|
+
} })] }), _jsx("small", { children: t('modelsContextBounds', {
|
|
181
|
+
default: model.defaultContextWindow,
|
|
182
|
+
max: model.maxContextWindow,
|
|
183
|
+
}) })] }))] }), provider === 'codex' &&
|
|
184
|
+
(model.fastTier === true ||
|
|
185
|
+
Object.hasOwn(draft.subagentSpeeds ?? {}, model.id)) && (_jsxs("div", { style: { display: 'grid', gap: 6 }, children: [_jsxs("label", { style: actions, children: [t('modelsSubagentSpeed'), _jsxs("select", { "aria-label": `${model.name} ${t('modelsSubagentSpeed')}`, value: Object.hasOwn(draft.subagentSpeeds ?? {}, model.id)
|
|
186
|
+
? draft.subagentSpeeds[model.id]
|
|
187
|
+
: 'standard', onChange: (event) => edit({
|
|
188
|
+
...draft,
|
|
189
|
+
subagentSpeeds: {
|
|
190
|
+
...draft.subagentSpeeds,
|
|
191
|
+
[model.id]: event.target
|
|
192
|
+
.value,
|
|
193
|
+
},
|
|
194
|
+
}), children: [_jsx("option", { value: "standard", children: t('speedStandard') }), _jsxs("option", { value: "fast", disabled: !model.fastTier, children: [t('speedFast'), !model.fastTier &&
|
|
195
|
+
` (${t('modelsUnavailable')})`] })] })] }), _jsx("small", { children: t('modelsSubagentSpeedHint') })] }))] }, model.id))), models.length === 0 && (_jsx("span", { children: t('modelDefaultsFilterEmpty', { query }) }))] })] })), supportsContext(provider) && (_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({
|
|
174
196
|
...draft,
|
|
175
197
|
tools: {
|
|
176
198
|
...draft.tools,
|
|
@@ -32,20 +32,21 @@ import { en } from './locales.js';
|
|
|
32
32
|
*/
|
|
33
33
|
export function createSpeedLoader(connection, models, sessionId) {
|
|
34
34
|
return async () => {
|
|
35
|
-
const state = await callSubscriptionsAuth(connection.rpc, 'speed', { sessionId });
|
|
36
35
|
const directories = models();
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
throw error;
|
|
47
|
-
return { visible: false, tier: state.tier };
|
|
36
|
+
let current = null;
|
|
37
|
+
if (directories !== undefined) {
|
|
38
|
+
try {
|
|
39
|
+
current = (await directories.directoryFor(sessionId).load()).current;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (!modelSelectionUnavailable(error))
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
48
45
|
}
|
|
46
|
+
const state = await callSubscriptionsAuth(connection.rpc, 'speed', {
|
|
47
|
+
sessionId,
|
|
48
|
+
...(current?.provider === 'codex' ? { model: current.model } : {}),
|
|
49
|
+
});
|
|
49
50
|
const visible = current !== null &&
|
|
50
51
|
current.provider === 'codex' &&
|
|
51
52
|
state.fastModels.includes(current.model);
|
|
@@ -13,6 +13,8 @@ export interface AccountStatus {
|
|
|
13
13
|
}
|
|
14
14
|
/** One provider's login state as answered by the `status` endpoint. */
|
|
15
15
|
export interface ProviderStatus {
|
|
16
|
+
/** Picker selection; absent follows discovery, [] hides all model quotas. */
|
|
17
|
+
visibleModels?: string[];
|
|
16
18
|
busy: boolean;
|
|
17
19
|
accounts: AccountStatus[];
|
|
18
20
|
detail?: string;
|
|
@@ -15,6 +15,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
15
15
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
16
16
|
import { en } from './locales.js';
|
|
17
17
|
import { resetActionBlock } from './reset-action.js';
|
|
18
|
+
import { visibleUsageWindows } from './usage-window-filter.js';
|
|
18
19
|
import { ProviderAccountManager } from './ProviderAccountManager.js';
|
|
19
20
|
import { providerSettingsCss } from './provider-settings-styles.js';
|
|
20
21
|
import { ExternalUsageCards } from './ExternalUsageCards.js';
|
|
@@ -641,6 +642,7 @@ export function SubscriptionsSection(props) {
|
|
|
641
642
|
return (_jsxs("div", { style: styles.card, children: [_jsxs("div", { style: styles.cardHeader, children: [_jsx("span", { style: { ...styles.dot, background: dotColor(status) } }), _jsx("span", { style: styles.name, children: name }), status?.clientVersion !== undefined && (_jsx("span", { style: styles.clientVersion, title: t('clientVersionHint'), children: clientVersionText(t, status.clientVersion) }))] }), _jsx("p", { style: styles.statusLine, children: statusText(t, status) }), status?.detail !== undefined && status.detail !== '' && (_jsx("p", { style: styles.statusLine, children: status.detail })), errors[id] !== undefined && (_jsx("p", { style: styles.errorLine, children: errors[id] })), accounts.map((account) => {
|
|
642
643
|
const usageKey = `${id}:${account.key}`;
|
|
643
644
|
const usage = usages[usageKey];
|
|
645
|
+
const shownWindows = visibleUsageWindows(id, usage?.windows, status?.visibleModels);
|
|
644
646
|
const usageError = usageErrors[usageKey];
|
|
645
647
|
const display = account.account ?? account.key;
|
|
646
648
|
const sortedCredits = [...(usage?.resetCredits ?? [])].sort((a, b) => (a.expiresAt ?? Infinity) - (b.expiresAt ?? Infinity));
|
|
@@ -664,7 +666,9 @@ export function SubscriptionsSection(props) {
|
|
|
664
666
|
}, children: t('logout') })] }), showUsage && (_jsxs("div", { style: styles.usage, children: [_jsxs("div", { style: styles.usageHeader, children: [_jsx("span", { style: styles.usageTitle, children: t('usageTitle') }), usage?.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: t('usagePlan', { plan: usage.plan }) })), _jsx("button", { type: "button", style: styles.usageRefresh, disabled: usageLoading[usageKey] === true, onClick: () => {
|
|
665
667
|
void loadUsage(id, account.key, true);
|
|
666
668
|
}, children: t('usageRefresh') })] }), usage === undefined && usageError === undefined && (_jsx("p", { style: styles.statusLine, children: t('usageLoading') })), usageError !== undefined && (_jsx("p", { style: styles.errorLine, children: t('usageError', { message: usageError }) })), usage?.windows !== undefined &&
|
|
667
|
-
usage.windows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageEmpty') })),
|
|
669
|
+
usage.windows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageEmpty') })), usage?.windows !== undefined &&
|
|
670
|
+
usage.windows.length > 0 &&
|
|
671
|
+
shownWindows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageNoVisibleModels') })), shownWindows.map((window, index) => {
|
|
668
672
|
const percent = displayUsedPercent(window.usedPercent);
|
|
669
673
|
return (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageMeta, children: [_jsx("span", { children: usageWindowLabel(t, window) }), _jsxs("span", { children: [percent === undefined
|
|
670
674
|
? t('usageMeterInvalid')
|
|
@@ -702,7 +706,7 @@ export function SubscriptionsSection(props) {
|
|
|
702
706
|
void login(id, 'keychain');
|
|
703
707
|
}, children: t('addAccountKeychain') })] })), !busy && accounts.length > 0 && id !== 'claude' && (_jsx("button", { type: "button", onClick: () => {
|
|
704
708
|
void login(id);
|
|
705
|
-
}, children: t('addAccount') })), _jsx("button", { type: "button", "aria-haspopup": "dialog", onClick: () => setManagedProvider({ id, name }), children: t('accountsManage') }), busy && (_jsx("button", { type: "button", onClick: () => {
|
|
709
|
+
}, children: t('addAccount') })), !busy && (_jsx("button", { type: "button", "aria-haspopup": "dialog", onClick: () => setManagedProvider({ id, name }), children: t('accountsManage') })), busy && (_jsx("button", { type: "button", onClick: () => {
|
|
706
710
|
void cancel(id);
|
|
707
711
|
}, children: t('cancel') }))] }), !busy && accounts.length > 0 && (_jsx("p", { style: styles.statusLine, children: t('addAccountHint') })), busy && deviceCode !== undefined && (_jsxs("div", { style: styles.deviceCode, children: [_jsx("span", { style: styles.statusLine, children: t('deviceCodePrompt') }), _jsx("span", { style: styles.deviceCodeText, children: deviceCode.userCode }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { type: "button", onClick: () => {
|
|
708
712
|
copyDeviceCode(id, deviceCode.userCode);
|
|
@@ -716,7 +720,10 @@ export function SubscriptionsSection(props) {
|
|
|
716
720
|
})) }), _jsx("button", { type: "button", onClick: () => {
|
|
717
721
|
void submitManual(id);
|
|
718
722
|
}, children: t('submit') })] })] }))] }, id));
|
|
719
|
-
}), _jsx(CursorCard, { rpc: rpc, t: t }), _jsx(OllamaCard, { rpc: rpc, t: t }), _jsx(ExternalUsageCards, { rpc: rpc, t: t }), managedProvider && (_jsx(ProviderAccountManager, { provider: managedProvider.id, name: managedProvider.name, rpc: rpc, t: t, onClose: () =>
|
|
723
|
+
}), _jsx(CursorCard, { rpc: rpc, t: t }), _jsx(OllamaCard, { rpc: rpc, t: t }), _jsx(ExternalUsageCards, { rpc: rpc, t: t }), managedProvider && (_jsx(ProviderAccountManager, { provider: managedProvider.id, name: managedProvider.name, rpc: rpc, t: t, onClose: () => {
|
|
724
|
+
setManagedProvider(undefined);
|
|
725
|
+
void refresh();
|
|
726
|
+
} })), _jsx("style", { children: '.subscriptions-reset-dialog::backdrop { background: var(--dsw-alias-bg-mask-1, #0006); backdrop-filter: var(--dsw-mask-blur, none); }' }), _jsx("dialog", { ref: resetDialogRef, className: "subscriptions-reset-dialog", "aria-labelledby": "subscriptions-reset-title", onCancel: () => setResetConfirmation(undefined), onClose: () => setResetConfirmation(undefined), style: {
|
|
720
727
|
...styles.modal,
|
|
721
728
|
display: resetConfirmation ? 'flex' : 'none',
|
|
722
729
|
margin: 'auto',
|
package/lib/client/locales.d.ts
CHANGED
|
@@ -29,6 +29,8 @@ export declare const en: {
|
|
|
29
29
|
modelsContextBounds: string;
|
|
30
30
|
modelsContextHint: string;
|
|
31
31
|
modelsContextInvalid: string;
|
|
32
|
+
modelsSubagentSpeed: string;
|
|
33
|
+
modelsSubagentSpeedHint: string;
|
|
32
34
|
modelsTools: string;
|
|
33
35
|
modelsToolsHint: string;
|
|
34
36
|
modelsImage: string;
|
|
@@ -78,6 +80,7 @@ export declare const en: {
|
|
|
78
80
|
usageRefresh: string;
|
|
79
81
|
usageLoading: string;
|
|
80
82
|
usageEmpty: string;
|
|
83
|
+
usageNoVisibleModels: string;
|
|
81
84
|
usageUnsupported: string;
|
|
82
85
|
usageError: string;
|
|
83
86
|
usageSession: string;
|
|
@@ -176,6 +179,9 @@ export declare const en: {
|
|
|
176
179
|
modelDefaultsSummaryEmpty: string;
|
|
177
180
|
modelDefaultsFilterPlaceholder: string;
|
|
178
181
|
modelDefaultsFilterEmpty: string;
|
|
182
|
+
modelDefaultsNoModels: string;
|
|
183
|
+
modelDefaultsNoAccount: string;
|
|
184
|
+
modelDefaultsDiscoveryFailed: string;
|
|
179
185
|
generating: string;
|
|
180
186
|
image: string;
|
|
181
187
|
viewImage: string;
|
|
@@ -269,6 +275,8 @@ export declare const zh: {
|
|
|
269
275
|
modelsContextBounds: string;
|
|
270
276
|
modelsContextHint: string;
|
|
271
277
|
modelsContextInvalid: string;
|
|
278
|
+
modelsSubagentSpeed: string;
|
|
279
|
+
modelsSubagentSpeedHint: string;
|
|
272
280
|
modelsTools: string;
|
|
273
281
|
modelsToolsHint: string;
|
|
274
282
|
modelsImage: string;
|
|
@@ -318,6 +326,7 @@ export declare const zh: {
|
|
|
318
326
|
usageRefresh: string;
|
|
319
327
|
usageLoading: string;
|
|
320
328
|
usageEmpty: string;
|
|
329
|
+
usageNoVisibleModels: string;
|
|
321
330
|
usageUnsupported: string;
|
|
322
331
|
usageError: string;
|
|
323
332
|
usageSession: string;
|
|
@@ -416,6 +425,9 @@ export declare const zh: {
|
|
|
416
425
|
modelDefaultsSummaryEmpty: string;
|
|
417
426
|
modelDefaultsFilterPlaceholder: string;
|
|
418
427
|
modelDefaultsFilterEmpty: string;
|
|
428
|
+
modelDefaultsNoModels: string;
|
|
429
|
+
modelDefaultsNoAccount: string;
|
|
430
|
+
modelDefaultsDiscoveryFailed: string;
|
|
419
431
|
generating: string;
|
|
420
432
|
image: string;
|
|
421
433
|
viewImage: string;
|
package/lib/client/locales.js
CHANGED
|
@@ -21,7 +21,7 @@ export const en = {
|
|
|
21
21
|
accountsCatalogUnavailable: 'Model discovery is currently unavailable. Saved model selections are preserved.',
|
|
22
22
|
modelsPartialSave: 'Some reasoning defaults were saved. Remaining changes are still in the draft; retry to finish.',
|
|
23
23
|
modelsEdit: 'Edit model list',
|
|
24
|
-
modelsHint: 'Configure model visibility, default reasoning effort, context and tools here; Save changes below submits them together with the account settings. Hidden models remain usable by existing sessions; tool changes apply to new sessions.',
|
|
24
|
+
modelsHint: 'Configure model visibility, default reasoning effort, subagent speed, context and tools here; Save changes below submits them together with the account settings. Hidden models remain usable by existing sessions; tool changes apply to new sessions.',
|
|
25
25
|
modelsAutomatic: 'Automatically show all models, including new models',
|
|
26
26
|
modelsSelectAll: 'Select all',
|
|
27
27
|
modelsSelectNone: 'Clear selection',
|
|
@@ -29,6 +29,8 @@ export const en = {
|
|
|
29
29
|
modelsContextBounds: 'Default {default} · Maximum {max}',
|
|
30
30
|
modelsContextHint: 'Leave blank to follow the provider. The requested window is capped by the account’s advertised maximum; account pools use the smallest member window. This controls local history and compaction, not the API’s capacity.',
|
|
31
31
|
modelsContextInvalid: 'Enter a positive whole number of tokens for {model}.',
|
|
32
|
+
modelsSubagentSpeed: 'Subagent default speed',
|
|
33
|
+
modelsSubagentSpeedHint: 'Applies to subsequent subagent requests using this model, regardless of the parent model. Main conversations keep their own Speed setting. Fast requires model support.',
|
|
32
34
|
modelsTools: 'Provider tools',
|
|
33
35
|
modelsToolsHint: 'Changes apply only to sessions created after saving. Existing sessions keep their tools. Shared tools remain available if any supporting provider is enabled.',
|
|
34
36
|
modelsImage: 'Image generation',
|
|
@@ -78,6 +80,7 @@ export const en = {
|
|
|
78
80
|
usageRefresh: 'Refresh',
|
|
79
81
|
usageLoading: 'Loading usage…',
|
|
80
82
|
usageEmpty: 'No usage windows reported.',
|
|
83
|
+
usageNoVisibleModels: 'No usage windows for the models visible in the picker.',
|
|
81
84
|
usageUnsupported: 'This endpoint has no usage surface.',
|
|
82
85
|
usageError: 'Usage lookup failed: {message}',
|
|
83
86
|
usageSession: '5-hour window',
|
|
@@ -176,6 +179,9 @@ export const en = {
|
|
|
176
179
|
modelDefaultsSummaryEmpty: 'No model advertises reasoning levels',
|
|
177
180
|
modelDefaultsFilterPlaceholder: 'Filter models',
|
|
178
181
|
modelDefaultsFilterEmpty: 'No model matches “{query}”.',
|
|
182
|
+
modelDefaultsNoModels: 'This provider reports no models.',
|
|
183
|
+
modelDefaultsNoAccount: 'No connected accounts. Connect one on the provider card first.',
|
|
184
|
+
modelDefaultsDiscoveryFailed: 'Model discovery failed or timed out. Use Refresh to try again.',
|
|
179
185
|
generating: 'Generating image…',
|
|
180
186
|
image: 'image',
|
|
181
187
|
viewImage: 'View image',
|
|
@@ -269,7 +275,7 @@ export const zh = {
|
|
|
269
275
|
accountsCatalogUnavailable: '暂时无法获取模型目录,已保存的模型选择会被保留。',
|
|
270
276
|
modelsPartialSave: '部分推理档已保存,其余更改仍保留在草稿中,请重试完成保存。',
|
|
271
277
|
modelsEdit: '编辑模型列表',
|
|
272
|
-
modelsHint: '
|
|
278
|
+
modelsHint: '在此配置模型显示、默认推理档、子代理速度、上下文与工具,底部的「保存更改」会连同账号设置一起提交。隐藏模型不影响已有会话;工具开关对新建会话生效。',
|
|
273
279
|
modelsAutomatic: '自动显示全部模型(包含以后新增的模型)',
|
|
274
280
|
modelsSelectAll: '全选',
|
|
275
281
|
modelsSelectNone: '清空选择',
|
|
@@ -277,6 +283,8 @@ export const zh = {
|
|
|
277
283
|
modelsContextBounds: '默认 {default} · 最大 {max}',
|
|
278
284
|
modelsContextHint: '留空跟随服务商。配置值按账号返回的最大窗口限制;账号池取各成员的最小窗口。此设置控制本地历史保留和压缩时机,不会扩大 API 本身的容量。',
|
|
279
285
|
modelsContextInvalid: '请为 {model} 输入正整数 token 数。',
|
|
286
|
+
modelsSubagentSpeed: '子代理默认速度',
|
|
287
|
+
modelsSubagentSpeedHint: '对使用此模型的子代理后续请求生效,与父会话模型无关。主会话仍使用自己的速度设置;快速档仅在模型支持时生效。',
|
|
280
288
|
modelsTools: 'Provider 工具',
|
|
281
289
|
modelsToolsHint: '仅对保存后新建的会话生效,已有会话保留原工具。任一支持的服务商启用共享工具时,该工具仍会显示。',
|
|
282
290
|
modelsImage: '图片生成',
|
|
@@ -326,6 +334,7 @@ export const zh = {
|
|
|
326
334
|
usageRefresh: '刷新',
|
|
327
335
|
usageLoading: '用量加载中…',
|
|
328
336
|
usageEmpty: '服务商未返回任何用量窗口。',
|
|
337
|
+
usageNoVisibleModels: '选择器中的可见模型没有任何用量窗口。',
|
|
329
338
|
usageUnsupported: '该端点不提供用量查询。',
|
|
330
339
|
usageError: '用量查询失败:{message}',
|
|
331
340
|
usageSession: '5 小时窗口',
|
|
@@ -424,6 +433,9 @@ export const zh = {
|
|
|
424
433
|
modelDefaultsSummaryEmpty: '没有模型声明推理档',
|
|
425
434
|
modelDefaultsFilterPlaceholder: '筛选模型',
|
|
426
435
|
modelDefaultsFilterEmpty: '没有匹配「{query}」的模型。',
|
|
436
|
+
modelDefaultsNoModels: '这个服务商没有报告任何模型。',
|
|
437
|
+
modelDefaultsNoAccount: '还没有连接账号。请先在卡片上连接一个账号。',
|
|
438
|
+
modelDefaultsDiscoveryFailed: '模型列表发现失败或超时。可以点「刷新」重试。',
|
|
427
439
|
generating: '正在生成图片…',
|
|
428
440
|
image: '图片',
|
|
429
441
|
viewImage: '查看图片',
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SubagentSpeedTier } from '../provider-settings.js';
|
|
2
|
+
import type { AccountCatalogRow } from './account-preferences.js';
|
|
3
|
+
/** What the model editor shows for the current filter state. */
|
|
4
|
+
export type ModelListState = 'provider-empty' | 'filter-empty' | 'rows';
|
|
5
|
+
/** Distinguish an empty provider from a query that filtered out every row. */
|
|
6
|
+
export declare function modelListState(total: number, matched: number): ModelListState;
|
|
7
|
+
/**
|
|
8
|
+
* Retain saved model ids when discovery returns no rows. A temporary catalog
|
|
9
|
+
* failure must not hide the visibility or subagent-speed settings they edit.
|
|
10
|
+
*/
|
|
11
|
+
export declare function savedModelIds(visibleModels: readonly string[] | undefined, subagentSpeeds: Readonly<Record<string, SubagentSpeedTier>> | undefined): string[];
|
|
12
|
+
/** Which notice replaces inert model controls. */
|
|
13
|
+
export type ModelEditorNotice = 'noAccount' | 'discoveryFailed' | 'noModels';
|
|
14
|
+
/**
|
|
15
|
+
* Use account discovery evidence to explain an empty provider. Connected
|
|
16
|
+
* accounts with failed discovery need a retry, not another login prompt.
|
|
17
|
+
*/
|
|
18
|
+
export declare function modelEditorNotice(accounts: readonly AccountCatalogRow[] | undefined): ModelEditorNotice;
|
|
19
|
+
/** Everything the component needs to choose rows or one explanatory notice. */
|
|
20
|
+
export interface ModelEditorState {
|
|
21
|
+
listState: ModelListState;
|
|
22
|
+
/** Rendered only when `listState` is `provider-empty`. */
|
|
23
|
+
noticeKey: (typeof NOTICE_KEYS)[ModelEditorNotice];
|
|
24
|
+
}
|
|
25
|
+
/** Locale keys for the three empty states. */
|
|
26
|
+
export declare const NOTICE_KEYS: {
|
|
27
|
+
readonly noAccount: "modelDefaultsNoAccount";
|
|
28
|
+
readonly discoveryFailed: "modelDefaultsDiscoveryFailed";
|
|
29
|
+
readonly noModels: "modelDefaultsNoModels";
|
|
30
|
+
};
|
|
31
|
+
/** Combine discovered and saved rows, filter results, and discovery evidence. */
|
|
32
|
+
export declare function modelEditorState(input: {
|
|
33
|
+
discovered: number;
|
|
34
|
+
saved: number;
|
|
35
|
+
matched: number;
|
|
36
|
+
accounts: readonly AccountCatalogRow[] | undefined;
|
|
37
|
+
}): ModelEditorState;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Distinguish an empty provider from a query that filtered out every row. */
|
|
2
|
+
export function modelListState(total, matched) {
|
|
3
|
+
if (total === 0)
|
|
4
|
+
return 'provider-empty';
|
|
5
|
+
return matched === 0 ? 'filter-empty' : 'rows';
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Retain saved model ids when discovery returns no rows. A temporary catalog
|
|
9
|
+
* failure must not hide the visibility or subagent-speed settings they edit.
|
|
10
|
+
*/
|
|
11
|
+
export function savedModelIds(visibleModels, subagentSpeeds) {
|
|
12
|
+
return [
|
|
13
|
+
...new Set([
|
|
14
|
+
...(visibleModels ?? []),
|
|
15
|
+
...Object.keys(subagentSpeeds ?? {}),
|
|
16
|
+
]),
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Use account discovery evidence to explain an empty provider. Connected
|
|
21
|
+
* accounts with failed discovery need a retry, not another login prompt.
|
|
22
|
+
*/
|
|
23
|
+
export function modelEditorNotice(accounts) {
|
|
24
|
+
// Account-neutral editors such as Cursor use the neutral empty-catalog copy.
|
|
25
|
+
if (accounts === undefined)
|
|
26
|
+
return 'noModels';
|
|
27
|
+
if (accounts.length === 0)
|
|
28
|
+
return 'noAccount';
|
|
29
|
+
return accounts.every((account) => account.unavailable === true)
|
|
30
|
+
? 'discoveryFailed'
|
|
31
|
+
: 'noModels';
|
|
32
|
+
}
|
|
33
|
+
/** Locale keys for the three empty states. */
|
|
34
|
+
export const NOTICE_KEYS = {
|
|
35
|
+
noAccount: 'modelDefaultsNoAccount',
|
|
36
|
+
discoveryFailed: 'modelDefaultsDiscoveryFailed',
|
|
37
|
+
noModels: 'modelDefaultsNoModels',
|
|
38
|
+
};
|
|
39
|
+
/** Combine discovered and saved rows, filter results, and discovery evidence. */
|
|
40
|
+
export function modelEditorState(input) {
|
|
41
|
+
return {
|
|
42
|
+
listState: modelListState(input.discovered + input.saved, input.matched),
|
|
43
|
+
noticeKey: NOTICE_KEYS[modelEditorNotice(input.accounts)],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { UsageWindow } from './SubscriptionsSection.js';
|
|
2
|
+
/**
|
|
3
|
+
* Keep model-scoped Google quotas aligned with the saved model-picker selection.
|
|
4
|
+
* Account-wide windows and other providers are unaffected; an absent selection
|
|
5
|
+
* follows discovery, while an empty selection hides every model-scoped window.
|
|
6
|
+
*/
|
|
7
|
+
export declare function visibleUsageWindows(provider: string, windows: UsageWindow[] | undefined, visibleModels: string[] | undefined): UsageWindow[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep model-scoped Google quotas aligned with the saved model-picker selection.
|
|
3
|
+
* Account-wide windows and other providers are unaffected; an absent selection
|
|
4
|
+
* follows discovery, while an empty selection hides every model-scoped window.
|
|
5
|
+
*/
|
|
6
|
+
export function visibleUsageWindows(provider, windows, visibleModels) {
|
|
7
|
+
if (windows === undefined)
|
|
8
|
+
return [];
|
|
9
|
+
if (provider !== 'antigravity' || visibleModels === undefined)
|
|
10
|
+
return windows;
|
|
11
|
+
return windows.filter((window) => !window.scope || visibleModels.includes(window.scope));
|
|
12
|
+
}
|