@cortexkit/anthropic-auth-core 1.9.2 → 1.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/accounts.js CHANGED
@@ -289,6 +289,46 @@ function accountRuntimeState(account) {
289
289
  quota: account.quota,
290
290
  });
291
291
  }
292
+ function quotaSnapshotCheckedAt(quota) {
293
+ return Math.max(quota?.five_hour?.checkedAt ?? 0, quota?.seven_day?.checkedAt ?? 0);
294
+ }
295
+ function mergeAccountRuntimeState(existing, incoming) {
296
+ if (!isRecord(existing))
297
+ return incoming;
298
+ const existingEntry = existing;
299
+ const existingQuotaCheckedAt = quotaSnapshotCheckedAt(existingEntry.quota);
300
+ const incomingQuotaCheckedAt = quotaSnapshotCheckedAt(incoming.quota);
301
+ if (existingQuotaCheckedAt > incomingQuotaCheckedAt) {
302
+ const tokenChanged = Boolean(existingEntry.access &&
303
+ incoming.access &&
304
+ existingEntry.access !== incoming.access);
305
+ const existingRefreshAt = existingEntry.lastRefreshedAt ?? 0;
306
+ const incomingRefreshAt = incoming.lastRefreshedAt ?? 0;
307
+ if (tokenChanged && incomingRefreshAt <= existingRefreshAt) {
308
+ const merged = { ...existingEntry };
309
+ if (typeof incoming.lastUsed === 'number' &&
310
+ (!(typeof existingEntry.lastUsed === 'number') ||
311
+ incoming.lastUsed > existingEntry.lastUsed)) {
312
+ merged.lastUsed = incoming.lastUsed;
313
+ }
314
+ return merged;
315
+ }
316
+ return {
317
+ ...existingEntry,
318
+ ...incoming,
319
+ quota: existingEntry.quota,
320
+ lastQuotaRefreshError: existingEntry.lastQuotaRefreshError,
321
+ };
322
+ }
323
+ const merged = { ...existingEntry, ...incoming };
324
+ if (!('lastQuotaRefreshError' in incoming)) {
325
+ delete merged.lastQuotaRefreshError;
326
+ }
327
+ if (!('lastRefreshError' in incoming)) {
328
+ delete merged.lastRefreshError;
329
+ }
330
+ return merged;
331
+ }
292
332
  function configFromStorage(storage) {
293
333
  const refresh = storage.refresh
294
334
  ? objectWithDefinedEntries({
@@ -361,6 +401,14 @@ export async function saveAccounts(storage, path = getAccountStoragePath()) {
361
401
  }
362
402
  function applyMainQuotaStatePatch(state, storage) {
363
403
  state.main = state.main ?? {};
404
+ const existingCheckedAt = typeof state.main.quotaCheckedAt === 'number'
405
+ ? state.main.quotaCheckedAt
406
+ : quotaSnapshotCheckedAt(state.main.quota);
407
+ const incomingCheckedAt = typeof storage.quota?.mainQuotaCheckedAt === 'number'
408
+ ? storage.quota.mainQuotaCheckedAt
409
+ : quotaSnapshotCheckedAt(storage.quota?.mainQuota);
410
+ if (existingCheckedAt > incomingCheckedAt)
411
+ return;
364
412
  state.main.quota = storage.quota?.mainQuota;
365
413
  state.main.quotaCheckedAt = storage.quota?.mainQuotaCheckedAt;
366
414
  state.main.quotaToken = storage.quota?.mainQuotaToken;
@@ -402,7 +450,7 @@ export async function saveAccountState(storage, path = getAccountStoragePath(),
402
450
  for (const account of storage.accounts) {
403
451
  if (ids && !ids.has(account.id))
404
452
  continue;
405
- next.accounts[account.id] = accountRuntimeState(account);
453
+ next.accounts[account.id] = mergeAccountRuntimeState(next.accounts[account.id], accountRuntimeState(account));
406
454
  }
407
455
  if (ids) {
408
456
  for (const id of ids) {
@@ -876,15 +924,18 @@ function tokenNeedsRefresh(account, storage, now) {
876
924
  !account.expires ||
877
925
  account.expires - now <= refreshBeforeExpiryMs(storage));
878
926
  }
879
- function quotaIsStale(account, storage, now) {
927
+ function quotaSnapshotIsFresh(quota, storage, now) {
880
928
  if (!quotaEnabled(storage))
881
- return false;
929
+ return true;
882
930
  const maxAge = getQuotaCheckIntervalMs(storage);
883
- return ['five_hour', 'seven_day'].some((key) => {
884
- const window = account.quota?.[key];
885
- return !window || now - window.checkedAt >= maxAge;
931
+ return ['five_hour', 'seven_day'].every((key) => {
932
+ const window = quota?.[key];
933
+ return Boolean(window && now - window.checkedAt < maxAge);
886
934
  });
887
935
  }
936
+ function quotaIsStale(account, storage, now) {
937
+ return !quotaSnapshotIsFresh(account.quota, storage, now);
938
+ }
888
939
  function cachedQuotaWindowStillRelevant(window, now) {
889
940
  if (!window)
890
941
  return false;
@@ -1012,13 +1063,14 @@ export class FallbackAccountManager {
1012
1063
  seedFallbackQuota(account, storage) {
1013
1064
  if (!this.quotaManager)
1014
1065
  return;
1015
- if (this.quotaManager.getFallback(account.id, account.access))
1016
- return;
1017
1066
  if (!account.quota)
1018
1067
  return;
1019
1068
  const checkedAt = Math.max(account.quota.five_hour?.checkedAt ?? 0, account.quota.seven_day?.checkedAt ?? 0);
1020
1069
  if (checkedAt <= 0)
1021
1070
  return;
1071
+ const existing = this.quotaManager.getFallback(account.id, account.access);
1072
+ if (existing && existing.checkedAt >= checkedAt)
1073
+ return;
1022
1074
  const checkInterval = getQuotaCheckIntervalMs(storage);
1023
1075
  this.quotaManager.setFallback(account.id, {
1024
1076
  quota: account.quota,
@@ -1402,6 +1454,7 @@ export class FallbackAccountManager {
1402
1454
  fetchImpl: this.fetchImpl,
1403
1455
  now: this.now,
1404
1456
  });
1457
+ const fetchStartedAt = this.now();
1405
1458
  try {
1406
1459
  target.quota = await fetchSnapshot(target.access);
1407
1460
  }
@@ -1417,6 +1470,24 @@ export class FallbackAccountManager {
1417
1470
  // 401 does not arm QuotaManager backoff, so this retry proceeds.
1418
1471
  target.quota = await fetchSnapshot(target.access);
1419
1472
  }
1473
+ const latestStorage = await this.load();
1474
+ const latestAccount = latestStorage?.accounts.find((candidate) => candidate.id === target.id && isOAuthAccount(candidate));
1475
+ if (latestStorage &&
1476
+ latestAccount &&
1477
+ latestAccount.access !== target.access) {
1478
+ this.seedFallbackQuota(latestAccount, latestStorage);
1479
+ updateStoredAccount(storage, latestAccount);
1480
+ return latestAccount;
1481
+ }
1482
+ if (latestStorage &&
1483
+ latestAccount?.access === target.access &&
1484
+ latestAccount.quota &&
1485
+ quotaSnapshotCheckedAt(latestAccount.quota) >= fetchStartedAt &&
1486
+ quotaSnapshotIsFresh(latestAccount.quota, latestStorage, this.now())) {
1487
+ this.seedFallbackQuota(latestAccount, latestStorage);
1488
+ updateStoredAccount(storage, latestAccount);
1489
+ return latestAccount;
1490
+ }
1420
1491
  target.lastQuotaRefreshError = undefined;
1421
1492
  updateStoredAccount(storage, target);
1422
1493
  // Sync to shared QuotaManager so all consumers see the same cache. The
package/dist/dump.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ type DumpHeaders = ConstructorParameters<typeof Headers>[0];
1
2
  export declare const CLAUDE_DUMP_COMMAND_NAME = "claude-dump";
2
3
  export type DumpCommandAction = {
3
4
  type: 'status';
@@ -20,6 +21,16 @@ export declare function executeDumpCommand(input: {
20
21
  argumentsText: string;
21
22
  enabled?: boolean;
22
23
  }): string;
24
+ export declare function dumpDirectRequest(input: {
25
+ affinity?: string | null;
26
+ route?: string;
27
+ status?: number;
28
+ error?: string;
29
+ bodyText: string;
30
+ url?: string;
31
+ method?: string;
32
+ headers?: DumpHeaders;
33
+ }): Promise<void>;
23
34
  export declare function dumpRelayRequest(input: {
24
35
  affinity: string;
25
36
  transport: 'http' | 'websocket';
@@ -31,3 +42,4 @@ export declare function dumpRelayRequest(input: {
31
42
  payload: unknown;
32
43
  relayBytes: number;
33
44
  }): Promise<void>;
45
+ export {};
package/dist/dump.js CHANGED
@@ -14,6 +14,8 @@ const DUMP_DIR_ENV = 'OPENCODE_ANTHROPIC_AUTH_DUMP_DIR';
14
14
  const DEFAULT_DUMP_DIR = join(tmpdir(), 'opencode-anthropic-auth-dumps');
15
15
  let dumpEnabled = false;
16
16
  let nextDumpId = 0;
17
+ const DIRECT_DUMP_PREVIOUS_BODY_LIMIT = 100;
18
+ const directDumpPreviousBodies = new Map();
17
19
  export function isDumpEnabled() {
18
20
  return dumpEnabled;
19
21
  }
@@ -22,6 +24,8 @@ export function setDumpEnabled(enabled) {
22
24
  }
23
25
  export function resetDumpState() {
24
26
  dumpEnabled = false;
27
+ nextDumpId = 0;
28
+ directDumpPreviousBodies.clear();
25
29
  }
26
30
  export function getDumpDirectory() {
27
31
  return process.env[DUMP_DIR_ENV] || DEFAULT_DUMP_DIR;
@@ -44,7 +48,7 @@ export function buildDumpStatusSummary(input) {
44
48
  `- Enabled: ${enabled ? 'enabled' : 'disabled'}`,
45
49
  `- Directory: ${getDumpDirectory()}`,
46
50
  '- Persisted: ~/.config/opencode/anthropic-auth.json',
47
- '- Captures: final rewritten Anthropic body plus redacted relay payload metadata',
51
+ '- Captures: final rewritten Anthropic body in direct and relay modes; request/relay metadata is redacted',
48
52
  '- Warning: body dumps may contain prompt/session content; turn this off after debugging',
49
53
  ].join('\n');
50
54
  }
@@ -87,6 +91,35 @@ function dumpFileSessionSegment(affinity) {
87
91
  return 'session-unknown';
88
92
  return normalized.length <= 80 ? normalized : normalized.slice(0, 80);
89
93
  }
94
+ function dumpRequestSegment(input) {
95
+ if (input.transport !== 'direct') {
96
+ return `${input.transport}-p${input.protocol}-${input.mode}`;
97
+ }
98
+ const route = input.route ? `-${dumpFileSessionSegment(input.route)}` : '';
99
+ return `direct${route}`;
100
+ }
101
+ function directDumpPreviousKey(input) {
102
+ const affinity = input.affinity?.trim();
103
+ if (affinity)
104
+ return `session:${affinity}`;
105
+ return `request:${input.route ?? 'direct'}:${input.url ?? ''}`;
106
+ }
107
+ function rememberDirectDumpBody(key, bodyText) {
108
+ if (!directDumpPreviousBodies.has(key)) {
109
+ while (directDumpPreviousBodies.size >= DIRECT_DUMP_PREVIOUS_BODY_LIMIT) {
110
+ const oldest = directDumpPreviousBodies.keys().next().value;
111
+ if (oldest === undefined)
112
+ break;
113
+ directDumpPreviousBodies.delete(oldest);
114
+ }
115
+ }
116
+ directDumpPreviousBodies.set(key, bodyText);
117
+ }
118
+ function headersToRecord(headers) {
119
+ if (headers == null)
120
+ return undefined;
121
+ return Object.fromEntries(new Headers(headers).entries());
122
+ }
90
123
  function hashText(value) {
91
124
  return createHash('sha256').update(value).digest('hex');
92
125
  }
@@ -189,42 +222,92 @@ function redactForDump(value) {
189
222
  }
190
223
  return redacted;
191
224
  }
192
- export async function dumpRelayRequest(input) {
225
+ async function dumpRequest(input) {
193
226
  if (!dumpEnabled)
194
227
  return;
195
228
  nextDumpId += 1;
196
- const id = `${new Date().toISOString().replace(/[:.]/g, '-')}-${String(nextDumpId).padStart(5, '0')}-${dumpFileSessionSegment(input.affinity)}-${input.transport}-p${input.protocol}-${input.mode}`;
229
+ const affinity = input.affinity?.trim() || 'session-unknown';
230
+ const id = `${new Date().toISOString().replace(/[:.]/g, '-')}-${String(nextDumpId).padStart(5, '0')}-${dumpFileSessionSegment(affinity)}-${dumpRequestSegment(input)}`;
197
231
  const dumpDir = getDumpDirectory();
198
232
  const prefix = join(dumpDir, id);
233
+ const files = {
234
+ body: `${prefix}.body.json`,
235
+ metadata: `${prefix}.meta.json`,
236
+ };
237
+ if (input.payload !== undefined)
238
+ files.relay = `${prefix}.relay.json`;
239
+ if (input.request !== undefined)
240
+ files.request = `${prefix}.request.json`;
199
241
  try {
200
242
  await mkdir(dumpDir, { recursive: true });
201
243
  const metadata = {
202
244
  id,
203
245
  createdAt: new Date().toISOString(),
204
- session: shortAffinity(input.affinity),
246
+ session: shortAffinity(affinity),
205
247
  transport: input.transport,
206
248
  protocol: input.protocol,
207
249
  mode: input.mode,
250
+ route: input.route,
208
251
  status: input.status,
252
+ error: input.error,
209
253
  bodyBytes: input.bodyText.length,
210
254
  relayBytes: input.relayBytes,
211
255
  bodyHash: hashText(input.bodyText),
212
256
  diff: diffSummary(input.previousBodyText, input.bodyText),
213
257
  body: bodyStructureSummary(input.bodyText),
214
- files: {
215
- body: `${prefix}.body.json`,
216
- relay: `${prefix}.relay.json`,
217
- metadata: `${prefix}.meta.json`,
218
- },
258
+ files,
219
259
  };
220
- await Promise.all([
221
- writeFile(`${prefix}.body.json`, input.bodyText, 'utf8'),
222
- writeFile(`${prefix}.relay.json`, `${JSON.stringify(redactForDump(input.payload), null, 2)}\n`, 'utf8'),
223
- writeFile(`${prefix}.meta.json`, `${JSON.stringify(metadata, null, 2)}\n`, 'utf8'),
224
- ]);
225
- relayLog(`dumped request id=${id} session=${shortAffinity(input.affinity)} body=${prefix}.body.json meta=${prefix}.meta.json`);
260
+ const writes = [
261
+ writeFile(files.body, input.bodyText, 'utf8'),
262
+ writeFile(files.metadata, `${JSON.stringify(metadata, null, 2)}\n`, 'utf8'),
263
+ ];
264
+ if (input.payload !== undefined && files.relay) {
265
+ writes.push(writeFile(files.relay, `${JSON.stringify(redactForDump(input.payload), null, 2)}\n`, 'utf8'));
266
+ }
267
+ if (input.request !== undefined && files.request) {
268
+ writes.push(writeFile(files.request, `${JSON.stringify(redactForDump({
269
+ ...input.request,
270
+ headers: headersToRecord(input.request.headers),
271
+ }), null, 2)}\n`, 'utf8'));
272
+ }
273
+ await Promise.all(writes);
274
+ relayLog(`dumped request id=${id} session=${shortAffinity(affinity)} body=${files.body} meta=${files.metadata}`);
226
275
  }
227
276
  catch (error) {
228
277
  relayLog(`dump failed: ${error instanceof Error ? error.message : String(error)}`);
229
278
  }
230
279
  }
280
+ export async function dumpDirectRequest(input) {
281
+ if (!dumpEnabled)
282
+ return;
283
+ const previousKey = directDumpPreviousKey(input);
284
+ const previousBodyText = directDumpPreviousBodies.get(previousKey);
285
+ await dumpRequest({
286
+ affinity: input.affinity,
287
+ transport: 'direct',
288
+ route: input.route,
289
+ status: input.status,
290
+ error: input.error,
291
+ bodyText: input.bodyText,
292
+ previousBodyText,
293
+ request: {
294
+ url: input.url,
295
+ method: input.method,
296
+ headers: input.headers,
297
+ },
298
+ });
299
+ rememberDirectDumpBody(previousKey, input.bodyText);
300
+ }
301
+ export async function dumpRelayRequest(input) {
302
+ await dumpRequest({
303
+ affinity: input.affinity,
304
+ transport: input.transport,
305
+ protocol: input.protocol,
306
+ mode: input.mode,
307
+ status: input.status,
308
+ bodyText: input.bodyText,
309
+ previousBodyText: input.previousBodyText,
310
+ payload: input.payload,
311
+ relayBytes: input.relayBytes,
312
+ });
313
+ }
package/dist/models.d.ts CHANGED
@@ -22,3 +22,4 @@ export declare const CLAUDE_FABLE_MYTHOS_5_MODEL_SPECS: Record<ClaudeFableMythos
22
22
  limited?: boolean;
23
23
  }>;
24
24
  export declare function isClaudeFableOrMythos5Model(model: unknown): boolean;
25
+ export declare function isOpenAIReasoningSignature(value: unknown): boolean;
package/dist/models.js CHANGED
@@ -33,3 +33,22 @@ export function isClaudeFableOrMythos5Model(model) {
33
33
  return (typeof model === 'string' &&
34
34
  CLAUDE_FABLE_MYTHOS_5_MODEL_IDS.some((id) => model === id || model.startsWith(`${id}-`)));
35
35
  }
36
+ export function isOpenAIReasoningSignature(value) {
37
+ if (typeof value !== 'string')
38
+ return false;
39
+ if (value.startsWith('gAAAA'))
40
+ return true;
41
+ if (!value.startsWith('{'))
42
+ return false;
43
+ try {
44
+ const parsed = JSON.parse(value);
45
+ return (parsed.type === 'reasoning' &&
46
+ typeof parsed.id === 'string' &&
47
+ parsed.id.startsWith('rs_') &&
48
+ typeof parsed.encrypted_content === 'string' &&
49
+ parsed.encrypted_content.startsWith('gAAAA'));
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ }
@@ -21,7 +21,7 @@ export type QuotaManagerOptions = {
21
21
  storage: AccountStorage | null;
22
22
  fetchImpl?: typeof fetch;
23
23
  now?: () => number;
24
- onMainQuotaFetched?: (quota: OAuthQuotaSnapshot, checkedAt: number, tokenFingerprint: string) => void;
24
+ onMainQuotaFetched?: (quota: OAuthQuotaSnapshot, checkedAt: number, tokenFingerprint: string, fetchStartedAt: number) => void;
25
25
  onApiError?: (error: AccountOperationError) => void;
26
26
  };
27
27
  export declare class QuotaManager {
@@ -75,10 +75,18 @@ export declare class QuotaManager {
75
75
  */
76
76
  needsRefresh(requestCount: number): boolean;
77
77
  updateStorage(storage: AccountStorage | null): void;
78
+ /**
79
+ * Seed/update the main quota cache from persisted state. This is deliberately
80
+ * callable after every disk load so another plugin process's fresh quota write
81
+ * can stop this process from showing "checking…" or making a redundant quota
82
+ * API call.
83
+ */
84
+ seedMainFromStorage(storage: AccountStorage | null, accessToken?: string): void;
85
+ private seedMainBackoffFromStorage;
78
86
  /**
79
87
  * Seed fallback cache entries from persisted account.quota data.
80
- * Only seeds accounts that don't already have a cache entry.
81
- * Prevents unnecessary API calls when persisted quota is still fresh.
88
+ * Updates older in-memory entries so a fresh quota write from another plugin
89
+ * process prevents redundant checks and stale sidebar writes.
82
90
  */
83
91
  seedFallbacksFromAccounts(accounts: OAuthAccount[]): void;
84
92
  /**
@@ -56,20 +56,8 @@ export class QuotaManager {
56
56
  // that produced it. refreshMain() drops this seed if the live token's
57
57
  // fingerprint differs (main-account switch), preventing stale wrong-account
58
58
  // quota from being served during backoff.
59
- const persisted = getPersistedMainQuota(opts.storage);
60
- if (persisted) {
61
- this.main = {
62
- quota: persisted.quota,
63
- refreshAfter: getQuotaNextRefreshAt(persisted.quota, opts.storage, persisted.checkedAt),
64
- checkedAt: persisted.checkedAt,
65
- };
66
- this.mainTokenFp = persisted.tokenFingerprint ?? null;
67
- }
68
- // Seed backoff state from persisted storage
69
- const persistedError = opts.storage?.quota?.mainLastQuotaApiError;
70
- if (persistedError && quotaBackoffActive(persistedError, this.now())) {
71
- this.mainLastApiError = persistedError;
72
- }
59
+ this.seedMainFromStorage(opts.storage);
60
+ this.seedMainBackoffFromStorage(opts.storage);
73
61
  }
74
62
  // =========================================================================
75
63
  // Get (synchronous, from cache)
@@ -230,24 +218,65 @@ export class QuotaManager {
230
218
  // =========================================================================
231
219
  updateStorage(storage) {
232
220
  this.storage = storage;
221
+ this.seedMainFromStorage(storage);
222
+ this.seedMainBackoffFromStorage(storage);
223
+ }
224
+ /**
225
+ * Seed/update the main quota cache from persisted state. This is deliberately
226
+ * callable after every disk load so another plugin process's fresh quota write
227
+ * can stop this process from showing "checking…" or making a redundant quota
228
+ * API call.
229
+ */
230
+ seedMainFromStorage(storage, accessToken) {
231
+ const persisted = getPersistedMainQuota(storage);
232
+ if (!persisted)
233
+ return;
234
+ const accessTokenFp = accessToken ? tokenFingerprint(accessToken) : null;
235
+ if (accessTokenFp &&
236
+ persisted.tokenFingerprint &&
237
+ persisted.tokenFingerprint !== accessTokenFp) {
238
+ return;
239
+ }
240
+ const entry = {
241
+ quota: persisted.quota,
242
+ refreshAfter: getQuotaNextRefreshAt(persisted.quota, storage, persisted.checkedAt),
243
+ checkedAt: persisted.checkedAt,
244
+ };
245
+ if (this.main &&
246
+ this.main.checkedAt >= entry.checkedAt &&
247
+ (!accessTokenFp ||
248
+ !this.mainTokenFp ||
249
+ this.mainTokenFp === accessTokenFp)) {
250
+ return;
251
+ }
252
+ this.main = entry;
253
+ this.mainTokenFp = persisted.tokenFingerprint ?? null;
254
+ }
255
+ seedMainBackoffFromStorage(storage) {
256
+ const persistedError = storage?.quota?.mainLastQuotaApiError;
257
+ this.mainLastApiError =
258
+ persistedError && quotaBackoffActive(persistedError, this.now())
259
+ ? persistedError
260
+ : undefined;
233
261
  }
234
262
  /**
235
263
  * Seed fallback cache entries from persisted account.quota data.
236
- * Only seeds accounts that don't already have a cache entry.
237
- * Prevents unnecessary API calls when persisted quota is still fresh.
264
+ * Updates older in-memory entries so a fresh quota write from another plugin
265
+ * process prevents redundant checks and stale sidebar writes.
238
266
  */
239
267
  seedFallbacksFromAccounts(accounts) {
240
268
  const checkInterval = getQuotaCheckIntervalMs(this.storage);
241
269
  for (const account of accounts) {
242
270
  if (account.enabled === false)
243
271
  continue;
244
- if (this.getFallback(account.id, account.access))
245
- continue;
246
272
  if (!account.quota)
247
273
  continue;
248
274
  const checkedAt = Math.max(account.quota.five_hour?.checkedAt ?? 0, account.quota.seven_day?.checkedAt ?? 0);
249
275
  if (checkedAt <= 0)
250
276
  continue;
277
+ const existing = this.getFallback(account.id, account.access);
278
+ if (existing && existing.checkedAt >= checkedAt)
279
+ continue;
251
280
  this.setFallback(account.id, {
252
281
  quota: account.quota,
253
282
  refreshAfter: checkedAt + checkInterval,
@@ -327,11 +356,13 @@ export class QuotaManager {
327
356
  ttlMs: 30_000,
328
357
  });
329
358
  if (!fileLock) {
330
- if (this.main)
331
- return this.main.quota;
359
+ const cached = this.main;
360
+ if (cached && this.now() < cached.refreshAfter)
361
+ return cached.quota;
332
362
  throw new Error('Quota refresh is already in progress');
333
363
  }
334
364
  try {
365
+ const fetchStartedAt = this.now();
335
366
  const quota = await fetchOAuthQuotaSnapshot({
336
367
  accessToken,
337
368
  fetchImpl: this.fetchImpl,
@@ -345,7 +376,7 @@ export class QuotaManager {
345
376
  checkedAt: now,
346
377
  };
347
378
  this.mainLastApiError = undefined;
348
- this.onMainQuotaFetched?.(quota, now, this.mainTokenFp);
379
+ this.onMainQuotaFetched?.(quota, now, this.mainTokenFp, fetchStartedAt);
349
380
  return quota;
350
381
  }
351
382
  catch (error) {
@@ -377,7 +408,7 @@ export class QuotaManager {
377
408
  });
378
409
  if (!fileLock) {
379
410
  const cached = this.getFallback(accountId, accessToken);
380
- if (cached)
411
+ if (cached && this.now() < cached.refreshAfter)
381
412
  return cached.quota;
382
413
  throw new Error('Quota refresh is already in progress');
383
414
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/anthropic-auth-core",
3
- "version": "1.9.2",
3
+ "version": "1.9.3",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",