dsh-subscriptions 0.2.0 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +220 -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.d.ts +8 -4
  6. package/lib/auth/rpc.js +89 -25
  7. package/lib/auth/store.js +80 -31
  8. package/lib/client/CursorCard.d.ts +1 -1
  9. package/lib/client/CursorCard.js +50 -16
  10. package/lib/client/ExternalUsageCards.d.ts +1 -1
  11. package/lib/client/ExternalUsageCards.js +37 -16
  12. package/lib/client/ImageGallery.d.ts +2 -2
  13. package/lib/client/ImageGallery.js +92 -33
  14. package/lib/client/ImageGenerateToolview.js +50 -14
  15. package/lib/client/OllamaCard.d.ts +1 -1
  16. package/lib/client/OllamaCard.js +26 -8
  17. package/lib/client/OllamaSettingsManager.js +113 -30
  18. package/lib/client/ProviderAccountManager.d.ts +1 -1
  19. package/lib/client/ProviderAccountManager.js +164 -49
  20. package/lib/client/ProviderModelEditor.js +137 -34
  21. package/lib/client/SpeedSelect.d.ts +62 -14
  22. package/lib/client/SpeedSelect.js +261 -49
  23. package/lib/client/SubscriptionUsageBadge.d.ts +4 -10
  24. package/lib/client/SubscriptionUsageBadge.js +300 -112
  25. package/lib/client/SubscriptionsSection.d.ts +2 -0
  26. package/lib/client/SubscriptionsSection.js +265 -85
  27. package/lib/client/UsageBadgeDisplaySetting.js +35 -12
  28. package/lib/client/UsageMeter.d.ts +1 -1
  29. package/lib/client/UsageMeter.js +62 -24
  30. package/lib/client/VideoGenerateToolview.js +69 -17
  31. package/lib/client/account-preferences.d.ts +8 -0
  32. package/lib/client/account-preferences.js +20 -5
  33. package/lib/client/host-contract.d.ts +11 -2
  34. package/lib/client/host-contract.js +20 -3
  35. package/lib/client/index.d.ts +5 -5
  36. package/lib/client/index.js +34 -12
  37. package/lib/client/locales.d.ts +22 -0
  38. package/lib/client/locales.js +73 -51
  39. package/lib/client/model-filter.d.ts +37 -0
  40. package/lib/client/model-filter.js +45 -0
  41. package/lib/client/ollama-draft.js +21 -12
  42. package/lib/client/reset-action.js +6 -4
  43. package/lib/client/reset-credits-view.d.ts +1 -1
  44. package/lib/client/reset-credits-view.js +66 -17
  45. package/lib/client/subscription-card-styles.js +94 -24
  46. package/lib/client/usage-color-preferences.js +4 -2
  47. package/lib/client/usage-pace.js +30 -12
  48. package/lib/client/usage-window-filter.d.ts +7 -0
  49. package/lib/client/usage-window-filter.js +12 -0
  50. package/lib/client.js +480 -136
  51. package/lib/client.js.map +1 -1
  52. package/lib/http.js +1 -1
  53. package/lib/index.d.ts +3 -3
  54. package/lib/index.js +167 -76
  55. package/lib/model-defaults.js +4 -2
  56. package/lib/provider-settings.d.ts +17 -0
  57. package/lib/provider-settings.js +79 -16
  58. package/lib/providers/account-preferences.js +51 -22
  59. package/lib/providers/accounts.js +15 -9
  60. package/lib/providers/antigravity-oauth-client.js +2 -2
  61. package/lib/providers/antigravity.js +141 -68
  62. package/lib/providers/catalog-store.js +120 -41
  63. package/lib/providers/claude-cli-version.js +3 -3
  64. package/lib/providers/claude.d.ts +9 -2
  65. package/lib/providers/claude.js +153 -90
  66. package/lib/providers/codex-client-version.js +3 -3
  67. package/lib/providers/codex-search.js +40 -13
  68. package/lib/providers/codex.js +196 -112
  69. package/lib/providers/common.js +71 -39
  70. package/lib/providers/copilot.js +143 -82
  71. package/lib/providers/cursor-adapter.js +42 -18
  72. package/lib/providers/cursor-auth.d.ts +1 -1
  73. package/lib/providers/cursor-auth.js +69 -24
  74. package/lib/providers/cursor-model-settings.js +14 -5
  75. package/lib/providers/cursor-usage.js +27 -13
  76. package/lib/providers/external-usage-controller.js +14 -5
  77. package/lib/providers/external-usage.js +59 -23
  78. package/lib/providers/grok.js +152 -89
  79. package/lib/providers/image-pool.js +29 -11
  80. package/lib/providers/minimax-usage.js +31 -11
  81. package/lib/providers/npm-cli-version.js +10 -3
  82. package/lib/providers/ollama-settings.js +24 -8
  83. package/lib/providers/ollama-web.js +29 -11
  84. package/lib/providers/ollama.js +129 -66
  85. package/lib/providers/pool-family.js +11 -3
  86. package/lib/providers/pool-health.js +28 -8
  87. package/lib/providers/pool-usage.js +16 -9
  88. package/lib/providers/pool.d.ts +8 -0
  89. package/lib/providers/pool.js +81 -42
  90. package/lib/providers/rate-limit.js +8 -2
  91. package/lib/providers/replay.js +40 -17
  92. package/lib/providers/reset-redemption.js +18 -6
  93. package/lib/tools/image-generate.js +123 -65
  94. package/lib/tools/registration.js +5 -2
  95. package/lib/tools/video-generate.js +51 -31
  96. package/lib/tools/x-search.js +50 -22
  97. package/lib/translate/anthropic.js +85 -37
  98. package/lib/translate/antigravity-schema.js +71 -28
  99. package/lib/translate/antigravity-thinking.js +22 -9
  100. package/lib/translate/antigravity.js +151 -52
  101. package/lib/translate/chat-completions.js +67 -23
  102. package/lib/translate/resolved.js +55 -21
  103. package/lib/translate/responses.js +69 -29
  104. package/lib/translate/sse.js +4 -1
  105. package/package.json +27 -4
