@cortexkit/anthropic-auth-core 1.19.1 → 1.21.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.
package/dist/auth.d.ts CHANGED
@@ -18,9 +18,11 @@ export type ClaudeOAuthRefreshResult = {
18
18
  refresh: string;
19
19
  expires: number;
20
20
  expiresIn: number;
21
+ authLineageId?: string;
21
22
  };
22
23
  export declare function refreshClaudeOAuthToken(input: {
23
24
  refreshToken: string;
25
+ authLineageId?: string;
24
26
  fetchImpl?: typeof fetch;
25
27
  now?: () => number;
26
28
  maxRetries?: number;
package/dist/auth.js CHANGED
@@ -1,5 +1,5 @@
1
- import { AUTHORIZE_URLS, AXIOS_USER_AGENT, CLIENT_ID, CODE_CALLBACK_URL, OAUTH_SCOPES, REFRESH_SCOPE, TOKEN_URL, } from "./constants.js";
2
- import { generatePKCE } from "./pkce.js";
1
+ import { AUTHORIZE_URLS, AXIOS_USER_AGENT, CLIENT_ID, CODE_CALLBACK_URL, OAUTH_SCOPES, REFRESH_SCOPE, TOKEN_URL, } from './constants.js';
2
+ import { generatePKCE } from './pkce.js';
3
3
  export function parseRetryAfterHeader(value) {
4
4
  if (!value)
5
5
  return undefined;
@@ -83,6 +83,7 @@ export async function refreshClaudeOAuthToken(input) {
83
83
  refresh: json.refresh_token ?? input.refreshToken,
84
84
  expires: refreshedAt + json.expires_in * 1000,
85
85
  expiresIn: json.expires_in,
86
+ authLineageId: input.authLineageId,
86
87
  };
87
88
  }
88
89
  catch (error) {
package/dist/cache1h.d.ts CHANGED
@@ -13,7 +13,7 @@ export type Cache1hCommandAction = {
13
13
  type: 'usage';
14
14
  };
15
15
  export declare function isCache1hEnabled(): boolean;
16
- export declare function getCache1hMode(): "explicit" | "automatic" | "hybrid";
16
+ export declare function getCache1hMode(): "automatic" | "explicit" | "hybrid";
17
17
  export declare function setCache1hEnabled(enabled: boolean): void;
18
18
  export declare function setCache1hMode(mode: Cache1hMode): void;
19
19
  export declare function setCache1hState(input: {
package/dist/cache1h.js CHANGED
@@ -1,4 +1,4 @@
1
- import { DEFAULT_CACHE_1H_MODE } from "./constants.js";
1
+ import { DEFAULT_CACHE_1H_MODE } from './constants.js';
2
2
  export const CACHE_1H_COMMAND_NAME = 'claude-cache';
3
3
  const CACHE_1H_STATUS_TITLE = '## Claude Cache Status';
4
4
  const CACHE_1H_ENABLED_TITLE = '## Claude Cache Enabled';
@@ -7,8 +7,10 @@ export declare const CACHE_KEEP_TICK_MS = 60000;
7
7
  export declare const CACHE_KEEP_EXTENDED_TTL_BETA = "extended-cache-ttl-2025-04-11";
8
8
  export declare const CACHE_KEEP_MAX_TARGETS = 32;
9
9
  export declare const CACHE_KEEP_MAX_BODY_BYTES: number;
10
- export declare const CACHE_KEEP_STALE_TARGET_MS: number;
11
10
  export declare const CACHE_KEEP_PREWARM_TIMEOUT_MS = 30000;
11
+ export declare const CACHE_KEEP_RETRY_BASE_MS = 60000;
12
+ export declare const CACHE_KEEP_RETRY_MAX_MS: number;
13
+ export declare const CACHE_KEEP_RETRY_JITTER_MAX_MS = 30000;
12
14
  export type CacheKeepWindow = {
13
15
  startHour: number;
14
16
  endHour: number;
@@ -70,8 +72,11 @@ export type CacheKeepTarget = {
70
72
  headers: Record<string, string>;
71
73
  bodyText: string;
72
74
  cacheExpiresAt: number;
75
+ nextPrewarmAt: number;
76
+ consecutiveFailures: number;
73
77
  dayKey: string;
74
78
  oauthAccountId?: string;
79
+ isSubagent: boolean;
75
80
  };
76
81
  export type CacheKeepPrewarmResult = {
77
82
  ok: true;
@@ -84,11 +89,13 @@ export type CacheKeepPrewarmResult = {
84
89
  ok: false;
85
90
  reason: string;
86
91
  status?: number;
92
+ transient?: true;
87
93
  };
88
94
  export declare class CacheKeepManager {
89
95
  private readonly options;
90
96
  private readonly targets;
91
97
  private timer;
98
+ private tickPromise;
92
99
  constructor(options: {
93
100
  loadStorage: () => Promise<AccountStorage | null>;
94
101
  fetchImpl?: typeof fetch;
@@ -98,6 +105,14 @@ export declare class CacheKeepManager {
98
105
  clearIntervalImpl?: typeof globalThis.clearInterval;
99
106
  prepareHeaders?: (headers: Headers, target: CacheKeepTarget) => Promise<Headers> | Headers;
100
107
  onTrackedSessionsChanged?: (sessions: readonly CacheKeepTrackedSession[]) => Promise<void> | void;
108
+ prepareBody?: (bodyText: string, target: CacheKeepTarget) => string | Promise<string>;
109
+ onResponse?: (input: {
110
+ target: CacheKeepTarget;
111
+ bodyText: string;
112
+ status: number;
113
+ data: unknown;
114
+ receivedAt: number;
115
+ }) => void | Promise<void>;
101
116
  });
102
117
  start(): void;
103
118
  stop(): void;
@@ -122,12 +137,13 @@ export declare class CacheKeepManager {
122
137
  storage: AccountStorage | null;
123
138
  cacheMode: string;
124
139
  oauthAccountId?: string;
140
+ isSubagent?: boolean;
125
141
  }): {
126
142
  tracked: boolean;
127
143
  reason: string;
128
144
  } | {
129
- tracked: boolean;
130
145
  reason?: undefined;
146
+ tracked: boolean;
131
147
  };
132
148
  prewarmNow(input: {
133
149
  sessionId: string;
@@ -135,8 +151,10 @@ export declare class CacheKeepManager {
135
151
  headers: Headers;
136
152
  bodyText: string;
137
153
  oauthAccountId?: string;
154
+ isSubagent?: boolean;
138
155
  }): Promise<CacheKeepPrewarmResult>;
139
156
  tick(): Promise<void>;
157
+ private runTick;
140
158
  private sendPrewarm;
141
159
  private prewarm;
142
160
  }
package/dist/cachekeep.js CHANGED
@@ -1,6 +1,7 @@
1
- import { signRequestBody } from "./cch.js";
2
- import { orderClaudeCodeBody } from "./claude-code.js";
3
- import { logger } from "./logger.js";
1
+ import { signRequestBody } from './cch.js';
2
+ import { orderClaudeCodeBody } from './claude-code.js';
3
+ import { dumpDirectRequest, dumpResponseArtifact } from './dump.js';
4
+ import { logger } from './logger.js';
4
5
  export const CLAUDE_CACHE_KEEP_COMMAND_NAME = 'claude-cachekeep';
5
6
  export const CACHE_KEEP_TTL_MS = 60 * 60_000;
6
7
  export const CACHE_KEEP_PREWARM_LEAD_MS = 5 * 60_000;
@@ -8,8 +9,10 @@ export const CACHE_KEEP_TICK_MS = 60_000;
8
9
  export const CACHE_KEEP_EXTENDED_TTL_BETA = 'extended-cache-ttl-2025-04-11';
9
10
  export const CACHE_KEEP_MAX_TARGETS = 32;
10
11
  export const CACHE_KEEP_MAX_BODY_BYTES = 16 * 1024 * 1024;
11
- export const CACHE_KEEP_STALE_TARGET_MS = 2 * CACHE_KEEP_TTL_MS;
12
12
  export const CACHE_KEEP_PREWARM_TIMEOUT_MS = 30_000;
13
+ export const CACHE_KEEP_RETRY_BASE_MS = 60_000;
14
+ export const CACHE_KEEP_RETRY_MAX_MS = 15 * 60_000;
15
+ export const CACHE_KEEP_RETRY_JITTER_MAX_MS = 30_000;
13
16
  const STATUS_TITLE = '## Claude Cache Keep Status';
14
17
  const ENABLED_TITLE = '## Claude Cache Keep Enabled';
15
18
  const DISABLED_TITLE = '## Claude Cache Keep Disabled';
@@ -228,10 +231,22 @@ export async function buildCacheKeepPrewarmBody(bodyText) {
228
231
  const signedBodyText = await signRequestBody(JSON.stringify(orderClaudeCodeBody(warm)));
229
232
  return { ok: true, bodyText: signedBodyText };
230
233
  }
234
+ function cacheKeepRetryDelayMs(targetId, failureCount) {
235
+ const exponential = Math.min(CACHE_KEEP_RETRY_MAX_MS, CACHE_KEEP_RETRY_BASE_MS * 2 ** Math.min(Math.max(failureCount - 1, 0), 8));
236
+ let hash = 2_166_136_261;
237
+ for (const character of `${targetId}:${failureCount}`) {
238
+ hash ^= character.charCodeAt(0);
239
+ hash = Math.imul(hash, 16_777_619);
240
+ }
241
+ const jitter = (hash >>> 0) %
242
+ (Math.min(CACHE_KEEP_RETRY_JITTER_MAX_MS, exponential / 4) + 1);
243
+ return exponential + jitter;
244
+ }
231
245
  export class CacheKeepManager {
232
246
  options;
233
247
  targets = new Map();
234
248
  timer = null;
249
+ tickPromise = null;
235
250
  constructor(options) {
236
251
  this.options = options;
237
252
  }
@@ -261,7 +276,7 @@ export class CacheKeepManager {
261
276
  const today = always ? 'always' : localWindowKey(window, new Date(now));
262
277
  const targets = [...this.targets.values()].filter((target) => target.dayKey === today);
263
278
  const nextPrewarmAt = targets.length
264
- ? Math.min(...targets.map((target) => target.cacheExpiresAt - CACHE_KEEP_PREWARM_LEAD_MS))
279
+ ? Math.min(...targets.map((target) => target.nextPrewarmAt))
265
280
  : undefined;
266
281
  return { trackedSessions: targets.length, nextPrewarmAt };
267
282
  }
@@ -270,7 +285,7 @@ export class CacheKeepManager {
270
285
  .map((target) => ({
271
286
  id: target.id,
272
287
  cacheExpiresAt: target.cacheExpiresAt,
273
- nextPrewarmAt: target.cacheExpiresAt - CACHE_KEEP_PREWARM_LEAD_MS,
288
+ nextPrewarmAt: target.nextPrewarmAt,
274
289
  }))
275
290
  .sort((left, right) => left.id.localeCompare(right.id));
276
291
  }
@@ -312,9 +327,8 @@ export class CacheKeepManager {
312
327
  this.targets.delete(id);
313
328
  continue;
314
329
  }
315
- if (now - target.cacheExpiresAt > CACHE_KEEP_STALE_TARGET_MS) {
330
+ if (now >= target.cacheExpiresAt)
316
331
  this.targets.delete(id);
317
- }
318
332
  }
319
333
  while (this.targets.size > CACHE_KEEP_MAX_TARGETS)
320
334
  this.evictOldestTarget();
@@ -356,8 +370,11 @@ export class CacheKeepManager {
356
370
  headers,
357
371
  bodyText: input.bodyText,
358
372
  cacheExpiresAt: now + CACHE_KEEP_TTL_MS,
373
+ nextPrewarmAt: now + CACHE_KEEP_TTL_MS - CACHE_KEEP_PREWARM_LEAD_MS,
374
+ consecutiveFailures: 0,
359
375
  dayKey: today,
360
376
  oauthAccountId: input.oauthAccountId,
377
+ isSubagent: input.isSubagent ?? false,
361
378
  });
362
379
  this.pruneTargets(now, today);
363
380
  this.publishTrackedSessions();
@@ -375,12 +392,26 @@ export class CacheKeepManager {
375
392
  headers,
376
393
  bodyText: input.bodyText,
377
394
  cacheExpiresAt: this.options.now?.() ?? Date.now(),
395
+ nextPrewarmAt: this.options.now?.() ?? Date.now(),
396
+ consecutiveFailures: 0,
378
397
  dayKey: '',
379
398
  oauthAccountId: input.oauthAccountId,
399
+ isSubagent: input.isSubagent ?? false,
380
400
  };
381
401
  return this.sendPrewarm(target);
382
402
  }
383
- async tick() {
403
+ tick() {
404
+ if (this.tickPromise)
405
+ return this.tickPromise;
406
+ let run;
407
+ run = this.runTick().finally(() => {
408
+ if (this.tickPromise === run)
409
+ this.tickPromise = null;
410
+ });
411
+ this.tickPromise = run;
412
+ return run;
413
+ }
414
+ async runTick() {
384
415
  const storage = await this.options.loadStorage();
385
416
  const window = getCacheKeepWindow(storage);
386
417
  const now = this.options.now?.() ?? Date.now();
@@ -404,49 +435,120 @@ export class CacheKeepManager {
404
435
  return;
405
436
  }
406
437
  logger.debug('cachekeep', 'fired', { targets: this.targets.size });
407
- const dueAt = now + CACHE_KEEP_PREWARM_LEAD_MS;
408
438
  for (const target of this.targets.values()) {
409
- if (target.cacheExpiresAt > dueAt)
439
+ if (target.nextPrewarmAt > now)
410
440
  continue;
411
- await this.prewarm(target, now);
441
+ try {
442
+ await this.prewarm(target, now);
443
+ }
444
+ catch (error) {
445
+ logger.warn('cachekeep', 'prewarm failed', {
446
+ session: target.id,
447
+ reason: error instanceof Error ? error.message : String(error),
448
+ });
449
+ target.consecutiveFailures += 1;
450
+ target.nextPrewarmAt =
451
+ now + cacheKeepRetryDelayMs(target.id, target.consecutiveFailures);
452
+ }
412
453
  }
413
454
  this.publishTrackedSessions();
414
455
  }
415
456
  async sendPrewarm(target) {
416
- const prewarm = await buildCacheKeepPrewarmBody(target.bodyText);
457
+ let bodyText = target.bodyText;
458
+ if (this.options.prepareBody) {
459
+ try {
460
+ bodyText = await this.options.prepareBody(bodyText, target);
461
+ }
462
+ catch (error) {
463
+ logger.warn('cachekeep', 'prepare body failed', {
464
+ session: target.id,
465
+ error: error instanceof Error ? error.message : String(error),
466
+ });
467
+ }
468
+ }
469
+ const preparedTarget = { ...target, bodyText };
470
+ const prewarm = await buildCacheKeepPrewarmBody(bodyText);
417
471
  if (!prewarm.ok)
418
472
  return prewarm;
419
473
  const fetchImpl = this.options.fetchImpl ?? fetch;
420
- const prewarmTarget = { ...target, bodyText: prewarm.bodyText };
474
+ const prewarmTarget = { ...preparedTarget, bodyText: prewarm.bodyText };
421
475
  const headers = this.options.prepareHeaders
422
476
  ? await this.options.prepareHeaders(new Headers(target.headers), prewarmTarget)
423
477
  : new Headers(target.headers);
424
478
  headers.delete('content-length');
425
479
  headers.delete('transfer-encoding');
426
- const response = await fetchImpl(target.url, {
427
- method: 'POST',
428
- headers,
429
- body: prewarm.bodyText,
430
- signal: AbortSignal.timeout(this.options.prewarmTimeoutMs ?? CACHE_KEEP_PREWARM_TIMEOUT_MS),
431
- });
480
+ let response;
481
+ try {
482
+ response = await fetchImpl(target.url, {
483
+ method: 'POST',
484
+ headers,
485
+ body: prewarm.bodyText,
486
+ signal: AbortSignal.timeout(this.options.prewarmTimeoutMs ?? CACHE_KEEP_PREWARM_TIMEOUT_MS),
487
+ });
488
+ }
489
+ catch (error) {
490
+ return {
491
+ ok: false,
492
+ reason: error instanceof Error ? error.message : String(error),
493
+ transient: true,
494
+ };
495
+ }
496
+ const receivedAt = this.options.now?.() ?? Date.now();
497
+ const raw = await response.text().catch(() => '');
498
+ let data = null;
499
+ try {
500
+ data = raw ? JSON.parse(raw) : null;
501
+ }
502
+ catch { }
503
+ try {
504
+ await this.options.onResponse?.({
505
+ target,
506
+ bodyText: prewarm.bodyText,
507
+ status: response.status,
508
+ data,
509
+ receivedAt,
510
+ });
511
+ }
512
+ catch { }
513
+ try {
514
+ const dumpHandle = await dumpDirectRequest({
515
+ affinity: target.id,
516
+ route: 'cachekeep',
517
+ status: response.status,
518
+ bodyText: prewarm.bodyText,
519
+ url: target.url,
520
+ method: 'POST',
521
+ headers,
522
+ tag: 'cachekeep',
523
+ });
524
+ await dumpResponseArtifact(dumpHandle, {
525
+ status: response.status,
526
+ message: data,
527
+ });
528
+ }
529
+ catch (error) {
530
+ logger.debug('cachekeep', 'dump failed', {
531
+ session: target.id,
532
+ error: error instanceof Error ? error.message : String(error),
533
+ });
534
+ }
432
535
  if (!response.ok) {
433
536
  return {
434
537
  ok: false,
435
- reason: await response
436
- .text()
437
- .catch(() => '')
438
- .then((body) => body || `HTTP ${response.status}`),
538
+ reason: raw || `HTTP ${response.status}`,
439
539
  status: response.status,
440
540
  };
441
541
  }
442
- const data = (await response.json().catch(() => null));
443
- const usage = data?.usage;
542
+ const objectData = data && typeof data === 'object' && !Array.isArray(data)
543
+ ? data
544
+ : null;
545
+ const usage = objectData?.usage;
444
546
  return { ok: true, ...(usage && { usage }) };
445
547
  }
446
548
  async prewarm(target, now) {
447
549
  const result = await this.sendPrewarm(target);
448
550
  if (!result.ok) {
449
- if (result.status == null) {
551
+ if (result.status == null && !result.transient) {
450
552
  logger.debug('cachekeep', 'prewarm skipped', {
451
553
  session: target.id,
452
554
  reason: result.reason,
@@ -459,11 +561,15 @@ export class CacheKeepManager {
459
561
  status: result.status,
460
562
  reason: result.reason,
461
563
  });
462
- target.cacheExpiresAt = now + CACHE_KEEP_PREWARM_LEAD_MS + 5 * 60_000;
564
+ target.consecutiveFailures += 1;
565
+ target.nextPrewarmAt =
566
+ now + cacheKeepRetryDelayMs(target.id, target.consecutiveFailures);
463
567
  }
464
568
  return;
465
569
  }
466
570
  target.cacheExpiresAt = now + CACHE_KEEP_TTL_MS;
571
+ target.nextPrewarmAt = target.cacheExpiresAt - CACHE_KEEP_PREWARM_LEAD_MS;
572
+ target.consecutiveFailures = 0;
467
573
  logger.debug('cachekeep', 'prewarm succeeded', {
468
574
  session: target.id,
469
575
  ...(result.usage && { usage: result.usage }),
package/dist/cch.d.ts CHANGED
@@ -18,6 +18,7 @@ export declare function extractFirstUserMessageText(messages: Message[]): string
18
18
  * masks to 20 bits, and writes that value into the billing-header placeholder.
19
19
  */
20
20
  export declare function computeCCH(bodyBytes: Uint8Array): Promise<string>;
21
+ export declare function computeXxhash64Hex(value: string): Promise<string>;
21
22
  export declare function resetBillingHeaderCCH(bodyString: string): string;
22
23
  export declare function extractBillingHeaderCCH(bodyString: string): string | null;
23
24
  export declare function signRequestBody(bodyString: string): Promise<string>;
package/dist/cch.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import xxhashInit from 'xxhash-wasm';
3
- import { CLAUDE_CODE_BUILD_HASH, CLAUDE_CODE_VERSION } from "./constants.js";
3
+ import { CLAUDE_CODE_BUILD_HASH, CLAUDE_CODE_VERSION } from './constants.js';
4
4
  const CCH_SEED = 0x6e52736ac806831en;
5
5
  const CCH_PLACEHOLDER = 'cch=00000;';
6
6
  export const CCH_PATTERN = /\bcch=([0-9a-f]{5});/;
@@ -46,6 +46,11 @@ export async function computeCCH(bodyBytes) {
46
46
  const hash = xxhash64Raw?.(bodyBytes, CCH_SEED) ?? 0n;
47
47
  return (hash & 0xfffffn).toString(16).padStart(5, '0');
48
48
  }
49
+ export async function computeXxhash64Hex(value) {
50
+ await ensureXxhash();
51
+ const hash = xxhash64Raw?.(new TextEncoder().encode(value), 0n) ?? 0n;
52
+ return hash.toString(16).padStart(16, '0').slice(0, 16);
53
+ }
49
54
  export function resetBillingHeaderCCH(bodyString) {
50
55
  return bodyString.replace(BILLING_HEADER_CCH_PATTERN, `$1${CCH_PLACEHOLDER}`);
51
56
  }
@@ -1,13 +1,16 @@
1
1
  export type ClaudeCodeIdentity = {
2
2
  deviceId: string;
3
+ accountIdentity?: string;
3
4
  accountUuid?: string;
4
5
  sessionId: string;
5
6
  };
7
+ export declare function setBounded<K, V>(map: Map<K, V>, key: K, value: V, limit?: number): void;
6
8
  export declare function getClaudeCodeIdentity(seed: string): ClaudeCodeIdentity;
7
- export declare function resolveClaudeCodeIdentity(accessToken: string, model?: string): Promise<ClaudeCodeIdentity>;
9
+ export declare function resetClaudeCodeIdentityCachesForTest(): void;
10
+ export declare function resolveClaudeCodeIdentity(accessToken: string, model: string | undefined, accountIdentity: string | undefined): Promise<ClaudeCodeIdentity>;
8
11
  export declare function buildClaudeCodeMetadataUserId(identity: ClaudeCodeIdentity): string | null;
9
12
  export declare function applyClaudeCodeMetadata(body: Record<string, unknown>, identity: ClaudeCodeIdentity): boolean;
10
- export declare const CLAUDE_CODE_FULL_AGENT_BETAS: readonly ["oauth-2025-04-20", "interleaved-thinking-2025-05-14", "thinking-token-count-2026-05-13", "context-management-2025-06-27", "prompt-caching-scope-2026-01-05", "claude-code-20250219", "advisor-tool-2026-03-01", "advanced-tool-use-2025-11-20", "extended-cache-ttl-2025-04-11", "cache-diagnosis-2026-04-07"];
13
+ export declare const CLAUDE_CODE_FULL_AGENT_BETAS: readonly ['oauth-2025-04-20', 'interleaved-thinking-2025-05-14', 'thinking-token-count-2026-05-13', 'context-management-2025-06-27', 'prompt-caching-scope-2026-01-05', 'claude-code-20250219', 'advisor-tool-2026-03-01', 'advanced-tool-use-2025-11-20', 'extended-cache-ttl-2025-04-11', 'cache-diagnosis-2026-04-07'];
11
14
  export declare function selectClaudeCodeBetas(body?: Record<string, unknown> | null, extraBetas?: string[]): string;
12
15
  export declare function applyClaudeCodeHeaders(headers: Headers, accessToken: string, options?: {
13
16
  body?: Record<string, unknown> | null;
@@ -1,15 +1,25 @@
1
1
  import { randomBytes, randomUUID } from 'node:crypto';
2
- import { CLAUDE_CODE_BUILD_HASH, CLAUDE_CODE_ENTRYPOINT, CLAUDE_CODE_STAINLESS_PACKAGE_VERSION, CLAUDE_CODE_STAINLESS_RUNTIME_VERSION, CLAUDE_CODE_VERSION, FAST_MODE_BETA, USER_AGENT, } from "./constants.js";
2
+ import { CLAUDE_CODE_BUILD_HASH, CLAUDE_CODE_ENTRYPOINT, CLAUDE_CODE_STAINLESS_PACKAGE_VERSION, CLAUDE_CODE_STAINLESS_RUNTIME_VERSION, CLAUDE_CODE_VERSION, FAST_MODE_BETA, USER_AGENT, } from './constants.js';
3
3
  const IDENTITY_CACHE_LIMIT = 1_000;
4
4
  const identityCache = new Map();
5
- function setBounded(map, key, value) {
6
- if (!map.has(key) && map.size >= IDENTITY_CACHE_LIMIT) {
5
+ export function setBounded(map, key, value, limit = IDENTITY_CACHE_LIMIT) {
6
+ if (!map.has(key) && map.size >= limit) {
7
7
  const oldest = map.keys().next().value;
8
8
  if (oldest !== undefined)
9
9
  map.delete(oldest);
10
10
  }
11
11
  map.set(key, value);
12
12
  }
13
+ function clearCachedAccountUuid(key, identity) {
14
+ if (!identity.accountUuid)
15
+ return identity;
16
+ if (identityCache.get(key) !== identity) {
17
+ return { ...identity, accountUuid: undefined };
18
+ }
19
+ const cleared = { ...identity, accountUuid: undefined };
20
+ setBounded(identityCache, key, cleared);
21
+ return cleared;
22
+ }
13
23
  export function getClaudeCodeIdentity(seed) {
14
24
  const cacheKey = seed || 'anonymous';
15
25
  const cached = identityCache.get(cacheKey);
@@ -26,6 +36,46 @@ const BOOTSTRAP_IDENTITY_CACHE_TTL_MS = 24 * 60 * 60_000;
26
36
  const BOOTSTRAP_IDENTITY_NEGATIVE_TTL_MS = 5 * 60_000;
27
37
  const bootstrapFetches = new Map();
28
38
  const bootstrapResults = new Map();
39
+ export function resetClaudeCodeIdentityCachesForTest() {
40
+ identityCache.clear();
41
+ bootstrapFetches.clear();
42
+ bootstrapResults.clear();
43
+ }
44
+ function compatibilityCacheKey(accessToken) {
45
+ return `compat:${accessToken || 'anonymous'}`;
46
+ }
47
+ function explicitCacheKey(accountIdentity) {
48
+ return `identity:${accountIdentity}`;
49
+ }
50
+ function accountCacheKey(accountUuid, accountIdentity) {
51
+ return `account:${accountUuid}:${accountIdentity}`;
52
+ }
53
+ function adoptCachedIdentity(cached, accountIdentity, accountUuid) {
54
+ if (!cached)
55
+ return undefined;
56
+ if (cached.accountIdentity !== undefined &&
57
+ cached.accountIdentity !== accountIdentity) {
58
+ return undefined;
59
+ }
60
+ if (accountUuid !== undefined &&
61
+ cached.accountUuid !== undefined &&
62
+ cached.accountUuid !== accountUuid) {
63
+ return undefined;
64
+ }
65
+ if (cached.accountIdentity === accountIdentity &&
66
+ (accountUuid === undefined || cached.accountUuid === accountUuid)) {
67
+ return cached;
68
+ }
69
+ return {
70
+ ...cached,
71
+ accountIdentity,
72
+ ...(accountUuid !== undefined && { accountUuid }),
73
+ };
74
+ }
75
+ function cacheAccountIdentity(identity, accountIdentity, accountUuid) {
76
+ setBounded(identityCache, explicitCacheKey(accountIdentity), identity);
77
+ setBounded(identityCache, accountCacheKey(accountUuid, accountIdentity), identity);
78
+ }
29
79
  async function fetchClaudeCodeAccountUuid(accessToken, model) {
30
80
  if (!accessToken.startsWith('sk-ant-oat'))
31
81
  return null;
@@ -60,47 +110,100 @@ async function fetchClaudeCodeAccountUuid(accessToken, model) {
60
110
  clearTimeout(timeout);
61
111
  }
62
112
  }
63
- export async function resolveClaudeCodeIdentity(accessToken, model) {
64
- const identity = getClaudeCodeIdentity(accessToken);
113
+ export async function resolveClaudeCodeIdentity(accessToken, model, accountIdentity) {
114
+ const stableAccountIdentity = accountIdentity?.trim() || undefined;
115
+ const cacheKey = stableAccountIdentity
116
+ ? explicitCacheKey(stableAccountIdentity)
117
+ : compatibilityCacheKey(accessToken);
118
+ let identity;
119
+ if (stableAccountIdentity) {
120
+ const cachedIdentity = adoptCachedIdentity(identityCache.get(cacheKey) ?? identityCache.get(accessToken), stableAccountIdentity);
121
+ identity = cachedIdentity ?? getClaudeCodeIdentity(cacheKey);
122
+ identity = adoptCachedIdentity(identity, stableAccountIdentity) ?? identity;
123
+ setBounded(identityCache, cacheKey, identity);
124
+ }
125
+ else {
126
+ identity =
127
+ identityCache.get(cacheKey) ??
128
+ identityCache.get(accessToken) ??
129
+ getClaudeCodeIdentity(cacheKey);
130
+ setBounded(identityCache, cacheKey, identity);
131
+ setBounded(identityCache, accessToken, identity);
132
+ }
65
133
  if (!accessToken.startsWith('sk-ant-oat'))
66
134
  return identity;
67
135
  const now = Date.now();
68
- const cachedResult = bootstrapResults.get(accessToken);
136
+ // A slot-stable identity survives account replacement; bootstrap is the
137
+ // account lookup that must be repeated for each credential presented to it.
138
+ const bootstrapKey = `${cacheKey}:${accessToken}`;
139
+ const cachedResult = bootstrapResults.get(bootstrapKey);
69
140
  if (cachedResult && cachedResult.expiresAt > now) {
70
- if (!cachedResult.accountUuid)
141
+ if (!cachedResult.accountUuid) {
142
+ identity = clearCachedAccountUuid(cacheKey, identity);
143
+ return identity;
144
+ }
145
+ if (!stableAccountIdentity) {
146
+ if (identity.accountUuid === cachedResult.accountUuid)
147
+ return identity;
148
+ identity = { ...identity, accountUuid: cachedResult.accountUuid };
149
+ setBounded(identityCache, bootstrapKey, identity);
71
150
  return identity;
72
- const accountCacheKey = `account:${cachedResult.accountUuid}`;
73
- const accountIdentity = identityCache.get(accountCacheKey);
74
- if (accountIdentity) {
75
- setBounded(identityCache, accessToken, accountIdentity);
76
- return accountIdentity;
77
151
  }
152
+ const cachedAccountIdentity = adoptCachedIdentity(identityCache.get(accountCacheKey(cachedResult.accountUuid, stableAccountIdentity)) ?? identityCache.get(`account:${cachedResult.accountUuid}`), stableAccountIdentity, cachedResult.accountUuid);
153
+ if (cachedAccountIdentity) {
154
+ identity = cachedAccountIdentity;
155
+ cacheAccountIdentity(identity, stableAccountIdentity, cachedResult.accountUuid);
156
+ return identity;
157
+ }
158
+ if (identity.accountUuid === cachedResult.accountUuid)
159
+ return identity;
160
+ identity = {
161
+ ...identity,
162
+ accountIdentity: stableAccountIdentity,
163
+ accountUuid: cachedResult.accountUuid,
164
+ };
165
+ cacheAccountIdentity(identity, stableAccountIdentity, cachedResult.accountUuid);
166
+ return identity;
78
167
  }
79
- let fetchPromise = bootstrapFetches.get(accessToken);
168
+ let fetchPromise = bootstrapFetches.get(bootstrapKey);
80
169
  if (!fetchPromise) {
81
170
  fetchPromise = fetchClaudeCodeAccountUuid(accessToken, model);
82
- bootstrapFetches.set(accessToken, fetchPromise);
171
+ setBounded(bootstrapFetches, bootstrapKey, fetchPromise);
83
172
  }
84
173
  const accountUuid = await fetchPromise.finally(() => {
85
- bootstrapFetches.delete(accessToken);
174
+ if (bootstrapFetches.get(bootstrapKey) === fetchPromise) {
175
+ bootstrapFetches.delete(bootstrapKey);
176
+ }
86
177
  });
87
- setBounded(bootstrapResults, accessToken, {
178
+ setBounded(bootstrapResults, bootstrapKey, {
88
179
  accountUuid,
89
180
  expiresAt: now +
90
181
  (accountUuid
91
182
  ? BOOTSTRAP_IDENTITY_CACHE_TTL_MS
92
183
  : BOOTSTRAP_IDENTITY_NEGATIVE_TTL_MS),
93
184
  });
94
- if (!accountUuid)
185
+ if (!accountUuid) {
186
+ identity = clearCachedAccountUuid(cacheKey, identity);
187
+ return identity;
188
+ }
189
+ if (!stableAccountIdentity) {
190
+ identity = { ...identity, accountUuid };
191
+ setBounded(identityCache, bootstrapKey, identity);
95
192
  return identity;
96
- const accountCacheKey = `account:${accountUuid}`;
97
- const accountIdentity = identityCache.get(accountCacheKey);
98
- if (accountIdentity) {
99
- setBounded(identityCache, accessToken, accountIdentity);
100
- return accountIdentity;
101
193
  }
102
- identity.accountUuid = accountUuid;
103
- setBounded(identityCache, accountCacheKey, identity);
194
+ const cachedAccountIdentity = adoptCachedIdentity(identityCache.get(accountCacheKey(accountUuid, stableAccountIdentity)) ??
195
+ identityCache.get(`account:${accountUuid}`), stableAccountIdentity, accountUuid);
196
+ if (cachedAccountIdentity) {
197
+ identity = cachedAccountIdentity;
198
+ cacheAccountIdentity(identity, stableAccountIdentity, accountUuid);
199
+ return identity;
200
+ }
201
+ identity = {
202
+ ...identity,
203
+ accountIdentity: stableAccountIdentity,
204
+ accountUuid,
205
+ };
206
+ cacheAccountIdentity(identity, stableAccountIdentity, accountUuid);
104
207
  return identity;
105
208
  }
106
209
  function isRecord(value) {
@@ -1,4 +1,4 @@
1
- import { formatOAuthAccountTier } from "../oauth-profile.js";
1
+ import { formatOAuthAccountTier } from '../oauth-profile.js';
2
2
  export const CLAUDE_ACCOUNT_COMMAND_NAME = 'claude-account';
3
3
  export function parseAccountCommandAction(argumentsText) {
4
4
  const parts = argumentsText.trim().split(/\s+/).filter(Boolean);
@@ -1,7 +1,7 @@
1
1
  export declare const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
2
2
  export declare const AUTHORIZE_URLS: {
3
- readonly console: "https://platform.claude.com/oauth/authorize";
4
- readonly max: "https://claude.com/cai/oauth/authorize";
3
+ readonly console: 'https://platform.claude.com/oauth/authorize';
4
+ readonly max: 'https://claude.com/cai/oauth/authorize';
5
5
  };
6
6
  export declare const CODE_CALLBACK_URL = "https://platform.claude.com/oauth/code/callback";
7
7
  export declare const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
@@ -24,7 +24,7 @@ export declare const CLAUDE_CODE_ENTRYPOINT = "cli";
24
24
  export declare const CLAUDE_CODE_STAINLESS_PACKAGE_VERSION = "0.94.0";
25
25
  export declare const CLAUDE_CODE_STAINLESS_RUNTIME_VERSION = "v24.3.0";
26
26
  export declare const USER_AGENT = "claude-cli/2.1.177 (external, cli)";
27
- export declare const CACHE_1H_MODES: readonly ["explicit", "automatic", "hybrid"];
27
+ export declare const CACHE_1H_MODES: readonly ['explicit', 'automatic', 'hybrid'];
28
28
  export type Cache1hMode = (typeof CACHE_1H_MODES)[number];
29
29
  export declare const DEFAULT_CACHE_1H_MODE: Cache1hMode;
30
30
  /**