@cortexkit/anthropic-auth-core 1.13.0 → 1.15.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.
@@ -68,6 +68,7 @@ export type AccountScopedQuotaWindow = AccountQuotaWindow & {
68
68
  };
69
69
  export type OAuthQuotaSnapshot = Partial<Record<QuotaWindowName, AccountQuotaWindow>> & {
70
70
  scoped?: AccountScopedQuotaWindow[];
71
+ checkedAt?: number;
71
72
  };
72
73
  export type RoutingMode = 'main-first' | 'fallback-first';
73
74
  export type KillswitchThresholds = Partial<Record<QuotaWindowName | '5h' | '1w' | 'scoped', number>>;
package/dist/accounts.js CHANGED
@@ -178,6 +178,14 @@ function normalizeQuota(value) {
178
178
  if (normalized)
179
179
  quota[key] = normalized;
180
180
  }
181
+ // Persist a top-level snapshot checkedAt through normalize so the
182
+ // mergeAccountRuntimeState freshness comparison stays meaningful when the
183
+ // snapshot has no per-window checkedAt (e.g. {scoped:[]}). Pre-feature
184
+ // inputs without this key are unaffected — only on-disk snapshots that
185
+ // already carry it reach this branch.
186
+ if (typeof value.checkedAt === 'number' && Number.isFinite(value.checkedAt)) {
187
+ quota.checkedAt = value.checkedAt;
188
+ }
181
189
  if (Array.isArray(value.scoped)) {
182
190
  const scoped = value.scoped
183
191
  .map((entry) => {
@@ -206,8 +214,11 @@ function normalizeQuota(value) {
206
214
  };
207
215
  })
208
216
  .filter((entry) => entry != null);
209
- if (scoped.length)
210
- quota.scoped = scoped;
217
+ // Preserve empty `[]` so a downstream reader can distinguish "scoped
218
+ // owned by anthropic-auth, none visible" from "no scoped data on this
219
+ // snapshot". Pre-feature inputs without a `scoped` key are not affected
220
+ // — only inputs that already carried an array reach this line.
221
+ quota.scoped = scoped;
211
222
  }
212
223
  return Object.keys(quota).length ? quota : undefined;
213
224
  }
@@ -384,7 +395,7 @@ function accountRuntimeState(account) {
384
395
  });
385
396
  }
386
397
  function quotaSnapshotCheckedAt(quota) {
387
- return Math.max(quota?.five_hour?.checkedAt ?? 0, quota?.seven_day?.checkedAt ?? 0, ...(quota?.scoped?.map((window) => window.checkedAt) ?? []));
398
+ return Math.max(quota?.five_hour?.checkedAt ?? 0, quota?.seven_day?.checkedAt ?? 0, ...(quota?.scoped?.map((window) => window.checkedAt) ?? []), quota?.checkedAt ?? 0);
388
399
  }
389
400
  function mergeAccountRuntimeState(existing, incoming) {
390
401
  if (!isRecord(existing))
@@ -1404,7 +1415,7 @@ function slugForQuotaIdentity(value) {
1404
1415
  }
1405
1416
  function mapScopedWeeklyLimits(limits, checkedAt) {
1406
1417
  if (!Array.isArray(limits))
1407
- return undefined;
1418
+ return [];
1408
1419
  const seen = new Set();
1409
1420
  const scoped = [];
1410
1421
  for (const limit of limits) {
@@ -1437,7 +1448,7 @@ function mapScopedWeeklyLimits(limits, checkedAt) {
1437
1448
  checkedAt,
1438
1449
  });
1439
1450
  }
1440
- return scoped.length ? scoped : undefined;
1451
+ return scoped;
1441
1452
  }
