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
@@ -3,12 +3,12 @@
3
3
  * auth.x.ai with the Grok CLI client id, and streaming against the xAI
4
4
  * Responses-style endpoint.
5
5
  */
6
- import { attributionHeaders, EMPTY_RESPONSE_CODE, errorChain, LlmAdapter, LlmError, ReasoningEffortId } from '@deepseek-ai/dsh-llm';
6
+ import { attributionHeaders, EMPTY_RESPONSE_CODE, errorChain, LlmAdapter, LlmError, ReasoningEffortId, } from '@deepseek-ai/dsh-llm';
7
7
  import { decodeJwtPayload } from '../auth/jwt.js';
8
8
  import { resolveImages } from '../translate/resolved.js';
9
- import { streamResponses, toResponsesInput, toResponsesTools } from '../translate/responses.js';
9
+ import { streamResponses, toResponsesInput, toResponsesTools, } from '../translate/responses.js';
10
10
  import { httpLlmError, idleWatchdog, mapFetchFailure, mergeReasoning, ModelCatalogCache, discoverAcrossAccounts, discoverOrRetryAuth, isDiscoveryAborted, isMissingOrInvalidCredential, parseProviderJson, oauthEndpointError, OAuthEndpointError, } from './common.js';
11
- import { AccountTokenManager, DISCOVERY_TIMEOUT_MS, unionAccountCatalogs } from './accounts.js';
11
+ import { AccountTokenManager, DISCOVERY_TIMEOUT_MS, unionAccountCatalogs, } from './accounts.js';
12
12
  import { hostFetch } from '../http.js';
13
13
  import { DEFAULT_RATE_LIMIT_WAIT, DEFAULT_RETRY, jsonBody, resetFromFields, subscriptionRetryPolicy, } from './rate-limit.js';
14
14
  export const GROK_CLIENT_ID = 'b1a00492-073a-47ea-816f-4c329264a828';
@@ -21,7 +21,12 @@ const GROK_DEFAULT_MAX_TOKENS = 32_000;
21
21
  /** Refresh when the access token has less than this much life left. */
22
22
  export const GROK_PREEMPT_MS = 2 * 60_000;
23
23
  /** Body fields xAI uses to name a delay or reset. */
24
- const GROK_RESET_FIELDS = ['retry_after', 'retry_after_seconds', 'resets_at', 'reset_at'];
24
+ const GROK_RESET_FIELDS = [
25
+ 'retry_after',
26
+ 'retry_after_seconds',
27
+ 'resets_at',
28
+ 'reset_at',
29
+ ];
25
30
  /**
26
31
  * Reads the reset instant of the xAI window that rejected a request.
27
32
  *
@@ -47,8 +52,8 @@ function assertXaiEndpoint(url, field) {
47
52
  catch {
48
53
  throw new Error(`grok OIDC discovery returned an invalid ${field}`);
49
54
  }
50
- if (parsed.protocol !== 'https:'
51
- || (parsed.hostname !== 'x.ai' && !parsed.hostname.endsWith('.x.ai'))) {
55
+ if (parsed.protocol !== 'https:' ||
56
+ (parsed.hostname !== 'x.ai' && !parsed.hostname.endsWith('.x.ai'))) {
52
57
  throw new Error(`grok OIDC discovery returned a non-x.ai ${field}`);
53
58
  }
54
59
  return url;
@@ -64,8 +69,9 @@ export async function grokDiscovery() {
64
69
  const response = await hostFetch(GROK_DISCOVERY_URL);
65
70
  if (!response.ok)
66
71
  throw await oauthEndpointError(response, 'grok OIDC discovery');
67
- const document = await parseProviderJson(response, 'grok OIDC discovery');
68
- if (typeof document.authorization_endpoint !== 'string' || typeof document.token_endpoint !== 'string') {
72
+ const document = (await parseProviderJson(response, 'grok OIDC discovery'));
73
+ if (typeof document.authorization_endpoint !== 'string' ||
74
+ typeof document.token_endpoint !== 'string') {
69
75
  throw new Error('grok OIDC discovery document is missing endpoints');
70
76
  }
71
77
  discoveryCache = {
@@ -141,12 +147,16 @@ export function grokTierName(accessToken) {
141
147
  /** Pick a display account from an id token's claims. */
142
148
  function grokAccount(idToken) {
143
149
  const payload = idToken === undefined ? undefined : decodeJwtPayload(idToken);
144
- const claim = payload?.email ?? payload?.preferred_username ?? payload?.name ?? payload?.sub;
150
+ const claim = payload?.email ??
151
+ payload?.preferred_username ??
152
+ payload?.name ??
153
+ payload?.sub;
145
154
  return typeof claim === 'string' && claim.length > 0 ? claim : undefined;
146
155
  }
