@cortexkit/opencode-openai-auth 1.0.4 → 1.1.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 (104) hide show
  1. package/dist/bin.js +9 -8
  2. package/dist/{pool-lifecycle-DqKq8Eej.d.ts → codex-request-BXDXToyt.d.ts} +158 -27
  3. package/dist/index-0yh85bwb.js +69 -0
  4. package/dist/{index-jye6vccc.js → index-4c8yw54k.js} +2 -2
  5. package/dist/{index-k4eacw7x.js → index-7c6sxtd6.js} +2 -2
  6. package/dist/index-7eb3dw37.js +90 -0
  7. package/dist/index-8re8rprk.js +2 -0
  8. package/dist/{index-bgq9d21h.js → index-csky2xpg.js} +1 -1
  9. package/dist/index-edbdscy7.js +65 -0
  10. package/dist/{index-45qgkb0m.js → index-f2fmb596.js} +1 -1
  11. package/dist/{index-envpbt0k.js → index-j8v2xwq6.js} +1 -1
  12. package/dist/{index-egrty6tz.js → index-k6cydkgt.js} +2 -2
  13. package/dist/{index-dryydk6b.js → index-m5qmvr6c.js} +2 -2
  14. package/dist/index-msx2m78k.js +8 -0
  15. package/dist/{index-yeyp8t0k.js → index-zpm95mt5.js} +1 -1
  16. package/dist/index.d.ts +15 -7
  17. package/dist/index.js +1 -1
  18. package/dist/{map-CCbSZIhT.d.ts → map-Buq-GW7l.d.ts} +1 -1
  19. package/dist/{protocol-CSIDDBDf.d.ts → model-YRg7Uowo.d.ts} +67 -25
  20. package/dist/{port-file-CZ9MIdy2.d.ts → port-file-CxEpgSHY.d.ts} +1 -1
  21. package/dist/protocol-CDV1Pc6h.d.ts +29 -0
  22. package/dist/rpc/port-file.d.ts +1 -1
  23. package/dist/rpc/port-file.js +1 -1
  24. package/dist/rpc/protocol.d.ts +1 -1
  25. package/dist/rpc/protocol.js +1 -1
  26. package/dist/rpc/rpc-client.d.ts +3 -3
  27. package/dist/rpc/rpc-client.js +1 -1
  28. package/dist/rpc/rpc-dir.js +1 -1
  29. package/dist/{sidebar-state-BCxFpH68.d.ts → sidebar-state-DjTWUbmf.d.ts} +15 -9
  30. package/dist/sidebar-state.d.ts +1 -1
  31. package/dist/sidebar-state.js +1 -1
  32. package/dist/{token-vykm9bxh.js → token-rz4t3ay6.js} +1 -1
  33. package/dist/token-util-tpy2c7h6.js +1 -0
  34. package/dist/tui-preferences.js +1 -1
  35. package/dist/tui.d.ts +20 -2
  36. package/dist/v2/server.d.ts +73 -1
  37. package/dist/v2/server.js +3 -3
  38. package/package.json +2 -2
  39. package/src/tui-compiled/raw/shared/0332701351b3eeec-format.js +78 -42
  40. package/src/tui-compiled/raw/shared/0f77ebc3c914c9c6-custody.js +354 -0
  41. package/src/tui-compiled/raw/shared/134b008900698fe0-backoff.js +92 -0
  42. package/src/tui-compiled/raw/shared/169b2b4bf30909bc-consumer.js +393 -0
  43. package/src/tui-compiled/raw/shared/2577ec69f9973ae9-quota-normalize.js +154 -0
  44. package/src/tui-compiled/raw/shared/2dce6e9613993329-quota-manager.js +473 -0
  45. package/src/tui-compiled/raw/shared/30f5bc480f31f51c-roster.js +566 -0
  46. package/src/tui-compiled/raw/shared/43fc9e6b994519c7-refresh-lock.js +0 -1
  47. package/src/tui-compiled/raw/shared/4593a4d71edf69fd-tombstone.js +25 -0
  48. package/src/tui-compiled/raw/shared/4d05c2d7a531aece-oauth.js +500 -0
  49. package/src/tui-compiled/raw/shared/6371b24a0573d79e-any-signal.js +42 -0
  50. package/src/tui-compiled/raw/shared/7c140680fc81be92-interlock.js +29 -0
  51. package/src/tui-compiled/raw/shared/85512cc5263ba259-provider.js +87 -0
  52. package/src/tui-compiled/raw/shared/abd062d633e29fd2-pool-routing.js +192 -0
  53. package/src/tui-compiled/raw/shared/b9588ad4725c1d10-enrollment.js +806 -0
  54. package/src/tui-compiled/raw/shared/bfbdd0f9aae56b47-pool-quota.js +85 -0
  55. package/src/tui-compiled/raw/shared/c852a15cfe489d12-pool.js +0 -1
  56. package/src/tui-compiled/raw/shared/ca0fccaba1d87af4-errors.js +8 -0
  57. package/src/tui-compiled/raw/shared/d0f0b6be200311af-insula.js +152 -0
  58. package/src/tui-compiled/raw/shared/d5a8f1061ee2f114-index.js +1 -1
  59. package/src/tui-compiled/raw/shared/d9d2892a4c371301-host-slot.js +72 -0
  60. package/src/tui-compiled/raw/shared/dbc61cd405941828-accounts.js +1470 -0
  61. package/src/tui-compiled/raw/shared/e8cd4929507508d8-refresh-file-lock.js +26 -12
  62. package/src/tui-compiled/raw/shared/e8d3c8da776ef9cd-index.js +24 -0
  63. package/src/tui-compiled/raw/sidebar-state.js +41 -14
  64. package/src/tui-compiled/raw/tui/command-dialogs.tsx +3 -34
  65. package/src/tui-compiled/raw/tui/section-text.js +21 -0
  66. package/src/tui-compiled/raw/tui/v2-dialog.js +137 -0
  67. package/src/tui-compiled/raw/tui/v2.tsx +133 -0
  68. package/src/tui-compiled/raw/tui.tsx +146 -40
  69. package/src/tui-compiled/runtime/shared/0332701351b3eeec-format.js +78 -42
  70. package/src/tui-compiled/runtime/shared/0f77ebc3c914c9c6-custody.js +354 -0
  71. package/src/tui-compiled/runtime/shared/134b008900698fe0-backoff.js +92 -0
  72. package/src/tui-compiled/runtime/shared/169b2b4bf30909bc-consumer.js +393 -0
  73. package/src/tui-compiled/runtime/shared/2577ec69f9973ae9-quota-normalize.js +154 -0
  74. package/src/tui-compiled/runtime/shared/2dce6e9613993329-quota-manager.js +473 -0
  75. package/src/tui-compiled/runtime/shared/30f5bc480f31f51c-roster.js +566 -0
  76. package/src/tui-compiled/runtime/shared/43fc9e6b994519c7-refresh-lock.js +0 -1
  77. package/src/tui-compiled/runtime/shared/4593a4d71edf69fd-tombstone.js +25 -0
  78. package/src/tui-compiled/runtime/shared/4d05c2d7a531aece-oauth.js +500 -0
  79. package/src/tui-compiled/runtime/shared/6371b24a0573d79e-any-signal.js +42 -0
  80. package/src/tui-compiled/runtime/shared/7c140680fc81be92-interlock.js +29 -0
  81. package/src/tui-compiled/runtime/shared/85512cc5263ba259-provider.js +87 -0
  82. package/src/tui-compiled/runtime/shared/abd062d633e29fd2-pool-routing.js +192 -0
  83. package/src/tui-compiled/runtime/shared/b9588ad4725c1d10-enrollment.js +806 -0
  84. package/src/tui-compiled/runtime/shared/bfbdd0f9aae56b47-pool-quota.js +85 -0
  85. package/src/tui-compiled/runtime/shared/c852a15cfe489d12-pool.js +0 -1
  86. package/src/tui-compiled/runtime/shared/ca0fccaba1d87af4-errors.js +8 -0
  87. package/src/tui-compiled/runtime/shared/d0f0b6be200311af-insula.js +152 -0
  88. package/src/tui-compiled/runtime/shared/d5a8f1061ee2f114-index.js +1 -1
  89. package/src/tui-compiled/runtime/shared/d9d2892a4c371301-host-slot.js +72 -0
  90. package/src/tui-compiled/runtime/shared/dbc61cd405941828-accounts.js +1470 -0
  91. package/src/tui-compiled/runtime/shared/e8cd4929507508d8-refresh-file-lock.js +26 -12
  92. package/src/tui-compiled/runtime/shared/e8d3c8da776ef9cd-index.js +24 -0
  93. package/src/tui-compiled/runtime/sidebar-state.js +41 -14
  94. package/src/tui-compiled/runtime/tui/command-dialogs.js +3 -15
  95. package/src/tui-compiled/runtime/tui/section-text.js +21 -0
  96. package/src/tui-compiled/runtime/tui/v2-dialog.js +137 -0
  97. package/src/tui-compiled/runtime/tui/v2.js +95 -0
  98. package/src/tui-compiled/runtime/tui.js +255 -163
  99. package/dist/index-2hch7e8n.js +0 -8
  100. package/dist/index-5dg931q0.js +0 -65
  101. package/dist/index-99esnzg0.js +0 -157
  102. package/dist/index-9reyazwh.js +0 -2
  103. package/dist/index-yf6arn5w.js +0 -2
  104. package/dist/token-util-gmn5yaqc.js +0 -1
