@eddyskywalker/dsh-chatgpt-subscription 0.12.4 → 0.13.0

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 (163) hide show
  1. package/CHANGELOG.md +637 -619
  2. package/lib/client.js +3975 -4521
  3. package/lib/client.js.map +1 -1
  4. package/lib/index.js +1920 -512
  5. package/lib/types/client/CodexComposerQuota.d.ts.map +1 -1
  6. package/lib/types/client/CodexSubscriptionSection.d.ts +1 -3
  7. package/lib/types/client/CodexSubscriptionSection.d.ts.map +1 -1
  8. package/lib/types/client/ProviderHubSection.d.ts +6 -5
  9. package/lib/types/client/ProviderHubSection.d.ts.map +1 -1
  10. package/lib/types/client/antigravity/AntigravityComposerQuota.d.ts +22 -1
  11. package/lib/types/client/antigravity/AntigravityComposerQuota.d.ts.map +1 -1
  12. package/lib/types/client/antigravity/AntigravitySection.d.ts +0 -2
  13. package/lib/types/client/antigravity/AntigravitySection.d.ts.map +1 -1
  14. package/lib/types/client/antigravity/locales.d.ts +33 -26
  15. package/lib/types/client/antigravity/locales.d.ts.map +1 -1
  16. package/lib/types/client/antigravity/styles.d.ts.map +1 -1
  17. package/lib/types/client/claude/ClaudeComposerQuota.d.ts +3 -7
  18. package/lib/types/client/claude/ClaudeComposerQuota.d.ts.map +1 -1
  19. package/lib/types/client/claude/ClaudeSection.d.ts +10 -9
  20. package/lib/types/client/claude/ClaudeSection.d.ts.map +1 -1
  21. package/lib/types/client/claude/locales.d.ts +29 -29
  22. package/lib/types/client/claude/locales.d.ts.map +1 -1
  23. package/lib/types/client/claude/useClaudeSection.d.ts +93 -0
  24. package/lib/types/client/claude/useClaudeSection.d.ts.map +1 -0
  25. package/lib/types/client/command-code/CommandCodeComposerQuota.d.ts +3 -7
  26. package/lib/types/client/command-code/CommandCodeComposerQuota.d.ts.map +1 -1
  27. package/lib/types/client/command-code/CommandCodeSection.d.ts +0 -3
  28. package/lib/types/client/command-code/CommandCodeSection.d.ts.map +1 -1
  29. package/lib/types/client/command-code/locales.d.ts +32 -29
  30. package/lib/types/client/command-code/locales.d.ts.map +1 -1
  31. package/lib/types/client/common/AccountPoolSection.d.ts +16 -4
  32. package/lib/types/client/common/AccountPoolSection.d.ts.map +1 -1
  33. package/lib/types/client/common/ComposerQuotaBadge.d.ts +35 -0
  34. package/lib/types/client/common/ComposerQuotaBadge.d.ts.map +1 -0
  35. package/lib/types/client/common/ContextWindowEditor.d.ts +69 -0
  36. package/lib/types/client/common/ContextWindowEditor.d.ts.map +1 -0
  37. package/lib/types/client/common/ModelChecklist.d.ts +29 -0
  38. package/lib/types/client/common/ModelChecklist.d.ts.map +1 -0
  39. package/lib/types/client/common/account-pool-labels.d.ts +18 -2
  40. package/lib/types/client/common/account-pool-labels.d.ts.map +1 -1
  41. package/lib/types/client/common/account-quota.d.ts +60 -0
  42. package/lib/types/client/common/account-quota.d.ts.map +1 -0
  43. package/lib/types/client/common/format.d.ts +30 -0
  44. package/lib/types/client/common/format.d.ts.map +1 -0
  45. package/lib/types/client/common/line-api.d.ts +8 -0
  46. package/lib/types/client/common/line-api.d.ts.map +1 -0
  47. package/lib/types/client/common/model-checklist-styles.d.ts +2 -0
  48. package/lib/types/client/common/model-checklist-styles.d.ts.map +1 -0
  49. package/lib/types/client/common/styles.d.ts.map +1 -1
  50. package/lib/types/client/common/use-store.d.ts +3 -0
  51. package/lib/types/client/common/use-store.d.ts.map +1 -0
  52. package/lib/types/client/hub/HubOverview.d.ts +14 -0
  53. package/lib/types/client/hub/HubOverview.d.ts.map +1 -0
  54. package/lib/types/client/hub/brand-icons.d.ts +46 -0
  55. package/lib/types/client/hub/brand-icons.d.ts.map +1 -0
  56. package/lib/types/client/hub/brand-svg.d.ts +51 -0
  57. package/lib/types/client/hub/brand-svg.d.ts.map +1 -0
  58. package/lib/types/client/hub/hub-styles.d.ts +2 -0
  59. package/lib/types/client/hub/hub-styles.d.ts.map +1 -0
  60. package/lib/types/client/hub/providers.d.ts +38 -0
  61. package/lib/types/client/hub/providers.d.ts.map +1 -0
  62. package/lib/types/client/index.d.ts +20 -6
  63. package/lib/types/client/index.d.ts.map +1 -1
  64. package/lib/types/client/kimi-code/KimiCodeComposerQuota.d.ts +3 -7
  65. package/lib/types/client/kimi-code/KimiCodeComposerQuota.d.ts.map +1 -1
  66. package/lib/types/client/kimi-code/KimiCodeSection.d.ts +0 -3
  67. package/lib/types/client/kimi-code/KimiCodeSection.d.ts.map +1 -1
  68. package/lib/types/client/kimi-code/locales.d.ts +32 -32
  69. package/lib/types/client/kimi-code/locales.d.ts.map +1 -1
  70. package/lib/types/client/kimi-code/styles.d.ts.map +1 -1
  71. package/lib/types/client/locales.d.ts +45 -35
  72. package/lib/types/client/locales.d.ts.map +1 -1
  73. package/lib/types/client/minimax-code/MinimaxCodeComposerQuota.d.ts +4 -8
  74. package/lib/types/client/minimax-code/MinimaxCodeComposerQuota.d.ts.map +1 -1
  75. package/lib/types/client/minimax-code/MinimaxCodeSection.d.ts +8 -37
  76. package/lib/types/client/minimax-code/MinimaxCodeSection.d.ts.map +1 -1
  77. package/lib/types/client/minimax-code/api.d.ts +3 -3
  78. package/lib/types/client/minimax-code/api.d.ts.map +1 -1
  79. package/lib/types/client/minimax-code/locales.d.ts +29 -56
  80. package/lib/types/client/minimax-code/locales.d.ts.map +1 -1
  81. package/lib/types/client/minimax-code/useMinimaxCodeSection.d.ts +115 -0
  82. package/lib/types/client/minimax-code/useMinimaxCodeSection.d.ts.map +1 -0
  83. package/lib/types/client/ollama/OllamaSection.d.ts +2 -2
  84. package/lib/types/client/ollama/OllamaSection.d.ts.map +1 -1
  85. package/lib/types/client/ollama/locales.d.ts +54 -24
  86. package/lib/types/client/ollama/locales.d.ts.map +1 -1
  87. package/lib/types/client/styles.d.ts.map +1 -1
  88. package/lib/types/client/workbuddy/WorkBuddyComposerQuota.d.ts +3 -7
  89. package/lib/types/client/workbuddy/WorkBuddyComposerQuota.d.ts.map +1 -1
  90. package/lib/types/client/workbuddy/WorkBuddySection.d.ts +10 -37
  91. package/lib/types/client/workbuddy/WorkBuddySection.d.ts.map +1 -1
  92. package/lib/types/client/workbuddy/locales.d.ts +0 -66
  93. package/lib/types/client/workbuddy/locales.d.ts.map +1 -1
  94. package/lib/types/client/workbuddy/useWorkBuddySection.d.ts +141 -0
  95. package/lib/types/client/workbuddy/useWorkBuddySection.d.ts.map +1 -0
  96. package/lib/types/host/antigravity/account-pool.d.ts +18 -1
  97. package/lib/types/host/antigravity/account-pool.d.ts.map +1 -1
  98. package/lib/types/host/antigravity/client.d.ts.map +1 -1
  99. package/lib/types/host/claude/account-pool.d.ts +22 -2
  100. package/lib/types/host/claude/account-pool.d.ts.map +1 -1
  101. package/lib/types/host/claude/adapter.d.ts +50 -0
  102. package/lib/types/host/claude/adapter.d.ts.map +1 -1
  103. package/lib/types/host/claude/client.d.ts +37 -5
  104. package/lib/types/host/claude/client.d.ts.map +1 -1
  105. package/lib/types/host/claude/mapper.d.ts +7 -0
  106. package/lib/types/host/claude/mapper.d.ts.map +1 -1
  107. package/lib/types/host/claude/routes.d.ts.map +1 -1
  108. package/lib/types/host/codex-account-pool.d.ts +26 -1
  109. package/lib/types/host/codex-account-pool.d.ts.map +1 -1
  110. package/lib/types/host/command-code/account-pool.d.ts +13 -1
  111. package/lib/types/host/command-code/account-pool.d.ts.map +1 -1
  112. package/lib/types/host/command-code/adapter.d.ts +24 -0
  113. package/lib/types/host/command-code/adapter.d.ts.map +1 -1
  114. package/lib/types/host/command-code/client.d.ts +2 -1
  115. package/lib/types/host/command-code/client.d.ts.map +1 -1
  116. package/lib/types/host/command-code/mapper.d.ts +44 -0
  117. package/lib/types/host/command-code/mapper.d.ts.map +1 -1
  118. package/lib/types/host/common/account-quota.d.ts +44 -0
  119. package/lib/types/host/common/account-quota.d.ts.map +1 -0
  120. package/lib/types/host/common/catalog-snapshot.d.ts +9 -0
  121. package/lib/types/host/common/catalog-snapshot.d.ts.map +1 -1
  122. package/lib/types/host/common/provider-route.d.ts +56 -0
  123. package/lib/types/host/common/provider-route.d.ts.map +1 -0
  124. package/lib/types/host/common/stream-error.d.ts +93 -0
  125. package/lib/types/host/common/stream-error.d.ts.map +1 -0
  126. package/lib/types/host/hub-overview.d.ts +26 -0
  127. package/lib/types/host/hub-overview.d.ts.map +1 -0
  128. package/lib/types/host/kimi-code/account-pool.d.ts +24 -1
  129. package/lib/types/host/kimi-code/account-pool.d.ts.map +1 -1
  130. package/lib/types/host/kimi-code/adapter.d.ts +27 -0
  131. package/lib/types/host/kimi-code/adapter.d.ts.map +1 -1
  132. package/lib/types/host/kimi-code/client.d.ts +23 -2
  133. package/lib/types/host/kimi-code/client.d.ts.map +1 -1
  134. package/lib/types/host/kimi-code/mapper.d.ts +26 -0
  135. package/lib/types/host/kimi-code/mapper.d.ts.map +1 -1
  136. package/lib/types/host/minimax-code/account-pool.d.ts +34 -2
  137. package/lib/types/host/minimax-code/account-pool.d.ts.map +1 -1
  138. package/lib/types/host/minimax-code/adapter.d.ts.map +1 -1
  139. package/lib/types/host/minimax-code/client.d.ts +23 -0
  140. package/lib/types/host/minimax-code/client.d.ts.map +1 -1
  141. package/lib/types/host/minimax-code/mapper.d.ts +39 -0
  142. package/lib/types/host/minimax-code/mapper.d.ts.map +1 -1
  143. package/lib/types/host/minimax-code/routes.d.ts.map +1 -1
  144. package/lib/types/host/oauth-service.d.ts +16 -0
  145. package/lib/types/host/oauth-service.d.ts.map +1 -1
  146. package/lib/types/host/ollama/adapter.d.ts.map +1 -1
  147. package/lib/types/host/ollama/types.d.ts +33 -0
  148. package/lib/types/host/ollama/types.d.ts.map +1 -1
  149. package/lib/types/host/usage-service.d.ts +27 -2
  150. package/lib/types/host/usage-service.d.ts.map +1 -1
  151. package/lib/types/host/workbuddy/account-pool.d.ts +13 -1
  152. package/lib/types/host/workbuddy/account-pool.d.ts.map +1 -1
  153. package/lib/types/host/workbuddy/client.d.ts +15 -0
  154. package/lib/types/host/workbuddy/client.d.ts.map +1 -1
  155. package/lib/types/host/workbuddy/routes.d.ts.map +1 -1
  156. package/lib/types/index.d.ts.map +1 -1
  157. package/lib/types/shared/account-pool-contracts.d.ts +44 -0
  158. package/lib/types/shared/account-pool-contracts.d.ts.map +1 -1
  159. package/lib/types/shared/antigravity-contracts.d.ts +9 -3
  160. package/lib/types/shared/antigravity-contracts.d.ts.map +1 -1
  161. package/lib/types/shared/hub-contracts.d.ts +37 -0
  162. package/lib/types/shared/hub-contracts.d.ts.map +1 -0
  163. package/package.json +1 -1
package/lib/index.js CHANGED
@@ -516,6 +516,17 @@ function catalogSnapshotPath(name) {
516
516
  return path.join(dshHomeDir(), "storages", `${name}-catalog.json`);
517
517
  }
518
518
  /**
519
+ * How many models one line's catalog holds, for the hub overview's "N/M models".
520
+ *
521
+ * An empty catalog is reported as `null` rather than `0`: "this line has synced
522
+ * nothing yet" and "this line offers no models" are different statements, and
523
+ * only the second one is a fact. The overview hides the model count for a null,
524
+ * which is what a line that has never synced should show.
525
+ */
526
+ function catalogTotal(count) {
527
+ return count === 0 ? null : count;
528
+ }
529
+ /**
519
530
  * Snapshot name for one catalog *and* one credential scope.
520
531
  *
521
532
  * A persisted snapshot answers before any credential is read — that is the whole
@@ -681,7 +692,7 @@ let snapshotRehydrated = null;
681
692
  * directly; returning it here under a different entry type would force every
682
693
  * one of them to convert it.
683
694
  */