@@ -13,12 +13,12 @@
13
13
  * fresh exchange, so the standard preempt/401-retry machinery applies
14
14
  * unchanged.
15
15
  */
16
- import { EMPTY_RESPONSE_CODE, errorChain, LlmAdapter, LlmError, ReasoningEffortId } from '@deepseek-ai/dsh-llm';
16
+ import { EMPTY_RESPONSE_CODE, errorChain, LlmAdapter, LlmError, ReasoningEffortId, } from '@deepseek-ai/dsh-llm';
17
17
  import { resolveImages } from '../translate/resolved.js';
18
18
  import { streamChatCompletions, toChatMessages, toChatTools, } from '../translate/chat-completions.js';
19
- import { streamResponses, toResponsesInput, toResponsesTools } from '../translate/responses.js';
19
+ import { streamResponses, toResponsesInput, toResponsesTools, } from '../translate/responses.js';
20
20
  import { httpLlmError, idleWatchdog, mapFetchFailure, mergeReasoning, ModelCatalogCache, discoverAcrossAccounts, discoverOrRetryAuth, isDiscoveryAborted, isMissingOrInvalidCredential, parseProviderJson, oauthEndpointError, OAuthEndpointError, } from './common.js';
21
- import { AccountTokenManager, DISCOVERY_TIMEOUT_MS, unionAccountCatalogs } from './accounts.js';
21
+ import { AccountTokenManager, DISCOVERY_TIMEOUT_MS, unionAccountCatalogs, } from './accounts.js';
22
22
  import { hostFetch } from '../http.js';
23
23
  import { DEFAULT_RATE_LIMIT_WAIT, DEFAULT_RETRY, subscriptionRetryPolicy, } from './rate-limit.js';
24
24
  /**
@@ -61,18 +61,21 @@ let vscodeVersionInflight;
61
61
  * @returns a `major.minor.patch` version string.
62
62
  */
63
63
  export async function latestVsCodeVersion(fetchFn = hostFetch, forceRefresh = false) {
64
- if (!forceRefresh && vscodeVersionCache !== undefined
65
- && Date.now() - vscodeVersionCache.at < VSCODE_VERSION_TTL_MS) {
64
+ if (!forceRefresh &&
65
+ vscodeVersionCache !== undefined &&
66
+ Date.now() - vscodeVersionCache.at < VSCODE_VERSION_TTL_MS) {
66
67
  return vscodeVersionCache.version;
67
68
  }
68
69
  vscodeVersionInflight ??= (async () => {
69
70
  try {
70
- const response = await fetchFn(VSCODE_RELEASES_URL, { headers: { accept: 'application/json' } });
71
+ const response = await fetchFn(VSCODE_RELEASES_URL, {
72
+ headers: { accept: 'application/json' },
73
+ });
71
74
  if (!response.ok)
72
75
  throw new Error(`HTTP ${String(response.status)}`);
73
76
  const releases = await response.json();
74
77
  const version = Array.isArray(releases)
75
- ? releases.find(entry => typeof entry === 'string' && /^\d+\.\d+\.\d+$/.test(entry))
78
+ ? releases.find((entry) => typeof entry === 'string' && /^\d+\.\d+\.\d+$/.test(entry))
76
79
  : undefined;
77
80
  if (version === undefined)
78
81
  throw new Error('no version string in the feed');
@@ -84,7 +87,9 @@ export async function latestVsCodeVersion(fetchFn = hostFetch, forceRefresh = fa
84
87
  // cache, else the pinned fallback.
85
88
  return vscodeVersionCache?.version ?? FALLBACK_VSCODE_VERSION;
86
89
  }
87
- })().finally(() => { vscodeVersionInflight = undefined; });
90
+ })().finally(() => {
91
+ vscodeVersionInflight = undefined;
92
+ });
88
93
  return vscodeVersionInflight;
89
94
  }
