dsh-subscriptions 0.2.2 → 0.3.0

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.
Files changed (43) hide show
  1. package/README.md +28 -32
  2. package/lib/auth/rpc.d.ts +10 -24
  3. package/lib/auth/rpc.js +18 -51
  4. package/lib/client/ProviderAccountManager.d.ts +1 -1
  5. package/lib/client/ProviderAccountManager.js +86 -110
  6. package/lib/client/ProviderModelEditor.d.ts +1 -1
  7. package/lib/client/ProviderModelEditor.js +61 -43
  8. package/lib/client/SpeedSelect.js +13 -12
  9. package/lib/client/SubscriptionUsageBadge.d.ts +1 -1
  10. package/lib/client/SubscriptionUsageBadge.js +1 -13
  11. package/lib/client/SubscriptionsSection.d.ts +2 -0
  12. package/lib/client/SubscriptionsSection.js +10 -4
  13. package/lib/client/locales.d.ts +12 -32
  14. package/lib/client/locales.js +14 -34
  15. package/lib/client/model-filter.d.ts +37 -0
  16. package/lib/client/model-filter.js +45 -0
  17. package/lib/client/subscription-card-styles.d.ts +1 -1
  18. package/lib/client/subscription-card-styles.js +1 -1
  19. package/lib/client/usage-window-filter.d.ts +7 -0
  20. package/lib/client/usage-window-filter.js +12 -0
  21. package/lib/client.js +374 -584
  22. package/lib/client.js.map +1 -1
  23. package/lib/index.js +249 -3322
  24. package/lib/provider-settings.d.ts +5 -0
  25. package/lib/provider-settings.js +21 -0
  26. package/lib/providers/ollama-settings.d.ts +1 -1
  27. package/lib/providers/ollama-settings.js +1 -1
  28. package/lib/translate/antigravity.js +10 -2
  29. package/package.json +2 -7
  30. package/lib/client/CursorCard.d.ts +0 -9
  31. package/lib/client/CursorCard.js +0 -151
  32. package/lib/providers/cursor-adapter.d.ts +0 -50
  33. package/lib/providers/cursor-adapter.js +0 -304
  34. package/lib/providers/cursor-auth.d.ts +0 -43
  35. package/lib/providers/cursor-auth.js +0 -221
  36. package/lib/providers/cursor-model-settings.d.ts +0 -15
  37. package/lib/providers/cursor-model-settings.js +0 -60
  38. package/lib/providers/cursor-usage.d.ts +0 -9
  39. package/lib/providers/cursor-usage.js +0 -131
  40. package/vendor/cursor/LICENSE +0 -21
  41. package/vendor/cursor/index.d.ts +0 -18
  42. package/vendor/cursor/index.js +0 -3574
  43. package/vendor/cursor/proto.js +0 -254
