dsh-subscriptions 0.2.0 → 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.
Files changed (99) hide show
  1. package/README.md +212 -180
  2. package/lib/auth/claude-code-creds.js +31 -9
  3. package/lib/auth/device-flow.js +16 -9
  4. package/lib/auth/oauth-flow.js +13 -10
  5. package/lib/auth/rpc.js +74 -23
  6. package/lib/auth/store.js +80 -31
  7. package/lib/client/CursorCard.d.ts +1 -1
  8. package/lib/client/CursorCard.js +50 -16
  9. package/lib/client/ExternalUsageCards.d.ts +1 -1
  10. package/lib/client/ExternalUsageCards.js +37 -16
  11. package/lib/client/ImageGallery.d.ts +2 -2
  12. package/lib/client/ImageGallery.js +92 -33
  13. package/lib/client/ImageGenerateToolview.js +50 -14
  14. package/lib/client/OllamaCard.d.ts +1 -1
  15. package/lib/client/OllamaCard.js +26 -8
  16. package/lib/client/OllamaSettingsManager.js +113 -30
  17. package/lib/client/ProviderAccountManager.d.ts +1 -1
  18. package/lib/client/ProviderAccountManager.js +164 -49
  19. package/lib/client/ProviderModelEditor.js +107 -26
  20. package/lib/client/SpeedSelect.d.ts +62 -14
  21. package/lib/client/SpeedSelect.js +257 -46
  22. package/lib/client/SubscriptionUsageBadge.d.ts +4 -10
  23. package/lib/client/SubscriptionUsageBadge.js +300 -112
  24. package/lib/client/SubscriptionsSection.js +258 -85
  25. package/lib/client/UsageBadgeDisplaySetting.js +35 -12
  26. package/lib/client/UsageMeter.d.ts +1 -1
  27. package/lib/client/UsageMeter.js +62 -24
  28. package/lib/client/VideoGenerateToolview.js +69 -17
  29. package/lib/client/account-preferences.d.ts +8 -0
  30. package/lib/client/account-preferences.js +20 -5
  31. package/lib/client/host-contract.d.ts +11 -2
  32. package/lib/client/host-contract.js +20 -3
  33. package/lib/client/index.d.ts +5 -5
  34. package/lib/client/index.js +34 -12
  35. package/lib/client/locales.d.ts +10 -0
  36. package/lib/client/locales.js +61 -51
  37. package/lib/client/ollama-draft.js +21 -12
  38. package/lib/client/reset-action.js +6 -4
  39. package/lib/client/reset-credits-view.d.ts +1 -1
  40. package/lib/client/reset-credits-view.js +66 -17
  41. package/lib/client/subscription-card-styles.js +94 -24
  42. package/lib/client/usage-color-preferences.js +4 -2
  43. package/lib/client/usage-pace.js +30 -12
  44. package/lib/client.js +287 -47
  45. package/lib/client.js.map +1 -1
  46. package/lib/http.js +1 -1
  47. package/lib/index.d.ts +3 -3
  48. package/lib/index.js +118 -64
  49. package/lib/model-defaults.js +4 -2
  50. package/lib/provider-settings.d.ts +12 -0
  51. package/lib/provider-settings.js +58 -16
  52. package/lib/providers/account-preferences.js +51 -22
  53. package/lib/providers/accounts.js +15 -9
  54. package/lib/providers/antigravity-oauth-client.js +2 -2
  55. package/lib/providers/antigravity.js +141 -68
  56. package/lib/providers/catalog-store.js +120 -41
  57. package/lib/providers/claude-cli-version.js +3 -3
  58. package/lib/providers/claude.d.ts +9 -2
  59. package/lib/providers/claude.js +153 -90
  60. package/lib/providers/codex-client-version.js +3 -3
  61. package/lib/providers/codex-search.js +40 -13
  62. package/lib/providers/codex.js +196 -112
  63. package/lib/providers/common.js +71 -39
  64. package/lib/providers/copilot.js +143 -82
  65. package/lib/providers/cursor-adapter.js +42 -18
  66. package/lib/providers/cursor-auth.d.ts +1 -1
  67. package/lib/providers/cursor-auth.js +69 -24
  68. package/lib/providers/cursor-model-settings.js +14 -5
  69. package/lib/providers/cursor-usage.js +27 -13
  70. package/lib/providers/external-usage-controller.js +14 -5
  71. package/lib/providers/external-usage.js +59 -23
  72. package/lib/providers/grok.js +152 -89
  73. package/lib/providers/image-pool.js +29 -11
  74. package/lib/providers/minimax-usage.js +31 -11
  75. package/lib/providers/npm-cli-version.js +10 -3
  76. package/lib/providers/ollama-settings.js +24 -8
  77. package/lib/providers/ollama-web.js +29 -11
  78. package/lib/providers/ollama.js +129 -66
  79. package/lib/providers/pool-family.js +11 -3
  80. package/lib/providers/pool-health.js +28 -8
  81. package/lib/providers/pool-usage.js +16 -9
  82. package/lib/providers/pool.d.ts +8 -0
  83. package/lib/providers/pool.js +81 -42
  84. package/lib/providers/rate-limit.js +8 -2
  85. package/lib/providers/replay.js +40 -17
  86. package/lib/providers/reset-redemption.js +18 -6
  87. package/lib/tools/image-generate.js +123 -65
  88. package/lib/tools/registration.js +5 -2
  89. package/lib/tools/video-generate.js +51 -31
  90. package/lib/tools/x-search.js +50 -22
  91. package/lib/translate/anthropic.js +85 -37
  92. package/lib/translate/antigravity-schema.js +71 -28
  93. package/lib/translate/antigravity-thinking.js +22 -9
  94. package/lib/translate/antigravity.js +141 -50
  95. package/lib/translate/chat-completions.js +67 -23
  96. package/lib/translate/resolved.js +55 -21
  97. package/lib/translate/responses.js +69 -29
  98. package/lib/translate/sse.js +4 -1
  99. package/package.json +26 -4