@@ -0,0 +1,473 @@
1
+ import { createHash } from "node:crypto";
2
+ import { acquireRefreshFileLock } from "./ec971142548df90e-index.js";
3
+ import { buildQuotaOperationError, quotaBackoffActive } from "./134b008900698fe0-backoff.js";
4
+ import { PRIMARY, SECONDARY } from "./85512cc5263ba259-provider.js";
5
+ import { isTombstoned } from "./4593a4d71edf69fd-tombstone.js";
6
+ const nativeSetTimeout = globalThis.setTimeout, DEFAULT_QUOTA_CHECK_INTERVAL_MINUTES = 5;
7
+ function getQuotaCheckIntervalMs(storage) {
8
+ const minutes = storage?.quota?.checkIntervalMinutes ?? DEFAULT_QUOTA_CHECK_INTERVAL_MINUTES;
9
+ return Math.max(1, minutes) * 60000;
10
+ }
11
+ function quotaEnabled(storage) {
12
+ return storage?.quota?.enabled !== !1;
13
+ }
14
+ export function quotaWindowResetIsPast(window, now) {
15
+ if (!window?.resetsAt)
16
+ return !1;
17
+ const resetTime = Date.parse(window.resetsAt);
18
+ return Number.isFinite(resetTime) && resetTime <= now;
19
+ }
20
+ export function resolveMidStreamRateLimitResetAt(quota, window, now, defaultMs, explicitResetAt) {
21
+ if (explicitResetAt !== void 0 && Number.isFinite(explicitResetAt) && explicitResetAt > now)
22
+ return explicitResetAt;
23
+ const named = window === PRIMARY ? quota?.primary : window === SECONDARY ? quota?.secondary : void 0, namedReset = named?.resetsAt ? Date.parse(named.resetsAt) : NaN;
24
+ if (Number.isFinite(namedReset) && namedReset > now)
25
+ return namedReset;
26
+ return now + defaultMs;
27
+ }
28
+ function normalizeThresholds(storage) {
29
+ const configured = storage?.quota?.minimumRemaining || {};
30
+ return {
31
+ primary: configured.primary ?? configured["5h"] ?? 15,
32
+ secondary: configured.secondary ?? configured["1w"] ?? 10
33
+ };
34
+ }
35
+ function getQuotaNextRefreshAt(quota, storage, now) {
36
+ if (!quotaEnabled(storage))
37
+ return now + getQuotaCheckIntervalMs(storage);
38
+ const thresholds = normalizeThresholds(storage), blockedResetTimes = [];
39
+ for (const key of ["primary", "secondary"]) {
40
+ const window = quota?.[key];
41
+ if (!window)
42
+ continue;
43
+ if (window.remainingPercent >= thresholds[key])
44
+ continue;
45
+ const resetTime = window.resetsAt ? Date.parse(window.resetsAt) : Number.NaN;
46
+ if (!Number.isFinite(resetTime) || resetTime <= now)
47
+ return now + getQuotaCheckIntervalMs(storage);
48
+ blockedResetTimes.push(resetTime);
49
+ }
50
+ if (!blockedResetTimes.length)
51
+ return now + getQuotaCheckIntervalMs(storage);
52
+ return Math.min(...blockedResetTimes) + 60000;
53
+ }
54
+ function getPersistedMainQuota(storage) {
55
+ if (!storage?.quota?.mainQuota || !storage.quota.mainQuotaCheckedAt)
56
+ return null;
57
+ return {
58
+ quota: storage.quota.mainQuota,
59
+ checkedAt: storage.quota.mainQuotaCheckedAt,
60
+ tokenFingerprint: storage.quota.mainQuotaToken
61
+ };
62
+ }
63
+ function getQuotaRefreshEveryNRequests(storage) {
64
+ const n = storage?.quota?.refreshEveryNRequests;
65
+ return typeof n === "number" && Number.isFinite(n) && n > 0 ? Math.floor(n) : 0;
66
+ }
67
+ export function tokenFingerprint(token) {
68
+ return createHash("sha256").update(token).digest("hex").slice(0, 16);
69
+ }
70
+
71
+ export class QuotaManager {
72
+ main = null;
73
+ mainTokenFp = null;
74
+ mainQuotaAccountId = null;
75
+ fallbacks = new Map;
76
+ fallbackTokenFps = new Map;
77
+ inflightMain = null;
78
+ inflightMainFp = null;
79
+ inflightFallbacks = new Map;
80
+ mainLastApiError = void 0;
81
+ fallbackApiErrors = new Map;
82
+ fallbackErrorTokenFps = new Map;
83
+ rateLimitedUntilMap = new Map;
84
+ apiGate = Promise.resolve();
85
+ lastApiCallAt = 0;
86
+ storage;
87
+ fetchImpl;
88
+ now;
89
+ fetchQuotaFn;
90
+ onMainQuotaFetched;
91
+ onApiError;
92
+ configPath;
93
+ constructor(opts) {
94
+ this.storage = opts.storage;
95
+ this.fetchImpl = opts.fetchImpl ?? fetch;
96
+ this.configPath = opts.configPath;
97
+ this.now = opts.now ?? Date.now;
98
+ this.fetchQuotaFn = opts.fetchQuotaFn;
99
+ this.onMainQuotaFetched = opts.onMainQuotaFetched;
100
+ this.onApiError = opts.onApiError;
101
+ this.seedMainFromStorage(opts.storage);
102
+ this.seedMainBackoffFromStorage(opts.storage);
103
+ }
104
+ getMain(accessToken) {
105
+ if (accessToken && this.main && this.mainTokenFp && this.mainTokenFp !== tokenFingerprint(accessToken)) {
106
+ this.main = null;
107
+ this.mainTokenFp = null;
108
+ }
109
+ return this.main;
110
+ }
111
+ getFallback(accountId, accessToken) {
112
+ const entry = this.fallbacks.get(accountId) ?? null;
113
+ if (!accessToken || !entry)
114
+ return entry;
115
+ if (this.fallbackTokenFps.get(accountId) !== tokenFingerprint(accessToken)) {
116
+ this.fallbacks.delete(accountId);
117
+ this.fallbackTokenFps.delete(accountId);
118
+ return null;
119
+ }
120
+ return entry;
121
+ }
122
+ getAllFallbacks() {
123
+ return this.fallbacks;
124
+ }
125
+ setMain(accessToken, entry, accountId, completeSnapshot = !1) {
126
+ if (!completeSnapshot && !hasAnyQuotaWindow(entry.quota))
127
+ return;
128
+ this.mainTokenFp = tokenFingerprint(accessToken);
129
+ if (accountId && this.mainQuotaAccountId && this.mainQuotaAccountId !== accountId)
130
+ this.clearRateLimited("main");
131
+ if (accountId)
132
+ this.mainQuotaAccountId = accountId;
133
+ this.main = entry;
134
+ if (quotaLooksHealthy(entry.quota))
135
+ this.clearRateLimited("main");
136
+ }
137
+ peekMainForPolicy(accountId) {
138
+ if (accountId && this.mainQuotaAccountId && this.mainQuotaAccountId !== accountId)
139
+ return null;
140
+ return this.main;
141
+ }
142
+ peekFallbackForPolicy(accountId, identity) {
143
+ const entry = this.fallbacks.get(accountId) ?? null;
144
+ if (identity && entry?.accountId && entry.accountId !== identity)
145
+ return null;
146
+ return entry;
147
+ }
148
+ setFallback(accountId, entry, accessToken, completeSnapshot = !1, identity) {
149
+ if (!completeSnapshot && !hasAnyQuotaWindow(entry.quota))
150
+ return;
151
+ const previousIdentity = this.fallbacks.get(accountId)?.accountId;
152
+ if (identity && previousIdentity && identity !== previousIdentity)
153
+ this.clearRateLimited(accountId);
154
+ const boundIdentity = identity ?? previousIdentity;
155
+ this.fallbacks.set(accountId, { ...entry, accountId: boundIdentity });
156
+ if (accessToken)
157
+ this.fallbackTokenFps.set(accountId, tokenFingerprint(accessToken));
158
+ else
159
+ this.fallbackTokenFps.delete(accountId);
160
+ if (quotaLooksHealthy(entry.quota))
161
+ this.clearRateLimited(accountId);
162
+ }
163
+ markRateLimited(accountId, resetAtMs) {
164
+ const existing = this.rateLimitedUntilMap.get(accountId);
165
+ if (existing !== void 0 && existing > resetAtMs)
166
+ return;
167
+ this.rateLimitedUntilMap.set(accountId, resetAtMs);
168
+ }
169
+ isRateLimited(accountId) {
170
+ const until = this.rateLimitedUntilMap.get(accountId);
171
+ return until !== void 0 && this.now() < until;
172
+ }
173
+ rateLimitedUntil(accountId) {
174
+ return this.rateLimitedUntilMap.get(accountId);
175
+ }
176
+ clearRateLimited(accountId) {
177
+ this.rateLimitedUntilMap.delete(accountId);
178
+ }
179
+ async refreshMain(accessToken) {
180
+ const fp = tokenFingerprint(accessToken);
181
+ if (this.mainTokenFp && this.mainTokenFp !== fp) {
182
+ this.main = null;
183
+ this.mainTokenFp = null;
184
+ }
185
+ if (this.inflightMain && this.inflightMainFp === fp)
186
+ return this.inflightMain;
187
+ if (this.isBackedOff()) {
188
+ if (this.main)
189
+ return this.main.quota;
190
+ throw Error("Quota API rate-limited \u2014 try again later");
191
+ }
192
+ this.inflightMainFp = fp;
193
+ this.inflightMain = this._fetchMain(accessToken);
194
+ return this.inflightMain;
195
+ }
196
+ async refreshFallback(accountId, accessToken) {
197
+ const inflightKey = QuotaManager.fallbackInflightKey(accountId, accessToken), inflight = this.inflightFallbacks.get(inflightKey);
198
+ if (inflight)
199
+ return inflight;
200
+ if (this.isFallbackBackedOff(accountId, accessToken)) {
201
+ const cached = this.getFallback(accountId, accessToken);
202
+ if (cached)
203
+ return cached.quota;
204
+ throw Error("Quota API rate-limited \u2014 try again later");
205
+ }
206
+ const promise = this._fetchFallback(accountId, accessToken);
207
+ this.inflightFallbacks.set(inflightKey, promise);
208
+ return promise;
209
+ }
210
+ async refreshAllFallbacks(accounts) {
211
+ const now = this.now();
212
+ for (const account of accounts) {
213
+ if (account.enabled === !1)
214
+ continue;
215
+ if (isTombstoned(account) || !account.access)
216
+ continue;
217
+ const cached = this.getFallback(account.id, account.access);
218
+ if (cached && now < cached.refreshAfter)
219
+ continue;
220
+ try {
221
+ await this.refreshFallback(account.id, account.access);
222
+ } catch {}
223
+ }
224
+ }
225
+ refreshMainInBackground(accessToken) {
226
+ const fp = tokenFingerprint(accessToken);
227
+ if (this.inflightMain && this.inflightMainFp === fp)
228
+ return;
229
+ if (this.isBackedOff())
230
+ return;
231
+ this.refreshMain(accessToken).catch(() => {});
232
+ }
233
+ isMainStale() {
234
+ if (!this.main)
235
+ return !0;
236
+ return this.now() >= this.main.refreshAfter;
237
+ }
238
+ isFallbackStale(accountId, accessToken) {
239
+ const entry = this.getFallback(accountId, accessToken);
240
+ if (!entry)
241
+ return !0;
242
+ return this.now() >= entry.refreshAfter;
243
+ }
244
+ shouldRefreshOnRequestCount(requestCount) {
245
+ const everyN = getQuotaRefreshEveryNRequests(this.storage);
246
+ if (everyN <= 0)
247
+ return !1;
248
+ return requestCount > 0 && requestCount % everyN === 0;
249
+ }
250
+ needsRefresh(requestCount) {
251
+ return this.isMainStale() || this.shouldRefreshOnRequestCount(requestCount);
252
+ }
253
+ updateStorage(storage) {
254
+ this.storage = storage;
255
+ this.seedMainFromStorage(storage);
256
+ this.seedMainBackoffFromStorage(storage);
257
+ }
258
+ seedMainFromStorage(storage, accessToken) {
259
+ const persisted = getPersistedMainQuota(storage);
260
+ if (!persisted)
261
+ return;
262
+ const accessTokenFp = accessToken ? tokenFingerprint(accessToken) : null;
263
+ if (accessTokenFp && persisted.tokenFingerprint && persisted.tokenFingerprint !== accessTokenFp)
264
+ return;
265
+ const entry = {
266
+ quota: persisted.quota,
267
+ refreshAfter: getQuotaNextRefreshAt(persisted.quota, storage, persisted.checkedAt),
268
+ checkedAt: persisted.checkedAt
269
+ };
270
+ if (this.main && this.main.checkedAt >= entry.checkedAt && (!accessTokenFp || !this.mainTokenFp || this.mainTokenFp === accessTokenFp))
271
+ return;
272
+ this.main = entry;
273
+ this.mainTokenFp = persisted.tokenFingerprint ?? null;
274
+ if (storage?.mainAccountId) {
275
+ if (this.mainQuotaAccountId && this.mainQuotaAccountId !== storage.mainAccountId)
276
+ this.clearRateLimited("main");
277
+ this.mainQuotaAccountId = storage.mainAccountId;
278
+ }
279
+ }
280
+ seedMainBackoffFromStorage(storage) {
281
+ const persistedError = storage?.quota?.mainLastQuotaApiError;
282
+ this.mainLastApiError = persistedError && quotaBackoffActive(persistedError, this.now()) ? persistedError : void 0;
283
+ }
284
+ seedFallbacksFromAccounts(accounts) {
285
+ const checkInterval = getQuotaCheckIntervalMs(this.storage);
286
+ for (const account of accounts) {
287
+ if (account.enabled === !1)
288
+ continue;
289
+ if (!account.quota)
290
+ continue;
291
+ const checkedAt = Math.max(account.quota.primary?.checkedAt ?? 0, account.quota.secondary?.checkedAt ?? 0);
292
+ if (checkedAt <= 0)
293
+ continue;
294
+ const existing = this.getFallback(account.id, account.access);
295
+ if (existing && existing.checkedAt >= checkedAt)
296
+ continue;
297
+ this.setFallback(account.id, {
298
+ quota: account.quota,
299
+ refreshAfter: checkedAt + checkInterval,
300
+ checkedAt
301
+ }, account.access, !1, account.accountId);
302
+ }
303
+ }
304
+ isBackedOff() {
305
+ return quotaBackoffActive(this.mainLastApiError, this.now());
306
+ }
307
+ isFallbackBackedOff(accountId, accessToken) {
308
+ if (accessToken) {
309
+ if (this.fallbackErrorTokenFps.get(accountId) !== tokenFingerprint(accessToken))
310
+ return !1;
311
+ }
312
+ return quotaBackoffActive(this.fallbackApiErrors.get(accountId), this.now());
313
+ }
314
+ getLastApiError() {
315
+ return this.mainLastApiError;
316
+ }
317
+ static API_CALL_GAP_MS = 1000;
318
+ static fallbackInflightKey(accountId, accessToken) {
319
+ return JSON.stringify([accountId, tokenFingerprint(accessToken)]);
320
+ }
321
+ static quotaLockName(accountId) {
322
+ return `opencode-fallback-quota-refresh-${accountId.replace(/[^a-zA-Z0-9._-]+/g, "-") || "account"}`;
323
+ }
324
+ _enqueueApiFetch(fn) {
325
+ const gatedFn = async () => {
326
+ const elapsed = this.now() - this.lastApiCallAt;
327
+ if (elapsed < QuotaManager.API_CALL_GAP_MS)
328
+ await new Promise((r) => {
329
+ const id = nativeSetTimeout(r, QuotaManager.API_CALL_GAP_MS - elapsed);
330
+ if (typeof id === "object" && "unref" in id)
331
+ id.unref();
332
+ });
333
+ this.lastApiCallAt = this.now();
334
+ return fn();
335
+ }, queued = this.apiGate.then(gatedFn, gatedFn);
336
+ this.apiGate = queued.catch(() => {});
337
+ return queued;
338
+ }
339
+ async _fetchMain(accessToken) {
340
+ const thisFetchFp = tokenFingerprint(accessToken);
341
+ return this._enqueueApiFetch(async () => {
342
+ try {
343
+ if (this.isBackedOff()) {
344
+ if (this.main)
345
+ return this.main.quota;
346
+ throw Error("Quota API rate-limited \u2014 try again later");
347
+ }
348
+ const fileLock = await acquireRefreshFileLock({
349
+ name: "opencode-main-quota-refresh",
350
+ ttlMs: 30000,
351
+ path: this.configPath
352
+ });
353
+ if (!fileLock) {
354
+ const cached = this.main;
355
+ if (cached && this.now() < cached.refreshAfter)
356
+ return cached.quota;
357
+ throw Error("Quota refresh is already in progress");
358
+ }
359
+ try {
360
+ const fetchStartedAt = this.now(), quota = await this._fetchQuota(accessToken), now = this.now();
361
+ this.mainTokenFp = tokenFingerprint(accessToken);
362
+ this.main = {
363
+ quota,
364
+ refreshAfter: getQuotaNextRefreshAt(quota, this.storage, now),
365
+ checkedAt: now
366
+ };
367
+ if (quotaLooksHealthy(quota))
368
+ this.clearRateLimited("main");
369
+ this.mainLastApiError = void 0;
370
+ this.onMainQuotaFetched?.(quota, now, this.mainTokenFp, fetchStartedAt);
371
+ return quota;
372
+ } catch (error) {
373
+ this._handleMainFetchError(error);
374
+ throw error;
375
+ } finally {
376
+ await fileLock.release();
377
+ }
378
+ } finally {
379
+ if (this.inflightMainFp === thisFetchFp) {
380
+ this.inflightMain = null;
381
+ this.inflightMainFp = null;
382
+ }
383
+ }
384
+ });
385
+ }
386
+ async _fetchFallback(accountId, accessToken) {
387
+ return this._enqueueApiFetch(async () => {
388
+ try {
389
+ if (this.isFallbackBackedOff(accountId, accessToken)) {
390
+ const cached = this.getFallback(accountId, accessToken);
391
+ if (cached)
392
+ return cached.quota;
393
+ throw Error("Quota API rate-limited \u2014 try again later");
394
+ }
395
+ const fileLock = await acquireRefreshFileLock({
396
+ name: QuotaManager.quotaLockName(accountId),
397
+ ttlMs: 30000,
398
+ path: this.configPath
399
+ });
400
+ if (!fileLock) {
401
+ const cached = this.getFallback(accountId, accessToken);
402
+ if (cached && this.now() < cached.refreshAfter)
403
+ return cached.quota;
404
+ throw Error("Quota refresh is already in progress");
405
+ }
406
+ try {
407
+ const quota = await this._fetchQuota(accessToken), now = this.now();
408
+ this.setFallback(accountId, {
409
+ quota,
410
+ refreshAfter: now + getQuotaCheckIntervalMs(this.storage),
411
+ checkedAt: now
412
+ }, accessToken, !0);
413
+ this.fallbackApiErrors.delete(accountId);
414
+ this.fallbackErrorTokenFps.delete(accountId);
415
+ return quota;
416
+ } finally {
417
+ await fileLock.release();
418
+ }
419
+ } catch (error) {
420
+ this._handleFallbackFetchError(accountId, accessToken, error);
421
+ throw error;
422
+ } finally {
423
+ this.inflightFallbacks.delete(QuotaManager.fallbackInflightKey(accountId, accessToken));
424
+ }
425
+ });
426
+ }
427
+ async _fetchQuota(accessToken) {
428
+ if (!this.fetchQuotaFn)
429
+ throw Error("Quota fetch not available \u2014 no fetchQuotaFn injected (push-only mode)");
430
+ return this.fetchQuotaFn({
431
+ accessToken,
432
+ fetchImpl: this.fetchImpl,
433
+ now: this.now
434
+ });
435
+ }
436
+ static isAuthError(error) {
437
+ return error?.status === 401;
438
+ }
439
+ _handleMainFetchError(error) {
440
+ if (QuotaManager.isAuthError(error))
441
+ return;
442
+ this.mainLastApiError = buildQuotaOperationError({
443
+ error,
444
+ now: this.now(),
445
+ previous: this.mainLastApiError
446
+ });
447
+ this.onApiError?.(this.mainLastApiError);
448
+ }
449
+ _handleFallbackFetchError(accountId, accessToken, error) {
450
+ if (QuotaManager.isAuthError(error))
451
+ return;
452
+ const tokenFp = tokenFingerprint(accessToken), previous = this.fallbackErrorTokenFps.get(accountId) === tokenFp ? this.fallbackApiErrors.get(accountId) : void 0;
453
+ this.fallbackApiErrors.set(accountId, buildQuotaOperationError({
454
+ error,
455
+ now: this.now(),
456
+ previous
457
+ }));
458
+ this.fallbackErrorTokenFps.set(accountId, tokenFp);
459
+ }
460
+ }
461
+ function hasAnyQuotaWindow(quota) {
462
+ return Boolean(quota.primary || quota.secondary);
463
+ }
464
+ function quotaLooksHealthy(quota) {
465
+ const { primary, secondary } = quota;
466
+ if (!primary && !secondary)
467
+ return !1;
468
+ if (primary && primary.usedPercent >= 100)
469
+ return !1;
470
+ if (secondary && secondary.usedPercent >= 100)
471
+ return !1;
472
+ return !0;
473
+ }