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
package/README.md CHANGED
@@ -10,9 +10,9 @@ reported quota in **Settings → Subscriptions**.
10
10
 
11
11
  ## What it adds
12
12
 
13
- - **Six subscription model sources:** Connect ChatGPT/Codex, Claude, Grok,
14
- GitHub Copilot, Google Antigravity, or Cursor and choose their models in DSH's
15
- picker. These routes use account sign-in, without provider API keys.
13
+ - **Five subscription model sources:** Connect ChatGPT/Codex, Claude, Grok,
14
+ GitHub Copilot, or Google Antigravity and choose their models in DSH's picker.
15
+ These routes use account sign-in, without provider API keys.
16
16
  - **Ollama Cloud key route:** Add the `ollama-cloud` route with an
17
17
  `OLLAMA_API_KEY` credential. The route ships no model catalog: configure it on
18
18
  the Settings → Subscriptions card, which fetches the cloud list, ticks the
@@ -46,10 +46,10 @@ reported quota in **Settings → Subscriptions**.
46
46
 
47
47
  ## Screenshots
48
48
 
49
- Captured against DSH 0.2.0-rc.2 in the web UI with a local stub serving frozen
50
- provider responses, so each image shows a product state rather than a real
51
- account. Identities in the images are fabricated, and the quota percentages,
52
- catalogs, and reset countdowns are sample data.
49
+ Captured against DSH 0.2.0-rc.2 and 0.2.1-rc.5 in the web UI with a local stub
50
+ serving frozen provider responses, so each image shows a product state rather
51
+ than a real account. Identities in the images are fabricated, and the quota
52
+ percentages, catalogs, and reset countdowns are sample data.
53
53
 
54
54
  ### Subscriptions
55
55
 
@@ -77,10 +77,17 @@ the composer's tool row: Standard or Fast, per session.
77
77
  ![Speed toggle with the Standard/Fast menu open](docs/images/speed-toggle.png)
78
78
 
79
79
  **Settings → Subscriptions → provider → Manage** edits account aliases, pool
