@slkiser/opencode-quota 4.8.2 → 4.10.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 (270) hide show
  1. package/README.md +18 -18
  2. package/dist/bin/opencode-quota.d.ts.map +1 -1
  3. package/dist/bin/opencode-quota.js +5 -3
  4. package/dist/bin/opencode-quota.js.map +1 -1
  5. package/dist/lib/accounting-format.d.ts +54 -0
  6. package/dist/lib/accounting-format.d.ts.map +1 -0
  7. package/dist/lib/accounting-format.js +271 -0
  8. package/dist/lib/accounting-format.js.map +1 -0
  9. package/dist/lib/anthropic.d.ts +21 -2
  10. package/dist/lib/anthropic.d.ts.map +1 -1
  11. package/dist/lib/anthropic.js +141 -22
  12. package/dist/lib/anthropic.js.map +1 -1
  13. package/dist/lib/cli-show.d.ts.map +1 -1
  14. package/dist/lib/cli-show.js +7 -1
  15. package/dist/lib/cli-show.js.map +1 -1
  16. package/dist/lib/config.d.ts +10 -1
  17. package/dist/lib/config.d.ts.map +1 -1
  18. package/dist/lib/config.js +69 -3
  19. package/dist/lib/config.js.map +1 -1
  20. package/dist/lib/copilot.d.ts +2 -0
  21. package/dist/lib/copilot.d.ts.map +1 -1
  22. package/dist/lib/copilot.js +38 -0
  23. package/dist/lib/copilot.js.map +1 -1
  24. package/dist/lib/cursor-usage.d.ts +1 -0
  25. package/dist/lib/cursor-usage.d.ts.map +1 -1
  26. package/dist/lib/cursor-usage.js +1 -0
  27. package/dist/lib/cursor-usage.js.map +1 -1
  28. package/dist/lib/deepseek.d.ts +12 -12
  29. package/dist/lib/deepseek.d.ts.map +1 -1
  30. package/dist/lib/deepseek.js +33 -29
  31. package/dist/lib/deepseek.js.map +1 -1
  32. package/dist/lib/display-sanitize.d.ts.map +1 -1
  33. package/dist/lib/display-sanitize.js +75 -22
  34. package/dist/lib/display-sanitize.js.map +1 -1
  35. package/dist/lib/entries.d.ts +122 -23
  36. package/dist/lib/entries.d.ts.map +1 -1
  37. package/dist/lib/entries.js +41 -1
  38. package/dist/lib/entries.js.map +1 -1
  39. package/dist/lib/format-utils.d.ts +11 -8
  40. package/dist/lib/format-utils.d.ts.map +1 -1
  41. package/dist/lib/format-utils.js +47 -6
  42. package/dist/lib/format-utils.js.map +1 -1
  43. package/dist/lib/format.d.ts +4 -0
  44. package/dist/lib/format.d.ts.map +1 -1
  45. package/dist/lib/format.js +144 -42
  46. package/dist/lib/format.js.map +1 -1
  47. package/dist/lib/glm-coding-plan.d.ts.map +1 -1
  48. package/dist/lib/glm-coding-plan.js +12 -3
  49. package/dist/lib/glm-coding-plan.js.map +1 -1
  50. package/dist/lib/google-agy.d.ts +2 -0
  51. package/dist/lib/google-agy.d.ts.map +1 -1
  52. package/dist/lib/google-agy.js +29 -14
  53. package/dist/lib/google-agy.js.map +1 -1
  54. package/dist/lib/google-gemini-cli.d.ts +2 -0
  55. package/dist/lib/google-gemini-cli.d.ts.map +1 -1
  56. package/dist/lib/google-gemini-cli.js +29 -3
  57. package/dist/lib/google-gemini-cli.js.map +1 -1
  58. package/dist/lib/google-token-cache.d.ts +5 -8
  59. package/dist/lib/google-token-cache.d.ts.map +1 -1
  60. package/dist/lib/google-token-cache.js +30 -118
  61. package/dist/lib/google-token-cache.js.map +1 -1
  62. package/dist/lib/google.d.ts +2 -0
  63. package/dist/lib/google.d.ts.map +1 -1
  64. package/dist/lib/google.js +31 -6
  65. package/dist/lib/google.js.map +1 -1
  66. package/dist/lib/grouped-entry-normalization.d.ts.map +1 -1
  67. package/dist/lib/grouped-entry-normalization.js +53 -20
  68. package/dist/lib/grouped-entry-normalization.js.map +1 -1
  69. package/dist/lib/minimax-auth.d.ts +1 -1
  70. package/dist/lib/minimax-auth.js +1 -1
  71. package/dist/lib/nanogpt.d.ts +5 -5
  72. package/dist/lib/nanogpt.d.ts.map +1 -1
  73. package/dist/lib/nanogpt.js +59 -23
  74. package/dist/lib/nanogpt.js.map +1 -1
  75. package/dist/lib/ollama-cloud.d.ts +2 -2
  76. package/dist/lib/ollama-cloud.d.ts.map +1 -1
  77. package/dist/lib/ollama-cloud.js +3 -45
  78. package/dist/lib/ollama-cloud.js.map +1 -1
  79. package/dist/lib/openai.d.ts +6 -0
  80. package/dist/lib/openai.d.ts.map +1 -1
  81. package/dist/lib/openai.js +51 -16
  82. package/dist/lib/openai.js.map +1 -1
  83. package/dist/lib/opencode-go-auth.js +1 -1
  84. package/dist/lib/opencode-go-auth.js.map +1 -1
  85. package/dist/lib/opencode-go.d.ts.map +1 -1
  86. package/dist/lib/opencode-go.js +28 -3
  87. package/dist/lib/opencode-go.js.map +1 -1
  88. package/dist/lib/opencode-zen-config.d.ts +0 -1
  89. package/dist/lib/opencode-zen-config.d.ts.map +1 -1
  90. package/dist/lib/opencode-zen-config.js +0 -22
  91. package/dist/lib/opencode-zen-config.js.map +1 -1
  92. package/dist/lib/opencode-zen.d.ts +3 -0
  93. package/dist/lib/opencode-zen.d.ts.map +1 -1
  94. package/dist/lib/opencode-zen.js +18 -1
  95. package/dist/lib/opencode-zen.js.map +1 -1
  96. package/dist/lib/openrouter.d.ts +2 -0
  97. package/dist/lib/openrouter.d.ts.map +1 -1
  98. package/dist/lib/openrouter.js +10 -0
  99. package/dist/lib/openrouter.js.map +1 -1
  100. package/dist/lib/provider-metadata.d.ts +2 -307
  101. package/dist/lib/provider-metadata.d.ts.map +1 -1
  102. package/dist/lib/provider-metadata.js +14 -316
  103. package/dist/lib/provider-metadata.js.map +1 -1
  104. package/dist/lib/provider-registration.d.ts +325 -0
  105. package/dist/lib/provider-registration.d.ts.map +1 -0
  106. package/dist/lib/provider-registration.js +338 -0
  107. package/dist/lib/provider-registration.js.map +1 -0
  108. package/dist/lib/quota-accounting-projection.d.ts +9 -0
  109. package/dist/lib/quota-accounting-projection.d.ts.map +1 -0
  110. package/dist/lib/quota-accounting-projection.js +254 -0
  111. package/dist/lib/quota-accounting-projection.js.map +1 -0
  112. package/dist/lib/quota-command-format.d.ts +5 -2
  113. package/dist/lib/quota-command-format.d.ts.map +1 -1
  114. package/dist/lib/quota-command-format.js +66 -44
  115. package/dist/lib/quota-command-format.js.map +1 -1
  116. package/dist/lib/quota-dialog-commands.d.ts.map +1 -1
  117. package/dist/lib/quota-dialog-commands.js +3 -0
  118. package/dist/lib/quota-dialog-commands.js.map +1 -1
  119. package/dist/lib/quota-exhaustion-projection.d.ts +11 -0
  120. package/dist/lib/quota-exhaustion-projection.d.ts.map +1 -0
  121. package/dist/lib/quota-exhaustion-projection.js +105 -0
  122. package/dist/lib/quota-exhaustion-projection.js.map +1 -0
  123. package/dist/lib/quota-export.d.ts +2 -1
  124. package/dist/lib/quota-export.d.ts.map +1 -1
  125. package/dist/lib/quota-export.js +36 -10
  126. package/dist/lib/quota-export.js.map +1 -1
  127. package/dist/lib/quota-providers-local.d.ts.map +1 -1
  128. package/dist/lib/quota-providers-local.js +14 -0
  129. package/dist/lib/quota-providers-local.js.map +1 -1
  130. package/dist/lib/quota-providers-remote.d.ts.map +1 -1
  131. package/dist/lib/quota-providers-remote.js +6 -2
  132. package/dist/lib/quota-providers-remote.js.map +1 -1
  133. package/dist/lib/quota-render-data.d.ts +0 -1
  134. package/dist/lib/quota-render-data.d.ts.map +1 -1
  135. package/dist/lib/quota-render-data.js +13 -138
  136. package/dist/lib/quota-render-data.js.map +1 -1
  137. package/dist/lib/quota-reset-notifications.d.ts.map +1 -1
  138. package/dist/lib/quota-reset-notifications.js +3 -0
  139. package/dist/lib/quota-reset-notifications.js.map +1 -1
  140. package/dist/lib/quota-state-codec.d.ts +16 -0
  141. package/dist/lib/quota-state-codec.d.ts.map +1 -0
  142. package/dist/lib/quota-state-codec.js +412 -0
  143. package/dist/lib/quota-state-codec.js.map +1 -0
  144. package/dist/lib/quota-state.d.ts +2 -11
  145. package/dist/lib/quota-state.d.ts.map +1 -1
  146. package/dist/lib/quota-state.js +165 -305
  147. package/dist/lib/quota-state.js.map +1 -1
  148. package/dist/lib/quota-status.d.ts.map +1 -1
  149. package/dist/lib/quota-status.js +15 -7
  150. package/dist/lib/quota-status.js.map +1 -1
  151. package/dist/lib/quota-telemetry.d.ts.map +1 -1
  152. package/dist/lib/quota-telemetry.js +7 -3
  153. package/dist/lib/quota-telemetry.js.map +1 -1
  154. package/dist/lib/quota-toast-runtime.d.ts +60 -0
  155. package/dist/lib/quota-toast-runtime.d.ts.map +1 -0
  156. package/dist/lib/quota-toast-runtime.js +774 -0
  157. package/dist/lib/quota-toast-runtime.js.map +1 -0
  158. package/dist/lib/qwen-local-quota.d.ts +2 -0
  159. package/dist/lib/qwen-local-quota.d.ts.map +1 -1
  160. package/dist/lib/qwen-local-quota.js +17 -1
  161. package/dist/lib/qwen-local-quota.js.map +1 -1
  162. package/dist/lib/resolved-auth-identity.d.ts +36 -0
  163. package/dist/lib/resolved-auth-identity.d.ts.map +1 -0
  164. package/dist/lib/resolved-auth-identity.js +193 -0
  165. package/dist/lib/resolved-auth-identity.js.map +1 -0
  166. package/dist/lib/scoped-update-migration.d.ts +101 -0
  167. package/dist/lib/scoped-update-migration.d.ts.map +1 -0
  168. package/dist/lib/scoped-update-migration.js +348 -0
  169. package/dist/lib/scoped-update-migration.js.map +1 -0
  170. package/dist/lib/scoped-update.d.ts +8 -0
  171. package/dist/lib/scoped-update.d.ts.map +1 -1
  172. package/dist/lib/scoped-update.js +263 -50
  173. package/dist/lib/scoped-update.js.map +1 -1
  174. package/dist/lib/toast-format-grouped.d.ts +4 -0
  175. package/dist/lib/toast-format-grouped.d.ts.map +1 -1
  176. package/dist/lib/toast-format-grouped.js +140 -39
  177. package/dist/lib/toast-format-grouped.js.map +1 -1
  178. package/dist/lib/tui-compact-format.d.ts +2 -0
  179. package/dist/lib/tui-compact-format.d.ts.map +1 -1
  180. package/dist/lib/tui-compact-format.js +224 -31
  181. package/dist/lib/tui-compact-format.js.map +1 -1
  182. package/dist/lib/tui-panel-state.d.ts +6 -0
  183. package/dist/lib/tui-panel-state.d.ts.map +1 -1
  184. package/dist/lib/tui-panel-state.js.map +1 -1
  185. package/dist/lib/tui-runtime.d.ts.map +1 -1
  186. package/dist/lib/tui-runtime.js +109 -10
  187. package/dist/lib/tui-runtime.js.map +1 -1
  188. package/dist/lib/tui-sidebar-format.d.ts +1 -1
  189. package/dist/lib/tui-sidebar-format.d.ts.map +1 -1
  190. package/dist/lib/tui-sidebar-format.js +4 -0
  191. package/dist/lib/tui-sidebar-format.js.map +1 -1
  192. package/dist/lib/types.d.ts +21 -13
  193. package/dist/lib/types.d.ts.map +1 -1
  194. package/dist/lib/types.js +2 -0
  195. package/dist/lib/types.js.map +1 -1
  196. package/dist/lib/xai.d.ts +4 -0
  197. package/dist/lib/xai.d.ts.map +1 -1
  198. package/dist/lib/xai.js +29 -3
  199. package/dist/lib/xai.js.map +1 -1
  200. package/dist/plugin.d.ts.map +1 -1
  201. package/dist/plugin.js +27 -720
  202. package/dist/plugin.js.map +1 -1
  203. package/dist/providers/accounting-decimal.d.ts +2 -0
  204. package/dist/providers/accounting-decimal.d.ts.map +1 -0
  205. package/dist/providers/accounting-decimal.js +23 -0
  206. package/dist/providers/accounting-decimal.js.map +1 -0
  207. package/dist/providers/anthropic.d.ts.map +1 -1
  208. package/dist/providers/anthropic.js +36 -0
  209. package/dist/providers/anthropic.js.map +1 -1
  210. package/dist/providers/cache-policies.d.ts +64 -0
  211. package/dist/providers/cache-policies.d.ts.map +1 -0
  212. package/dist/providers/cache-policies.js +197 -0
  213. package/dist/providers/cache-policies.js.map +1 -0
  214. package/dist/providers/cursor.d.ts.map +1 -1
  215. package/dist/providers/cursor.js +85 -58
  216. package/dist/providers/cursor.js.map +1 -1
  217. package/dist/providers/deepseek.d.ts +2 -2
  218. package/dist/providers/deepseek.d.ts.map +1 -1
  219. package/dist/providers/deepseek.js +64 -33
  220. package/dist/providers/deepseek.js.map +1 -1
  221. package/dist/providers/google-agy.js +2 -2
  222. package/dist/providers/google-agy.js.map +1 -1
  223. package/dist/providers/kilo.d.ts.map +1 -1
  224. package/dist/providers/kilo.js +56 -29
  225. package/dist/providers/kilo.js.map +1 -1
  226. package/dist/providers/mimo.d.ts.map +1 -1
  227. package/dist/providers/mimo.js +37 -15
  228. package/dist/providers/mimo.js.map +1 -1
  229. package/dist/providers/minimax-coding-plan.d.ts +8 -7
  230. package/dist/providers/minimax-coding-plan.d.ts.map +1 -1
  231. package/dist/providers/minimax-coding-plan.js +97 -83
  232. package/dist/providers/minimax-coding-plan.js.map +1 -1
  233. package/dist/providers/nanogpt.d.ts.map +1 -1
  234. package/dist/providers/nanogpt.js +72 -51
  235. package/dist/providers/nanogpt.js.map +1 -1
  236. package/dist/providers/ollama-cloud.d.ts +1 -2
  237. package/dist/providers/ollama-cloud.d.ts.map +1 -1
  238. package/dist/providers/ollama-cloud.js +1 -20
  239. package/dist/providers/ollama-cloud.js.map +1 -1
  240. package/dist/providers/openai.d.ts.map +1 -1
  241. package/dist/providers/openai.js +0 -3
  242. package/dist/providers/openai.js.map +1 -1
  243. package/dist/providers/opencode-go.d.ts +1 -0
  244. package/dist/providers/opencode-go.d.ts.map +1 -1
  245. package/dist/providers/opencode-go.js +32 -5
  246. package/dist/providers/opencode-go.js.map +1 -1
  247. package/dist/providers/opencode-zen.d.ts.map +1 -1
  248. package/dist/providers/opencode-zen.js +78 -17
  249. package/dist/providers/opencode-zen.js.map +1 -1
  250. package/dist/providers/quota-providers.d.ts.map +1 -1
  251. package/dist/providers/quota-providers.js +31 -23
  252. package/dist/providers/quota-providers.js.map +1 -1
  253. package/dist/providers/qwen-code.d.ts.map +1 -1
  254. package/dist/providers/qwen-code.js +1 -0
  255. package/dist/providers/qwen-code.js.map +1 -1
  256. package/dist/providers/registry.d.ts +3 -2
  257. package/dist/providers/registry.d.ts.map +1 -1
  258. package/dist/providers/registry.js +41 -31
  259. package/dist/providers/registry.js.map +1 -1
  260. package/dist/providers/result-helpers.d.ts +12 -6
  261. package/dist/providers/result-helpers.d.ts.map +1 -1
  262. package/dist/providers/result-helpers.js +4 -3
  263. package/dist/providers/result-helpers.js.map +1 -1
  264. package/dist/providers/xai.d.ts.map +1 -1
  265. package/dist/providers/xai.js +3 -0
  266. package/dist/providers/xai.js.map +1 -1
  267. package/dist/tui.d.ts.map +1 -1
  268. package/dist/tui.js +24 -11
  269. package/dist/tui.tsx +39 -10
  270. package/package.json +1 -1
