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,13 +3,13 @@
3
3
  * Codex CLI client id, and streaming against the ChatGPT backend Responses
4
4
  * endpoint.
5
5
  */
6
- import { createHash, randomUUID } from 'node:crypto';
7
- import { attributionHeaders, EMPTY_RESPONSE_CODE, errorChain, LlmAdapter, LlmError, ReasoningEffortId } from '@deepseek-ai/dsh-llm';
6
+ import { createHash } from 'node:crypto';
7
+ import { attributionHeaders, EMPTY_RESPONSE_CODE, errorChain, LlmAdapter, LlmError, ReasoningEffortId, } from '@deepseek-ai/dsh-llm';
8
8
  import { decodeJwtPayload } from '../auth/jwt.js';
9
9
  import { resolveImages } from '../translate/resolved.js';
10
- import { streamResponses, toResponsesInput, toResponsesTools } from '../translate/responses.js';
10
+ import { streamResponses, toResponsesInput, toResponsesTools, } from '../translate/responses.js';
11
11
  import { deterministicSessionId, effortDisplayName, httpLlmError, idleWatchdog, mapFetchFailure, mergeReasoning, ModelCatalogCache, discoverAcrossAccounts, discoverOrRetryAuth, isDiscoveryAborted, isDiscoveryAuthFailure, isMissingOrInvalidCredential, parseProviderJson, oauthEndpointError, OAuthEndpointError, } from './common.js';
12
- import { AccountTokenManager, DISCOVERY_TIMEOUT_MS, unionAccountCatalogs } from './accounts.js';
12
+ import { AccountTokenManager, DISCOVERY_TIMEOUT_MS, unionAccountCatalogs, } from './accounts.js';
13
13
  import { hostFetch } from '../http.js';
14
14
  import { DEFAULT_RATE_LIMIT_WAIT, DEFAULT_RETRY, jsonBody, resetFromFields, subscriptionRetryPolicy, } from './rate-limit.js';
15
15
  export const CODEX_CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';
@@ -27,7 +27,12 @@ export const CODEX_PREEMPT_MS = 5 * 60_000;
27
27
  * carries `usage_limit_reached` with the seconds left on the window — the case
28
28
  * that used to classify as a terminal quota and never be retried at all.
29
29
  */
30
- const CODEX_RESET_FIELDS = ['resets_in_seconds', 'reset_after_seconds', 'resets_at', 'reset_at'];
30
+ const CODEX_RESET_FIELDS = [
31
+ 'resets_in_seconds',
32
+ 'reset_after_seconds',
33
+ 'resets_at',
34
+ 'reset_at',
35
+ ];
31
36
  /**
32
37
  * Reads the reset instant of the Codex window that rejected a request.
33
38
  *
@@ -40,8 +45,8 @@ const CODEX_RESET_FIELDS = ['resets_in_seconds', 'reset_after_seconds', 'resets_
40
45
  */
41
46
  export const codexRateLimitReset = (_response, body, now) => resetFromFields(jsonBody(body), CODEX_RESET_FIELDS, now);
42
47
  /** Default instruction when the request carries no system prompt. */
43
- const DEFAULT_CODEX_INSTRUCTIONS = 'You are Codex, a coding agent based on GPT-5. '
44
- + 'Help the user with their software engineering tasks.';
48
+ const DEFAULT_CODEX_INSTRUCTIONS = 'You are Codex, a coding agent based on GPT-5. ' +
49
+ 'Help the user with their software engineering tasks.';
45
50
  /** Refresh-grant rejections that mean the login is gone for good. */
