@cortexkit/anthropic-auth-core 1.19.0 → 1.20.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/dump.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { createHash, randomBytes } from 'node:crypto';
2
- import { lstat, mkdir, readdir, rename, unlink, writeFile, } from 'node:fs/promises';
2
+ import { lstat, mkdir, readdir, readFile, rename, unlink, writeFile, } from 'node:fs/promises';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { join, resolve } from 'node:path';
5
- import { extractBillingHeaderCCH } from "./cch.js";
6
- import { isSecretKey, logger, relayLog } from "./logger.js";
5
+ import { extractBillingHeaderCCH } from './cch.js';
6
+ import { isSecretKey, logger, relayLog } from './logger.js';
7
7
  export const CLAUDE_DUMP_COMMAND_NAME = 'claude-dump';
8
8
  const DUMP_STATUS_TITLE = '## Claude Dump Status';
9
9
  const DUMP_ENABLED_TITLE = '## Claude Dump Enabled';
@@ -17,12 +17,14 @@ const DEFAULT_DUMP_MAX_BYTES = 512 * 1024 * 1024;
17
17
  const DUMP_SWEEP_INTERVAL_MS = 5 * 60 * 1000;
18
18
  const DUMP_SWEEP_NEWNESS_FLOOR_MS = 60 * 1000;
19
19
  const DUMP_PARTIAL_STALE_MS = 10 * 60 * 1000;
20
- const DUMP_ARTIFACT_SUFFIX_PATTERN = /\.(body|meta|relay|request)\.json$/;
20
+ const DUMP_ARTIFACT_SUFFIX_PATTERN = /\.(body|meta|relay|request|response)\.json$/;
21
21
  // Earlier builds emitted five-digit counters; current counters grow without truncation.
22
22
  const DUMP_ARTIFACT_ID_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z-\d{5,}-(.+)$/;
23
23
  const DUMP_SEGMENT_PATTERN = '[a-zA-Z0-9._-]';
24
24
  const DIRECT_DUMP_PATTERN = new RegExp(`^${DUMP_SEGMENT_PATTERN}{1,80}-direct(?:-${DUMP_SEGMENT_PATTERN}{1,80})?$`);
25
25
  const RELAY_DUMP_PATTERN = new RegExp(`^${DUMP_SEGMENT_PATTERN}{1,80}-(?:http|websocket)-p[12]-(?:full_sync|patch)$`);
26
+ const DIRECT_DUMP_SUFFIX_PATTERN = new RegExp(`^-direct(?:-${DUMP_SEGMENT_PATTERN}{1,80})?$`);
27
+ const RELAY_DUMP_SUFFIX_PATTERN = /^-(?:http|websocket)-p[12]-(?:full_sync|patch)$/;
26
28
  let dumpEnabled = false;
27
29
  let nextDumpId = 0;
28
30
  let lastDumpSweepAt = 0;
@@ -136,6 +138,9 @@ export async function sweepDumpDirectory(options) {
136
138
  size: stats.size,
137
139
  mtimeMs: stats.mtimeMs,
138
140
  partial,
141
+ ...(!partial && {
142
+ artifactGroup: entry.name.replace(DUMP_ARTIFACT_SUFFIX_PATTERN, ''),
143
+ }),
139
144
  });
140
145
  }
141
146
  catch {
@@ -143,18 +148,9 @@ export async function sweepDumpDirectory(options) {
143
148
  }
144
149
  }));
145
150
  let totalBytes = files.reduce((total, file) => total + file.size, 0);
146
- files.sort((left, right) => left.mtimeMs - right.mtimeMs || left.path.localeCompare(right.path));
147
151
  let removed = 0;
148
152
  let freedBytes = 0;