90
95
  /**
@@ -123,7 +128,7 @@ export function copilotHeaders(hasVision = false, vscodeVersion = FALLBACK_VSCOD
123
128
  'copilot-integration-id': 'vscode-chat',
124
129
  'openai-intent': 'conversation-edits',
125
130
  'x-github-api-version': '2026-06-01',
126
- ...hasVision ? { 'copilot-vision-request': 'true' } : {},
131
+ ...(hasVision ? { 'copilot-vision-request': 'true' } : {}),
127
132
  };
128
133
  }
129
134
  /**
@@ -137,14 +142,14 @@ export function copilotHeaders(hasVision = false, vscodeVersion = FALLBACK_VSCOD
137
142
  export async function exchangeCopilotToken(githubToken, fetchFn = hostFetch) {
138
143
  const response = await fetchFn(COPILOT_TOKEN_URL, {
139
144
  headers: {
140
- 'authorization': `Bearer ${githubToken}`,
141
- 'accept': 'application/json',
145
+ authorization: `Bearer ${githubToken}`,
146
+ accept: 'application/json',
142
147
  ...copilotHeaders(false, await latestVsCodeVersion(fetchFn)),
143
148
  },
144
149
  });
145
150
  if (!response.ok)
146
151
  throw await oauthEndpointError(response, 'copilot');
147
- const wire = await parseProviderJson(response, 'copilot token exchange');
152
+ const wire = (await parseProviderJson(response, 'copilot token exchange'));
148
153
  if (typeof wire.token !== 'string' || wire.token.length === 0) {
149
154
  throw new Error('copilot token endpoint returned no token');
150
155
  }
@@ -170,14 +175,14 @@ export async function completeCopilotLogin(githubToken, fetchFn = hostFetch) {
170
175
  try {
171
176
  const response = await fetchFn(GITHUB_USER_URL, {
172
177
  headers: {
173
- 'authorization': `Bearer ${githubToken}`,
174
- 'accept': 'application/json',
178
+ authorization: `Bearer ${githubToken}`,
179
+ accept: 'application/json',
175
180
  // api.github.com only demands a user agent; no editor disguise needed.
176
181
  'user-agent': 'GitHubCopilotChat/0.35.0',
177
182
  },
178
183
  });
179
184
  if (response.ok) {
180
- const profile = await response.json();
185
+ const profile = (await response.json());
181
186
  if (typeof profile.login === 'string' && profile.login.length > 0)
182
187
  account = profile.login;
183
188
  }
@@ -189,7 +194,7 @@ export async function completeCopilotLogin(githubToken, fetchFn = hostFetch) {
189
194
  accessToken: pair.accessToken,
190
195
  refreshToken: githubToken,
191
196
  expiresAt: pair.expiresAt,
192
- ...account === undefined ? {} : { account },
197
+ ...(account === undefined ? {} : { account }),
193
198
  };
194
199
  }
195
200
  /**
@@ -205,7 +210,7 @@ export async function refreshCopilot(session, fetchFn = hostFetch) {
205
210
  accessToken: pair.accessToken,
206
211
  refreshToken: session.refreshToken,
207
212
  expiresAt: pair.expiresAt,
208
- ...session.account === undefined ? {} : { account: session.account },
213
+ ...(session.account === undefined ? {} : { account: session.account }),
209
214
  };
210
215
  }
211
216
  /**
@@ -214,11 +219,14 @@ export async function refreshCopilot(session, fetchFn = hostFetch) {
214
219
  * @returns true when re-login is the only fix (GitHub token revoked or the subscription lost).
215
220
  */
216
221
  export function isCopilotPermanentRefreshError(error) {
217
- return error instanceof OAuthEndpointError && (error.status === 401 || error.status === 403);
222
+ return (error instanceof OAuthEndpointError &&
223
+ (error.status === 401 || error.status === 403));
218
224
  }
219
225
  /** Display name for one Copilot wire reasoning-effort value. */
220
226
  function copilotEffortName(effort) {
221
- return effort === 'xhigh' ? 'Extra High' : effort.charAt(0).toUpperCase() + effort.slice(1);
227
+ return effort === 'xhigh'
228
+ ? 'Extra High'
229
+ : effort.charAt(0).toUpperCase() + effort.slice(1);
222
230
  }