46
51
  const PERMANENT_REFRESH_CODES = new Set([
47
52
  'refresh_token_expired',
@@ -122,13 +127,16 @@ export function codexProfileClaims(idToken) {
122
127
  ? auth.chatgpt_plan_type
123
128
  : undefined;
124
129
  return {
125
- ...typeof email === 'string' && email.length > 0 ? { emailAddress: email } : {},
126
- ...typeof plan === 'string' && plan.length > 0 ? { planType: plan } : {},
130
+ ...(typeof email === 'string' && email.length > 0
131
+ ? { emailAddress: email }
132
+ : {}),
133
+ ...(typeof plan === 'string' && plan.length > 0 ? { planType: plan } : {}),
127
134
  };
128
135
  }
129
136
  /** Build a session from a token response; expires_in wins, JWT exp is the fallback. */
130
137
  function codexSession(tokens, fallback) {
131
- if (typeof tokens.access_token !== 'string' || tokens.access_token.length === 0) {
138
+ if (typeof tokens.access_token !== 'string' ||
139
+ tokens.access_token.length === 0) {
132
140
  throw new Error('codex token endpoint returned no access token');
133
141
  }
134
142
  const refreshToken = tokens.refresh_token ?? fallback?.refreshToken;
@@ -149,8 +157,12 @@ function codexSession(tokens, fallback) {
149
157
  // one keeps the claims the stored session already had.
150
158
  const idToken = tokens.id_token ?? fallback?.idToken;
151
159
  const claims = {
152
- ...fallback?.emailAddress === undefined ? {} : { emailAddress: fallback.emailAddress },
153
- ...fallback?.planType === undefined ? {} : { planType: fallback.planType },
160
+ ...(fallback?.emailAddress === undefined
161
+ ? {}
162
+ : { emailAddress: fallback.emailAddress }),
163
+ ...(fallback?.planType === undefined
164
+ ? {}
165
+ : { planType: fallback.planType }),
154
166
  ...codexProfileClaims(tokens.id_token),
155
167
  };
156
168
  return {
@@ -160,7 +172,7 @@ function codexSession(tokens, fallback) {
160
172
  accountId: tokens.id_token === undefined && fallback !== undefined
161
173
  ? fallback.accountId
162
174
  : accountIdOf(tokens.id_token),
163
- ...idToken === undefined ? {} : { idToken },
175
+ ...(idToken === undefined ? {} : { idToken }),
164
176
  ...claims,
165
177
  };
166
178
  }
@@ -185,7 +197,7 @@ export async function exchangeCodexCode(code, verifier, redirectUri) {
185
197
  });
186
198
  if (!response.ok)
187
199
  throw await oauthEndpointError(response, 'codex');
188
- return codexSession(await parseProviderJson(response, 'codex token exchange'));
200
+ return codexSession((await parseProviderJson(response, 'codex token exchange')));
189
201
  }
190
202
  /**
191
203
  * Refresh a codex session (JSON grant — unlike the code exchange).
@@ -204,7 +216,7 @@ export async function refreshCodex(session) {
204
216
  });
205
217
  if (!response.ok)
206
218
  throw await oauthEndpointError(response, 'codex');
207
- return codexSession(await parseProviderJson(response, 'codex token refresh'), session);
219
+ return codexSession((await parseProviderJson(response, 'codex token refresh')), session);
208
220
  }
209
221
  /**
210
222
  * Whether a codex refresh failure means the login is permanently gone.
@@ -212,9 +224,9 @@ export async function refreshCodex(session) {
212
224
  * @returns true when re-login is the only fix.
213
225
  */
214
226
  export function isCodexPermanentRefreshError(error) {
215
- return error instanceof OAuthEndpointError
216
- && error.oauthCode !== undefined
217
- && PERMANENT_REFRESH_CODES.has(error.oauthCode);
227
+ return (error instanceof OAuthEndpointError &&
228
+ error.oauthCode !== undefined &&
229
+ PERMANENT_REFRESH_CODES.has(error.oauthCode));
218
230
  }
219
231
  export const CODEX_USAGE_URL = 'https://chatgpt.com/backend-api/wham/usage';
220
232
  export const CODEX_RESET_CREDITS_URL = 'https://chatgpt.com/backend-api/wham/rate-limit-reset-credits';
@@ -235,22 +247,29 @@ export async function fetchCodexResetCredits(session, fetchFn = hostFetch, signa
235
247
  accept: 'application/json',
236
248
  ...attributionHeaders(),
237
249
  },
238
- ...signal === undefined ? {} : { signal },
250
+ ...(signal === undefined ? {} : { signal }),
239
251
  });
240
252
  if (!response.ok)
241
253
  throw await oauthEndpointError(response, 'codex reset credits');
242
- const payload = await parseProviderJson(response, 'codex reset credits');
254
+ const payload = (await parseProviderJson(response, 'codex reset credits'));
243
255
  if (!Array.isArray(payload?.credits))
244
256
  throw new Error('codex reset credits: unexpected response (missing credits array)');
245
257
  return payload.credits.flatMap((value) => {
246
258
  if (typeof value !== 'object' || value === null)
247
259
  return [];
248
260
  const credit = value;
249
- if (credit.status !== 'available' || credit.reset_type !== 'codex_rate_limits')
261
+ if (credit.status !== 'available' ||
262
+ credit.reset_type !== 'codex_rate_limits')
250
263
  return [];
251
264
  const grantedAt = parseResetDate(credit.granted_at);
252
265
  const expiresAt = parseResetDate(credit.expires_at);
253
- return [{ ...(typeof credit.id === 'string' ? { id: credit.id } : {}), ...(grantedAt === undefined ? {} : { grantedAt }), ...(expiresAt === undefined ? {} : { expiresAt }) }];
266
+ return [
267
+ {
268
+ ...(typeof credit.id === 'string' ? { id: credit.id } : {}),
269
+ ...(grantedAt === undefined ? {} : { grantedAt }),
270
+ ...(expiresAt === undefined ? {} : { expiresAt }),
271
+ },
272
+ ];
254
273
  });
255
274
  }
256
275
  /** Submit one explicit reset; ambiguous or unconfirmed responses throw without retry. */