80
- participation, model allowlists, and the per-model effort, context, and tool
81
- settings. A context value only lowers the window a session plans against, never
82
- above what the provider advertises; Codex and Claude support it, and it does not
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
  ![Model settings](docs/images/model-settings.png)
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`, and `0.2.1-rc.2`.
158
- The package's peers admit `>=0.1.7-rc.2 <0.3.0-0`: untested versions inside that
159
- range install but are unsupported, and versions outside it need an explicit
160
- `dsh plugin allow-version` exemption. `dsh plugin add` checks those ranges with
161
- `includePrerelease` semantics, so the admitted set covers every host build from
162
- `0.1.7-rc.2` through the whole `0.2.x` line, prereleases included, and blocks
163
- the `0.3.0` line. See [docs/compatibility.md](docs/compatibility.md) for the
164
- support window and [docs/testing.md](docs/testing.md) for the test layers. With
165
- `dsh` available, install the plugin into the web profile from npm:
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
@@ -216,12 +224,6 @@ the checks and their limits.
216
224
  A manual check in an isolated `0.1.7-rc.2` web profile found the client bundle
217
225
  served and the auth and usage-status RPCs answering. Live sign-in and model
218
226
  requests on the supported DSH lines remain to be checked.
219
- - **Cursor:** Browser sign-in and live model discovery were verified in an
220
- isolated `0.1.7-rc.2` profile. A successful live model request remains to be
221
- verified. Its transport comes from
222
- [orrinzeng/dsh-cursor-subscription](https://github.com/orrinzeng/dsh-cursor-subscription)
223
- and uses an undocumented Cursor protocol; see the
224
- [source note](docs/cursor-origin.md).
225
227
  - **OpenCode Go and Kimi Code:** Live usage responses were verified. Their API
226
228
  keys stay on the DSH host; the browser receives configured status and quota
227
229
  windows, not the keys.
@@ -231,12 +233,6 @@ the checks and their limits.
231
233
  browser test. Live chat, listing, usage, and web calls against `ollama.com`
232
234
  remain to be verified with a real key.
233
235
 
234
- Cursor uses one connected account and a live model catalog. Its model visibility
235
- selection persists under the DSH home directory, and automatic display follows
236
- newly discovered models. It ships no fallback model list and does not import the
237
- local `cursor-agent` credential cache. Individual usage comes from Cursor
238
- dashboard endpoints; the documented Admin API covers team usage.
239
-
240
236
  The plugin follows DSH network settings. Its former proxy configuration and UI
241
237
  have been retired; an old plugin proxy config file is ignored.
242
238
 
package/lib/auth/rpc.d.ts CHANGED
@@ -12,7 +12,6 @@ import { type ProviderId } from './store.js';
12
12
  import type { ProviderUsage } from '../providers/common.js';
13
13
  import type { CliVersion } from '../providers/npm-cli-version.js';
14
14
  import type { ExternalUsageSource, ExternalUsageStatus } from '../providers/external-usage-controller.js';
15
- import type { CursorAuthStatus } from '../providers/cursor-auth.js';
16
15
  /**
17
16
  * Endpoint-name prefix under the shared `/api` channel: endpoint `status`
18
17
  * is served at `/api/subscriptions-auth.status`. The browser half calls
@@ -23,7 +22,7 @@ export declare const SUBSCRIPTIONS_AUTH_PREFIX = "subscriptions-auth.";
23
22
  * Every endpoint {@link dispatch} answers; each gets one exact Fetch route.
24
23
  * Kept in one place so the route table and the switch cannot drift apart.
25
24
  */
26
- export declare const SUBSCRIPTIONS_AUTH_ENDPOINTS: readonly ["providerSettings", "setProviderSettings", "status", "login", "manual", "cancel", "logout", "setDefault", "usage", "externalStatus", "externalUsage", "cursorStatus", "cursorLogin", "cursorCancel", "cursorLogout", "cursorUsage", "cursorModels", "cursorSettings", "cursorSetSettings", "ollamaSettings", "ollamaSetSettings", "ollamaModels", "image", "video", "prepareReset", "consumeReset", "speed", "setSpeed", "modelDefaults", "setModelDefault"];
25
+ export declare const SUBSCRIPTIONS_AUTH_ENDPOINTS: readonly ["providerSettings", "setProviderSettings", "status", "login", "manual", "cancel", "logout", "setDefault", "usage", "externalStatus", "externalUsage", "ollamaSettings", "ollamaSetSettings", "ollamaModels", "image", "video", "prepareReset", "consumeReset", "speed", "setSpeed", "modelDefaults", "setModelDefault"];
27
26
  /** Decoded image bytes returned by the `image` endpoint. */
28
27
  export interface ImageBytesResult {
29
28
  mediaType: string;
@@ -38,15 +37,15 @@ export interface VideoBytesResult {
38
37
  export type SpeedTier = 'standard' | 'fast';
39
38
  /** `speed` endpoint value: the session's choice plus the visibility list. */
40
39
  export interface SpeedState {
41
- /** The session's current speed tier (default `standard`). */
40
+ /** Explicit session speed, or the subagent's model default when supplied. */
42
41
  tier: SpeedTier;
43
42
  /** Codex model ids whose catalog advertises a fast tier. */
44
43
  fastModels: string[];
45
44
  }
46
- /** Speed state the RPC handler delegates to (in-memory, per session). */
45
+ /** Session overrides plus persisted subagent model defaults. */
47
46
  export interface SpeedController {
48
- /** Current speed state: the session's tier and the fast-capable codex models. */
49
- speed(sessionId: string): Promise<SpeedState>;
47
+ /** Pass the current Codex model to resolve a subagent's default speed. */
48
+ speed(sessionId: string, model?: string): Promise<SpeedState>;
50
49
  /** Set one session's speed tier. */
51
50
  setSpeed(sessionId: string, tier: SpeedTier): Promise<void>;
52
51
  }
@@ -65,6 +64,8 @@ export interface AccountStatus {
65
64
  }
66
65
  /** Login state of one provider, as rendered by the Settings page. */
67
66
  export interface ProviderStatus {
67
+ /** Saved picker visibility, attached by the status RPC from local preferences. */
68
+ visibleModels?: string[];
68
69
  /** Whether a login attempt is currently waiting for its code. */
69
70
  busy: boolean;
70
71
  /** Logged-in accounts, default first. */
@@ -110,6 +111,8 @@ export interface ProviderAccountCatalog {
110
111
  }
111
112
  /** Provider and account preference operations behind providerSettings/setProviderSettings. */
112
113
  export interface ProviderSettingsController {
114
+ /** Local picker selection, without triggering model discovery. */
115
+ visibleModels?(provider: ProviderId): string[] | undefined;
113
116
  get(provider: ProviderId, force: boolean): Promise<unknown>;
114
117
  set(provider: ProviderId, settings: unknown): Promise<void>;
115
118
  }
@@ -185,22 +188,6 @@ export interface ExternalUsageRpcController {
185
188
  status(): Promise<Record<ExternalUsageSource, ExternalUsageStatus>>;
186
189
  usage(source: ExternalUsageSource, signal: AbortSignal): Promise<ProviderUsage>;
187
190
  }
188
- /** Cursor account actions; OAuth tokens remain in the host credential service. */
189
- export interface CursorRpcController {
190
- status(): Promise<CursorAuthStatus>;
191
- login(): Promise<{
192
- authorizeUrl: string;
193
- }>;
194
- cancel(): Promise<void>;
195
- logout(): Promise<void>;
196
- usage(signal: AbortSignal): Promise<ProviderUsage>;
197
- models(force: boolean, signal: AbortSignal): Promise<{
198
- id: string;
199
- name: string;
200
- }[]>;
201
- settings(force: boolean, signal: AbortSignal): Promise<unknown>;
202
- setSettings(input: unknown): Promise<void>;
203
- }
204
191
  /** One model in the Ollama Cloud picker catalog, with its capability flags. */
205
192
  export interface OllamaModelView {
206
193
  id: string;
@@ -276,7 +263,6 @@ export interface OllamaRpcController {
276
263
  * @param modelDefaults - optional per-model default-effort state backing `modelDefaults`/`setModelDefault`.
277
264
  * @param providerSettings - optional OAuth provider/account preferences.
278
265
  * @param externalUsage - optional credential-backed quota readers.
279
- * @param cursor - optional Cursor account and model-visibility actions.
280
266
  * @param ollama - optional Ollama Cloud catalog and connection settings.
281
267
  */
282
- export declare function registerAuthRpc(ctx: Context, controller: AuthController, speed: SpeedController, modelDefaults?: ModelDefaultsController | undefined, providerSettings?: ProviderSettingsController | undefined, externalUsage?: ExternalUsageRpcController | undefined, cursor?: CursorRpcController | undefined, ollama?: OllamaRpcController | undefined): void;
268
+ export declare function registerAuthRpc(ctx: Context, controller: AuthController, speed: SpeedController, modelDefaults?: ModelDefaultsController | undefined, providerSettings?: ProviderSettingsController | undefined, externalUsage?: ExternalUsageRpcController | undefined, ollama?: OllamaRpcController | undefined): void;
package/lib/auth/rpc.js CHANGED
@@ -32,14 +32,6 @@ export const SUBSCRIPTIONS_AUTH_ENDPOINTS = [
32
32
  'usage',
33
33
  'externalStatus',
34
34
  'externalUsage',
35
- 'cursorStatus',
36
- 'cursorLogin',
37
- 'cursorCancel',
38
- 'cursorLogout',
39
- 'cursorUsage',
40
- 'cursorModels',
41
- 'cursorSettings',
42
- 'cursorSetSettings',
43
35
  'ollamaSettings',
44
36
  'ollamaSetSettings',
45
37
  'ollamaModels',
@@ -284,7 +276,7 @@ function readSessionId(payload) {
284
276
  throw new BadRequest('payload must be an object');
285
277
  return readString(payload, 'sessionId');
286
278
  }
287
- async function dispatch(controller, speed, modelDefaults, endpoint, payload, signal, providerSettings, externalUsage, cursor, ollama) {
279
+ async function dispatch(controller, speed, modelDefaults, endpoint, payload, signal, providerSettings, externalUsage, ollama) {
288
280
  switch (endpoint) {
289
281
  case 'providerSettings':
290
282
  if (!providerSettings)
@@ -309,7 +301,18 @@ async function dispatch(controller, speed, modelDefaults, endpoint, payload, sig
309
301
  detail: error instanceof Error ? error.message : String(error),
310
302
  })),
311
303
  ]));
312
- return ok({ providers: Object.fromEntries(entries) });
304
+ return ok({
305
+ providers: Object.fromEntries(entries.map(([provider, status]) => {
306
+ const visibleModels = providerSettings?.visibleModels?.(provider);
307
+ return [
308
+ provider,
309
+ {
310
+ ...status,
311
+ ...(visibleModels === undefined ? {} : { visibleModels }),
312
+ },
313
+ ];
314
+ })),
315
+ });
313
316
  }
314
317
  case 'login': {
315
318
  const provider = readProvider(payload);
@@ -350,43 +353,6 @@ async function dispatch(controller, speed, modelDefaults, endpoint, payload, sig
350
353
  }
351
354
  return ok(await externalUsage.usage(source, signal));
352
355
  }
353
- case 'cursorStatus':
354
- if (!cursor)
355
- throw new BadRequest('Cursor account is unavailable');
356
- return ok(await cursor.status());
357
- case 'cursorLogin':
358
- if (!cursor)
359
- throw new BadRequest('Cursor account is unavailable');
360
- return ok(await cursor.login());
361
- case 'cursorCancel':
362
- if (!cursor)
363
- throw new BadRequest('Cursor account is unavailable');
364
- await cursor.cancel();
365
- return ok({ ok: true });
366
- case 'cursorLogout':
367
- if (!cursor)
368
- throw new BadRequest('Cursor account is unavailable');
369
- await cursor.logout();
370
- return ok({ ok: true });
371
- case 'cursorUsage':
372
- if (!cursor)
373
- throw new BadRequest('Cursor account is unavailable');
374
- return ok(await cursor.usage(signal));
375
- case 'cursorModels':
376
- if (!cursor)
377
- throw new BadRequest('Cursor account is unavailable');
378
- return ok(await cursor.models(readForce(payload), signal));
379
- case 'cursorSettings':
380
- if (!cursor)
381
- throw new BadRequest('Cursor account is unavailable');
382
- return ok(await cursor.settings(readForce(payload), signal));
383
- case 'cursorSetSettings':
384
- if (!cursor)
385
- throw new BadRequest('Cursor account is unavailable');
386
- if (payload === null || typeof payload !== 'object')
387
- throw new BadRequest('payload must be an object');
388
- await cursor.setSettings(payload.settings);
389
- return ok({ ok: true });
390
356
  case 'ollamaSettings':
391
357
  if (!ollama)
392
358
  throw new BadRequest('Ollama settings are unavailable');
@@ -433,7 +399,9 @@ async function dispatch(controller, speed, modelDefaults, endpoint, payload, sig
433
399
  case 'video':
434
400
  return ok(await controller.readVideo(readVideoName(payload), signal));
435
401
  case 'speed':
436
- return ok(await speed.speed(readSessionId(payload)));
402
+ return ok(await speed.speed(readSessionId(payload), payload.model === undefined
403
+ ? undefined
404
+ : readString(payload, 'model')));
437
405
  case 'setSpeed':
438
406
  await speed.setSpeed(readSessionId(payload), readSpeedTier(payload));
439
407
  return ok({ ok: true });
@@ -462,10 +430,9 @@ async function dispatch(controller, speed, modelDefaults, endpoint, payload, sig
462
430
  * @param modelDefaults - optional per-model default-effort state backing `modelDefaults`/`setModelDefault`.
463
431
  * @param providerSettings - optional OAuth provider/account preferences.
464
432
  * @param externalUsage - optional credential-backed quota readers.
465
- * @param cursor - optional Cursor account and model-visibility actions.
466
433
  * @param ollama - optional Ollama Cloud catalog and connection settings.
467
434
  */
468
- export function registerAuthRpc(ctx, controller, speed, modelDefaults = undefined, providerSettings = undefined, externalUsage = undefined, cursor = undefined, ollama = undefined) {
435
+ export function registerAuthRpc(ctx, controller, speed, modelDefaults = undefined, providerSettings = undefined, externalUsage = undefined, ollama = undefined) {
469
436
  // `connection` is not in this plugin's inject list (headless compositions
470
437
  // lack it), so its startup order is unconstrained: defer registration until
471
438
  // the service exists instead of probing once at apply time.
@@ -482,7 +449,7 @@ export function registerAuthRpc(ctx, controller, speed, modelDefaults = undefine
482
449
  const connection = ctx.get('connection');
483
450
  const handler = async (endpoint, payload, signal) => {
484
451
  try {
485
- return await dispatch(controller, speed, modelDefaults, endpoint, payload, signal, providerSettings, externalUsage, cursor, ollama);
452
+ return await dispatch(controller, speed, modelDefaults, endpoint, payload, signal, providerSettings, externalUsage, ollama);
486
453
  }
487
454
  catch (error) {
488
455
  return failure(error);
@@ -2,7 +2,7 @@ import type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client';
2
2
  import type { SubscriptionProvider } from './SubscriptionsSection.js';
3
3
  import type { SubscriptionsKey } from './locales.js';
4
4
  interface Props {
5
- provider: SubscriptionProvider | 'cursor-subscription';
5
+ provider: SubscriptionProvider;
6
6
  name: string;
7
7
  rpc: ConnectionHandle['rpc'];
8
8
  t: (key: SubscriptionsKey, params?: Record<string, unknown>) => string;
@@ -47,9 +47,7 @@ export function ProviderAccountManager({ provider, name, rpc, t, onClose, }) {
47
47
  let current = true;
48
48
  setLoading(true);
49
49
  setError('');
50
- void callSubscriptionsAuth(rpc, provider === 'cursor-subscription'
51
- ? 'cursorSettings'
52
- : 'providerSettings', { provider })
50
+ void callSubscriptionsAuth(rpc, 'providerSettings', { provider })
53
51
  .then((data) => {
54
52
  if (current) {
55
53
  setCatalog(data);
@@ -94,43 +92,32 @@ export function ProviderAccountManager({ provider, name, rpc, t, onClose, }) {
94
92
  setSaveError('');
95
93
  let savedEfforts = 0;
96
94
  try {
97
- if (provider !== 'cursor-subscription') {
98
- for (const { model, effort } of models.efforts) {
99
- await callSubscriptionsAuth(rpc, 'setModelDefault', {
100
- provider,
101
- model,
102
- ...(effort ? { effort } : {}),
103
- });
104
- savedEfforts++;
105
- if (!alive.current)
106
- return;
107
- }
95
+ for (const { model, effort } of models.efforts) {
96
+ await callSubscriptionsAuth(rpc, 'setModelDefault', {
97
+ provider,
98
+ model,
99
+ ...(effort ? { effort } : {}),
100
+ });
101
+ savedEfforts++;
102
+ if (!alive.current)
103
+ return;
108
104
  }
109
105
  // Re-read before writing: another client may have saved since this
110
106
  // dialog loaded, and a stale snapshot must never be written back.
111
- const latest = await callSubscriptionsAuth(rpc, provider === 'cursor-subscription'
112
- ? 'cursorSettings'
113
- : 'providerSettings', { provider });
107
+ const latest = await callSubscriptionsAuth(rpc, 'providerSettings', { provider });
114
108
  if (!alive.current)
115
109
  return;
116
- if (provider === 'cursor-subscription') {
117
- await callSubscriptionsAuth(rpc, 'cursorSetSettings', {
118
- settings: models.settings ?? latest.settings,
119
- });
120
- }
121
- else {
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
- });
133
- }
110
+ const base = models.settings === undefined
111
+ ? latest.settings
112
+ : mergeLatestAccounts(models.settings, latest.settings);
113
+ // The editor's draft carries the TTL it loaded, so the dialog always
114
+ // replaces it: the picker's choice when touched, otherwise the value
115
+ // from the freshly re-read settings.
116
+ const withTtl = mergePromptCacheTtl(base, ttlDirty ? ttlDraft : latest.settings.promptCacheTtl);
117
+ await callSubscriptionsAuth(rpc, 'setProviderSettings', {
118
+ provider,
119
+ settings: mergeAccountChanges(withTtl, changes),
120
+ });
134
121
  if (alive.current)
135
122
  onClose();
136
123
  }
@@ -155,81 +142,70 @@ export function ProviderAccountManager({ provider, name, rpc, t, onClose, }) {
155
142
  maxWidth: 'calc(100vw - 32px)',
156
143
  maxHeight: 'calc(100dvh - 32px)',
157
144
  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: {
145
+ }, 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('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('accountsEmpty') })), catalog.accounts.map((account) => {
146
+ const preferences = changes[account.key] ??
147
+ catalog.settings.accounts?.[account.key] ??
148
+ {};
149
+ const selected = accountPoolSelection(preferences, account.models);
150
+ const models = accountModelRows(account, preferences);
151
+ return (_jsxs("fieldset", { style: {
152
+ ...stack,
153
+ border,
154
+ borderRadius: 'var(--dsw-radius-lg, 12px)',
155
+ padding: 14,
156
+ margin: 0,
157
+ }, children: [_jsx("legend", { style: {
158
+ padding: '0 6px',
159
+ overflowWrap: 'anywhere',
160
+ maxWidth: '100%',
161
+ }, 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) => {
162
+ const next = { ...preferences };
163
+ if (event.target.value.trim())
164
+ next.alias = event.target.value;
165
+ else
166
+ delete next.alias;
167
+ edit(account.key, next);
168
+ } })] }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: preferences.poolEnabled !== false, onChange: (event) => edit(account.key, {
169
+ ...preferences,
170
+ poolEnabled: event.target.checked,
171
+ }) }), ' ', t('accountsPoolEnabled')] }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: preferences.independentEntry === true, onChange: (event) => edit(account.key, {
172
+ ...preferences,
173
+ independentEntry: event.target.checked,
174
+ }) }), ' ', 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
175
+ ? t('accountsModelsAll')
176
+ : t('accountsModelsCount', {
177
+ count: preferences.poolModels.length,
178
+ })] }), _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) => {
179
+ const next = { ...preferences };
180
+ if (event.target.checked)
181
+ delete next.poolModels;
182
+ else
183
+ next.poolModels = account.models.map((model) => model.id);
184
+ edit(account.key, next);
185
+ } }), ' ', t('accountsModelsAutomatic')] }), _jsxs("div", { style: actions, children: [_jsx("button", { type: "button", onClick: () => edit(account.key, {
186
+ ...preferences,
187
+ poolModels: models.map((model) => model.id),
188
+ }), children: t('modelsSelectAll') }), _jsx("button", { type: "button", onClick: () => edit(account.key, {
189
+ ...preferences,
190
+ poolModels: [],
191
+ }), children: t('modelsSelectNone') })] }), _jsxs("div", { style: {
192
+ ...stack,
193
+ gap: 8,
194
+ maxHeight: 240,
195
+ overflowY: 'auto',
196
+ }, children: [models.map((model) => (_jsxs("label", { style: { overflowWrap: 'anywhere' }, children: [_jsx("input", { type: "checkbox", checked: selected.has(model.id), onChange: (event) => {
197
+ const next = new Set(selected);
198
+ if (event.target.checked)
199
+ next.add(model.id);
200
+ else
201
+ next.delete(model.id);
202
+ edit(account.key, {
203
+ ...preferences,
204
+ poolModels: [...next],
205
+ });
206
+ } }), ' ', model.name, model.unavailable &&
207
+ ` (${t('modelsUnavailable')})`] }, model.id))), models.length === 0 && (_jsx("p", { className: "dsh-subscription-hint", children: t('accountsNoModels') }))] })] })] })] }, account.key));
208
+ })] })), _jsx(ProviderModelEditor, { ref: editor, provider: provider, rpc: rpc, t: t, disabled: saving, onDirtyChange: setModelsDirty }), provider === 'claude' && catalog && (_jsxs("div", { style: {
233
209
  ...stack,
234
210
  gap: 6,
235
211
  borderTop: border,
@@ -3,7 +3,7 @@ import type { ProviderPreferences } from '../provider-settings.js';
3
3
  import type { SubscriptionProvider } from './SubscriptionsSection.js';
4
4
  import type { SubscriptionsKey } from './locales.js';
5
5
  interface Props {
6
- provider: SubscriptionProvider | 'cursor-subscription';
6
+ provider: SubscriptionProvider;
7
7
  rpc: ConnectionHandle['rpc'];
8
8
  t: (key: SubscriptionsKey, params?: Record<string, unknown>) => string;
9
9
  /** Freeze the editor while the owning dialog submits. */