223
231
  /**
224
232
  * Map a catalog entry's `supports.reasoning_effort` array into selectable
@@ -236,7 +244,10 @@ function copilotReasoning(entry) {
236
244
  if (typeof value !== 'string' || value.length === 0 || seen.has(value))
237
245
  continue;
238
246
  seen.add(value);
239
- efforts.push({ id: ReasoningEffortId(value), name: copilotEffortName(value) });
247
+ efforts.push({
248
+ id: ReasoningEffortId(value),
249
+ name: copilotEffortName(value),
250
+ });
240
251
  }
241
252
  return efforts.length > 0 ? { efforts } : undefined;
242
253
  }
@@ -260,23 +271,26 @@ function copilotReasoning(entry) {
260
271
  export async function fetchCopilotModels(session, fetchFn = hostFetch, signal) {
261
272
  const response = await fetchFn(COPILOT_MODELS_URL, {
262
273
  headers: {
263
- 'authorization': `Bearer ${session.accessToken}`,
264
- 'accept': 'application/json',
274
+ authorization: `Bearer ${session.accessToken}`,
275
+ accept: 'application/json',
265
276
  ...copilotHeaders(false, await latestVsCodeVersion(fetchFn)),
266
277
  },
267
- ...signal === undefined ? {} : { signal },
278
+ ...(signal === undefined ? {} : { signal }),
268
279
  });
269
280
  if (!response.ok)
270
281
  throw await oauthEndpointError(response, 'copilot models');
271
- const payload = await parseProviderJson(response, 'copilot models');
282
+ const payload = (await parseProviderJson(response, 'copilot models'));
272
283
  if (!Array.isArray(payload.data))
273
284
  throw new Error('copilot models endpoint returned no data array');
274
285
  const seen = new Set();
275
286
  const discovered = [];
276
287
  for (const entry of payload.data) {
277
- if (typeof entry.id !== 'string' || entry.id.length === 0 || seen.has(entry.id))
288
+ if (typeof entry.id !== 'string' ||
289
+ entry.id.length === 0 ||
290
+ seen.has(entry.id))
278
291
  continue;
279
- if (entry.model_picker_enabled !== true || entry.policy?.state === 'disabled')
292
+ if (entry.model_picker_enabled !== true ||
293
+ entry.policy?.state === 'disabled')
280
294
  continue;
281
295
  let wire;
282
296
  let responsesSupported = false;
@@ -293,15 +307,19 @@ export async function fetchCopilotModels(session, fetchFn = hostFetch, signal) {
293
307
  const reasoning = copilotReasoning(entry);
294
308
  discovered.push({
295
309
  id: entry.id,
296
- name: typeof entry.name === 'string' && entry.name.length > 0 ? entry.name : entry.id,
297
- ...typeof entry.capabilities?.limits?.max_context_window_tokens === 'number'
298
- && entry.capabilities.limits.max_context_window_tokens > 0
310
+ name: typeof entry.name === 'string' && entry.name.length > 0
311
+ ? entry.name
312
+ : entry.id,
313
+ ...(typeof entry.capabilities?.limits?.max_context_window_tokens ===
314
+ 'number' && entry.capabilities.limits.max_context_window_tokens > 0
299
315
  ? { contextWindow: entry.capabilities.limits.max_context_window_tokens }
300
- : {},
301
- inputModalities: entry.capabilities?.supports?.vision === true ? ['text', 'image'] : ['text'],
302
- ...reasoning === undefined ? {} : { reasoning },
303
- ...wire === undefined ? {} : { copilotWire: wire },
304
- ...responsesSupported ? { copilotResponses: true } : {},
316
+ : {}),
317
+ inputModalities: entry.capabilities?.supports?.vision === true
318
+ ? ['text', 'image']
319
+ : ['text'],
320
+ ...(reasoning === undefined ? {} : { reasoning }),
321
+ ...(wire === undefined ? {} : { copilotWire: wire }),
322
+ ...(responsesSupported ? { copilotResponses: true } : {}),
305
323
  });
306
324
  }
307
325
  // An empty catalog from a 200 response is treated as a discovery failure so
@@ -343,7 +361,8 @@ export function copilotRequestWire(entry, options) {
343
361
  return wire;
344
362
  if (options.tools === undefined || options.tools.length === 0)
345
363
  return wire;
346
- if (options.reasoningEffort === undefined || options.reasoningEffort === 'none')
364
+ if (options.reasoningEffort === undefined ||
365
+ options.reasoningEffort === 'none')
347
366
  return wire;
348
367
  return 'responses';
349
368
  }
@@ -360,16 +379,18 @@ export function copilotChatRequestBody(options, messages) {
360
379
  return {
361
380
  model: options.model,
362
381
  messages,
363
- ...options.tools !== undefined && options.tools.length > 0
382
+ ...(options.tools !== undefined && options.tools.length > 0
364
383
  ? { tools: toChatTools(options.tools), tool_choice: 'auto' }
365
- : {},
366
- ...options.maxTokens !== undefined ? { max_completion_tokens: options.maxTokens } : {},
384
+ : {}),
385
+ ...(options.maxTokens !== undefined
386
+ ? { max_completion_tokens: options.maxTokens }
387
+ : {}),
367
388
  // The harness only passes an effort the resolved model advertised (the
368
389
  // catalog's reasoning_effort array), and copilotRequestWire keeps the
369
390
  // tools+effort combination off this wire for models that reject it.
370
- ...options.reasoningEffort !== undefined
391
+ ...(options.reasoningEffort !== undefined
371
392
  ? { reasoning_effort: String(options.reasoningEffort) }
372
- : {},
393
+ : {}),
373
394
  // The upstream is stream-only; usage arrives on the terminal chunk.
374
395
  stream: true,
375
396
  stream_options: { include_usage: true },
@@ -385,17 +406,24 @@ export function copilotChatRequestBody(options, messages) {
385
406
  export function copilotResponsesRequestBody(options, resolved) {
386
407
  return {
387
408
  model: options.model,
388
- ...resolved.instructions !== undefined ? { instructions: resolved.instructions } : {},
409
+ ...(resolved.instructions !== undefined
410
+ ? { instructions: resolved.instructions }
411
+ : {}),
389
412
  input: resolved.input,
390
- ...options.tools !== undefined && options.tools.length > 0
391
- ? { tools: toResponsesTools(options.tools, { strict: false }), tool_choice: 'auto' }
392
- : {},
393
- ...options.maxTokens !== undefined ? { max_output_tokens: options.maxTokens } : {},
413
+ ...(options.tools !== undefined && options.tools.length > 0
414
+ ? {
415
+ tools: toResponsesTools(options.tools, { strict: false }),
416
+ tool_choice: 'auto',
417
+ }
418
+ : {}),
419
+ ...(options.maxTokens !== undefined
420
+ ? { max_output_tokens: options.maxTokens }
421
+ : {}),
394
422
  // The Responses wire spells the effort nested; only advertised efforts
395
423
  // ever reach this branch (see copilotChatRequestBody).
396
- ...options.reasoningEffort !== undefined
424
+ ...(options.reasoningEffort !== undefined
397
425
  ? { reasoning: { effort: String(options.reasoningEffort) } }
398
- : {},
426
+ : {}),
399
427
  // [2026-08-23]-[a reasoning model continuing a tool chain must replay its
400
428
  // encrypted reasoning on the next request, and the blobs only arrive when
401
429
  // asked for; for non-reasoning models the include is a no-op]
@@ -413,15 +441,18 @@ export function copilotResponsesRequestBody(options, resolved) {
413
441
  * invalid input item.
414
442
  */