149
- for (const file of files) {
150
- if (protectedPaths.has(resolve(file.path)))
151
- continue;
152
- const ageMs = now - file.mtimeMs;
153
- const stalePartial = file.partial && ageMs >= partialStaleMs;
154
- if (!stalePartial && totalBytes <= maxBytes)
155
- continue;
156
- if (file.partial ? !stalePartial : ageMs < minAgeMs)
157
- continue;
153
+ const removeFile = async (file) => {
158
154
  try {
159
155
  await unlink(file.path);
160
156
  totalBytes -= file.size;
@@ -164,6 +160,43 @@ export async function sweepDumpDirectory(options) {
164
160
  catch {
165
161
  // Dump cleanup is best-effort and must not affect request handling.
166
162
  }
163
+ };
164
+ // Partials are not usable dump records. Reclaim stale ones independently,
165
+ // including while the completed-artifact set is already below the cap.
166
+ const partials = files
167
+ .filter((file) => file.partial)
168
+ .sort((left, right) => left.mtimeMs - right.mtimeMs || left.path.localeCompare(right.path));
169
+ for (const file of partials) {
170
+ if (protectedPaths.has(resolve(file.path)))
171
+ continue;
172
+ if (now - file.mtimeMs < partialStaleMs)
173
+ continue;
174
+ await removeFile(file);
175
+ }
176
+ const artifactGroups = new Map();
177
+ for (const file of files) {
178
+ if (file.partial || !file.artifactGroup)
179
+ continue;
180
+ const group = artifactGroups.get(file.artifactGroup) ?? {
181
+ files: [],
182
+ newestMtimeMs: 0,
183
+ };
184
+ group.files.push(file);
185
+ group.newestMtimeMs = Math.max(group.newestMtimeMs, file.mtimeMs);
186
+ artifactGroups.set(file.artifactGroup, group);
187
+ }
188
+ const completedGroups = [...artifactGroups.entries()].sort(([leftName, left], [rightName, right]) => left.newestMtimeMs - right.newestMtimeMs ||
189
+ leftName.localeCompare(rightName));
190
+ for (const [, group] of completedGroups) {
191
+ if (totalBytes <= maxBytes)
192
+ break;
193
+ if (now - group.newestMtimeMs < minAgeMs)
194
+ continue;
195
+ if (group.files.some((file) => protectedPaths.has(resolve(file.path)))) {
196
+ continue;
197
+ }
198
+ for (const file of group.files)
199
+ await removeFile(file);
167
200
  }
168
201
  if (removed > 0) {
169
202
  logger.debug('dump', 'removed old dump files', { removed, freedBytes });
@@ -233,14 +266,16 @@ export function executeDumpCommand(input) {
233
266
  function shortAffinity(affinity) {
234
267
  return affinity.length <= 16 ? affinity : `${affinity.slice(0, 12)}…`;
235
268
  }
236
- function dumpFileSessionSegment(affinity) {
269
+ function dumpFileSessionSegment(affinity, maxLength = 80) {
237
270
  const normalized = affinity
238
271
  .trim()
239
272
  .replace(/[^a-zA-Z0-9._-]+/g, '-')
240
273
  .replace(/^-+|-+$/g, '');
241
274
  if (!normalized)
242
275
  return 'session-unknown';
243
- return normalized.length <= 80 ? normalized : normalized.slice(0, 80);
276
+ return normalized.length <= maxLength
277
+ ? normalized
278
+ : normalized.slice(0, maxLength);
244
279
  }
245
280
  function dumpRequestSegment(input) {
246
281
  if (input.transport !== 'direct') {
@@ -249,12 +284,85 @@ function dumpRequestSegment(input) {
249
284
  const route = input.route ? `-${dumpFileSessionSegment(input.route)}` : '';
250
285
  return `direct${route}`;
251
286
  }
287
+ function dumpTagSegment(tag) {
288
+ if (tag === 'cachekeep')
289
+ return '-prewarm-cachekeep';
290
+ if (tag === 'start')
291
+ return '-start';
292
+ return '';
293
+ }
252
294
  function directDumpPreviousKey(input) {
253
295
  const affinity = input.affinity?.trim();
254
296
  if (affinity)
255
297
  return `session:${affinity}`;
256
298
  return `request:${input.route ?? 'direct'}:${input.url ?? ''}`;
257
299
  }
300
+ function dumpBodyTagForAffinity(name, affinity) {
301
+ if (!name.endsWith('.body.json'))
302
+ return undefined;
303
+ const stem = name.replace(DUMP_ARTIFACT_SUFFIX_PATTERN, '');
304
+ const requestPath = DUMP_ARTIFACT_ID_PATTERN.exec(stem)?.[1];
305
+ if (!requestPath ||
306
+ (!DIRECT_DUMP_PATTERN.test(requestPath) &&
307
+ !RELAY_DUMP_PATTERN.test(requestPath))) {
308
+ return undefined;
309
+ }
310
+ const cachekeepTag = dumpTagSegment('cachekeep');
311
+ const candidates = [
312
+ { prefix: dumpFileSessionSegment(affinity), tag: null },
313
+ {
314
+ prefix: `${dumpFileSessionSegment(affinity, 80 - cachekeepTag.length)}${cachekeepTag}`,
315
+ tag: 'cachekeep',
316
+ },
317
+ ];
318
+ for (const candidate of candidates) {
319
+ if (!requestPath.startsWith(`${candidate.prefix}-`))
320
+ continue;
321
+ const suffix = requestPath.slice(candidate.prefix.length);
322
+ if (DIRECT_DUMP_SUFFIX_PATTERN.test(suffix) ||
323
+ RELAY_DUMP_SUFFIX_PATTERN.test(suffix)) {
324
+ return candidate.tag;
325
+ }
326
+ }
327
+ return undefined;
328
+ }
329
+ async function loadLatestDumpBody(input) {
330
+ const affinity = input.affinity?.trim();
331
+ if (!affinity)
332
+ return undefined;
333
+ const dumpDir = getDumpDirectory();
334
+ let names;
335
+ try {
336
+ names = await readdir(dumpDir);
337
+ }
338
+ catch {
339
+ return undefined;
340
+ }
341
+ const candidates = names
342
+ .map((name) => ({ name, tag: dumpBodyTagForAffinity(name, affinity) }))
343
+ .filter((candidate) => candidate.tag !== undefined)
344
+ .sort((a, b) => b.name.localeCompare(a.name));
345
+ for (const candidate of candidates) {
346
+ try {
347
+ const path = join(dumpDir, candidate.name);
348
+ const metadataPath = path.replace(/\.body\.json$/, '.meta.json');
349
+ if (!(await lstat(path)).isFile())
350
+ continue;
351
+ const metadata = JSON.parse(await readFile(metadataPath, 'utf8'));
352
+ if (metadata.session !== shortAffinity(affinity) ||
353
+ (candidate.tag === null
354
+ ? metadata.tag != null
355
+ : metadata.tag !== candidate.tag)) {
356
+ continue;
357
+ }
358
+ return await readFile(path, 'utf8');
359
+ }
360
+ catch {
361
+ // A concurrent sweep can remove the newest artifact; try its predecessor.
362
+ }
363
+ }
364
+ return undefined;
365
+ }
258
366
  function rememberDirectDumpBody(key, bodyText) {
259
367
  if (!directDumpPreviousBodies.has(key)) {
260
368
  while (directDumpPreviousBodies.size >= DIRECT_DUMP_PREVIOUS_BODY_LIMIT) {
@@ -376,15 +484,17 @@ function redactForDump(value) {
376
484
  }
377
485
  async function dumpRequest(input) {
378
486
  if (!dumpEnabled)
379
- return;
487
+ return null;
380
488
  nextDumpId += 1;
381
489
  const affinity = input.affinity?.trim() || 'session-unknown';
382
- const id = `${new Date().toISOString().replace(/[:.]/g, '-')}-${String(nextDumpId).padStart(6, '0')}-${dumpFileSessionSegment(affinity)}-${dumpRequestSegment(input)}`;
490
+ const tagSegment = dumpTagSegment(input.tag);
491
+ const id = `${new Date().toISOString().replace(/[:.]/g, '-')}-${String(nextDumpId).padStart(6, '0')}-${dumpFileSessionSegment(affinity, 80 - tagSegment.length)}${tagSegment}-${dumpRequestSegment(input)}`;
383
492
  const dumpDir = getDumpDirectory();
384
493
  const prefix = join(dumpDir, id);
385
494
  const files = {
386
495
  body: `${prefix}.body.json`,
387
496
  metadata: `${prefix}.meta.json`,
497
+ response: `${prefix}.response.json`,
388
498
  };
389
499
  if (input.payload !== undefined)
390
500
  files.relay = `${prefix}.relay.json`;
@@ -392,6 +502,7 @@ async function dumpRequest(input) {
392
502
  files.request = `${prefix}.request.json`;
393
503
  try {
394
504
  await mkdir(dumpDir, { recursive: true });
505
+ const previousBodyText = input.previousBodyText ?? (await loadLatestDumpBody(input));
395
506
  const metadata = {
396
507
  id,
397
508
  createdAt: new Date().toISOString(),
@@ -402,10 +513,11 @@ async function dumpRequest(input) {
402
513
  route: input.route,
403
514
  status: input.status,
404
515
  error: input.error,
516
+ tag: input.tag,
405
517
  bodyBytes: input.bodyText.length,
406
518
  relayBytes: input.relayBytes,
407
519
  bodyHash: hashText(input.bodyText),
408
- diff: diffSummary(input.previousBodyText, input.bodyText),
520
+ diff: diffSummary(previousBodyText, input.bodyText),
409
521
  body: bodyStructureSummary(input.bodyText),
410
522
  files,
411
523
  };
@@ -428,14 +540,19 @@ async function dumpRequest(input) {
428
540
  }
429
541
  catch (error) {
430
542
  relayLog(`dump failed: ${error instanceof Error ? error.message : String(error)}`);
543
+ return null;
431
544
  }
545
+ return {
546
+ responsePath: files.response,
547
+ ...(input.tag ? { tag: input.tag } : {}),
548
+ };
432
549
  }
433
550
  export async function dumpDirectRequest(input) {
434
551
  if (!dumpEnabled)
435
- return;
552
+ return null;
436
553
  const previousKey = directDumpPreviousKey(input);
437
554
  const previousBodyText = directDumpPreviousBodies.get(previousKey);
438
- await dumpRequest({
555
+ const handle = await dumpRequest({
439
556
  affinity: input.affinity,
440
557
  transport: 'direct',
441
558
  route: input.route,
@@ -448,11 +565,13 @@ export async function dumpDirectRequest(input) {
448
565
  method: input.method,
449
566
  headers: input.headers,
450
567
  },
568
+ tag: input.tag,
451
569
  });
452
570
  rememberDirectDumpBody(previousKey, input.bodyText);
571
+ return handle;
453
572
  }
454
573
  export async function dumpRelayRequest(input) {
455
- await dumpRequest({
574
+ return dumpRequest({
456
575
  affinity: input.affinity,
457
576
  transport: input.transport,
458
577
  protocol: input.protocol,
@@ -462,5 +581,30 @@ export async function dumpRelayRequest(input) {
462
581
  previousBodyText: input.previousBodyText,
463
582
  payload: input.payload,
464
583
  relayBytes: input.relayBytes,
584
+ tag: input.tag,
465
585
  });
466
586
  }
587
+ export async function dumpResponseArtifact(handle, input) {
588
+ if (!handle)
589
+ return;
590
+ const message = input.message != null &&
591
+ typeof input.message === 'object' &&
592
+ !Array.isArray(input.message)
593
+ ? input.message
594
+ : {};
595
+ const artifact = { status: input.status };
596
+ if (typeof message.id === 'string' && message.id.length > 0)
597
+ artifact.message_id = message.id;
598
+ if (typeof message.model === 'string' && message.model.length > 0)
599
+ artifact.model = message.model;
600
+ if (Object.hasOwn(message, 'usage'))
601
+ artifact.usage = message.usage;
602
+ if (Object.hasOwn(message, 'diagnostics'))
603
+ artifact.diagnostics = message.diagnostics;
604
+ try {
605
+ await writeDumpFile(handle.responsePath, `${JSON.stringify(artifact, null, 2)}\n`);
606
+ }
607
+ catch (error) {
608
+ relayLog(`dump response failed: ${error instanceof Error ? error.message : String(error)}`);
609
+ }
610
+ }
package/dist/index.d.ts CHANGED
@@ -15,10 +15,12 @@ export * from './logging.ts';
15
15
  export * from './models.ts';
16
16
  export * from './oauth-profile.ts';
17
17
  export * from './pkce.ts';
18
+ export * from './prime.ts';
18
19
  export * from './provider.ts';
19
20
  export * from './quota-headers.ts';
20
21
  export * from './quota-manager.ts';
21
22
  export * from './quotas.ts';
22
23
  export * from './relay.ts';
23
24
  export * from './routing.ts';
25
+ export * from './start.ts';
24
26
  export * from './sticky-routing.ts';
package/dist/index.js CHANGED
@@ -1,24 +1,26 @@
1
- export * from "./accounts.js";
2
- export * from "./auth.js";
3
- export * from "./cache1h.js";
4
- export * from "./cachekeep.js";
5
- export * from "./cachekeep-registry.js";
6
- export * from "./cch.js";
7
- export * from "./claude-code.js";
8
- export * from "./commands/account.js";
9
- export * from "./constants.js";
10
- export * from "./dump.js";
11
- export * from "./fast.js";
12
- export * from "./killswitch.js";
13
- export * from "./logger.js";
14
- export * from "./logging.js";
15
- export * from "./models.js";
16
- export * from "./oauth-profile.js";
17
- export * from "./pkce.js";
18
- export * from "./provider.js";
19
- export * from "./quota-headers.js";
20
- export * from "./quota-manager.js";
21
- export * from "./quotas.js";
22
- export * from "./relay.js";
23
- export * from "./routing.js";
24
- export * from "./sticky-routing.js";
1
+ export * from './accounts.js';
2
+ export * from './auth.js';
3
+ export * from './cache1h.js';
4
+ export * from './cachekeep.js';
5
+ export * from './cachekeep-registry.js';
6
+ export * from './cch.js';
7
+ export * from './claude-code.js';
8
+ export * from './commands/account.js';
9
+ export * from './constants.js';
10
+ export * from './dump.js';
11
+ export * from './fast.js';
12
+ export * from './killswitch.js';
13
+ export * from './logger.js';
14
+ export * from './logging.js';
15
+ export * from './models.js';
16
+ export * from './oauth-profile.js';
17
+ export * from './pkce.js';
18
+ export * from './prime.js';
19
+ export * from './provider.js';
20
+ export * from './quota-headers.js';
21
+ export * from './quota-manager.js';
22
+ export * from './quotas.js';
23
+ export * from './relay.js';
24
+ export * from './routing.js';
25
+ export * from './start.js';
26
+ export * from './sticky-routing.js';
@@ -1,4 +1,4 @@
1
- import { DEFAULT_KILLSWITCH_THRESHOLDS as DEFAULT_THRESHOLDS, normalizeKillswitchThresholds, } from "./accounts.js";
1
+ import { DEFAULT_KILLSWITCH_THRESHOLDS as DEFAULT_THRESHOLDS, normalizeKillswitchThresholds, } from './accounts.js';
2
2
  export const KILLSWITCH_COMMAND_NAME = 'claude-killswitch';
3
3
  export function parseKillswitchCommandAction(argumentsText) {
4
4
  const parts = argumentsText.trim().split(/\s+/).filter(Boolean);
package/dist/models.d.ts CHANGED
@@ -1,10 +1,26 @@
1
1
  export declare const CLAUDE_FABLE_5_MODEL_ID = "claude-fable-5";
2
2
  export declare const CLAUDE_MYTHOS_5_MODEL_ID = "claude-mythos-5";
3
+ /**
4
+ * Haiku 4.5 model identifier used by `/claude-prime` to start each OAuth
5
+ * account's five-hour quota window with a minimal request. Usage is measured
6
+ * from response accounting. Kept distinct from the Fable/Mythos pricing block
7
+ * above so per-million-token cost estimation does not conflate families.
8
+ */
9
+ export declare const CLAUDE_HAIKU_4_5_MODEL_ID = "claude-haiku-4-5";
10
+ /**
11
+ * Per-million-token USD pricing for Haiku 4.5. Used to project the cumulative
12
+ * cost of prime requests from persisted usage counters — never persisted on
13
+ * disk; derive at display time so future pricing revisions land in one place.
14
+ */
15
+ export declare const CLAUDE_HAIKU_4_5_PRICING: {
16
+ readonly input: 1;
17
+ readonly output: 5;
18
+ };
3
19
  export declare const CLAUDE_FABLE_MYTHOS_5_MODEL_IDS: readonly ["claude-fable-5", "claude-mythos-5"];
4
20
  export type ClaudeFableMythos5ModelId = (typeof CLAUDE_FABLE_MYTHOS_5_MODEL_IDS)[number];
5
21
  export declare const CLAUDE_FABLE_MYTHOS_5_SUMMARIZED_THINKING: {
6
- readonly type: "adaptive";
7
- readonly display: "summarized";
22
+ readonly type: 'adaptive';
23
+ readonly display: 'summarized';
8
24
  };
9
25
  export declare const CLAUDE_FABLE_MYTHOS_5_PRICING: {
10
26
  readonly input: 10;
@@ -30,8 +46,8 @@ export declare const CLAUDE_SONNET_5_MODEL_ID = "claude-sonnet-5";
30
46
  * shape because the API contract is identical.
31
47
  */
32
48
  export declare const CLAUDE_SONNET_5_ADAPTIVE_THINKING: {
33
- readonly type: "adaptive";
34
- readonly display: "summarized";
49
+ readonly type: 'adaptive';
50
+ readonly display: 'summarized';
35
51
  };
36
52
  export declare function isClaudeSonnet5Model(model: unknown): boolean;
37
53
  export declare const CLAUDE_OPUS_5_MODEL_ID = "claude-opus-5";
@@ -44,8 +60,8 @@ export declare const CLAUDE_OPUS_5_MODEL_ID = "claude-opus-5";
44
60
  * only changes this re-export, not every call site.
45
61
  */
46
62
  export declare const CLAUDE_OPUS_5_ADAPTIVE_THINKING: {
47
- readonly type: "adaptive";
48
- readonly display: "summarized";
63
+ readonly type: 'adaptive';
64
+ readonly display: 'summarized';
49
65
  };
50
66
  export declare function isClaudeOpus5Model(model: unknown): boolean;
51
67
  export declare function isOpenAIReasoningSignature(value: unknown): boolean;
package/dist/models.js CHANGED
@@ -1,5 +1,21 @@
1
1
  export const CLAUDE_FABLE_5_MODEL_ID = 'claude-fable-5';
2
2
  export const CLAUDE_MYTHOS_5_MODEL_ID = 'claude-mythos-5';
3
+ /**
4
+ * Haiku 4.5 model identifier used by `/claude-prime` to start each OAuth
5
+ * account's five-hour quota window with a minimal request. Usage is measured
6
+ * from response accounting. Kept distinct from the Fable/Mythos pricing block
7
+ * above so per-million-token cost estimation does not conflate families.
8
+ */
9
+ export const CLAUDE_HAIKU_4_5_MODEL_ID = 'claude-haiku-4-5';
10
+ /**
11
+ * Per-million-token USD pricing for Haiku 4.5. Used to project the cumulative
12
+ * cost of prime requests from persisted usage counters — never persisted on
13
+ * disk; derive at display time so future pricing revisions land in one place.
14
+ */
15
+ export const CLAUDE_HAIKU_4_5_PRICING = {
16
+ input: 1,
17
+ output: 5,
18
+ };
3
19
  export const CLAUDE_FABLE_MYTHOS_5_MODEL_IDS = [
4
20
  CLAUDE_FABLE_5_MODEL_ID,
5
21
  CLAUDE_MYTHOS_5_MODEL_ID,
@@ -1,4 +1,4 @@
1
- import { tokenFingerprint } from "./quota-manager.js";
1
+ import { tokenFingerprint } from './quota-manager.js';
2
2
  const PROFILE_URL = 'https://api.anthropic.com/api/oauth/profile';
3
3
  export const PROFILE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
4
4
  export async function fetchOAuthAccountProfile(input) {