1442
1453
  function mapUsageWindow(window, checkedAt) {
1443
1454
  if (typeof window?.utilization !== 'number')
@@ -1478,6 +1489,7 @@ export async function fetchOAuthQuotaSnapshot(input) {
1478
1489
  five_hour: mapUsageWindow(usage.five_hour, checkedAt),
1479
1490
  seven_day: mapUsageWindow(usage.seven_day, checkedAt),
1480
1491
  scoped: mapScopedWeeklyLimits(usage.limits, checkedAt),
1492
+ checkedAt,
1481
1493
  };
1482
1494
  }
1483
1495
  function updateStoredAccount(storage, account) {
@@ -7,6 +7,7 @@ export declare const CACHE_KEEP_EXTENDED_TTL_BETA = "extended-cache-ttl-2025-04-
7
7
  export declare const CACHE_KEEP_MAX_TARGETS = 32;
8
8
  export declare const CACHE_KEEP_MAX_BODY_BYTES: number;
9
9
  export declare const CACHE_KEEP_STALE_TARGET_MS: number;
10
+ export declare const CACHE_KEEP_PREWARM_TIMEOUT_MS = 30000;
10
11
  export type CacheKeepWindow = {
11
12
  startHour: number;
12
13
  endHour: number;
@@ -63,6 +64,18 @@ export type CacheKeepTarget = {
63
64
  dayKey: string;
64
65
  oauthAccountId?: string;
65
66
  };
67
+ export type CacheKeepPrewarmResult = {
68
+ ok: true;
69
+ usage?: {
70
+ input_tokens?: number;
71
+ cache_creation_input_tokens?: number;
72
+ cache_read_input_tokens?: number;
73
+ };
74
+ } | {
75
+ ok: false;
76
+ reason: string;
77
+ status?: number;
78
+ };
66
79
  export declare class CacheKeepManager {
67
80
  private readonly options;
68
81
  private readonly targets;
@@ -71,6 +84,7 @@ export declare class CacheKeepManager {
71
84
  loadStorage: () => Promise<AccountStorage | null>;
72
85
  fetchImpl?: typeof fetch;
73
86
  now?: () => number;
87
+ prewarmTimeoutMs?: number;
74
88
  prepareHeaders?: (headers: Headers, target: CacheKeepTarget) => Promise<Headers> | Headers;
75
89
  });
76
90
  start(): void;
@@ -98,6 +112,14 @@ export declare class CacheKeepManager {
98
112
  tracked: boolean;
99
113
  reason?: undefined;
100
114
  };
115
+ prewarmNow(input: {
116
+ sessionId: string;
117
+ url: string;
118
+ headers: Headers;
119
+ bodyText: string;
120
+ oauthAccountId?: string;
121
+ }): Promise<CacheKeepPrewarmResult>;
101
122
  tick(): Promise<void>;
123
+ private sendPrewarm;
102
124
  private prewarm;
103
125
  }
package/dist/cachekeep.js CHANGED
@@ -9,6 +9,7 @@ export const CACHE_KEEP_EXTENDED_TTL_BETA = 'extended-cache-ttl-2025-04-11';
9
9
  export const CACHE_KEEP_MAX_TARGETS = 32;
10
10
  export const CACHE_KEEP_MAX_BODY_BYTES = 16 * 1024 * 1024;
11
11
  export const CACHE_KEEP_STALE_TARGET_MS = 2 * CACHE_KEEP_TTL_MS;
12
+ export const CACHE_KEEP_PREWARM_TIMEOUT_MS = 30_000;
12
13
  const STATUS_TITLE = '## Claude Cache Keep Status';
13
14
  const ENABLED_TITLE = '## Claude Cache Keep Enabled';
14
15
  const DISABLED_TITLE = '## Claude Cache Keep Disabled';
@@ -293,6 +294,22 @@ export class CacheKeepManager {
293
294
  this.start();
294
295
  return { tracked: true };
295
296
  }
297
+ async prewarmNow(input) {
298
+ const headers = {};
299
+ input.headers.forEach((value, key) => {
300
+ headers[key] = value;
301
+ });
302
+ const target = {
303
+ id: input.sessionId,
304
+ url: input.url,
305
+ headers,
306
+ bodyText: input.bodyText,
307
+ cacheExpiresAt: this.options.now?.() ?? Date.now(),
308
+ dayKey: '',
309
+ oauthAccountId: input.oauthAccountId,
310
+ };
311
+ return this.sendPrewarm(target);
312
+ }
296
313
  async tick() {
297
314
  const storage = await this.options.loadStorage();
298
315
  const window = getCacheKeepWindow(storage);
@@ -315,16 +332,10 @@ export class CacheKeepManager {
315
332
  await this.prewarm(target, now);
316
333
  }
317
334
  }
318
- async prewarm(target, now) {
335
+ async sendPrewarm(target) {
319
336
  const prewarm = await buildCacheKeepPrewarmBody(target.bodyText);
320
- if (!prewarm.ok) {
321
- logger.debug('cachekeep', 'prewarm skipped', {
322
- session: target.id,
323
- reason: prewarm.reason,
324
- });
325
- this.targets.delete(target.id);
326
- return;
327
- }
337
+ if (!prewarm.ok)
338
+ return prewarm;
328
339
  const fetchImpl = this.options.fetchImpl ?? fetch;
329
340
  const prewarmTarget = { ...target, bodyText: prewarm.bodyText };
330
341
  const headers = this.options.prepareHeaders
@@ -336,27 +347,46 @@ export class CacheKeepManager {
336
347
  method: 'POST',
337
348
  headers,
338
349
  body: prewarm.bodyText,
350
+ signal: AbortSignal.timeout(this.options.prewarmTimeoutMs ?? CACHE_KEEP_PREWARM_TIMEOUT_MS),
339
351
  });
340
352
  if (!response.ok) {
341
- logger.warn('cachekeep', 'prewarm failed', {
342
- session: target.id,
353
+ return {
354
+ ok: false,
355
+ reason: await response
356
+ .text()
357
+ .catch(() => '')
358
+ .then((body) => body || `HTTP ${response.status}`),
343
359
  status: response.status,
344
- body: await response.text().catch(() => ''),
345
- });
346
- target.cacheExpiresAt = now + CACHE_KEEP_PREWARM_LEAD_MS + 5 * 60_000;
347
- return;
360
+ };
348
361
  }
349
362
  const data = (await response.json().catch(() => null));
350
363
  const usage = data?.usage;
351
- target.cacheExpiresAt = now + CACHE_KEEP_TTL_MS;
352
- if (usage) {
353
- logger.debug('cachekeep', 'prewarm succeeded', {
354
- session: target.id,
355
- usage,
356
- });
357
- }
358
- else {
359
- logger.debug('cachekeep', 'prewarm succeeded', { session: target.id });
364
+ return { ok: true, ...(usage && { usage }) };
365
+ }
366
+ async prewarm(target, now) {
367
+ const result = await this.sendPrewarm(target);
368
+ if (!result.ok) {
369
+ if (result.status == null) {
370
+ logger.debug('cachekeep', 'prewarm skipped', {
371
+ session: target.id,
372
+ reason: result.reason,
373
+ });
374
+ this.targets.delete(target.id);
375
+ }
376
+ else {
377
+ logger.warn('cachekeep', 'prewarm failed', {
378
+ session: target.id,
379
+ status: result.status,
380
+ reason: result.reason,
381
+ });
382
+ target.cacheExpiresAt = now + CACHE_KEEP_PREWARM_LEAD_MS + 5 * 60_000;
383
+ }
384
+ return;
360
385
  }
386
+ target.cacheExpiresAt = now + CACHE_KEEP_TTL_MS;
387
+ logger.debug('cachekeep', 'prewarm succeeded', {
388
+ session: target.id,
389
+ ...(result.usage && { usage: result.usage }),
390
+ });
361
391
  }
362
392
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/anthropic-auth-core",
3
- "version": "1.13.0",
3
+ "version": "1.15.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",