@@ -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)';
@@ -47,9 +48,7 @@ export const ProviderModelEditor = forwardRef(function ProviderModelEditor({ pro
47
48
  setBusy(true);
48
49
  setError('');
49
50
  try {
50
- const data = await callSubscriptionsAuth(rpc, provider === 'cursor-subscription'
51
- ? 'cursorSettings'
52
- : 'providerSettings', { provider, force });
51
+ const data = await callSubscriptionsAuth(rpc, 'providerSettings', { provider, force });
53
52
  if (generation.current === request)
54
53
  reset(data);
55
54
  }
@@ -108,18 +107,24 @@ export const ProviderModelEditor = forwardRef(function ProviderModelEditor({ pro
108
107
  }), [catalog, dirty, draft, contexts, efforts, provider, t]);
109
108
  const allModels = catalog?.models ?? [];
110
109
  const known = new Set(allModels.map((model) => model.id));
111
- const missing = (draft.visibleModels ?? [])
110
+ const savedIds = savedModelIds(draft.visibleModels, draft.subagentSpeeds);
111
+ const missing = savedIds
112
112
  .filter((id) => !known.has(id))
113
113
  .map((id) => ({ id, name: id }));
114
114
  const models = [...allModels, ...missing].filter((model) => `${model.name} ${model.id}`
115
115
  .toLowerCase()
116
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: () => {
117
+ const editorState = modelEditorState({
118
+ discovered: allModels.length,
119
+ saved: missing.length,
120
+ matched: models.length,
121
+ accounts: catalog?.accounts,
122
+ });
123
+ const selected = new Set(draft.visibleModels ??
124
+ [...allModels, ...missing].map((model) => model.id));
125
+ 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('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
126
  void load(true);
122
- }, children: t('usageRefresh') }), busy && _jsx("span", { role: "status", children: t('modelDefaultsLoading') })] }), catalog && (_jsxs("fieldset", { disabled: busy || disabled, style: {
127
+ }, children: t('usageRefresh') }), busy && _jsx("span", { role: "status", children: t('modelDefaultsLoading') })] }), !busy && catalog && (_jsxs("fieldset", { disabled: busy || disabled, style: {
123
128
  border: 0,
124
129
  padding: 0,
125
130
  margin: 0,
@@ -133,44 +138,57 @@ export const ProviderModelEditor = forwardRef(function ProviderModelEditor({ pro
133
138
  else
134
139
  next.visibleModels = allModels.map((model) => model.id);
135
140
  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
- ...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',
141
+ } }), ' ', 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({
142
+ ...draft,
143
+ visibleModels: allModels.map((model) => model.id),
144
+ }), children: t('modelsSelectAll') }), _jsx("button", { type: "button", onClick: () => edit({ ...draft, visibleModels: [] }), children: t('modelsSelectNone') })] }), _jsxs("div", { style: {
145
+ maxHeight: 360,
146
+ overflowY: 'auto',
147
147
  display: 'grid',
148
148
  gap: 8,
149
- }, children: [_jsxs("label", { style: { overflowWrap: 'anywhere' }, children: [_jsx("input", { type: "checkbox", checked: selected.has(model.id), onChange: (event) => {
150
- const next = new Set(selected);
151
- if (event.target.checked)
152
- next.add(model.id);
153
- else
154
- next.delete(model.id);
155
- edit({ ...draft, visibleModels: [...next] });
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
- });
161
- setDirty(true);
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,
149
+ }, children: [models.map((model) => (_jsxs("div", { style: {
150
+ borderBottom: border,
151
+ padding: '8px 2px',
152
+ display: 'grid',
153
+ gap: 8,
154
+ }, children: [_jsxs("label", { style: { overflowWrap: 'anywhere' }, children: [_jsx("input", { type: "checkbox", checked: selected.has(model.id), onChange: (event) => {
155
+ const next = new Set(selected);
156
+ if (event.target.checked)
157
+ next.add(model.id);
158
+ else
159
+ next.delete(model.id);
160
+ edit({ ...draft, visibleModels: [...next] });
161
+ } }), ' ', model.name, !known.has(model.id) &&
162
+ ` (${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) => {
163
+ setEfforts({
164
+ ...efforts,
167
165
  [model.id]: event.target.value,
168
166
  });
169
167
  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({
168
+ }, children: [_jsx("option", { value: "", children: t('modelDefaultsFollowProvider') }), model.configured &&
169
+ !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) &&
170
+ 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) => {
171
+ setContexts({
172
+ ...contexts,
173
+ [model.id]: event.target.value,
174
+ });
175
+ setDirty(true);
176
+ } })] }), _jsx("small", { children: t('modelsContextBounds', {
177
+ default: model.defaultContextWindow,
178
+ max: model.maxContextWindow,
179
+ }) })] }))] }), provider === 'codex' &&
180
+ (model.fastTier === true ||
181
+ 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)
182
+ ? draft.subagentSpeeds[model.id]
183
+ : 'standard', onChange: (event) => edit({
184
+ ...draft,
185
+ subagentSpeeds: {
186
+ ...draft.subagentSpeeds,
187
+ [model.id]: event.target
188
+ .value,
189
+ },
190
+ }), children: [_jsx("option", { value: "standard", children: t('speedStandard') }), _jsxs("option", { value: "fast", disabled: !model.fastTier, children: [t('speedFast'), !model.fastTier &&
191
+ ` (${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
192
  ...draft,
175
193
  tools: {
176
194
  ...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
- if (directories === undefined)
38
- return { visible: false, tier: state.tier };
39
- let current;
40
- try {
41
- const loaded = await directories.directoryFor(sessionId).load();
42
- current = loaded.current;
43
- }
44
- catch (error) {
45
- if (!modelSelectionUnavailable(error))
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);
@@ -48,7 +48,7 @@ export interface ProviderUsageDisplay {
48
48
  /** The account the collapsed pill reads: the default one, else the first listed. */
49
49
  export declare function pillAccountOf(d: ProviderUsageDisplay): AccountUsageDisplay;
50
50
  /** Brand display names (short form for the compact badge). */
51
- export type BadgeProvider = SubscriptionProvider | 'cursor-subscription' | 'opencode-go' | 'kimi-coding' | 'minimax' | 'minimax-cn' | 'ollama-cloud';
51
+ export type BadgeProvider = SubscriptionProvider | 'opencode-go' | 'kimi-coding' | 'minimax' | 'minimax-cn' | 'ollama-cloud';
52
52
  export interface UsageRosterEntry {
53
53
  provider: BadgeProvider;
54
54
  account: AccountStatus;
@@ -60,7 +60,6 @@ const PROVIDER_NAMES = {
60
60
  grok: 'Grok',
61
61
  copilot: 'Copilot',
62
62
  antigravity: 'Antigravity',
63
- 'cursor-subscription': 'Cursor',
64
63
  'opencode-go': 'OpenCode Go',
65
64
  'kimi-coding': 'Kimi Code',
66
65
  minimax: 'MiniMax',
@@ -110,9 +109,8 @@ export function groupUsageDisplays(roster, windowsOf, plans) {
110
109
  }
111
110
  /** Read each account source independently so a failing endpoint cannot hide the others. */
112
111
  export async function loadBadgeRoster(rpc) {
113
- const [subscriptions, cursor, external] = await Promise.allSettled([
112
+ const [subscriptions, external] = await Promise.allSettled([
114
113
  callSubscriptionsAuth(rpc, 'status', {}),
115
- callSubscriptionsAuth(rpc, 'cursorStatus', {}),
116
114
  callSubscriptionsAuth(rpc, 'externalStatus', {}),
117
115
  ]);
118
116
  const roster = [];
@@ -130,14 +128,6 @@ export async function loadBadgeRoster(rpc) {
130
128
  roster.push({ provider, account });
131
129
  }
132
130
  }
133
- if (cursor.status === 'fulfilled') {
134
- refreshed.add('cursor-subscription');
135
- if (cursor.value.authenticated)
136
- roster.push({
137
- provider: 'cursor-subscription',
138
- account: { key: 'cursor', isDefault: true },
139
- });
140
- }
141
131
  if (external.status === 'fulfilled') {
142
132
  for (const [source, provider] of [
143
133
  ['opencode-go', 'opencode-go'],
@@ -157,8 +147,6 @@ export async function loadBadgeRoster(rpc) {
157
147
  return { roster, refreshed };
158
148
  }
159
149
  export async function usageOf(rpc, { provider, account }) {
160
- if (provider === 'cursor-subscription')
161
- return callSubscriptionsAuth(rpc, 'cursorUsage', {});
162
150
  if (provider === 'opencode-go' ||
163
151
  provider === 'kimi-coding' ||
164
152
  provider === 'minimax' ||
@@ -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,10 +15,10 @@ 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';
21
- import { CursorCard } from './CursorCard.js';
22
22
  import { OllamaCard } from './OllamaCard.js';
23
23
  import { UsageMeter } from './UsageMeter.js';
24
24
  import { ResetCreditsDisclosure, ResetCreditsErrorLine, showsResetCredits, } from './reset-credits-view.js';
@@ -641,6 +641,7 @@ export function SubscriptionsSection(props) {
641
641
  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
642
  const usageKey = `${id}:${account.key}`;
643
643
  const usage = usages[usageKey];
644
+ const shownWindows = visibleUsageWindows(id, usage?.windows, status?.visibleModels);
644
645
  const usageError = usageErrors[usageKey];
645
646
  const display = account.account ?? account.key;
646
647
  const sortedCredits = [...(usage?.resetCredits ?? [])].sort((a, b) => (a.expiresAt ?? Infinity) - (b.expiresAt ?? Infinity));
@@ -664,7 +665,9 @@ export function SubscriptionsSection(props) {
664
665
  }, 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
666
  void loadUsage(id, account.key, true);
666
667
  }, 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') })), (usage?.windows ?? []).map((window, index) => {
668
+ usage.windows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageEmpty') })), usage?.windows !== undefined &&
669
+ usage.windows.length > 0 &&
670
+ shownWindows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageNoVisibleModels') })), shownWindows.map((window, index) => {
668
671
  const percent = displayUsedPercent(window.usedPercent);
669
672
  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
673
  ? t('usageMeterInvalid')
@@ -702,7 +705,7 @@ export function SubscriptionsSection(props) {
702
705
  void login(id, 'keychain');
703
706
  }, children: t('addAccountKeychain') })] })), !busy && accounts.length > 0 && id !== 'claude' && (_jsx("button", { type: "button", onClick: () => {
704
707
  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: () => {
708
+ }, 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
709
  void cancel(id);
707
710
  }, 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
711
  copyDeviceCode(id, deviceCode.userCode);
@@ -716,7 +719,10 @@ export function SubscriptionsSection(props) {
716
719
  })) }), _jsx("button", { type: "button", onClick: () => {
717
720
  void submitManual(id);
718
721
  }, 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: () => setManagedProvider(undefined) })), _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: {
722
+ }), _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
+ setManagedProvider(undefined);
724
+ void refresh();
725
+ } })), _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
726
  ...styles.modal,
721
727
  display: resetConfirmation ? 'flex' : 'none',
722
728
  margin: 'auto',
@@ -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;
@@ -145,22 +148,6 @@ export declare const en: {
145
148
  externalUsageApiKey: string;
146
149
  externalUsageConfigure: string;
147
150
  externalUsageUnavailable: string;
148
- cursorTitle: string;
149
- cursorOpenLogin: string;
150
- cursorWaiting: string;
151
- cursorConnected: string;
152
- cursorManageTitle: string;
153
- cursorManageSignIn: string;
154
- cursorAccountsHint: string;
155
- cursorManageHint: string;
156
- cursorFilterModels: string;
157
- cursorModelsLoading: string;
158
- cursorModelsError: string;
159
- cursorModelsCount: string;
160
- cursorIncluded: string;
161
- cursorModels: string;
162
- cursorOtherModels: string;
163
- cursorIncludedRequests: string;
164
151
  modelDefaultsTitle: string;
165
152
  modelDefaultsHint: string;
166
153
  modelDefaultsFollowProvider: string;
@@ -176,6 +163,9 @@ export declare const en: {
176
163
  modelDefaultsSummaryEmpty: string;
177
164
  modelDefaultsFilterPlaceholder: string;
178
165
  modelDefaultsFilterEmpty: string;
166
+ modelDefaultsNoModels: string;
167
+ modelDefaultsNoAccount: string;
168
+ modelDefaultsDiscoveryFailed: string;
179
169
  generating: string;
180
170
  image: string;
181
171
  viewImage: string;
@@ -269,6 +259,8 @@ export declare const zh: {
269
259
  modelsContextBounds: string;
270
260
  modelsContextHint: string;
271
261
  modelsContextInvalid: string;
262
+ modelsSubagentSpeed: string;
263
+ modelsSubagentSpeedHint: string;
272
264
  modelsTools: string;
273
265
  modelsToolsHint: string;
274
266
  modelsImage: string;
@@ -318,6 +310,7 @@ export declare const zh: {
318
310
  usageRefresh: string;
319
311
  usageLoading: string;
320
312
  usageEmpty: string;
313
+ usageNoVisibleModels: string;
321
314
  usageUnsupported: string;
322
315
  usageError: string;
323
316
  usageSession: string;
@@ -385,22 +378,6 @@ export declare const zh: {
385
378
  externalUsageApiKey: string;
386
379
  externalUsageConfigure: string;
387
380
  externalUsageUnavailable: string;
388
- cursorTitle: string;
389
- cursorOpenLogin: string;
390
- cursorWaiting: string;
391
- cursorConnected: string;
392
- cursorManageTitle: string;
393
- cursorManageSignIn: string;
394
- cursorAccountsHint: string;
395
- cursorManageHint: string;
396
- cursorFilterModels: string;
397
- cursorModelsLoading: string;
398
- cursorModelsError: string;
399
- cursorModelsCount: string;
400
- cursorIncluded: string;
401
- cursorModels: string;
402
- cursorOtherModels: string;
403
- cursorIncludedRequests: string;
404
381
  modelDefaultsTitle: string;
405
382
  modelDefaultsHint: string;
406
383
  modelDefaultsFollowProvider: string;
@@ -416,6 +393,9 @@ export declare const zh: {
416
393
  modelDefaultsSummaryEmpty: string;
417
394
  modelDefaultsFilterPlaceholder: string;
418
395
  modelDefaultsFilterEmpty: string;
396
+ modelDefaultsNoModels: string;
397
+ modelDefaultsNoAccount: string;
398
+ modelDefaultsDiscoveryFailed: string;
419
399
  generating: string;
420
400
  image: string;
421
401
  viewImage: string;
@@ -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',
@@ -145,22 +148,6 @@ export const en = {
145
148
  externalUsageApiKey: 'API key',
146
149
  externalUsageConfigure: 'No API key found at {ref}. Add one in Settings → Models.',
147
150
  externalUsageUnavailable: 'Usage status unavailable: {message}',
148
- cursorTitle: 'Cursor',
149
- cursorOpenLogin: 'Open Cursor sign-in',
150
- cursorWaiting: 'Waiting for Cursor sign-in…',
151
- cursorConnected: 'Connected',
152
- cursorManageTitle: 'Manage Cursor',
153
- cursorManageSignIn: 'Sign in to see the models available to this Cursor account.',
154
- cursorAccountsHint: 'Cursor uses one connected account. Choose which of its models appear in the model picker.',
155
- cursorManageHint: 'Choose model visibility here. Refresh fetches the current models from Cursor; Save changes updates the model picker.',
156
- cursorFilterModels: 'Filter models',
157
- cursorModelsLoading: 'Loading Cursor models…',
158
- cursorModelsError: 'Could not load Cursor models: {message}',
159
- cursorModelsCount: '{count} models available',
160
- cursorIncluded: 'Included usage',
161
- cursorModels: 'Cursor Models',
162
- cursorOtherModels: 'Other Models',
163
- cursorIncludedRequests: 'Included requests',
164
151
  modelDefaultsTitle: 'Default reasoning effort',
165
152
  modelDefaultsHint: 'The model picker preselects this level when you switch to the model; models without levels always follow the provider.',
166
153
  modelDefaultsFollowProvider: 'Follow provider',
@@ -176,6 +163,9 @@ export const en = {
176
163
  modelDefaultsSummaryEmpty: 'No model advertises reasoning levels',
177
164
  modelDefaultsFilterPlaceholder: 'Filter models',
178
165
  modelDefaultsFilterEmpty: 'No model matches “{query}”.',
166
+ modelDefaultsNoModels: 'This provider reports no models.',
167
+ modelDefaultsNoAccount: 'No connected accounts. Connect one on the provider card first.',
168
+ modelDefaultsDiscoveryFailed: 'Model discovery failed or timed out. Use Refresh to try again.',
179
169
  generating: 'Generating image…',
180
170
  image: 'image',
181
171
  viewImage: 'View image',
@@ -269,7 +259,7 @@ export const zh = {
269
259
  accountsCatalogUnavailable: '暂时无法获取模型目录,已保存的模型选择会被保留。',
270
260
  modelsPartialSave: '部分推理档已保存,其余更改仍保留在草稿中,请重试完成保存。',
271
261
  modelsEdit: '编辑模型列表',
272
- modelsHint: '在此配置模型显示、默认推理档、上下文与工具,底部的「保存更改」会连同账号设置一起提交。隐藏模型不影响已有会话;工具开关对新建会话生效。',
262
+ modelsHint: '在此配置模型显示、默认推理档、子代理速度、上下文与工具,底部的「保存更改」会连同账号设置一起提交。隐藏模型不影响已有会话;工具开关对新建会话生效。',
273
263
  modelsAutomatic: '自动显示全部模型(包含以后新增的模型)',
274
264
  modelsSelectAll: '全选',
275
265
  modelsSelectNone: '清空选择',
@@ -277,6 +267,8 @@ export const zh = {
277
267
  modelsContextBounds: '默认 {default} · 最大 {max}',
278
268
  modelsContextHint: '留空跟随服务商。配置值按账号返回的最大窗口限制;账号池取各成员的最小窗口。此设置控制本地历史保留和压缩时机,不会扩大 API 本身的容量。',
279
269
  modelsContextInvalid: '请为 {model} 输入正整数 token 数。',
270
+ modelsSubagentSpeed: '子代理默认速度',
271
+ modelsSubagentSpeedHint: '对使用此模型的子代理后续请求生效,与父会话模型无关。主会话仍使用自己的速度设置;快速档仅在模型支持时生效。',
280
272
  modelsTools: 'Provider 工具',
281
273
  modelsToolsHint: '仅对保存后新建的会话生效,已有会话保留原工具。任一支持的服务商启用共享工具时,该工具仍会显示。',
282
274
  modelsImage: '图片生成',
@@ -326,6 +318,7 @@ export const zh = {
326
318
  usageRefresh: '刷新',
327
319
  usageLoading: '用量加载中…',
328
320
  usageEmpty: '服务商未返回任何用量窗口。',
321
+ usageNoVisibleModels: '选择器中的可见模型没有任何用量窗口。',
329
322
  usageUnsupported: '该端点不提供用量查询。',
330
323
  usageError: '用量查询失败:{message}',
331
324
  usageSession: '5 小时窗口',
@@ -393,22 +386,6 @@ export const zh = {
393
386
  externalUsageApiKey: 'API 密钥',
394
387
  externalUsageConfigure: '未在 {ref} 找到 API 密钥。请在「设置 → 模型」中添加。',
395
388
  externalUsageUnavailable: '无法获取用量状态:{message}',
396
- cursorTitle: 'Cursor',
397
- cursorOpenLogin: '打开 Cursor 登录页面',
398
- cursorWaiting: '等待 Cursor 登录…',
399
- cursorConnected: '已连接',
400
- cursorManageTitle: '管理 Cursor',
401
- cursorManageSignIn: '登录后可查看此 Cursor 账号可用的模型。',
402
- cursorAccountsHint: 'Cursor 使用一个已连接账号。可在此选择模型选择器中显示的模型。',
403
- cursorManageHint: '在此选择模型是否显示。刷新可从 Cursor 获取当前模型;保存更改后更新模型选择器。',
404
- cursorFilterModels: '筛选模型',
405
- cursorModelsLoading: '正在加载 Cursor 模型…',
406
- cursorModelsError: '无法加载 Cursor 模型:{message}',
407
- cursorModelsCount: '可用模型:{count} 个',
408
- cursorIncluded: '套餐内用量',
409
- cursorModels: 'Cursor 模型',
410
- cursorOtherModels: '其他模型',
411
- cursorIncludedRequests: '套餐内请求',
412
389
  modelDefaultsTitle: '默认推理档',
413
390
  modelDefaultsHint: '切换到该模型时,模型选择器会预选此档位;没有推理档的模型始终跟随服务商默认。',
414
391
  modelDefaultsFollowProvider: '跟随服务商',
@@ -424,6 +401,9 @@ export const zh = {
424
401
  modelDefaultsSummaryEmpty: '没有模型声明推理档',
425
402
  modelDefaultsFilterPlaceholder: '筛选模型',
426
403
  modelDefaultsFilterEmpty: '没有匹配「{query}」的模型。',
404
+ modelDefaultsNoModels: '这个服务商没有报告任何模型。',
405
+ modelDefaultsNoAccount: '还没有连接账号。请先在卡片上连接一个账号。',
406
+ modelDefaultsDiscoveryFailed: '模型列表发现失败或超时。可以点「刷新」重试。',
427
407
  generating: '正在生成图片…',
428
408
  image: '图片',
429
409
  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
+ // Missing account evidence uses 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
+ }
@@ -1,3 +1,3 @@
1
1
  import type { CSSProperties } from 'react';
2
- /** Shared visual language for OAuth, Cursor, and built-in provider cards. */
2
+ /** Shared visual language for OAuth and built-in provider cards. */
3
3
  export declare const subscriptionCardStyles: Record<string, CSSProperties>;
@@ -1,4 +1,4 @@
1
- /** Shared visual language for OAuth, Cursor, and built-in provider cards. */
1
+ /** Shared visual language for OAuth and built-in provider cards. */
2
2
  export const subscriptionCardStyles = {
3
3
  card: {
4
4
  border: '1px solid var(--dsw-alias-border-l2)',