@@ -260,16 +279,18 @@ export async function consumeCodexResetCredit(session, creditId, requestId, fetc
260
279
  headers: {
261
280
  authorization: 'Bearer ' + session.accessToken,
262
281
  'chatgpt-account-id': session.accountId,
263
- originator: 'Codex Desktop', 'openai-beta': 'codex-1',
264
- 'content-type': 'application/json', accept: 'application/json',
282
+ originator: 'Codex Desktop',
283
+ 'openai-beta': 'codex-1',
284
+ 'content-type': 'application/json',
285
+ accept: 'application/json',
265
286
  ...attributionHeaders(),
266
287
  },
267
288
  body: JSON.stringify({ redeem_request_id: requestId, credit_id: creditId }),
268
- ...signal === undefined ? {} : { signal },
289
+ ...(signal === undefined ? {} : { signal }),
269
290
  });
270
291
  if (!response.ok)
271
292
  throw await oauthEndpointError(response, 'codex consume reset');
272
- const payload = await parseProviderJson(response, 'codex consume reset');
293
+ const payload = (await parseProviderJson(response, 'codex consume reset'));
273
294
  if (payload?.code !== 'reset')
274
295
  throw new Error('Reset was not confirmed. Verify the account in Codex before retrying.');
275
296
  }
@@ -301,24 +322,30 @@ function codexUsageWindow(value, fallbackKind) {
301
322
  if (typeof value !== 'object' || value === null)
302
323
  return undefined;
303
324
  const window = value;
304
- if (typeof window.used_percent !== 'number' || !Number.isFinite(window.used_percent))
325
+ if (typeof window.used_percent !== 'number' ||
326
+ !Number.isFinite(window.used_percent))
305
327
  return undefined;
306
328
  let resetsAt;
307
329
  if (typeof window.reset_at === 'number' && window.reset_at > 0) {
308
330
  resetsAt = window.reset_at * 1000;
309
331
  }
310
- else if (typeof window.reset_after_seconds === 'number' && window.reset_after_seconds > 0) {
332
+ else if (typeof window.reset_after_seconds === 'number' &&
333
+ window.reset_after_seconds > 0) {
311
334
  resetsAt = Date.now() + window.reset_after_seconds * 1000;
312
335
  }
313
336
  return {
314
337
  kind: codexWindowKind(window, fallbackKind),
315
- ...typeof window.limit_window_seconds === 'number' && Number.isFinite(window.limit_window_seconds)
316
- && window.limit_window_seconds > 0 ? {
317
- // wham reports the duration and reset of this same quota window.
318
- windowDurationMs: window.limit_window_seconds * 1000, fixedWindow: true,
319
- } : {},
338
+ ...(typeof window.limit_window_seconds === 'number' &&
339
+ Number.isFinite(window.limit_window_seconds) &&
340
+ window.limit_window_seconds > 0
341
+ ? {
342
+ // wham reports the duration and reset of this same quota window.
343
+ windowDurationMs: window.limit_window_seconds * 1000,
344
+ fixedWindow: true,
345
+ }
346
+ : {}),
320
347
  usedPercent: window.used_percent,
321
- ...resetsAt === undefined ? {} : { resetsAt },
348
+ ...(resetsAt === undefined ? {} : { resetsAt }),
322
349
  };
323
350
  }
324
351
  /**
@@ -337,17 +364,17 @@ function codexUsageWindow(value, fallbackKind) {
337
364
  export async function fetchCodexUsage(session, fetchFn = hostFetch, signal) {
338
365
  const response = await fetchFn(CODEX_USAGE_URL, {
339
366
  headers: {
340
- 'authorization': `Bearer ${session.accessToken}`,
367
+ authorization: `Bearer ${session.accessToken}`,
341
368
  'chatgpt-account-id': session.accountId,
342
- 'originator': 'codex_cli_rs',
343
- 'accept': 'application/json',
369
+ originator: 'codex_cli_rs',
370
+ accept: 'application/json',
344
371
  ...attributionHeaders(),
345
372
  },
346
- ...signal === undefined ? {} : { signal },
373
+ ...(signal === undefined ? {} : { signal }),
347
374
  });
348
375
  if (!response.ok)
349
376
  throw await oauthEndpointError(response, 'codex usage');
350
- const payload = await parseProviderJson(response, 'codex usage');
377
+ const payload = (await parseProviderJson(response, 'codex usage'));
351
378
  const windows = [];
352
379
  const primary = codexUsageWindow(payload.rate_limit?.primary_window, 'session');
353
380
  const secondary = codexUsageWindow(payload.rate_limit?.secondary_window, 'weekly');
@@ -358,9 +385,9 @@ export async function fetchCodexUsage(session, fetchFn = hostFetch, signal) {
358
385
  return {
359
386
  supported: true,
360
387
  windows,
361
- ...typeof payload.plan_type === 'string' && payload.plan_type.length > 0
388
+ ...(typeof payload.plan_type === 'string' && payload.plan_type.length > 0
362
389
  ? { plan: payload.plan_type }
363
- : {},
390
+ : {}),
364
391
  };
365
392
  }
366
393
  export const CODEX_MODELS_URL = 'https://chatgpt.com/backend-api/codex/models';
@@ -384,8 +411,7 @@ export function codexClientVersion(value = CODEX_CLIENT_VERSION) {
384
411
  * wire value, or the legacy `additional_speed_tiers` "fast" entry.
385
412
  */
386
413
  function supportsFastTier(entry) {
387
- return (entry.service_tiers ?? []).some(tier => tier.id === CODEX_FAST_SERVICE_TIER)
388
- || (entry.additional_speed_tiers ?? []).includes(CODEX_FAST_SPEED_TIER);
414
+ return ((entry.service_tiers ?? []).some((tier) => tier.id === CODEX_FAST_SERVICE_TIER) || (entry.additional_speed_tiers ?? []).includes(CODEX_FAST_SPEED_TIER));
389
415
  }
390
416
  /**
391
417
  * Fetch the live codex model catalog with the session's auth headers.
@@ -400,17 +426,17 @@ export async function fetchCodexModels(session, fetchFn = hostFetch, signal, cli
400
426
  const url = `${CODEX_MODELS_URL}?client_version=${encodeURIComponent(version)}`;
401
427
  const response = await fetchFn(url, {
402
428
  headers: {
403
- 'authorization': `Bearer ${session.accessToken}`,
429
+ authorization: `Bearer ${session.accessToken}`,
404
430
  'chatgpt-account-id': session.accountId,
405
- 'originator': 'codex_cli_rs',
406
- 'accept': 'application/json',
431
+ originator: 'codex_cli_rs',
432
+ accept: 'application/json',
407
433
  ...attributionHeaders(),
408
434
  },
409
- ...signal === undefined ? {} : { signal },
435
+ ...(signal === undefined ? {} : { signal }),
410
436
  });
411
437
  if (!response.ok)
412
438
  throw await oauthEndpointError(response, 'codex models');
413
- const payload = await parseProviderJson(response, 'codex models');
439
+ const payload = (await parseProviderJson(response, 'codex models'));
414
440
  if (!Array.isArray(payload.models))
415
441
  throw new Error('codex models endpoint returned no models array');
416
442
  const discovered = [];
@@ -424,15 +450,20 @@ export async function fetchCodexModels(session, fetchFn = hostFetch, signal, cli
424
450
  // "ultra" is a Codex-client mode (max reasoning + automatic sub-agent
425
451
  // delegation), not a wire value: the Responses API rejects it with HTTP 400.
426
452
  const efforts = (entry.supported_reasoning_levels ?? [])
427
- .filter(level => typeof level.effort === 'string' && level.effort.length > 0 && level.effort !== 'ultra')
428
- .map(level => ({
453
+ .filter((level) => typeof level.effort === 'string' &&
454
+ level.effort.length > 0 &&
455
+ level.effort !== 'ultra')
456
+ .map((level) => ({
429
457
  id: ReasoningEffortId(level.effort),
430
458
  name: effortDisplayName(level.effort),
431
- ...level.description === undefined ? {} : { description: level.description },
459
+ ...(level.description === undefined
460
+ ? {}
461
+ : { description: level.description }),
432
462
  }));
433
- const defaultEffort = typeof entry.default_reasoning_level === 'string'
434
- && entry.default_reasoning_level.length > 0
435
- && efforts.some(effort => effort.id === ReasoningEffortId(entry.default_reasoning_level))
463
+ const defaultEffort = typeof entry.default_reasoning_level === 'string' &&
464
+ entry.default_reasoning_level.length > 0 &&
465
+ efforts.some((effort) => effort.id ===
466
+ ReasoningEffortId(entry.default_reasoning_level))
436
467
  ? ReasoningEffortId(entry.default_reasoning_level)
437
468
  : undefined;
438
469
  const model = {
@@ -440,24 +471,33 @@ export async function fetchCodexModels(session, fetchFn = hostFetch, signal, cli
440
471
  name: typeof entry.display_name === 'string' && entry.display_name.length > 0
441
472
  ? entry.display_name
442
473
  : entry.slug,
443
- ...typeof entry.description === 'string' && entry.description.length > 0
474
+ ...(typeof entry.description === 'string' && entry.description.length > 0
444
475
  ? { description: entry.description }
445
- : {},
446
- ...typeof entry.context_window === 'number' && entry.context_window > 0
476
+ : {}),
477
+ ...(typeof entry.context_window === 'number' && entry.context_window > 0
447
478
  ? { contextWindow: entry.context_window }
448
- : {},
449
- ...Number.isSafeInteger(entry.max_context_window) && entry.max_context_window > 0
479
+ : {}),
480
+ ...(Number.isSafeInteger(entry.max_context_window) &&
481
+ entry.max_context_window > 0
450
482
  ? { maxContextWindow: entry.max_context_window }
451
- : {},
452
- ...typeof entry.priority === 'number' ? { priority: entry.priority } : {},
453
- ...efforts.length > 0
454
- ? { reasoning: { efforts, ...defaultEffort === undefined ? {} : { defaultEffort } } }
455
- : {},
456
- ...supportsFastTier(entry) ? { fastTier: true } : {},
483
+ : {}),
484
+ ...(typeof entry.priority === 'number'
485
+ ? { priority: entry.priority }
486
+ : {}),
487
+ ...(efforts.length > 0
488
+ ? {
489
+ reasoning: {
490
+ efforts,
491
+ ...(defaultEffort === undefined ? {} : { defaultEffort }),
492
+ },
493
+ }
494
+ : {}),
495
+ ...(supportsFastTier(entry) ? { fastTier: true } : {}),
457
496
  };
458
497
  discovered.push(model);
459
498
  }
460
- discovered.sort((a, b) => (a.priority ?? Number.MAX_SAFE_INTEGER) - (b.priority ?? Number.MAX_SAFE_INTEGER));
499
+ discovered.sort((a, b) => (a.priority ?? Number.MAX_SAFE_INTEGER) -
500
+ (b.priority ?? Number.MAX_SAFE_INTEGER));
461
501
  // An empty catalog from a 200 response means the backend gated us out (e.g.
462
502
  // client_version too old): surface it as a discovery failure so the adapter
463
503
  // falls back to the static catalog instead of vanishing from the picker.
@@ -478,7 +518,8 @@ const CODEX_CALL_ID_PREFIX = 'call_';
478
518
  function normalizeCodexCallIds(input) {
479
519
  const mapping = new Map();
480
520
  const used = new Set();
481
- const callId = (item) => (item.type === 'function_call' || item.type === 'function_call_output') && typeof item.call_id === 'string'
521
+ const callId = (item) => (item.type === 'function_call' || item.type === 'function_call_output') &&
522
+ typeof item.call_id === 'string'
482
523
  ? item.call_id
483
524
  : undefined;
484
525
  for (const item of input) {
@@ -531,20 +572,28 @@ export function reconcileResponsesToolCalls(input) {
531
572
  for (const item of input) {
532
573
  if (item.type === 'function_call' && typeof item.call_id === 'string')
533
574
  calls.add(item.call_id);
534
- else if (item.type === 'function_call_output' && typeof item.call_id === 'string')
575
+ else if (item.type === 'function_call_output' &&
576
+ typeof item.call_id === 'string')
535
577
  outputs.add(item.call_id);
536
578
  }
537
- const missing = new Set([...calls].filter(callId => !outputs.has(callId)));
538
- if (missing.size === 0 && [...outputs].every(callId => calls.has(callId)))
579
+ const missing = new Set([...calls].filter((callId) => !outputs.has(callId)));
580
+ if (missing.size === 0 && [...outputs].every((callId) => calls.has(callId)))
539
581
  return input;
540
582
  const balanced = [];
541
583
  for (const item of input) {
542
- if (item.type === 'function_call_output' && (typeof item.call_id !== 'string' || !calls.has(item.call_id))) {
584
+ if (item.type === 'function_call_output' &&
585
+ (typeof item.call_id !== 'string' || !calls.has(item.call_id))) {
543
586
  continue;
544
587
  }
545
588
  balanced.push(item);
546
- if (item.type === 'function_call' && typeof item.call_id === 'string' && missing.delete(item.call_id)) {
547
- balanced.push({ type: 'function_call_output', call_id: item.call_id, output: CODEX_UNKNOWN_TOOL_OUTCOME });
589
+ if (item.type === 'function_call' &&
590
+ typeof item.call_id === 'string' &&
591
+ missing.delete(item.call_id)) {
592
+ balanced.push({
593
+ type: 'function_call_output',
594
+ call_id: item.call_id,
595
+ output: CODEX_UNKNOWN_TOOL_OUTCOME,
596
+ });
548
597
  }
549
598
  }
550
599
  return balanced;
@@ -573,17 +622,28 @@ export function codexRequestBody(options, resolved, fast) {
573
622
  // Omit tool controls for tool-less requests, matching the other adapters.
574
623
  // This is request-shape consistency, not a claim that Codex rejects the
575
624
  // controls when no tools are supplied.
576
- ...options.tools !== undefined && options.tools.length > 0
577
- ? { tools: toResponsesTools(options.tools, { strict: false }), tool_choice: 'auto', parallel_tool_calls: true }
578
- : {},
579
- ...options.reasoningEffort !== undefined
580
- ? { reasoning: { effort: codexWireEffort(String(options.reasoningEffort)), summary: 'auto' } }
581
- : {},
625
+ ...(options.tools !== undefined && options.tools.length > 0
626
+ ? {
627
+ tools: toResponsesTools(options.tools, { strict: false }),
628
+ tool_choice: 'auto',
629
+ parallel_tool_calls: true,
630
+ }
631
+ : {}),
632
+ ...(options.reasoningEffort !== undefined
633
+ ? {
634
+ reasoning: {
635
+ effort: codexWireEffort(String(options.reasoningEffort)),
636
+ summary: 'auto',
637
+ },
638
+ }
639
+ : {}),
582
640
  store: false,
583
641
  stream: true,
584
642
  include: ['reasoning.encrypted_content'],
585
- ...options.sessionId !== undefined ? { prompt_cache_key: String(options.sessionId) } : {},
586
- ...fast ? { service_tier: CODEX_FAST_SERVICE_TIER } : {},
643
+ ...(options.sessionId !== undefined
644
+ ? { prompt_cache_key: String(options.sessionId) }
645
+ : {}),
646
+ ...(fast ? { service_tier: CODEX_FAST_SERVICE_TIER } : {}),
587
647
  };
588
648
  }
589
649
  /** Codex wire adapter: one instance serves the `codex` provider route. */
@@ -602,7 +662,8 @@ export class CodexAdapter extends LlmAdapter {
602
662
  }
603
663
  /** Discovery fetcher: resolves the session through the refresh-aware path. */
604
664
  async fetchCatalog(account, signal) {
605
- const version = this.options.clientVersion ?? await this.options.resolveClientVersion?.();
665
+ const version = this.options.clientVersion ??
666
+ (await this.options.resolveClientVersion?.());
606
667
  signal?.throwIfAborted();
607
668
  return fetchCodexModels(await this.options.tokens.session(account), this.options.fetchFn, signal, version);
608
669
  }
@@ -619,7 +680,9 @@ export class CodexAdapter extends LlmAdapter {
619
680
  this.accountCatalogs.get(account)?.invalidate();
620
681
  this.accountCatalogs.delete(account);
621
682
  }
622
- if (account === undefined || this.catalogOwner === account || this.catalogOwner === undefined) {
683
+ if (account === undefined ||
684
+ this.catalogOwner === account ||
685
+ this.catalogOwner === undefined) {
623
686
  this.catalogOwner = undefined;
624
687
  this.catalog.invalidate();
625
688
  }
@@ -644,13 +707,15 @@ export class CodexAdapter extends LlmAdapter {
644
707
  }
645
708
  /** Picker rows for discovered entries (the wire id doubles as the route id). */
646
709
  listed(provider, models) {
647
- return models.map(model => ({
710
+ return models.map((model) => ({
648
711
  provider,
649
712
  id: model.id,
650
713
  name: model.name,
651
- ...model.description === undefined ? {} : { description: model.description },
714
+ ...(model.description === undefined
715
+ ? {}
716
+ : { description: model.description }),
652
717
  inputModalities: CODEX_MODALITIES,
653
- ...model.priority === undefined ? {} : { priority: model.priority },
718
+ ...(model.priority === undefined ? {} : { priority: model.priority }),
654
719
  }));
655
720
  }
656
721
  /**
@@ -659,7 +724,8 @@ export class CodexAdapter extends LlmAdapter {
659
724
  * listed a model minutes ago is still a better answer than "no models".
660
725
  */
661
726
  async lastKnownOwnModels(provider, account) {
662
- if (!this.options.discovery || !await this.options.tokens.hasSession(account))
727
+ if (!this.options.discovery ||
728
+ !(await this.options.tokens.hasSession(account)))
663
729
  return undefined;
664
730
  const known = (await this.catalogFor(account)).lastKnown();
665
731
  return known === undefined ? undefined : this.listed(provider, known);
@@ -671,7 +737,7 @@ export class CodexAdapter extends LlmAdapter {
671
737
  return subscriptionRetryPolicy(DEFAULT_RETRY, this.options.rateLimit ?? DEFAULT_RATE_LIMIT_WAIT, `codex: provider "${provider}" retryPolicy`);
672
738
  }
673
739
  staticModels(provider) {
674
- return this.options.models.map(model => ({
740
+ return this.options.models.map((model) => ({
675
741
  provider,
676
742
  id: model.id,
677
743
  name: model.name ?? model.id,
@@ -684,19 +750,22 @@ export class CodexAdapter extends LlmAdapter {
684
750
  if (pool === undefined)
685
751
  return own;
686
752
  const extra = await pool.modelsForProvider(provider);
687
- const seen = new Set(own.map(model => model.id));
753
+ const seen = new Set(own.map((model) => model.id));
688
754
  // Account pools reuse the catalog row; only configured tiers are extra.
689
- return [...own, ...extra.filter(model => !seen.has(model.id))];
755
+ return [...own, ...extra.filter((model) => !seen.has(model.id))];
690
756
  }
691
757
  /** The provider's own catalog: union of every account, or one account when named. */
692
758
  async listOwnModels(provider, account, signal) {
693
759
  if (account === undefined) {
694
- const accounts = (await this.options.tokens.list()).map(entry => entry.key);
760
+ const accounts = (await this.options.tokens.list()).map((entry) => entry.key);
695
761
  if (accounts.length === 0)
696
762
  return [];
697
- return unionAccountCatalogs(accounts, (key, accountSignal) => this.listOwnModels(provider, key, accountSignal), { timeoutMs: this.options.discoveryTimeoutMs ?? DISCOVERY_TIMEOUT_MS, ...signal === undefined ? {} : { signal } });
763
+ return unionAccountCatalogs(accounts, (key, accountSignal) => this.listOwnModels(provider, key, accountSignal), {
764
+ timeoutMs: this.options.discoveryTimeoutMs ?? DISCOVERY_TIMEOUT_MS,
765
+ ...(signal === undefined ? {} : { signal }),
766
+ });
698
767
  }
699
- if (!await this.options.tokens.hasSession(account)) {
768
+ if (!(await this.options.tokens.hasSession(account))) {
700
769
  return [];
701
770
  }
702
771
  if (!this.options.discovery)
@@ -706,7 +775,7 @@ export class CodexAdapter extends LlmAdapter {
706
775
  // The fetcher runs only on a cache miss, and resolves the session
707
776
  // through the refresh-aware path so an expired access token renews here
708
777
  // instead of failing discovery into the static fallback.
709
- const discovered = await discoverOrRetryAuth(force => this.options.tokens.session(account, force), catalog, () => catalog.get(() => this.fetchCatalog(account, signal)));
778
+ const discovered = await discoverOrRetryAuth((force) => this.options.tokens.session(account, force), catalog, () => catalog.get(() => this.fetchCatalog(account, signal)));
710
779
  return this.listed(provider, discovered);
711
780
  }
712
781
  catch (error) {
@@ -722,7 +791,9 @@ export class CodexAdapter extends LlmAdapter {
722
791
  // the login is revoked server-side. Listing the built-in catalog here
723
792
  // would route requests to a dead account and show phantom models.
724
793
  if (isDiscoveryAuthFailure(error)) {
725
- const who = (await this.options.tokens.peek(account))?.emailAddress ?? account ?? 'default account';
794
+ const who = (await this.options.tokens.peek(account))?.emailAddress ??
795
+ account ??
796
+ 'default account';
726
797
  this.options.onWarn?.(`codex rejected the login of ${who}; hiding its models until it logs in again (${errorChain(error)})`);
727
798
  return [];
728
799
  }
@@ -749,12 +820,12 @@ export class CodexAdapter extends LlmAdapter {
749
820
  if (!this.options.discovery)
750
821
  return undefined;
751
822
  const accounts = account === undefined
752
- ? (await this.options.tokens.list()).map(entry => entry.key)
823
+ ? (await this.options.tokens.list()).map((entry) => entry.key)
753
824
  : [account];
754
825
  return discoverAcrossAccounts(accounts, async (account) => {
755
826
  const catalog = await this.catalogFor(account);
756
827
  const models = await catalog.resolve(() => this.fetchCatalog(account));
757
- return models?.find(entry => entry.id === model);
828
+ return models?.find((entry) => entry.id === model);
758
829
  });
759
830
  }
760
831
  /** Whether the discovered catalog advertises a fast tier for this model. */
@@ -768,7 +839,7 @@ export class CodexAdapter extends LlmAdapter {
768
839
  // Not logged in → no fast models, so the Speed toggle hides after logout
769
840
  // (mirrors the listModels guard above). Union every account: a fast-capable
770
841
  // model only the non-default lists (e.g. gpt-5.6-sol) must still show Speed.
771
- const accounts = (await this.options.tokens.list()).map(entry => entry.key);
842
+ const accounts = (await this.options.tokens.list()).map((entry) => entry.key);
772
843
  if (accounts.length === 0)
773
844
  return [];
774
845
  const seen = new Set();
@@ -792,7 +863,8 @@ export class CodexAdapter extends LlmAdapter {
792
863
  }
793
864
  async resolveModel(provider, model) {
794
865
  const pool = this.options.pool?.();
795
- if (pool !== undefined && await pool.owns(provider, model)) {
866
+ if (pool !== undefined &&
867
+ (await pool.owns(provider, model))) {
796
868
  return pool.resolveModel(provider, model);
797
869
  }
798
870
  return this.resolveOwnModel(provider, model);
@@ -803,17 +875,22 @@ export class CodexAdapter extends LlmAdapter {
803
875
  // the static entry wins over the built-in defaults. A configured default
804
876
  // effort merges over both.
805
877
  const discovered = await this.discovered(model, account);
806
- const configured = this.options.models.find(entry => entry.id === model);
878
+ const configured = this.options.models.find((entry) => entry.id === model);
807
879
  // `extendable` only while falling back to the built-in list: that one is
808
880
  // known to trail the backend, so a configured level it omits still has to
809
881
  // be selectable. A discovered catalog is the truth about what the model
810
882
  // accepts, and a stale override must not be forced onto every request.
811
- const reasoning = mergeReasoning(this.options.defaultEffortOf?.(model), discovered?.reasoning ?? { efforts: CODEX_EFFORTS, defaultEffort: CODEX_DEFAULT_EFFORT }, { extendable: discovered?.reasoning === undefined });
883
+ const reasoning = mergeReasoning(this.options.defaultEffortOf?.(model), discovered?.reasoning ?? {
884
+ efforts: CODEX_EFFORTS,
885
+ defaultEffort: CODEX_DEFAULT_EFFORT,
886
+ }, { extendable: discovered?.reasoning === undefined });
812
887
  return {
813
888
  provider,
814
889
  id: model,
815
890
  name: discovered?.name ?? configured?.name ?? model,
816
- ...discovered?.description === undefined ? {} : { description: discovered.description },
891
+ ...(discovered?.description === undefined
892
+ ? {}
893
+ : { description: discovered.description }),
817
894
  inputModalities: configured?.inputModalities ?? CODEX_MODALITIES,
818
895
  context: { contextWindow: await this.contextWindowFor(model, account) },
819
896
  defaultMaxTokens: configured?.maxTokens ?? CODEX_DEFAULT_MAX_TOKENS,
@@ -823,8 +900,10 @@ export class CodexAdapter extends LlmAdapter {
823
900
  /** Account-specific bounds; absent maximum conservatively keeps the advertised default. */
824
901
  async contextLimits(model, account) {
825
902
  const discovered = await this.discovered(model, account);
826
- const configured = this.options.models.find(entry => entry.id === model);
827
- const fallback = discovered?.contextWindow ?? configured?.contextWindow ?? CODEX_CONTEXT_WINDOW;
903
+ const configured = this.options.models.find((entry) => entry.id === model);
904
+ const fallback = discovered?.contextWindow ??
905
+ configured?.contextWindow ??
906
+ CODEX_CONTEXT_WINDOW;
828
907
  return { default: fallback, max: discovered?.maxContextWindow ?? fallback };
829
908
  }
830
909
  async contextWindowFor(model, account) {
@@ -833,7 +912,8 @@ export class CodexAdapter extends LlmAdapter {
833
912
  }
834
913
  async *stream(options) {
835
914
  const pool = this.options.pool?.();
836
- if (pool !== undefined && await pool.owns(options.provider, options.model)) {
915
+ if (pool !== undefined &&
916
+ (await pool.owns(options.provider, options.model))) {
837
917
  yield* pool.stream(options);
838
918
  return;
839
919
  }
@@ -856,13 +936,17 @@ export class CodexAdapter extends LlmAdapter {
856
936
  if (!response.ok) {
857
937
  throw await httpLlmError(response, 'codex API', {
858
938
  rateLimitReset: codexRateLimitReset,
859
- ...this.options.onWarn === undefined ? {} : { onWarn: this.options.onWarn },
939
+ ...(this.options.onWarn === undefined
940
+ ? {}
941
+ : { onWarn: this.options.onWarn }),
860
942
  });
861
943
  }
862
944
  if (response.body === null) {
863
945
  throw new LlmError('codex API returned no response body', EMPTY_RESPONSE_CODE);
864
946
  }
865
- yield* streamResponses(response.body, () => { watchdog.pulse(); });
947
+ yield* streamResponses(response.body, () => {
948
+ watchdog.pulse();
949
+ });
866
950
  }
867
951
  catch (error) {
868
952
  throw mapFetchFailure('codex API', error, watchdog, options.signal);
@@ -873,17 +957,17 @@ export class CodexAdapter extends LlmAdapter {
873
957
  }
874
958
  async request(options, session, signal) {
875
959
  const messages = await resolveImages(options.messages, this.options.resolveAttachments?.(), signal);
876
- const fast = this.options.speedFor !== undefined
877
- && await this.options.speedFor(options.sessionId, options.model);
960
+ const fast = this.options.speedFor !== undefined &&
961
+ (await this.options.speedFor(options.sessionId, options.model));
878
962
  const body = codexRequestBody(options, toResponsesInput(messages, options.system), fast);
879
963
  return hostFetch(CODEX_API_URL, {
880
964
  method: 'POST',
881
965
  headers: {
882
- 'authorization': `Bearer ${session.accessToken}`,
966
+ authorization: `Bearer ${session.accessToken}`,
883
967
  'chatgpt-account-id': session.accountId,
884
- 'originator': 'codex_cli_rs',
968
+ originator: 'codex_cli_rs',
885
969
  'session-id': deterministicSessionId(options.sessionId),
886
- 'accept': 'text/event-stream',
970
+ accept: 'text/event-stream',
887
971
  'content-type': 'application/json',
888
972
  ...attributionHeaders(),
889
973
  },