415
443
  function completedReasoningItem(item) {
416
- if (typeof item.encrypted_content !== 'string' || item.encrypted_content.length === 0)
444
+ if (typeof item.encrypted_content !== 'string' ||
445
+ item.encrypted_content.length === 0)
417
446
  return undefined;
418
447
  if (typeof item.id !== 'string' || item.id.length === 0)
419
448
  return undefined;
420
449
  return {
421
450
  type: 'reasoning',
422
451
  id: item.id,
423
- ...Array.isArray(item.summary) ? { summary: item.summary } : {},
424
- ...typeof item.status === 'string' && item.status.length > 0 ? { status: item.status } : {},
452
+ ...(Array.isArray(item.summary) ? { summary: item.summary } : {}),
453
+ ...(typeof item.status === 'string' && item.status.length > 0
454
+ ? { status: item.status }
455
+ : {}),
425
456
  encrypted_content: item.encrypted_content,
426
457
  };
427
458
  }
@@ -484,7 +515,9 @@ export class CopilotResponsesItemNormalizer {
484
515
  : `copilot-item-${String(this.adds)}`;
485
516
  this.lastKey = key;
486
517
  const item = event.item;
487
- if (item?.type === 'function_call' && typeof item.call_id === 'string' && item.call_id.length > 0) {
518
+ if (item?.type === 'function_call' &&
519
+ typeof item.call_id === 'string' &&
520
+ item.call_id.length > 0) {
488
521
  this.capturedCallIds.push(item.call_id);
489
522
  }
490
523
  return item === undefined
@@ -507,7 +540,8 @@ export class CopilotResponsesItemNormalizer {
507
540
  if (event.type === 'response.completed') {
508
541
  // Both sides present is the only replayable response; clear either way —
509
542
  // one SSE stream may carry multiple responses.
510
- if (this.capturedCallIds.length > 0 && this.capturedReasoning.length > 0) {
543
+ if (this.capturedCallIds.length > 0 &&
544
+ this.capturedReasoning.length > 0) {
511
545
  this.onCaptured?.(this.capturedCallIds, this.capturedReasoning);
512
546
  }
513
547
  this.capturedCallIds = [];
@@ -558,7 +592,9 @@ export class CopilotAdapter extends LlmAdapter {
558
592
  this.accountCatalogs.clear();
559
593
  else
560
594
  this.accountCatalogs.delete(account);
561
- if (account === undefined || this.catalogOwner === account || this.catalogOwner === undefined) {
595
+ if (account === undefined ||
596
+ this.catalogOwner === account ||
597
+ this.catalogOwner === undefined) {
562
598
  this.catalogOwner = undefined;
563
599
  this.catalog.invalidate();
564
600
  }
@@ -588,7 +624,7 @@ export class CopilotAdapter extends LlmAdapter {
588
624
  return subscriptionRetryPolicy(DEFAULT_RETRY, this.options.rateLimit ?? DEFAULT_RATE_LIMIT_WAIT, `copilot: provider "${provider}" retryPolicy`);
589
625
  }
590
626
  staticModels(provider) {
591
- return this.options.models.map(model => ({
627
+ return this.options.models.map((model) => ({
592
628
  provider,
593
629
  id: model.id,
594
630
  name: model.name ?? model.id,
@@ -601,19 +637,22 @@ export class CopilotAdapter extends LlmAdapter {
601
637
  if (pool === undefined)
602
638
  return own;
603
639
  const extra = await pool.modelsForProvider(provider);
604
- const seen = new Set(own.map(model => model.id));
640
+ const seen = new Set(own.map((model) => model.id));
605
641
  // Account pools reuse the catalog row; only configured tiers are extra.
606
- return [...own, ...extra.filter(model => !seen.has(model.id))];
642
+ return [...own, ...extra.filter((model) => !seen.has(model.id))];
607
643
  }
608
644
  /** The provider's own catalog: union of every account, or one account when named. */
609
645
  async listOwnModels(provider, account, signal) {
610
646
  if (account === undefined) {
611
- const accounts = (await this.options.tokens.list()).map(entry => entry.key);
647
+ const accounts = (await this.options.tokens.list()).map((entry) => entry.key);
612
648
  if (accounts.length === 0)
613
649
  return [];
614
- return unionAccountCatalogs(accounts, (key, accountSignal) => this.listOwnModels(provider, key, accountSignal), { timeoutMs: DISCOVERY_TIMEOUT_MS, ...signal === undefined ? {} : { signal } });
650
+ return unionAccountCatalogs(accounts, (key, accountSignal) => this.listOwnModels(provider, key, accountSignal), {
651
+ timeoutMs: DISCOVERY_TIMEOUT_MS,
652
+ ...(signal === undefined ? {} : { signal }),
653
+ });
615
654
  }
616
- if (!await this.options.tokens.hasSession(account)) {
655
+ if (!(await this.options.tokens.hasSession(account))) {
617
656
  return [];
618
657
  }
619
658
  if (!this.options.discovery)
@@ -623,13 +662,17 @@ export class CopilotAdapter extends LlmAdapter {
623
662
  // The fetcher runs only on a cache miss, and resolves the session
624
663
  // through the refresh-aware path so an expired access token renews here
625
664
  // instead of failing discovery into the static fallback.
626
- const discovered = await discoverOrRetryAuth(force => this.options.tokens.session(account, force), catalog, () => catalog.get(() => this.fetchCatalog(account, signal)));
627
- return discovered.map(model => ({
665
+ const discovered = await discoverOrRetryAuth((force) => this.options.tokens.session(account, force), catalog, () => catalog.get(() => this.fetchCatalog(account, signal)));
666
+ return discovered.map((model) => ({
628
667
  provider,
629
668
  id: model.id,
630
669
  name: model.name,
631
- ...model.description === undefined ? {} : { description: model.description },
632
- ...model.inputModalities === undefined ? {} : { inputModalities: model.inputModalities },
670
+ ...(model.description === undefined
671
+ ? {}
672
+ : { description: model.description }),
673
+ ...(model.inputModalities === undefined
674
+ ? {}
675
+ : { inputModalities: model.inputModalities }),
633
676
  }));
634
677
  }
635
678
  catch (error) {
@@ -652,11 +695,11 @@ export class CopilotAdapter extends LlmAdapter {
652
695
  async discovered(model) {
653
696
  if (!this.options.discovery)
654
697
  return undefined;
655
- const accounts = (await this.options.tokens.list()).map(entry => entry.key);
698
+ const accounts = (await this.options.tokens.list()).map((entry) => entry.key);
656
699
  return discoverAcrossAccounts(accounts, async (account) => {
657
700
  const catalog = await this.catalogFor(account);
658
701
  const models = await catalog.resolve(() => this.fetchCatalog(account));
659
- return models?.find(entry => entry.id === model);
702
+ return models?.find((entry) => entry.id === model);
660
703
  });
661
704
  }
662
705
  /**
@@ -667,10 +710,14 @@ export class CopilotAdapter extends LlmAdapter {
667
710
  * `models[].wire` now routes the request even without discovery]
668
711
  */
669
712
  configuredWireEntry(model) {
670
- const configured = this.options.models.find(entry => entry.id === model);
713
+ const configured = this.options.models.find((entry) => entry.id === model);
671
714
  return configured?.wire === undefined
672
715
  ? undefined
673
- : { id: configured.id, name: configured.name ?? configured.id, copilotWire: configured.wire };
716
+ : {
717
+ id: configured.id,
718
+ name: configured.name ?? configured.id,
719
+ copilotWire: configured.wire,
720
+ };
674
721
  }
675
722
  /**
676
723
  * The replay scope isolating one ACCOUNT × CONVERSATION × MODEL. The
@@ -779,7 +826,8 @@ export class CopilotAdapter extends LlmAdapter {
779
826
  }
780
827
  async resolveModel(provider, model) {
781
828
  const pool = this.options.pool?.();
782
- if (pool !== undefined && await pool.owns(provider, model)) {
829
+ if (pool !== undefined &&
830
+ (await pool.owns(provider, model))) {
783
831
  return pool.resolveModel(provider, model);
784
832
  }
785
833
  return this.resolveOwnModel(provider, model);
@@ -787,7 +835,7 @@ export class CopilotAdapter extends LlmAdapter {
787
835
  /** Capability resolution of the provider's own models (the pool resolves members here). */
788
836
  async resolveOwnModel(provider, model) {
789
837
  const discovered = await this.discovered(model);
790
- const configured = this.options.models.find(entry => entry.id === model);
838
+ const configured = this.options.models.find((entry) => entry.id === model);
791
839
  // Efforts come from the discovered catalog's reasoning_effort array; a
792
840
  // model that did not advertise one exposes none, so the harness rejects
793
841
  // an explicit effort before provider I/O instead of the API 400ing
@@ -799,16 +847,24 @@ export class CopilotAdapter extends LlmAdapter {
799
847
  provider,
800
848
  id: model,
801
849
  name: discovered?.name ?? configured?.name ?? model,
802
- ...discovered?.description === undefined ? {} : { description: discovered.description },
803
- inputModalities: discovered?.inputModalities ?? configured?.inputModalities ?? ['text'],
804
- context: { contextWindow: discovered?.contextWindow ?? configured?.contextWindow ?? COPILOT_CONTEXT_WINDOW },
850
+ ...(discovered?.description === undefined
851
+ ? {}
852
+ : { description: discovered.description }),
853
+ inputModalities: discovered?.inputModalities ??
854
+ configured?.inputModalities ?? ['text'],
855
+ context: {
856
+ contextWindow: discovered?.contextWindow ??
857
+ configured?.contextWindow ??
858
+ COPILOT_CONTEXT_WINDOW,
859
+ },
805
860
  defaultMaxTokens: configured?.maxTokens ?? COPILOT_DEFAULT_MAX_TOKENS,
806
- ...reasoning === undefined ? {} : { reasoning },
861
+ ...(reasoning === undefined ? {} : { reasoning }),
807
862
  };
808
863
  }
809
864
  async *stream(options) {
810
865
  const pool = this.options.pool?.();
811
- if (pool !== undefined && await pool.owns(options.provider, options.model)) {
866
+ if (pool !== undefined &&
867
+ (await pool.owns(options.provider, options.model))) {
812
868
  yield* pool.stream(options);
813
869
  return;
814
870
  }
@@ -826,7 +882,8 @@ export class CopilotAdapter extends LlmAdapter {
826
882
  // dual-protocol models reroute there once the request combines function
827
883
  // tools with a reasoning effort (gpt-5.4 400s on the chat wire then).
828
884
  // A configured `wire` outranks the catalog (see configuredWireEntry).
829
- const wire = copilotRequestWire(this.configuredWireEntry(options.model) ?? await this.discovered(options.model), options);
885
+ const wire = copilotRequestWire(this.configuredWireEntry(options.model) ??
886
+ (await this.discovered(options.model)), options);
830
887
  let session = await this.options.tokens.session(account);
831
888
  // Replay scope: account identity × conversation × model (see
832
889
  // replayScope); a Copilot-token refresh preserves the GitHub token, so
@@ -847,19 +904,23 @@ export class CopilotAdapter extends LlmAdapter {
847
904
  // Copilot has no provider-specific reset reader yet. The shared
848
905
  // mapper still honors its generic retry-after header and warns with
849
906
  // rate-limit-shaped headers/body when GitHub sends another signal.
850
- ...this.options.onWarn === undefined ? {} : { onWarn: this.options.onWarn },
907
+ ...(this.options.onWarn === undefined
908
+ ? {}
909
+ : { onWarn: this.options.onWarn }),
851
910
  });
852
911
  }
853
912
  if (response.body === null) {
854
913
  throw new LlmError('copilot API returned no response body', EMPTY_RESPONSE_CODE);
855
914
  }
856
- const pulse = () => { watchdog.pulse(); };
915
+ const pulse = () => {
916
+ watchdog.pulse();
917
+ };
857
918
  if (wire === 'responses') {
858
919
  // The normalizer doubles as the capture point for completed reasoning.
859
920
  const normalizer = new CopilotResponsesItemNormalizer((callIds, items) => {
860
921
  this.captureReasoning(scope, callIds, items);
861
922
  });
862
- yield* streamResponses(response.body, pulse, event => normalizer.push(event));
923
+ yield* streamResponses(response.body, pulse, (event) => normalizer.push(event));
863
924
  }
864
925
  else {
865
926
  yield* streamChatCompletions(response.body, pulse);
@@ -874,17 +935,17 @@ export class CopilotAdapter extends LlmAdapter {
874
935
  }
875
936
  async request(options, session, signal, wire, replayScopeKey) {
876
937
  const messages = await resolveImages(options.messages, this.options.resolveAttachments?.(), signal);
877
- const hasVision = messages.some(message => message.content.some(block => block.type === 'image'));
938
+ const hasVision = messages.some((message) => message.content.some((block) => block.type === 'image'));
878
939
  const body = wire === 'responses'
879
940
  ? copilotResponsesRequestBody(options, toResponsesInput(messages, options.system,
880
941
  // Captured completed reasoning replays ahead of its tool call.
881
- callId => this.replayFor(replayScopeKey, callId)))
942
+ (callId) => this.replayFor(replayScopeKey, callId)))
882
943
  : copilotChatRequestBody(options, toChatMessages(messages, options.system));
883
944
  return hostFetch(wire === 'responses' ? COPILOT_RESPONSES_URL : COPILOT_API_URL, {
884
945
  method: 'POST',
885
946
  headers: {
886
- 'authorization': `Bearer ${session.accessToken}`,
887
- 'accept': 'text/event-stream',
947
+ authorization: `Bearer ${session.accessToken}`,
948
+ accept: 'text/event-stream',
888
949
  'content-type': 'application/json',
889
950
  ...copilotHeaders(hasVision, await latestVsCodeVersion(this.options.fetchFn ?? hostFetch)),
890
951
  },
@@ -4,16 +4,18 @@ import { CursorAdapter } from 'dsh-subscriptions/cursor-transport';
4
4
  * blocks. DSH 0.1.7 carries them as first-class role=tool messages instead.
5
5
  */
6
6
  export function projectCursorMessages(messages) {
7
- return messages.map(message => message.role === 'tool'
7
+ return messages.map((message) => message.role === 'tool'
8
8
  ? {
9
9
  ...message,
10
10
  role: 'user',
11
- content: [{
11
+ content: [
12
+ {
12
13
  type: 'tool-result',
13
14
  toolCallId: message.toolCallId,
14
15
  isError: message.isError,
15
16
  content: message.content,
16
- }],
17
+ },
18
+ ],
17
19
  }
18
20
  : message);
19
21
  }
@@ -23,7 +25,11 @@ export function projectCursorMessages(messages) {
23
25
  * carry a `classifyCursorError` code or `CURSOR_PROTOCOL`, never these, so the
24
26
  * code is a sound discriminator for them.
25
27
  */
26
- const LOCAL_VENDOR_CODES = new Set(['UNSUPPORTED_OPTION', 'UNSUPPORTED_CONTENT', 'TOOL_LIMIT']);
28
+ const LOCAL_VENDOR_CODES = new Set([
29
+ 'UNSUPPORTED_OPTION',
30
+ 'UNSUPPORTED_CONTENT',
31
+ 'TOOL_LIMIT',
32
+ ]);
27
33
  /**
28
34
  * Plain-error messages the vendor throws as fixed literals (`vendor/cursor/
29
35
  * index.js`: `waitForResponse`, the request write, the tool-continuation write,
@@ -70,19 +76,30 @@ const LOCAL_VENDOR_PATTERNS = [
70
76
  * protocol errors) are not recognised and stay replaced.
71
77
  */
72
78
  const LOCAL_SYSTEM_ERRNO = new Set([
73
- 'ECONNREFUSED', 'ECONNRESET', 'ECONNABORTED', 'ENOTFOUND', 'EAI_AGAIN', 'ETIMEDOUT',
74
- 'ENETUNREACH', 'ENETDOWN', 'EHOSTUNREACH', 'EHOSTDOWN', 'EPIPE',
79
+ 'ECONNREFUSED',
80
+ 'ECONNRESET',
81
+ 'ECONNABORTED',
82
+ 'ENOTFOUND',
83
+ 'EAI_AGAIN',
84
+ 'ETIMEDOUT',
85
+ 'ENETUNREACH',
86
+ 'ENETDOWN',
87
+ 'EHOSTUNREACH',
88
+ 'EHOSTDOWN',
89
+ 'EPIPE',
75
90
  ]);
76
91
  function errorCode(value) {
77
- return typeof value === 'object' && value !== null ? value.code : undefined;
92
+ return typeof value === 'object' && value !== null
93
+ ? value.code
94
+ : undefined;
78
95
  }
79
96
  function isLocalSystemError(error) {
80
97
  if (!(error instanceof Error))
81
98
  return false;
82
99
  const own = errorCode(error);
83
100
  const cause = errorCode(error.cause);
84
- return (typeof own === 'string' && LOCAL_SYSTEM_ERRNO.has(own))
85
- || (typeof cause === 'string' && LOCAL_SYSTEM_ERRNO.has(cause));
101
+ return ((typeof own === 'string' && LOCAL_SYSTEM_ERRNO.has(own)) ||
102
+ (typeof cause === 'string' && LOCAL_SYSTEM_ERRNO.has(cause)));
86
103
  }
87
104
  function messageOf(error) {
88
105
  return error instanceof Error ? error.message : String(error);
@@ -123,7 +140,8 @@ class LocalFailureMessages {
123
140
  wrapAttachments(resolve) {
124
141
  return () => {
125
142
  const store = resolve();
126
- const readImage = store?.readImage;
143
+ const readImage = store
144
+ ?.readImage;
127
145
  if (typeof readImage !== 'function')
128
146
  return store;
129
147
  return Object.create(store, {
@@ -184,7 +202,8 @@ class LocalFailureMessages {
184
202
  }
185
203
  }
186
204
  function isVendorLocalMessage(message) {
187
- return LOCAL_VENDOR_MESSAGES.has(message) || LOCAL_VENDOR_PATTERNS.some(pattern => pattern.test(message));
205
+ return (LOCAL_VENDOR_MESSAGES.has(message) ||
206
+ LOCAL_VENDOR_PATTERNS.some((pattern) => pattern.test(message)));
188
207
  }
189
208
  /**
190
209
  * Cursor adapter for the DSH host.
@@ -230,7 +249,9 @@ export class CursorCompatAdapter extends CursorAdapter {
230
249
  auth: localFailures.wrap(options.auth),
231
250
  ...(options.resolveAttachments === undefined
232
251
  ? {}
233
- : { resolveAttachments: localFailures.wrapAttachments(options.resolveAttachments) }),
252
+ : {
253
+ resolveAttachments: localFailures.wrapAttachments(options.resolveAttachments),
254
+ }),
234
255
  });
235
256
  this.localFailures = localFailures;
236
257
  this.visibleModels = options.visibleModels;
@@ -238,12 +259,14 @@ export class CursorCompatAdapter extends CursorAdapter {
238
259
  // `AgentRun` factory) in its constructor but does not type it as a member.
239
260
  const vendor = this;
240
261
  const createRun = vendor.createAgentRun;
241
- vendor.createAgentRun = access => localFailures.observeRun(createRun(access));
262
+ vendor.createAgentRun = (access) => localFailures.observeRun(createRun(access));
242
263
  }
243
264
  async listModels(provider) {
244
265
  const models = await super.listModels(provider);
245
266
  const visible = this.visibleModels?.();
246
- return visible === undefined ? models : models.filter(model => visible.includes(model.id));
267
+ return visible === undefined
268
+ ? models
269
+ : models.filter((model) => visible.includes(model.id));
247
270
  }
248
271
  async *stream(options) {
249
272
  for await (const chunk of super.stream({
@@ -252,10 +275,11 @@ export class CursorCompatAdapter extends CursorAdapter {
252
275
  model: options.model === 'composer-2' ? 'composer-2.5' : options.model,
253
276
  messages: projectCursorMessages(options.messages),
254
277
  })) {
255
- if (chunk.type === 'finish' && chunk.reason.kind === 'error'
256
- && !LOCAL_VENDOR_CODES.has(chunk.reason.failure.code)
257
- && !isVendorLocalMessage(chunk.reason.failure.message)
258
- && !this.localFailures.has(chunk.reason.failure.message)) {
278
+ if (chunk.type === 'finish' &&
279
+ chunk.reason.kind === 'error' &&
280
+ !LOCAL_VENDOR_CODES.has(chunk.reason.failure.code) &&
281
+ !isVendorLocalMessage(chunk.reason.failure.message) &&
282
+ !this.localFailures.has(chunk.reason.failure.message)) {
259
283
  // The vendor uses raw headers, trailers and end-stream errors for protocol
260
284
  // checks and classification before this boundary. Keep its code and retry
261
285
  // decisions; do not reclassify the replacement text. "Safe" here means no
@@ -29,7 +29,7 @@ export declare class CursorAuth {
29
29
  constructor(credentials: CursorCredentialService, http?: typeof fetch, now?: () => number);
30
30
  private read;
31
31
  status(): Promise<CursorAuthStatus>;
32
- accessToken({ signal }?: {
32
+ accessToken({ signal, }?: {
33
33
  signal?: AbortSignal;
34
34
  }): Promise<string>;
35
35
  private refresh;