684
- function getCachedCatalog() {
695
+ function getCachedCatalog$2() {
685
696
  return catalogCache$2?.models;
686
697
  }
687
698
  /**
@@ -740,7 +751,7 @@ async function performCatalogLoad$1(credentials, options) {
740
751
  live = true;
741
752
  writeCatalogSnapshot(catalogSnapshotName(SNAPSHOT_SCOPE, key), [...models], Date.now());
742
753
  } catch {
743
- models = getCachedCatalog() ?? [];
754
+ models = getCachedCatalog$2() ?? [];
744
755
  live = false;
745
756
  }
746
757
  catalogCache$2 = {
@@ -791,7 +802,7 @@ function parseListing(payload) {
791
802
  function parseListingEntry$1(value) {
792
803
  const record = asRecord$10(value);
793
804
  if (record === void 0) return [];
794
- const id = asString$17(record.slug) ?? asString$17(record.id);
805
+ const id = asString$18(record.slug) ?? asString$18(record.id);
795
806
  if (id === void 0) return [];
796
807
  const contextWindow = asNumber$4(record.context_window) ?? asNumber$4(record.contextWindow);
797
808
  const modalities = parseModalities(record.input_modalities);
@@ -799,18 +810,18 @@ function parseListingEntry$1(value) {
799
810
  const fallback = CODEX_MODEL_CATALOG.find((entry) => entry.id === id);
800
811
  return [{
801
812
  id,
802
- name: asString$17(record.display_name) ?? asString$17(record.displayName) ?? fallback?.name ?? id,
813
+ name: asString$18(record.display_name) ?? asString$18(record.displayName) ?? fallback?.name ?? id,
803
814
  contextWindow: contextWindow !== void 0 && contextWindow > 0 ? contextWindow : null,
804
815
  inputModalities: modalities ?? (fallback ? [...fallback.inputModalities] : ["text"]),
805
816
  ...efforts !== null ? { reasoningEfforts: efforts } : {},
806
- defaultReasoningEffort: asString$17(record.default_reasoning_level) ?? fallback?.defaultReasoningEffort ?? null
817
+ defaultReasoningEffort: asString$18(record.default_reasoning_level) ?? fallback?.defaultReasoningEffort ?? null
807
818
  }];
808
819
  }
809
820
  function parseModalities(value) {
810
821
  if (!Array.isArray(value)) return null;
811
822
  const out = [];
812
823
  for (const item of value) {
813
- const name = asString$17(item);
824
+ const name = asString$18(item);
814
825
  if (name === "text" || name === "image") out.push(name);
815
826
  }
816
827
  return out.length > 0 ? out : null;
@@ -825,7 +836,7 @@ function parseEfforts(value) {
825
836
  if (!Array.isArray(value)) return null;
826
837
  const out = [];
827
838
  for (const item of value) {
828
- const effort = asString$17(item) ?? asString$17(asRecord$10(item)?.effort);
839
+ const effort = asString$18(item) ?? asString$18(asRecord$10(item)?.effort);
829
840
  if (effort !== void 0 && !out.includes(effort)) out.push(effort);
830
841
  }
831
842
  return out.length > 0 ? out : null;
@@ -843,7 +854,7 @@ function accountKeyFor$1(credentials) {
843
854
  function asRecord$10(value) {
844
855
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
845
856
  }
846
- function asString$17(value) {
857
+ function asString$18(value) {
847
858
  return typeof value === "string" && value !== "" ? value : void 0;
848
859
  }
849
860
  function asNumber$4(value) {
@@ -1085,6 +1096,22 @@ var WindowsDpapiTokenStore = class extends WindowsDpapiCredentialStore {
1085
1096
  super(path, parseStoredCredentials);
1086
1097
  }
1087
1098
  };
1099
+ /**
1100
+ * How long a helper may run before it is killed and the read fails.
1101
+ *
1102
+ * A healthy spawn costs ~200 ms (see the cache note on this class), so the guard
1103
+ * exists only for a child that never exits, and it was set at 10 s - fifty times
1104
+ * the healthy cost. That turned out to be too tight for one real environment: a
1105
+ * cold Windows runner whose freshly-installed dependency tree is being scanned
1106
+ * on first spawn, where the helper legitimately took longer than 10 s and the
1107
+ * kill turned a working credential read into `DPAPI helper timed out`.
1108
+ *
1109
+ * The guard is kept, and only its headroom widened: 30 s is still bounded, still
1110
+ * far above anything a warm machine needs, and a hung helper now blocks one
1111
+ * credential read for 30 s rather than 10 - a worse wait in a case that is already
1112
+ * a failure, in exchange for not failing a machine that is merely cold.
1113
+ */
1114
+ const POWERSHELL_HELPER_TIMEOUT_MS = 3e4;
1088
1115
  function runPowerShell(script, path, stdin) {
1089
1116
  return new Promise((resolve, reject) => {
1090
1117
  const child = spawn("powershell.exe", [
@@ -1110,7 +1137,7 @@ function runPowerShell(script, path, stdin) {
1110
1137
  const timer = setTimeout(() => {
1111
1138
  child.kill();
1112
1139
  reject(/* @__PURE__ */ new Error("DPAPI helper timed out"));
1113
- }, 1e4);
1140
+ }, POWERSHELL_HELPER_TIMEOUT_MS);
1114
1141
  child.stdout.setEncoding("utf8");
1115
1142
  child.stdout.on("data", (chunk) => {
1116
1143
  stdout += chunk;
@@ -2505,13 +2532,31 @@ var OAuthService = class {
2505
2532
  * "credentials are required" on every web search.
2506
2533
  */
2507
2534
  async credentials(forceRefresh = false, access = {}) {
2535
+ return (await this.credentialSelection(forceRefresh, access)).credentials;
2536
+ }
2537
+ /**
2538
+ * The credential to use next, together with the pool row that owns it.
2539
+ *
2540
+ * The settings card keys each account's quota snapshot by the row the reading
2541
+ * was made for, and the credential cannot do that job: a refresh rotates its
2542
+ * tokens, and an account may state no id of its own. Callers that care about
2543
+ * identity ask here; every other caller keeps using {@link credentials} and
2544
+ * sees no difference.
2545
+ *
2546
+ * @param forceRefresh - refresh even a token that is not close to expiry.
2547
+ * @param access - who is asking; see {@link credentials}.
2548
+ */
2549
+ async credentialSelection(forceRefresh = false, access = {}) {
2508
2550
  if (this.pool !== null) {
2509
- if (access.purpose === "tool") return (await this.pool.getCredentialAccount(this.fetchFn, forceRefresh)).credentials;
2510
- return (await this.pool.getEffectiveAccount(void 0, this.fetchFn, forceRefresh)).credentials;
2551
+ const picked = access.purpose === "tool" ? await this.pool.getCredentialAccount(this.fetchFn, forceRefresh) : await this.pool.getEffectiveAccount(void 0, this.fetchFn, forceRefresh);
2552
+ return {
2553
+ accountId: picked.account.id,
2554
+ credentials: picked.credentials
2555
+ };
2511
2556
  }
2512
2557
  const stored = await this.loadAuthenticated();
2513
- if (forceRefresh || stored.expiresAt - this.now() <= 6e4) return this.refreshCredentials(stored);
2514
- return stored;
2558
+ if (forceRefresh || stored.expiresAt - this.now() <= 6e4) return { credentials: await this.refreshCredentials(stored) };
2559
+ return { credentials: stored };
2515
2560
  }
2516
2561
  dispose() {
2517
2562
  if (this.disposed) return;
@@ -2807,6 +2852,49 @@ async function oauthErrorIdentifier(response) {
2807
2852
  return null;
2808
2853
  }
2809
2854
  //#endregion
2855
+ //#region src/host/common/account-quota.ts
2856
+ /**
2857
+ * A reset instant as Unix milliseconds.
2858
+ *
2859
+ * Providers state this in seconds, in milliseconds, or not at all, and the two
2860
+ * numeric forms are told apart by magnitude rather than by a flag: 10^10
2861
+ * seconds is the year 2286, so anything larger is already milliseconds.
2862
+ */
2863
+ function quotaResetMs(value) {
2864
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return null;
2865
+ return value > 1e10 ? value : value * 1e3;
2866
+ }
2867
+ /**
2868
+ * One window, or null when its consumed share was never measured.
2869
+ *
2870
+ * `label` may be empty: the card names an unlabeled window from its length, and
2871
+ * only falls back to a generic name when the line stated neither.
2872
+ */
2873
+ function quotaWindow$1(label, usedPercent, options = {}) {
2874
+ if (typeof usedPercent !== "number" || !Number.isFinite(usedPercent)) return null;
2875
+ const windowDurationMins = typeof options.windowDurationMins === "number" && Number.isFinite(options.windowDurationMins) ? options.windowDurationMins : null;
2876
+ return {
2877
+ label,
2878
+ usedPercent: Math.min(100, Math.max(0, usedPercent)),
2879
+ windowDurationMins,
2880
+ resetsAt: quotaResetMs(options.resetsAt)
2881
+ };
2882
+ }
2883
+ /**
2884
+ * The snapshot to publish for one account, or undefined when there is none.
2885
+ *
2886
+ * Undefined is the honest answer for "never read" and for "read before this
2887
+ * process started": both mean the card has nothing to show, and neither means
2888
+ * the account is unspent.
2889
+ */
2890
+ function poolQuota(fetchedAt, windows) {
2891
+ if (typeof fetchedAt !== "number" || !Number.isFinite(fetchedAt) || fetchedAt <= 0) return void 0;
2892
+ return {
2893
+ windows: windows.filter((window) => window !== null),
2894
+ fetchedAt
2895
+ };
2896
+ }
2897
+ //#endregion
2810
2898
  //#region src/host/codex-account-pool.ts
2811
2899
  /** Encrypted pool file the ChatGPT line owns; the single-credential store stays the mirror. */
2812
2900
  function codexPoolPath() {
@@ -2875,13 +2963,32 @@ const concurrency = new ConcurrencyGate();
2875
2963
  function chatGPTConcurrency() {
2876
2964
  return concurrency;
2877
2965
  }
2966
+ /**
2967
+ * One ChatGPT usage snapshot as the account card's per-account quota.
2968
+ *
2969
+ * A bucket lists its windows either in `windows` or, on older payloads, only as
2970
+ * the `primary`/`secondary` pair — the same fallback the quota card itself
2971
+ * applies, so the account row cannot show fewer windows than the card does. The
2972
+ * label stays empty on purpose: a ChatGPT window is named by its length, and
2973
+ * the card localizes that length in the reader's own language.
2974
+ */
2975
+ function codexAccountQuota(snapshot) {
2976
+ return poolQuota(snapshot.fetchedAt, snapshot.usage.buckets.flatMap((bucket) => {
2977
+ return (bucket.windows.length > 0 ? bucket.windows : [bucket.primary, bucket.secondary].filter((window) => window !== null)).map((window) => quotaWindow$1("", window.usedPercent, {
2978
+ windowDurationMins: window.windowDurationMins,
2979
+ resetsAt: window.resetsAt
2980
+ }));
2981
+ }));
2982
+ }
2878
2983
  var CodexAccountPool = class extends AccountPoolCore {
2879
2984
  refresherRef;
2880
2985
  store;
2986
+ quotaSnapshotRef;
2881
2987
  quotaBlockedUntil;
2882
2988
  constructor(options = {}) {
2883
2989
  const store = options.store ?? createPlatformTokenStore();
2884
2990
  const ref = {};
2991
+ const quotaSnapshotRef = {};
2885
2992
  const hooks = {
2886
2993
  providerId: "codex-chatgpt",
2887
2994
  displayName: "ChatGPT",
@@ -2932,7 +3039,11 @@ var CodexAccountPool = class extends AccountPoolCore {
2932
3039
  extendSummary: (account, base) => ({
2933
3040
  ...base,
2934
3041
  ...account.email === void 0 ? {} : { email: account.email },
2935
- ...account.planType === void 0 ? {} : { planLabel: account.planType }
3042
+ ...account.planType === void 0 ? {} : { planLabel: account.planType },
3043
+ ...(() => {
3044
+ const quota = quotaSnapshotRef.current?.(account);
3045
+ return quota === void 0 ? {} : { quota };
3046
+ })()
2936
3047
  }),
2937
3048
  emptyMessage: "未登录 ChatGPT 账号,请在「设置 → 订阅服务 → ChatGPT」中添加并登录账号。",
2938
3049
  ...options.backend === void 0 ? {} : { backend: options.backend },
@@ -2940,6 +3051,7 @@ var CodexAccountPool = class extends AccountPoolCore {
2940
3051
  };
2941
3052
  super(hooks);
2942
3053
  this.refresherRef = ref;
3054
+ this.quotaSnapshotRef = quotaSnapshotRef;
2943
3055
  this.store = store;
2944
3056
  }
2945
3057
  /** Hand the pool the OAuth service's per-account refresh implementation. */
@@ -2957,6 +3069,18 @@ var CodexAccountPool = class extends AccountPoolCore {
2957
3069
  setQuotaBlockedUntil(lookup) {
2958
3070
  this.quotaBlockedUntil = lookup;
2959
3071
  }
3072
+ /**
3073
+ * Wire in the account's own newest quota snapshot, for display only.
3074
+ *
3075
+ * The same map that already spares an exhausted account a wasted request also
3076
+ * answers "what did this account's window look like last time we read it".
3077
+ * The lookup must be a memory read: it runs while the settings card builds
3078
+ * its account list, and that page must never fan out an upstream request per
3079
+ * pooled account.
3080
+ */
3081
+ setQuotaSnapshot(lookup) {
3082
+ this.quotaSnapshotRef.current = lookup;
3083
+ }
2960
3084
  isEligible(account, now, triedAccountIds) {
2961
3085
  if (!super.isEligible(account, now, triedAccountIds)) return false;
2962
3086
  const blockedUntil = this.quotaBlockedUntil?.(account, now);
@@ -3309,13 +3433,13 @@ function createCodexFetchProvider(options = {}) {
3309
3433
  //#region src/host/common/llm-compat.ts
3310
3434
  /** This package's kind in the harness message-source map. */
3311
3435
  const PLUGIN_MESSAGE_SOURCE_KIND = "dsh-chatgpt-subscription";
3312
- function isRecord$29(value) {
3436
+ function isRecord$30(value) {
3313
3437
  return typeof value === "object" && value !== null && !Array.isArray(value);
3314
3438
  }
3315
3439
  /** Read a message body's blocks, keeping every entry that carries a block tag. */
3316
3440
  function readBlocks(raw) {
3317
3441
  if (!Array.isArray(raw)) return [];
3318
- return raw.filter((block) => isRecord$29(block) && typeof block.type === "string");
3442
+ return raw.filter((block) => isRecord$30(block) && typeof block.type === "string");
3319
3443
  }
3320
3444
  /**
3321
3445
  * Normalize one harness message list into {@link Message}.
@@ -3330,10 +3454,10 @@ function readBlocks(raw) {
3330
3454
  function normalizeMessages(messages) {
3331
3455
  const normalized = [];
3332
3456
  for (const raw of messages ?? []) {
3333
- if (!isRecord$29(raw)) continue;
3457
+ if (!isRecord$30(raw)) continue;
3334
3458
  const content = readBlocks(raw.content);
3335
3459
  if (raw.role === "tool") {
3336
- const source = isRecord$29(raw.source) ? raw.source : void 0;
3460
+ const source = isRecord$30(raw.source) ? raw.source : void 0;
3337
3461
  const callId = String(raw.toolCallId ?? source?.callId ?? "");
3338
3462
  normalized.push({
3339
3463
  id: raw.id,
@@ -3355,7 +3479,7 @@ function normalizeMessages(messages) {
3355
3479
  id: raw.id,
3356
3480
  role: typeof raw.role === "string" ? raw.role : "user",
3357
3481
  content,
3358
- ...isRecord$29(raw.source) ? { source: raw.source } : {}
3482
+ ...isRecord$30(raw.source) ? { source: raw.source } : {}
3359
3483
  });
3360
3484
  }
3361
3485
  return normalized;
@@ -4072,6 +4196,56 @@ function createControlledModelFetch(baseFetch, options) {
4072
4196
  };
4073
4197
  }
4074
4198
  //#endregion
4199
+ //#region src/host/common/provider-route.ts
4200
+ /**
4201
+ * Claim one provider route for this plugin and keep claiming it.
4202
+ *
4203
+ * The route is taken immediately, and every `llm/adapters-updated` reports the
4204
+ * claim again — it is a no-op while the registration is held, and takes the
4205
+ * route back once another owner releases it. A rejected claim is remembered
4206
+ * rather than thrown, because the line whose route table asks through
4207
+ * {@link ProviderRouteClaim.conflict} still has to render.
4208
+ * @param ctx - The plugin context owning the `llm` service and the event seam.
4209
+ * @param options - The route id, the display label the two log lines name, and the adapter serving it.
4210
+ * @returns The claim's state and its disposer.
4211
+ */
4212
+ function claimProviderRoute(ctx, options) {
4213
+ let registration;
4214
+ let conflict = null;
4215
+ const claim = () => {
4216
+ if (registration !== void 0) return;
4217
+ try {
4218
+ registration = ctx.llm.registerAdapter([options.providerId], options.adapter);
4219
+ if (conflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${options.label} route "${options.providerId}" is now served by this plugin`);
4220
+ conflict = null;
4221
+ } catch (error) {
4222
+ conflict = error instanceof Error ? error.message : String(error);
4223
+ ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${options.providerId}" is already owned by another adapter; ${options.label} models keep being served by that one until its configuration is removed (${conflict})`);
4224
+ }
4225
+ };
4226
+ claim();
4227
+ const watch = options.watch === false || typeof ctx.on !== "function" ? void 0 : ctx.on("llm/adapters-updated", () => {
4228
+ claim();
4229
+ });
4230
+ return {
4231
+ serving: () => registration !== void 0,
4232
+ conflict: () => conflict,
4233
+ dispose: () => {
4234
+ releaseHandle(watch);
4235
+ registration?.();
4236
+ registration = void 0;
4237
+ }
4238
+ };
4239
+ }
4240
+ /** Cordis event handles are either a disposer function or a disposable object. */
4241
+ function releaseHandle(handle) {
4242
+ if (typeof handle === "function") {
4243
+ handle();
4244
+ return;
4245
+ }
4246
+ handle?.dispose?.();
4247
+ }
4248
+ //#endregion
4075
4249
  //#region src/host/common/request-diagnostics.ts
4076
4250
  /**
4077
4251
  * Reusable request diagnostics wrapper for model provider calls.
@@ -6223,6 +6397,72 @@ function acceptIdentity(current, incoming) {
6223
6397
  return typeof incoming === "string" && incoming.length > 0 ? incoming : current;
6224
6398
  }
6225
6399
  //#endregion
6400
+ //#region src/shared/hub-contracts.ts
6401
+ /**
6402
+ * Wire contracts for the subscription hub's overview route.
6403
+ *
6404
+ * The overview is the settings page's opening screen: one card per provider
6405
+ * line, annotated with how many accounts it holds and whether it is enabled.
6406
+ * Everything here is a non-secret fact a settings card may render; no
6407
+ * credential, quota figure, or upstream call ever crosses this boundary.
6408
+ */
6409
+ /** The aggregated, read-only overview the hub's opening screen renders. */
6410
+ const HUB_OVERVIEW_PATH = `${ROUTE_PREFIX$5}/hub/overview`;
6411
+ //#endregion
6412
+ //#region src/host/hub-overview.ts
6413
+ function registerHubOverviewRoutes(ctx, sources) {
6414
+ const handler = async (request, response) => {
6415
+ if (request.method !== "GET") {
6416
+ response.writeHead(405, {
6417
+ allow: "GET",
6418
+ "content-type": "application/json; charset=utf-8"
6419
+ });
6420
+ response.end(JSON.stringify({
6421
+ ok: false,
6422
+ error: {
6423
+ code: "bad-request",
6424
+ message: "Method not allowed."
6425
+ }
6426
+ }));
6427
+ return;
6428
+ }
6429
+ const envelope = {
6430
+ ok: true,
6431
+ value: { providers: (await Promise.allSettled(sources.map(async (source) => ({
6432
+ id: source.id,
6433
+ providerId: source.providerId,
6434
+ canToggle: source.canToggle,
6435
+ ...await source.read()
6436
+ })))).map((result, index) => {
6437
+ if (result.status === "fulfilled") return result.value;
6438
+ const source = sources[index];
6439
+ return {
6440
+ id: source.id,
6441
+ providerId: source.providerId,
6442
+ canToggle: source.canToggle,
6443
+ enabled: !source.canToggle,
6444
+ accountCount: 0,
6445
+ authenticated: false,
6446
+ enabledModelCount: null,
6447
+ totalModelCount: null,
6448
+ error: true
6449
+ };
6450
+ }) }
6451
+ };
6452
+ response.writeHead(200, {
6453
+ "content-type": "application/json; charset=utf-8",
6454
+ "cache-control": "no-store",
6455
+ "x-content-type-options": "nosniff"
6456
+ });
6457
+ response.end(JSON.stringify(envelope));
6458
+ };
6459
+ return ctx.webServer.register({
6460
+ kind: "exact",
6461
+ path: HUB_OVERVIEW_PATH,
6462
+ handler
6463
+ });
6464
+ }
6465
+ //#endregion
6226
6466
  //#region src/host/common/same-origin.ts
6227
6467
  /**
6228
6468
  * Whether one mutation request comes from this application's own document.
@@ -6330,9 +6570,9 @@ var UsageService = class {
6330
6570
  };
6331
6571
  const now = this.now();
6332
6572
  if (!force && !this.invalidated && this.cache !== null && now - this.cache.fetchedAt < 6e4 && this.cacheIdentityRevision === this.oauth.currentIdentityRevision()) return this.fromCache(false);
6333
- let credentials;
6573
+ let selection;
6334
6574
  try {
6335
- credentials = await this.oauth.credentials(false, USAGE_CREDENTIAL_ACCESS);
6575
+ selection = await this.oauth.credentialSelection(false, USAGE_CREDENTIAL_ACCESS);
6336
6576
  } catch (error) {
6337
6577
  const reason = error instanceof Error && error.message !== "" ? ` (${error.message})` : "";
6338
6578
  return this.failure({
@@ -6340,7 +6580,8 @@ var UsageService = class {
6340
6580
  message: `ChatGPT credentials could not be refreshed.${reason}`
6341
6581
  });
6342
6582
  }
6343
- const accountKey = identityKey(credentials);
6583
+ const credentials = selection.credentials;
6584
+ const accountKey = this.snapshotKeyFor(selection.accountId, credentials);
6344
6585
  if (this.cache !== null && this.cache.accountKey !== accountKey) this.clear();
6345
6586
  if (!force && !this.invalidated && this.cache !== null && now - this.cache.fetchedAt < 6e4) return this.fromCache(false);
6346
6587
  if (this.cache !== null && now - this.lastUpstreamAt < 15e3) return this.fromCache(this.invalidated || now - this.cache.fetchedAt >= 6e4);
@@ -6368,9 +6609,9 @@ var UsageService = class {
6368
6609
  * The rejection is absorbed here: this refresh has no caller to receive it,
6369
6610
  * and {@link fromCache} keeps serving the snapshot it failed to replace.
6370
6611
  */
6371
- startBackgroundRefresh(credentials, accountKey) {
6612
+ startBackgroundRefresh(credentials, snapshotKey) {
6372
6613
  if (this.inFlight !== null) return;
6373
- const task = this.refreshUpstream(credentials, accountKey).finally(() => {
6614
+ const task = this.refreshUpstream(credentials, snapshotKey).finally(() => {
6374
6615
  if (this.inFlight === task) this.inFlight = null;
6375
6616
  });
6376
6617
  this.inFlight = task;
@@ -6387,8 +6628,8 @@ var UsageService = class {
6387
6628
  * cooldown rather than forever, so a missing upstream field cannot strand an
6388
6629
  * account permanently.
6389
6630
  */
6390
- blockedUntilFor(credentials, now) {
6391
- const snapshot = this.snapshots.get(identityKey(credentials));
6631
+ blockedUntilFor(accountId, credentials, now) {
6632
+ const snapshot = this.snapshots.get(this.snapshotKeyFor(accountId, credentials));
6392
6633
  if (snapshot === void 0) return void 0;
6393
6634
  let reopen;
6394
6635
  for (const bucket of snapshot.usage.buckets) {
@@ -6401,6 +6642,32 @@ var UsageService = class {
6401
6642
  }
6402
6643
  return reopen !== void 0 && reopen > now ? reopen : void 0;
6403
6644
  }
6645
+ /**
6646
+ * The newest quota snapshot held for one account, if any.
6647
+ *
6648
+ * Quota follows the account, so the settings card shows each account its own
6649
+ * progress. The pool asks here with the row id it addresses the account by —
6650
+ * the identity that survives a token refresh — while its credentials answer
6651
+ * for a composition without a pool. This reads the remembered map only, never
6652
+ * the network, because opening the settings page must not cost one upstream
6653
+ * request per pooled account.
6654
+ */
6655
+ snapshotFor(accountId, credentials) {
6656
+ return this.snapshots.get(this.snapshotKeyFor(accountId, credentials));
6657
+ }
6658
+ /**
6659
+ * Key one account's remembered snapshot is filed under.
6660
+ *
6661
+ * A pool row id when the caller names one, prefixed so it can never be
6662
+ * confused with the credential-derived key, and otherwise the credential
6663
+ * identity — which is all a composition without a pool has. Two pooled accounts
6664
+ * that state neither an account id nor an email would otherwise collapse onto
6665
+ * one key and show each other's numbers, so the row id is the answer wherever
6666
+ * a pool is in charge.
6667
+ */
6668
+ snapshotKeyFor(accountId, credentials) {
6669
+ return accountId === void 0 || accountId === "" ? identityKey(credentials) : `row:${accountId}`;
6670
+ }
6404
6671
  /** Remember the newest snapshot for one account, bounded to the accounts in use. */
6405
6672
  rememberSnapshot(accountKey, usage, fetchedAt) {
6406
6673
  this.snapshots.set(accountKey, {
@@ -6420,8 +6687,8 @@ var UsageService = class {
6420
6687
  this.invalidated = false;
6421
6688
  }
6422
6689
  /** Drop the remembered snapshot of one account, after its window is known to have reset. */
6423
- forgetSnapshot(credentials) {
6424
- this.snapshots.delete(identityKey(credentials));
6690
+ forgetSnapshot(accountId, credentials) {
6691
+ this.snapshots.delete(this.snapshotKeyFor(accountId, credentials));
6425
6692
  }
6426
6693
  async consumeResetCredit() {
6427
6694
  if (this.resetConsumeInFlight !== null) return this.resetConsumeInFlight;
@@ -6432,12 +6699,17 @@ var UsageService = class {
6432
6699
  }
6433
6700
  async consumeResetCreditUpstream() {
6434
6701
  let credentials;
6702
+ let accountId;
6435
6703
  try {
6436
- credentials = await this.oauth.credentials(false, USAGE_CREDENTIAL_ACCESS);
6704
+ const selection = await this.oauth.credentialSelection(false, USAGE_CREDENTIAL_ACCESS);
6705
+ credentials = selection.credentials;
6706
+ accountId = selection.accountId;
6437
6707
  let creditsResponse = await this.fetchResetCredits(credentials);
6438
6708
  if (creditsResponse.status === 401) {
6439
6709
  await creditsResponse.body?.cancel().catch(() => void 0);
6440
- credentials = await this.oauth.credentials(true, USAGE_CREDENTIAL_ACCESS);
6710
+ const renewed = await this.oauth.credentialSelection(true, USAGE_CREDENTIAL_ACCESS);
6711
+ credentials = renewed.credentials;
6712
+ accountId = renewed.accountId;
6441
6713
  creditsResponse = await this.fetchResetCredits(credentials);
6442
6714
  }
6443
6715
  if (!creditsResponse.ok) {
@@ -6468,7 +6740,9 @@ var UsageService = class {
6468
6740
  });
6469
6741
  if (consumeResponse.status === 401) {
6470
6742
  await consumeResponse.body?.cancel().catch(() => void 0);
6471
- credentials = await this.oauth.credentials(true, USAGE_CREDENTIAL_ACCESS);
6743
+ const renewed = await this.oauth.credentialSelection(true, USAGE_CREDENTIAL_ACCESS);
6744
+ credentials = renewed.credentials;
6745
+ accountId = renewed.accountId;
6472
6746
  consumeResponse = await this.fetchFn(CODEX_RESET_CREDITS_CONSUME_URL, {
6473
6747
  method: "POST",
6474
6748
  headers: {
@@ -6492,7 +6766,7 @@ var UsageService = class {
6492
6766
  }
6493
6767
  await consumeResponse.body?.cancel().catch(() => void 0);
6494
6768
  this.clear();
6495
- const refreshed = await this.refreshUpstream(credentials, identityKey(credentials));
6769
+ const refreshed = await this.refreshUpstream(credentials, this.snapshotKeyFor(accountId, credentials));
6496
6770
  if (refreshed.error !== void 0) throw new UsageServiceError({
6497
6771
  code: "quota-failed",
6498
6772
  message: "The reset credit was used, but usage could not be refreshed."
@@ -6519,16 +6793,17 @@ var UsageService = class {
6519
6793
  checkedAt: Math.floor(this.now() / 1e3)
6520
6794
  };
6521
6795
  }
6522
- async refreshUpstream(initialCredentials, initialAccountKey) {
6796
+ async refreshUpstream(initialCredentials, initialSnapshotKey) {
6523
6797
  this.lastUpstreamAt = this.now();
6524
6798
  try {
6525
6799
  let credentials = initialCredentials;
6526
- let accountKey = initialAccountKey;
6800
+ let snapshotKey = initialSnapshotKey;
6527
6801
  let response = await this.fetch(credentials);
6528
6802
  if (response.status === 401) {
6529
6803
  await response.body?.cancel().catch(() => void 0);
6530
- credentials = await this.oauth.credentials(true, USAGE_CREDENTIAL_ACCESS);
6531
- accountKey = identityKey(credentials);
6804
+ const renewed = await this.oauth.credentialSelection(true, USAGE_CREDENTIAL_ACCESS);
6805
+ credentials = renewed.credentials;
6806
+ snapshotKey = this.snapshotKeyFor(renewed.accountId, credentials);
6532
6807
  response = await this.fetch(credentials);
6533
6808
  }
6534
6809
  if (response.status === 429) {
@@ -6561,10 +6836,10 @@ var UsageService = class {
6561
6836
  this.cache = {
6562
6837
  usage,
6563
6838
  fetchedAt: this.now(),
6564
- accountKey
6839
+ accountKey: snapshotKey
6565
6840
  };
6566
6841
  this.cacheIdentityRevision = this.oauth.currentIdentityRevision();
6567
- this.rememberSnapshot(accountKey, usage, this.cache.fetchedAt);
6842
+ this.rememberSnapshot(snapshotKey, usage, this.cache.fetchedAt);
6568
6843
  this.invalidated = false;
6569
6844
  return this.fromCache(false);
6570
6845
  } catch (error) {
@@ -7055,7 +7330,7 @@ function field(value, name) {
7055
7330
  const candidate = value[name];
7056
7331
  return typeof candidate === "string" && candidate !== "" ? candidate : null;
7057
7332
  }
7058
- function isRecord$28(value) {
7333
+ function isRecord$29(value) {
7059
7334
  return typeof value === "object" && value !== null && !Array.isArray(value);
7060
7335
  }
7061
7336
  function readPreferencesUpdate(value, current) {
@@ -7089,7 +7364,7 @@ function readPreferencesUpdate(value, current) {
7089
7364
  patch.searchProvider = value.searchProvider;
7090
7365
  }
7091
7366
  if ("contextWindowOverrides" in value) {
7092
- if (!isRecord$28(value.contextWindowOverrides)) throw new PreferenceError("contextWindowOverrides must be an object.");
7367
+ if (!isRecord$29(value.contextWindowOverrides)) throw new PreferenceError("contextWindowOverrides must be an object.");
7093
7368
  const overrides = {};
7094
7369
  for (const [model, contextWindow] of Object.entries(value.contextWindowOverrides)) {
7095
7370
  if (!isCodexModelId(model)) throw new PreferenceError("Unknown Codex model for a context window override.");
@@ -7903,7 +8178,7 @@ var FileModelSettingsStore$2 = class {
7903
8178
  */
7904
8179
  /** Default loopback port the Antigravity CLI's own flow uses. */
7905
8180
  const DEFAULT_CALLBACK_PORT$2 = 51121;
7906
- let cachedQuota$1;
8181
+ let cachedQuota;
7907
8182
  let quotaFetchInFlight = null;
7908
8183
  /** Bumped by every cache clear so an in-flight fetch cannot publish stale state. */
7909
8184
  let quotaCacheEpoch$2 = 0;
@@ -8080,7 +8355,7 @@ function parseQuotaSummary(data) {
8080
8355
  for (const bucket of rawBuckets) {
8081
8356
  if (typeof bucket !== "object" || bucket === null) continue;
8082
8357
  const bRec = bucket;
8083
- const remaining = typeof bRec.remainingFraction === "number" ? Math.max(0, Math.min(1, bRec.remainingFraction)) : 0;
8358
+ const remaining = typeof bRec.remainingFraction === "number" && Number.isFinite(bRec.remainingFraction) ? Math.max(0, Math.min(1, bRec.remainingFraction)) : null;
8084
8359
  buckets.push({
8085
8360
  bucketId: String(bRec.bucketId || bRec.displayName || "limit"),
8086
8361
  displayName: String(bRec.displayName || bRec.bucketId || "Limit"),
@@ -8143,7 +8418,7 @@ function mergeCatalogSelection(stored, storedCatalog) {
8143
8418
  return stored.filter((id) => known.has(id));
8144
8419
  }
8145
8420
  async function fetchAccountQuota(store = new FileCredentialStore$2(), modelSettings, fetchFn = fetch, force = false) {
8146
- if (!force && cachedQuota$1 && Date.now() - (cachedQuota$1.fetchedAt || 0) < 12e4) return cachedQuota$1;
8421
+ if (!force && cachedQuota && Date.now() - (cachedQuota.fetchedAt || 0) < 12e4) return cachedQuota;
8147
8422
  if (quotaFetchInFlight) return quotaFetchInFlight;
8148
8423
  const epoch = quotaCacheEpoch$2;
8149
8424
  const request = (async () => {
@@ -8179,7 +8454,7 @@ async function fetchAccountQuota(store = new FileCredentialStore$2(), modelSetti
8179
8454
  fetchedAt: Date.now()
8180
8455
  };
8181
8456
  if (epoch !== quotaCacheEpoch$2) return snapshot;
8182
- cachedQuota$1 = snapshot;
8457
+ cachedQuota = snapshot;
8183
8458
  if (modelSettings && catalogModels.length > 0) {
8184
8459
  const current = await modelSettings.read();
8185
8460
  const mergedEnabled = mergeCatalogSelection(current.enabledModelIds, current.catalogModels);
@@ -8195,11 +8470,11 @@ async function fetchAccountQuota(store = new FileCredentialStore$2(), modelSetti
8195
8470
  }
8196
8471
  }
8197
8472
  function getCachedQuota() {
8198
- return cachedQuota$1;
8473
+ return cachedQuota;
8199
8474
  }
8200
8475
  function clearCachedQuota() {
8201
8476
  quotaCacheEpoch$2 += 1;
8202
- cachedQuota$1 = void 0;
8477
+ cachedQuota = void 0;
8203
8478
  quotaFetchInFlight = null;
8204
8479
  }
8205
8480
  //#endregion
@@ -8589,7 +8864,7 @@ const schemaFields = /* @__PURE__ */ new Set([
8589
8864
  "required",
8590
8865
  "propertyOrdering"
8591
8866
  ]);
8592
- function isRecord$27(value) {
8867
+ function isRecord$28(value) {
8593
8868
  return typeof value === "object" && value !== null && !Array.isArray(value);
8594
8869
  }
8595
8870
  function mergeSchemas(left, right) {
@@ -8597,7 +8872,7 @@ function mergeSchemas(left, right) {
8597
8872
  ...left,
8598
8873
  ...right
8599
8874
  };
8600
- if (isRecord$27(left.properties) && isRecord$27(right.properties)) merged.properties = {
8875
+ if (isRecord$28(left.properties) && isRecord$28(right.properties)) merged.properties = {
8601
8876
  ...left.properties,
8602
8877
  ...right.properties
8603
8878
  };
@@ -8633,9 +8908,9 @@ const mergedTypeKeys = /* @__PURE__ */ new Set([
8633
8908
  * model with strictly less than the schema it started from.
8634
8909
  */
8635
8910
  function describeBranch(branch) {
8636
- const type = typeof branch.type === "string" ? branch.type : isRecord$27(branch.properties) ? "object" : isRecord$27(branch.items) ? "array" : "value";
8911
+ const type = typeof branch.type === "string" ? branch.type : isRecord$28(branch.properties) ? "object" : isRecord$28(branch.items) ? "array" : "value";
8637
8912
  const details = [];
8638
- if (isRecord$27(branch.properties)) {
8913
+ if (isRecord$28(branch.properties)) {
8639
8914
  const names = Object.keys(branch.properties);
8640
8915
  if (names.length > 0) details.push(`{${names.join(",")}}`);
8641
8916
  }
@@ -8649,7 +8924,7 @@ function describeBranch(branch) {
8649
8924
  }
8650
8925
  /** How well one branch can stand in for a whole union; a named object shape wins. */
8651
8926
  function branchRank(branch) {
8652
- if (isRecord$27(branch.properties)) return 3;
8927
+ if (isRecord$28(branch.properties)) return 3;
8653
8928
  if (branch.type === "object") return 2;
8654
8929
  if (branch.type === "array") return 1;
8655
8930
  return 0;
@@ -8678,7 +8953,7 @@ function unionShape(branches) {
8678
8953
  shape: branches[0],
8679
8954
  folded: false
8680
8955
  };
8681
- if (new Set(branches.map((branch) => branch.type)).size === 1 && branches.every((branch) => !isRecord$27(branch.properties) && !isRecord$27(branch.items))) {
8956
+ if (new Set(branches.map((branch) => branch.type)).size === 1 && branches.every((branch) => !isRecord$28(branch.properties) && !isRecord$28(branch.items))) {
8682
8957
  const shape = { type: branches[0].type };
8683
8958
  if (branches.every((branch) => Array.isArray(branch.enum))) {
8684
8959
  const values = [...new Set(branches.flatMap((branch) => branch.enum))];
@@ -8715,7 +8990,7 @@ function foldOwnFields(node) {
8715
8990
  const out = {};
8716
8991
  for (const [key, value] of Object.entries(node)) {
8717
8992
  if (key === "anyOf" || key === "oneOf" || key === "allOf") continue;
8718
- if (key === "properties" && isRecord$27(value)) {
8993
+ if (key === "properties" && isRecord$28(value)) {
8719
8994
  out.properties = Object.fromEntries(Object.entries(value).map(([name, child]) => [name, foldSchemaUnions(child)]));
8720
8995
  continue;
8721
8996
  }
@@ -8744,15 +9019,15 @@ function foldOwnFields(node) {
8744
9019
  * argument the model sends anyway fails there, naming the actual problem.
8745
9020
  */
8746
9021
  function foldSchemaUnions(node) {
8747
- if (!isRecord$27(node)) return node;
9022
+ if (!isRecord$28(node)) return node;
8748
9023
  let own = foldOwnFields(node);
8749
9024
  if (Array.isArray(node.allOf)) for (const branch of node.allOf) {
8750
9025
  const folded = foldSchemaUnions(branch);
8751
- if (isRecord$27(folded)) own = mergeSchemas(own, folded);
9026
+ if (isRecord$28(folded)) own = mergeSchemas(own, folded);
8752
9027
  }
8753
9028
  const alternatives = Array.isArray(node.anyOf) ? node.anyOf : Array.isArray(node.oneOf) ? node.oneOf : [];
8754
9029
  if (alternatives.length === 0) return own;
8755
- const branches = alternatives.map((branch) => foldSchemaUnions(branch)).filter(isRecord$27);
9030
+ const branches = alternatives.map((branch) => foldSchemaUnions(branch)).filter(isRecord$28);
8756
9031
  if (branches.length === 0) return own;
8757
9032
  const shapes = branches.filter((branch) => branch.type !== "null");
8758
9033
  const acceptsNull = branches.some((branch) => branch.type === "null");
@@ -8768,7 +9043,7 @@ function foldSchemaUnions(node) {
8768
9043
  return merged;
8769
9044
  }
8770
9045
  function toAntigravityToolSchema(schema, options = {}) {
8771
- if (!isRecord$27(schema)) return schema;
9046
+ if (!isRecord$28(schema)) return schema;
8772
9047
  const root = schema;
8773
9048
  function resolveReference(reference) {
8774
9049
  let target = root;
@@ -8776,13 +9051,13 @@ function toAntigravityToolSchema(schema, options = {}) {
8776
9051
  const path = reference === "#" ? [] : reference.slice(2).split("/");
8777
9052
  for (const segment of path) {
8778
9053
  const key = segment.replace(/~1/g, "/").replace(/~0/g, "~");
8779
- target = isRecord$27(target) && Object.hasOwn(target, key) ? target[key] : void 0;
9054
+ target = isRecord$28(target) && Object.hasOwn(target, key) ? target[key] : void 0;
8780
9055
  }
8781
- if (!isRecord$27(target)) throw new LlmError(`Antigravity tool schema reference was not found: ${reference}`, "PROVIDER_ERROR");
9056
+ if (!isRecord$28(target)) throw new LlmError(`Antigravity tool schema reference was not found: ${reference}`, "PROVIDER_ERROR");
8782
9057
  return target;
8783
9058
  }
8784
9059
  function convert(node, references) {
8785
- if (!isRecord$27(node)) return {};
9060
+ if (!isRecord$28(node)) return {};
8786
9061
  let inherited = {};
8787
9062
  if (typeof node.$ref === "string") {
8788
9063
  if (references.has(node.$ref)) throw new LlmError(`Antigravity tool schema contains a recursive reference: ${node.$ref}`, "PROVIDER_ERROR");
@@ -8791,8 +9066,8 @@ function toAntigravityToolSchema(schema, options = {}) {
8791
9066
  if (Array.isArray(node.allOf)) for (const branch of node.allOf) inherited = mergeSchemas(inherited, convert(branch, references));
8792
9067
  const out = {};
8793
9068
  for (const [key, value] of Object.entries(node)) if (schemaFields.has(key)) out[key] = value;
8794
- if (isRecord$27(node.properties)) out.properties = Object.fromEntries(Object.entries(node.properties).map(([name, child]) => [name, convert(child, references)]));
8795
- if (isRecord$27(node.items)) out.items = convert(node.items, references);
9069
+ if (isRecord$28(node.properties)) out.properties = Object.fromEntries(Object.entries(node.properties).map(([name, child]) => [name, convert(child, references)]));
9070
+ if (isRecord$28(node.items)) out.items = convert(node.items, references);
8796
9071
  const alternatives = Array.isArray(node.anyOf) ? node.anyOf : node.oneOf;
8797
9072
  if (Array.isArray(alternatives)) out.anyOf = alternatives.map((child) => convert(child, references));
8798
9073
  if (Array.isArray(node.type)) {
@@ -8828,10 +9103,10 @@ let toolCallCounter = 0;
8828
9103
  function sanitizeText$5(text) {
8829
9104
  return text.replace(/\0/g, "");
8830
9105
  }
8831
- function isRecord$26(value) {
9106
+ function isRecord$27(value) {
8832
9107
  return typeof value === "object" && value !== null && !Array.isArray(value);
8833
9108
  }
8834
- function asString$16(value) {
9109
+ function asString$17(value) {
8835
9110
  return typeof value === "string" ? value : void 0;
8836
9111
  }
8837
9112
  function safeJsonParse$5(text) {
@@ -8848,25 +9123,25 @@ function toolCallIdNeeded(modelId, runtimeModel) {
8848
9123
  return modelId.startsWith("claude-") || modelId.startsWith("gpt-oss-") || runtimeModel.startsWith("claude-") || runtimeModel.startsWith("gpt-oss-");
8849
9124
  }
8850
9125
  function parseArguments$1(raw) {
8851
- if (isRecord$26(raw)) return raw;
9126
+ if (isRecord$27(raw)) return raw;
8852
9127
  if (raw === void 0 || raw === null || raw === "") return {};
8853
9128
  const parsed = typeof raw === "string" ? safeJsonParse$5(raw) : raw;
8854
- return isRecord$26(parsed) ? parsed : {};
9129
+ return isRecord$27(parsed) ? parsed : {};
8855
9130
  }
8856
9131
  const NO_RESOLVED_IMAGES$5 = /* @__PURE__ */ new Map();
8857
9132
  function attachmentOf$5(block) {
8858
9133
  const attachment = block.attachment;
8859
- if (!isRecord$26(attachment)) return void 0;
9134
+ if (!isRecord$27(attachment)) return void 0;
8860
9135
  return typeof attachment.attachmentId === "string" ? attachment : void 0;
8861
9136
  }
8862
9137
  function attachmentLabel$6(block) {
8863
- const attachment = isRecord$26(block.attachment) ? block.attachment : void 0;
8864
- return asString$16(attachment?.name) || asString$16(attachment?.attachmentId);
9138
+ const attachment = isRecord$27(block.attachment) ? block.attachment : void 0;
9139
+ return asString$17(attachment?.name) || asString$17(attachment?.attachmentId);
8865
9140
  }
8866
9141
  function collectImageRefs$4(content, refs) {
8867
9142
  if (!Array.isArray(content)) return;
8868
9143
  for (const block of content) {
8869
- if (!isRecord$26(block)) continue;
9144
+ if (!isRecord$27(block)) continue;
8870
9145
  if (block.type === "image") {
8871
9146
  const attachment = attachmentOf$5(block);
8872
9147
  if (attachment) refs.set(attachment.attachmentId, attachment);
@@ -8904,13 +9179,13 @@ function base64Length$4(bytes) {
8904
9179
  function requestImageBytes$4(block) {
8905
9180
  const attachment = attachmentOf$5(block);
8906
9181
  if (attachment) return base64Length$4(attachment.bytes);
8907
- const inline = asString$16(block.data) || asString$16(block.base64);
9182
+ const inline = asString$17(block.data) || asString$17(block.base64);
8908
9183
  return inline ? inline.length : void 0;
8909
9184
  }
8910
9185
  function collectRequestImageBytes$4(content, lengths) {
8911
9186
  if (!Array.isArray(content)) return;
8912
9187
  for (const block of content) {
8913
- if (!isRecord$26(block) || block.type !== "image") continue;
9188
+ if (!isRecord$27(block) || block.type !== "image") continue;
8914
9189
  const bytes = requestImageBytes$4(block);
8915
9190
  if (bytes !== void 0) lengths.push(bytes);
8916
9191
  }
@@ -8945,7 +9220,7 @@ function offloadOldestRequestImages$4(options) {
8945
9220
  if (remaining.count === 0 || !Array.isArray(message.content)) return message;
8946
9221
  let replaced = false;
8947
9222
  const content = message.content.map((block) => {
8948
- if (remaining.count === 0 || !isRecord$26(block) || block.type !== "image") return block;
9223
+ if (remaining.count === 0 || !isRecord$27(block) || block.type !== "image") return block;
8949
9224
  if (requestImageBytes$4(block) === void 0) return block;
8950
9225
  remaining.count -= 1;
8951
9226
  replaced = true;
@@ -9008,10 +9283,10 @@ function unavailableImageText$6(block) {
9008
9283
  return `[image unavailable: ${label ? `${label} could not be read` : "the image could not be read"}; ask the user to attach it again if the image is needed]`;
9009
9284
  }
9010
9285
  function imageBlockToPart(block, images) {
9011
- let data = asString$16(block.data) || asString$16(block.base64);
9012
- const source = isRecord$26(block.source) ? block.source : void 0;
9013
- if (!data && source) data = asString$16(source.data) || asString$16(source.base64);
9014
- let mimeType = asString$16(block.mimeType) || asString$16(block.mediaType) || (source ? asString$16(source.mimeType) || asString$16(source.mediaType) : void 0) || "image/png";
9286
+ let data = asString$17(block.data) || asString$17(block.base64);
9287
+ const source = isRecord$27(block.source) ? block.source : void 0;
9288
+ if (!data && source) data = asString$17(source.data) || asString$17(source.base64);
9289
+ let mimeType = asString$17(block.mimeType) || asString$17(block.mediaType) || (source ? asString$17(source.mimeType) || asString$17(source.mediaType) : void 0) || "image/png";
9015
9290
  if (data?.startsWith("data:")) {
9016
9291
  const match = data.match(/^data:([^;,]+);base64,(.*)$/s);
9017
9292
  if (match) {
@@ -9034,8 +9309,8 @@ function contentToUserParts(content, images) {
9034
9309
  if (typeof content === "string") return [{ text: sanitizeText$5(content) }];
9035
9310
  if (!Array.isArray(content)) return [];
9036
9311
  const parts = [];
9037
- for (const block of content) if (isRecord$26(block) && block.type === "text" && typeof block.text === "string") parts.push({ text: sanitizeText$5(block.text) });
9038
- else if (isRecord$26(block) && block.type === "image") {
9312
+ for (const block of content) if (isRecord$27(block) && block.type === "text" && typeof block.text === "string") parts.push({ text: sanitizeText$5(block.text) });
9313
+ else if (isRecord$27(block) && block.type === "image") {
9039
9314
  const img = imageBlockToPart(block, images);
9040
9315
  parts.push(img ?? { text: unavailableImageText$6(block) });
9041
9316
  }
@@ -9050,7 +9325,7 @@ function toolResultImageParts(blocks, images) {
9050
9325
  if (!Array.isArray(blocks)) return [];
9051
9326
  const parts = [];
9052
9327
  for (const block of blocks) {
9053
- if (!isRecord$26(block)) continue;
9328
+ if (!isRecord$27(block)) continue;
9054
9329
  if (block.type === "image") {
9055
9330
  parts.push(imageBlockToPart(block, images) ?? { text: unavailableImageText$6(block) });
9056
9331
  continue;
@@ -9062,7 +9337,7 @@ function toolResultImageParts(blocks, images) {
9062
9337
  function toolResultText$5(blocks) {
9063
9338
  if (!Array.isArray(blocks)) return "";
9064
9339
  return blocks.map((block) => {
9065
- if (!isRecord$26(block)) return "";
9340
+ if (!isRecord$27(block)) return "";
9066
9341
  if (block.type === "text" && typeof block.text === "string") return sanitizeText$5(block.text);
9067
9342
  if (block.type === "tool-result") return toolResultText$5(block.content);
9068
9343
  if (block.type === "image") {
@@ -9076,16 +9351,16 @@ function replayBlockFor$1(message, index) {
9076
9351
  const source = message.source;
9077
9352
  if (!source || source.kind !== "model" || source.provider !== "antigravity") return void 0;
9078
9353
  const state = source.replayState;
9079
- if (!isRecord$26(state)) return void 0;
9354
+ if (!isRecord$27(state)) return void 0;
9080
9355
  if (Array.isArray(state.blocks)) return state.blocks[index];
9081
- const resp = isRecord$26(state.response) ? state.response : void 0;
9356
+ const resp = isRecord$27(state.response) ? state.response : void 0;
9082
9357
  if (resp) {
9083
9358
  if (Array.isArray(resp.outputItems)) return resp.outputItems[index];
9084
9359
  if (Array.isArray(resp.blocks)) return resp.blocks[index];
9085
9360
  }
9086
9361
  }
9087
9362
  function thoughtSignature(part) {
9088
- return asString$16(part?.thoughtSignature) || asString$16(part?.thought_signature) || asString$16(part?.thinkingSignature) || asString$16(part?.textSignature);
9363
+ return asString$17(part?.thoughtSignature) || asString$17(part?.thought_signature) || asString$17(part?.thinkingSignature) || asString$17(part?.textSignature);
9089
9364
  }
9090
9365
  function replayPart(part) {
9091
9366
  const copy = { ...part };
@@ -9138,11 +9413,11 @@ function assistantParts(message, model, runtimeModel, toolCalls) {
9138
9413
  if (!Array.isArray(message.content)) return parts;
9139
9414
  for (let index = 0; index < message.content.length; index++) {
9140
9415
  const block = message.content[index];
9141
- if (!isRecord$26(block)) continue;
9416
+ if (!isRecord$27(block)) continue;
9142
9417
  if (block.type === "reasoning" && runtimeModel.startsWith("claude-") && (message.source?.kind !== "model" || message.source.provider !== "antigravity" || !("model" in message.source) || message.source.model !== model.id)) continue;
9143
9418
  const replay = replayBlockFor$1(message, index);
9144
- const originalParts = Array.isArray(replay?.parts) ? replay.parts.filter(isRecord$26) : [];
9145
- if ((block.type === "text" || block.type === "reasoning") && originalParts.length > 0 && originalParts.every((part) => !part.functionCall) && originalParts.map((part) => asString$16(part.text) || "").join("") === sanitizeText$5(String(block.text || ""))) {
9419
+ const originalParts = Array.isArray(replay?.parts) ? replay.parts.filter(isRecord$27) : [];
9420
+ if ((block.type === "text" || block.type === "reasoning") && originalParts.length > 0 && originalParts.every((part) => !part.functionCall) && originalParts.map((part) => asString$17(part.text) || "").join("") === sanitizeText$5(String(block.text || ""))) {
9146
9421
  parts.push(...originalParts.map(replayPart));
9147
9422
  continue;
9148
9423
  }
@@ -9162,8 +9437,8 @@ function assistantParts(message, model, runtimeModel, toolCalls) {
9162
9437
  } else if (block.type === "tool-call") {
9163
9438
  const toolId = String(block.id || "");
9164
9439
  const toolName = String(block.name || "");
9165
- const originalCall = originalParts.find((part) => isRecord$26(part.functionCall));
9166
- const wireId = asString$16((isRecord$26(originalCall?.functionCall) ? originalCall.functionCall : void 0)?.id) || (toolCallIdNeeded(model.id, runtimeModel) ? sanitizeToolCallId(toolId, toolName) : originalCall ? void 0 : toolId || void 0);
9440
+ const originalCall = originalParts.find((part) => isRecord$27(part.functionCall));
9441
+ const wireId = asString$17((isRecord$27(originalCall?.functionCall) ? originalCall.functionCall : void 0)?.id) || (toolCallIdNeeded(model.id, runtimeModel) ? sanitizeToolCallId(toolId, toolName) : originalCall ? void 0 : toolId || void 0);
9167
9442
  toolCalls.set(toolId, {
9168
9443
  name: toolName,
9169
9444
  id: wireId
@@ -9215,7 +9490,7 @@ function convertMessages(options, model, runtimeModel, images = NO_RESOLVED_IMAG
9215
9490
  continue;
9216
9491
  }
9217
9492
  const content = Array.isArray(message.content) ? message.content : [];
9218
- const userParts = contentToUserParts(content.filter((b) => !isRecord$26(b) || b.type !== "tool-result"), images);
9493
+ const userParts = contentToUserParts(content.filter((b) => !isRecord$27(b) || b.type !== "tool-result"), images);
9219
9494
  if (role === "system") {
9220
9495
  if (userParts.length) contents.push({
9221
9496
  role: GEMINI_ROLE.user,
@@ -9227,7 +9502,7 @@ function convertMessages(options, model, runtimeModel, images = NO_RESOLVED_IMAG
9227
9502
  role: GEMINI_ROLE.user,
9228
9503
  parts: userParts
9229
9504
  });
9230
- for (const b of content) if (isRecord$26(b) && b.type === "tool-result") pushToolResult(contents, b, toolCalls, model, runtimeModel, images);
9505
+ for (const b of content) if (isRecord$27(b) && b.type === "tool-result") pushToolResult(contents, b, toolCalls, model, runtimeModel, images);
9231
9506
  }
9232
9507
  return contents;
9233
9508
  }
@@ -9362,7 +9637,7 @@ const USAGE_FIELDS = [
9362
9637
  "totalTokenCount"
9363
9638
  ];
9364
9639
  function collectUsage(value, state) {
9365
- if (!isRecord$26(value)) return;
9640
+ if (!isRecord$27(value)) return;
9366
9641
  for (const key of USAGE_FIELDS) {
9367
9642
  const count = value[key];
9368
9643
  if (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0) continue;
@@ -9392,17 +9667,17 @@ function processStreamLine$2(line, state) {
9392
9667
  }
9393
9668
  if (!json) return [];
9394
9669
  const chunk = safeJsonParse$5(json);
9395
- if (!isRecord$26(chunk)) return [];
9396
- const responseData = isRecord$26(chunk.response) ? chunk.response : chunk;
9397
- const error = isRecord$26(chunk.error) ? chunk.error : isRecord$26(responseData.error) ? responseData.error : void 0;
9398
- if (error !== void 0) throw new LlmError$1(`Antigravity stream error: ${asString$16(error.message) ?? "unknown error"}`, isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
9670
+ if (!isRecord$27(chunk)) return [];
9671
+ const responseData = isRecord$27(chunk.response) ? chunk.response : chunk;
9672
+ const error = isRecord$27(chunk.error) ? chunk.error : isRecord$27(responseData.error) ? responseData.error : void 0;
9673
+ if (error !== void 0) throw new LlmError$1(`Antigravity stream error: ${asString$17(error.message) ?? "unknown error"}`, isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
9399
9674
  const candidates = Array.isArray(responseData.candidates) ? responseData.candidates : [];
9400
- const candidate = isRecord$26(candidates[0]) ? candidates[0] : void 0;
9401
- const content = isRecord$26(candidate?.content) ? candidate.content : void 0;
9675
+ const candidate = isRecord$27(candidates[0]) ? candidates[0] : void 0;
9676
+ const content = isRecord$27(candidate?.content) ? candidate.content : void 0;
9402
9677
  const parts = Array.isArray(content?.parts) ? content.parts : [];
9403
9678
  const out = [];
9404
9679
  for (const part of parts) {
9405
- if (!isRecord$26(part)) continue;
9680
+ if (!isRecord$27(part)) continue;
9406
9681
  if (typeof part.text === "string" && part.text !== "") {
9407
9682
  const isThinking = Boolean(part.thought);
9408
9683
  const blockType = isThinking ? "reasoning" : "text";
@@ -9434,7 +9709,7 @@ function processStreamLine$2(line, state) {
9434
9709
  index: state.currentBlock.index,
9435
9710
  text: delta
9436
9711
  });
9437
- } else if (!isRecord$26(part.functionCall) && thoughtSignature(part)) {
9712
+ } else if (!isRecord$27(part.functionCall) && thoughtSignature(part)) {
9438
9713
  if (state.replayBlocks.length === 0) {
9439
9714
  const type = part.thought ? "reasoning" : "text";
9440
9715
  state.blocks.push({
@@ -9458,12 +9733,12 @@ function processStreamLine$2(line, state) {
9458
9733
  }
9459
9734
  state.replayBlocks[state.replayBlocks.length - 1].parts.push(replayPart(part));
9460
9735
  }
9461
- if (isRecord$26(part.functionCall)) {
9736
+ if (isRecord$27(part.functionCall)) {
9462
9737
  out.push(...closeCurrentBlock(state));
9463
9738
  const fc = part.functionCall;
9464
- const toolName = asString$16(fc.name) || "";
9465
- const toolId = asString$16(fc.id) || sanitizeToolCallId("", toolName);
9466
- const argsText = JSON.stringify(isRecord$26(fc.args) ? fc.args : {});
9739
+ const toolName = asString$17(fc.name) || "";
9740
+ const toolId = asString$17(fc.id) || sanitizeToolCallId("", toolName);
9741
+ const argsText = JSON.stringify(isRecord$27(fc.args) ? fc.args : {});
9467
9742
  const index = state.blocks.length;
9468
9743
  const block = {
9469
9744
  type: "tool-call",
@@ -9500,7 +9775,7 @@ function processStreamLine$2(line, state) {
9500
9775
  }
9501
9776
  collectUsage(chunk.usageMetadata, state);
9502
9777
  if (responseData !== chunk) collectUsage(responseData.usageMetadata, state);
9503
- const finishReason = asString$16(candidate?.finishReason) || asString$16(responseData.finishReason);
9778
+ const finishReason = asString$17(candidate?.finishReason) || asString$17(responseData.finishReason);
9504
9779
  if (finishReason) {
9505
9780
  state.finishReason = finishReason;
9506
9781
  out.push(...closeCurrentBlock(state));
@@ -9579,6 +9854,54 @@ function createPoolCredentialBackend(filePath) {
9579
9854
  throw new Error("Antigravity pool encrypted storage requires Windows, macOS, or Linux.");
9580
9855
  }
9581
9856
  /**
9857
+ * One bucket's reset moment as Unix milliseconds, when it parses as a date.
9858
+ *
9859
+ * The service states it as an ISO-ish string; anything `Date.parse` refuses is
9860
+ * dropped rather than guessed at, because a wrong instant on the card is worse
9861
+ * than none.
9862
+ */
9863
+ function resetTimeMs(resetTime) {
9864
+ if (resetTime === void 0 || resetTime === "") return null;
9865
+ const parsed = Date.parse(resetTime);
9866
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
9867
+ }
9868
+ /**
9869
+ * One cached Antigravity snapshot as the shared account card renders it.
9870
+ *
9871
+ * One window per bucket. The service states what is LEFT, and the card draws what
9872
+ * is SPENT, so the used share is the complement. A bucket whose fraction is
9873
+ * `null` (never measured) or outside 0-1 is dropped rather than drawn as 0% or
9874
+ * 100%: "not stated" and "nothing used" are different claims and only one of
9875
+ * them is true. A stated 0 is a real measurement — the allowance is exhausted —
9876
+ * and is published as 100% used.
9877
+ *
9878
+ * `windowDurationMins` is deliberately absent. The bucket's `window` field is
9879
+ * carried through this line's cache verbatim and nothing in the line interprets
9880
+ * it as a duration, so naming a window from it would be a guess; the card names
9881
+ * each window from the label below instead.
9882
+ */
9883
+ function antigravityPoolQuota(quota) {
9884
+ if (quota === void 0) return void 0;
9885
+ const nameCounts = /* @__PURE__ */ new Map();
9886
+ for (const group of quota.groups) for (const bucket of group.buckets) {
9887
+ const name = bucket.displayName.trim();
9888
+ if (name === "") continue;
9889
+ nameCounts.set(name, (nameCounts.get(name) ?? 0) + 1);
9890
+ }
9891
+ const windows = [];
9892
+ for (const group of quota.groups) {
9893
+ const groupName = group.displayName.trim();
9894
+ for (const bucket of group.buckets) {
9895
+ const remaining = bucket.remainingFraction;
9896
+ if (remaining === null || !Number.isFinite(remaining) || remaining < 0 || remaining > 1) continue;
9897
+ const name = bucket.displayName.trim();
9898
+ const label = (name === "" || (nameCounts.get(name) ?? 0) > 1) && groupName !== "" ? name === "" ? groupName : groupName + " · " + name : name;
9899
+ windows.push(quotaWindow$1(label, (1 - remaining) * 100, { resetsAt: resetTimeMs(bucket.resetTime) }));
9900
+ }
9901
+ }
9902
+ return poolQuota(quota.fetchedAt, windows);
9903
+ }
9904
+ /**
9582
9905
  * Antigravity's account pool.
9583
9906
  *
9584
9907
  * The storage, eligibility, rotation and cooldown rules live in the shared
@@ -9642,7 +9965,11 @@ var AccountPoolStore = class {
9642
9965
  ...base,
9643
9966
  ...projectId === void 0 ? {} : { projectId },
9644
9967
  ...email === void 0 ? {} : { email },
9645
- ...account.planLabel === void 0 ? {} : { planLabel: account.planLabel }
9968
+ ...account.planLabel === void 0 ? {} : { planLabel: account.planLabel },
9969
+ ...account.isPrimary === true ? (() => {
9970
+ const quota = antigravityPoolQuota(getCachedQuota());
9971
+ return quota === void 0 ? {} : { quota };
9972
+ })() : {}
9646
9973
  };
9647
9974
  },
9648
9975
  emptyMessage: "未登录 Antigravity 账号,请在「设置 → Antigravity」中添加并登录账号。",
@@ -11859,13 +12186,13 @@ function commandCodeHeaders(apiKey, extra = {}) {
11859
12186
  function timeoutSignal$3(signal, ms) {
11860
12187
  return signal ? AbortSignal.any([signal, AbortSignal.timeout(ms)]) : AbortSignal.timeout(ms);
11861
12188
  }
11862
- function isRecord$25(value) {
12189
+ function isRecord$26(value) {
11863
12190
  return typeof value === "object" && value !== null && !Array.isArray(value);
11864
12191
  }
11865
12192
  function asRecord$9(value) {
11866
- return isRecord$25(value) ? value : void 0;
12193
+ return isRecord$26(value) ? value : void 0;
11867
12194
  }
11868
- function asString$15(value) {
12195
+ function asString$16(value) {
11869
12196
  if (typeof value === "string" && value.trim() !== "") return value;
11870
12197
  if (typeof value === "number" && Number.isFinite(value)) return String(value);
11871
12198
  }
@@ -11878,7 +12205,7 @@ function asNumber$3(value) {
11878
12205
  }
11879
12206
  function firstString$1(record, keys) {
11880
12207
  for (const key of keys) {
11881
- const value = asString$15(record[key]);
12208
+ const value = asString$16(record[key]);
11882
12209
  if (value !== void 0) return value;
11883
12210
  }
11884
12211
  }
@@ -11907,7 +12234,7 @@ function deepValue(value, keys, depth = 0) {
11907
12234
  }
11908
12235
  return;
11909
12236
  }
11910
- if (!isRecord$25(value)) return void 0;
12237
+ if (!isRecord$26(value)) return void 0;
11911
12238
  for (const key of keys) if (value[key] !== void 0 && value[key] !== null) return value[key];
11912
12239
  for (const nested of Object.values(value)) {
11913
12240
  const hit = deepValue(nested, keys, depth + 1);
@@ -11921,7 +12248,7 @@ function collectRecords(value, keys, depth = 0, out = []) {
11921
12248
  for (const entry of value) collectRecords(entry, keys, depth + 1, out);
11922
12249
  return out;
11923
12250
  }
11924
- if (!isRecord$25(value)) return out;
12251
+ if (!isRecord$26(value)) return out;
11925
12252
  if (keys.some((key) => value[key] !== void 0)) out.push(value);
11926
12253
  for (const nested of Object.values(value)) collectRecords(nested, keys, depth + 1, out);
11927
12254
  return out;
@@ -12125,7 +12452,7 @@ function parseCreditBalances(payload, consumed = []) {
12125
12452
  /** Resolve the plan id the subscription or credits payload reports, when either does. */
12126
12453
  function parsePlanId(payloads) {
12127
12454
  for (const payload of payloads) {
12128
- const id = asString$15(deepValue(payload, [
12455
+ const id = asString$16(deepValue(payload, [
12129
12456
  "planId",
12130
12457
  "plan_id",
12131
12458
  "priceId",
@@ -12144,7 +12471,7 @@ function parsePlanId(payloads) {
12144
12471
  */
12145
12472
  function parseSubscriptionStatus(payload) {
12146
12473
  const root = asRecord$9(payload) ?? {};
12147
- return asString$15(deepValue(asRecord$9(root.data) ?? root, ["status"])) ?? null;
12474
+ return asString$16(deepValue(asRecord$9(root.data) ?? root, ["status"])) ?? null;
12148
12475
  }
12149
12476
  /** End of the current billing period, in Unix milliseconds. */
12150
12477
  function parseSubscriptionPeriodEnd(payload) {
@@ -12553,7 +12880,41 @@ function whoami(apiKey, options = {}) {
12553
12880
  async function verifyApiKey(apiKey, options = {}) {
12554
12881
  return parseWhoami(await whoami(apiKey, options), { authenticatedAt: Date.now() });
12555
12882
  }
12556
- let cachedQuotaEntry;
12883
+ /**
12884
+ * One newest snapshot PER ACCOUNT.
12885
+ *
12886
+ * Quota follows the account: rotation decides which key spends the next
12887
+ * request, so a single slot only ever described whichever key was read last.
12888
+ * The pool's card draws every account its own reading, so each account keeps
12889
+ * its own entry; past the cap the oldest read is evicted.
12890
+ *
12891
+ * This map is a memory read only. Nothing here fetches — the settings card
12892
+ * builds its account list from it, and that must never cost one upstream
12893
+ * request per pooled key.
12894
+ */
12895
+ const cachedQuotaEntries$1 = /* @__PURE__ */ new Map();
12896
+ /** How many accounts' snapshots are remembered; mirrors the Codex usage service. */
12897
+ const QUOTA_CACHE_LIMIT$1 = 20;
12898
+ /** Key of a snapshot whose caller named no account. */
12899
+ const UNNAMED_ACCOUNT_KEY$1 = "";
12900
+ function quotaCacheKey(accountId) {
12901
+ return accountId ?? UNNAMED_ACCOUNT_KEY$1;
12902
+ }
12903
+ /** The entry read most recently, whichever account it belongs to. */
12904
+ function newestQuotaEntry$2() {
12905
+ let newest;
12906
+ for (const entry of cachedQuotaEntries$1.values()) if (newest === void 0 || entry.fetchedAt > newest.fetchedAt) newest = entry;
12907
+ return newest;
12908
+ }
12909
+ /** Remember one account's newest snapshot, bounded to the accounts in use. */
12910
+ function rememberQuotaEntry(entry) {
12911
+ cachedQuotaEntries$1.set(quotaCacheKey(entry.accountId), entry);
12912
+ while (cachedQuotaEntries$1.size > QUOTA_CACHE_LIMIT$1) {
12913
+ const oldest = [...cachedQuotaEntries$1.entries()].sort((left, right) => left[1].fetchedAt - right[1].fetchedAt)[0];
12914
+ if (oldest === void 0) break;
12915
+ cachedQuotaEntries$1.delete(oldest[0]);
12916
+ }
12917
+ }
12557
12918
  let quotaInFlight$2 = null;
12558
12919
  let quotaInFlightAccountId$1;
12559
12920
  let quotaCacheEpoch$1 = 0;
@@ -12562,21 +12923,22 @@ let quotaCacheEpoch$1 = 0;
12562
12923
  * Kept with a no-argument signature for backward compatibility.
12563
12924
  */
12564
12925
  function getCachedQuota$2() {
12565
- return cachedQuotaEntry?.quota;
12926
+ return newestQuotaEntry$2()?.quota;
12566
12927
  }
12567
12928
  /**
12568
12929
  * The cached snapshot only when it belongs to this account.
12569
12930
  *
12570
12931
  * Passing no account keeps the pre-pool behavior of returning whatever was
12571
- * fetched last.
12932
+ * fetched last. This is the lookup the account pool calls while it builds its
12933
+ * summaries: a pure memory read, never a fetch.
12572
12934
  */
12573
- function getCachedQuotaFor$1(accountId) {
12574
- if (accountId === void 0 || accountId === null) return cachedQuotaEntry?.quota;
12575
- return cachedQuotaEntry?.accountId === accountId ? cachedQuotaEntry.quota : void 0;
12935
+ function getCachedQuotaFor$3(accountId) {
12936
+ if (accountId === void 0 || accountId === null) return newestQuotaEntry$2()?.quota;
12937
+ return cachedQuotaEntries$1.get(accountId)?.quota;
12576
12938
  }
12577
12939
  function clearCachedQuota$2() {
12578
12940
  quotaCacheEpoch$1 += 1;
12579
- cachedQuotaEntry = void 0;
12941
+ cachedQuotaEntries$1.clear();
12580
12942
  quotaInFlight$2 = null;
12581
12943
  quotaInFlightAccountId$1 = void 0;
12582
12944
  }
@@ -12591,8 +12953,8 @@ async function fetchAccountQuota$2(store = new FileCredentialStore$1(), fetchFnO
12591
12953
  const fetchFn = typeof fetchFnOrOptions === "function" ? fetchFnOrOptions : fetchFnOrOptions?.fetchFn ?? fetch;
12592
12954
  const effectiveForce = isOptionsObj ? fetchFnOrOptions.force ?? force : force;
12593
12955
  const effectiveAccountId = isOptionsObj ? fetchFnOrOptions.accountId ?? accountId : accountId;
12594
- const sameAccount = effectiveAccountId === void 0 || cachedQuotaEntry?.accountId === effectiveAccountId;
12595
- if (!effectiveForce && sameAccount && cachedQuotaEntry && Date.now() - cachedQuotaEntry.fetchedAt < 12e4) return cachedQuotaEntry.quota;
12956
+ const cached = getCachedQuotaFor$3(effectiveAccountId);
12957
+ if (!effectiveForce && cached !== void 0 && Date.now() - (cached.fetchedAt || 0) < 12e4) return cached;
12596
12958
  if (quotaInFlight$2 !== null && (effectiveAccountId === void 0 || quotaInFlightAccountId$1 === effectiveAccountId)) return quotaInFlight$2;
12597
12959
  const epoch = quotaCacheEpoch$1;
12598
12960
  const request = (async () => {
@@ -12648,11 +13010,11 @@ async function fetchAccountQuota$2(store = new FileCredentialStore$1(), fetchFnO
12648
13010
  sources
12649
13011
  };
12650
13012
  if (epoch !== quotaCacheEpoch$1) return snapshot;
12651
- cachedQuotaEntry = {
13013
+ rememberQuotaEntry({
12652
13014
  accountId: effectiveAccountId,
12653
13015
  quota: snapshot,
12654
13016
  fetchedAt: snapshot.fetchedAt
12655
- };
13017
+ });
12656
13018
  return snapshot;
12657
13019
  })();
12658
13020
  quotaInFlight$2 = request;
@@ -12696,10 +13058,10 @@ function buildModelOptions$2(catalog, enabledModelIds, overrides) {
12696
13058
  *
12697
13059
  * https://commandcode.ai/blog/command-code-provider-api
12698
13060
  */
12699
- function isRecord$24(value) {
13061
+ function isRecord$25(value) {
12700
13062
  return typeof value === "object" && value !== null && !Array.isArray(value);
12701
13063
  }
12702
- function asString$14(value) {
13064
+ function asString$15(value) {
12703
13065
  return typeof value === "string" ? value : void 0;
12704
13066
  }
12705
13067
  function safeJsonParse$4(text) {
@@ -12736,17 +13098,17 @@ const SUPPORTED_IMAGE_MEDIA_TYPES$4 = /* @__PURE__ */ new Set([
12736
13098
  ]);
12737
13099
  function attachmentOf$4(block) {
12738
13100
  const attachment = block.attachment;
12739
- if (!isRecord$24(attachment)) return void 0;
13101
+ if (!isRecord$25(attachment)) return void 0;
12740
13102
  return typeof attachment.attachmentId === "string" ? attachment : void 0;
12741
13103
  }
12742
13104
  function attachmentLabel$5(block) {
12743
- const attachment = isRecord$24(block.attachment) ? block.attachment : void 0;
12744
- return asString$14(attachment?.name) || asString$14(attachment?.attachmentId);
13105
+ const attachment = isRecord$25(block.attachment) ? block.attachment : void 0;
13106
+ return asString$15(attachment?.name) || asString$15(attachment?.attachmentId);
12745
13107
  }
12746
13108
  function collectImageRefs$3(content, refs) {
12747
13109
  if (!Array.isArray(content)) return;
12748
13110
  for (const block of content) {
12749
- if (!isRecord$24(block)) continue;
13111
+ if (!isRecord$25(block)) continue;
12750
13112
  if (block.type === "image") {
12751
13113
  const attachment = attachmentOf$4(block);
12752
13114
  if (attachment) refs.set(attachment.attachmentId, attachment);
@@ -12761,13 +13123,13 @@ function base64Length$3(bytes) {
12761
13123
  function requestImageBytes$3(block) {
12762
13124
  const attachment = attachmentOf$4(block);
12763
13125
  if (attachment) return base64Length$3(attachment.bytes);
12764
- const inline = asString$14(block.data) || asString$14(block.base64);
13126
+ const inline = asString$15(block.data) || asString$15(block.base64);
12765
13127
  return inline ? inline.length : void 0;
12766
13128
  }
12767
13129
  function collectRequestImageBytes$3(content, lengths) {
12768
13130
  if (!Array.isArray(content)) return;
12769
13131
  for (const block of content) {
12770
- if (!isRecord$24(block) || block.type !== "image") continue;
13132
+ if (!isRecord$25(block) || block.type !== "image") continue;
12771
13133
  const bytes = requestImageBytes$3(block);
12772
13134
  if (bytes !== void 0) lengths.push(bytes);
12773
13135
  }
@@ -12794,7 +13156,7 @@ function offloadOldestRequestImages$3(options) {
12794
13156
  if (remaining.count === 0 || !Array.isArray(message.content)) return message;
12795
13157
  let replaced = false;
12796
13158
  const content = message.content.map((block) => {
12797
- if (remaining.count === 0 || !isRecord$24(block) || block.type !== "image") return block;
13159
+ if (remaining.count === 0 || !isRecord$25(block) || block.type !== "image") return block;
12798
13160
  if (requestImageBytes$3(block) === void 0) return block;
12799
13161
  remaining.count -= 1;
12800
13162
  replaced = true;
@@ -12847,10 +13209,10 @@ function unavailableImageText$5(block) {
12847
13209
  return `[image unavailable: ${label ? `${label} could not be read` : "the image could not be read"}; ask the user to attach it again if the image is needed]`;
12848
13210
  }
12849
13211
  function imageBlockToInline$3(block, images) {
12850
- let data = asString$14(block.data) || asString$14(block.base64);
12851
- const source = isRecord$24(block.source) ? block.source : void 0;
12852
- if (!data && source) data = asString$14(source.data) || asString$14(source.base64);
12853
- let mediaType = asString$14(block.mimeType) || asString$14(block.mediaType) || (source ? asString$14(source.mimeType) || asString$14(source.mediaType) : void 0) || "image/png";
13212
+ let data = asString$15(block.data) || asString$15(block.base64);
13213
+ const source = isRecord$25(block.source) ? block.source : void 0;
13214
+ if (!data && source) data = asString$15(source.data) || asString$15(source.base64);
13215
+ let mediaType = asString$15(block.mimeType) || asString$15(block.mediaType) || (source ? asString$15(source.mimeType) || asString$15(source.mediaType) : void 0) || "image/png";
12854
13216
  if (data?.startsWith("data:")) {
12855
13217
  const matched = data.match(/^data:([^;,]+);base64,(.*)$/s);
12856
13218
  if (matched) {
@@ -12874,7 +13236,7 @@ function textOf$5(content) {
12874
13236
  if (!Array.isArray(content)) return "";
12875
13237
  const parts = [];
12876
13238
  for (const block of content) {
12877
- if (!isRecord$24(block)) continue;
13239
+ if (!isRecord$25(block)) continue;
12878
13240
  if (block.type === "text" && typeof block.text === "string") parts.push(sanitizeText$4(block.text));
12879
13241
  else if (block.type === "tool-result") parts.push(textOf$5(block.content));
12880
13242
  }
@@ -12883,7 +13245,7 @@ function textOf$5(content) {
12883
13245
  function toolResultText$4(blocks) {
12884
13246
  if (!Array.isArray(blocks)) return "";
12885
13247
  return blocks.map((block) => {
12886
- if (!isRecord$24(block)) return "";
13248
+ if (!isRecord$25(block)) return "";
12887
13249
  if (block.type === "text" && typeof block.text === "string") return sanitizeText$4(block.text);
12888
13250
  if (block.type === "tool-result") return toolResultText$4(block.content);
12889
13251
  if (block.type === "image") return `[image: ${attachmentLabel$5(block) ?? "attached image"}]`;
@@ -12901,7 +13263,7 @@ function toolResultBlocks$2(blocks, images) {
12901
13263
  const out = [];
12902
13264
  let hasImage = false;
12903
13265
  for (const block of blocks) {
12904
- if (!isRecord$24(block)) continue;
13266
+ if (!isRecord$25(block)) continue;
12905
13267
  if (block.type === "text" && typeof block.text === "string") {
12906
13268
  out.push({
12907
13269
  type: "text",
@@ -12950,7 +13312,7 @@ function toolResultImageBlocks$2(blocks, images) {
12950
13312
  if (!Array.isArray(blocks)) return [];
12951
13313
  const out = [];
12952
13314
  for (const block of blocks) {
12953
- if (!isRecord$24(block)) continue;
13315
+ if (!isRecord$25(block)) continue;
12954
13316
  if (block.type === "image") {
12955
13317
  const inline = imageBlockToInline$3(block, images);
12956
13318
  if (inline && SUPPORTED_IMAGE_MEDIA_TYPES$4.has(inline.mediaType)) out.push({
@@ -12994,7 +13356,7 @@ function nonSystemMessages$4(options) {
12994
13356
  }
12995
13357
  /** Drop the JSON-Schema keywords provider gateways reject or ignore. */
12996
13358
  function stripMetaSchema$2(schema) {
12997
- if (!isRecord$24(schema)) return {
13359
+ if (!isRecord$25(schema)) return {
12998
13360
  type: "object",
12999
13361
  properties: {}
13000
13362
  };
@@ -13011,7 +13373,7 @@ function openAIUserContent$2(message, images) {
13011
13373
  const parts = [];
13012
13374
  let hasImage = false;
13013
13375
  for (const block of message.content) {
13014
- if (!isRecord$24(block)) continue;
13376
+ if (!isRecord$25(block)) continue;
13015
13377
  if (block.type === "text" && typeof block.text === "string") {
13016
13378
  const text = sanitizeText$4(block.text);
13017
13379
  if (text !== "") parts.push({
@@ -13039,7 +13401,7 @@ function openAIAssistantContent$2(message) {
13039
13401
  const textParts = [];
13040
13402
  const toolCalls = [];
13041
13403
  for (const block of message.content) {
13042
- if (!isRecord$24(block)) continue;
13404
+ if (!isRecord$25(block)) continue;
13043
13405
  if (block.type === "text" && typeof block.text === "string") textParts.push(sanitizeText$4(block.text));
13044
13406
  else if (block.type === "tool-call" && typeof block.name === "string") toolCalls.push({
13045
13407
  id: typeof block.id === "string" && block.id !== "" ? block.id : `call_${toolCalls.length}`,
@@ -13071,7 +13433,7 @@ function buildOpenAIRequest$1(options, images = NO_RESOLVED_IMAGES$4) {
13071
13433
  while (index < conversation.length && isToolResultMessage$4(conversation[index])) {
13072
13434
  const current = conversation[index];
13073
13435
  const block = current.content[0];
13074
- const callId = isRecord$24(block) && typeof block.toolCallId === "string" ? block.toolCallId : "";
13436
+ const callId = isRecord$25(block) && typeof block.toolCallId === "string" ? block.toolCallId : "";
13075
13437
  messages.push({
13076
13438
  role: "tool",
13077
13439
  tool_call_id: callId,
@@ -13132,7 +13494,7 @@ function anthropicUserContent$2(message, images) {
13132
13494
  if (!Array.isArray(message.content)) return [];
13133
13495
  const blocks = [];
13134
13496
  for (const block of message.content) {
13135
- if (!isRecord$24(block)) continue;
13497
+ if (!isRecord$25(block)) continue;
13136
13498
  if (block.type === "text" && typeof block.text === "string") {
13137
13499
  const text = sanitizeText$4(block.text);
13138
13500
  if (text !== "") blocks.push({
@@ -13169,7 +13531,7 @@ function anthropicUserContent$2(message, images) {
13169
13531
  function anthropicAssistantContent$2(message) {
13170
13532
  const blocks = [];
13171
13533
  for (const block of message.content) {
13172
- if (!isRecord$24(block)) continue;
13534
+ if (!isRecord$25(block)) continue;
13173
13535
  if (block.type === "text" && typeof block.text === "string") {
13174
13536
  const text = sanitizeText$4(block.text);
13175
13537
  if (text !== "") blocks.push({
@@ -13182,7 +13544,7 @@ function anthropicAssistantContent$2(message) {
13182
13544
  type: "tool_use",
13183
13545
  id: typeof block.id === "string" && block.id !== "" ? block.id : `toolu_${blocks.length}`,
13184
13546
  name: block.name,
13185
- input: isRecord$24(parsed) ? parsed : {}
13547
+ input: isRecord$25(parsed) ? parsed : {}
13186
13548
  });
13187
13549
  }
13188
13550
  }
@@ -13276,7 +13638,7 @@ function responsesUserContent(message, images) {
13276
13638
  if (!Array.isArray(message.content)) return [];
13277
13639
  const parts = [];
13278
13640
  for (const block of message.content) {
13279
- if (!isRecord$24(block)) continue;
13641
+ if (!isRecord$25(block)) continue;
13280
13642
  if (block.type === "text" && typeof block.text === "string") {
13281
13643
  const text = sanitizeText$4(block.text);
13282
13644
  if (text !== "") parts.push({
@@ -13301,7 +13663,7 @@ function responsesToolResultImageBlocks(blocks, images) {
13301
13663
  if (!Array.isArray(blocks)) return [];
13302
13664
  const out = [];
13303
13665
  for (const block of blocks) {
13304
- if (!isRecord$24(block)) continue;
13666
+ if (!isRecord$25(block)) continue;
13305
13667
  if (block.type === "image") {
13306
13668
  const inline = imageBlockToInline$3(block, images);
13307
13669
  if (inline && SUPPORTED_IMAGE_MEDIA_TYPES$4.has(inline.mediaType)) out.push({
@@ -13328,8 +13690,8 @@ function buildResponsesRequest(options, images = NO_RESOLVED_IMAGES$4) {
13328
13690
  const imageBlocks = [];
13329
13691
  while (index < conversation.length && isToolResultMessage$4(conversation[index])) {
13330
13692
  const current = conversation[index];
13331
- const toolResult = Array.isArray(current.content) ? current.content.find((b) => isRecord$24(b) && b.type === "tool-result") : void 0;
13332
- const callId = (isRecord$24(toolResult) && typeof toolResult.toolCallId === "string" ? toolResult.toolCallId : "") || (typeof current.source?.callId === "string" ? current.source.callId : "");
13693
+ const toolResult = Array.isArray(current.content) ? current.content.find((b) => isRecord$25(b) && b.type === "tool-result") : void 0;
13694
+ const callId = (isRecord$25(toolResult) && typeof toolResult.toolCallId === "string" ? toolResult.toolCallId : "") || (typeof current.source?.callId === "string" ? current.source.callId : "");
13333
13695
  input.push({
13334
13696
  type: "function_call_output",
13335
13697
  call_id: callId,
@@ -13352,7 +13714,7 @@ function buildResponsesRequest(options, images = NO_RESOLVED_IMAGES$4) {
13352
13714
  content
13353
13715
  });
13354
13716
  for (const call of toolCalls) {
13355
- const fn = isRecord$24(call.function) ? call.function : void 0;
13717
+ const fn = isRecord$25(call.function) ? call.function : void 0;
13356
13718
  input.push({
13357
13719
  type: "function_call",
13358
13720
  call_id: call.id,
@@ -13475,25 +13837,33 @@ function processOpenAIStreamLine$1(line, state) {
13475
13837
  }
13476
13838
  if (payload === "") return [];
13477
13839
  const chunk = safeJsonParse$4(payload);
13478
- if (!isRecord$24(chunk)) return [];
13840
+ if (!isRecord$25(chunk)) return [];
13479
13841
  const out = [];
13480
- if (isRecord$24(chunk.error)) throw new LlmError$1(`Command Code stream error: ${asString$14(chunk.error.message) ?? "unknown error"}`, isContextOverflow(chunk.error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
13481
- const usage = isRecord$24(chunk.usage) ? chunk.usage : void 0;
13842
+ if (isRecord$25(chunk.error)) {
13843
+ const message = asString$15(chunk.error.message) ?? "unknown error";
13844
+ state.streamError = {
13845
+ vocabulary: "openai",
13846
+ error: chunk.error,
13847
+ message
13848
+ };
13849
+ throw new LlmError$1(`Command Code stream error: ${message}`, isContextOverflow(chunk.error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
13850
+ }
13851
+ const usage = isRecord$25(chunk.usage) ? chunk.usage : void 0;
13482
13852
  if (usage) {
13483
13853
  state.sawUsage = true;
13484
13854
  const prompt = numberOr$5(usage.prompt_tokens, 0);
13485
- const details = isRecord$24(usage.prompt_tokens_details) ? usage.prompt_tokens_details : void 0;
13855
+ const details = isRecord$25(usage.prompt_tokens_details) ? usage.prompt_tokens_details : void 0;
13486
13856
  const cached = details ? numberOr$5(details.cached_tokens, 0) : 0;
13487
13857
  state.inputTokens = Math.max(0, prompt - cached);
13488
13858
  state.cacheReadTokens = cached;
13489
13859
  state.outputTokens = numberOr$5(usage.completion_tokens, state.outputTokens);
13490
- if (isRecord$24(usage.completion_tokens_details)) state.reasoningTokens = numberOr$5(usage.completion_tokens_details.reasoning_tokens, state.reasoningTokens);
13860
+ if (isRecord$25(usage.completion_tokens_details)) state.reasoningTokens = numberOr$5(usage.completion_tokens_details.reasoning_tokens, state.reasoningTokens);
13491
13861
  }
13492
13862
  const choices = Array.isArray(chunk.choices) ? chunk.choices : [];
13493
- const choice = isRecord$24(choices[0]) ? choices[0] : void 0;
13494
- const delta = isRecord$24(choice?.delta) ? choice.delta : void 0;
13863
+ const choice = isRecord$25(choices[0]) ? choices[0] : void 0;
13864
+ const delta = isRecord$25(choice?.delta) ? choice.delta : void 0;
13495
13865
  if (delta) {
13496
- const reasoning = asString$14(delta.reasoning_content) ?? asString$14(delta.reasoning);
13866
+ const reasoning = asString$15(delta.reasoning_content) ?? asString$15(delta.reasoning);
13497
13867
  if (reasoning !== void 0 && reasoning !== "") {
13498
13868
  out.push(...closeToolCalls$4(state));
13499
13869
  if (state.current === null || state.current.type !== "reasoning") out.push(...openTextBlock$3(state, "reasoning"));
@@ -13505,7 +13875,7 @@ function processOpenAIStreamLine$1(line, state) {
13505
13875
  text: sanitizeText$4(reasoning)
13506
13876
  });
13507
13877
  }
13508
- const content = asString$14(delta.content);
13878
+ const content = asString$15(delta.content);
13509
13879
  if (content !== void 0 && content !== "") {
13510
13880
  out.push(...closeToolCalls$4(state));
13511
13881
  if (state.current === null || state.current.type !== "text") out.push(...openTextBlock$3(state, "text"));
@@ -13519,11 +13889,11 @@ function processOpenAIStreamLine$1(line, state) {
13519
13889
  }
13520
13890
  const toolDeltas = Array.isArray(delta.tool_calls) ? delta.tool_calls : [];
13521
13891
  for (const entry of toolDeltas) {
13522
- if (!isRecord$24(entry)) continue;
13892
+ if (!isRecord$25(entry)) continue;
13523
13893
  out.push(...applyOpenAIToolDelta$1(entry, state));
13524
13894
  }
13525
13895
  }
13526
- const finish = asString$14(choice?.finish_reason);
13896
+ const finish = asString$15(choice?.finish_reason);
13527
13897
  if (finish !== void 0 && finish !== "") {
13528
13898
  state.finishReason = finish;
13529
13899
  out.push(...closeCurrent$3(state));
@@ -13533,15 +13903,15 @@ function processOpenAIStreamLine$1(line, state) {
13533
13903
  }
13534
13904
  function applyOpenAIToolDelta$1(entry, state) {
13535
13905
  const wireIndex = typeof entry.index === "number" ? entry.index : 0;
13536
- const fn = isRecord$24(entry.function) ? entry.function : {};
13906
+ const fn = isRecord$25(entry.function) ? entry.function : {};
13537
13907
  const out = [];
13538
13908
  let call = state.toolCalls.get(wireIndex);
13539
13909
  if (call === void 0) {
13540
13910
  out.push(...closeCurrent$3(state));
13541
13911
  call = {
13542
13912
  blockIndex: state.blocks.length,
13543
- id: asString$14(entry.id) ?? `call_${wireIndex}`,
13544
- name: asString$14(fn.name) ?? "",
13913
+ id: asString$15(entry.id) ?? `call_${wireIndex}`,
13914
+ name: asString$15(fn.name) ?? "",
13545
13915
  arguments: "",
13546
13916
  started: false
13547
13917
  };
@@ -13554,13 +13924,13 @@ function applyOpenAIToolDelta$1(entry, state) {
13554
13924
  state.toolCalls.set(wireIndex, call);
13555
13925
  } else {
13556
13926
  if (call.id === `call_${wireIndex}`) {
13557
- const id = asString$14(entry.id);
13927
+ const id = asString$15(entry.id);
13558
13928
  if (id !== void 0) call.id = id;
13559
13929
  }
13560
- const name = asString$14(fn.name);
13930
+ const name = asString$15(fn.name);
13561
13931
  if (name !== void 0 && name !== "") call.name = name;
13562
13932
  }
13563
- const argsDelta = asString$14(fn.arguments) ?? "";
13933
+ const argsDelta = asString$15(fn.arguments) ?? "";
13564
13934
  if (argsDelta !== "") call.arguments += argsDelta;
13565
13935
  if (!call.started) {
13566
13936
  call.started = true;
@@ -13591,12 +13961,12 @@ function processAnthropicStreamLine$1(line, state) {
13591
13961
  const payload = trimmed.slice(5).trim();
13592
13962
  if (payload === "" || payload === "[DONE]") return [];
13593
13963
  const event = safeJsonParse$4(payload);
13594
- if (!isRecord$24(event)) return [];
13595
- const type = asString$14(event.type);
13964
+ if (!isRecord$25(event)) return [];
13965
+ const type = asString$15(event.type);
13596
13966
  const out = [];
13597
13967
  if (type === "message_start") {
13598
- const message = isRecord$24(event.message) ? event.message : void 0;
13599
- const usage = message && isRecord$24(message.usage) ? message.usage : void 0;
13968
+ const message = isRecord$25(event.message) ? event.message : void 0;
13969
+ const usage = message && isRecord$25(message.usage) ? message.usage : void 0;
13600
13970
  if (usage) {
13601
13971
  state.sawUsage = true;
13602
13972
  state.inputTokens = numberOr$5(usage.input_tokens, 0);
@@ -13604,21 +13974,21 @@ function processAnthropicStreamLine$1(line, state) {
13604
13974
  state.cacheWriteTokens = numberOr$5(usage.cache_creation_input_tokens, 0);
13605
13975
  state.outputTokens = numberOr$5(usage.output_tokens, 0);
13606
13976
  }
13607
- const stop = message ? asString$14(message.stop_reason) : void 0;
13977
+ const stop = message ? asString$15(message.stop_reason) : void 0;
13608
13978
  if (stop !== void 0 && stop !== null) state.finishReason = stop;
13609
13979
  return out;
13610
13980
  }
13611
13981
  if (type === "content_block_start") {
13612
13982
  const contentIndex = numberOr$5(event.index, 0);
13613
- const block = isRecord$24(event.content_block) ? event.content_block : {};
13614
- const blockType = asString$14(block.type);
13983
+ const block = isRecord$25(event.content_block) ? event.content_block : {};
13984
+ const blockType = asString$15(block.type);
13615
13985
  out.push(...closeCurrent$3(state));
13616
13986
  if (blockType === "tool_use") {
13617
13987
  const index = state.blocks.length;
13618
13988
  const pending = {
13619
13989
  blockIndex: index,
13620
- id: asString$14(block.id) ?? `toolu_${contentIndex}`,
13621
- name: asString$14(block.name) ?? "",
13990
+ id: asString$15(block.id) ?? `toolu_${contentIndex}`,
13991
+ name: asString$15(block.name) ?? "",
13622
13992
  arguments: "",
13623
13993
  started: true
13624
13994
  };
@@ -13660,11 +14030,11 @@ function processAnthropicStreamLine$1(line, state) {
13660
14030
  }
13661
14031
  if (type === "content_block_delta") {
13662
14032
  const contentIndex = numberOr$5(event.index, 0);
13663
- const delta = isRecord$24(event.delta) ? event.delta : {};
13664
- const deltaType = asString$14(delta.type);
14033
+ const delta = isRecord$25(event.delta) ? event.delta : {};
14034
+ const deltaType = asString$15(delta.type);
13665
14035
  if (deltaType === "input_json_delta") {
13666
14036
  const pending = state.toolCalls.get(contentIndex);
13667
- const partial = asString$14(delta.partial_json) ?? "";
14037
+ const partial = asString$15(delta.partial_json) ?? "";
13668
14038
  if (pending !== void 0) {
13669
14039
  pending.arguments += partial;
13670
14040
  out.push({
@@ -13677,7 +14047,7 @@ function processAnthropicStreamLine$1(line, state) {
13677
14047
  }
13678
14048
  return out;
13679
14049
  }
13680
- const text = deltaType === "thinking_delta" ? asString$14(delta.thinking) : asString$14(delta.text);
14050
+ const text = deltaType === "thinking_delta" ? asString$15(delta.thinking) : asString$15(delta.text);
13681
14051
  if (text !== void 0 && text !== "") {
13682
14052
  const index = state.contentIndexes.get(contentIndex) ?? state.current?.index;
13683
14053
  const kind = deltaType === "thinking_delta" ? "reasoning" : "text";
@@ -13733,10 +14103,10 @@ function processAnthropicStreamLine$1(line, state) {
13733
14103
  return out;
13734
14104
  }
13735
14105
  if (type === "message_delta") {
13736
- const delta = isRecord$24(event.delta) ? event.delta : void 0;
13737
- const stop = delta ? asString$14(delta.stop_reason) : void 0;
14106
+ const delta = isRecord$25(event.delta) ? event.delta : void 0;
14107
+ const stop = delta ? asString$15(delta.stop_reason) : void 0;
13738
14108
  if (stop !== void 0 && stop !== "") state.finishReason = stop;
13739
- const usage = isRecord$24(event.usage) ? event.usage : void 0;
14109
+ const usage = isRecord$25(event.usage) ? event.usage : void 0;
13740
14110
  if (usage) {
13741
14111
  state.sawUsage = true;
13742
14112
  state.outputTokens = numberOr$5(usage.output_tokens, state.outputTokens);
@@ -13748,8 +14118,14 @@ function processAnthropicStreamLine$1(line, state) {
13748
14118
  return closeStream$4(state);
13749
14119
  }
13750
14120
  if (type === "error") {
13751
- const error = isRecord$24(event.error) ? event.error : {};
13752
- throw new LlmError$1(`Command Code stream error: ${asString$14(error.message) ?? "unknown error"}`, isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
14121
+ const error = isRecord$25(event.error) ? event.error : {};
14122
+ const message = asString$15(error.message) ?? "unknown error";
14123
+ state.streamError = {
14124
+ vocabulary: "anthropic",
14125
+ error,
14126
+ message
14127
+ };
14128
+ throw new LlmError$1(`Command Code stream error: ${message}`, isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
13753
14129
  }
13754
14130
  return out;
13755
14131
  }
@@ -13788,7 +14164,7 @@ function processResponsesStreamLine(line, state) {
13788
14164
  } catch {
13789
14165
  return [];
13790
14166
  }
13791
- if (!isRecord$24(parsed)) return [];
14167
+ if (!isRecord$25(parsed)) return [];
13792
14168
  const type = typeof parsed.type === "string" ? parsed.type : "";
13793
14169
  if (type === "response.output_text.delta") {
13794
14170
  const delta = typeof parsed.delta === "string" ? parsed.delta : "";
@@ -13815,7 +14191,7 @@ function processResponsesStreamLine(line, state) {
13815
14191
  }];
13816
14192
  }
13817
14193
  if (type === "response.output_item.added") {
13818
- const item = isRecord$24(parsed.item) ? parsed.item : null;
14194
+ const item = isRecord$25(parsed.item) ? parsed.item : null;
13819
14195
  if (item === null || item.type !== "function_call") return [];
13820
14196
  return startResponsesToolCall(state, parsed, item);
13821
14197
  }
@@ -13839,19 +14215,24 @@ function processResponsesStreamLine(line, state) {
13839
14215
  return [];
13840
14216
  }
13841
14217
  if (type === "response.completed" || type === "response.incomplete") {
13842
- const response = isRecord$24(parsed.response) ? parsed.response : null;
14218
+ const response = isRecord$25(parsed.response) ? parsed.response : null;
13843
14219
  if (response !== null) {
13844
14220
  if (response.status === "failed") {
13845
14221
  state.done = true;
13846
- const errorObj = isRecord$24(response.error) ? response.error : null;
14222
+ const errorObj = isRecord$25(response.error) ? response.error : null;
13847
14223
  const detail = errorObj && typeof errorObj.message === "string" ? errorObj.message : typeof response.error === "string" ? response.error : "";
14224
+ state.streamError = {
14225
+ vocabulary: "responses",
14226
+ error: errorObj,
14227
+ message: detail
14228
+ };
13848
14229
  throw new LlmError$1(detail === "" ? "Command Code responses stream failed" : `Command Code responses stream failed: ${detail}`, "PROVIDER_ERROR");
13849
14230
  }
13850
14231
  readResponsesUsage(response, state);
13851
14232
  }
13852
14233
  state.done = true;
13853
14234
  if (type === "response.incomplete") {
13854
- const details = response && isRecord$24(response.incomplete_details) ? response.incomplete_details : null;
14235
+ const details = response && isRecord$25(response.incomplete_details) ? response.incomplete_details : null;
13855
14236
  const reason = details && typeof details.reason === "string" ? details.reason : "";
13856
14237
  state.finishReason = reason === "max_output_tokens" || reason === "length" || reason === "max_tokens" ? "length" : reason || "length";
13857
14238
  } else state.finishReason = "stop";
@@ -13859,8 +14240,13 @@ function processResponsesStreamLine(line, state) {
13859
14240
  }
13860
14241
  if (type === "response.failed" || type === "error") {
13861
14242
  state.done = true;
13862
- const errorObj = isRecord$24(parsed.error) ? parsed.error : isRecord$24(parsed.response) && isRecord$24(parsed.response.error) ? parsed.response.error : null;
13863
- const detail = (typeof parsed.message === "string" ? parsed.message : "") || (errorObj && typeof errorObj.message === "string" ? errorObj.message : "") || (isRecord$24(parsed.response) && typeof parsed.response.error === "string" ? parsed.response.error : typeof parsed.error === "string" ? parsed.error : "");
14243
+ const errorObj = isRecord$25(parsed.error) ? parsed.error : isRecord$25(parsed.response) && isRecord$25(parsed.response.error) ? parsed.response.error : null;
14244
+ const detail = (typeof parsed.message === "string" ? parsed.message : "") || (errorObj && typeof errorObj.message === "string" ? errorObj.message : "") || (isRecord$25(parsed.response) && typeof parsed.response.error === "string" ? parsed.response.error : typeof parsed.error === "string" ? parsed.error : "");
14245
+ state.streamError = {
14246
+ vocabulary: "responses",
14247
+ error: errorObj,
14248
+ message: detail
14249
+ };
13864
14250
  throw new LlmError$1(detail === "" ? "Command Code responses stream failed" : `Command Code responses stream failed: ${detail}`, isContextOverflow(errorObj ?? detail) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
13865
14251
  }
13866
14252
  return [];
@@ -13943,15 +14329,15 @@ function responsesToolCall(state, key) {
13943
14329
  for (const call of state.toolCalls.values()) if (call.id === toToolCallId(key)) return call;
13944
14330
  }
13945
14331
  function readResponsesUsage(response, state) {
13946
- const usage = isRecord$24(response.usage) ? response.usage : null;
14332
+ const usage = isRecord$25(response.usage) ? response.usage : null;
13947
14333
  if (usage === null) return;
13948
- const cached = isRecord$24(usage.input_tokens_details) ? numberOrZero(usage.input_tokens_details.cached_tokens) : 0;
13949
- const written = isRecord$24(usage.input_tokens_details) ? numberOrZero(usage.input_tokens_details.cache_write_tokens) : 0;
14334
+ const cached = isRecord$25(usage.input_tokens_details) ? numberOrZero(usage.input_tokens_details.cached_tokens) : 0;
14335
+ const written = isRecord$25(usage.input_tokens_details) ? numberOrZero(usage.input_tokens_details.cache_write_tokens) : 0;
13950
14336
  state.inputTokens = Math.max(0, numberOrZero(usage.input_tokens) - cached - written);
13951
14337
  state.cacheReadTokens = cached;
13952
14338
  state.cacheWriteTokens = written;
13953
14339
  state.outputTokens = numberOrZero(usage.output_tokens);
13954
- const outputDetails = isRecord$24(usage.output_tokens_details) ? usage.output_tokens_details : null;
14340
+ const outputDetails = isRecord$25(usage.output_tokens_details) ? usage.output_tokens_details : null;
13955
14341
  if (outputDetails !== null) state.reasoningTokens = numberOrZero(outputDetails.reasoning_tokens);
13956
14342
  state.sawUsage = true;
13957
14343
  }
@@ -13978,6 +14364,136 @@ function assertStreamComplete$4(state) {
13978
14364
  if (!state.done && state.finishReason === null) throw new LlmError$1("Command Code stream ended before its terminal event", "PROVIDER_ERROR");
13979
14365
  }
13980
14366
  //#endregion
14367
+ //#region src/host/common/stream-error.ts
14368
+ /**
14369
+ * A failure the provider reported INSIDE a 200 stream.
14370
+ *
14371
+ * A service can answer 200 and then report the failure in the stream, in the
14372
+ * same `{ error: { type, message } }` envelope a non-2xx body carries. A mapper
14373
+ * has to type that event with one code, and the code that is right in general is
14374
+ * PROVIDER_ERROR - it cannot know whether output has started - which is outside
14375
+ * every retry set. So a transient failure delivered this way ends the turn on
14376
+ * the first try while the identical failure delivered as a status is retried.
14377
+ *
14378
+ * The reclassification itself is per line, because only the adapter knows
14379
+ * whether anything has reached the caller yet, and because every line owns rules
14380
+ * its own HTTP path already owns. What is shared is the question each line asks
14381
+ * its own classifier: what status WOULD this failure have carried?
14382
+ */
14383
+ /**
14384
+ * The status an in-band `error` event stands for, or null for a type this
14385
+ * vocabulary does not name.
14386
+ *
14387
+ * The Anthropic-compatible shims (MiniMax Code, Kimi Code, and Command Code's
14388
+ * messages stream) speak one vocabulary, and each of these types already has a
14389
+ * status: 529 is Anthropic's documented overload, 429 the rate limit, and a 5xx
14390
+ * the server-side failure. Naming the STATUS rather than the verdict is what lets
14391
+ * each line hand the answer to its own classifier, so a rule that line already
14392
+ * owns - a 429 that names an exhausted plan, a credential refusal reported as
14393
+ * 403 - keeps owning it here.
14394
+ *
14395
+ * Null for everything else: a request the provider rejected, a context
14396
+ * overflow, a credential refusal, a type from the future. Null is what keeps
14397
+ * the mapper's own verdict, and that is the safe direction to fail in - a
14398
+ * fallback to some made-up status would file an unknown type as a retryable
14399
+ * server error, which is worse than not retrying at all.
14400
+ */
14401
+ function inBandAnthropicStatus(error) {
14402
+ const type = readErrorType(error);
14403
+ if (type === null) return null;
14404
+ return ANTHROPIC_IN_BAND_STATUS[type] ?? null;
14405
+ }
14406
+ /** The status each transient Anthropic-compatible in-band type stands for. */
14407
+ const ANTHROPIC_IN_BAND_STATUS = {
14408
+ overloaded_error: 529,
14409
+ rate_limit_error: 429,
14410
+ api_error: 500
14411
+ };
14412
+ /**
14413
+ * The DSH code an in-band Responses-vocabulary failure becomes, or null when it
14414
+ * names nothing transient.
14415
+ *
14416
+ * The Responses stream reports failure as `response.failed` or `error`, whose
14417
+ * message is free text and whose structured evidence is split across TWO fields:
14418
+ * this plugin's own routes document `{"error":{"message":"Upstream model
14419
+ * provider is temporarily unavailable. Please try again in a moment.","type":
14420
+ * "server_error"}}` - `type`, not `code` - so reading only `code` would miss
14421
+ * the exact body this exists for, and its message names nothing the heuristic
14422
+ * would catch. Both fields are read, and the message is read too, because a
14423
+ * deployment that says "overloaded" in prose is the whole signal there.
14424
+ *
14425
+ * Anything not recognized keeps the mapper's verdict.
14426
+ */
14427
+ function inBandResponsesCode(error, message) {
14428
+ const fields = isRecord$24(error) ? error : {};
14429
+ const rawCode = (asString$14(fields.code) ?? asString$14(fields.type) ?? "").toLowerCase();
14430
+ const text = message.toLowerCase();
14431
+ if (text.includes("rate limit") || rawCode === "rate_limit" || rawCode === "rate_limit_exceeded") return "RATE_LIMIT";
14432
+ if (text.includes("overload") || text.includes("server error") || rawCode === "server_error" || rawCode === "service_unavailable" || rawCode === "internal_error") return "SERVER";
14433
+ return null;
14434
+ }
14435
+ /**
14436
+ * The error one in-band stream failure becomes, or `thrown` itself when the
14437
+ * line's own verdict stands.
14438
+ *
14439
+ * Call this only while nothing has reached the caller: the whole reason a fresh
14440
+ * request is still free is what makes the retry safe (see module note 1 in the
14441
+ * Claude adapter, which this generalizes). After the first chunk the mapper's
14442
+ * verdict stands and this must not be called at all.
14443
+ *
14444
+ * Only the CODE changes. The message stays the mapper's, which names the wire
14445
+ * type the user saw, and no `status` is attached: the response really was a
14446
+ * 200, and a synthetic one would misreport what the provider said.
14447
+ *
14448
+ * @param thrown - the mapper's own verdict, passed through when it stands.
14449
+ * @param rawError - the event's wire `error` object, from the stream state.
14450
+ * @param classify - this line's own failure classifier, so every rule its HTTP
14451
+ * path owns still owns the verdict reached here.
14452
+ * @param statusFor - the vocabulary's status table; Responses lines pass
14453
+ * {@link inBandResponsesCode} through {@link reclassifyInBandResponsesError}.
14454
+ */
14455
+ function reclassifyInBandError(thrown, rawError, classify, statusFor = inBandAnthropicStatus) {
14456
+ const status = statusFor(rawError);
14457
+ if (status === null) return thrown;
14458
+ const failure = classify(status, JSON.stringify({ error: rawError }));
14459
+ if (!failure.retryable) return thrown;
14460
+ return new LlmError(thrown.message, failure.code, { cause: thrown });
14461
+ }
14462
+ /**
14463
+ * {@link reclassifyInBandError} for a line whose classifier reads a status, fed
14464
+ * by the Responses vocabulary instead of the Anthropic one.
14465
+ *
14466
+ * The transient verdict found here is a QUESTION, not the answer: a
14467
+ * `rate_limit` code says only that the failure is worth retrying, and whether
14468
+ * it actually is - a 429 naming a spent balance is this route's PROVIDER_ERROR,
14469
+ * not a RATE_LIMIT - is a rule the line's own classifier owns. So the answer is
14470
+ * asked of `classify` at the status the verdict stands for, exactly as the
14471
+ * Anthropic sibling does, and the CODE that comes back is the one the HTTP path
14472
+ * would have produced for the same body. That agreement is the whole point: the
14473
+ * in-band delivery must not be the one delivery with its own opinion.
14474
+ */
14475
+ function reclassifyInBandResponsesError(thrown, rawError, message, classify) {
14476
+ const code = inBandResponsesCode(rawError, message);
14477
+ if (code === null) return thrown;
14478
+ const failure = classify(code === "RATE_LIMIT" ? 429 : 500, JSON.stringify({
14479
+ error: rawError,
14480
+ message
14481
+ }));
14482
+ if (!failure.retryable) return thrown;
14483
+ return new LlmError(thrown.message, failure.code, { cause: thrown });
14484
+ }
14485
+ /** The wire `error` object of an in-band event, read defensively. */
14486
+ function readErrorType(error) {
14487
+ if (!isRecord$24(error)) return null;
14488
+ return typeof error.type === "string" ? error.type : null;
14489
+ }
14490
+ function asString$14(value) {
14491
+ return typeof value === "string" ? value : void 0;
14492
+ }
14493
+ function isRecord$24(value) {
14494
+ return typeof value === "object" && value !== null && !Array.isArray(value);
14495
+ }
14496
+ //#endregion
13981
14497
  //#region src/host/common/capabilities.ts
13982
14498
  /** Error thrown by requireCapability when a capability contract fails. */
13983
14499
  var CapabilityError = class extends Error {
@@ -14253,6 +14769,35 @@ function isCommandCodeCredentialInvalid(status, detail) {
14253
14769
  }
14254
14770
  return false;
14255
14771
  }
14772
+ /**
14773
+ * The verdict one failed response becomes on this route.
14774
+ *
14775
+ * An in-band `error` event is a status the provider chose not to send as a
14776
+ * status, so the chain that answers a non-2xx body has to answer it too rather
14777
+ * than a second, looser one: every rule this route already owns - a model the
14778
+ * plan does not include, a rejected key, a spent quota - keeps owning the
14779
+ * answer, and the codes below are the ONLY place the HTTP path reads them from.
14780
+ *
14781
+ * `retryable` is membership of this route's own policy rather than a second
14782
+ * judgement, because a code is worth repeating exactly when RETRY_POLICY repeats
14783
+ * it; a verdict the policy would not act on must not be filed as transient.
14784
+ *
14785
+ * @param status - the response status, or the status an in-band type stands for.
14786
+ * @param detail - the response body, or the serialized in-band error envelope.
14787
+ */
14788
+ function classifyCommandCodeFailure(status, detail) {
14789
+ let code;
14790
+ if (status === 422) code = "PROVIDER_ERROR";
14791
+ else if (isCommandCodeModelAccessDenied(status, detail)) code = "PROVIDER_ERROR";
14792
+ else if (isCommandCodeCredentialInvalid(status, detail) || status === 401) code = "INVALID_CREDENTIAL";
14793
+ else if (status === 429) code = "RATE_LIMIT";
14794
+ else if (status >= 500) code = "SERVER";
14795
+ else code = isHttpContextOverflow(status, detail) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR";
14796
+ return {
14797
+ code,
14798
+ retryable: RETRY_POLICY$5.mode === "normal" && RETRY_POLICY$5.retryableCodes.includes(code)
14799
+ };
14800
+ }
14256
14801
  /** Cooldown one rate-limited key takes when the provider states no delay. */
14257
14802
  const POOL_COOLDOWN_MS$4 = 15 * 6e4;
14258
14803
  var CommandCodeAdapter = class extends LlmAdapter {
@@ -14447,49 +14992,105 @@ var CommandCodeAdapter = class extends LlmAdapter {
14447
14992
  }
14448
14993
  if (response === void 0 || !response.ok) {
14449
14994
  const status = response?.status ?? 500;
14995
+ const { code } = classifyCommandCodeFailure(status, detail);
14450
14996
  if (status === 422) {
14451
- if (detail.includes("cmd_zdr_no_providers")) throw new LlmError(`${PROVIDER_NAME$5} rejected request under Zero Data Retention: no ZDR-capable upstream is available for this model (${detail || "cmd_zdr_no_providers"}).`, "PROVIDER_ERROR", { status: 422 });
14452
- throw new LlmError(`${PROVIDER_NAME$5} validation error (422): ${detail || "Unprocessable Entity"}`, "PROVIDER_ERROR", { status: 422 });
14997
+ if (detail.includes("cmd_zdr_no_providers")) throw new LlmError(`${PROVIDER_NAME$5} rejected request under Zero Data Retention: no ZDR-capable upstream is available for this model (${detail || "cmd_zdr_no_providers"}).`, code, { status: 422 });
14998
+ throw new LlmError(`${PROVIDER_NAME$5} validation error (422): ${detail || "Unprocessable Entity"}`, code, { status: 422 });
14453
14999
  }
14454
- if (isCommandCodeModelAccessDenied(status, detail)) throw new LlmError(`${PROVIDER_NAME$5} access denied for model ${options.model}: this model is not included in the plan or requires higher entitlement (${status}).${detail ? ` ${detail}` : ""}`, "PROVIDER_ERROR", { status });
14455
- if (isCommandCodeCredentialInvalid(status, detail) || status === 401) throw new LlmError(`${PROVIDER_NAME$5} rejected the stored API key (${status}). Sign in again from Settings > Command Code.${detail ? ` ${detail}` : ""}`, "INVALID_CREDENTIAL", { status });
15000
+ if (isCommandCodeModelAccessDenied(status, detail)) throw new LlmError(`${PROVIDER_NAME$5} access denied for model ${options.model}: this model is not included in the plan or requires higher entitlement (${status}).${detail ? ` ${detail}` : ""}`, code, { status });
15001
+ if (isCommandCodeCredentialInvalid(status, detail) || status === 401) throw new LlmError(`${PROVIDER_NAME$5} rejected the stored API key (${status}). Sign in again from Settings > Command Code.${detail ? ` ${detail}` : ""}`, code, { status });
14456
15002
  if (status === 429) {
14457
15003
  const after = response === void 0 ? void 0 : retryAfterMs$1(response.headers);
14458
- throw new LlmError(`${PROVIDER_NAME$5} rate limit or plan quota reached (429). Check the quota card in Settings > Command Code.${detail ? ` ${detail}` : ""}`, "RATE_LIMIT", {
15004
+ throw new LlmError(`${PROVIDER_NAME$5} rate limit or plan quota reached (429). Check the quota card in Settings > Command Code.${detail ? ` ${detail}` : ""}`, code, {
14459
15005
  status: 429,
14460
15006
  ...after === void 0 ? {} : { providerRetryAfterMs: after }
14461
15007
  });
14462
15008
  }
14463
- if (status >= 500) throw new LlmError(`${PROVIDER_NAME$5} upstream server error (${status}): ${detail || "No response"}`, "SERVER", { status });
14464
- throw new LlmError(`${PROVIDER_NAME$5} API error (${status}): ${detail || "No response"}`, isHttpContextOverflow(status, detail) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR", { status });
15009
+ if (status >= 500) throw new LlmError(`${PROVIDER_NAME$5} upstream server error (${status}): ${detail || "No response"}`, code, { status });
15010
+ throw new LlmError(`${PROVIDER_NAME$5} API error (${status}): ${detail || "No response"}`, code, { status });
14465
15011
  }
14466
15012
  if (response.body === null) throw new LlmError("Command Code returned an empty response body", "PROVIDER_ERROR");
14467
15013
  const reader = response.body.getReader();
14468
15014
  const decoder = new TextDecoder();
14469
15015
  const state = createStreamState$5(wire);
14470
15016
  let buffer = "";
15017
+ /**
15018
+ * Set the moment ANY chunk reaches the caller, and never cleared.
15019
+ *
15020
+ * Deliberately broader than `a text delta or a tool call`: a retry would repeat
15021
+ * a block-start the caller has already seen just as surely, and could re-issue
15022
+ * a tool call the agent has already run, so the conservative reading is the
15023
+ * one that cannot be wrong. It is what keeps the in-band reclassification
15024
+ * below pre-output only, and before that point a fresh request is still free.
15025
+ */
15026
+ let outputStarted = false;
14471
15027
  try {
14472
- while (true) {
14473
- const { done, value } = await reader.read();
14474
- if (done) break;
14475
- buffer += decoder.decode(value, { stream: true });
14476
- const lines = buffer.split("\n");
14477
- buffer = lines.pop() ?? "";
14478
- for (const line of lines) {
14479
- for (const chunk of processLine$1(line, state, wire)) yield chunk;
14480
- if (state.finished) return;
15028
+ try {
15029
+ while (true) {
15030
+ const { done, value } = await reader.read();
15031
+ if (done) break;
15032
+ buffer += decoder.decode(value, { stream: true });
15033
+ const lines = buffer.split("\n");
15034
+ buffer = lines.pop() ?? "";
15035
+ for (const line of lines) {
15036
+ for (const chunk of processLine$1(line, state, wire)) {
15037
+ outputStarted = true;
15038
+ yield chunk;
15039
+ }
15040
+ if (state.finished) return;
15041
+ }
15042
+ }
15043
+ buffer += decoder.decode();
15044
+ if (buffer.trim() !== "") for (const line of buffer.split("\n")) for (const chunk of processLine$1(line, state, wire)) {
15045
+ outputStarted = true;
15046
+ yield chunk;
15047
+ }
15048
+ if (state.finished) return;
15049
+ assertStreamComplete$4(state);
15050
+ for (const chunk of closeStream$4(state)) {
15051
+ outputStarted = true;
15052
+ yield chunk;
14481
15053
  }
15054
+ } catch (error) {
15055
+ const inBand = state.streamError;
15056
+ if (error instanceof LlmError && !outputStarted && inBand !== void 0 && error.code === "PROVIDER_ERROR") {
15057
+ const reclassified = reclassifyInBandStreamError(error, inBand);
15058
+ if (reclassified.code === "RATE_LIMIT" && pool !== null && accountId !== void 0) {
15059
+ const after = retryAfterMs$1(response.headers);
15060
+ await pool.markCooldown(accountId, after ?? POOL_COOLDOWN_MS$4, `${PROVIDER_NAME$5} in-stream 429`).catch(() => void 0);
15061
+ }
15062
+ throw reclassified;
15063
+ }
15064
+ throw error;
14482
15065
  }
14483
- buffer += decoder.decode();
14484
- if (buffer.trim() !== "") for (const line of buffer.split("\n")) for (const chunk of processLine$1(line, state, wire)) yield chunk;
14485
- if (state.finished) return;
14486
- assertStreamComplete$4(state);
14487
- for (const chunk of closeStream$4(state)) yield chunk;
14488
15066
  } finally {
14489
15067
  reader.cancel().catch(() => void 0);
14490
15068
  }
14491
15069
  }
14492
15070
  };
15071
+ /**
15072
+ * The error one recorded in-band failure becomes, or the mapper's own verdict.
15073
+ *
15074
+ * Two rules, because this line speaks three in-band vocabularies and they do
15075
+ * not carry the same evidence. The messages wire names a transient failure with
15076
+ * a `type` that STANDS FOR A STATUS - an `overloaded_error` is the 529 it would
15077
+ * have sent - so its envelope is answered by the status table. The
15078
+ * chat-completions and Responses wires have no such type: their evidence is a
15079
+ * `code`, a `type` in the looser `server_error` sense, or plain prose, and the
15080
+ * status-reading helper is what turns that into the question this line's own
15081
+ * classifier answers.
15082
+ *
15083
+ * The switch is exhaustive on purpose. A fourth vocabulary must be a compile
15084
+ * error here rather than a silent fall into the rule above, which is the one
15085
+ * shape of this bug that cannot be caught by a test.
15086
+ */
15087
+ function reclassifyInBandStreamError(thrown, inBand) {
15088
+ switch (inBand.vocabulary) {
15089
+ case "anthropic": return reclassifyInBandError(thrown, inBand.error, classifyCommandCodeFailure);
15090
+ case "openai":
15091
+ case "responses": return reclassifyInBandResponsesError(thrown, inBand.error, inBand.message, classifyCommandCodeFailure);
15092
+ }
15093
+ }
14493
15094
  /** Path suffix the chosen route answers on. */
14494
15095
  function endpointPathFor(wire) {
14495
15096
  if (wire === "anthropic") return "/messages";
@@ -14503,6 +15104,34 @@ function processLine$1(line, state, wire) {
14503
15104
  }
14504
15105
  //#endregion
14505
15106
  //#region src/host/command-code/account-pool.ts
15107
+ /**
15108
+ * One cached snapshot as the shared account card renders it.
15109
+ *
15110
+ * A usage window states its own consumed share, so it maps straight across; a
15111
+ * window whose share nobody measured is dropped rather than drawn at 0, which
15112
+ * would claim an unspent window no reading supports.
15113
+ *
15114
+ * Exported for the pool's regression tests; the card only reaches it through
15115
+ * the summary hook.
15116
+ */
15117
+ function commandCodePoolQuota(cached) {
15118
+ return poolQuota(cached.fetchedAt, cached.windows.map((window) => quotaWindow$1(window.label, window.usedPercent, {
15119
+ windowDurationMins: window.windowDurationMins,
15120
+ resetsAt: window.resetsAt
15121
+ })));
15122
+ }
15123
+ /**
15124
+ * This key's own newest snapshot, ready for the account card.
15125
+ *
15126
+ * Quota follows the account, so the pool reports what the line already
15127
+ * remembered for THIS account id. The lookup is a pure memory read: the card
15128
+ * builds its account list here, and that must never cost one upstream request
15129
+ * per pooled key.
15130
+ */
15131
+ function quotaFor$1(accountId) {
15132
+ const cached = getCachedQuotaFor$3(accountId);
15133
+ return cached === void 0 ? void 0 : commandCodePoolQuota(cached);
15134
+ }
14506
15135
  /** Encrypted pool file this line owns; the pre-pool credential file stays the mirror. */
14507
15136
  function commandCodePoolPath() {
14508
15137
  return path.join(dshHomeDir(), "storages", "command-code-pool.json");
@@ -14640,7 +15269,11 @@ var CommandCodeAccountPool = class extends AccountPoolCore {
14640
15269
  ...account.userName === void 0 ? {} : { userName: account.userName },
14641
15270
  ...account.organizationName === void 0 ? {} : { organizationName: account.organizationName },
14642
15271
  ...account.planId === void 0 ? {} : { planId: account.planId },
14643
- ...account.userId === void 0 ? {} : { userId: account.userId }
15272
+ ...account.userId === void 0 ? {} : { userId: account.userId },
15273
+ ...(() => {
15274
+ const quota = quotaFor$1(account.id);
15275
+ return quota === void 0 ? {} : { quota };
15276
+ })()
14644
15277
  };
14645
15278
  },
14646
15279
  emptyMessage: "Not signed in to Command Code. 请在「设置 → 订阅服务 → Command Code」中添加账号或粘贴 API Key。",
@@ -15195,7 +15828,7 @@ async function getCommandCodeWebStatus(store, modelSettings, preferences, option
15195
15828
  const catalog = live.length > 0 ? live : fallbackCatalog$1();
15196
15829
  const enabled = settings.enabled !== false;
15197
15830
  const models = buildModelOptions$2(catalog, resolveEnabledModelIds$4(settings.enabledModelIds, catalog, enabled), settings.contextWindowOverrides);
15198
- const quota = getCachedQuotaFor$1(activeAccount?.id ?? null);
15831
+ const quota = getCachedQuotaFor$3(activeAccount?.id ?? null);
15199
15832
  const activeAccountView = activePoolAccount === void 0 ? null : {
15200
15833
  userId: activePoolAccount.userId ?? activePoolAccount.credentials.userId ?? null,
15201
15834
  userName: activePoolAccount.userName ?? activePoolAccount.credentials.userName ?? null,
@@ -15268,7 +15901,7 @@ function registerCommandCodeRoutes(ctx, store, modelSettings, preferences, optio
15268
15901
  if (method !== "GET") return sendMethodNotAllowed$5(response);
15269
15902
  const credentials = await store.read();
15270
15903
  const targetId = await activeAccountId();
15271
- const cached = getCachedQuotaFor$1(targetId);
15904
+ const cached = getCachedQuotaFor$3(targetId);
15272
15905
  if ((credentials !== null || targetId !== void 0) && (cached === void 0 || Date.now() - (cached.fetchedAt || 0) > 12e4)) {
15273
15906
  const refresh = () => fetchAccountQuota$2(quotaStore, fetchFn, false, targetId);
15274
15907
  if (cached === void 0) await quotaRefresh.run(refresh);
@@ -15541,6 +16174,24 @@ const DEFAULT_CONTEXT_WINDOW$4 = 128e3;
15541
16174
  * ceiling rather than a claim about what the model can do.
15542
16175
  */
15543
16176
  const DEFAULT_MAX_OUTPUT_TOKENS = 8192;
16177
+ /**
16178
+ * Raise a cap that is too small for this model's forced thinking.
16179
+ *
16180
+ * Left alone: a model whose thinking CAN be disabled - its caller may have asked
16181
+ * for thinking off on purpose, and raising the cap would not enable it while the
16182
+ * number read back would be misleading - and an absent cap, which
16183
+ * `DEFAULT_MAX_OUTPUT_TOKENS` already owns at a size nothing is short of.
16184
+ *
16185
+ * @param modelId - the routed model id.
16186
+ * @param requested - the caller's cap, undefined when it stated none.
16187
+ * @returns the cap to request, raised only when that is the difference between an
16188
+ * empty truncated answer and a usable one.
16189
+ */
16190
+ function floorForcedThinkingTokens$1(modelId, requested) {
16191
+ if (requested === void 0) return void 0;
16192
+ if (thinkingModeForModel(modelId) !== "levels") return requested;
16193
+ return Math.max(requested, 512);
16194
+ }
15544
16195
  /** How long a catalog sync may take before the cached list is used instead. */
15545
16196
  const CATALOG_TIMEOUT_MS = 15e3;
15546
16197
  /**
@@ -16586,6 +17237,23 @@ var OllamaAdapter = class extends LlmAdapter {
16586
17237
  let lastStatus;
16587
17238
  let lastDetail = "";
16588
17239
  let accountId;
17240
+ /**
17241
+ * Set the moment ANY chunk reaches the caller, and never cleared.
17242
+ *
17243
+ * Deliberately broader than "some text arrived": a block start or a tool
17244
+ * call the caller has already seen is exactly as visible as the text, so the
17245
+ * conservative reading is the one that cannot be wrong. It also survives the
17246
+ * pool rotation below, because output an account produced before failing is
17247
+ * still output.
17248
+ */
17249
+ let outputStarted = false;
17250
+ /**
17251
+ * The code a transient in-band failure becomes, or null for this attempt.
17252
+ *
17253
+ * Assigned per failure rather than accumulated, so a later attempt that
17254
+ * failed for a reason of its own is never judged by an earlier one's wording.
17255
+ */
17256
+ let lastInBandCode = null;
16589
17257
  for (;;) {
16590
17258
  let credentials;
16591
17259
  if (pool === null) {
@@ -16613,6 +17281,7 @@ var OllamaAdapter = class extends LlmAdapter {
16613
17281
  if (event.type === "error") {
16614
17282
  lastStatus = openedStatus;
16615
17283
  lastDetail = event.message;
17284
+ lastInBandCode = outputStarted || !(openedStatus !== void 0 && openedStatus >= 200 && openedStatus < 300) ? null : inBandTransientCode(event.message);
16616
17285
  failed = true;
16617
17286
  break;
16618
17287
  }
@@ -16621,12 +17290,18 @@ var OllamaAdapter = class extends LlmAdapter {
16621
17290
  continue;
16622
17291
  }
16623
17292
  if (event.type === "text" || event.type === "thinking" || event.type === "tool_call") {
16624
- for (const chunk of applyEvent(state, event)) yield chunk;
17293
+ for (const chunk of applyEvent(state, event)) {
17294
+ outputStarted = true;
17295
+ yield chunk;
17296
+ }
16625
17297
  continue;
16626
17298
  }
16627
17299
  }
16628
17300
  if (!failed) {
16629
- for (const chunk of closeStream$3(state)) yield chunk;
17301
+ for (const chunk of closeStream$3(state)) {
17302
+ outputStarted = true;
17303
+ yield chunk;
17304
+ }
16630
17305
  if (pool !== null && accountId !== void 0 && spent !== null) await pool.recordUsage(accountId, spent.inputTokens, spent.outputTokens);
16631
17306
  return;
16632
17307
  }
@@ -16646,9 +17321,76 @@ var OllamaAdapter = class extends LlmAdapter {
16646
17321
  const status = lastStatus;
16647
17322
  if (status === 401 || status === 403) throw new LlmError(`${PROVIDER_NAME$4} rejected the API key (${status}). Replace it from Settings > ${PROVIDER_NAME$4}.${lastDetail ? ` ${lastDetail}` : ""}`, "INVALID_CREDENTIAL", { status });
16648
17323
  if (status === 429) throw new LlmError(`${PROVIDER_NAME$4} rate limit or plan quota reached (429). Add another key, or wait for the cooldown shown in Settings > ${PROVIDER_NAME$4}.${lastDetail ? ` ${lastDetail}` : ""}`, "RATE_LIMIT", { status: 429 });
16649
- throw new LlmError(`${PROVIDER_NAME$4} request failed${status === void 0 ? "" : ` (${status})`}.${lastDetail}`, status !== void 0 && status >= 500 ? "SERVER" : "INVALID_REQUEST", status === void 0 ? {} : { status });
17324
+ throw new LlmError(`${PROVIDER_NAME$4} request failed${status === void 0 ? "" : ` (${status})`}.${lastDetail}`, lastInBandCode ?? (status !== void 0 && status >= 500 ? "SERVER" : "INVALID_REQUEST"), status === void 0 ? {} : { status });
16650
17325
  }
16651
17326
  };
17327
+ /**
17328
+ * The DSH code an in-band Ollama failure becomes, or null when the message names
17329
+ * nothing transient.
17330
+ *
17331
+ * Ollama reports a failure inside a 200 as `{"error": "<message>"}` - one string,
17332
+ * no type - so the shared helper's Anthropic table cannot apply and there is no
17333
+ * status to name either. The wording is the whole signal, which is the same
17334
+ * message heuristic the Codex line already reads on `response.failed` in
17335
+ * `responses-client.ts` (generalized for the Responses vocabulary by
17336
+ * `inBandResponsesCode` in `common/stream-error.ts`); it is kept local here
17337
+ * because these are Ollama's own phrasings, not a vocabulary lines share.
17338
+ *
17339
+ * Every phrase below names a condition Ollama reports that clears on its own: a
17340
+ * model still being pulled into memory, a runner that would not start, a server
17341
+ * with no slot for this request, an overloaded or throttled front door. That is
17342
+ * what makes a retry worth taking - the request did not change, so a later one
17343
+ * is genuinely a different bet.
17344
+ *
17345
+ * Deliberately narrow, because the cost of guessing is paid by the user in
17346
+ * backoff. A refusal the wording does not name is usually the service objecting
17347
+ * to the caller's message, and retrying repeats it verbatim: three identical
17348
+ * requests ending in the same verdict, with the real fault unreported. Falling
17349
+ * back to some invented status instead would be worse on both counts, filing an
17350
+ * unrecognized refusal as a retryable server error and inviting the pool chain
17351
+ * to cool an account down for something nothing observed.
17352
+ */
17353
+ function inBandTransientCode(message) {
17354
+ const text = message.toLowerCase();
17355
+ if (IN_BAND_RATE_LIMIT.some((phrase) => text.includes(phrase))) return "RATE_LIMIT";
17356
+ if (IN_BAND_SERVER.some((phrase) => text.includes(phrase))) return "SERVER";
17357
+ return null;
17358
+ }
17359
+ /**
17360
+ * Throttling wording, read first because it is the narrower of the two verdicts.
17361
+ *
17362
+ * The same conditions this line already types RATE_LIMIT for when they arrive as
17363
+ * a 429; a gateway that reports one inside the stream is describing the same
17364
+ * state, and the harness backoff that 429 earns is what the turn should get.
17365
+ */
17366
+ const IN_BAND_RATE_LIMIT = [
17367
+ "rate limit",
17368
+ "rate-limit",
17369
+ "rate_limit",
17370
+ "too many requests",
17371
+ "quota"
17372
+ ];
17373
+ /**
17374
+ * Server-side conditions, in no particular order.
17375
+ *
17376
+ * The model-loading and runner wording covers the two failures Ollama raises while
17377
+ * it is still getting ready to serve - the request was well formed and the
17378
+ * service was not ready for it - and the rest is what its front door says when it
17379
+ * is saturated.
17380
+ */
17381
+ const IN_BAND_SERVER = [
17382
+ "overload",
17383
+ "server busy",
17384
+ "model is loading",
17385
+ "loading model",
17386
+ "couldn't load",
17387
+ "could not load",
17388
+ "unable to load",
17389
+ "failed to load",
17390
+ "runner",
17391
+ "service unavailable",
17392
+ "internal server error"
17393
+ ];
16652
17394
  function isAbort$3(error, signal) {
16653
17395
  return signal?.aborted === true || error instanceof Error && error.name === "AbortError";
16654
17396
  }
@@ -16812,7 +17554,8 @@ async function toOllamaRequest(options, attachments, signal) {
16812
17554
  model: options.model,
16813
17555
  messages
16814
17556
  };
16815
- if (options.maxTokens !== void 0) request.maxOutputTokens = options.maxTokens;
17557
+ const flooredMax = floorForcedThinkingTokens$1(options.model, options.maxTokens);
17558
+ if (flooredMax !== void 0) request.maxOutputTokens = flooredMax;
16816
17559
  if (options.temperature !== void 0) request.temperature = options.temperature;
16817
17560
  const think = thinkForModel(options.model, options.reasoningEffort);
16818
17561
  if (think !== void 0) request.think = think;
@@ -18971,19 +19714,89 @@ function buildModelOptions$1(catalog, enabledModelIds, contextWindowOverrides) {
18971
19714
  };
18972
19715
  });
18973
19716
  }
18974
- let quotaCache$1 = null;
18975
- let quotaAccountId;
19717
+ /**
19718
+ * How many accounts' snapshots are remembered.
19719
+ *
19720
+ * Mirrors the bound the ChatGPT line's usage service applies to its own
19721
+ * per-account map: room for every account a user is likely to pool, and no way
19722
+ * for a long-lived process to accumulate one snapshot per account it has seen.
19723
+ */
19724
+ const QUOTA_ACCOUNT_LIMIT$1 = 20;
19725
+ /** Key of a snapshot whose caller named no account: the single-credential read. */
19726
+ const UNNAMED_ACCOUNT_KEY = "";
19727
+ /**
19728
+ * One newest snapshot PER ACCOUNT, keyed by the account id the caller stated.
19729
+ *
19730
+ * Quota follows the account: rotation decides which account spends the next
19731
+ * request, so a single slot could only ever describe whichever account was read
19732
+ * last. The pool's card draws every account its own reading, so each account
19733
+ * keeps its own entry; past the cap the oldest read is evicted.
19734
+ *
19735
+ * This map is a memory read only. Nothing here fetches — the settings card
19736
+ * builds its account list from it, and that must never cost one upstream
19737
+ * request per pooled account.
19738
+ */
19739
+ const quotaCache$1 = /* @__PURE__ */ new Map();
19740
+ /**
19741
+ * Key of the entry written most recently.
19742
+ *
19743
+ * The pre-pool card asks "whatever was fetched last"; the last WRITE answers
19744
+ * that exactly, where a scan by `fetchedAt` would break a tie between two
19745
+ * accounts read inside the same millisecond by map order.
19746
+ */
19747
+ let quotaLastKey$1 = UNNAMED_ACCOUNT_KEY;
19748
+ function quotaCacheKeyFor(accountId) {
19749
+ return accountId ?? UNNAMED_ACCOUNT_KEY;
19750
+ }
19751
+ /** The entry read most recently, whichever account it belongs to. */
19752
+ function newestQuotaEntry$1() {
19753
+ return quotaCache$1.get(quotaLastKey$1);
19754
+ }
19755
+ /** Remember one account's newest snapshot, bounded to the accounts in use. */
19756
+ function rememberQuota$1(accountId, snapshot) {
19757
+ const key = quotaCacheKeyFor(accountId);
19758
+ quotaCache$1.set(key, snapshot);
19759
+ quotaLastKey$1 = key;
19760
+ while (quotaCache$1.size > QUOTA_ACCOUNT_LIMIT$1) {
19761
+ const oldest = [...quotaCache$1.entries()].sort((left, right) => left[1].fetchedAt - right[1].fetchedAt)[0];
19762
+ if (oldest === void 0) break;
19763
+ quotaCache$1.delete(oldest[0]);
19764
+ if (quotaLastKey$1 === oldest[0]) quotaLastKey$1 = UNNAMED_ACCOUNT_KEY;
19765
+ }
19766
+ }
19767
+ /**
19768
+ * The snapshot read most recently, whichever account it belongs to.
19769
+ *
19770
+ * Kept for the pre-pool single-credential card, which has no account id to ask
19771
+ * with. A pooled caller asks {@link getCachedQuotaFor} instead.
19772
+ */
18976
19773
  function getCachedQuota$3() {
18977
- return quotaCache$1;
19774
+ return newestQuotaEntry$1() ?? null;
18978
19775
  }
18979
- /** The cached snapshot only when it belongs to this account. */
18980
- function getCachedQuotaFor(accountId) {
18981
- if (accountId === void 0 || accountId === null) return quotaCache$1;
18982
- return quotaAccountId === accountId ? quotaCache$1 : null;
19776
+ /**
19777
+ * The cached snapshot only when it belongs to this account.
19778
+ *
19779
+ * Passing no account keeps the pre-pool behavior of returning whatever was
19780
+ * fetched last. Passing one answers that account's own newest snapshot, or null
19781
+ * when this line has never read it — null is "never read", which is a different
19782
+ * statement from a snapshot whose windows are all at zero.
19783
+ */
19784
+ function getCachedQuotaFor$2(accountId) {
19785
+ if (accountId === void 0 || accountId === null) return newestQuotaEntry$1() ?? null;
19786
+ return cachedQuotaFor$1(accountId);
19787
+ }
19788
+ /**
19789
+ * The newest snapshot held for one account, for display only.
19790
+ *
19791
+ * The pool asks here while it builds the account list for the settings card, so
19792
+ * this is a pure memory read: it never fetches.
19793
+ */
19794
+ function cachedQuotaFor$1(accountId) {
19795
+ return quotaCache$1.get(accountId) ?? null;
18983
19796
  }
18984
19797
  function clearCachedQuota$3() {
18985
- quotaCache$1 = null;
18986
- quotaAccountId = void 0;
19798
+ quotaCache$1.clear();
19799
+ quotaLastKey$1 = UNNAMED_ACCOUNT_KEY;
18987
19800
  }
18988
19801
  /**
18989
19802
  * Display name and nominal length for each window key the service reports.
@@ -19230,8 +20043,8 @@ function parsePlanLevel(payload) {
19230
20043
  * account's real usage.
19231
20044
  */
19232
20045
  async function fetchAccountQuota$3(store, options = {}) {
19233
- const sameAccount = options.accountId === void 0 || quotaAccountId === options.accountId;
19234
- if (options.force !== true && sameAccount && quotaCache$1 !== null && Date.now() - quotaCache$1.fetchedAt < 12e4) return quotaCache$1;
20046
+ const cached = quotaCache$1.get(quotaCacheKeyFor(options.accountId));
20047
+ if (options.force !== true && cached !== void 0 && Date.now() - cached.fetchedAt < 12e4) return cached;
19235
20048
  const fetchFn = options.fetchFn ?? fetch;
19236
20049
  const pooled = options.credentials !== void 0;
19237
20050
  let credentials;
@@ -19280,8 +20093,7 @@ async function fetchAccountQuota$3(store, options = {}) {
19280
20093
  fetchedAt: Date.now(),
19281
20094
  sources: [openAIUrl(USAGES_PATH, credentials.region)]
19282
20095
  };
19283
- quotaCache$1 = snapshot;
19284
- quotaAccountId = options.accountId;
20096
+ rememberQuota$1(options.accountId, snapshot);
19285
20097
  return snapshot;
19286
20098
  }
19287
20099
  /**
@@ -21104,7 +21916,15 @@ function processOpenAIStreamLine(line, state) {
21104
21916
  if (!isRecord$17(chunk)) return [];
21105
21917
  const out = [];
21106
21918
  const errorPayload = isRecord$17(chunk.error) ? chunk.error : void 0;
21107
- if (errorPayload !== void 0) throw new LlmError$1(`Kimi Code stream error: ${asString$9(errorPayload.message) ?? "unknown error"}`, isContextOverflow(errorPayload) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
21919
+ if (errorPayload !== void 0) {
21920
+ const message = asString$9(errorPayload.message) ?? "unknown error";
21921
+ state.streamError = {
21922
+ vocabulary: "openai",
21923
+ error: errorPayload,
21924
+ message
21925
+ };
21926
+ throw new LlmError$1(`Kimi Code stream error: ${message}`, isContextOverflow(errorPayload) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
21927
+ }
21108
21928
  const usage = isRecord$17(chunk.usage) ? chunk.usage : void 0;
21109
21929
  if (usage) {
21110
21930
  state.sawUsage = true;
@@ -21373,7 +22193,13 @@ function processAnthropicStreamLine(line, state) {
21373
22193
  }
21374
22194
  if (type === "error") {
21375
22195
  const error = isRecord$17(event.error) ? event.error : {};
21376
- throw new LlmError$1(`Kimi Code stream error: ${asString$9(error.message) ?? "unknown error"}`, isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
22196
+ const message = asString$9(error.message) ?? "unknown error";
22197
+ state.streamError = {
22198
+ vocabulary: "anthropic",
22199
+ error,
22200
+ message
22201
+ };
22202
+ throw new LlmError$1(`Kimi Code stream error: ${message}`, isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
21377
22203
  }
21378
22204
  return out;
21379
22205
  }
@@ -22014,27 +22840,98 @@ var KimiCodeAdapter = class extends LlmAdapter {
22014
22840
  const state = createStreamState$3(wire, requestOptions.sessionId);
22015
22841
  markSessionActive(requestOptions.sessionId);
22016
22842
  let buffer = "";
22843
+ /**
22844
+ * Set the moment ANY chunk reaches the caller, and never cleared.
22845
+ *
22846
+ * Deliberately broader than "text arrived": a block-start the caller has
22847
+ * already seen repeats just as badly as text it has already read, so the
22848
+ * conservative reading is the one that cannot be wrong. It is what keeps the
22849
+ * in-band reclassification below pre-output only.
22850
+ */
22851
+ let outputStarted = false;
22017
22852
  try {
22018
- while (true) {
22019
- const { done, value } = await reader.read();
22020
- if (done) break;
22021
- buffer += decoder.decode(value, { stream: true });
22022
- const lines = buffer.split("\n");
22023
- buffer = lines.pop() ?? "";
22024
- for (const line of lines) {
22025
- for (const chunk of processLine(line, state, wire)) yield chunk;
22026
- if (state.finished) return;
22853
+ try {
22854
+ while (true) {
22855
+ const { done, value } = await reader.read();
22856
+ if (done) break;
22857
+ buffer += decoder.decode(value, { stream: true });
22858
+ const lines = buffer.split("\n");
22859
+ buffer = lines.pop() ?? "";
22860
+ for (const line of lines) {
22861
+ for (const chunk of processLine(line, state, wire)) {
22862
+ outputStarted = true;
22863
+ yield chunk;
22864
+ }
22865
+ if (state.finished) return;
22866
+ }
22867
+ }
22868
+ buffer += decoder.decode();
22869
+ if (buffer.trim() !== "") for (const line of buffer.split("\n")) for (const chunk of processLine(line, state, wire)) {
22870
+ outputStarted = true;
22871
+ yield chunk;
22027
22872
  }
22873
+ if (state.finished) return;
22874
+ assertStreamComplete$3(state);
22875
+ for (const chunk of closeStream$2(state, servedByAccountId)) {
22876
+ outputStarted = true;
22877
+ yield chunk;
22878
+ }
22879
+ } catch (error) {
22880
+ const inBand = state.streamError;
22881
+ if (error instanceof LlmError && !outputStarted && error.code === "PROVIDER_ERROR" && inBand !== void 0) throw await this.inBandStreamFailure(error, inBand, servedByAccountId);
22882
+ throw error;
22028
22883
  }
22029
- buffer += decoder.decode();
22030
- if (buffer.trim() !== "") for (const line of buffer.split("\n")) for (const chunk of processLine(line, state, wire)) yield chunk;
22031
- if (state.finished) return;
22032
- assertStreamComplete$3(state);
22033
- for (const chunk of closeStream$2(state, servedByAccountId)) yield chunk;
22034
22884
  } finally {
22035
22885
  reader.cancel().catch(() => void 0);
22036
22886
  }
22037
22887
  }
22888
+ /**
22889
+ * What one in-band failure becomes once the pool has been told what the
22890
+ * verdict means for it.
22891
+ *
22892
+ * Reclassification and the pool reaction are separate because they answer
22893
+ * different questions about the same classification: the code decides whether
22894
+ * the harness retries, `accountScoped` decides whether a retry could land
22895
+ * somewhere that can answer. The mapper can supply only the first, which is
22896
+ * why the wire object is recorded there and read here.
22897
+ *
22898
+ * The body is the one a non-2xx response would have carried, so every pattern
22899
+ * below reads exactly what the HTTP branch reads and a limit cannot mean one
22900
+ * thing as a status and another as an event. The request itself is NOT
22901
+ * re-issued: its body is already open and part-read, and the harness retry
22902
+ * policy owns the repeat.
22903
+ */
22904
+ async inBandStreamFailure(thrown, inBand, accountId) {
22905
+ const bodyText = JSON.stringify({ error: inBand.error });
22906
+ const reclassified = inBand.vocabulary === "anthropic" ? reclassifyInBandError(thrown, inBand.error, classifyKimiFailure) : reclassifyInBandResponsesError(thrown, inBand.error, inBand.message, classifyKimiFailure);
22907
+ const pool = this.accountPool;
22908
+ if (pool === null || accountId === void 0) return reclassified;
22909
+ if (inBand.vocabulary === "anthropic") {
22910
+ const status = inBandAnthropicStatus(inBand.error);
22911
+ if (status === null) return reclassified;
22912
+ const failure = classifyKimiFailure(status, bodyText);
22913
+ const planScoped = status === 429 && matchesAny$1(bodyText, ENTITLEMENT_PATTERNS);
22914
+ if (status === 429 && !planScoped) await this.coolInBandRateLimit(pool, accountId, failure.accountScoped, bodyText);
22915
+ else if (failure.code === "INVALID_CREDENTIAL") await pool.markAuthFailed(accountId, failure.message).catch(() => void 0);
22916
+ else if (failure.accountScoped) await pool.markCooldown(accountId, accountLimitCooldownMs(bodyText), `${PROVIDER_NAME$3} in-stream 403`).catch(() => void 0);
22917
+ return reclassified;
22918
+ }
22919
+ if (reclassified.code === "RATE_LIMIT" && !matchesAny$1(bodyText, ENTITLEMENT_PATTERNS)) await this.coolInBandRateLimit(pool, accountId, classifyKimiFailure(429, bodyText).accountScoped, bodyText);
22920
+ return reclassified;
22921
+ }
22922
+ /**
22923
+ * Cool the account whose own rate limit the stream reported.
22924
+ *
22925
+ * The window this route already defaults to, never a Retry-After: there is no
22926
+ * 429 response to read one from, and a delay stated on the 200 belongs to a
22927
+ * different message. An account-scoped verdict takes its own window instead,
22928
+ * because a spent balance is not back-pressure and 15 minutes would put the
22929
+ * account back into rotation four times inside the window that refused it.
22930
+ */
22931
+ async coolInBandRateLimit(pool, accountId, accountScoped, bodyText) {
22932
+ const cooldownMs = accountScoped ? accountLimitCooldownMs(bodyText) : POOL_COOLDOWN_MS$2;
22933
+ await pool.markCooldown(accountId, cooldownMs, `${PROVIDER_NAME$3} in-stream 429`).catch(() => void 0);
22934
+ }
22038
22935
  };
22039
22936
  function processLine(line, state, wire) {
22040
22937
  return wire === "anthropic" ? processAnthropicStreamLine(line, state) : processOpenAIStreamLine(line, state);
@@ -22097,6 +22994,34 @@ function parseKimiCodePoolData(value) {
22097
22994
  return result;
22098
22995
  }
22099
22996
  /**
22997
+ * One account's own newest quota snapshot, in the shape the account card draws.
22998
+ *
22999
+ * The snapshot comes from 'client.ts''s per-account cache keyed by the pool
23000
+ * account id, and this is where this line's own numbers are translated into the
23001
+ * shared shape. Every judgement below trades a possibly-nice-looking bar for an
23002
+ * honest one:
23003
+ *
23004
+ * - a window whose consumed share nobody measured is DROPPED (`quotaWindow`
23005
+ * returns null), because "0% used" and "not stated" are different claims. This
23006
+ * parser only mints a window once it has a ratio, so a window that states none
23007
+ * never reaches here at all — the drop is the second line of defence;
23008
+ * - `resetsAt` is already Unix MILLISECONDS on this line, so it is passed
23009
+ * through and normalized rather than multiplied;
23010
+ * - the read time is the snapshot's own `fetchedAt`, which this line always
23011
+ * sets. A snapshot with no read time is not a snapshot, and `poolQuota`
23012
+ * reports that as undefined.
23013
+ *
23014
+ * A null snapshot means "this account was never read", which the card renders as
23015
+ * exactly that instead of drawing a zero.
23016
+ */
23017
+ function kimiCodePoolQuota(snapshot) {
23018
+ if (snapshot === null) return void 0;
23019
+ return poolQuota(snapshot.fetchedAt, snapshot.windows.map((window) => quotaWindow$1(window.label, window.usedPercent, {
23020
+ windowDurationMins: window.windowDurationMins,
23021
+ resetsAt: window.resetsAt
23022
+ })));
23023
+ }
23024
+ /**
22100
23025
  * Kimi Code's account pool.
22101
23026
  *
22102
23027
  * The credential is an OAuth pair whose refresh token rotates, so the pool owns
@@ -22175,6 +23100,7 @@ var KimiCodeAccountPool = class extends AccountPoolCore {
22175
23100
  const nickname = account.nickname ?? account.credentials.nickname;
22176
23101
  const planName = account.planName ?? account.credentials.planName;
22177
23102
  const region = account.region ?? account.credentials.region;
23103
+ const quota = kimiCodePoolQuota(cachedQuotaFor$1(account.id));
22178
23104
  return {
22179
23105
  ...base,
22180
23106
  ...email === void 0 ? {} : { email },
@@ -22184,7 +23110,8 @@ var KimiCodeAccountPool = class extends AccountPoolCore {
22184
23110
  planLabel: planName
22185
23111
  },
22186
23112
  ...region === void 0 ? {} : { region },
22187
- ...account.userId === void 0 ? {} : { userId: account.userId }
23113
+ ...account.userId === void 0 ? {} : { userId: account.userId },
23114
+ ...quota === void 0 ? {} : { quota }
22188
23115
  };
22189
23116
  },
22190
23117
  emptyMessage: "Not signed in to Kimi Code. Sign in from Settings > Kimi Code.",
@@ -22298,7 +23225,7 @@ async function getKimiCodeWebStatus(store, modelSettings, preferences, options =
22298
23225
  const catalog = live.length > 0 ? live : fallbackCatalog();
22299
23226
  const enabled = settings.enabled !== false;
22300
23227
  const models = buildModelOptions$1(catalog, resolveEnabledModelIds$3(settings.enabledModelIds, catalog, enabled), settings.contextWindowOverrides);
22301
- const quota = getCachedQuotaFor(activeAccount?.id ?? null);
23228
+ const quota = getCachedQuotaFor$2(activeAccount?.id ?? null);
22302
23229
  const account = quota?.account ?? (active === null || active === void 0 ? null : accountFromCredentials$2(active));
22303
23230
  return {
22304
23231
  enabled,
@@ -22432,7 +23359,7 @@ function registerKimiCodeRoutes(ctx, store, modelSettings, preferences, options
22432
23359
  if (path === "" || path === "status") {
22433
23360
  if (method !== "GET") return sendMethodNotAllowed$3(response);
22434
23361
  const poolActive = await activeAccount();
22435
- const cached = getCachedQuotaFor(poolActive.id ?? null);
23362
+ const cached = getCachedQuotaFor$2(poolActive.id ?? null);
22436
23363
  if (poolActive.credentials !== void 0 && (cached === null || Date.now() - (cached.fetchedAt || 0) > 12e4)) {
22437
23364
  const refresh = async () => {
22438
23365
  const credentials = await effectiveCredentials(poolActive);
@@ -23997,6 +24924,26 @@ function outputConfigFor(modelId, requestedEffort) {
23997
24924
  const effort = effortForModel(modelId, requestedEffort ?? null);
23998
24925
  return effort === "default" ? void 0 : { effort };
23999
24926
  }
24927
+ /**
24928
+ * Raise a cap that is too small for this line's forced thinking.
24929
+ *
24930
+ * Left alone: a model that can disable thinking (its caller may have asked for
24931
+ * thinking off on purpose, and raising the cap would not enable it but a caller
24932
+ * reading the number back would be misled), and an absent cap (the caller stated
24933
+ * none, so \`maxOutputTokensFor\` owns the ceiling).
24934
+ *
24935
+ * @param modelId - the routed model id.
24936
+ * @param requested - the caller's cap, undefined when it stated none.
24937
+ * @returns the cap to request, raised only when that is the difference between an
24938
+ * empty truncated answer and a usable one.
24939
+ */
24940
+ function floorForcedThinkingTokens(modelId, requested) {
24941
+ if (requested === void 0) return void 0;
24942
+ const model = catalogEntry(modelId);
24943
+ const thinking = model?.thinking;
24944
+ if (thinking !== "always-on" && thinking !== "forced-effort") return requested;
24945
+ return Math.min(Math.max(requested, 512), Math.max(requested, model?.maxTokens ?? requested));
24946
+ }
24000
24947
  /** Output cap one request asks for, tracked against the model's declared ceiling. */
24001
24948
  function maxOutputTokensFor$1(modelId, contextWindow) {
24002
24949
  const model = catalogEntry(modelId);
@@ -24515,6 +25462,7 @@ function processMinimaxStreamLine(line, state) {
24515
25462
  }
24516
25463
  if (type === "error") {
24517
25464
  const error = isRecord$16(event.error) ? event.error : {};
25465
+ state.streamError = error;
24518
25466
  throw new LlmError$1("MiniMax Code(编程订阅) stream error: " + (asString$8(error.message) ?? "unknown error"), isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
24519
25467
  }
24520
25468
  return out;
@@ -26254,29 +27202,75 @@ const QUOTA_FAILURE_CACHE_MS = 10 * 6e4;
26254
27202
  const QUOTA_TIMEOUT_MS = 8e3;
26255
27203
  let quotaHostInForce = null;
26256
27204
  /**
26257
- * The last usage snapshot, plus the last failure to have one.
27205
+ * How many accounts' snapshots are remembered.
26258
27206
  *
26259
- * `value` and `reason` are tracked SEPARATELY from the last GOOD value on
26260
- * purpose. A read that fails carries no information about the previous one, so
26261
- * replacing a good snapshot with `null` turned every transient refusal into a
26262
- * visible disappearance of the quota box; `quotaLastGood` lets the card keep
26263
- * showing the last known numbers while saying they are old.
27207
+ * Bounded like the ChatGPT line's own per-account map: the pool accepts a
27208
+ * handful of accounts, and a long-lived process must not accumulate one entry
27209
+ * per id it has ever seen. The oldest WRITE is evicted, which is also the read
27210
+ * least likely to be looked at again.
27211
+ */
27212
+ const QUOTA_SNAPSHOT_ACCOUNT_LIMIT = 20;
27213
+ /**
27214
+ * The newest read, whichever account it belonged to.
27215
+ *
27216
+ * Kept beside the per-account map because the page-level block and the
27217
+ * "is a read even worth starting" test are about the line's latest reading,
27218
+ * not about one account.
26264
27219
  */
26265
27220
  let quotaSnapshot = null;
26266
- /** The last snapshot that actually parsed, kept across failures. */
26267
- let quotaLastGood = null;
27221
+ /** Per-account snapshots, so an account is never shown the numbers read for another. */
27222
+ const quotaByAccount = /* @__PURE__ */ new Map();
26268
27223
  /** How long a refused credential is remembered before asking again. */
26269
27224
  const QUOTA_REJECTED_CACHE_MS = 30 * 6e4;
26270
27225
  /** Forget the cached usage snapshot (a sign-out, a sign-in, a test). */
26271
27226
  function clearCachedQuota$4() {
26272
27227
  quotaSnapshot = null;
26273
- quotaLastGood = null;
27228
+ quotaByAccount.clear();
26274
27229
  quotaHostInForce = null;
26275
27230
  }
26276
27231
  /** The last usage snapshot without touching the network. */
26277
27232
  function getCachedQuota$4() {
26278
27233
  return quotaSnapshot?.value ?? null;
26279
27234
  }
27235
+ /**
27236
+ * The cached snapshot only when it belongs to this account, without touching the
27237
+ * network.
27238
+ *
27239
+ * Quota follows the account: a rotation decides whose credential is spent next,
27240
+ * so the snapshot a read produced describes the row it was made for. The pool
27241
+ * asks here while it builds the settings card's account list, which must never
27242
+ * wait on — or trigger — an upstream read.
27243
+ *
27244
+ * An absent, empty or null id keeps the pre-pool behaviour and answers with the
27245
+ * newest read, which is what the page-level block wants.
27246
+ */
27247
+ function getCachedQuotaFor$1(accountId) {
27248
+ if (accountId === void 0 || accountId === null || accountId === "") return getCachedQuota$4();
27249
+ return quotaByAccount.get(accountId)?.value ?? null;
27250
+ }
27251
+ /** The entry a read for this account should consult, when there is one. */
27252
+ function quotaEntryFor(accountId) {
27253
+ if (accountId === void 0 || accountId === "") return quotaSnapshot;
27254
+ return quotaByAccount.get(accountId) ?? null;
27255
+ }
27256
+ /**
27257
+ * Remember one read against the account it was made for.
27258
+ *
27259
+ * Re-inserting an id moves it to the newest position, so the map's first key is
27260
+ * always the least recently written entry and eviction is a single delete.
27261
+ */
27262
+ function rememberQuotaSnapshot(entry) {
27263
+ quotaSnapshot = entry;
27264
+ const accountId = entry.accountId;
27265
+ if (accountId === void 0 || accountId === "") return;
27266
+ quotaByAccount.delete(accountId);
27267
+ quotaByAccount.set(accountId, entry);
27268
+ while (quotaByAccount.size > QUOTA_SNAPSHOT_ACCOUNT_LIMIT) {
27269
+ const oldest = quotaByAccount.keys().next().value;
27270
+ if (oldest === void 0) break;
27271
+ quotaByAccount.delete(oldest);
27272
+ }
27273
+ }
26280
27274
  /** Why the last read produced no snapshot, or null when it produced one. */
26281
27275
  function getQuotaUnavailable() {
26282
27276
  return quotaSnapshot?.reason ?? null;
@@ -26398,9 +27392,10 @@ function parseTokenPlanQuota(payload, now = Date.now()) {
26398
27392
  async function fetchTokenPlanQuota(credentials, options = {}) {
26399
27393
  const fetchFn = options.fetchFn ?? fetch;
26400
27394
  const now = Date.now();
26401
- if (options.force !== true && quotaSnapshot !== null) {
26402
- const ttl = quotaSnapshot.reason === "credential-not-accepted" ? QUOTA_REJECTED_CACHE_MS : quotaSnapshot.reason === "token-expired" && quotaSnapshot.value === null ? QUOTA_FAILURE_CACHE_MS : quotaSnapshot.value === null ? QUOTA_FAILURE_CACHE_MS : QUOTA_CACHE_MS;
26403
- if (now - quotaSnapshot.at < ttl) return quotaSnapshot.value;
27395
+ const cached = quotaEntryFor(options.accountId);
27396
+ if (options.force !== true && cached !== null) {
27397
+ const ttl = cached.reason === "credential-not-accepted" ? QUOTA_REJECTED_CACHE_MS : cached.reason === "token-expired" && cached.value === null ? QUOTA_FAILURE_CACHE_MS : cached.value === null ? QUOTA_FAILURE_CACHE_MS : QUOTA_CACHE_MS;
27398
+ if (now - cached.at < ttl) return cached.value;
26404
27399
  }
26405
27400
  const candidates = quotaHostCandidates(credentials.region);
26406
27401
  const ordered = quotaHostInForce === null ? candidates : [quotaHostInForce, ...candidates.filter((host) => host !== quotaHostInForce)];
@@ -26440,19 +27435,19 @@ async function fetchTokenPlanQuota(credentials, options = {}) {
26440
27435
  });
26441
27436
  }
26442
27437
  if (value === null && reason === null) reason = authRefused ? "token-expired" : "unreachable";
26443
- if (value !== null) quotaLastGood = {
26444
- value,
26445
- at: Date.now()
26446
- };
26447
- else if (quotaLastGood !== null) {
26448
- value = quotaLastGood.value;
26449
- reason = "stale";
27438
+ if (value === null) {
27439
+ const previous = quotaEntryFor(options.accountId);
27440
+ if (previous !== null && previous.value !== null) {
27441
+ value = previous.value;
27442
+ reason = "stale";
27443
+ }
26450
27444
  }
26451
- quotaSnapshot = {
27445
+ rememberQuotaSnapshot({
27446
+ accountId: options.accountId,
26452
27447
  at: Date.now(),
26453
27448
  value,
26454
27449
  reason
26455
- };
27450
+ });
26456
27451
  return value;
26457
27452
  }
26458
27453
  /**
@@ -27175,7 +28170,7 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
27175
28170
  }
27176
28171
  let authOwner = computeMinimaxAuthOwner(credentials, accountId);
27177
28172
  const withUploads = await uploadOversizedMedia(requestOptions, images, videos, credentials, accountId, this.options.fetchFn ?? fetch, signal);
27178
- const effectiveMax = clampOutputToContext(requestOptions.maxTokens ?? maxOutputTokensFor$1(options.model, contextWindow), contextWindow, estimatedInputTokens(requestOptions));
28173
+ const effectiveMax = clampOutputToContext(floorForcedThinkingTokens(options.model, requestOptions.maxTokens) ?? maxOutputTokensFor$1(options.model, contextWindow), contextWindow, estimatedInputTokens(requestOptions));
27179
28174
  const buildBodyForOwner = (owner) => {
27180
28175
  return assertRequestBodyFits(buildMinimaxRequest({
27181
28176
  ...withUploads,
@@ -27235,23 +28230,46 @@ var MinimaxCodeAdapter = class extends LlmAdapter {
27235
28230
  provider: PROVIDER_ID$2
27236
28231
  });
27237
28232
  let buffer = "";
28233
+ /**
28234
+ * Set the moment ANY chunk reaches the caller, and never cleared.
28235
+ *
28236
+ * Deliberately broader than "a content delta or a tool block": a retry would
28237
+ * duplicate a block-start the caller has already seen just as surely, so the
28238
+ * conservative reading is the one that cannot be wrong. It is what keeps the
28239
+ * in-band reclassification below pre-output only.
28240
+ */
28241
+ let outputStarted = false;
27238
28242
  try {
27239
- while (true) {
27240
- const { done, value } = await reader.read();
27241
- if (done) break;
27242
- buffer += decoder.decode(value, { stream: true });
27243
- const lines = buffer.split("\n");
27244
- buffer = lines.pop() ?? "";
27245
- for (const line of lines) {
27246
- for (const chunk of processMinimaxStreamLine(line, state)) yield chunk;
27247
- if (state.finished) return;
28243
+ try {
28244
+ while (true) {
28245
+ const { done, value } = await reader.read();
28246
+ if (done) break;
28247
+ buffer += decoder.decode(value, { stream: true });
28248
+ const lines = buffer.split("\n");
28249
+ buffer = lines.pop() ?? "";
28250
+ for (const line of lines) {
28251
+ for (const chunk of processMinimaxStreamLine(line, state)) {
28252
+ outputStarted = true;
28253
+ yield chunk;
28254
+ }
28255
+ if (state.finished) return;
28256
+ }
27248
28257
  }
28258
+ buffer += decoder.decode();
28259
+ if (buffer.trim() !== "") for (const line of buffer.split("\n")) for (const chunk of processMinimaxStreamLine(line, state)) {
28260
+ outputStarted = true;
28261
+ yield chunk;
28262
+ }
28263
+ if (state.finished) return;
28264
+ assertStreamComplete(state);
28265
+ for (const chunk of closeMinimaxStream(state)) {
28266
+ outputStarted = true;
28267
+ yield chunk;
28268
+ }
28269
+ } catch (error) {
28270
+ if (error instanceof LlmError && !outputStarted && error.code === "PROVIDER_ERROR" && state.streamError !== void 0) throw reclassifyInBandError(error, state.streamError, classifyMinimaxFailure);
28271
+ throw error;
27249
28272
  }
27250
- buffer += decoder.decode();
27251
- if (buffer.trim() !== "") for (const line of buffer.split("\n")) for (const chunk of processMinimaxStreamLine(line, state)) yield chunk;
27252
- if (state.finished) return;
27253
- assertStreamComplete(state);
27254
- for (const chunk of closeMinimaxStream(state)) yield chunk;
27255
28273
  } finally {
27256
28274
  reader.cancel().catch(() => void 0);
27257
28275
  }
@@ -27467,6 +28485,38 @@ function parseMinimaxCodePoolData(value) {
27467
28485
  return result;
27468
28486
  }
27469
28487
  /**
28488
+ * Nominal length of the two windows the Token Plan reports, in minutes.
28489
+ *
28490
+ * The line's own card names these two keys (`quotaWindowInterval` = 5 小时窗口,
28491
+ * `quotaWindowWeekly` = 每周窗口), and 5 hours / 7 days is what those words mean,
28492
+ * so the shared account card can name an unlabeled window from its length
28493
+ * instead of showing a generic one. The same two numbers label the equivalent
28494
+ * windows on the Command Code and Kimi Code lines.
28495
+ */
28496
+ const WINDOW_MINUTES = {
28497
+ interval: 300,
28498
+ weekly: 10080
28499
+ };
28500
+ /**
28501
+ * One cached read as the shared account card renders it.
28502
+ *
28503
+ * Every window is labeled through its LENGTH rather than with a string: the
28504
+ * canonical window names live in the client's dictionary, and the host has no
28505
+ * business shipping display text. A window whose consumed share nobody measured
28506
+ * is dropped by {@link quotaWindow} — a null percentage is not 0%, it is
28507
+ * "not stated", and a bar would lie about it. An `unlimited` window states no
28508
+ * consumed share at all and the per-account shape has no way to say
28509
+ * "unlimited", so it is dropped too rather than drawn as an empty bar.
28510
+ */
28511
+ function minimaxCodePoolQuota(quota) {
28512
+ if (quota === null || quota === void 0) return void 0;
28513
+ const windows = quota.windows === void 0 || quota.windows.length === 0 ? [quotaWindow$1("", quota.usedPercent, { resetsAt: quota.resetsAtMs })] : quota.windows.map((window) => quotaWindow$1("", window.usedPercent, {
28514
+ windowDurationMins: WINDOW_MINUTES[window.key],
28515
+ resetsAt: window.resetsAtMs
28516
+ }));
28517
+ return poolQuota(quota.fetchedAtMs, windows);
28518
+ }
28519
+ /**
27470
28520
  * MiniMax Code's account pool.
27471
28521
  *
27472
28522
  * It owns rotation and the write-back, exactly like the sibling lines, with one
@@ -27547,7 +28597,11 @@ var MinimaxCodeAccountPool = class extends AccountPoolCore {
27547
28597
  ...planName === void 0 ? {} : {
27548
28598
  planName,
27549
28599
  planLabel: planName
27550
- }
28600
+ },
28601
+ ...(() => {
28602
+ const quota = minimaxCodePoolQuota(getCachedQuotaFor$1(account.id));
28603
+ return quota === void 0 ? {} : { quota };
28604
+ })()
27551
28605
  };
27552
28606
  },
27553
28607
  liveCredentialsFor: (account) => this.nativeAuthority(account),
@@ -27634,6 +28688,34 @@ var MinimaxCodeAccountPool = class extends AccountPoolCore {
27634
28688
  });
27635
28689
  }
27636
28690
  /**
28691
+ * The account a usage read belongs to, together with the credential to read it
28692
+ * with.
28693
+ *
28694
+ * Authored here rather than in the routes because it is provider-shaped: the
28695
+ * routes need a credential and the ACCOUNT it was read for, so a snapshot can
28696
+ * be remembered against the row whose numbers it describes. The selection is
28697
+ * the core's own rule for a caller that names no account — the active one, then
28698
+ * the primary, then the first — repeated in these three lines because the core
28699
+ * exposes it only through `getFreshCredential`, which returns the credential
28700
+ * alone; the credential (and its single-flight refresh, write-back and
28701
+ * bookkeeping) still comes from the core, so no rotation rule lives here.
28702
+ *
28703
+ * No upstream request is made for the selection itself, and none of this moves
28704
+ * the rotation: reading quota is not serving a request.
28705
+ */
28706
+ async getQuotaReadTarget(fetchFn = fetch) {
28707
+ const data = await this.read();
28708
+ const target = data.accounts.find((account) => account.id === data.activeAccountId) ?? data.accounts.find((account) => account.isPrimary) ?? data.accounts[0];
28709
+ if (target === void 0) {
28710
+ await this.getFreshCredential(void 0, fetchFn);
28711
+ throw new Error("MiniMax Code 号池中没有账号。");
28712
+ }
28713
+ return {
28714
+ accountId: target.id,
28715
+ credentials: await this.getFreshCredential(target.id, fetchFn)
28716
+ };
28717
+ }
28718
+ /**
27637
28719
  * Pick the account for the next request.
27638
28720
  *
27639
28721
  * The returned credential carries its own region and record key, so a caller
@@ -27967,8 +29049,12 @@ function registerMinimaxCodeRoutes(ctx, store = new MinimaxCodeCredentialStore()
27967
29049
  * network, and the status route reads the snapshot through the cache instead.
27968
29050
  */
27969
29051
  const refreshQuota = async (force) => {
27970
- const credentials = await (options.accountPool === void 0 ? ensureAccessToken$2(store, { fetchFn }).catch(() => null) : options.accountPool.getFreshCredential(void 0, fetchFn).catch(() => null));
27971
- if (credentials === null) return null;
29052
+ const target = await (options.accountPool === void 0 ? ensureAccessToken$2(store, { fetchFn }).then((credentials) => ({
29053
+ accountId: void 0,
29054
+ credentials
29055
+ })).catch(() => null) : options.accountPool.getQuotaReadTarget(fetchFn).catch(() => null));
29056
+ if (target === null) return null;
29057
+ const credentials = target.credentials;
27972
29058
  const readToken = credentials.refreshToken;
27973
29059
  const isCredentialStale = async () => {
27974
29060
  const live = await store.readFresh().catch(() => null);
@@ -27978,10 +29064,11 @@ function registerMinimaxCodeRoutes(ctx, store = new MinimaxCodeCredentialStore()
27978
29064
  fetchFn,
27979
29065
  force,
27980
29066
  isCredentialStale,
29067
+ ...target.accountId === void 0 ? {} : { accountId: target.accountId },
27981
29068
  renewCredential: async () => await (options.accountPool === void 0 ? ensureAccessToken$2(store, {
27982
29069
  fetchFn,
27983
29070
  force: true
27984
- }) : options.accountPool.renewCredential(void 0, fetchFn)).catch(() => null)
29071
+ }) : options.accountPool.renewCredential(target.accountId, fetchFn)).catch(() => null)
27985
29072
  }).catch(() => null);
27986
29073
  };
27987
29074
  /**
@@ -30046,6 +31133,9 @@ const CATALOG_CACHE_TTL_MS$1 = 1800 * 1e3;
30046
31133
  * every cache miss.
30047
31134
  */
30048
31135
  const catalogSnapshotScopesLoaded = /* @__PURE__ */ new Set();
31136
+ function getCachedCatalog$1() {
31137
+ return cachedCatalog?.models ?? [];
31138
+ }
30049
31139
  function clearCachedCatalog$1() {
30050
31140
  cachedCatalog = void 0;
30051
31141
  catalogCacheEpoch += 1;
@@ -30237,16 +31327,64 @@ function billingMeters(billing) {
30237
31327
  }
30238
31328
  return meters;
30239
31329
  }
30240
- let cachedQuota;
31330
+ /**
31331
+ * One newest snapshot PER ACCOUNT, keyed by the snapshot's own `account.id` —
31332
+ * the same public key the pool addresses an account with.
31333
+ *
31334
+ * Quota follows the account: rotation decides which account spends the next
31335
+ * request, so a single slot only ever described whichever account was read
31336
+ * last. The pool's card draws every account its own reading, so each account
31337
+ * keeps its own entry; past the cap the oldest read is evicted.
31338
+ *
31339
+ * This map is a memory read only. Nothing here fetches — the settings card
31340
+ * builds its account list from it, and that must never cost one upstream
31341
+ * request per pooled account.
31342
+ */
31343
+ const cachedQuotaEntries = /* @__PURE__ */ new Map();
31344
+ /** How many accounts' snapshots are remembered; mirrors the Codex usage service. */
31345
+ const QUOTA_CACHE_LIMIT = 20;
31346
+ /** The entry read most recently, whichever account it belongs to. */
31347
+ function newestCachedQuota() {
31348
+ let newest;
31349
+ for (const snapshot of cachedQuotaEntries.values()) if (newest === void 0 || (snapshot.fetchedAt || 0) > (newest.fetchedAt || 0)) newest = snapshot;
31350
+ return newest;
31351
+ }
31352
+ /** Remember one account's newest snapshot, bounded to the accounts in use. */
31353
+ function rememberCachedQuota(snapshot) {
31354
+ cachedQuotaEntries.set(snapshot.account.id, snapshot);
31355
+ while (cachedQuotaEntries.size > QUOTA_CACHE_LIMIT) {
31356
+ const oldest = [...cachedQuotaEntries.entries()].sort((left, right) => (left[1].fetchedAt || 0) - (right[1].fetchedAt || 0))[0];
31357
+ if (oldest === void 0) break;
31358
+ cachedQuotaEntries.delete(oldest[0]);
31359
+ }
31360
+ }
30241
31361
  let quotaInFlight$1 = null;
30242
31362
  let quotaInFlightAccountId = null;
30243
31363
  let quotaCacheEpoch = 0;
31364
+ /**
31365
+ * The snapshot read most recently, whichever account it belongs to.
31366
+ *
31367
+ * Kept with a no-argument signature for backward compatibility; the page-level
31368
+ * facts use {@link getCachedQuotaFor} instead, because they describe one
31369
+ * account and must never be another account's numbers.
31370
+ */
30244
31371
  function getCachedQuota$5() {
30245
- return cachedQuota;
31372
+ return newestCachedQuota();
31373
+ }
31374
+ /**
31375
+ * The snapshot remembered for one account, if it was read before.
31376
+ *
31377
+ * This is the lookup the account pool calls while it builds its summaries: a
31378
+ * pure memory read, never a fetch. Passing no account keeps the pre-pool
31379
+ * behavior of returning whatever was fetched last.
31380
+ */
31381
+ function getCachedQuotaFor(accountId) {
31382
+ if (accountId === void 0 || accountId === null) return newestCachedQuota();
31383
+ return cachedQuotaEntries.get(accountId);
30246
31384
  }
30247
31385
  function clearCachedQuota$5() {
30248
31386
  quotaCacheEpoch += 1;
30249
- cachedQuota = void 0;
31387
+ cachedQuotaEntries.clear();
30250
31388
  quotaInFlight$1 = null;
30251
31389
  quotaInFlightAccountId = null;
30252
31390
  }
@@ -30263,7 +31401,8 @@ async function fetchAccountQuota$4(store = new FileCredentialStore$4(), fetchFn
30263
31401
  });
30264
31402
  if (credentials === null) throw new Error(`Not signed in to ${PROVIDER_NAME$1}.`);
30265
31403
  const requestedAccountId = workBuddyAccountId(credentials);
30266
- if (!force && cachedQuota && cachedQuota.account.id === requestedAccountId && Date.now() - (cachedQuota.fetchedAt || 0) < 12e4) return cachedQuota;
31404
+ const cached = cachedQuotaEntries.get(requestedAccountId);
31405
+ if (!force && cached !== void 0 && Date.now() - (cached.fetchedAt || 0) < 12e4) return cached;
30267
31406
  if (quotaInFlight$1 && quotaInFlightAccountId === requestedAccountId) return quotaInFlight$1;
30268
31407
  const epoch = quotaCacheEpoch;
30269
31408
  const request = (async () => {
@@ -30299,7 +31438,7 @@ async function fetchAccountQuota$4(store = new FileCredentialStore$4(), fetchFn
30299
31438
  sources: ["billing/meter/get-user-resource"]
30300
31439
  };
30301
31440
  if (epoch !== quotaCacheEpoch) return snapshot;
30302
- cachedQuota = snapshot;
31441
+ rememberCachedQuota(snapshot);
30303
31442
  return snapshot;
30304
31443
  })();
30305
31444
  quotaInFlight$1 = request;
@@ -30614,7 +31753,7 @@ async function getWorkBuddyWebStatus(store, modelSettings, preferences, options
30614
31753
  const catalog = live.length > 0 ? live : FALLBACK_MODELS$1;
30615
31754
  const available = modelsForRegion(credentials?.region ?? "intl", catalog).map((model) => model.id);
30616
31755
  const models = buildModelOptions(catalog, available, resolveEnabledModelIds$2(settings.enabledModelIds, available, enabled), settings.contextWindowOverrides);
30617
- const quota = getCachedQuota$5();
31756
+ const quota = getCachedQuotaFor(credentials === null ? null : accountFromCredentials$1(credentials).id);
30618
31757
  const poolData = options.accountPool === void 0 ? null : await options.accountPool.read().catch(() => null);
30619
31758
  const poolAccounts = options.accountPool === void 0 ? [] : await options.accountPool.listAccounts().catch(() => []);
30620
31759
  const hiddenIds = new Set(settings.hiddenAccountIds);
@@ -30673,12 +31812,10 @@ function registerWorkBuddyRoutes(ctx, store, modelSettings, preferences, options
30673
31812
  accountId: settings.selectedAccountId,
30674
31813
  hiddenAccountIds: settings.hiddenAccountIds
30675
31814
  });
30676
- const cached = getCachedQuota$5();
30677
- const quotaMatches = cached?.account.id === (credentials === null ? void 0 : accountFromCredentials$1(credentials).id);
30678
- if (credentials !== null && (!quotaMatches || cached === void 0 || Date.now() - (cached.fetchedAt || 0) > 12e4)) {
30679
- if (!quotaMatches) clearCachedQuota$5();
31815
+ const cached = getCachedQuotaFor(credentials === null ? null : accountFromCredentials$1(credentials).id);
31816
+ if (credentials !== null && (cached === void 0 || Date.now() - (cached.fetchedAt || 0) > 12e4)) {
30680
31817
  const refresh = () => fetchAccountQuota$4(store, fetchFn, false, settings.selectedAccountId, settings.hiddenAccountIds);
30681
- if (cached === void 0 || !quotaMatches) await quotaRefresh.run(refresh);
31818
+ if (cached === void 0) await quotaRefresh.run(refresh);
30682
31819
  else quotaRefresh.start(refresh);
30683
31820
  }
30684
31821
  return sendJson$1(response, 200, {
@@ -32164,6 +33301,47 @@ function defaultAliasFor(credentials, position) {
32164
33301
  return credentials.nickname || credentials.uin || credentials.uid || `账号 ${position}`;
32165
33302
  }
32166
33303
  /**
33304
+ * A meter's consumed share, from whichever fraction this line measured.
33305
+ *
33306
+ * `usedFraction` is the DTO's own "share consumed"; `remainingFraction` is the
33307
+ * same statement read from the other end, and is the one the rest of the card
33308
+ * renders. A meter that states neither is dropped (null) rather than drawn at
33309
+ * 0, which would claim a full window no reading supports.
33310
+ */
33311
+ function meterUsedPercent(meter) {
33312
+ if (typeof meter.usedFraction === "number" && Number.isFinite(meter.usedFraction)) return meter.usedFraction * 100;
33313
+ if (typeof meter.remainingFraction === "number" && Number.isFinite(meter.remainingFraction)) return (1 - meter.remainingFraction) * 100;
33314
+ return null;
33315
+ }
33316
+ /**
33317
+ * One cached snapshot as the shared account card renders it.
33318
+ *
33319
+ * Only what the DTO states is published: every meter that measured a share
33320
+ * becomes a window, and a meter that measured none is dropped rather than drawn
33321
+ * at 0 — which would claim an unspent allowance no reading supports.
33322
+ *
33323
+ * Exported for the pool's regression tests; the card only reaches it through
33324
+ * the summary hook.
33325
+ */
33326
+ function workBuddyPoolQuota(snapshot) {
33327
+ const windows = snapshot.meters.map((meter) => quotaWindow$1(meter.label, meterUsedPercent(meter), { resetsAt: meter.resetsAt }));
33328
+ if (!snapshot.meters.some((meter) => meter.id === "cycle") && snapshot.cycleCredits !== null && snapshot.cycleCredits > 0 && snapshot.cycleUsedCredits !== null) windows.push(quotaWindow$1(snapshot.packageName ?? "Billing cycle", snapshot.cycleUsedCredits / snapshot.cycleCredits * 100, { resetsAt: snapshot.cycleEndsAt }));
33329
+ return poolQuota(snapshot.fetchedAt, windows);
33330
+ }
33331
+ /**
33332
+ * This account's own newest snapshot, ready for the account card.
33333
+ *
33334
+ * Quota follows the account, so the pool reports the snapshot the line already
33335
+ * remembered for THIS account id. The lookup is a pure memory read: the card
33336
+ * builds its account list here, and that must never cost one upstream request
33337
+ * per pooled account. Undefined means exactly that — never read — which is not
33338
+ * the same statement as "nothing used".
33339
+ */
33340
+ function quotaFor(accountId) {
33341
+ const snapshot = getCachedQuotaFor(accountId);
33342
+ return snapshot === void 0 ? void 0 : workBuddyPoolQuota(snapshot);
33343
+ }
33344
+ /**
32167
33345
  * WorkBuddy's account pool.
32168
33346
  *
32169
33347
  * Desktop accounts are adopted into the pool so they can be scheduled and
@@ -32224,7 +33402,11 @@ var WorkBuddyAccountPool = class extends AccountPoolCore {
32224
33402
  ...account.credentials.domain === "" ? {} : { domain: account.credentials.domain },
32225
33403
  ...account.credentials.backend === "" ? {} : { backend: account.credentials.backend },
32226
33404
  ...account.credentials.accountType === void 0 ? {} : { accountType: account.credentials.accountType },
32227
- ...account.credentials.expiresAt > 0 ? {} : { expiresAt: void 0 }
33405
+ ...account.credentials.expiresAt > 0 ? {} : { expiresAt: void 0 },
33406
+ ...(() => {
33407
+ const quota = quotaFor(account.id);
33408
+ return quota === void 0 ? {} : { quota };
33409
+ })()
32228
33410
  }),
32229
33411
  preferAccountId: () => this.resolveSelectionId(options.selection?.().selectedAccountId ?? null),
32230
33412
  emptyMessage: `Not signed in to ${PROVIDER_NAME$1}. Sign in with the CodeBuddy desktop client, or add an account from Settings > WorkBuddy.`,
@@ -34233,6 +35415,10 @@ const catalogInFlight = /* @__PURE__ */ new Map();
34233
35415
  function clearCachedCatalog() {
34234
35416
  catalogCache = null;
34235
35417
  }
35418
+ /** The listing currently held, or an empty array before the first load. */
35419
+ function getCachedCatalog() {
35420
+ return catalogCache?.models ?? [];
35421
+ }
34236
35422
  /**
34237
35423
  * Read one entry of the live listing.
34238
35424
  *
@@ -34528,22 +35714,108 @@ function parseQuotaHeaders(headers) {
34528
35714
  }
34529
35715
  };
34530
35716
  }
34531
- let quotaCache = null;
35717
+ /**
35718
+ * Cache key of one account's snapshot.
35719
+ *
35720
+ * A pool row id when the caller names one — that is the identity that survives a
35721
+ * token refresh and the one the account card asks with — and otherwise the
35722
+ * credential's token tail, which is all a single-credential process has. The
35723
+ * `acc:` prefix keeps the two apart, so no row id can ever be answered with an
35724
+ * entry written under a token tail.
35725
+ */
35726
+ function quotaEntryKey(accountId, credentials) {
35727
+ return accountId === void 0 || accountId === "" ? accountKeyFor(credentials) : `acc:${accountId}`;
35728
+ }
35729
+ /**
35730
+ * How many accounts' snapshots are remembered.
35731
+ *
35732
+ * Mirrors the bound the ChatGPT line's usage service applies to its own
35733
+ * per-account map: room for every account a user is likely to pool, and no way
35734
+ * for a long-lived process to accumulate one snapshot per token it has seen.
35735
+ */
35736
+ const QUOTA_ACCOUNT_LIMIT = 20;
35737
+ /**
35738
+ * The newest snapshot of each account, keyed by {@link quotaEntryKey}.
35739
+ *
35740
+ * ONE ENTRY PER ACCOUNT rather than one slot for the line: quota follows the
35741
+ * account, and rotation decides which account spends the next request, so a
35742
+ * single slot could only ever describe whichever account was read last. This is
35743
+ * a memory read only — the settings card builds its account list from it, and
35744
+ * that must never cost one upstream request per pooled account.
35745
+ */
35746
+ const quotaCache = /* @__PURE__ */ new Map();
35747
+ /**
35748
+ * Key of the entry written most recently.
35749
+ *
35750
+ * The pre-pool card asks "whatever was read last", and a header reading that
35751
+ * names no account may only merge into the snapshot it was read beside. Both
35752
+ * need the last WRITE rather than a scan: two accounts can be written inside one
35753
+ * millisecond, and a tie would otherwise be broken by map order.
35754
+ */
35755
+ let quotaLastKey = null;
34532
35756
  const quotaInFlight = /* @__PURE__ */ new Map();
34533
- /** Drop the cached snapshot. Called on sign-out and after a credential change. */
35757
+ /** Drop every cached snapshot. Called on sign-out and after a credential change. */
34534
35758
  function clearCachedQuota$1() {
34535
- quotaCache = null;
35759
+ quotaCache.clear();
35760
+ quotaLastKey = null;
35761
+ }
35762
+ /** The entry written most recently, whichever account it belongs to. */
35763
+ function newestQuotaEntry() {
35764
+ return quotaLastKey === null ? null : quotaCache.get(quotaLastKey) ?? null;
35765
+ }
35766
+ /**
35767
+ * Remember one account's newest snapshot, bounded to the accounts in use.
35768
+ *
35769
+ * The oldest entry is measured by {@link QuotaCacheEntry.observedAt} rather than
35770
+ * by `fetchedAt`: a snapshot kept warm by response headers has no full read
35771
+ * behind it at all, and it is still the newest thing known about that account.
35772
+ */
35773
+ function rememberQuota(entry) {
35774
+ quotaCache.set(entry.key, entry);
35775
+ quotaLastKey = entry.key;
35776
+ while (quotaCache.size > QUOTA_ACCOUNT_LIMIT) {
35777
+ const oldest = [...quotaCache.entries()].sort((left, right) => left[1].observedAt - right[1].observedAt)[0];
35778
+ if (oldest === void 0) break;
35779
+ quotaCache.delete(oldest[0]);
35780
+ if (quotaLastKey === oldest[0]) quotaLastKey = null;
35781
+ }
34536
35782
  }
34537
35783
  /**
34538
35784
  * The cached snapshot, optionally only when it belongs to this credential.
34539
35785
  *
34540
35786
  * With one stored account the argument is unnecessary; it exists so a later
34541
35787
  * pool chunk cannot render one account's usage under another account's name.
35788
+ * Given a credential the answer is THAT account's own newest snapshot, which is
35789
+ * what keeps two pooled accounts apart: a later read of a different account
35790
+ * cannot displace it. Without one the pre-pool answer is kept — whatever was
35791
+ * written last — because the single-account card has no identity to ask with.
34542
35792
  */
34543
35793
  function getCachedQuota$1(credentials) {
34544
- if (quotaCache === null) return null;
34545
- if (credentials === void 0) return quotaCache.quota;
34546
- return quotaCache.key === accountKeyFor(credentials) ? quotaCache.quota : null;
35794
+ if (credentials === void 0) return newestQuotaEntry()?.quota ?? null;
35795
+ return cachedQuotaFor(credentials);
35796
+ }
35797
+ /**
35798
+ * The newest snapshot held for one credential's account, for display only.
35799
+ *
35800
+ * The token-tail form, kept for a composition with a single stored credential.
35801
+ * A pooled caller asks {@link cachedQuotaForPool} with the row id instead: a
35802
+ * token is rotated by every refresh, so an entry filed under a tail is lost the
35803
+ * moment the account renews.
35804
+ */
35805
+ function cachedQuotaFor(credentials) {
35806
+ return quotaCache.get(accountKeyFor(credentials))?.quota ?? null;
35807
+ }
35808
+ /**
35809
+ * The newest snapshot held for one pool row, for display only.
35810
+ *
35811
+ * The pool asks here while it builds the account list for the settings card, so
35812
+ * this is a pure memory read: it never fetches. Null means nothing was ever read
35813
+ * for this account, which is a different statement from "nothing used". A row id
35814
+ * that is undefined falls back to the credential's own tail, which is what a
35815
+ * composition without a pool has.
35816
+ */
35817
+ function cachedQuotaForPool(accountId, credentials) {
35818
+ return quotaCache.get(quotaEntryKey(accountId, credentials))?.quota ?? null;
34547
35819
  }
34548
35820
  /**
34549
35821
  * Read the account's usage meters.
@@ -34561,12 +35833,13 @@ function getCachedQuota$1(credentials) {
34561
35833
  * up staring at a card that claims they are fine.
34562
35834
  */
34563
35835
  function fetchAccountQuota$1(credentials, options = {}) {
34564
- const key = accountKeyFor(credentials);
35836
+ const key = quotaEntryKey(options.accountId, credentials);
34565
35837
  const at = now();
34566
- if (options.force !== true && quotaCache !== null && quotaCache.key === key) {
34567
- const warm = at - quotaCache.observedAt < QUOTA_CACHE_TTL_MS;
34568
- const freshFullRead = quotaCache.fullReadAt !== null && at - quotaCache.fullReadAt < 3e5;
34569
- if (warm && freshFullRead) return Promise.resolve(quotaCache.quota);
35838
+ const cached = quotaCache.get(key);
35839
+ if (options.force !== true && cached !== void 0) {
35840
+ const warm = at - cached.observedAt < QUOTA_CACHE_TTL_MS;
35841
+ const freshFullRead = cached.fullReadAt !== null && at - cached.fullReadAt < 3e5;
35842
+ if (warm && freshFullRead) return Promise.resolve(cached.quota);
34570
35843
  }
34571
35844
  const pending = quotaInFlight.get(key);
34572
35845
  if (pending !== void 0) return pending;
@@ -34581,9 +35854,9 @@ function fetchAccountQuota$1(credentials, options = {}) {
34581
35854
  return run;
34582
35855
  }
34583
35856
  async function performQuotaFetch(credentials, options) {
34584
- const key = accountKeyFor(credentials);
35857
+ const key = quotaEntryKey(options.accountId, credentials);
34585
35858
  const fetchFn = options.fetchFn ?? fetch;
34586
- const previous = quotaCache !== null && quotaCache.key === key ? quotaCache.quota : null;
35859
+ const previous = quotaCache.get(key)?.quota ?? null;
34587
35860
  let response;
34588
35861
  try {
34589
35862
  response = await fetchFn("https://api.anthropic.com/api/oauth/usage", {
@@ -34621,12 +35894,12 @@ async function performQuotaFetch(credentials, options) {
34621
35894
  status: null,
34622
35895
  representativeClaim: null
34623
35896
  };
34624
- quotaCache = {
35897
+ rememberQuota({
34625
35898
  quota,
34626
35899
  key,
34627
35900
  fullReadAt: at,
34628
35901
  observedAt: at
34629
- };
35902
+ });
34630
35903
  return quota;
34631
35904
  }
34632
35905
  /**
@@ -36641,6 +37914,7 @@ function processStreamLine(line, state) {
36641
37914
  const error = isRecord$5(event.error) ? event.error : {};
36642
37915
  const message = asString$2(error.message) ?? "unknown error";
36643
37916
  const kind = asString$2(error.type);
37917
+ state.streamError = error;
36644
37918
  throw new LlmError$1("Claude stream error" + (kind === void 0 ? "" : " (" + kind + ")") + ": " + message, isContextOverflow(error) ? CONTEXT_OVERFLOW_CODE : "PROVIDER_ERROR");
36645
37919
  }
36646
37920
  return out;
@@ -37814,6 +39088,13 @@ function cancelLogin() {
37814
39088
  * - `request` -> PROVIDER_ERROR;
37815
39089
  * - `network` -> TRANSPORT.
37816
39090
  *
39091
+ * An in-band `error` event inside a 200 stream carries the same envelope and is
39092
+ * classified the same way while nothing has reached the caller; after the first
39093
+ * chunk it is surfaced as PROVIDER_ERROR, for the reason in note 1
39094
+ * ({@link inBandStreamVerdict}). A reclassified in-band failure is weighed by
39095
+ * {@link shouldRotateAccount} exactly as a non-2xx one is, so an account-scoped
39096
+ * limit reported inside the stream still takes that account out of rotation.
39097
+ *
37817
39098
  * A reported-client-version rejection is a REQUEST problem even though it
37818
39099
  * arrives as a 400 that mentions the client: it must NOT sign the user out, and
37819
39100
  * it has a real remedy (raise the reported version), so it is handled
@@ -38213,8 +39494,19 @@ var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
38213
39494
  * a later reordering of this method cannot silently drop it.
38214
39495
  */
38215
39496
  let outputStarted = false;
39497
+ /**
39498
+ * The account the response now being streamed was issued with.
39499
+ *
39500
+ * Hoisted out of the loop because the in-stream failure path needs it: a
39501
+ * rate limit reported inside a 200 stream belongs to the account that was
39502
+ * asked, and that account is what the failure takes out of rotation — the
39503
+ * harness is about to repeat this request, and repeating it against the same
39504
+ * exhausted account fails identically.
39505
+ */
39506
+ let usedAccountId;
38216
39507
  while (true) {
38217
39508
  const { credentials, accountId } = await this.resolveCredential(pool, tried, fetchFn, signal);
39509
+ usedAccountId = accountId;
38218
39510
  response = await this.attemptRequest(credentials, requestOptions, images, toolNames, thinking, settings, signal, fetchFn);
38219
39511
  if (response.ok) break;
38220
39512
  const detail = (await response.text().catch(() => "")).slice(0, 2e3);
@@ -38260,7 +39552,12 @@ var ClaudeAdapter = class ClaudeAdapter extends LlmAdapter {
38260
39552
  yield chunk;
38261
39553
  }
38262
39554
  } catch (error) {
38263
- if (error instanceof LlmError) throw error;
39555
+ if (error instanceof LlmError) {
39556
+ if (outputStarted || error.code !== "PROVIDER_ERROR" || state.streamError === void 0) throw error;
39557
+ const verdict = inBandStreamVerdict(error, state.streamError, response.headers);
39558
+ if (verdict.failure !== null && shouldRotateAccount(verdict.failure, outputStarted) && usedAccountId !== void 0) await pool?.markCooldown(usedAccountId, cooldownMsFor(verdict.failure), "Claude(订阅) in-stream 429").catch(() => void 0);
39559
+ throw verdict.error;
39560
+ }
38264
39561
  if (signal.aborted) throw new LlmError("Claude request aborted", "ABORTED", { cause: error });
38265
39562
  throw new LlmError("Claude stream failed: " + (error instanceof Error ? error.message : String(error)), outputStarted ? "PROVIDER_ERROR" : "TRANSPORT", { cause: error });
38266
39563
  }
@@ -38350,6 +39647,49 @@ function codeForFailure(failure) {
38350
39647
  return "PROVIDER_ERROR";
38351
39648
  }
38352
39649
  /**
39650
+ * The DSH error an in-band `error` event becomes when it arrived BEFORE any
39651
+ * chunk reached the caller.
39652
+ *
39653
+ * Anthropic can answer 200 and then report the failure inside the stream, in
39654
+ * the same `{ error: { type, message } }` envelope a non-2xx body carries. The
39655
+ * mapper types every such event PROVIDER_ERROR, because it cannot know whether
39656
+ * output has started — and PROVIDER_ERROR is outside the retry set, so an
39657
+ * `overloaded_error` delivered this way ended the turn on the first try while
39658
+ * the identical overload delivered as a 529 is retried. Before any output a
39659
+ * fresh request is still free (module note 1), so the envelope is classified
39660
+ * exactly as a response body would be, and a transient verdict (overloaded,
39661
+ * server, rate limit) takes its retryable code.
39662
+ *
39663
+ * Everything else keeps the mapper's verdict: a request problem, a context
39664
+ * overflow, a credential refusal, and a type this line does not recognize. The
39665
+ * last matters: classifyFailure falls back to the status line for an unknown
39666
+ * type, and no status here describes the failure — the response itself was a
39667
+ * 200 — so the status passed is 0 and never consulted for a recognized type.
39668
+ *
39669
+ * The message stays the mapper's, which names the wire type the user saw.
39670
+ *
39671
+ * The classified failure rides along beside the error so the caller can weigh it
39672
+ * with {@link shouldRotateAccount} instead of re-deriving what it says. It is
39673
+ * null whenever the mapper's verdict stands, so a caller never has to guess
39674
+ * whether `accountScoped` is meaningful.
39675
+ *
39676
+ * @param thrown - the mapper's PROVIDER_ERROR (or context-overflow) verdict.
39677
+ * @param streamError - the event's wire `error` object, from the stream state.
39678
+ * @param headers - the 200 response's headers, read for the rate-limit verdict.
39679
+ */
39680
+ function inBandStreamVerdict(thrown, streamError, headers) {
39681
+ const failure = classifyFailure(0, JSON.stringify({ error: streamError }), headers);
39682
+ if (failure.type === null || !failure.retryable) return {
39683
+ error: thrown,
39684
+ failure: null
39685
+ };
39686
+ const retryAfter = failure.retryAfterMs !== null && failure.retryAfterMs > 0 ? failure.retryAfterMs : void 0;
39687
+ return {
39688
+ error: new LlmError(thrown.message, codeForFailure(failure), retryAfter === void 0 ? {} : { providerRetryAfterMs: retryAfter }),
39689
+ failure
39690
+ };
39691
+ }
39692
+ /**
38353
39693
  * Turn one classified failure into the DSH error the caller sees.
38354
39694
  *
38355
39695
  * The facts the harness needs ride along: the HTTP status, and the delay the
@@ -39186,6 +40526,32 @@ async function refreshClaudeCredential(credentials, fetchFn) {
39186
40526
  };
39187
40527
  }
39188
40528
  /**
40529
+ * One account's own newest quota snapshot, in the shape the account card draws.
40530
+ *
40531
+ * The snapshot comes from 'client.ts''s per-account cache, and this is where
40532
+ * this line's own numbers are translated into the shared shape. Every judgement
40533
+ * below trades a possibly-nice-looking bar for an honest one:
40534
+ *
40535
+ * - a window whose consumed share nobody measured is DROPPED (`quotaWindow`
40536
+ * returns null), because "0% used" and "not stated" are different claims;
40537
+ * - the share falls back to `100 - remainingPercent` only when that field was
40538
+ * stated; when the payload stated NEITHER share there is nothing to draw;
40539
+ * - the read time is the full read's own `fetchedAt`, or — for a snapshot that
40540
+ * only response headers ever kept warm, which performed no full read — the
40541
+ * `observedAt` instant those numbers arrived. Either way it is when this line
40542
+ * saw the numbers, never when upstream served them.
40543
+ *
40544
+ * A null snapshot means "this account was never read", which `poolQuota` reports
40545
+ * as undefined so the card can say exactly that instead of drawing a zero.
40546
+ */
40547
+ function claudePoolQuota(snapshot) {
40548
+ if (snapshot === null) return void 0;
40549
+ return poolQuota(snapshot.fetchedAt ?? snapshot.observedAt, snapshot.windows.map((window) => quotaWindow$1(window.label, window.usedPercent ?? (window.remainingPercent === null ? null : 100 - window.remainingPercent), {
40550
+ windowDurationMins: window.windowMinutes,
40551
+ resetsAt: typeof window.resetsAt === "string" ? Date.parse(window.resetsAt) : null
40552
+ })));
40553
+ }
40554
+ /**
39189
40555
  * The Claude subscription account pool.
39190
40556
  *
39191
40557
  * Read the module comment first: it carries the two rules this class exists to
@@ -39283,6 +40649,7 @@ var ClaudeAccountPool = class extends AccountPoolCore {
39283
40649
  const subscriptionType = account.subscriptionType ?? credentials.subscriptionType;
39284
40650
  const adopted = account.adopted === true;
39285
40651
  const expired = adopted && adoptedCredentialExpired(credentials);
40652
+ const quota = claudePoolQuota(cachedQuotaForPool(account.id, credentials));
39286
40653
  return {
39287
40654
  ...base,
39288
40655
  ...email === void 0 ? {} : { email },
@@ -39294,6 +40661,7 @@ var ClaudeAccountPool = class extends AccountPoolCore {
39294
40661
  source: account.source,
39295
40662
  removable: !adopted,
39296
40663
  ...account.sourcePath === void 0 ? {} : { sourcePath: account.sourcePath },
40664
+ ...quota === void 0 ? {} : { quota },
39297
40665
  ...expired ? {
39298
40666
  authStatus: "expired",
39299
40667
  authFailedReason: ADOPTED_CREDENTIAL_EXPIRED_HINT
@@ -39860,7 +41228,7 @@ async function getClaudeWebStatus(store, modelSettings, preferences, options = {
39860
41228
  const catalog = live.length > 0 ? live : FALLBACK_MODELS;
39861
41229
  const available = catalog.map((model) => model.id);
39862
41230
  const models = buildClaudeModelOptions(catalog, available, resolveEnabledModelIds(settings.enabledModelIds, available, enabled), settings.contextWindowOverrides);
39863
- const quota = credential === void 0 ? getCachedQuota$1() : getCachedQuota$1(credential);
41231
+ const quota = credential === void 0 ? getCachedQuota$1() : cachedQuotaForPool(active.id, credential);
39864
41232
  const poolData = options.accountPool === void 0 ? null : await options.accountPool.read().catch(() => null);
39865
41233
  const poolAccounts = options.accountPool === void 0 ? [] : await options.accountPool.listAccounts().catch(() => []);
39866
41234
  const presence = await claudeCodeCredentialPresence(options.adoptPaths ?? claudeCodeCredentialPaths()).catch(() => ({
@@ -39994,13 +41362,14 @@ function registerClaudeRoutes(ctx, store, modelSettings, preferences, options =
39994
41362
  if (path === "" || path === "status") {
39995
41363
  if (method !== "GET") return sendMethodNotAllowed(response);
39996
41364
  const active = await activeAccount();
39997
- const cached = active.credentials === void 0 ? null : getCachedQuota$1(active.credentials);
41365
+ const cached = active.credentials === void 0 ? null : cachedQuotaForPool(active.id, active.credentials);
39998
41366
  const stale = cached === null || Date.now() - (cached.fetchedAt || 0) > 12e4;
39999
41367
  if (active.credentials !== void 0 && stale) {
40000
41368
  const credentials = active.credentials;
40001
41369
  const refresh = () => fetchAccountQuota$1(credentials, {
40002
41370
  fetchFn,
40003
- signal: quotaAbort.signal
41371
+ signal: quotaAbort.signal,
41372
+ accountId: active.id
40004
41373
  });
40005
41374
  if (cached === null) await quotaRefresh.run(refresh);
40006
41375
  else quotaRefresh.start(refresh);
@@ -40122,7 +41491,8 @@ function registerClaudeRoutes(ctx, store, modelSettings, preferences, options =
40122
41491
  await fetchAccountQuota$1(credentials, {
40123
41492
  fetchFn,
40124
41493
  force: method === "POST",
40125
- signal: quotaAbort.signal
41494
+ signal: quotaAbort.signal,
41495
+ accountId: active.id
40126
41496
  });
40127
41497
  } catch (error) {
40128
41498
  quotaError = error instanceof Error ? error.message : String(error);
@@ -43738,39 +45108,24 @@ function apply(ctx, pluginConfig = {}) {
43738
45108
  provider,
43739
45109
  limits: providerLimits
43740
45110
  })]));
43741
- const antigravityAdapter = new AntigravityAdapter(antigravityStore, antigravityModelSettings, antigravityPreferences, {
43742
- fetchFn: modelFetch.antigravity,
43743
- attachments: ctx.attachments
43744
- }, antigravityAccountPool);
43745
- let antigravityRegistration;
43746
- let antigravityConflict = null;
43747
- const claimAntigravityRoute = () => {
43748
- if (antigravityRegistration !== void 0) return;
43749
- try {
43750
- antigravityRegistration = ctx.llm.registerAdapter([PROVIDER_ID$6], antigravityAdapter);
43751
- if (antigravityConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] Antigravity route "${PROVIDER_ID$6}" is now served by this plugin`);
43752
- antigravityConflict = null;
43753
- } catch (error) {
43754
- antigravityConflict = error instanceof Error ? error.message : String(error);
43755
- ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$6}" is already owned by another adapter; Antigravity models keep being served by that one until its configuration is removed (${antigravityConflict})`);
43756
- }
43757
- };
43758
- claimAntigravityRoute();
43759
- const antigravityRouteWatch = typeof ctx.on === "function" ? ctx.on("llm/adapters-updated", () => {
43760
- claimAntigravityRoute();
43761
- }) : void 0;
45111
+ const antigravityClaim = claimProviderRoute(ctx, {
45112
+ providerId: PROVIDER_ID$6,
45113
+ label: PROVIDER_NAME$6,
45114
+ adapter: new AntigravityAdapter(antigravityStore, antigravityModelSettings, antigravityPreferences, {
45115
+ fetchFn: modelFetch.antigravity,
45116
+ attachments: ctx.attachments
45117
+ }, antigravityAccountPool)
45118
+ });
43762
45119
  const disposeAntigravityRoutes = registerAntigravityRoutes(ctx, antigravityStore, antigravityModelSettings, antigravityPreferences, proxyFetch, antigravityAccountPool);
43763
45120
  const disposeCommandCodeRoutes = registerCommandCodeRoutes(ctx, commandCodeStore, commandCodeModelSettings, commandCodePreferences, {
43764
45121
  fetchFn: proxyFetch,
43765
- serving: () => commandCodeRegistration !== void 0,
43766
- conflict: () => commandCodeConflict
45122
+ serving: () => commandCodeClaim.serving(),
45123
+ conflict: () => commandCodeClaim.conflict()
43767
45124
  }, commandCodeAccountPool);
43768
45125
  const commandCodeAdapter = new CommandCodeAdapter(commandCodeStore, commandCodeModelSettings, commandCodePreferences, {
43769
45126
  fetchFn: modelFetch["command-code"],
43770
45127
  attachments: ctx.attachments
43771
45128
  }, commandCodeAccountPool);
43772
- let commandCodeRegistration;
43773
- let commandCodeConflict = null;
43774
45129
  const ollamaStore = new FileCredentialStore$5();
43775
45130
  const ollamaModelSettings = new FileModelSettingsStore$5();
43776
45131
  const ollamaAccountPool = new OllamaAccountPool({ store: ollamaStore });
@@ -43778,92 +45133,48 @@ function apply(ctx, pluginConfig = {}) {
43778
45133
  fetchFn: modelFetch.ollama,
43779
45134
  attachments: ctx.attachments
43780
45135
  }, ollamaAccountPool);
43781
- let ollamaRegistration;
43782
- let ollamaConflict = null;
43783
- const kimiCodeAdapter = new KimiCodeAdapter(kimiCodeStore, kimiCodeModelSettings, kimiCodePreferences, {
43784
- fetchFn: modelFetch["kimi-code"],
43785
- attachments: ctx.attachments,
43786
- videos: { readVideo: async (ref) => ({
43787
- data: await readVideoBytes(ref),
43788
- mediaType: ref.mediaType
43789
- }) }
43790
- }, kimiCodeAccountPool);
43791
- let kimiCodeRegistration;
43792
- let kimiCodeConflict = null;
43793
- const claimKimiCodeRoute = () => {
43794
- if (kimiCodeRegistration !== void 0) return;
43795
- try {
43796
- kimiCodeRegistration = ctx.llm.registerAdapter([PROVIDER_ID$3], kimiCodeAdapter);
43797
- if (kimiCodeConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$3} route "${PROVIDER_ID$3}" is now served by this plugin`);
43798
- kimiCodeConflict = null;
43799
- } catch (error) {
43800
- kimiCodeConflict = error instanceof Error ? error.message : String(error);
43801
- ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$3}" is already owned by another adapter; ${PROVIDER_NAME$3} models keep being served by that one until its configuration is removed (${kimiCodeConflict})`);
43802
- }
43803
- };
43804
- claimKimiCodeRoute();
43805
- const kimiCodeRouteWatch = typeof ctx.on === "function" ? ctx.on("llm/adapters-updated", () => {
43806
- claimKimiCodeRoute();
43807
- }) : void 0;
43808
- const claimOllamaRoute = () => {
43809
- if (ollamaRegistration !== void 0) return;
43810
- try {
43811
- ollamaRegistration = ctx.llm.registerAdapter([PROVIDER_ID$4], ollamaAdapter);
43812
- if (ollamaConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$4} route "${PROVIDER_ID$4}" is now served by this plugin`);
43813
- ollamaConflict = null;
43814
- } catch (error) {
43815
- ollamaConflict = error instanceof Error ? error.message : String(error);
43816
- ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$4}" is already owned by another adapter; ${PROVIDER_NAME$4} models keep being served by that one until its configuration is removed (${ollamaConflict})`);
43817
- }
43818
- };
43819
- claimOllamaRoute();
45136
+ const kimiCodeClaim = claimProviderRoute(ctx, {
45137
+ providerId: PROVIDER_ID$3,
45138
+ label: PROVIDER_NAME$3,
45139
+ adapter: new KimiCodeAdapter(kimiCodeStore, kimiCodeModelSettings, kimiCodePreferences, {
45140
+ fetchFn: modelFetch["kimi-code"],
45141
+ attachments: ctx.attachments,
45142
+ videos: { readVideo: async (ref) => ({
45143
+ data: await readVideoBytes(ref),
45144
+ mediaType: ref.mediaType
45145
+ }) }
45146
+ }, kimiCodeAccountPool)
45147
+ });
45148
+ const ollamaClaim = claimProviderRoute(ctx, {
45149
+ providerId: PROVIDER_ID$4,
45150
+ label: PROVIDER_NAME$4,
45151
+ adapter: ollamaAdapter,
45152
+ watch: false
45153
+ });
43820
45154
  const disposeOllamaRoutes = registerOllamaRoutes(ctx, {
43821
45155
  accountPool: ollamaAccountPool,
43822
45156
  modelSettings: ollamaModelSettings,
43823
45157
  fetchFn: proxyFetch
43824
45158
  });
43825
- const claimCommandCodeRoute = () => {
43826
- if (commandCodeRegistration !== void 0) return;
43827
- try {
43828
- commandCodeRegistration = ctx.llm.registerAdapter([PROVIDER_ID$5], commandCodeAdapter);
43829
- if (commandCodeConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$5} route "${PROVIDER_ID$5}" is now served by this plugin`);
43830
- commandCodeConflict = null;
43831
- } catch (error) {
43832
- commandCodeConflict = error instanceof Error ? error.message : String(error);
43833
- ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$5}" is already owned by another adapter; ${PROVIDER_NAME$5} models keep being served by that one until its configuration is removed (${commandCodeConflict})`);
43834
- }
43835
- };
43836
- claimCommandCodeRoute();
43837
- const commandCodeRouteWatch = typeof ctx.on === "function" ? ctx.on("llm/adapters-updated", () => {
43838
- claimCommandCodeRoute();
43839
- }) : void 0;
45159
+ const commandCodeClaim = claimProviderRoute(ctx, {
45160
+ providerId: PROVIDER_ID$5,
45161
+ label: PROVIDER_NAME$5,
45162
+ adapter: commandCodeAdapter
45163
+ });
43840
45164
  const disposeKimiCodeRoutes = registerKimiCodeRoutes(ctx, kimiCodeStore, kimiCodeModelSettings, kimiCodePreferences, {
43841
45165
  fetchFn: proxyFetch,
43842
- serving: () => kimiCodeRegistration !== void 0,
43843
- conflict: () => kimiCodeConflict
45166
+ serving: () => kimiCodeClaim.serving(),
45167
+ conflict: () => kimiCodeClaim.conflict()
43844
45168
  }, kimiCodeAccountPool);
43845
- const minimaxCodeAdapter = new MinimaxCodeAdapter(minimaxCodeStore, {
43846
- fetchFn: modelFetch["minimax-code"],
43847
- attachments: ctx.attachments,
43848
- accountPool: minimaxCodeAccountPool
43849
- }, minimaxCodeModelSettings, minimaxCodePreferences);
43850
- let minimaxCodeRegistration;
43851
- let minimaxCodeConflict = null;
43852
- const claimMinimaxCodeRoute = () => {
43853
- if (minimaxCodeRegistration !== void 0) return;
43854
- try {
43855
- minimaxCodeRegistration = ctx.llm.registerAdapter([PROVIDER_ID$2], minimaxCodeAdapter);
43856
- if (minimaxCodeConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$2} route "${PROVIDER_ID$2}" is now served by this plugin`);
43857
- minimaxCodeConflict = null;
43858
- } catch (error) {
43859
- minimaxCodeConflict = error instanceof Error ? error.message : String(error);
43860
- ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$2}" is already owned by another adapter; ${PROVIDER_NAME$2} models keep being served by that one until its configuration is removed (${minimaxCodeConflict})`);
43861
- }
43862
- };
43863
- claimMinimaxCodeRoute();
43864
- const minimaxCodeRouteWatch = typeof ctx.on === "function" ? ctx.on("llm/adapters-updated", () => {
43865
- claimMinimaxCodeRoute();
43866
- }) : void 0;
45169
+ const minimaxCodeClaim = claimProviderRoute(ctx, {
45170
+ providerId: PROVIDER_ID$2,
45171
+ label: PROVIDER_NAME$2,
45172
+ adapter: new MinimaxCodeAdapter(minimaxCodeStore, {
45173
+ fetchFn: modelFetch["minimax-code"],
45174
+ attachments: ctx.attachments,
45175
+ accountPool: minimaxCodeAccountPool
45176
+ }, minimaxCodeModelSettings, minimaxCodePreferences)
45177
+ });
43867
45178
  let minimaxCodeAppVersion;
43868
45179
  try {
43869
45180
  const manifest = createRequire(import.meta.url)("../package.json");
@@ -43887,33 +45198,20 @@ function apply(ctx, pluginConfig = {}) {
43887
45198
  minimaxCodeCheckinTick();
43888
45199
  const disposeMinimaxCodeRoutes = registerMinimaxCodeRoutes(ctx, minimaxCodeStore, {
43889
45200
  fetchFn: proxyFetch,
43890
- serving: () => minimaxCodeRegistration !== void 0,
43891
- conflict: () => minimaxCodeConflict,
45201
+ serving: () => minimaxCodeClaim.serving(),
45202
+ conflict: () => minimaxCodeClaim.conflict(),
43892
45203
  accountPool: minimaxCodeAccountPool,
43893
45204
  checkin: minimaxCodeCheckin
43894
45205
  }, minimaxCodeModelSettings, minimaxCodePreferences);
43895
- const workBuddyAdapter = new WorkBuddyAdapter(workBuddyStore, workBuddyModelSettings, workBuddyPreferences, {
43896
- fetchFn: modelFetch.workbuddy,
43897
- attachments: ctx.attachments,
43898
- accountPool: workBuddyAccountPool
45206
+ const workBuddyClaim = claimProviderRoute(ctx, {
45207
+ providerId: PROVIDER_ID$1,
45208
+ label: PROVIDER_NAME$1,
45209
+ adapter: new WorkBuddyAdapter(workBuddyStore, workBuddyModelSettings, workBuddyPreferences, {
45210
+ fetchFn: modelFetch.workbuddy,
45211
+ attachments: ctx.attachments,
45212
+ accountPool: workBuddyAccountPool
45213
+ })
43899
45214
  });
43900
- let workBuddyRegistration;
43901
- let workBuddyConflict = null;
43902
- const claimWorkBuddyRoute = () => {
43903
- if (workBuddyRegistration !== void 0) return;
43904
- try {
43905
- workBuddyRegistration = ctx.llm.registerAdapter([PROVIDER_ID$1], workBuddyAdapter);
43906
- if (workBuddyConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME$1} route "${PROVIDER_ID$1}" is now served by this plugin`);
43907
- workBuddyConflict = null;
43908
- } catch (error) {
43909
- workBuddyConflict = error instanceof Error ? error.message : String(error);
43910
- ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID$1}" is already owned by another adapter; ${PROVIDER_NAME$1} models keep being served by that one until its configuration is removed (${workBuddyConflict})`);
43911
- }
43912
- };
43913
- claimWorkBuddyRoute();
43914
- const workBuddyRouteWatch = typeof ctx.on === "function" ? ctx.on("llm/adapters-updated", () => {
43915
- claimWorkBuddyRoute();
43916
- }) : void 0;
43917
45215
  const workBuddyCheckin = new WorkBuddyCheckinService(workBuddyStore, {
43918
45216
  fetchFn: proxyFetch,
43919
45217
  settings: () => workBuddyPreferences.status().checkin,
@@ -43929,37 +45227,24 @@ function apply(ctx, pluginConfig = {}) {
43929
45227
  checkinTick();
43930
45228
  const disposeWorkBuddyRoutes = registerWorkBuddyRoutes(ctx, workBuddyStore, workBuddyModelSettings, workBuddyPreferences, {
43931
45229
  fetchFn: proxyFetch,
43932
- serving: () => workBuddyRegistration !== void 0,
43933
- conflict: () => workBuddyConflict,
45230
+ serving: () => workBuddyClaim.serving(),
45231
+ conflict: () => workBuddyClaim.conflict(),
43934
45232
  accountPool: workBuddyAccountPool,
43935
45233
  checkin: workBuddyCheckin
43936
45234
  });
43937
- const claudeAdapter = new ClaudeAdapter(claudeStore, claudeModelSettings, claudePreferences, {
43938
- fetchFn: modelFetch.claude,
43939
- attachments: ctx.attachments,
43940
- accountPool: claudeAccountPool
45235
+ const claudeClaim = claimProviderRoute(ctx, {
45236
+ providerId: PROVIDER_ID,
45237
+ label: PROVIDER_NAME,
45238
+ adapter: new ClaudeAdapter(claudeStore, claudeModelSettings, claudePreferences, {
45239
+ fetchFn: modelFetch.claude,
45240
+ attachments: ctx.attachments,
45241
+ accountPool: claudeAccountPool
45242
+ })
43941
45243
  });
43942
- let claudeRegistration;
43943
- let claudeConflict = null;
43944
- const claimClaudeRoute = () => {
43945
- if (claudeRegistration !== void 0) return;
43946
- try {
43947
- claudeRegistration = ctx.llm.registerAdapter([PROVIDER_ID], claudeAdapter);
43948
- if (claudeConflict !== null) ctx.logger.info(`[dsh-chatgpt-subscription] ${PROVIDER_NAME} route "${PROVIDER_ID}" is now served by this plugin`);
43949
- claudeConflict = null;
43950
- } catch (error) {
43951
- claudeConflict = error instanceof Error ? error.message : String(error);
43952
- ctx.logger.warn(`[dsh-chatgpt-subscription] provider route "${PROVIDER_ID}" is already owned by another adapter; ${PROVIDER_NAME} models keep being served by that one until its configuration is removed (${claudeConflict})`);
43953
- }
43954
- };
43955
- claimClaudeRoute();
43956
- const claudeRouteWatch = typeof ctx.on === "function" ? ctx.on("llm/adapters-updated", () => {
43957
- claimClaudeRoute();
43958
- }) : void 0;
43959
45244
  const disposeClaudeRoutes = registerClaudeRoutes(ctx, claudeStore, claudeModelSettings, claudePreferences, {
43960
45245
  fetchFn: proxyFetch,
43961
- serving: () => claudeRegistration !== void 0,
43962
- conflict: () => claudeConflict,
45246
+ serving: () => claudeClaim.serving(),
45247
+ conflict: () => claudeClaim.conflict(),
43963
45248
  accountPool: claudeAccountPool
43964
45249
  });
43965
45250
  workBuddyAccountPool.syncDesktopAccounts().catch(() => void 0);
@@ -43970,7 +45255,11 @@ function apply(ctx, pluginConfig = {}) {
43970
45255
  pool: codexAccountPool
43971
45256
  });
43972
45257
  const usage = new UsageService(oauth, { fetchFn: proxyFetch });
43973
- codexAccountPool.setQuotaBlockedUntil((account, now) => usage.blockedUntilFor(account.credentials, now));
45258
+ codexAccountPool.setQuotaBlockedUntil((account, now) => usage.blockedUntilFor(account.id, account.credentials, now));
45259
+ codexAccountPool.setQuotaSnapshot((account) => {
45260
+ const snapshot = usage.snapshotFor(account.id, account.credentials);
45261
+ return snapshot === void 0 ? void 0 : codexAccountQuota(snapshot);
45262
+ });
43974
45263
  const adapter = new CodexChatGptAdapter(new ResponsesClient(oauth, ctx.attachments, {
43975
45264
  fetchFn: modelFetch["codex-chatgpt"],
43976
45265
  accountPool: codexAccountPool,
@@ -44026,6 +45315,145 @@ function apply(ctx, pluginConfig = {}) {
44026
45315
  });
44027
45316
  };
44028
45317
  const disposeRoutes = registerRoutes(ctx, oauth, usage, preferences, proxyManager, searchSwitcher, readRouteAudit, codexAccountPool, fetchConfiguration);
45318
+ const routable = (accounts) => accounts.some((account) => account.authStatus === void 0 || account.authStatus === "ok");
45319
+ const disposeHubOverview = registerHubOverviewRoutes(ctx, [
45320
+ {
45321
+ id: "chatgpt",
45322
+ providerId: PROVIDER_ID$7,
45323
+ canToggle: true,
45324
+ read: async () => {
45325
+ const current = preferences.status();
45326
+ const accounts = await codexAccountPool.listAccounts().catch(() => []);
45327
+ return {
45328
+ enabled: current.enabled !== false,
45329
+ accountCount: accounts.length,
45330
+ authenticated: routable(accounts),
45331
+ enabledModelCount: current.visibleModelIds.length,
45332
+ totalModelCount: CODEX_MODEL_CATALOG.length
45333
+ };
45334
+ }
45335
+ },
45336
+ {
45337
+ id: "antigravity",
45338
+ providerId: PROVIDER_ID$6,
45339
+ canToggle: true,
45340
+ read: async () => {
45341
+ const current = antigravityPreferences.status();
45342
+ const accounts = await antigravityAccountPool.listAccounts().catch(() => []);
45343
+ const total = catalogTotal((await antigravityModelSettings.read()).catalogModels.length);
45344
+ return {
45345
+ enabled: current.enabled !== false,
45346
+ accountCount: accounts.length,
45347
+ authenticated: routable(accounts),
45348
+ enabledModelCount: total === null ? null : Math.min(current.enabledModelIds?.length ?? 0, total),
45349
+ totalModelCount: total
45350
+ };
45351
+ }
45352
+ },
45353
+ {
45354
+ id: "command-code",
45355
+ providerId: PROVIDER_ID$5,
45356
+ canToggle: true,
45357
+ read: async () => {
45358
+ const current = commandCodePreferences.status();
45359
+ const accounts = await commandCodeAccountPool.listAccounts().catch(() => []);
45360
+ const total = catalogTotal((await commandCodeModelSettings.read()).catalogModels.length);
45361
+ return {
45362
+ enabled: current.enabled !== false,
45363
+ accountCount: accounts.length,
45364
+ authenticated: routable(accounts),
45365
+ enabledModelCount: total === null ? null : Math.min(current.enabledModelIds?.length ?? 0, total),
45366
+ totalModelCount: total
45367
+ };
45368
+ }
45369
+ },
45370
+ {
45371
+ id: "kimi-code",
45372
+ providerId: PROVIDER_ID$3,
45373
+ canToggle: true,
45374
+ read: async () => {
45375
+ const current = kimiCodePreferences.status();
45376
+ const accounts = await kimiCodeAccountPool.listAccounts().catch(() => []);
45377
+ const total = catalogTotal((await kimiCodeModelSettings.read()).catalogModels.length);
45378
+ return {
45379
+ enabled: current.enabled !== false,
45380
+ accountCount: accounts.length,
45381
+ authenticated: routable(accounts),
45382
+ enabledModelCount: total === null ? null : Math.min(current.enabledModelIds?.length ?? 0, total),
45383
+ totalModelCount: total
45384
+ };
45385
+ }
45386
+ },
45387
+ {
45388
+ id: "workbuddy",
45389
+ providerId: PROVIDER_ID$1,
45390
+ canToggle: true,
45391
+ read: async () => {
45392
+ const current = workBuddyPreferences.status();
45393
+ const accounts = await workBuddyAccountPool.listAccounts().catch(() => []);
45394
+ const total = catalogTotal(getCachedCatalog$1().length);
45395
+ return {
45396
+ enabled: current.enabled !== false,
45397
+ accountCount: accounts.length,
45398
+ authenticated: routable(accounts),
45399
+ enabledModelCount: total === null ? null : Math.min(current.enabledModelIds?.length ?? 0, total),
45400
+ totalModelCount: total
45401
+ };
45402
+ }
45403
+ },
45404
+ {
45405
+ id: "minimax-code",
45406
+ providerId: PROVIDER_ID$2,
45407
+ canToggle: true,
45408
+ read: async () => {
45409
+ const current = minimaxCodePreferences.status();
45410
+ const accounts = await minimaxCodeAccountPool.listAccounts().catch(() => []);
45411
+ const total = catalogTotal(catalogSize());
45412
+ return {
45413
+ enabled: current.enabled !== false,
45414
+ accountCount: accounts.length,
45415
+ authenticated: routable(accounts),
45416
+ enabledModelCount: total === null ? null : Math.min(current.enabledModelIds?.length ?? 0, total),
45417
+ totalModelCount: total
45418
+ };
45419
+ }
45420
+ },
45421
+ {
45422
+ id: "claude",
45423
+ providerId: PROVIDER_ID,
45424
+ canToggle: true,
45425
+ read: async () => {
45426
+ const current = claudePreferences.status();
45427
+ const accounts = await claudeAccountPool.listAccounts().catch(() => []);
45428
+ const total = catalogTotal(getCachedCatalog().length);
45429
+ return {
45430
+ enabled: current.enabled !== false,
45431
+ accountCount: accounts.length,
45432
+ authenticated: routable(accounts),
45433
+ enabledModelCount: total === null ? null : Math.min(current.enabledModelIds?.length ?? 0, total),
45434
+ totalModelCount: total
45435
+ };
45436
+ }
45437
+ },
45438
+ {
45439
+ id: "ollama",
45440
+ providerId: PROVIDER_ID$4,
45441
+ canToggle: false,
45442
+ read: async () => {
45443
+ const accounts = await ollamaAccountPool.listAccounts().catch(() => []);
45444
+ const current = ollamaModelSettings.status();
45445
+ const total = catalogTotal(current.catalogModels.length);
45446
+ const enabled = current.enabledModelIds.length === 0 ? total : current.enabledModelIds.length;
45447
+ return {
45448
+ enabled: true,
45449
+ accountCount: accounts.length,
45450
+ authenticated: routable(accounts),
45451
+ enabledModelCount: total === null ? null : Math.min(enabled ?? 0, total),
45452
+ totalModelCount: total
45453
+ };
45454
+ }
45455
+ }
45456
+ ]);
44029
45457
  const disposeAdapter = ctx.llm.registerAdapter([PROVIDER_ID$7], adapter);
44030
45458
  const disposeImageTool = ctx.tools.register(createCodexImageTool(oauth, ctx.attachments, { fetchFn: proxyFetch }));
44031
45459
  const disposeVideoTool = ctx.tools.register(createKimiVideoTool(ctx, { fetchFn: proxyFetch }));
@@ -44052,48 +45480,28 @@ function apply(ctx, pluginConfig = {}) {
44052
45480
  disposeVideoTool();
44053
45481
  disposeAdapter();
44054
45482
  disposeRoutes();
45483
+ disposeHubOverview();
44055
45484
  disposeAntigravityRoutes();
44056
- releaseHandle(antigravityRouteWatch);
44057
- antigravityRegistration?.();
44058
- antigravityRegistration = void 0;
45485
+ antigravityClaim.dispose();
44059
45486
  disposeOllamaRoutes();
44060
- ollamaRegistration?.();
44061
- ollamaRegistration = void 0;
45487
+ ollamaClaim.dispose();
44062
45488
  disposeCommandCodeRoutes();
44063
- releaseHandle(commandCodeRouteWatch);
44064
- commandCodeRegistration?.();
44065
- commandCodeRegistration = void 0;
45489
+ commandCodeClaim.dispose();
44066
45490
  disposeKimiCodeRoutes();
44067
- releaseHandle(kimiCodeRouteWatch);
44068
- kimiCodeRegistration?.();
44069
- kimiCodeRegistration = void 0;
45491
+ kimiCodeClaim.dispose();
44070
45492
  disposeMinimaxCodeRoutes();
44071
- releaseHandle(minimaxCodeRouteWatch);
44072
- minimaxCodeRegistration?.();
44073
- minimaxCodeRegistration = void 0;
45493
+ minimaxCodeClaim.dispose();
44074
45494
  clearInterval(minimaxCodeCheckinTimer);
44075
45495
  clearInterval(checkinTimer);
44076
45496
  disposeWorkBuddyRoutes();
44077
- releaseHandle(workBuddyRouteWatch);
44078
- workBuddyRegistration?.();
44079
- workBuddyRegistration = void 0;
45497
+ workBuddyClaim.dispose();
44080
45498
  disposeClaudeRoutes();
44081
- releaseHandle(claudeRouteWatch);
44082
- claudeRegistration?.();
44083
- claudeRegistration = void 0;
45499
+ claudeClaim.dispose();
44084
45500
  oauth.dispose();
44085
45501
  proxyManager.dispose();
44086
45502
  };
44087
45503
  }, "dsh-chatgpt-subscription: adapter, routes, and lifecycle");
44088
45504
  }
44089
- /** Cordis event handles are either a disposer function or a disposable object. */
44090
- function releaseHandle(handle) {
44091
- if (typeof handle === "function") {
44092
- handle();
44093
- return;
44094
- }
44095
- handle?.dispose?.();
44096
- }
44097
45505
  function localWebServerBaseUrl(host, port) {
44098
45506
  return `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${port}`;
44099
45507
  }