@@ -22,19 +22,22 @@ export function validateModels(models, label) {
22
22
  if (model.name !== undefined && model.name.length === 0) {
23
23
  throw new Error(`${label}: catalog model "${model.id}" has an empty name`);
24
24
  }
25
- if (model.contextWindow !== undefined
26
- && (!Number.isInteger(model.contextWindow) || model.contextWindow <= 0)) {
25
+ if (model.contextWindow !== undefined &&
26
+ (!Number.isInteger(model.contextWindow) || model.contextWindow <= 0)) {
27
27
  throw new Error(`${label}: catalog model "${model.id}" contextWindow must be a positive integer`);
28
28
  }
29
- if (model.maxTokens !== undefined && (!Number.isInteger(model.maxTokens) || model.maxTokens <= 0)) {
29
+ if (model.maxTokens !== undefined &&
30
+ (!Number.isInteger(model.maxTokens) || model.maxTokens <= 0)) {
30
31
  throw new Error(`${label}: catalog model "${model.id}" maxTokens must be a positive integer`);
31
32
  }
32
- if (model.inputModalities !== undefined
33
- && (model.inputModalities.length === 0
34
- || model.inputModalities.some(modality => modality !== 'text' && modality !== 'image'))) {
33
+ if (model.inputModalities !== undefined &&
34
+ (model.inputModalities.length === 0 ||
35
+ model.inputModalities.some((modality) => modality !== 'text' && modality !== 'image'))) {
35
36
  throw new Error(`${label}: catalog model "${model.id}" inputModalities must be a non-empty list of "text"/"image"`);
36
37
  }
37
- if (model.wire !== undefined && model.wire !== 'chat-completions' && model.wire !== 'responses') {
38
+ if (model.wire !== undefined &&
39
+ model.wire !== 'chat-completions' &&
40
+ model.wire !== 'responses') {
38
41
  throw new Error(`${label}: catalog model "${model.id}" wire must be "chat-completions" or "responses"`);
39
42
  }
40
43
  if (seen.has(model.id))
@@ -42,11 +45,15 @@ export function validateModels(models, label) {
42
45
  seen.add(model.id);
43
46
  return {
44
47
  id: model.id,
45
- ...model.name === undefined ? {} : { name: model.name },
46
- ...model.contextWindow === undefined ? {} : { contextWindow: model.contextWindow },
47
- ...model.maxTokens === undefined ? {} : { maxTokens: model.maxTokens },
48
- ...model.inputModalities === undefined ? {} : { inputModalities: [...model.inputModalities] },
49
- ...model.wire === undefined ? {} : { wire: model.wire },
48
+ ...(model.name === undefined ? {} : { name: model.name }),
49
+ ...(model.contextWindow === undefined
50
+ ? {}
51
+ : { contextWindow: model.contextWindow }),
52
+ ...(model.maxTokens === undefined ? {} : { maxTokens: model.maxTokens }),
53
+ ...(model.inputModalities === undefined
54
+ ? {}
55
+ : { inputModalities: [...model.inputModalities] }),
56
+ ...(model.wire === undefined ? {} : { wire: model.wire }),
50
57
  };
51
58
  });
52
59
  }
@@ -80,6 +87,8 @@ export async function parseProviderJson(input, origin) {
80
87
  catch (error) {
81
88
  if (!(error instanceof SyntaxError))
82
89
  throw error;
90
+ // Do not attach the original SyntaxError: Node's message quotes the input, and the host renders cause.
91
+ // eslint-disable-next-line preserve-caught-error -- the replacement omits the provider excerpt on purpose
83
92
  throw new SyntaxError(providerDiagnostic(`${origin}: invalid JSON`, true));
84
93
  }
85
94
  }
@@ -122,7 +131,9 @@ export async function readBoundedJson(response, label, maxBytes) {
122
131
  try {
123
132
  reader.releaseLock();
124
133
  }
125
- catch { /* best effort */ }
134
+ catch {
135
+ /* best effort */
136
+ }
126
137
  }
127
138
  try {
128
139
  return JSON.parse(text);
@@ -187,7 +198,9 @@ export async function httpLlmError(response, label, options = {}) {
187
198
  // re-hitting the same closed window.
188
199
  const rateLimited = response.status === 429;
189
200
  const reset = rateLimited
190
- ? options.rateLimitReset?.(response, body, now) ?? googleQuotaReset(body, now) ?? retryAfterInstant(response, now)
201
+ ? (options.rateLimitReset?.(response, body, now) ??
202
+ googleQuotaReset(body, now) ??
203
+ retryAfterInstant(response, now))
191
204
  : retryAfterInstant(response, now);
192
205
  if (reset === undefined && rateLimited) {
193
206
  // Header names and values are provider-controlled too; none are displayed.
@@ -195,7 +208,9 @@ export async function httpLlmError(response, label, options = {}) {
195
208
  }
196
209
  return new LlmError(message, code, {
197
210
  status: response.status,
198
- ...reset === undefined ? {} : { providerRetryAfterMs: waitFromReset(reset, now) },
211
+ ...(reset === undefined
212
+ ? {}
213
+ : { providerRetryAfterMs: waitFromReset(reset, now) }),
199
214
  });
200
215
  }
201
216
  /**
@@ -284,9 +299,13 @@ export function idleWatchdog(caller, timeoutMs) {
284
299
  */
285
300
  export function mapFetchFailure(label, error, watchdog, caller) {
286
301
  if (watchdog.timedOut())
287
- return new LlmError(`${label} stream idle timeout`, 'TIMEOUT', { cause: error });
302
+ return new LlmError(`${label} stream idle timeout`, 'TIMEOUT', {
303
+ cause: error,
304
+ });
288
305
  if (caller?.aborted === true)
289
- return new LlmError(`${label} request aborted by caller`, 'ABORTED', { cause: error });
306
+ return new LlmError(`${label} request aborted by caller`, 'ABORTED', {
307
+ cause: error,
308
+ });
290
309
  if (error instanceof LlmError)
291
310
  return error;
292
311
  return new LlmError(`${label} request failed`, 'TRANSPORT', { cause: error });
@@ -387,10 +406,11 @@ export class TokenManager {
387
406
  async session(forceRefresh = false) {
388
407
  const session = await this.options.load();
389
408
  if (session === undefined) {
390
- throw new LlmError(`dsh-subscriptions: not logged in to ${this.options.displayName}; `
391
- + 'log in via Settings → Subscriptions in the dsh web app', 'MISSING_CREDENTIAL');
409
+ throw new LlmError(`dsh-subscriptions: not logged in to ${this.options.displayName}; ` +
410
+ 'log in via Settings → Subscriptions in the dsh web app', 'MISSING_CREDENTIAL');
392
411
  }
393
- if (!forceRefresh && session.expiresAt - Date.now() > this.options.preemptMs) {
412
+ if (!forceRefresh &&
413
+ session.expiresAt - Date.now() > this.options.preemptMs) {
394
414
  return session;
395
415
  }
396
416
  this.inflight ??= this.doRefresh(session).finally(() => {
@@ -406,7 +426,9 @@ export class TokenManager {
406
426
  // log out an account that just signed in. Serve it instead.
407
427
  const attempted = this.attempted;
408
428
  const stored = await this.options.load();
409
- if (stored !== undefined && attempted !== undefined && stored.refreshToken !== attempted.refreshToken) {
429
+ if (stored !== undefined &&
430
+ attempted !== undefined &&
431
+ stored.refreshToken !== attempted.refreshToken) {
410
432
  return stored;
411
433
  }
412
434
  await this.options.remove();
@@ -426,9 +448,9 @@ export class TokenManager {
426
448
  // A concurrent caller may have refreshed while this one waited: re-read
427
449
  // the store and skip the round trip when the stored session is fresh.
428
450
  const current = await this.options.load();
429
- if (current !== undefined
430
- && current.accessToken !== session.accessToken
431
- && current.expiresAt - Date.now() > this.options.preemptMs) {
451
+ if (current !== undefined &&
452
+ current.accessToken !== session.accessToken &&
453
+ current.expiresAt - Date.now() > this.options.preemptMs) {
432
454
  return current;
433
455
  }
434
456
  const attempted = current ?? session;
@@ -446,14 +468,14 @@ export const DISCOVERY_TIMEOUT_MS = 10_000;
446
468
  */
447
469
  export function withTimeout(work, timeoutMs) {
448
470
  const signal = AbortSignal.timeout(timeoutMs);
449
- const aborted = new Promise(resolve => {
471
+ const aborted = new Promise((resolve) => {
450
472
  if (signal.aborted)
451
473
  resolve(undefined);
452
474
  else
453
475
  signal.addEventListener('abort', () => resolve(undefined), { once: true });
454
476
  });
455
477
  return Promise.race([
456
- work(signal).then(value => (signal.aborted ? undefined : value), (error) => {
478
+ work(signal).then((value) => (signal.aborted ? undefined : value), (error) => {
457
479
  if (signal.aborted)
458
480
  return undefined;
459
481
  throw error;
@@ -463,7 +485,9 @@ export function withTimeout(work, timeoutMs) {
463
485
  }
464
486
  /** Display name for a wire reasoning-effort identifier. */
465
487
  export function effortDisplayName(effort) {
466
- return effort === 'xhigh' ? 'Extra High' : effort.charAt(0).toUpperCase() + effort.slice(1);
488
+ return effort === 'xhigh'
489
+ ? 'Extra High'
490
+ : effort.charAt(0).toUpperCase() + effort.slice(1);
467
491
  }
468
492
  /**
469
493
  * Fold a configured per-model default effort into a reasoning block, keeping
@@ -492,7 +516,9 @@ export function mergeReasoning(configuredDefault, base, options) {
492
516
  ? undefined
493
517
  : {
494
518
  efforts: [...base.efforts],
495
- ...(base.defaultEffort === undefined ? {} : { defaultEffort: base.defaultEffort }),
519
+ ...(base.defaultEffort === undefined
520
+ ? {}
521
+ : { defaultEffort: base.defaultEffort }),
496
522
  };
497
523
  if (configuredDefault === undefined)
498
524
  return detached;
@@ -502,10 +528,13 @@ export function mergeReasoning(configuredDefault, base, options) {
502
528
  // catalog does not cover). Inventing a reasoning block here would claim a
503
529
  // capability nobody advertised; only a fallback-based provider may.
504
530
  return options?.extendable === true
505
- ? { efforts: [{ id: effort, name: effortDisplayName(effort) }], defaultEffort: effort }
531
+ ? {
532
+ efforts: [{ id: effort, name: effortDisplayName(effort) }],
533
+ defaultEffort: effort,
534
+ }
506
535
  : undefined;
507
536
  }
508
- if (base.efforts.some(entry => entry.id === effort)) {
537
+ if (base.efforts.some((entry) => entry.id === effort)) {
509
538
  return { efforts: [...base.efforts], defaultEffort: effort };
510
539
  }
511
540
  if (options?.extendable !== true)
@@ -584,7 +613,9 @@ export class ModelCatalogCache {
584
613
  if (this.persistence === undefined)
585
614
  return Promise.resolve();
586
615
  this.seeded ??= this.persistence.load().then((snapshot) => {
587
- if (snapshot !== undefined && this.entry === undefined && !this.seedDisabled) {
616
+ if (snapshot !== undefined &&
617
+ this.entry === undefined &&
618
+ !this.seedDisabled) {
588
619
  this.entry = snapshot;
589
620
  }
590
621
  }, () => undefined);
@@ -660,8 +691,8 @@ export class ModelCatalogCache {
660
691
  }
661
692
  /** Whether discovery failed because the stored login is gone. */
662
693
  export function isMissingOrInvalidCredential(error) {
663
- return error instanceof LlmError
664
- && (error.code === 'MISSING_CREDENTIAL' || error.code === 'INVALID_CREDENTIAL');
694
+ return (error instanceof LlmError &&
695
+ (error.code === 'MISSING_CREDENTIAL' || error.code === 'INVALID_CREDENTIAL'));
665
696
  }
666
697
  /** Whether discovery stopped because the caller cancelled or the timeout fired. */
667
698
  export function isDiscoveryAborted(error, signal) {
@@ -669,9 +700,9 @@ export function isDiscoveryAborted(error, signal) {
669
700
  return true;
670
701
  // Only treat abort-shaped errors as cancellation when this call had a signal;
671
702
  // a refresh TimeoutError must not fail the whole picker union.
672
- return signal !== undefined
673
- && error instanceof Error
674
- && (error.name === 'AbortError' || error.name === 'TimeoutError');
703
+ return (signal !== undefined &&
704
+ error instanceof Error &&
705
+ (error.name === 'AbortError' || error.name === 'TimeoutError'));
675
706
  }
676
707
  /**
677
708
  * Whether discovery failed because the access token was rejected. After
@@ -680,8 +711,8 @@ export function isDiscoveryAborted(error, signal) {
680
711
  * the refresh grant still answers, so the store keeps the session.
681
712
  */
682
713
  export function isDiscoveryAuthFailure(error) {
683
- return (error instanceof OAuthEndpointError && error.status === 401)
684
- || (error instanceof LlmError && error.code === 'AUTH');
714
+ return ((error instanceof OAuthEndpointError && error.status === 401) ||
715
+ (error instanceof LlmError && error.code === 'AUTH'));
685
716
  }
686
717
  /**
687
718
  * Run a catalog fetch, retrying once after a forced token refresh when the
@@ -701,7 +732,8 @@ export async function discoverOrRetryAuth(session, catalog, run) {
701
732
  return await run();
702
733
  }
703
734
  catch (retryError) {
704
- if (!isMissingOrInvalidCredential(retryError) && isDiscoveryAuthFailure(retryError)) {
735
+ if (!isMissingOrInvalidCredential(retryError) &&
736
+ isDiscoveryAuthFailure(retryError)) {
705
737
  catalog.invalidate();
706
738
  }
707
739
  throw retryError;