dsh-subscriptions 0.1.5 → 0.2.2
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 +212 -154
- package/lib/auth/claude-code-creds.js +31 -9
- package/lib/auth/device-flow.js +16 -9
- package/lib/auth/oauth-flow.js +13 -10
- package/lib/auth/rpc.d.ts +73 -2
- package/lib/auth/rpc.js +124 -23
- package/lib/auth/store.js +80 -31
- package/lib/client/CursorCard.d.ts +1 -1
- package/lib/client/CursorCard.js +50 -16
- package/lib/client/ExternalUsageCards.d.ts +1 -1
- package/lib/client/ExternalUsageCards.js +37 -16
- package/lib/client/ImageGallery.d.ts +2 -2
- package/lib/client/ImageGallery.js +92 -33
- package/lib/client/ImageGenerateToolview.js +50 -14
- package/lib/client/OllamaCard.d.ts +13 -0
- package/lib/client/OllamaCard.js +94 -0
- package/lib/client/OllamaSettingsManager.d.ts +24 -0
- package/lib/client/OllamaSettingsManager.js +329 -0
- package/lib/client/ProviderAccountManager.d.ts +1 -1
- package/lib/client/ProviderAccountManager.js +164 -49
- package/lib/client/ProviderModelEditor.js +109 -26
- package/lib/client/SpeedSelect.d.ts +62 -14
- package/lib/client/SpeedSelect.js +257 -46
- package/lib/client/SubscriptionUsageBadge.d.ts +5 -11
- package/lib/client/SubscriptionUsageBadge.js +301 -112
- package/lib/client/SubscriptionsSection.d.ts +2 -0
- package/lib/client/SubscriptionsSection.js +259 -85
- package/lib/client/UsageBadgeDisplaySetting.js +35 -12
- package/lib/client/UsageMeter.d.ts +1 -1
- package/lib/client/UsageMeter.js +69 -27
- package/lib/client/VideoGenerateToolview.js +69 -17
- package/lib/client/account-preferences.d.ts +8 -0
- package/lib/client/account-preferences.js +20 -5
- package/lib/client/host-contract.d.ts +11 -2
- package/lib/client/host-contract.js +20 -3
- package/lib/client/index.d.ts +5 -5
- package/lib/client/index.js +34 -12
- package/lib/client/locales.d.ts +78 -0
- package/lib/client/locales.js +121 -34
- package/lib/client/ollama-draft.d.ts +229 -0
- package/lib/client/ollama-draft.js +255 -0
- package/lib/client/reset-action.js +6 -4
- package/lib/client/reset-credits-view.d.ts +1 -1
- package/lib/client/reset-credits-view.js +66 -17
- package/lib/client/subscription-card-styles.js +94 -24
- package/lib/client/usage-color-preferences.js +4 -2
- package/lib/client/usage-pace.d.ts +7 -0
- package/lib/client/usage-pace.js +43 -10
- package/lib/client.js +1647 -266
- package/lib/client.js.map +1 -1
- package/lib/http.js +1 -1
- package/lib/index.d.ts +28 -3
- package/lib/index.js +9329 -8217
- package/lib/model-defaults.js +4 -2
- package/lib/provider-settings.d.ts +14 -1
- package/lib/provider-settings.js +64 -20
- package/lib/providers/account-preferences.js +51 -22
- package/lib/providers/accounts.js +15 -9
- package/lib/providers/antigravity-oauth-client.js +2 -2
- package/lib/providers/antigravity.js +141 -68
- package/lib/providers/catalog-store.js +120 -41
- package/lib/providers/claude-cli-version.js +3 -3
- package/lib/providers/claude.d.ts +23 -3
- package/lib/providers/claude.js +167 -92
- package/lib/providers/codex-client-version.js +3 -3
- package/lib/providers/codex-search.js +40 -13
- package/lib/providers/codex.js +196 -112
- package/lib/providers/common.d.ts +14 -0
- package/lib/providers/common.js +118 -38
- package/lib/providers/copilot.js +143 -82
- package/lib/providers/cursor-adapter.js +42 -18
- package/lib/providers/cursor-auth.d.ts +1 -1
- package/lib/providers/cursor-auth.js +69 -24
- package/lib/providers/cursor-model-settings.js +14 -5
- package/lib/providers/cursor-usage.js +27 -13
- package/lib/providers/external-usage-controller.d.ts +9 -2
- package/lib/providers/external-usage-controller.js +30 -7
- package/lib/providers/external-usage.d.ts +8 -0
- package/lib/providers/external-usage.js +140 -20
- package/lib/providers/grok.js +152 -89
- package/lib/providers/image-pool.js +29 -11
- package/lib/providers/minimax-usage.js +31 -11
- package/lib/providers/npm-cli-version.js +10 -3
- package/lib/providers/ollama-settings.d.ts +90 -0
- package/lib/providers/ollama-settings.js +213 -0
- package/lib/providers/ollama-web.d.ts +46 -0
- package/lib/providers/ollama-web.js +231 -0
- package/lib/providers/ollama.d.ts +107 -0
- package/lib/providers/ollama.js +608 -0
- package/lib/providers/pool-family.js +11 -3
- package/lib/providers/pool-health.js +28 -8
- package/lib/providers/pool-usage.js +16 -9
- package/lib/providers/pool.d.ts +8 -0
- package/lib/providers/pool.js +81 -42
- package/lib/providers/rate-limit.js +8 -2
- package/lib/providers/replay.js +40 -17
- package/lib/providers/reset-redemption.js +18 -6
- package/lib/tools/image-generate.js +123 -65
- package/lib/tools/registration.js +5 -2
- package/lib/tools/video-generate.js +51 -31
- package/lib/tools/x-search.js +50 -22
- package/lib/translate/anthropic.js +85 -37
- package/lib/translate/antigravity-schema.js +71 -28
- package/lib/translate/antigravity-thinking.js +22 -9
- package/lib/translate/antigravity.js +141 -50
- package/lib/translate/chat-completions.js +67 -23
- package/lib/translate/resolved.js +55 -21
- package/lib/translate/responses.js +69 -29
- package/lib/translate/sse.js +4 -1
- package/package.json +26 -4
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useId, useRef, useState } from 'react';
|
|
3
3
|
import { callSubscriptionsAuth } from './subscriptions-rpc.js';
|
|
4
|
-
import { accountModelRows, accountPoolSelection, mergeAccountChanges, mergeLatestAccounts } from './account-preferences.js';
|
|
4
|
+
import { accountModelRows, accountPoolSelection, mergeAccountChanges, mergeLatestAccounts, mergePromptCacheTtl, } from './account-preferences.js';
|
|
5
5
|
import { ProviderModelEditor } from './ProviderModelEditor.js';
|
|
6
6
|
import { providerSettingsCss } from './provider-settings-styles.js';
|
|
7
7
|
const border = '0.5px solid var(--dsw-alias-border-l2)';
|
|
8
8
|
const stack = { display: 'grid', gap: 12, minWidth: 0 };
|
|
9
|
-
const actions = {
|
|
9
|
+
const actions = {
|
|
10
|
+
display: 'flex',
|
|
11
|
+
flexWrap: 'wrap',
|
|
12
|
+
gap: 8,
|
|
13
|
+
alignItems: 'center',
|
|
14
|
+
};
|
|
10
15
|
/** Native top-layer dialog provides keyboard containment, Escape and focus restoration. */
|
|
11
|
-
export function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
16
|
+
export function ProviderAccountManager({ provider, name, rpc, t, onClose, }) {
|
|
12
17
|
const dialog = useRef(null);
|
|
13
18
|
const title = useId();
|
|
14
19
|
const description = useId();
|
|
@@ -20,6 +25,9 @@ export function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
20
25
|
const [saveError, setSaveError] = useState('');
|
|
21
26
|
const [attempt, setAttempt] = useState(0);
|
|
22
27
|
const [modelsDirty, setModelsDirty] = useState(false);
|
|
28
|
+
// The Claude prompt-cache TTL draft: undefined follows the plugin config.
|
|
29
|
+
const [ttlDraft, setTtlDraft] = useState();
|
|
30
|
+
const [ttlDirty, setTtlDirty] = useState(false);
|
|
23
31
|
const alive = useRef(true);
|
|
24
32
|
const saveLock = useRef(false);
|
|
25
33
|
const editor = useRef(null);
|
|
@@ -39,18 +47,34 @@ export function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
39
47
|
let current = true;
|
|
40
48
|
setLoading(true);
|
|
41
49
|
setError('');
|
|
42
|
-
void callSubscriptionsAuth(rpc, provider === 'cursor-subscription'
|
|
43
|
-
|
|
50
|
+
void callSubscriptionsAuth(rpc, provider === 'cursor-subscription'
|
|
51
|
+
? 'cursorSettings'
|
|
52
|
+
: 'providerSettings', { provider })
|
|
53
|
+
.then((data) => {
|
|
54
|
+
if (current) {
|
|
44
55
|
setCatalog(data);
|
|
45
|
-
|
|
56
|
+
setTtlDraft(data.settings.promptCacheTtl);
|
|
57
|
+
setTtlDirty(false);
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
.catch((error) => {
|
|
61
|
+
if (current)
|
|
62
|
+
setError(t('accountsLoadFailed', {
|
|
63
|
+
message: error instanceof Error ? error.message : String(error),
|
|
64
|
+
}));
|
|
65
|
+
})
|
|
66
|
+
.finally(() => {
|
|
46
67
|
if (current)
|
|
47
|
-
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
|
|
68
|
+
setLoading(false);
|
|
69
|
+
});
|
|
70
|
+
return () => {
|
|
71
|
+
current = false;
|
|
72
|
+
};
|
|
73
|
+
// `t` only formats the load error. A new translator must not refetch.
|
|
74
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- refetch on provider, rpc, and attempt only
|
|
51
75
|
}, [provider, rpc, attempt]);
|
|
52
76
|
function edit(key, next) {
|
|
53
|
-
setChanges(current => ({ ...current, [key]: next }));
|
|
77
|
+
setChanges((current) => ({ ...current, [key]: next }));
|
|
54
78
|
}
|
|
55
79
|
/**
|
|
56
80
|
* One submit for both drafts: the model editor's (visibility, effort,
|
|
@@ -72,7 +96,11 @@ export function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
72
96
|
try {
|
|
73
97
|
if (provider !== 'cursor-subscription') {
|
|
74
98
|
for (const { model, effort } of models.efforts) {
|
|
75
|
-
await callSubscriptionsAuth(rpc, 'setModelDefault', {
|
|
99
|
+
await callSubscriptionsAuth(rpc, 'setModelDefault', {
|
|
100
|
+
provider,
|
|
101
|
+
model,
|
|
102
|
+
...(effort ? { effort } : {}),
|
|
103
|
+
});
|
|
76
104
|
savedEfforts++;
|
|
77
105
|
if (!alive.current)
|
|
78
106
|
return;
|
|
@@ -80,15 +108,28 @@ export function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
80
108
|
}
|
|
81
109
|
// Re-read before writing: another client may have saved since this
|
|
82
110
|
// dialog loaded, and a stale snapshot must never be written back.
|
|
83
|
-
const latest = await callSubscriptionsAuth(rpc, provider === 'cursor-subscription'
|
|
111
|
+
const latest = await callSubscriptionsAuth(rpc, provider === 'cursor-subscription'
|
|
112
|
+
? 'cursorSettings'
|
|
113
|
+
: 'providerSettings', { provider });
|
|
84
114
|
if (!alive.current)
|
|
85
115
|
return;
|
|
86
116
|
if (provider === 'cursor-subscription') {
|
|
87
|
-
await callSubscriptionsAuth(rpc, 'cursorSetSettings', {
|
|
117
|
+
await callSubscriptionsAuth(rpc, 'cursorSetSettings', {
|
|
118
|
+
settings: models.settings ?? latest.settings,
|
|
119
|
+
});
|
|
88
120
|
}
|
|
89
121
|
else {
|
|
90
|
-
const base = models.settings === undefined
|
|
91
|
-
|
|
122
|
+
const base = models.settings === undefined
|
|
123
|
+
? latest.settings
|
|
124
|
+
: mergeLatestAccounts(models.settings, latest.settings);
|
|
125
|
+
// The editor's draft carries the TTL it loaded, so the dialog always
|
|
126
|
+
// replaces it: the picker's choice when touched, otherwise the value
|
|
127
|
+
// from the freshly re-read settings.
|
|
128
|
+
const withTtl = mergePromptCacheTtl(base, ttlDirty ? ttlDraft : latest.settings.promptCacheTtl);
|
|
129
|
+
await callSubscriptionsAuth(rpc, 'setProviderSettings', {
|
|
130
|
+
provider,
|
|
131
|
+
settings: mergeAccountChanges(withTtl, changes),
|
|
132
|
+
});
|
|
92
133
|
}
|
|
93
134
|
if (alive.current)
|
|
94
135
|
onClose();
|
|
@@ -96,7 +137,8 @@ export function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
96
137
|
catch (error) {
|
|
97
138
|
if (alive.current) {
|
|
98
139
|
const message = error instanceof Error ? error.message : String(error);
|
|
99
|
-
setSaveError((savedEfforts ? t('modelsPartialSave') + ' ' : '') +
|
|
140
|
+
setSaveError((savedEfforts ? t('modelsPartialSave') + ' ' : '') +
|
|
141
|
+
t('accountsSaveFailed', { message }));
|
|
100
142
|
}
|
|
101
143
|
}
|
|
102
144
|
finally {
|
|
@@ -105,36 +147,109 @@ export function ProviderAccountManager({ provider, name, rpc, t, onClose }) {
|
|
|
105
147
|
setSaving(false);
|
|
106
148
|
}
|
|
107
149
|
}
|
|
108
|
-
return _jsxs("dialog", { className: "dsh-subscription-manager", ref: dialog, "aria-labelledby": title, "aria-describedby": description, onCancel: event => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
150
|
+
return (_jsxs("dialog", { className: "dsh-subscription-manager", ref: dialog, "aria-labelledby": title, "aria-describedby": description, onCancel: (event) => {
|
|
151
|
+
if (saveLock.current)
|
|
152
|
+
event.preventDefault();
|
|
153
|
+
}, onClose: onClose, style: {
|
|
154
|
+
width: 620,
|
|
155
|
+
maxWidth: 'calc(100vw - 32px)',
|
|
156
|
+
maxHeight: 'calc(100dvh - 32px)',
|
|
157
|
+
overflow: 'auto',
|
|
158
|
+
}, 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'
|
|
159
|
+
? 'cursorAccountsHint'
|
|
160
|
+
: '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'
|
|
161
|
+
? 'cursorManageSignIn'
|
|
162
|
+
: 'accountsEmpty') })), provider === 'cursor-subscription' &&
|
|
163
|
+
catalog.accounts.length > 0 && (_jsxs("fieldset", { style: {
|
|
164
|
+
border,
|
|
165
|
+
borderRadius: 'var(--dsw-radius-lg, 12px)',
|
|
166
|
+
padding: 14,
|
|
167
|
+
}, children: [_jsx("legend", { style: { padding: '0 6px' }, children: name }), _jsx("p", { className: "dsh-subscription-hint", children: t('cursorConnected') })] })), provider !== 'cursor-subscription' &&
|
|
168
|
+
catalog.accounts.map((account) => {
|
|
169
|
+
const preferences = changes[account.key] ??
|
|
170
|
+
catalog.settings.accounts?.[account.key] ??
|
|
171
|
+
{};
|
|
172
|
+
const selected = accountPoolSelection(preferences, account.models);
|
|
173
|
+
const models = accountModelRows(account, preferences);
|
|
174
|
+
return (_jsxs("fieldset", { style: {
|
|
175
|
+
...stack,
|
|
176
|
+
border,
|
|
177
|
+
borderRadius: 'var(--dsw-radius-lg, 12px)',
|
|
178
|
+
padding: 14,
|
|
179
|
+
margin: 0,
|
|
180
|
+
}, children: [_jsx("legend", { style: {
|
|
181
|
+
padding: '0 6px',
|
|
182
|
+
overflowWrap: 'anywhere',
|
|
183
|
+
maxWidth: '100%',
|
|
184
|
+
}, 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) => {
|
|
185
|
+
const next = { ...preferences };
|
|
186
|
+
if (event.target.value.trim())
|
|
187
|
+
next.alias = event.target.value;
|
|
188
|
+
else
|
|
189
|
+
delete next.alias;
|
|
190
|
+
edit(account.key, next);
|
|
191
|
+
} })] }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: preferences.poolEnabled !== false, onChange: (event) => edit(account.key, {
|
|
192
|
+
...preferences,
|
|
193
|
+
poolEnabled: event.target.checked,
|
|
194
|
+
}) }), ' ', t('accountsPoolEnabled')] }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: preferences.independentEntry === true, onChange: (event) => edit(account.key, {
|
|
195
|
+
...preferences,
|
|
196
|
+
independentEntry: event.target.checked,
|
|
197
|
+
}) }), ' ', 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
|
|
198
|
+
? t('accountsModelsAll')
|
|
199
|
+
: t('accountsModelsCount', {
|
|
200
|
+
count: preferences.poolModels.length,
|
|
201
|
+
})] }), _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) => {
|
|
202
|
+
const next = { ...preferences };
|
|
203
|
+
if (event.target.checked)
|
|
204
|
+
delete next.poolModels;
|
|
205
|
+
else
|
|
206
|
+
next.poolModels = account.models.map((model) => model.id);
|
|
207
|
+
edit(account.key, next);
|
|
208
|
+
} }), ' ', t('accountsModelsAutomatic')] }), _jsxs("div", { style: actions, children: [_jsx("button", { type: "button", onClick: () => edit(account.key, {
|
|
209
|
+
...preferences,
|
|
210
|
+
poolModels: models.map((model) => model.id),
|
|
211
|
+
}), children: t('modelsSelectAll') }), _jsx("button", { type: "button", onClick: () => edit(account.key, {
|
|
212
|
+
...preferences,
|
|
213
|
+
poolModels: [],
|
|
214
|
+
}), children: t('modelsSelectNone') })] }), _jsxs("div", { style: {
|
|
215
|
+
...stack,
|
|
216
|
+
gap: 8,
|
|
217
|
+
maxHeight: 240,
|
|
218
|
+
overflowY: 'auto',
|
|
219
|
+
}, children: [models.map((model) => (_jsxs("label", { style: { overflowWrap: 'anywhere' }, children: [_jsx("input", { type: "checkbox", checked: selected.has(model.id), onChange: (event) => {
|
|
220
|
+
const next = new Set(selected);
|
|
221
|
+
if (event.target.checked)
|
|
222
|
+
next.add(model.id);
|
|
223
|
+
else
|
|
224
|
+
next.delete(model.id);
|
|
225
|
+
edit(account.key, {
|
|
226
|
+
...preferences,
|
|
227
|
+
poolModels: [...next],
|
|
228
|
+
});
|
|
229
|
+
} }), ' ', model.name, model.unavailable &&
|
|
230
|
+
` (${t('modelsUnavailable')})`] }, model.id))), models.length === 0 && (_jsx("p", { className: "dsh-subscription-hint", children: t('accountsNoModels') }))] })] })] })] }, account.key));
|
|
231
|
+
})] })), (provider !== 'cursor-subscription' ||
|
|
232
|
+
(catalog?.accounts.length ?? 0) > 0) && (_jsx(ProviderModelEditor, { ref: editor, provider: provider, rpc: rpc, t: t, disabled: saving, onDirtyChange: setModelsDirty })), provider === 'claude' && catalog && (_jsxs("div", { style: {
|
|
233
|
+
...stack,
|
|
234
|
+
gap: 6,
|
|
235
|
+
borderTop: border,
|
|
236
|
+
marginTop: 12,
|
|
237
|
+
paddingTop: 12,
|
|
238
|
+
}, children: [_jsxs("label", { style: { ...stack, gap: 6 }, children: [_jsx("span", { className: "dsh-subscription-label", children: t('claudeCacheTtl') }), _jsxs("select", { "aria-label": t('claudeCacheTtl'), style: { width: 220 }, value: ttlDraft ?? '', disabled: saving, onChange: (event) => {
|
|
239
|
+
const value = event.target.value;
|
|
240
|
+
setTtlDraft(value === '' ? undefined : value);
|
|
241
|
+
setTtlDirty(true);
|
|
242
|
+
}, children: [_jsx("option", { value: "", children: t('claudeCacheTtlFollowConfig') }), _jsx("option", { value: "5m", children: t('claudeCacheTtl5m') }), _jsx("option", { value: "1h", children: t('claudeCacheTtl1h') })] })] }), _jsx("p", { className: "dsh-subscription-hint", children: t('claudeCacheTtlHint') })] })), _jsxs("footer", { style: {
|
|
243
|
+
...stack,
|
|
244
|
+
gap: 8,
|
|
245
|
+
borderTop: border,
|
|
246
|
+
padding: '14px 0 8px',
|
|
247
|
+
position: 'sticky',
|
|
248
|
+
bottom: 0,
|
|
249
|
+
background: 'var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1))',
|
|
250
|
+
}, 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 ||
|
|
251
|
+
saving ||
|
|
252
|
+
(!Object.keys(changes).length && !modelsDirty && !ttlDirty), onClick: () => {
|
|
253
|
+
void save();
|
|
254
|
+
}, children: saving ? t('modelDefaultsSaving') : t('modelsSave') })] })] })] })] }));
|
|
140
255
|
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
2
|
+
import { forwardRef, useEffect, useImperativeHandle, useRef, useState, } from 'react';
|
|
3
3
|
import { callSubscriptionsAuth } from './subscriptions-rpc.js';
|
|
4
|
+
/** Providers whose runtime honours a local context-window override. */
|
|
5
|
+
const supportsContext = (provider) => provider === 'codex' || provider === 'claude';
|
|
4
6
|
const border = '0.5px solid var(--dsw-alias-border-l2, #ddd)';
|
|
5
|
-
const actions = {
|
|
7
|
+
const actions = {
|
|
8
|
+
display: 'flex',
|
|
9
|
+
flexWrap: 'wrap',
|
|
10
|
+
gap: 8,
|
|
11
|
+
alignItems: 'center',
|
|
12
|
+
};
|
|
6
13
|
/**
|
|
7
14
|
* Embedded section of the account manager dialog. It owns the model draft
|
|
8
15
|
* (visibility, effort, context, tools) but not its submission: the dialog
|
|
@@ -19,12 +26,19 @@ export const ProviderModelEditor = forwardRef(function ProviderModelEditor({ pro
|
|
|
19
26
|
const [dirty, setDirty] = useState(false);
|
|
20
27
|
const [error, setError] = useState('');
|
|
21
28
|
const generation = useRef(0);
|
|
22
|
-
useEffect(() => () => {
|
|
23
|
-
|
|
29
|
+
useEffect(() => () => {
|
|
30
|
+
generation.current++;
|
|
31
|
+
}, []);
|
|
32
|
+
// Notify only when dirty changes. The parent passes a fresh callback each render.
|
|
33
|
+
/* eslint-disable react-hooks/exhaustive-deps -- onDirtyChange is not a trigger */
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
onDirtyChange?.(dirty);
|
|
36
|
+
}, [dirty]);
|
|
37
|
+
/* eslint-enable react-hooks/exhaustive-deps */
|
|
24
38
|
function reset(data) {
|
|
25
39
|
setCatalog(data);
|
|
26
40
|
setDraft(data.settings);
|
|
27
|
-
setEfforts(Object.fromEntries(data.models.map(model => [model.id, model.configured ?? ''])));
|
|
41
|
+
setEfforts(Object.fromEntries(data.models.map((model) => [model.id, model.configured ?? ''])));
|
|
28
42
|
setContexts(Object.fromEntries(Object.entries(data.settings.contextWindows ?? {}).map(([id, value]) => [id, String(value)])));
|
|
29
43
|
setDirty(false);
|
|
30
44
|
}
|
|
@@ -33,7 +47,9 @@ export const ProviderModelEditor = forwardRef(function ProviderModelEditor({ pro
|
|
|
33
47
|
setBusy(true);
|
|
34
48
|
setError('');
|
|
35
49
|
try {
|
|
36
|
-
const data = await callSubscriptionsAuth(rpc, provider === 'cursor-subscription'
|
|
50
|
+
const data = await callSubscriptionsAuth(rpc, provider === 'cursor-subscription'
|
|
51
|
+
? 'cursorSettings'
|
|
52
|
+
: 'providerSettings', { provider, force });
|
|
37
53
|
if (generation.current === request)
|
|
38
54
|
reset(data);
|
|
39
55
|
}
|
|
@@ -46,8 +62,16 @@ export const ProviderModelEditor = forwardRef(function ProviderModelEditor({ pro
|
|
|
46
62
|
setBusy(false);
|
|
47
63
|
}
|
|
48
64
|
}
|
|
49
|
-
function edit(next) {
|
|
50
|
-
|
|
65
|
+
function edit(next) {
|
|
66
|
+
setDraft(next);
|
|
67
|
+
setDirty(true);
|
|
68
|
+
}
|
|
69
|
+
// `load` is a new function each render; provider and rpc are the triggers.
|
|
70
|
+
/* eslint-disable react-hooks/exhaustive-deps -- avoid refetching every render */
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
void load();
|
|
73
|
+
}, [provider, rpc]);
|
|
74
|
+
/* eslint-enable react-hooks/exhaustive-deps */
|
|
51
75
|
useImperativeHandle(ref, () => ({
|
|
52
76
|
collect() {
|
|
53
77
|
if (!catalog || !dirty)
|
|
@@ -57,47 +81,106 @@ export const ProviderModelEditor = forwardRef(function ProviderModelEditor({ pro
|
|
|
57
81
|
if (!text.trim())
|
|
58
82
|
continue;
|
|
59
83
|
const value = Number(text);
|
|
60
|
-
if (!/^\d+$/.test(text.trim()) ||
|
|
84
|
+
if (!/^\d+$/.test(text.trim()) ||
|
|
85
|
+
!Number.isSafeInteger(value) ||
|
|
86
|
+
value <= 0) {
|
|
61
87
|
setError(t('modelsContextInvalid', { model }));
|
|
62
88
|
return undefined;
|
|
63
89
|
}
|
|
64
90
|
windows[model] = value;
|
|
65
91
|
}
|
|
66
92
|
setError('');
|
|
67
|
-
const { accounts: _accounts, ...settings } = {
|
|
93
|
+
const { accounts: _accounts, ...settings } = {
|
|
94
|
+
...draft,
|
|
95
|
+
...(supportsContext(provider) ? { contextWindows: windows } : {}),
|
|
96
|
+
};
|
|
68
97
|
return {
|
|
69
98
|
settings,
|
|
70
|
-
efforts: catalog.models.flatMap(model => {
|
|
99
|
+
efforts: catalog.models.flatMap((model) => {
|
|
71
100
|
const effort = efforts[model.id] ?? '';
|
|
72
|
-
return effort === (model.configured ?? '') ||
|
|
101
|
+
return effort === (model.configured ?? '') ||
|
|
102
|
+
!model.efforts?.length
|
|
103
|
+
? []
|
|
104
|
+
: [{ model: model.id, effort }];
|
|
73
105
|
}),
|
|
74
106
|
};
|
|
75
107
|
},
|
|
76
|
-
}), [catalog, dirty, draft, contexts, efforts, provider]);
|
|
108
|
+
}), [catalog, dirty, draft, contexts, efforts, provider, t]);
|
|
77
109
|
const allModels = catalog?.models ?? [];
|
|
78
|
-
const known = new Set(allModels.map(model => model.id));
|
|
79
|
-
const missing = (draft.visibleModels ?? [])
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
110
|
+
const known = new Set(allModels.map((model) => model.id));
|
|
111
|
+
const missing = (draft.visibleModels ?? [])
|
|
112
|
+
.filter((id) => !known.has(id))
|
|
113
|
+
.map((id) => ({ id, name: id }));
|
|
114
|
+
const models = [...allModels, ...missing].filter((model) => `${model.name} ${model.id}`
|
|
115
|
+
.toLowerCase()
|
|
116
|
+
.includes(query.trim().toLowerCase()));
|
|
117
|
+
const selected = new Set(draft.visibleModels ?? allModels.map((model) => model.id));
|
|
118
|
+
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
|
+
? 'cursorManageHint'
|
|
120
|
+
: '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
|
+
void load(true);
|
|
122
|
+
}, children: t('usageRefresh') }), busy && _jsx("span", { role: "status", children: t('modelDefaultsLoading') })] }), catalog && (_jsxs("fieldset", { disabled: busy || disabled, style: {
|
|
123
|
+
border: 0,
|
|
124
|
+
padding: 0,
|
|
125
|
+
margin: 0,
|
|
126
|
+
minWidth: 0,
|
|
127
|
+
display: 'grid',
|
|
128
|
+
gap: 12,
|
|
129
|
+
}, children: [_jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: draft.visibleModels === undefined, onChange: (event) => {
|
|
83
130
|
const next = { ...draft };
|
|
84
131
|
if (event.target.checked)
|
|
85
132
|
delete next.visibleModels;
|
|
86
133
|
else
|
|
87
|
-
next.visibleModels = allModels.map(model => model.id);
|
|
134
|
+
next.visibleModels = allModels.map((model) => model.id);
|
|
88
135
|
edit(next);
|
|
89
|
-
} }),
|
|
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
|
+
...draft,
|
|
138
|
+
visibleModels: allModels.map((model) => model.id),
|
|
139
|
+
}), children: t('modelsSelectAll') }), _jsx("button", { type: "button", onClick: () => edit({ ...draft, visibleModels: [] }), children: t('modelsSelectNone') })] }), _jsxs("div", { style: {
|
|
140
|
+
maxHeight: 360,
|
|
141
|
+
overflowY: 'auto',
|
|
142
|
+
display: 'grid',
|
|
143
|
+
gap: 8,
|
|
144
|
+
}, children: [models.map((model) => (_jsxs("div", { style: {
|
|
145
|
+
borderBottom: border,
|
|
146
|
+
padding: '8px 2px',
|
|
147
|
+
display: 'grid',
|
|
148
|
+
gap: 8,
|
|
149
|
+
}, children: [_jsxs("label", { style: { overflowWrap: 'anywhere' }, children: [_jsx("input", { type: "checkbox", checked: selected.has(model.id), onChange: (event) => {
|
|
90
150
|
const next = new Set(selected);
|
|
91
151
|
if (event.target.checked)
|
|
92
152
|
next.add(model.id);
|
|
93
153
|
else
|
|
94
154
|
next.delete(model.id);
|
|
95
155
|
edit({ ...draft, visibleModels: [...next] });
|
|
96
|
-
} }),
|
|
97
|
-
setEfforts({
|
|
156
|
+
} }), ' ', 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) => {
|
|
157
|
+
setEfforts({
|
|
158
|
+
...efforts,
|
|
159
|
+
[model.id]: event.target.value,
|
|
160
|
+
});
|
|
98
161
|
setDirty(true);
|
|
99
|
-
}, children: [_jsx("option", { value: "", children: t('modelDefaultsFollowProvider') }), model.configured &&
|
|
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))
|
|
101
|
-
|
|
102
|
-
|
|
162
|
+
}, children: [_jsx("option", { value: "", children: t('modelDefaultsFollowProvider') }), model.configured &&
|
|
163
|
+
!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) &&
|
|
164
|
+
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) => {
|
|
165
|
+
setContexts({
|
|
166
|
+
...contexts,
|
|
167
|
+
[model.id]: event.target.value,
|
|
168
|
+
});
|
|
169
|
+
setDirty(true);
|
|
170
|
+
} })] }), _jsx("small", { children: t('modelsContextBounds', {
|
|
171
|
+
default: model.defaultContextWindow,
|
|
172
|
+
max: model.maxContextWindow,
|
|
173
|
+
}) })] }))] })] }, 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
|
+
...draft,
|
|
175
|
+
tools: {
|
|
176
|
+
...draft.tools,
|
|
177
|
+
[tool]: event.target.checked,
|
|
178
|
+
},
|
|
179
|
+
}) }), ' ', t(tool === 'image_generate'
|
|
180
|
+
? 'modelsImage'
|
|
181
|
+
: tool === 'video_generate'
|
|
182
|
+
? 'modelsVideo'
|
|
183
|
+
: tool === 'web_search'
|
|
184
|
+
? 'modelsWebSearch'
|
|
185
|
+
: 'modelsSearch')] }, tool)))] }))] }))] })] }));
|
|
103
186
|
});
|
|
@@ -19,16 +19,28 @@ export interface SpeedSelectState {
|
|
|
19
19
|
* ship it with this shape; it replaces rc.2's `connection.api.sessions.models`
|
|
20
20
|
* read, which the 0.1.2-alpha removed along with the whole `.api` face.
|
|
21
21
|
*/
|
|
22
|
+
/** Selected model pushed by ui-model-selection's directory store. */
|
|
23
|
+
export interface ModelSelectionSnapshot {
|
|
24
|
+
current: {
|
|
25
|
+
provider: string;
|
|
26
|
+
model: string;
|
|
27
|
+
} | null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The directory store (`ModelDirectory.store`). Present on hosts that publish
|
|
31
|
+
* selection changes; absent on a directory that only offers `load()`.
|
|
32
|
+
*/
|
|
33
|
+
export interface ModelSelectionStore {
|
|
34
|
+
subscribe(listener: () => void): () => void;
|
|
35
|
+
getSnapshot(): ModelSelectionSnapshot;
|
|
36
|
+
}
|
|
22
37
|
export interface ModelDirectoriesLike {
|
|
23
38
|
/** Resolve one session's shared model directory (throws for unknown sessions). */
|
|
24
39
|
directoryFor(sessionId: string): {
|
|
25
40
|
/** Load the directory; `current` is the session's effective model selection. */
|
|
26
|
-
load(): Promise<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
model: string;
|
|
30
|
-
} | null;
|
|
31
|
-
}>;
|
|
41
|
+
load(): Promise<ModelSelectionSnapshot>;
|
|
42
|
+
/** Pushes selection changes. Missing when the host only offers `load()`. */
|
|
43
|
+
store?: ModelSelectionStore;
|
|
32
44
|
};
|
|
33
45
|
}
|
|
34
46
|
/** Injected dependencies of {@link SpeedSelect} (slot `inject`, session-bound). */
|
|
@@ -37,6 +49,12 @@ export interface SpeedSelectInjected {
|
|
|
37
49
|
loadSpeed: () => Promise<SpeedSelectState>;
|
|
38
50
|
/** Set the session's speed tier; resolves false when the write failed. */
|
|
39
51
|
setSpeed: (tier: SpeedTier) => Promise<boolean>;
|
|
52
|
+
/**
|
|
53
|
+
* Subscribe to the session's selected model. The listener runs when
|
|
54
|
+
* `provider` or `model` changes, and once when a directory that was missing
|
|
55
|
+
* at subscribe time becomes available. The returned function detaches it.
|
|
56
|
+
*/
|
|
57
|
+
watchModel: (listener: () => void) => () => void;
|
|
40
58
|
}
|
|
41
59
|
/**
|
|
42
60
|
* Props delivered by the slot outlet: the framework session kit and InputZone
|
|
@@ -46,9 +64,10 @@ export interface SpeedSelectInjected {
|
|
|
46
64
|
export type SpeedSelectProps = PropsRuntime<'conversation.input.right'> & Partial<SpeedSelectInjected> & Partial<PropsLocale<'settings.subscriptions'>>;
|
|
47
65
|
/**
|
|
48
66
|
* The `loadSpeed` half of the inject face: the plugin's own speed state plus
|
|
49
|
-
* the host's current model selection (the visibility gate). A
|
|
50
|
-
*
|
|
51
|
-
*
|
|
67
|
+
* the host's current model selection (the visibility gate). A session whose
|
|
68
|
+
* model selection is unavailable (an addressed subagent) answers hidden.
|
|
69
|
+
* Any other model-directory failure throws, so the caller keeps its last
|
|
70
|
+
* known state instead of locking the toggle away.
|
|
52
71
|
*
|
|
53
72
|
* `sessionId` is a plain string: slot and command contexts brand it through
|
|
54
73
|
* different dsh-session copies, and only the service boundary needs one.
|
|
@@ -58,14 +77,43 @@ export type SpeedSelectProps = PropsRuntime<'conversation.input.right'> & Partia
|
|
|
58
77
|
* at all) simply keeps the toggle hidden.
|
|
59
78
|
*/
|
|
60
79
|
export declare function createSpeedLoader(connection: ConnectionHandle, models: () => ModelDirectoriesLike | undefined, sessionId: string): SpeedSelectInjected['loadSpeed'];
|
|
80
|
+
/** Stable identity of a model selection. Empty when nothing is selected. */
|
|
81
|
+
export declare function modelSelectionKey(current: ModelSelectionSnapshot['current'] | undefined): string;
|
|
82
|
+
/**
|
|
83
|
+
* How often a host without a directory store is re-read, and how often a
|
|
84
|
+
* missing directory service is retried. Neither interval calls
|
|
85
|
+
* `subscriptions-auth.speed`.
|
|
86
|
+
*/
|
|
87
|
+
export declare const MODEL_DIRECTORY_POLL_INTERVAL_MS = 3000;
|
|
88
|
+
/**
|
|
89
|
+
* Mount reads of `speed`, including the first attempt. Further failures stop
|
|
90
|
+
* instead of polling for the life of the composer.
|
|
91
|
+
*/
|
|
92
|
+
export declare const SPEED_MOUNT_READ_ATTEMPTS = 4;
|
|
93
|
+
/** Fired after `/fast` writes a tier, so an open composer re-reads `speed`. */
|
|
94
|
+
export declare const SPEED_CHANGED_EVENT = "dsh:subscriptions:speed-changed";
|
|
95
|
+
/** Tell mounted Speed controls that this page changed a session tier. */
|
|
96
|
+
export declare function notifySpeedChanged(): void;
|
|
97
|
+
/**
|
|
98
|
+
* Subscribe to the session's selected model. A directory store notifies the
|
|
99
|
+
* listener only when `provider` or `model` changes. A directory with no store
|
|
100
|
+
* is re-read on {@link MODEL_DIRECTORY_POLL_INTERVAL_MS}; a service that is
|
|
101
|
+
* not registered yet, or whose `directoryFor` throws, is retried on that same
|
|
102
|
+
* interval. The first successful attach after a miss notifies once, so a
|
|
103
|
+
* mount that ran before the service existed reads `speed` again.
|
|
104
|
+
*/
|
|
105
|
+
export declare function createModelSelectionWatch(models: () => ModelDirectoriesLike | undefined, sessionId: string): SpeedSelectInjected['watchModel'];
|
|
61
106
|
/** The `setSpeed` half of the inject face: boolean outcome for the component's busy state. */
|
|
62
107
|
export declare function createSpeedSetter(connection: ConnectionHandle, sessionId: string): SpeedSelectInjected['setSpeed'];
|
|
63
108
|
/**
|
|
64
109
|
* The composer Speed control: a trigger reading `速度 · 快速`/`速度 · 标准`
|
|
65
110
|
* that opens a two-row menu (standard/fast with descriptions, check mark on
|
|
66
|
-
* the current tier).
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
111
|
+
* the current tier). It reads `subscriptions-auth.speed` on mount, when the
|
|
112
|
+
* menu opens, when {@link SpeedSelectInjected.watchModel} reports a new
|
|
113
|
+
* selected model, and when {@link notifySpeedChanged} fires. Until the first
|
|
114
|
+
* read succeeds, a failure is retried on
|
|
115
|
+
* {@link MODEL_DIRECTORY_POLL_INTERVAL_MS}, at most
|
|
116
|
+
* {@link SPEED_MOUNT_READ_ATTEMPTS} times; after that, a failed read keeps
|
|
117
|
+
* the last known state.
|
|
70
118
|
*/
|
|
71
|
-
export declare function SpeedSelect({ loadSpeed, setSpeed, t }: SpeedSelectProps): import("react").JSX.Element | null;
|
|
119
|
+
export declare function SpeedSelect({ loadSpeed, setSpeed, watchModel, t, }: SpeedSelectProps): import("react").JSX.Element | null;
|