@@ -0,0 +1,774 @@
1
+ import { getProviders } from "../providers/registry.js";
2
+ import { DEFAULT_ALIBABA_AUTH_CACHE_MAX_AGE_MS, isAlibabaModelId, resolveAlibabaCodingPlanAuthCached, } from "./alibaba-auth.js";
3
+ import { getOrFetchWithCacheControl } from "./cache.js";
4
+ import { createLoadConfigMeta } from "./config.js";
5
+ import { isCursorModelId, isCursorProviderId } from "./cursor-pricing.js";
6
+ import { sanitizeDisplayText } from "./display-sanitize.js";
7
+ import { formatQuotaRows } from "./format.js";
8
+ import { formatQuotaModeHeading } from "./format-utils.js";
9
+ import { BUNDLED_MAINTAINER_ANNOUNCEMENTS, formatMaintainerAnnouncementHomeCountLine, getMaintainerAnnouncementsSummary, } from "./maintainer-announcements.js";
10
+ import { maybeRefreshPricingSnapshot, setPricingSnapshotAutoRefresh, setPricingSnapshotSelection, } from "./modelsdev-pricing.js";
11
+ import { resolveQuotaFormatStyle } from "./quota-format-style.js";
12
+ import { customQuotaProviderDefinitions, QUOTA_PROVIDERS_AGGREGATE_ID } from "./quota-providers.js";
13
+ import { collectQuotaRenderData, } from "./quota-render-data.js";
14
+ import { formatQuotaResetNotification, observeQuotaResetNotifications, } from "./quota-reset-notifications.js";
15
+ import { createQuotaRuntimeRequestContext, resolveQuotaRuntimeContext, } from "./quota-runtime-context.js";
16
+ import { isQwenCodeModelId, resolveQwenLocalPlanCached } from "./qwen-auth.js";
17
+ import { inspectTuiConfig } from "./tui-config-diagnostics.js";
18
+ import { DEFAULT_CONFIG } from "./types.js";
19
+ const DEFERRED_QUOTA_REFRESH_DELAYS_MS = [3_000, 15_000, 60_000, 300_000];
20
+ export function createQuotaToastRuntime(dependencies) {
21
+ let config = DEFAULT_CONFIG;
22
+ let configLoaded = false;
23
+ let configInFlight = null;
24
+ let configMeta = createLoadConfigMeta();
25
+ let runtimeProviders = getProviders();
26
+ let initDone = false;
27
+ const deferredQuotaRefreshes = new Map();
28
+ const detectedProviderIdsByToastCacheKey = new Map();
29
+ const maintainerAnnouncementToastFallback = {
30
+ pending: true,
31
+ inFlight: false,
32
+ };
33
+ function getDeferredQuotaRefreshDelayMs(attempts) {
34
+ const index = Math.min(Math.max(0, attempts), DEFERRED_QUOTA_REFRESH_DELAYS_MS.length - 1);
35
+ return DEFERRED_QUOTA_REFRESH_DELAYS_MS[index];
36
+ }
37
+ function clearDeferredQuotaRefresh(sessionID) {
38
+ const state = deferredQuotaRefreshes.get(sessionID);
39
+ if (state?.timer) {
40
+ clearTimeout(state.timer);
41
+ }
42
+ deferredQuotaRefreshes.delete(sessionID);
43
+ }
44
+ function clearDeferredQuotaRefreshTimer(state) {
45
+ if (!state.timer)
46
+ return;
47
+ clearTimeout(state.timer);
48
+ state.timer = null;
49
+ }
50
+ function scheduleDeferredQuotaRefresh(params) {
51
+ let state = deferredQuotaRefreshes.get(params.sessionID);
52
+ if (!state) {
53
+ state = {
54
+ sessionID: params.sessionID,
55
+ attempts: 0,
56
+ reason: params.reason,
57
+ queuedAtMs: Date.now(),
58
+ timer: null,
59
+ inFlight: false,
60
+ };
61
+ deferredQuotaRefreshes.set(params.sessionID, state);
62
+ }
63
+ else {
64
+ if (params.incrementAttempts) {
65
+ state.attempts += 1;
66
+ }
67
+ state.reason = params.reason;
68
+ clearDeferredQuotaRefreshTimer(state);
69
+ }
70
+ const delayMs = getDeferredQuotaRefreshDelayMs(state.attempts);
71
+ state.timer = setTimeout(() => {
72
+ void runDeferredQuotaRefresh(params.sessionID);
73
+ }, delayMs);
74
+ state.timer.unref?.();
75
+ void dependencies.log("Deferred quota refresh scheduled", {
76
+ sessionID: params.sessionID,
77
+ reason: params.reason,
78
+ attempts: state.attempts,
79
+ delayMs,
80
+ });
81
+ }
82
+ async function runDeferredQuotaRefresh(sessionID) {
83
+ const state = deferredQuotaRefreshes.get(sessionID);
84
+ if (!state || state.inFlight)
85
+ return;
86
+ await handleTriggerInternal({
87
+ sessionID,
88
+ trigger: "deferred.retry",
89
+ deferredRetry: true,
90
+ });
91
+ }
92
+ function isProviderEnabled(providerId) {
93
+ return config.enabledProviders === "auto" || config.enabledProviders.includes(providerId);
94
+ }
95
+ async function shouldBypassToastCacheForLiveLocalUsage(params) {
96
+ if (isProviderEnabled(QUOTA_PROVIDERS_AGGREGATE_ID) &&
97
+ customQuotaProviderDefinitions(config.quotaProviders).some((definition) => definition.mode === "local-estimate")) {
98
+ return true;
99
+ }
100
+ const currentSession = params.sessionMeta ?? (await dependencies.resolveSessionMeta(params.sessionID));
101
+ const currentModel = currentSession.modelID;
102
+ if (currentSession.providerID === "qwen-code" || isQwenCodeModelId(currentModel)) {
103
+ const plan = await resolveQwenLocalPlanCached();
104
+ return plan.state === "qwen_free" && isProviderEnabled("qwen-code");
105
+ }
106
+ if (currentSession.providerID === "alibaba-coding-plan" ||
107
+ currentSession.providerID === "alibaba" ||
108
+ isAlibabaModelId(currentModel)) {
109
+ const plan = await resolveAlibabaCodingPlanAuthCached({
110
+ maxAgeMs: DEFAULT_ALIBABA_AUTH_CACHE_MAX_AGE_MS,
111
+ fallbackTier: "lite",
112
+ });
113
+ return plan.state === "configured" && isProviderEnabled("alibaba-coding-plan");
114
+ }
115
+ if (isCursorProviderId(currentSession.providerID) || isCursorModelId(currentModel)) {
116
+ return isProviderEnabled("cursor");
117
+ }
118
+ return false;
119
+ }
120
+ function buildToastCacheKey(params) {
121
+ const formatStyle = resolveQuotaFormatStyle(config.formatStyle);
122
+ const enabledProviders = config.enabledProviders === "auto" ? "auto" : config.enabledProviders.join(",");
123
+ const googleModels = config.googleModels.join(",");
124
+ const currentModel = config.onlyCurrentModel && params.sessionID ? (params.sessionMeta?.modelID ?? "") : "";
125
+ const currentProviderID = config.onlyCurrentModel && params.sessionID ? (params.sessionMeta?.providerID ?? "") : "";
126
+ const renderIdentity = JSON.stringify({
127
+ accountingDetail: config.accountingDetail,
128
+ quotaProjection: config.quotaProjection,
129
+ percentLabelStyle: config.percentLabelStyle,
130
+ resetTimeDecimals: config.resetTimeDecimals,
131
+ resetTimeSpaced: config.resetTimeSpaced,
132
+ sessionTokenScope: config.sessionTokenScope,
133
+ opencodeGoWindows: config.opencodeGoWindows,
134
+ opencodeMonthlyLimit: config.opencodeMonthlyLimit,
135
+ quotaProviders: config.quotaProviders,
136
+ });
137
+ const rootIdentity = JSON.stringify(dependencies.roots());
138
+ return [
139
+ `sessionID=${params.sessionID}`,
140
+ `enabledProviders=${enabledProviders}`,
141
+ `formatStyle=${formatStyle}`,
142
+ `percentDisplayMode=${config.percentDisplayMode}`,
143
+ `layout=${JSON.stringify(config.layout)}`,
144
+ `showSessionTokens=${config.showSessionTokens ? "yes" : "no"}`,
145
+ `onlyCurrentModel=${config.onlyCurrentModel ? "yes" : "no"}`,
146
+ `currentModel=${currentModel}`,
147
+ `currentProviderID=${currentProviderID}`,
148
+ `anthropicBinaryPath=${config.anthropicBinaryPath}`,
149
+ `googleModels=${googleModels}`,
150
+ `cursorPlan=${config.cursorPlan}`,
151
+ `cursorIncludedApiUsd=${config.cursorIncludedApiUsd ?? ""}`,
152
+ `cursorBillingCycleStartDay=${config.cursorBillingCycleStartDay ?? ""}`,
153
+ `renderIdentity=${renderIdentity}`,
154
+ `rootIdentity=${rootIdentity}`,
155
+ ].join("|");
156
+ }
157
+ async function kickPricingRefresh(params) {
158
+ try {
159
+ const refreshPromise = maybeRefreshPricingSnapshot({
160
+ reason: params.reason,
161
+ snapshotSelection: config.pricingSnapshot.source,
162
+ });
163
+ const guardedRefreshPromise = refreshPromise.catch(() => undefined);
164
+ if (!params.maxWaitMs || params.maxWaitMs <= 0) {
165
+ void guardedRefreshPromise;
166
+ return;
167
+ }
168
+ await Promise.race([
169
+ guardedRefreshPromise,
170
+ new Promise((resolve) => {
171
+ setTimeout(resolve, params.maxWaitMs);
172
+ }),
173
+ ]);
174
+ }
175
+ catch (error) {
176
+ await dependencies.log("Pricing refresh failed", {
177
+ reason: params.reason,
178
+ error: error instanceof Error ? error.message : String(error),
179
+ });
180
+ }
181
+ }
182
+ function onFirstConfigLoaded() {
183
+ if (initDone)
184
+ return;
185
+ initDone = true;
186
+ if (config.enabled) {
187
+ void kickPricingRefresh({ reason: "init" });
188
+ }
189
+ dependencies.onInitialized({
190
+ configLoaded,
191
+ configSource: configMeta.source,
192
+ configPaths: configMeta.paths,
193
+ enabledProviders: config.enabledProviders,
194
+ minIntervalMs: config.minIntervalMs,
195
+ googleModels: config.googleModels,
196
+ cursorPlan: config.cursorPlan,
197
+ cursorIncludedApiUsd: config.cursorIncludedApiUsd,
198
+ cursorBillingCycleStartDay: config.cursorBillingCycleStartDay,
199
+ pricingSnapshotSource: config.pricingSnapshot.source,
200
+ pricingSnapshotAutoRefresh: config.pricingSnapshot.autoRefresh,
201
+ showOnIdle: config.showOnIdle,
202
+ showOnQuestion: config.showOnQuestion,
203
+ showOnCompact: config.showOnCompact,
204
+ showOnBothFail: config.showOnBothFail,
205
+ });
206
+ }
207
+ async function refreshConfig() {
208
+ if (configInFlight)
209
+ return configInFlight;
210
+ configInFlight = (async () => {
211
+ try {
212
+ const runtime = await resolveQuotaRuntimeContext({
213
+ client: dependencies.client,
214
+ roots: dependencies.roots(),
215
+ });
216
+ configMeta = runtime.configMeta;
217
+ config = runtime.config;
218
+ runtimeProviders = runtime.providers;
219
+ setPricingSnapshotAutoRefresh(config.pricingSnapshot.autoRefresh);
220
+ setPricingSnapshotSelection(config.pricingSnapshot.source);
221
+ configLoaded = true;
222
+ onFirstConfigLoaded();
223
+ }
224
+ catch {
225
+ config = DEFAULT_CONFIG;
226
+ configMeta = createLoadConfigMeta();
227
+ runtimeProviders = getProviders();
228
+ setPricingSnapshotAutoRefresh(DEFAULT_CONFIG.pricingSnapshot.autoRefresh);
229
+ setPricingSnapshotSelection(DEFAULT_CONFIG.pricingSnapshot.source);
230
+ }
231
+ finally {
232
+ configInFlight = null;
233
+ }
234
+ })();
235
+ return configInFlight;
236
+ }
237
+ async function resolvePluginRuntimeContext(params) {
238
+ return resolveQuotaRuntimeContext({
239
+ client: dependencies.client,
240
+ roots: dependencies.roots(),
241
+ config,
242
+ configMeta,
243
+ providers: runtimeProviders,
244
+ sessionID: params.sessionID,
245
+ sessionMeta: params.sessionMeta,
246
+ resolveSessionMeta: dependencies.resolveSessionMeta,
247
+ includeSessionMeta: (runtimeConfig) => runtimeConfig.onlyCurrentModel,
248
+ });
249
+ }
250
+ function emptyCollectionResult(params) {
251
+ return {
252
+ message: params.message,
253
+ cacheRenderedMessage: false,
254
+ retryable: params.retryable,
255
+ retryReason: params.retryReason,
256
+ hasQuotaRows: false,
257
+ detectedProviderIds: [],
258
+ freshProviderResults: [],
259
+ shouldReconcileDetectedProviders: false,
260
+ };
261
+ }
262
+ async function fetchQuotaMessageResult(params) {
263
+ if (!configLoaded) {
264
+ await refreshConfig();
265
+ }
266
+ if (!configLoaded) {
267
+ return emptyCollectionResult({
268
+ message: config.debug
269
+ ? formatQuotaToastDebugInfo({
270
+ trigger: params.trigger,
271
+ reason: "config load failed",
272
+ config,
273
+ configMeta,
274
+ })
275
+ : null,
276
+ retryable: true,
277
+ retryReason: "config_load_failed",
278
+ });
279
+ }
280
+ if (!config.enabled) {
281
+ return emptyCollectionResult({
282
+ message: config.debug
283
+ ? formatQuotaToastDebugInfo({
284
+ trigger: params.trigger,
285
+ reason: "disabled",
286
+ config,
287
+ configMeta,
288
+ })
289
+ : null,
290
+ retryable: false,
291
+ });
292
+ }
293
+ if (config.enabledProviders !== "auto" && config.enabledProviders.length === 0) {
294
+ return emptyCollectionResult({
295
+ message: config.debug
296
+ ? formatQuotaToastDebugInfo({
297
+ trigger: params.trigger,
298
+ reason: "enabledProviders empty",
299
+ config,
300
+ configMeta,
301
+ })
302
+ : null,
303
+ retryable: false,
304
+ });
305
+ }
306
+ const runtime = await resolvePluginRuntimeContext({
307
+ sessionID: params.sessionID,
308
+ sessionMeta: params.sessionMeta,
309
+ });
310
+ const collection = await collectQuotaToastMessage({
311
+ trigger: params.trigger,
312
+ runtime,
313
+ bypassProviderCache: params.bypassProviderCache,
314
+ });
315
+ if (collection.shouldReconcileDetectedProviders) {
316
+ await dependencies.reconcileDetectedProviders(collection.detectedProviderIds);
317
+ }
318
+ if (runtime.config.showSessionTokens) {
319
+ dependencies.setSessionTokenError(collection.sessionTokenError);
320
+ }
321
+ return collection;
322
+ }
323
+ async function reconcileDeferredQuotaRefresh(params) {
324
+ const existing = deferredQuotaRefreshes.get(params.sessionID);
325
+ if (!params.result.retryable) {
326
+ if (existing) {
327
+ clearDeferredQuotaRefresh(params.sessionID);
328
+ await dependencies.log("Deferred quota refresh cleared", {
329
+ sessionID: params.sessionID,
330
+ trigger: params.trigger,
331
+ reason: params.result.hasQuotaRows ? "quota_rows_available" : "not_retryable",
332
+ });
333
+ }
334
+ return;
335
+ }
336
+ if (!params.result.retryReason)
337
+ return;
338
+ scheduleDeferredQuotaRefresh({
339
+ sessionID: params.sessionID,
340
+ reason: params.result.retryReason,
341
+ incrementAttempts: params.consumedDeferredRetry,
342
+ });
343
+ }
344
+ function isTriggerEnabled(trigger) {
345
+ if (trigger === "session.idle")
346
+ return config.showOnIdle;
347
+ if (trigger === "session.compacted")
348
+ return config.showOnCompact;
349
+ if (trigger === "question")
350
+ return config.showOnQuestion;
351
+ return trigger === "deferred.retry";
352
+ }
353
+ function triggerMaintainerAnnouncementToastFallback(trigger, detectedProviderIds, runtimeConfig) {
354
+ if (!maintainerAnnouncementToastFallback.pending ||
355
+ maintainerAnnouncementToastFallback.inFlight) {
356
+ return;
357
+ }
358
+ if (!runtimeConfig.enabled || !runtimeConfig.enableToast) {
359
+ maintainerAnnouncementToastFallback.pending = false;
360
+ return;
361
+ }
362
+ if (!runtimeConfig.maintainerAnnouncements.enabled ||
363
+ !runtimeConfig.maintainerAnnouncements.home) {
364
+ maintainerAnnouncementToastFallback.pending = false;
365
+ return;
366
+ }
367
+ maintainerAnnouncementToastFallback.inFlight = true;
368
+ void (async () => {
369
+ try {
370
+ const summary = getMaintainerAnnouncementsSummary({
371
+ announcements: BUNDLED_MAINTAINER_ANNOUNCEMENTS,
372
+ enabledProviders: detectedProviderIds,
373
+ });
374
+ if (summary.activeCount <= 0) {
375
+ if (summary.futureCount <= 0) {
376
+ maintainerAnnouncementToastFallback.pending = false;
377
+ }
378
+ return;
379
+ }
380
+ const tuiDiagnostics = await inspectTuiConfig({ roots: dependencies.roots() });
381
+ if (tuiDiagnostics.quotaPluginConfigured) {
382
+ maintainerAnnouncementToastFallback.pending = false;
383
+ return;
384
+ }
385
+ const message = formatMaintainerAnnouncementHomeCountLine(summary.activeCount);
386
+ if (!message)
387
+ return;
388
+ await dependencies.showToast({
389
+ message: sanitizeDisplayText(message),
390
+ variant: "info",
391
+ duration: runtimeConfig.toastDurationMs,
392
+ });
393
+ maintainerAnnouncementToastFallback.pending = false;
394
+ await dependencies.log("Displayed maintainer announcement fallback toast", { trigger });
395
+ }
396
+ catch (error) {
397
+ await dependencies.log("Failed to show maintainer announcement fallback toast", {
398
+ trigger,
399
+ error: error instanceof Error ? error.message : String(error),
400
+ });
401
+ }
402
+ finally {
403
+ maintainerAnnouncementToastFallback.inFlight = false;
404
+ }
405
+ })();
406
+ }
407
+ async function emitQuotaToastPlan(params) {
408
+ const { config: runtimeConfig, message, detectedProviderIds, freshProviderResults, } = params.plan;
409
+ let resetNotification;
410
+ if (runtimeConfig.enableToast &&
411
+ runtimeConfig.resetNotifications.enabled &&
412
+ freshProviderResults.length > 0) {
413
+ try {
414
+ const notices = await observeQuotaResetNotifications({
415
+ providers: freshProviderResults,
416
+ windows: runtimeConfig.resetNotifications.windows,
417
+ });
418
+ resetNotification = formatQuotaResetNotification(notices) ?? undefined;
419
+ }
420
+ catch (error) {
421
+ await dependencies.log("Failed to observe quota reset transitions", {
422
+ error: error instanceof Error ? error.message : String(error),
423
+ });
424
+ }
425
+ }
426
+ if (params.plan.deferredRetryResult) {
427
+ await dependencies.log("Deferred quota refresh did not produce reportable data", {
428
+ sessionID: params.sessionID,
429
+ trigger: params.trigger,
430
+ retryable: params.plan.deferredRetryResult.retryable,
431
+ retryReason: params.plan.deferredRetryResult.retryReason,
432
+ });
433
+ return;
434
+ }
435
+ if (!message) {
436
+ await dependencies.log("No quota message to display", { trigger: params.trigger });
437
+ return;
438
+ }
439
+ if (!runtimeConfig.enableToast) {
440
+ await dependencies.log("Toast disabled (enableToast=false)", { trigger: params.trigger });
441
+ return;
442
+ }
443
+ try {
444
+ await dependencies.showToast({
445
+ ...(runtimeConfig.percentLabelStyle === "bare"
446
+ ? { title: formatQuotaModeHeading(runtimeConfig.percentDisplayMode) }
447
+ : {}),
448
+ message: sanitizeDisplayText(message),
449
+ variant: "info",
450
+ duration: runtimeConfig.toastDurationMs,
451
+ });
452
+ triggerMaintainerAnnouncementToastFallback(params.trigger, detectedProviderIds, runtimeConfig);
453
+ await dependencies.log("Displayed quota toast", { message, trigger: params.trigger });
454
+ if (resetNotification) {
455
+ await dependencies.showToast({
456
+ title: "Quota available",
457
+ message: sanitizeDisplayText(resetNotification),
458
+ variant: "success",
459
+ duration: runtimeConfig.toastDurationMs,
460
+ });
461
+ await dependencies.log("Displayed quota reset notification", {
462
+ message: resetNotification,
463
+ trigger: params.trigger,
464
+ });
465
+ }
466
+ }
467
+ catch (error) {
468
+ await dependencies.log("Failed to show toast", {
469
+ error: error instanceof Error ? error.message : String(error),
470
+ });
471
+ }
472
+ }
473
+ async function prepareTrigger(params) {
474
+ if (!configLoaded) {
475
+ await refreshConfig();
476
+ }
477
+ if (!config.enabled) {
478
+ clearDeferredQuotaRefresh(params.sessionID);
479
+ return null;
480
+ }
481
+ if (!isTriggerEnabled(params.trigger))
482
+ return null;
483
+ const pendingDeferred = deferredQuotaRefreshes.get(params.sessionID);
484
+ const consumedDeferredRetry = params.deferredRetry === true || Boolean(pendingDeferred);
485
+ if (pendingDeferred) {
486
+ if (pendingDeferred.inFlight && !params.deferredRetry) {
487
+ await dependencies.log("Skipping duplicate deferred quota refresh", {
488
+ sessionID: params.sessionID,
489
+ trigger: params.trigger,
490
+ });
491
+ return null;
492
+ }
493
+ pendingDeferred.inFlight = true;
494
+ clearDeferredQuotaRefreshTimer(pendingDeferred);
495
+ }
496
+ let planPrepared = false;
497
+ let completed = false;
498
+ const complete = () => {
499
+ if (completed)
500
+ return;
501
+ completed = true;
502
+ const state = deferredQuotaRefreshes.get(params.sessionID);
503
+ if (state && state === pendingDeferred) {
504
+ state.inFlight = false;
505
+ }
506
+ };
507
+ try {
508
+ if (await dependencies.isSubagentSession(params.sessionID)) {
509
+ if (consumedDeferredRetry) {
510
+ clearDeferredQuotaRefresh(params.sessionID);
511
+ }
512
+ await dependencies.log("Skipping toast for subagent session", {
513
+ sessionID: params.sessionID,
514
+ trigger: params.trigger,
515
+ });
516
+ return null;
517
+ }
518
+ const sessionMeta = await dependencies.resolveSessionMeta(params.sessionID);
519
+ const bypassForLiveLocalUsage = await shouldBypassToastCacheForLiveLocalUsage({
520
+ sessionID: params.sessionID,
521
+ sessionMeta,
522
+ });
523
+ const bypassMessageCache = config.debug || consumedDeferredRetry || bypassForLiveLocalUsage;
524
+ const bypassProviderCache = consumedDeferredRetry || bypassForLiveLocalUsage;
525
+ const toastCacheKey = buildToastCacheKey({
526
+ sessionID: params.sessionID,
527
+ sessionMeta,
528
+ });
529
+ let fetchResult;
530
+ const fetchForToast = () => fetchQuotaMessageResult({
531
+ trigger: params.trigger,
532
+ sessionID: params.sessionID,
533
+ sessionMeta,
534
+ bypassProviderCache,
535
+ });
536
+ const message = bypassMessageCache
537
+ ? await (async () => {
538
+ fetchResult = await fetchForToast();
539
+ return fetchResult.message;
540
+ })()
541
+ : await (async () => {
542
+ const fetched = {};
543
+ const cachedMessage = await getOrFetchWithCacheControl(toastCacheKey, async () => {
544
+ const result = await fetchForToast();
545
+ fetched.result = result;
546
+ return {
547
+ message: result.message,
548
+ cache: Boolean(result.message && result.cacheRenderedMessage && result.hasQuotaRows),
549
+ };
550
+ }, config.minIntervalMs);
551
+ fetchResult = fetched.result;
552
+ return cachedMessage;
553
+ })();
554
+ if (fetchResult) {
555
+ detectedProviderIdsByToastCacheKey.set(toastCacheKey, [...fetchResult.detectedProviderIds]);
556
+ await reconcileDeferredQuotaRefresh({
557
+ sessionID: params.sessionID,
558
+ result: fetchResult,
559
+ consumedDeferredRetry,
560
+ trigger: params.trigger,
561
+ });
562
+ }
563
+ planPrepared = true;
564
+ return {
565
+ config,
566
+ message,
567
+ detectedProviderIds: fetchResult?.detectedProviderIds ??
568
+ detectedProviderIdsByToastCacheKey.get(toastCacheKey) ??
569
+ [],
570
+ freshProviderResults: fetchResult?.freshProviderResults ?? [],
571
+ complete,
572
+ deferredRetryResult: params.deferredRetry && fetchResult && !fetchResult.hasQuotaRows
573
+ ? {
574
+ retryable: fetchResult.retryable,
575
+ retryReason: fetchResult.retryReason,
576
+ }
577
+ : undefined,
578
+ };
579
+ }
580
+ finally {
581
+ if (!planPrepared)
582
+ complete();
583
+ }
584
+ }
585
+ async function handleTriggerInternal(params) {
586
+ const plan = await prepareTrigger(params);
587
+ if (!plan)
588
+ return;
589
+ try {
590
+ await emitQuotaToastPlan({ ...params, plan });
591
+ }
592
+ finally {
593
+ plan.complete();
594
+ }
595
+ }
596
+ async function handleTrigger(params) {
597
+ await handleTriggerInternal(params);
598
+ }
599
+ return { handleTrigger };
600
+ }
601
+ export function formatQuotaToastDebugInfo(params) {
602
+ const availability = params.availability
603
+ ? params.availability.map((item) => `${item.id}=${item.ok ? "ok" : "no"}`).join(" ")
604
+ : "unknown";
605
+ const providers = params.config.enabledProviders === "auto"
606
+ ? "(auto)"
607
+ : params.config.enabledProviders.length > 0
608
+ ? params.config.enabledProviders.join(",")
609
+ : "(none)";
610
+ const modelPart = params.currentModel ? ` model=${params.currentModel}` : "";
611
+ const paths = params.configMeta.paths.length > 0 ? params.configMeta.paths.join(" | ") : "(none)";
612
+ return [
613
+ "Quota Toast Debug (opencode-quota)",
614
+ `trigger=${params.trigger} reason=${params.reason}`,
615
+ `configSource=${params.configMeta.source} paths=${paths}`,
616
+ `enabled=${params.config.enabled} providers=${providers}${modelPart}`,
617
+ `available=${availability}`,
618
+ ].join("\n");
619
+ }
620
+ function hasRetryableProviderError(errors) {
621
+ return errors.some((error) => error.retryable === true);
622
+ }
623
+ export async function collectQuotaToastMessage(params) {
624
+ const runtimeConfig = params.runtime.config;
625
+ const quotaResult = await collectQuotaRenderData({
626
+ client: params.runtime.client,
627
+ resolveRuntimeProviderIds: params.runtime.resolveRuntimeProviderIds,
628
+ config: runtimeConfig,
629
+ configMeta: params.runtime.configMeta,
630
+ request: createQuotaRuntimeRequestContext(params.runtime),
631
+ surfaceExplicitProviderIssues: true,
632
+ formatStyle: resolveQuotaFormatStyle(runtimeConfig.formatStyle),
633
+ bypassProviderCache: params.bypassProviderCache,
634
+ providers: params.runtime.providers,
635
+ });
636
+ const { selection, availability, active, providerResults, attemptedAny, hasExplicitProviderIssues, data, } = quotaResult;
637
+ const detectedProviderIds = active.map((provider) => provider.id);
638
+ const collectionMetadata = {
639
+ detectedProviderIds,
640
+ freshProviderResults: providerResults,
641
+ sessionTokenError: quotaResult.sessionTokenError,
642
+ shouldReconcileDetectedProviders: selection?.isAutoMode === true,
643
+ };
644
+ const currentModel = selection?.currentModel;
645
+ const errors = data?.errors ?? [];
646
+ const hasProviderQuotaRows = Boolean(data?.entries.length);
647
+ const hasQuotaRows = Boolean(hasProviderQuotaRows || data?.sessionTokens);
648
+ const hasRetryableFetchFailure = attemptedAny && hasRetryableProviderError(errors);
649
+ const retryableAvailabilityFailure = active.length === 0 && availability.some((item) => !item.ok && item.error === true);
650
+ if (active.length === 0 && !(hasExplicitProviderIssues && errors.length > 0)) {
651
+ const message = runtimeConfig.debug
652
+ ? formatQuotaToastDebugInfo({
653
+ trigger: params.trigger,
654
+ reason: "no enabled providers available",
655
+ config: runtimeConfig,
656
+ configMeta: params.runtime.configMeta,
657
+ currentModel,
658
+ availability: availability.map((item) => ({
659
+ id: item.provider.id,
660
+ ok: item.ok,
661
+ })),
662
+ })
663
+ : null;
664
+ const retryableNoProviders = selection?.isAutoMode === true || retryableAvailabilityFailure;
665
+ return {
666
+ message,
667
+ cacheRenderedMessage: false,
668
+ retryable: retryableNoProviders,
669
+ retryReason: retryableNoProviders ? "no_available_providers" : undefined,
670
+ hasQuotaRows: false,
671
+ ...collectionMetadata,
672
+ };
673
+ }
674
+ if (hasQuotaRows) {
675
+ const formatted = formatQuotaRows({
676
+ version: "1.0.0",
677
+ layout: runtimeConfig.layout,
678
+ entries: data?.entries ?? [],
679
+ errors: data?.errors ?? [],
680
+ style: resolveQuotaFormatStyle(runtimeConfig.formatStyle),
681
+ percentDisplayMode: runtimeConfig.percentDisplayMode,
682
+ percentLabelStyle: runtimeConfig.percentLabelStyle,
683
+ accountingDetail: runtimeConfig.accountingDetail,
684
+ resetTimeDecimals: runtimeConfig.resetTimeDecimals,
685
+ resetTimeSpaced: runtimeConfig.resetTimeSpaced,
686
+ sessionTokens: data?.sessionTokens,
687
+ });
688
+ const retryableMaskedProviderFailure = !hasProviderQuotaRows && hasRetryableFetchFailure;
689
+ if (!runtimeConfig.debug) {
690
+ return {
691
+ message: formatted,
692
+ cacheRenderedMessage: true,
693
+ retryable: retryableMaskedProviderFailure,
694
+ retryReason: retryableMaskedProviderFailure ? "provider_fetch_failed" : undefined,
695
+ hasQuotaRows: true,
696
+ ...collectionMetadata,
697
+ };
698
+ }
699
+ const debugFooter = `\n\n[debug] src=${params.runtime.configMeta.source} providers=${runtimeConfig.enabledProviders === "auto" ? "(auto)" : runtimeConfig.enabledProviders.join(",") || "(none)"} avail=${availability
700
+ .map((item) => `${item.provider.id}:${item.ok ? "ok" : "no"}`)
701
+ .join(" ")}`;
702
+ return {
703
+ message: formatted + debugFooter,
704
+ cacheRenderedMessage: false,
705
+ retryable: retryableMaskedProviderFailure,
706
+ retryReason: retryableMaskedProviderFailure ? "provider_fetch_failed" : undefined,
707
+ hasQuotaRows: true,
708
+ ...collectionMetadata,
709
+ };
710
+ }
711
+ if ((runtimeConfig.showOnBothFail && attemptedAny && errors.length > 0) ||
712
+ hasExplicitProviderIssues) {
713
+ const errorLines = errors.map((error) => `${error.label}: ${error.message}`).join("\n");
714
+ const retryableFetchFailure = hasRetryableFetchFailure;
715
+ const retryableFailure = retryableFetchFailure || retryableAvailabilityFailure;
716
+ const retryReason = retryableFetchFailure
717
+ ? "provider_fetch_failed"
718
+ : retryableAvailabilityFailure
719
+ ? "no_available_providers"
720
+ : undefined;
721
+ const message = !runtimeConfig.debug
722
+ ? errorLines || "Quota unavailable"
723
+ : (errorLines || "Quota unavailable") +
724
+ "\n\n" +
725
+ formatQuotaToastDebugInfo({
726
+ trigger: params.trigger,
727
+ reason: hasExplicitProviderIssues
728
+ ? "providers missing/unavailable"
729
+ : "all providers failed",
730
+ config: runtimeConfig,
731
+ configMeta: params.runtime.configMeta,
732
+ currentModel,
733
+ availability: availability.map((item) => ({
734
+ id: item.provider.id,
735
+ ok: item.ok,
736
+ })),
737
+ });
738
+ return {
739
+ message,
740
+ cacheRenderedMessage: false,
741
+ retryable: retryableFailure,
742
+ retryReason,
743
+ hasQuotaRows: false,
744
+ ...collectionMetadata,
745
+ };
746
+ }
747
+ const retryableNoData = hasRetryableFetchFailure ||
748
+ (selection?.isAutoMode === true && active.length > 0 && errors.length === 0);
749
+ return {
750
+ message: runtimeConfig.debug
751
+ ? formatQuotaToastDebugInfo({
752
+ trigger: params.trigger,
753
+ reason: "no entries",
754
+ config: runtimeConfig,
755
+ configMeta: params.runtime.configMeta,
756
+ currentModel,
757
+ availability: availability.map((item) => ({
758
+ id: item.provider.id,
759
+ ok: item.ok,
760
+ })),
761
+ })
762
+ : null,
763
+ cacheRenderedMessage: false,
764
+ retryable: retryableNoData,
765
+ retryReason: hasRetryableFetchFailure
766
+ ? "provider_fetch_failed"
767
+ : retryableNoData
768
+ ? "no_reportable_data"
769
+ : undefined,
770
+ hasQuotaRows: false,
771
+ ...collectionMetadata,
772
+ };
773
+ }
774
+ //# sourceMappingURL=quota-toast-runtime.js.map