147
156
  /** Build a session from a token response. */
148
157
  function grokSession(tokens, tokenEndpoint, fallbackRefreshToken) {
149
- if (typeof tokens.access_token !== 'string' || tokens.access_token.length === 0) {
158
+ if (typeof tokens.access_token !== 'string' ||
159
+ tokens.access_token.length === 0) {
150
160
  throw new Error('grok token endpoint returned no access token');
151
161
  }
152
162
  const refreshToken = tokens.refresh_token ?? fallbackRefreshToken;
@@ -161,8 +171,8 @@ function grokSession(tokens, tokenEndpoint, fallbackRefreshToken) {
161
171
  refreshToken,
162
172
  expiresAt: Date.now() + tokens.expires_in * 1000,
163
173
  tokenEndpoint,
164
- ...typeof tokens.scope === 'string' ? { scopes: tokens.scope } : {},
165
- ...account === undefined ? {} : { account },
174
+ ...(typeof tokens.scope === 'string' ? { scopes: tokens.scope } : {}),
175
+ ...(account === undefined ? {} : { account }),
166
176
  };
167
177
  }
168
178
  /**
@@ -191,12 +201,12 @@ export async function exchangeGrokCode(code, verifier, redirectUri, challenge) {
191
201
  }).toString(),
192
202
  });
193
203
  if (response.status === 403) {
194
- throw new OAuthEndpointError('grok token endpoint refused the exchange (HTTP 403): your X plan does not include '
195
- + 'the API OAuth entitlement; an X Premium or xAI subscription with API access is required', 403);
204
+ throw new OAuthEndpointError('grok token endpoint refused the exchange (HTTP 403): your X plan does not include ' +
205
+ 'the API OAuth entitlement; an X Premium or xAI subscription with API access is required', 403);
196
206
  }
197
207
  if (!response.ok)
198
208
  throw await oauthEndpointError(response, 'grok');
199
- return grokSession(await parseProviderJson(response, 'grok token exchange'), discovery.tokenEndpoint);
209
+ return grokSession((await parseProviderJson(response, 'grok token exchange')), discovery.tokenEndpoint);
200
210
  }
201
211
  /**
202
212
  * Refresh a grok session (form-encoded grant).
@@ -215,11 +225,13 @@ export async function refreshGrok(session) {
215
225
  });
216
226
  if (!response.ok)
217
227
  throw await oauthEndpointError(response, 'grok');
218
- const next = grokSession(await parseProviderJson(response, 'grok token refresh'), session.tokenEndpoint, session.refreshToken);
228
+ const next = grokSession((await parseProviderJson(response, 'grok token refresh')), session.tokenEndpoint, session.refreshToken);
219
229
  return {
220
230
  ...next,
221
- ...session.account === undefined ? {} : { account: session.account },
222
- ...next.scopes === undefined && session.scopes !== undefined ? { scopes: session.scopes } : {},
231
+ ...(session.account === undefined ? {} : { account: session.account }),
232
+ ...(next.scopes === undefined && session.scopes !== undefined
233
+ ? { scopes: session.scopes }
234
+ : {}),
223
235
  };
224
236
  }
225
237
  /**
@@ -228,7 +240,7 @@ export async function refreshGrok(session) {
228
240
  * @returns true when re-login is the only fix.
229
241
  */
230
242
  export function isGrokPermanentRefreshError(error) {
231
- return error instanceof OAuthEndpointError && error.oauthCode === 'invalid_grant';
243
+ return (error instanceof OAuthEndpointError && error.oauthCode === 'invalid_grant');
232
244
  }
233
245
  /**
234
246
  * The Grok Build CLI chat proxy's billing endpoint (the source of the CLI's
@@ -256,50 +268,61 @@ function grokResetsAt(value) {
256
268
  export async function fetchGrokUsage(session, fetchFn = hostFetch, signal) {
257
269
  const response = await fetchFn(GROK_BILLING_URL, {
258
270
  headers: {
259
- 'authorization': `Bearer ${session.accessToken}`,
271
+ authorization: `Bearer ${session.accessToken}`,
260
272
  // The proxy only honors bearer tokens presented as the Grok CLI.
261
273
  'x-xai-token-auth': 'xai-grok-cli',
262
- 'accept': 'application/json',
274
+ accept: 'application/json',
263
275
  ...attributionHeaders(),
264
276
  },
265
- ...signal === undefined ? {} : { signal },
277
+ ...(signal === undefined ? {} : { signal }),
266
278
  });
267
279
  if (!response.ok)
268
280
  throw await oauthEndpointError(response, 'grok billing');
269
- const payload = await parseProviderJson(response, 'grok billing');
270
- const config = typeof payload.config === 'object' && payload.config !== null ? payload.config : {};
281
+ const payload = (await parseProviderJson(response, 'grok billing'));
282
+ const config = typeof payload.config === 'object' && payload.config !== null
283
+ ? payload.config
284
+ : {};
271
285
  const windows = [];
272
- if (config.currentPeriod || (typeof config.creditUsagePercent === 'number' && Number.isFinite(config.creditUsagePercent))) {
286
+ if (config.currentPeriod ||
287
+ (typeof config.creditUsagePercent === 'number' &&
288
+ Number.isFinite(config.creditUsagePercent))) {
273
289
  const kind = config.currentPeriod?.type === 'USAGE_PERIOD_TYPE_WEEKLY'
274
290
  ? 'weekly'
275
291
  : 'other';
276
292
  const resetsAt = grokResetsAt(config.currentPeriod?.end);
277
- const usedPercent = typeof config.creditUsagePercent === 'number' && Number.isFinite(config.creditUsagePercent)
293
+ const usedPercent = typeof config.creditUsagePercent === 'number' &&
294
+ Number.isFinite(config.creditUsagePercent)
278
295
  ? config.creditUsagePercent
279
296
  : 0;
280
297
  const startsAt = grokResetsAt(config.currentPeriod?.start);
281
- windows.push({ kind, usedPercent, ...resetsAt === undefined ? {} : { resetsAt },
282
- ...startsAt === undefined ? {} : { startsAt } });
298
+ windows.push({
299
+ kind,
300
+ usedPercent,
301
+ ...(resetsAt === undefined ? {} : { resetsAt }),
302
+ ...(startsAt === undefined ? {} : { startsAt }),
303
+ });
283
304
  }
284
- else if (typeof config.monthlyLimit?.val === 'number' && config.monthlyLimit.val > 0) {
305
+ else if (typeof config.monthlyLimit?.val === 'number' &&
306
+ config.monthlyLimit.val > 0) {
285
307
  const used = typeof config.used?.val === 'number' ? config.used.val : 0;
286
308
  const resetsAt = grokResetsAt(config.billingPeriodEnd);
287
309
  windows.push({
288
310
  kind: 'other',
289
311
  usedPercent: (used / config.monthlyLimit.val) * 100,
290
- ...resetsAt === undefined ? {} : { resetsAt },
312
+ ...(resetsAt === undefined ? {} : { resetsAt }),
291
313
  });
292
314
  }
293
315
  // The upstream billing response rarely carries `subscriptionTier` (the CLI
294
316
  // enriches it locally from its settings cache), so the access token's
295
317
  // `tier` claim is the working fallback.
296
- const plan = typeof payload.subscriptionTier === 'string' && payload.subscriptionTier.length > 0
318
+ const plan = typeof payload.subscriptionTier === 'string' &&
319
+ payload.subscriptionTier.length > 0
297
320
  ? payload.subscriptionTier
298
321
  : grokTierName(session.accessToken);
299
322
  return {
300
323
  supported: true,
301
324
  windows,
302
- ...plan === undefined ? {} : { plan },
325
+ ...(plan === undefined ? {} : { plan }),
303
326
  };
304
327
  }
305
328
  export const GROK_MODELS_URL = 'https://api.x.ai/v1/models';
@@ -323,24 +346,26 @@ function grokCliReasoning(entry) {
323
346
  if (entry.supports_reasoning_effort !== true)
324
347
  return undefined;
325
348
  const efforts = (entry.reasoning_efforts ?? [])
326
- .filter(level => typeof level.value === 'string' && level.value.length > 0)
327
- .map(level => ({
349
+ .filter((level) => typeof level.value === 'string' && level.value.length > 0)
350
+ .map((level) => ({
328
351
  id: ReasoningEffortId(level.value),
329
- name: typeof level.label === 'string' && level.label.length > 0 ? level.label : level.value,
330
- ...typeof level.description === 'string' && level.description.length > 0
352
+ name: typeof level.label === 'string' && level.label.length > 0
353
+ ? level.label
354
+ : level.value,
355
+ ...(typeof level.description === 'string' && level.description.length > 0
331
356
  ? { description: level.description }
332
- : {},
357
+ : {}),
333
358
  }));
334
359
  if (efforts.length === 0)
335
360
  return undefined;
336
361
  // The per-entry `default` flags are unreliable (the live catalog marks
337
362
  // several levels default at once), so the top-level `reasoning_effort`
338
363
  // field is the trusted default.
339
- const defaultEffort = typeof entry.reasoning_effort === 'string'
340
- && efforts.some(effort => effort.id === ReasoningEffortId(entry.reasoning_effort))
364
+ const defaultEffort = typeof entry.reasoning_effort === 'string' &&
365
+ efforts.some((effort) => effort.id === ReasoningEffortId(entry.reasoning_effort))
341
366
  ? ReasoningEffortId(entry.reasoning_effort)
342
367
  : undefined;
343
- return { efforts, ...defaultEffort === undefined ? {} : { defaultEffort } };
368
+ return { efforts, ...(defaultEffort === undefined ? {} : { defaultEffort }) };
344
369
  }
345
370
  /**
346
371
  * Fetch the CLI catalog and index its per-model metadata by model id.
@@ -352,17 +377,17 @@ function grokCliReasoning(entry) {
352
377
  export async function fetchGrokCliCatalog(session, fetchFn = hostFetch, signal) {
353
378
  const response = await fetchFn(GROK_CLI_MODELS_URL, {
354
379
  headers: {
355
- 'authorization': `Bearer ${session.accessToken}`,
380
+ authorization: `Bearer ${session.accessToken}`,
356
381
  // The proxy only honors bearer tokens presented as the Grok CLI.
357
382
  'x-xai-token-auth': 'xai-grok-cli',
358
- 'accept': 'application/json',
383
+ accept: 'application/json',
359
384
  ...attributionHeaders(),
360
385
  },
361
- ...signal === undefined ? {} : { signal },
386
+ ...(signal === undefined ? {} : { signal }),
362
387
  });
363
388
  if (!response.ok)
364
389
  throw await oauthEndpointError(response, 'grok CLI catalog');
365
- const payload = await parseProviderJson(response, 'grok CLI catalog');
390
+ const payload = (await parseProviderJson(response, 'grok CLI catalog'));
366
391
  if (!Array.isArray(payload.data))
367
392
  throw new Error('grok CLI catalog returned no data array');
368
393
  const catalog = new Map();
@@ -371,14 +396,16 @@ export async function fetchGrokCliCatalog(session, fetchFn = hostFetch, signal)
371
396
  continue;
372
397
  const reasoning = grokCliReasoning(entry);
373
398
  catalog.set(entry.id, {
374
- ...typeof entry.name === 'string' && entry.name.length > 0 ? { name: entry.name } : {},
375
- ...typeof entry.description === 'string' && entry.description.length > 0
399
+ ...(typeof entry.name === 'string' && entry.name.length > 0
400
+ ? { name: entry.name }
401
+ : {}),
402
+ ...(typeof entry.description === 'string' && entry.description.length > 0
376
403
  ? { description: entry.description }
377
- : {},
378
- ...typeof entry.context_window === 'number' && entry.context_window > 0
404
+ : {}),
405
+ ...(typeof entry.context_window === 'number' && entry.context_window > 0
379
406
  ? { contextWindow: entry.context_window }
380
- : {},
381
- ...reasoning === undefined ? {} : { reasoning },
407
+ : {}),
408
+ ...(reasoning === undefined ? {} : { reasoning }),
382
409
  });
383
410
  }
384
411
  return catalog;
@@ -402,8 +429,12 @@ function grokPriorMeta(prior) {
402
429
  return {};
403
430
  return {
404
431
  ...(prior.name.length > 0 ? { name: prior.name } : {}),
405
- ...(prior.description === undefined ? {} : { description: prior.description }),
406
- ...(prior.contextWindow === undefined ? {} : { contextWindow: prior.contextWindow }),
432
+ ...(prior.description === undefined
433
+ ? {}
434
+ : { description: prior.description }),
435
+ ...(prior.contextWindow === undefined
436
+ ? {}
437
+ : { contextWindow: prior.contextWindow }),
407
438
  ...(prior.reasoning === undefined ? {} : { reasoning: prior.reasoning }),
408
439
  };
409
440
  }
@@ -426,15 +457,15 @@ function grokPriorMeta(prior) {
426
457
  export async function fetchGrokModels(session, fetchFn = hostFetch, onWarn, previous, signal) {
427
458
  const previousById = previous === undefined || previous.length === 0
428
459
  ? undefined
429
- : new Map(previous.map(model => [model.id, model]));
460
+ : new Map(previous.map((model) => [model.id, model]));
430
461
  const [response, cliCatalog] = await Promise.all([
431
462
  fetchFn(GROK_MODELS_URL, {
432
463
  headers: {
433
- 'authorization': `Bearer ${session.accessToken}`,
434
- 'accept': 'application/json',
464
+ authorization: `Bearer ${session.accessToken}`,
465
+ accept: 'application/json',
435
466
  ...attributionHeaders(),
436
467
  },
437
- ...signal === undefined ? {} : { signal },
468
+ ...(signal === undefined ? {} : { signal }),
438
469
  }),
439
470
  fetchGrokCliCatalog(session, fetchFn, signal).catch((error) => {
440
471
  if (isDiscoveryAborted(error, signal))
@@ -447,13 +478,15 @@ export async function fetchGrokModels(session, fetchFn = hostFetch, onWarn, prev
447
478
  ]);
448
479
  if (!response.ok)
449
480
  throw await oauthEndpointError(response, 'grok models');
450
- const payload = await parseProviderJson(response, 'grok models');
481
+ const payload = (await parseProviderJson(response, 'grok models'));
451
482
  if (!Array.isArray(payload.data))
452
483
  throw new Error('grok models endpoint returned no data array');
453
484
  const seen = new Set();
454
485
  const discovered = [];
455
486
  for (const entry of payload.data) {
456
- if (typeof entry.id !== 'string' || entry.id.length === 0 || seen.has(entry.id))
487
+ if (typeof entry.id !== 'string' ||
488
+ entry.id.length === 0 ||
489
+ seen.has(entry.id))
457
490
  continue;
458
491
  if (!isChatModel(entry.id))
459
492
  continue;
@@ -482,23 +515,33 @@ export async function fetchGrokModels(session, fetchFn = hostFetch, onWarn, prev
482
515
  export function grokRequestBody(options, resolved) {
483
516
  return {
484
517
  model: options.model,
485
- ...resolved.instructions === undefined ? {} : { instructions: resolved.instructions },
518
+ ...(resolved.instructions === undefined
519
+ ? {}
520
+ : { instructions: resolved.instructions }),
486
521
  input: resolved.input,
487
- ...options.tools !== undefined && options.tools.length > 0
488
- ? { tools: toResponsesTools(options.tools), tool_choice: 'auto', parallel_tool_calls: true }
489
- : {},
490
- ...options.maxTokens !== undefined ? { max_output_tokens: options.maxTokens } : {},
522
+ ...(options.tools !== undefined && options.tools.length > 0
523
+ ? {
524
+ tools: toResponsesTools(options.tools),
525
+ tool_choice: 'auto',
526
+ parallel_tool_calls: true,
527
+ }
528
+ : {}),
529
+ ...(options.maxTokens !== undefined
530
+ ? { max_output_tokens: options.maxTokens }
531
+ : {}),
491
532
  // The harness only passes an effort the resolved model advertised (the
492
533
  // CLI catalog's), so this never reaches a model that rejects it.
493
- ...options.reasoningEffort !== undefined
534
+ ...(options.reasoningEffort !== undefined
494
535
  ? { reasoning: { effort: String(options.reasoningEffort) } }
495
- : {},
536
+ : {}),
496
537
  // Cache-affinity hint (mirrors codex): xAI caches prompts per server,
497
538
  // and `prompt_cache_key` is the Responses-API signal that routes repeat
498
539
  // requests back to the cache-holding shard — without it every turn's
499
540
  // cache hit is shard-routing luck. The session id is the stable key
500
541
  // xAI's own docs recommend.
501
- ...options.sessionId !== undefined ? { prompt_cache_key: String(options.sessionId) } : {},
542
+ ...(options.sessionId !== undefined
543
+ ? { prompt_cache_key: String(options.sessionId) }
544
+ : {}),
502
545
  store: false,
503
546
  stream: true,
504
547
  };
@@ -518,7 +561,8 @@ export class GrokAdapter extends LlmAdapter {
518
561
  }
519
562
  /** Discovery fetcher: resolves the session through the refresh-aware path. */
520
563
  async fetchCatalog(account, signal) {
521
- const lastKnown = account === undefined || account === await this.options.tokens.defaultAccount()
564
+ const lastKnown = account === undefined ||
565
+ account === (await this.options.tokens.defaultAccount())
522
566
  ? this.catalog.lastKnown()
523
567
  : this.accountCatalogs.get(account)?.lastKnown();
524
568
  return fetchGrokModels(await this.options.tokens.session(account), this.options.fetchFn, this.options.onWarn, lastKnown, signal);
@@ -529,7 +573,9 @@ export class GrokAdapter extends LlmAdapter {
529
573
  this.accountCatalogs.clear();
530
574
  else
531
575
  this.accountCatalogs.delete(account);
532
- if (account === undefined || this.catalogOwner === account || this.catalogOwner === undefined) {
576
+ if (account === undefined ||
577
+ this.catalogOwner === account ||
578
+ this.catalogOwner === undefined) {
533
579
  this.catalogOwner = undefined;
534
580
  this.catalog.invalidate();
535
581
  }
@@ -553,11 +599,13 @@ export class GrokAdapter extends LlmAdapter {
553
599
  return cache;
554
600
  }
555
601
  listed(provider, discovered) {
556
- return discovered.map(model => ({
602
+ return discovered.map((model) => ({
557
603
  provider,
558
604
  id: model.id,
559
605
  name: model.name,
560
- ...model.description === undefined ? {} : { description: model.description },
606
+ ...(model.description === undefined
607
+ ? {}
608
+ : { description: model.description }),
561
609
  inputModalities: grokModalities(model.id),
562
610
  }));
563
611
  }
@@ -568,7 +616,7 @@ export class GrokAdapter extends LlmAdapter {
568
616
  return subscriptionRetryPolicy(DEFAULT_RETRY, this.options.rateLimit ?? DEFAULT_RATE_LIMIT_WAIT, `grok: provider "${provider}" retryPolicy`);
569
617
  }
570
618
  staticModels(provider) {
571
- return this.options.models.map(model => ({
619
+ return this.options.models.map((model) => ({
572
620
  provider,
573
621
  id: model.id,
574
622
  name: model.name ?? model.id,
@@ -581,19 +629,22 @@ export class GrokAdapter extends LlmAdapter {
581
629
  if (pool === undefined)
582
630
  return own;
583
631
  const extra = await pool.modelsForProvider(provider);
584
- const seen = new Set(own.map(model => model.id));
632
+ const seen = new Set(own.map((model) => model.id));
585
633
  // Account pools reuse the catalog row; only configured tiers are extra.
586
- return [...own, ...extra.filter(model => !seen.has(model.id))];
634
+ return [...own, ...extra.filter((model) => !seen.has(model.id))];
587
635
  }
588
636
  /** The provider's own catalog: union of every account, or one account when named. */
589
637
  async listOwnModels(provider, account, signal) {
590
638
  if (account === undefined) {
591
- const accounts = (await this.options.tokens.list()).map(entry => entry.key);
639
+ const accounts = (await this.options.tokens.list()).map((entry) => entry.key);
592
640
  if (accounts.length === 0)
593
641
  return [];
594
- return unionAccountCatalogs(accounts, (key, accountSignal) => this.listOwnModels(provider, key, accountSignal), { timeoutMs: DISCOVERY_TIMEOUT_MS, ...signal === undefined ? {} : { signal } });
642
+ return unionAccountCatalogs(accounts, (key, accountSignal) => this.listOwnModels(provider, key, accountSignal), {
643
+ timeoutMs: DISCOVERY_TIMEOUT_MS,
644
+ ...(signal === undefined ? {} : { signal }),
645
+ });
595
646
  }
596
- if (!await this.options.tokens.hasSession(account)) {
647
+ if (!(await this.options.tokens.hasSession(account))) {
597
648
  return [];
598
649
  }
599
650
  if (!this.options.discovery)
@@ -603,7 +654,7 @@ export class GrokAdapter extends LlmAdapter {
603
654
  // The fetcher runs only on a cache miss, and resolves the session
604
655
  // through the refresh-aware path so an expired access token renews here
605
656
  // instead of failing discovery into the static fallback.
606
- return this.listed(provider, await discoverOrRetryAuth(force => this.options.tokens.session(account, force), catalog, () => catalog.get(() => this.fetchCatalog(account, signal))));
657
+ return this.listed(provider, await discoverOrRetryAuth((force) => this.options.tokens.session(account, force), catalog, () => catalog.get(() => this.fetchCatalog(account, signal))));
607
658
  }
608
659
  catch (error) {
609
660
  if (isDiscoveryAborted(error, signal))
@@ -627,16 +678,17 @@ export class GrokAdapter extends LlmAdapter {
627
678
  async discovered(model) {
628
679
  if (!this.options.discovery)
629
680
  return undefined;
630
- const accounts = (await this.options.tokens.list()).map(entry => entry.key);
681
+ const accounts = (await this.options.tokens.list()).map((entry) => entry.key);
631
682
  return discoverAcrossAccounts(accounts, async (account) => {
632
683
  const catalog = await this.catalogFor(account);
633
684
  const models = await catalog.resolve(() => this.fetchCatalog(account));
634
- return models?.find(entry => entry.id === model);
685
+ return models?.find((entry) => entry.id === model);
635
686
  });
636
687
  }
637
688
  async resolveModel(provider, model) {
638
689
  const pool = this.options.pool?.();
639
- if (pool !== undefined && await pool.owns(provider, model)) {
690
+ if (pool !== undefined &&
691
+ (await pool.owns(provider, model))) {
640
692
  return pool.resolveModel(provider, model);
641
693
  }
642
694
  return this.resolveOwnModel(provider, model);
@@ -644,7 +696,7 @@ export class GrokAdapter extends LlmAdapter {
644
696
  /** Capability resolution of the provider's own models (the pool resolves members here). */
645
697
  async resolveOwnModel(provider, model) {
646
698
  const discovered = await this.discovered(model);
647
- const configured = this.options.models.find(entry => entry.id === model);
699
+ const configured = this.options.models.find((entry) => entry.id === model);
648
700
  // Efforts come from the discovered CLI catalog; models it does not
649
701
  // cover expose none, so the harness rejects explicit efforts before
650
702
  // provider I/O instead of the API 400ing. A configured default effort
@@ -655,16 +707,23 @@ export class GrokAdapter extends LlmAdapter {
655
707
  provider,
656
708
  id: model,
657
709
  name: discovered?.name ?? configured?.name ?? model,
658
- ...discovered?.description === undefined ? {} : { description: discovered.description },
710
+ ...(discovered?.description === undefined
711
+ ? {}
712
+ : { description: discovered.description }),
659
713
  inputModalities: configured?.inputModalities ?? grokModalities(model),
660
- context: { contextWindow: discovered?.contextWindow ?? configured?.contextWindow ?? GROK_CONTEXT_WINDOW },
714
+ context: {
715
+ contextWindow: discovered?.contextWindow ??
716
+ configured?.contextWindow ??
717
+ GROK_CONTEXT_WINDOW,
718
+ },
661
719
  defaultMaxTokens: configured?.maxTokens ?? GROK_DEFAULT_MAX_TOKENS,
662
- ...reasoning === undefined ? {} : { reasoning },
720
+ ...(reasoning === undefined ? {} : { reasoning }),
663
721
  };
664
722
  }
665
723
  async *stream(options) {
666
724
  const pool = this.options.pool?.();
667
- if (pool !== undefined && await pool.owns(options.provider, options.model)) {
725
+ if (pool !== undefined &&
726
+ (await pool.owns(options.provider, options.model))) {
668
727
  yield* pool.stream(options);
669
728
  return;
670
729
  }
@@ -687,13 +746,17 @@ export class GrokAdapter extends LlmAdapter {
687
746
  if (!response.ok) {
688
747
  throw await httpLlmError(response, 'grok API', {
689
748
  rateLimitReset: grokRateLimitReset,
690
- ...this.options.onWarn === undefined ? {} : { onWarn: this.options.onWarn },
749
+ ...(this.options.onWarn === undefined
750
+ ? {}
751
+ : { onWarn: this.options.onWarn }),
691
752
  });
692
753
  }
693
754
  if (response.body === null) {
694
755
  throw new LlmError('grok API returned no response body', EMPTY_RESPONSE_CODE);
695
756
  }
696
- yield* streamResponses(response.body, () => { watchdog.pulse(); });
757
+ yield* streamResponses(response.body, () => {
758
+ watchdog.pulse();
759
+ });
697
760
  }
698
761
  catch (error) {
699
762
  throw mapFetchFailure('grok API', error, watchdog, options.signal);
@@ -708,8 +771,8 @@ export class GrokAdapter extends LlmAdapter {
708
771
  return hostFetch(GROK_API_URL, {
709
772
  method: 'POST',
710
773
  headers: {
711
- 'authorization': `Bearer ${session.accessToken}`,
712
- 'accept': 'text/event-stream',
774
+ authorization: `Bearer ${session.accessToken}`,
775
+ accept: 'text/event-stream',
713
776
  'content-type': 'application/json',
714
777
  ...attributionHeaders(),
715
778
  },
@@ -1,7 +1,7 @@
1
1
  /** Account scheduling shared by image generation and editing, independent of chat catalogs/quota. */
2
2
  import { LlmError } from '@deepseek-ai/dsh-llm';
3
3
  import { httpLlmError } from './common.js';
4
- import { AUTH_COOLDOWN_MS, DEFAULT_QUOTA_COOLDOWN_MS, TRANSIENT_COOLDOWN_MS, PoolHealthRegistry, memberKey } from './pool-health.js';
4
+ import { AUTH_COOLDOWN_MS, DEFAULT_QUOTA_COOLDOWN_MS, TRANSIENT_COOLDOWN_MS, PoolHealthRegistry, memberKey, } from './pool-health.js';
5
5
  export class ImageAccountPool {
6
6
  options;
7
7
  // Deliberately separate from chat health: image quotas/entitlements need not match chat.
@@ -24,10 +24,13 @@ export class ImageAccountPool {
24
24
  throw new LlmError('image_generate: no image account is logged in', 'MISSING_CREDENTIAL');
25
25
  }
26
26
  const pooling = this.options.enabled !== false;
27
- const members = (pooling ? accounts : accounts.slice(0, 1)).map(entry => entry.key);
28
- const sticky = pooling && owner !== undefined ? this.sticky.get(owner)?.get(provider) : undefined;
27
+ const members = (pooling ? accounts : accounts.slice(0, 1)).map((entry) => entry.key);
28
+ const sticky = pooling && owner !== undefined
29
+ ? this.sticky.get(owner)?.get(provider)
30
+ : undefined;
29
31
  const ordered = sticky !== undefined && members.includes(sticky)
30
- ? [sticky, ...members.filter(key => key !== sticky)] : members;
32
+ ? [sticky, ...members.filter((key) => key !== sticky)]
33
+ : members;
31
34
  let lastError;
32
35
  for (const account of ordered) {
33
36
  signal.throwIfAborted();
@@ -60,30 +63,45 @@ export class ImageAccountPool {
60
63
  }
61
64
  // Images can be produced despite timeouts/server failures. Switch only on
62
65
  // explicit auth/quota/entitlement rejection, never ambiguous transport/5xx.
63
- failure = await httpLlmError(response, 'image_generate', { rateLimitReset });
66
+ failure = await httpLlmError(response, 'image_generate', {
67
+ rateLimitReset,
68
+ });
64
69
  if (![401, 402, 403, 404, 429].includes(response.status))
65
70
  throw failure;
66
71
  }
67
72
  catch (error) {
68
73
  signal.throwIfAborted();
69
- if (!(error instanceof LlmError) || !['AUTH', 'INVALID_CREDENTIAL', 'MISSING_CREDENTIAL', 'RATE_LIMIT', 'HTTP_402', 'HTTP_404'].includes(error.code))
74
+ if (!(error instanceof LlmError) ||
75
+ ![
76
+ 'AUTH',
77
+ 'INVALID_CREDENTIAL',
78
+ 'MISSING_CREDENTIAL',
79
+ 'RATE_LIMIT',
80
+ 'HTTP_402',
81
+ 'HTTP_404',
82
+ ].includes(error.code))
70
83
  throw error;
71
84
  failure = error;
72
85
  }
73
86
  if (!pooling)
74
87
  throw failure;
75
- const delay = failure.failure.providerRetryAfterMs ?? (failure.code === 'RATE_LIMIT'
76
- ? DEFAULT_QUOTA_COOLDOWN_MS
77
- : ['AUTH', 'INVALID_CREDENTIAL', 'MISSING_CREDENTIAL'].includes(failure.code) ? AUTH_COOLDOWN_MS : TRANSIENT_COOLDOWN_MS);
88
+ const delay = failure.failure.providerRetryAfterMs ??
89
+ (failure.code === 'RATE_LIMIT'
90
+ ? DEFAULT_QUOTA_COOLDOWN_MS
91
+ : ['AUTH', 'INVALID_CREDENTIAL', 'MISSING_CREDENTIAL'].includes(failure.code)
92
+ ? AUTH_COOLDOWN_MS
93
+ : TRANSIENT_COOLDOWN_MS);
78
94
  this.health.markUnavailable(key, delay, failure.code);
79
95
  this.options.onWarn?.(`image pool ${provider}: account ${members.indexOf(account) + 1}/${members.length} rejected (${failure.code}); checking remaining accounts`);
80
96
  lastError = failure;
81
97
  }
82
98
  if (lastError !== undefined)
83
99
  throw lastError;
84
- const recovery = this.health.earliestRecovery(new Set(members.map(account => memberKey(provider, account, 'images'))));
100
+ const recovery = this.health.earliestRecovery(new Set(members.map((account) => memberKey(provider, account, 'images'))));
85
101
  throw new LlmError(`image_generate: all ${provider} image accounts are cooling down`, 'RATE_LIMIT', {
86
- ...recovery === undefined ? {} : { providerRetryAfterMs: Math.max(0, recovery - Date.now()) },
102
+ ...(recovery === undefined
103
+ ? {}
104
+ : { providerRetryAfterMs: Math.max(0, recovery - Date.now()) }),
87
105
  });
88
106
  }
89
107
  }