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
package/lib/http.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Host-managed network routing for subscription requests. */
2
- import { getDefaultAutoSelectFamilyAttemptTimeout, setDefaultAutoSelectFamilyAttemptTimeout } from 'node:net';
2
+ import { getDefaultAutoSelectFamilyAttemptTimeout, setDefaultAutoSelectFamilyAttemptTimeout, } from 'node:net';
3
3
  /** Keep a sufficient per-address connection budget on high-latency networks. */
4
4
  export const MIN_CONNECT_ATTEMPT_TIMEOUT_MS = 1500;
5
5
  export function ensureConnectAttemptTimeout(minMs = MIN_CONNECT_ATTEMPT_TIMEOUT_MS) {
package/lib/index.d.ts CHANGED
@@ -13,13 +13,13 @@ import type { ModelEntry } from './providers/common.js';
13
13
  import type { RateLimitConfig } from './providers/rate-limit.js';
14
14
  import type { PromptCacheTtl } from './translate/anthropic.js';
15
15
  import type { CliVersion, NpmCliVersionCache } from './providers/npm-cli-version.js';
16
- export type { AccountPreferences, ProviderPreferences } from './provider-settings.js';
16
+ export type { AccountPreferences, ProviderPreferences, } from './provider-settings.js';
17
17
  import type { PoolMemberRef } from './providers/pool-family.js';
18
18
  import type { AntigravityRuntimeConfig } from './providers/antigravity.js';
19
- export type { ModelEntry, ProviderUsage, UsageWindow } from './providers/common.js';
19
+ export type { ModelEntry, ProviderUsage, UsageWindow, } from './providers/common.js';
20
20
  export type { RateLimitConfig, RateLimitWait } from './providers/rate-limit.js';
21
21
  export type { ProviderStatus } from './auth/rpc.js';
22
- export type { AntigravitySession, ClaudeSession, CodexSession, CopilotSession, GrokSession, ProviderId } from './auth/store.js';
22
+ export type { AntigravitySession, ClaudeSession, CodexSession, CopilotSession, GrokSession, ProviderId, } from './auth/store.js';
23
23
  export declare const name = "dsh-subscriptions";
24
24
  export declare const inject: string[];
25
25
  /** Default maximum provider idle time while one stream read is outstanding. */
package/lib/index.js CHANGED
@@ -1205,7 +1205,7 @@ var DeviceFlowManager = class {
1205
1205
  const response = await fetchFn(spec.deviceCodeUrl, {
1206
1206
  method: "POST",
1207
1207
  headers: {
1208
- "accept": "application/json",
1208
+ accept: "application/json",
1209
1209
  "content-type": "application/x-www-form-urlencoded"
1210
1210
  },
1211
1211
  body: new URLSearchParams({
@@ -1244,7 +1244,7 @@ var DeviceFlowManager = class {
1244
1244
  const pollResponse = await fetchFn(spec.tokenUrl, {
1245
1245
  method: "POST",
1246
1246
  headers: {
1247
- "accept": "application/json",
1247
+ accept: "application/json",
1248
1248
  "content-type": "application/x-www-form-urlencoded"
1249
1249
  },
1250
1250
  body: new URLSearchParams({
@@ -3064,8 +3064,8 @@ function withoutImages(messages) {
3064
3064
  }
3065
3065
  function discoveryHeaders(apiKey) {
3066
3066
  return {
3067
- "accept": "application/json",
3068
- ...apiKey === void 0 ? {} : { "authorization": `Bearer ${apiKey}` },
3067
+ accept: "application/json",
3068
+ ...apiKey === void 0 ? {} : { authorization: `Bearer ${apiKey}` },
3069
3069
  ...attributionHeaders()
3070
3070
  };
3071
3071
  }
@@ -3340,8 +3340,8 @@ var OllamaAdapter = class extends LlmAdapter {
3340
3340
  const response = await (this.options.fetchFn ?? hostFetch)(`${chatBase}/chat/completions`, {
3341
3341
  method: "POST",
3342
3342
  headers: {
3343
- "authorization": `Bearer ${apiKey}`,
3344
- "accept": "text/event-stream",
3343
+ authorization: `Bearer ${apiKey}`,
3344
+ accept: "text/event-stream",
3345
3345
  "content-type": "application/json",
3346
3346
  ...attributionHeaders()
3347
3347
  },
@@ -3377,7 +3377,7 @@ function valueOf(value) {
3377
3377
  const DEFAULT_REFS = {
3378
3378
  "opencode-go": "OPENCODE_GO_API_KEY",
3379
3379
  "kimi-code": "KIMI_CODING_API_KEY",
3380
- "minimax": "MINIMAX_API_KEY",
3380
+ minimax: "MINIMAX_API_KEY",
3381
3381
  "minimax-cn": "MINIMAX_CN_API_KEY",
3382
3382
  "ollama-cloud": DEFAULT_OLLAMA_API_KEY_REF
3383
3383
  };
@@ -3834,7 +3834,13 @@ async function dispatch(controller, speed, modelDefaults, endpoint, payload, sig
3834
3834
  accounts: [],
3835
3835
  detail: error instanceof Error ? error.message : String(error)
3836
3836
  }))]));
3837
- return ok({ providers: Object.fromEntries(entries) });
3837
+ return ok({ providers: Object.fromEntries(entries.map(([provider, status]) => {
3838
+ const visibleModels = providerSettings?.visibleModels?.(provider);
3839
+ return [provider, {
3840
+ ...status,
3841
+ ...visibleModels === void 0 ? {} : { visibleModels }
3842
+ }];
3843
+ })) });
3838
3844
  }
3839
3845
  case "login": {
3840
3846
  const provider = readProvider(payload);
@@ -3931,7 +3937,7 @@ async function dispatch(controller, speed, modelDefaults, endpoint, payload, sig
3931
3937
  return ok({ ok: true });
3932
3938
  case "image": return ok(await controller.readImage(readImageRef(payload), signal));
3933
3939
  case "video": return ok(await controller.readVideo(readVideoName(payload), signal));
3934
- case "speed": return ok(await speed.speed(readSessionId(payload)));
3940
+ case "speed": return ok(await speed.speed(readSessionId(payload), payload.model === void 0 ? void 0 : readString(payload, "model")));
3935
3941
  case "setSpeed":
3936
3942
  await speed.setSpeed(readSessionId(payload), readSpeedTier(payload));
3937
3943
  return ok({ ok: true });
@@ -6950,8 +6956,8 @@ async function postJson(url, apiKey, payload, options, signal) {
6950
6956
  method: "POST",
6951
6957
  headers: {
6952
6958
  "content-type": "application/json",
6953
- "accept": "application/json",
6954
- "authorization": `Bearer ${apiKey}`,
6959
+ accept: "application/json",
6960
+ authorization: `Bearer ${apiKey}`,
6955
6961
  ...attributionHeaders()
6956
6962
  },
6957
6963
  body: JSON.stringify(payload),
@@ -8008,13 +8014,39 @@ async function readCatalogFile(path) {
8008
8014
  return {};
8009
8015
  }
8010
8016
  }
8017
+ /**
8018
+ * Windows refuses to replace a file another handle holds open without
8019
+ * `FILE_SHARE_DELETE` (an indexer, antivirus scan, or a second dsh profile
8020
+ * reading the cache), surfacing as a transient `EPERM`/`EBUSY`/`EACCES` on
8021
+ * `rename` (V1ki/dsh-plugin-subscriptions#129). On Windows only, retry
8022
+ * briefly before giving the write up: on POSIX those codes are real
8023
+ * failures, so retrying would only delay surfacing them.
8024
+ */
8025
+ const RENAME_RETRY_CODES = new Set([
8026
+ "EPERM",
8027
+ "EBUSY",
8028
+ "EACCES"
8029
+ ]);
8030
+ const RENAME_RETRIES = 5;
8031
+ const RENAME_RETRY_DELAY_MS = 20;
8032
+ /** `rename` with a short bounded retry on Windows share-mode conflicts. */
8033
+ async function renameWithRetry(from, to) {
8034
+ for (let attempt = 0;; attempt++) try {
8035
+ await rename(from, to);
8036
+ return;
8037
+ } catch (error) {
8038
+ const code = error.code;
8039
+ if (process.platform !== "win32" || attempt >= RENAME_RETRIES || code === void 0 || !RENAME_RETRY_CODES.has(code)) throw error;
8040
+ await new Promise((resolve$2) => setTimeout(resolve$2, RENAME_RETRY_DELAY_MS * (attempt + 1)));
8041
+ }
8042
+ }
8011
8043
  /** Persist the whole file atomically (tmp file + rename). */
8012
8044
  async function writeCatalogFile(store, path) {
8013
8045
  await mkdir(dirname(path), { recursive: true });
8014
8046
  const tmp = `${path}.tmp-${process.pid}-${Math.random().toString(36).slice(2)}`;
8015
8047
  try {
8016
8048
  await writeFile(tmp, JSON.stringify(store, null, 2));
8017
- await rename(tmp, path);
8049
+ await renameWithRetry(tmp, path);
8018
8050
  } catch (error) {
8019
8051
  await rm(tmp, { force: true });
8020
8052
  throw error;
@@ -8550,10 +8582,10 @@ async function fetchClaudeUsage(session, fetchFn = hostFetch, signal, cliVersion
8550
8582
  const userAgent = claudeCliUserAgent(await cliVersion());
8551
8583
  const init = {
8552
8584
  headers: {
8553
- "authorization": `Bearer ${session.accessToken}`,
8585
+ authorization: `Bearer ${session.accessToken}`,
8554
8586
  "anthropic-beta": "oauth-2025-04-20",
8555
8587
  "user-agent": userAgent,
8556
- "accept": "application/json"
8588
+ accept: "application/json"
8557
8589
  },
8558
8590
  ...signal === void 0 ? {} : { signal }
8559
8591
  };
@@ -8625,11 +8657,11 @@ async function fetchClaudeModels(session, fetchFn = hostFetch, signal, cliVersio
8625
8657
  const userAgent = claudeCliUserAgent(await cliVersion());
8626
8658
  const response = await fetchFn(CLAUDE_MODELS_URL, {
8627
8659
  headers: {
8628
- "authorization": `Bearer ${session.accessToken}`,
8660
+ authorization: `Bearer ${session.accessToken}`,
8629
8661
  "anthropic-version": "2023-06-01",
8630
8662
  "user-agent": userAgent,
8631
8663
  "anthropic-dangerous-direct-browser-access": "true",
8632
- "accept": "application/json"
8664
+ accept: "application/json"
8633
8665
  },
8634
8666
  ...signal === void 0 ? {} : { signal }
8635
8667
  });
@@ -8697,8 +8729,9 @@ const CLAUDE_MODALITIES = ["text", "image"];
8697
8729
  * @param effort - the reasoning effort, when the model advertises efforts.
8698
8730
  * @param cacheTtl - lifetime of every cache breakpoint; defaults to five minutes.
8699
8731
  * Requests that carry a `purpose` (compaction, session titles) always use five
8700
- * minutes to preserve their existing auxiliary-request behavior. A five-minute
8701
- * mark can still read an entry a one-hour mark wrote.
8732
+ * minutes to preserve their existing auxiliary-request behavior — the override
8733
+ * changes the TTL for ordinary turns only, never the auxiliary floor. A
8734
+ * five-minute mark can still read an entry a one-hour mark wrote.
8702
8735
  * @returns the JSON body to POST.
8703
8736
  */
8704
8737
  function claudeRequestBody(options, messages, maxTokens, thinking, effort, cacheTtl) {
@@ -8913,18 +8946,18 @@ var ClaudeAdapter = class extends LlmAdapter {
8913
8946
  }
8914
8947
  const disc = await this.discovered(options.model);
8915
8948
  const maxTokens = options.maxTokens ?? claudeMaxTokens(this.options.models.find((entry) => entry.id === options.model), disc);
8916
- const body = claudeRequestBody(options, messages, maxTokens, this.thinkingParam(disc?.thinkingType, maxTokens), options.reasoningEffort !== void 0 && disc?.reasoning !== void 0 ? String(options.reasoningEffort) : void 0, this.options.promptCacheTtl);
8949
+ const body = claudeRequestBody(options, messages, maxTokens, this.thinkingParam(disc?.thinkingType, maxTokens), options.reasoningEffort !== void 0 && disc?.reasoning !== void 0 ? String(options.reasoningEffort) : void 0, this.options.promptCacheTtlOverride?.() ?? this.options.promptCacheTtl);
8917
8950
  const cliVersion = await (this.options.resolveCliVersion ?? localClaudeCliVersion)();
8918
8951
  return hostFetch(CLAUDE_API_URL, {
8919
8952
  method: "POST",
8920
8953
  headers: {
8921
- "authorization": `Bearer ${session.accessToken}`,
8954
+ authorization: `Bearer ${session.accessToken}`,
8922
8955
  "anthropic-version": "2023-06-01",
8923
8956
  "anthropic-beta": CLAUDE_BETA_FLAGS,
8924
8957
  "user-agent": claudeCliUserAgent(cliVersion),
8925
8958
  "x-app": "cli",
8926
8959
  "anthropic-dangerous-direct-browser-access": "true",
8927
- "accept": "text/event-stream",
8960
+ accept: "text/event-stream",
8928
8961
  "content-type": "application/json"
8929
8962
  },
8930
8963
  body: JSON.stringify(body),
@@ -9653,10 +9686,10 @@ function codexUsageWindow(value, fallbackKind) {
9653
9686
  async function fetchCodexUsage(session, fetchFn = hostFetch, signal) {
9654
9687
  const response = await fetchFn(CODEX_USAGE_URL, {
9655
9688
  headers: {
9656
- "authorization": `Bearer ${session.accessToken}`,
9689
+ authorization: `Bearer ${session.accessToken}`,
9657
9690
  "chatgpt-account-id": session.accountId,
9658
- "originator": "codex_cli_rs",
9659
- "accept": "application/json",
9691
+ originator: "codex_cli_rs",
9692
+ accept: "application/json",
9660
9693
  ...attributionHeaders()
9661
9694
  },
9662
9695
  ...signal === void 0 ? {} : { signal }
@@ -9707,10 +9740,10 @@ async function fetchCodexModels(session, fetchFn = hostFetch, signal, clientVers
9707
9740
  const version = codexClientVersion(clientVersion);
9708
9741
  const response = await fetchFn(`${CODEX_MODELS_URL}?client_version=${encodeURIComponent(version)}`, {
9709
9742
  headers: {
9710
- "authorization": `Bearer ${session.accessToken}`,
9743
+ authorization: `Bearer ${session.accessToken}`,
9711
9744
  "chatgpt-account-id": session.accountId,
9712
- "originator": "codex_cli_rs",
9713
- "accept": "application/json",
9745
+ originator: "codex_cli_rs",
9746
+ accept: "application/json",
9714
9747
  ...attributionHeaders()
9715
9748
  },
9716
9749
  ...signal === void 0 ? {} : { signal }
@@ -10101,11 +10134,11 @@ var CodexAdapter = class extends LlmAdapter {
10101
10134
  return hostFetch(CODEX_API_URL, {
10102
10135
  method: "POST",
10103
10136
  headers: {
10104
- "authorization": `Bearer ${session.accessToken}`,
10137
+ authorization: `Bearer ${session.accessToken}`,
10105
10138
  "chatgpt-account-id": session.accountId,
10106
- "originator": "codex_cli_rs",
10139
+ originator: "codex_cli_rs",
10107
10140
  "session-id": deterministicSessionId(options.sessionId),
10108
- "accept": "text/event-stream",
10141
+ accept: "text/event-stream",
10109
10142
  "content-type": "application/json",
10110
10143
  ...attributionHeaders()
10111
10144
  },
@@ -10185,10 +10218,10 @@ var CodexWebSearchProvider = class {
10185
10218
  response = await fetchFn(CODEX_SEARCH_URL, {
10186
10219
  method: "POST",
10187
10220
  headers: {
10188
- "authorization": `Bearer ${session.accessToken}`,
10221
+ authorization: `Bearer ${session.accessToken}`,
10189
10222
  "chatgpt-account-id": session.accountId,
10190
10223
  "content-type": "application/json",
10191
- "originator": "codex_cli_rs",
10224
+ originator: "codex_cli_rs",
10192
10225
  ...attributionHeaders()
10193
10226
  },
10194
10227
  body: JSON.stringify(body),
@@ -10769,7 +10802,7 @@ var PoolAdapter = class extends LlmAdapter {
10769
10802
  if (definition === void 0) throw new LlmError(`unknown pool model "${options.model}"`, "NO_ADAPTER");
10770
10803
  const members = await this.concrete(definition.members);
10771
10804
  const candidates = await this.select(options.model, members, options.sessionId);
10772
- if (candidates.length === 0) throw this.exhausted(options.model, members);
10805
+ if (candidates.length === 0) throw this.exhaustedWithWarn(options.model, members);
10773
10806
  const failures = [];
10774
10807
  for (const member of candidates) {
10775
10808
  const adapter = this.options.adapters[member.provider];
@@ -10804,7 +10837,7 @@ var PoolAdapter = class extends LlmAdapter {
10804
10837
  }
10805
10838
  return;
10806
10839
  }
10807
- throw this.exhausted(options.model, members, failures);
10840
+ throw this.exhaustedWithWarn(options.model, members, failures);
10808
10841
  }
10809
10842
  /**
10810
10843
  * Order the candidates for one request. Health filters both strategies;
@@ -10845,6 +10878,18 @@ var PoolAdapter = class extends LlmAdapter {
10845
10878
  }
10846
10879
  this.sticky.set(key, memberKey(member.provider, member.account, member.model));
10847
10880
  }
10881
+ /**
10882
+ * Build the terminal exhaustion error and log it. Per-member failovers are
10883
+ * already warned above; without this line dsh.log shows the failover chain
10884
+ * but not the exhaustion that ends it. The message carries only the
10885
+ * LlmError's own sanitized text and code — raw provider payloads never
10886
+ * reach here (see the note in {@link exhausted}).
10887
+ */
10888
+ exhaustedWithWarn(model, pool, failures = []) {
10889
+ const error = this.exhausted(model, pool, failures);
10890
+ this.options.onWarn(`pool "${model}": exhausted (${error.code}: ${error.message})`);
10891
+ return error;
10892
+ }
10848
10893
  /** Report attempt failures or this pool's active cooldowns, never unrelated health records. */
10849
10894
  exhausted(model, pool, failures = []) {
10850
10895
  if (pool.length === 0) return new LlmError(`pool "${model}" has no usable member: no account could be resolved`, "NO_ADAPTER");
@@ -11647,9 +11692,9 @@ function grokResetsAt(value) {
11647
11692
  async function fetchGrokUsage(session, fetchFn = hostFetch, signal) {
11648
11693
  const response = await fetchFn(GROK_BILLING_URL, {
11649
11694
  headers: {
11650
- "authorization": `Bearer ${session.accessToken}`,
11695
+ authorization: `Bearer ${session.accessToken}`,
11651
11696
  "x-xai-token-auth": "xai-grok-cli",
11652
- "accept": "application/json",
11697
+ accept: "application/json",
11653
11698
  ...attributionHeaders()
11654
11699
  },
11655
11700
  ...signal === void 0 ? {} : { signal }
@@ -11726,9 +11771,9 @@ function grokCliReasoning(entry) {
11726
11771
  async function fetchGrokCliCatalog(session, fetchFn = hostFetch, signal) {
11727
11772
  const response = await fetchFn(GROK_CLI_MODELS_URL, {
11728
11773
  headers: {
11729
- "authorization": `Bearer ${session.accessToken}`,
11774
+ authorization: `Bearer ${session.accessToken}`,
11730
11775
  "x-xai-token-auth": "xai-grok-cli",
11731
- "accept": "application/json",
11776
+ accept: "application/json",
11732
11777
  ...attributionHeaders()
11733
11778
  },
11734
11779
  ...signal === void 0 ? {} : { signal }
@@ -11792,8 +11837,8 @@ async function fetchGrokModels(session, fetchFn = hostFetch, onWarn, previous, s
11792
11837
  const previousById = previous === void 0 || previous.length === 0 ? void 0 : new Map(previous.map((model) => [model.id, model]));
11793
11838
  const [response, cliCatalog] = await Promise.all([fetchFn(GROK_MODELS_URL, {
11794
11839
  headers: {
11795
- "authorization": `Bearer ${session.accessToken}`,
11796
- "accept": "application/json",
11840
+ authorization: `Bearer ${session.accessToken}`,
11841
+ accept: "application/json",
11797
11842
  ...attributionHeaders()
11798
11843
  },
11799
11844
  ...signal === void 0 ? {} : { signal }
@@ -12017,8 +12062,8 @@ var GrokAdapter = class extends LlmAdapter {
12017
12062
  return hostFetch(GROK_API_URL, {
12018
12063
  method: "POST",
12019
12064
  headers: {
12020
- "authorization": `Bearer ${session.accessToken}`,
12021
- "accept": "text/event-stream",
12065
+ authorization: `Bearer ${session.accessToken}`,
12066
+ accept: "text/event-stream",
12022
12067
  "content-type": "application/json",
12023
12068
  ...attributionHeaders()
12024
12069
  },
@@ -12131,8 +12176,8 @@ function copilotHeaders(hasVision = false, vscodeVersion = FALLBACK_VSCODE_VERSI
12131
12176
  */
12132
12177
  async function exchangeCopilotToken(githubToken, fetchFn = hostFetch) {
12133
12178
  const response = await fetchFn(COPILOT_TOKEN_URL, { headers: {
12134
- "authorization": `Bearer ${githubToken}`,
12135
- "accept": "application/json",
12179
+ authorization: `Bearer ${githubToken}`,
12180
+ accept: "application/json",
12136
12181
  ...copilotHeaders(false, await latestVsCodeVersion(fetchFn))
12137
12182
  } });
12138
12183
  if (!response.ok) throw await oauthEndpointError(response, "copilot");
@@ -12155,8 +12200,8 @@ async function completeCopilotLogin(githubToken, fetchFn = hostFetch) {
12155
12200
  let account;
12156
12201
  try {
12157
12202
  const response = await fetchFn(GITHUB_USER_URL, { headers: {
12158
- "authorization": `Bearer ${githubToken}`,
12159
- "accept": "application/json",
12203
+ authorization: `Bearer ${githubToken}`,
12204
+ accept: "application/json",
12160
12205
  "user-agent": "GitHubCopilotChat/0.35.0"
12161
12206
  } });
12162
12207
  if (response.ok) {
@@ -12240,8 +12285,8 @@ function copilotReasoning(entry) {
12240
12285
  async function fetchCopilotModels(session, fetchFn = hostFetch, signal) {
12241
12286
  const response = await fetchFn(COPILOT_MODELS_URL, {
12242
12287
  headers: {
12243
- "authorization": `Bearer ${session.accessToken}`,
12244
- "accept": "application/json",
12288
+ authorization: `Bearer ${session.accessToken}`,
12289
+ accept: "application/json",
12245
12290
  ...copilotHeaders(false, await latestVsCodeVersion(fetchFn))
12246
12291
  },
12247
12292
  ...signal === void 0 ? {} : { signal }
@@ -12757,8 +12802,8 @@ var CopilotAdapter = class CopilotAdapter extends LlmAdapter {
12757
12802
  return hostFetch(wire === "responses" ? COPILOT_RESPONSES_URL : COPILOT_API_URL, {
12758
12803
  method: "POST",
12759
12804
  headers: {
12760
- "authorization": `Bearer ${session.accessToken}`,
12761
- "accept": "text/event-stream",
12805
+ authorization: `Bearer ${session.accessToken}`,
12806
+ accept: "text/event-stream",
12762
12807
  "content-type": "application/json",
12763
12808
  ...copilotHeaders(hasVision, await latestVsCodeVersion(this.options.fetchFn ?? hostFetch))
12764
12809
  },
@@ -12999,9 +13044,17 @@ function toAntigravityTools(tools, model = "gemini") {
12999
13044
  })) }];
13000
13045
  }
13001
13046
  const SKIP_THOUGHT_SIGNATURE_VALIDATOR = "skip_thought_signature_validator";
13002
- /** Gemini 3 validates the first function call in every model tool-call step. */
13047
+ /**
13048
+ * Whether this model validates thought signatures on replayed function calls.
13049
+ *
13050
+ * Gemini 3 and 4 require a signature on the first function call of each step;
13051
+ * foreign history uses the documented placeholder when no signature can be reused.
13052
+ * https://ai.google.dev/gemini-api/docs/generate-content/thought-signatures
13053
+ * Keep this scoped to these model families; future versions and unversioned aliases
13054
+ * need verification before enabling the same fallback.
13055
+ */
13003
13056
  function requiresThoughtSignatures(model) {
13004
- return model !== void 0 && /^gemini-3(?:[.-]|$)/i.test(model);
13057
+ return model !== void 0 && /^gemini-[34](?:[.-]|$)/i.test(model.trim());
13005
13058
  }
13006
13059
  /**
13007
13060
  * Convert resolved harness messages into Gemini contents. Function response
@@ -13432,7 +13485,7 @@ function antigravityFlow(oauth) {
13432
13485
  /** Shared Antigravity API headers. */
13433
13486
  function antigravityHeaders(accessToken, userAgent = ANTIGRAVITY_DEFAULT_USER_AGENT) {
13434
13487
  return {
13435
- "authorization": `Bearer ${accessToken}`,
13488
+ authorization: `Bearer ${accessToken}`,
13436
13489
  "content-type": "application/json",
13437
13490
  "user-agent": userAgent
13438
13491
  };
@@ -13951,9 +14004,9 @@ function createXSearchTool(options) {
13951
14004
  const response = await (options.fetchFn ?? hostFetch)(X_SEARCH_URL, {
13952
14005
  method: "POST",
13953
14006
  headers: {
13954
- "authorization": `Bearer ${session.accessToken}`,
14007
+ authorization: `Bearer ${session.accessToken}`,
13955
14008
  "content-type": "application/json",
13956
- "accept": "application/json"
14009
+ accept: "application/json"
13957
14010
  },
13958
14011
  body: JSON.stringify({
13959
14012
  model: X_SEARCH_MODEL,
@@ -14033,7 +14086,7 @@ const GROK_ASPECT_RATIOS = {
14033
14086
  "1024x1024": "1:1",
14034
14087
  "1024x1536": "2:3",
14035
14088
  "1536x1024": "3:2",
14036
- "auto": "auto"
14089
+ auto: "auto"
14037
14090
  };
14038
14091
  /**
14039
14092
  * Assemble the grok request body from the same tool arguments: `size` maps
@@ -14319,11 +14372,11 @@ function createImageGenerateTool(options) {
14319
14372
  send: (session) => fetchFn(references === void 0 ? IMAGE_GENERATE_URL : IMAGE_EDIT_URL, {
14320
14373
  method: "POST",
14321
14374
  headers: {
14322
- "authorization": `Bearer ${session.accessToken}`,
14375
+ authorization: `Bearer ${session.accessToken}`,
14323
14376
  "chatgpt-account-id": session.accountId,
14324
- "originator": "codex_cli_rs",
14377
+ originator: "codex_cli_rs",
14325
14378
  "content-type": "application/json",
14326
- "accept": "application/json"
14379
+ accept: "application/json"
14327
14380
  },
14328
14381
  body: JSON.stringify({
14329
14382
  ...buildImageGenerateBody(args),
@@ -14341,9 +14394,9 @@ function createImageGenerateTool(options) {
14341
14394
  send: (session) => fetchFn(references === void 0 ? GROK_IMAGE_GENERATE_URL : GROK_IMAGE_EDIT_URL, {
14342
14395
  method: "POST",
14343
14396
  headers: {
14344
- "authorization": `Bearer ${session.accessToken}`,
14397
+ authorization: `Bearer ${session.accessToken}`,
14345
14398
  "content-type": "application/json",
14346
- "accept": "application/json"
14399
+ accept: "application/json"
14347
14400
  },
14348
14401
  body: JSON.stringify({
14349
14402
  ...buildGrokImageGenerateBody(args),
@@ -14602,8 +14655,8 @@ function createVideoGenerateTool(options) {
14602
14655
  const session = await options.tokens.session();
14603
14656
  const fetchFn = options.fetchFn ?? hostFetch;
14604
14657
  const headers = {
14605
- "authorization": `Bearer ${session.accessToken}`,
14606
- "accept": "application/json"
14658
+ authorization: `Bearer ${session.accessToken}`,
14659
+ accept: "application/json"
14607
14660
  };
14608
14661
  const submit = await fetchFn(VIDEO_GENERATE_URL, {
14609
14662
  method: "POST",
@@ -14705,6 +14758,20 @@ function validatePreferences(provider, input) {
14705
14758
  result.contextWindows[model] = value;
14706
14759
  }
14707
14760
  }
14761
+ if (raw.promptCacheTtl !== void 0) {
14762
+ if (provider !== "claude") throw new Error("the prompt-cache TTL override is supported only for Claude");
14763
+ if (raw.promptCacheTtl !== "5m" && raw.promptCacheTtl !== "1h") throw new Error("promptCacheTtl must be \"5m\" or \"1h\"");
14764
+ result.promptCacheTtl = raw.promptCacheTtl;
14765
+ }
14766
+ if (raw.subagentSpeeds !== void 0) {
14767
+ if (provider !== "codex") throw new Error("subagent speeds are currently supported only for Codex");
14768
+ if (!raw.subagentSpeeds || typeof raw.subagentSpeeds !== "object" || Array.isArray(raw.subagentSpeeds)) throw new Error("subagent speeds must be a model-to-speed map");
14769
+ result.subagentSpeeds = Object.create(null);
14770
+ for (const [model, tier] of Object.entries(raw.subagentSpeeds)) {
14771
+ if (!model.trim() || tier !== "standard" && tier !== "fast") throw new Error("subagent speeds require a model id and either \"standard\" or \"fast\"");
14772
+ result.subagentSpeeds[model] = tier;
14773
+ }
14774
+ }
14708
14775
  if (raw.tools !== void 0) {
14709
14776
  if (!raw.tools || typeof raw.tools !== "object" || Array.isArray(raw.tools)) throw new Error("tools must be an object");
14710
14777
  result.tools = {};
@@ -14753,6 +14820,19 @@ var ProviderSettingsStore = class {
14753
14820
  const windows = this.current.providers[provider]?.contextWindows;
14754
14821
  return windows && Object.hasOwn(windows, model) ? windows[model] : void 0;
14755
14822
  }
14823
+ /** Read on every request so saved changes apply to the next subagent request. */
14824
+ subagentSpeed(model) {
14825
+ const speeds = this.current.providers.codex?.subagentSpeeds;
14826
+ return speeds && Object.hasOwn(speeds, model) ? speeds[model] : "standard";
14827
+ }
14828
+ /**
14829
+ * The Claude route's prompt-cache TTL override, read live so a saved change
14830
+ * takes effect from the next request without a restart. Absent means follow
14831
+ * the `claudePromptCacheTtl` config.
14832
+ */
14833
+ promptCacheTtl() {
14834
+ return this.current.providers.claude?.promptCacheTtl;
14835
+ }
14756
14836
  /** Creation-time policy survives restarts and never changes an existing session. */
14757
14837
  toolEnabled(provider, tool, createdAt = Date.now()) {
14758
14838
  let enabled = true;
@@ -15064,7 +15144,7 @@ var SubscriptionsAuthController = class {
15064
15144
  * cannot be read off the flow manager.
15065
15145
  */
15066
15146
  claims = /* @__PURE__ */ new Map();
15067
- constructor(flows, deviceFlows, onAuthChanged, resolveAttachments, usageFetchers = {}, readClaudeCreds = readClaudeCodeCredentials, poolUsage = void 0, antigravityConfig = {}, clientVersions = {}, resetRedemption) {
15147
+ constructor(flows, deviceFlows, onAuthChanged, resolveAttachments, usageFetchers = {}, readClaudeCreds = readClaudeCodeCredentials, poolUsage = void 0, antigravityConfig = {}, clientVersions = {}, resetRedemption, onUsageForced) {
15068
15148
  this.flows = flows;
15069
15149
  this.deviceFlows = deviceFlows;
15070
15150
  this.onAuthChanged = onAuthChanged;
@@ -15075,6 +15155,7 @@ var SubscriptionsAuthController = class {
15075
15155
  this.antigravityConfig = antigravityConfig;
15076
15156
  this.clientVersions = clientVersions;
15077
15157
  this.resetRedemption = resetRedemption;
15158
+ this.onUsageForced = onUsageForced;
15078
15159
  }
15079
15160
  prepareReset(account, signal) {
15080
15161
  if (!this.resetRedemption) throw new BadRequest("Reset redemption unavailable");
@@ -15085,6 +15166,7 @@ var SubscriptionsAuthController = class {
15085
15166
  await this.resetRedemption.redeem(account, ticket, AbortSignal.any([signal, AbortSignal.timeout(2e4)]));
15086
15167
  }
15087
15168
  usage(provider, account, signal, force = false) {
15169
+ if (force) this.onUsageForced?.(provider, account);
15088
15170
  const fetcher = this.usageFetchers[provider];
15089
15171
  if (fetcher === void 0) return Promise.resolve({ supported: false });
15090
15172
  if (this.poolUsage === void 0) return fetcher(account, signal);
@@ -15347,6 +15429,13 @@ function apply(ctx, config) {
15347
15429
  let grokTokens;
15348
15430
  const usageFetchers = {};
15349
15431
  const speedBySession = /* @__PURE__ */ new Map();
15432
+ const selectedSpeed = (sessionId, model) => {
15433
+ const explicit = speedBySession.get(sessionId);
15434
+ if (explicit !== void 0) return explicit;
15435
+ const agent = ctx.get("agents")?.get(sessionId);
15436
+ if (model !== void 0 && agent?.session.header.origin === "subagent") return preferences.subagentSpeed(parseAccountModelId(model)?.model ?? model);
15437
+ return "standard";
15438
+ };
15350
15439
  let codexAdapter;
15351
15440
  let claudeAdapter;
15352
15441
  let copilotAdapter;
@@ -15397,8 +15486,7 @@ function apply(ctx, config) {
15397
15486
  };
15398
15487
  }
15399
15488
  };
15400
- let adapter;
15401
- adapter = new CodexAdapter({
15489
+ const adapter = new CodexAdapter({
15402
15490
  ...config.codexClientVersion === void 0 ? {} : { clientVersion: config.codexClientVersion },
15403
15491
  resolveClientVersion: () => codexVersion.resolve(),
15404
15492
  models: catalog.codex,
@@ -15413,7 +15501,7 @@ function apply(ctx, config) {
15413
15501
  defaultEffortOf: (model) => defaultEffortOf("codex", model),
15414
15502
  contextWindowOf: (model) => preferences.contextWindow(model),
15415
15503
  pool: () => poolAdapter,
15416
- speedFor: (sessionId, model) => sessionId !== void 0 && speedBySession.get(sessionId) === "fast" && adapter.supportsFastTier(model)
15504
+ speedFor: (sessionId, model) => sessionId !== void 0 && selectedSpeed(sessionId, model) === "fast" && adapter.supportsFastTier(model)
15417
15505
  });
15418
15506
  codexAdapter = adapter;
15419
15507
  adapters.set("codex", adapter);
@@ -15449,6 +15537,7 @@ function apply(ctx, config) {
15449
15537
  contextWindowOf: (model) => preferences.contextWindow(model, "claude"),
15450
15538
  resolveCliVersion: () => claudeVersion.resolve(),
15451
15539
  promptCacheTtl: claudePromptCacheTtl,
15540
+ promptCacheTtlOverride: () => preferences.promptCacheTtl(),
15452
15541
  pool: () => poolAdapter
15453
15542
  });
15454
15543
  claudeAdapter = adapter;
@@ -15628,24 +15717,23 @@ function apply(ctx, config) {
15628
15717
  adapter.listModels = async (route) => (await list(route)).filter((model) => preferences.visible(provider, model.id));
15629
15718
  }
15630
15719
  const speed = {
15631
- async speed(sessionId) {
15720
+ async speed(sessionId, model) {
15632
15721
  const fastModels = await codexAdapter?.fastCapableModels() ?? [];
15633
15722
  const accountPreferences = preferences.get("codex").accounts;
15634
15723
  for (const { key } of await codexTokens?.list() ?? []) {
15635
15724
  if (!accountPreferences || !Object.hasOwn(accountPreferences, key) || accountPreferences[key].independentEntry !== true) continue;
15636
- for (const model of [...fastModels]) {
15637
- if (parseAccountModelId(model)) continue;
15638
- if (await codexAdapter?.supportsFastTier(model, key)) fastModels.push(accountModelId(key, model));
15725
+ for (const model$1 of [...fastModels]) {
15726
+ if (parseAccountModelId(model$1)) continue;
15727
+ if (await codexAdapter?.supportsFastTier(model$1, key)) fastModels.push(accountModelId(key, model$1));
15639
15728
  }
15640
15729
  }
15641
15730
  return {
15642
- tier: speedBySession.get(sessionId) ?? "standard",
15731
+ tier: selectedSpeed(sessionId, model),
15643
15732
  fastModels
15644
15733
  };
15645
15734
  },
15646
15735
  async setSpeed(sessionId, tier) {
15647
- if (tier === "standard") speedBySession.delete(sessionId);
15648
- else speedBySession.set(sessionId, tier);
15736
+ speedBySession.set(sessionId, tier);
15649
15737
  }
15650
15738
  };
15651
15739
  const modelDefaults = {
@@ -15661,7 +15749,7 @@ function apply(ctx, config) {
15661
15749
  const catalog$1 = [];
15662
15750
  for (const provider of PROVIDER_IDS) {
15663
15751
  if (!visible.has(provider)) continue;
15664
- let models = [];
15752
+ let models;
15665
15753
  try {
15666
15754
  models = await ctx.llm.listModels(provider);
15667
15755
  } catch {
@@ -15776,7 +15864,8 @@ function apply(ctx, config) {
15776
15864
  source: "config"
15777
15865
  }) } : {},
15778
15866
  ...providers.includes("claude") ? { claude: presentedVersion(claudeVersion) } : {}
15779
- }, resetRedemption), speed, modelDefaults, {
15867
+ }, resetRedemption, (provider, account) => poolHealth?.clear(provider, account)), speed, modelDefaults, {
15868
+ visibleModels: (provider) => preferences.get(provider).visibleModels,
15780
15869
  async get(provider, force) {
15781
15870
  await loadModelDefaults();
15782
15871
  const adapter = adapters.get(provider);
@@ -15795,6 +15884,7 @@ function apply(ctx, config) {
15795
15884
  models: await withTimeout((signal) => adapter.listOwnModels(provider, account.key, signal), DISCOVERY_TIMEOUT_MS).catch(() => void 0)
15796
15885
  })));
15797
15886
  const tierIds = new Set((await poolAdapter?.modelsForProvider(provider).catch(() => []) ?? []).map((model) => model.id));
15887
+ const fastModels = new Set(provider === "codex" ? await codexAdapter?.fastCapableModels() ?? [] : []);
15798
15888
  const rows = await Promise.all(models.map(async (model) => {
15799
15889
  const contexts = [];
15800
15890
  if (provider === "codex" && codexAdapter) {
@@ -15819,6 +15909,7 @@ function apply(ctx, config) {
15819
15909
  name: name$1
15820
15910
  })) ?? [],
15821
15911
  configured: defaultEffortOf(provider, model.id),
15912
+ ...provider === "codex" ? { fastTier: !tierIds.has(model.id) && fastModels.has(model.id) } : {},
15822
15913
  ...contexts.length ? {
15823
15914
  defaultContextWindow: Math.min(...contexts.map((entry) => entry.default)),
15824
15915
  maxContextWindow: Math.min(...contexts.map((entry) => entry.max))
@@ -163,7 +163,9 @@ export function defaultEffortOf(provider, model) {
163
163
  // plain index would inherit from Object.prototype for names like
164
164
  // `toString`, handing a *function* to mergeReasoning (which then throws and
165
165
  // breaks that model's resolution).
166
- return Object.prototype.hasOwnProperty.call(section, model) ? section[model] : undefined;
166
+ return Object.prototype.hasOwnProperty.call(section, model)
167
+ ? section[model]
168
+ : undefined;
167
169
  }
168
170
  /**
169
171
  * Detached snapshot for store assertions in tests; no production RPC uses it.
@@ -193,7 +195,7 @@ export function setDefaultEffort(provider, model, effort) {
193
195
  // shared chain.
194
196
  const run = writeChain.then(async () => {
195
197
  await ensureReady();
196
- const section = { ...sectionOf(current, provider) ?? {} };
198
+ const section = { ...(sectionOf(current, provider) ?? {}) };
197
199
  if (effort === undefined) {
198
200
  delete section[model];
199
201
  }