@cortexkit/anthropic-auth-core 1.21.0 → 1.23.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/README.md +1 -1
- package/dist/accounts.d.ts +100 -3
- package/dist/accounts.js +1099 -169
- package/dist/auth.js +6 -10
- package/dist/cachekeep.d.ts +18 -1
- package/dist/cachekeep.js +108 -82
- package/dist/cch.d.ts +23 -9
- package/dist/cch.js +104 -21
- package/dist/claude-code.d.ts +8 -3
- package/dist/claude-code.js +24 -7
- package/dist/claustrum-enrollment-registry.d.ts +17 -0
- package/dist/claustrum-enrollment-registry.js +194 -0
- package/dist/claustrum-enrollment.d.ts +70 -0
- package/dist/claustrum-enrollment.js +485 -0
- package/dist/claustrum-scoped-roster.d.ts +23 -0
- package/dist/claustrum-scoped-roster.js +194 -0
- package/dist/claustrum-scoped-runtime.d.ts +28 -0
- package/dist/claustrum-scoped-runtime.js +230 -0
- package/dist/claustrum-scoped.d.ts +39 -0
- package/dist/claustrum-scoped.js +193 -0
- package/dist/claustrum.d.ts +252 -0
- package/dist/claustrum.js +1471 -0
- package/dist/commands/account.d.ts +43 -11
- package/dist/commands/account.js +147 -4
- package/dist/constants.d.ts +5 -5
- package/dist/constants.js +5 -5
- package/dist/custom-headers.d.ts +3 -0
- package/dist/custom-headers.js +102 -0
- package/dist/dump.js +2 -1
- package/dist/fast.js +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/json.d.ts +1 -0
- package/dist/json.js +10 -0
- package/dist/logger.js +3 -1
- package/dist/mid-conversation-output-config.d.ts +13 -0
- package/dist/mid-conversation-output-config.js +89 -0
- package/dist/model-remap.d.ts +27 -0
- package/dist/model-remap.js +72 -0
- package/dist/models.d.ts +46 -1
- package/dist/models.js +87 -8
- package/dist/network-errors.d.ts +1 -0
- package/dist/network-errors.js +27 -0
- package/dist/oauth-profile.d.ts +2 -1
- package/dist/oauth-profile.js +6 -0
- package/dist/prime.d.ts +11 -3
- package/dist/prime.js +25 -7
- package/dist/quota-header-feed.d.ts +32 -7
- package/dist/quota-header-feed.js +93 -10
- package/dist/quota-headers.js +59 -6
- package/dist/quota-manager.d.ts +20 -2
- package/dist/quota-manager.js +101 -24
- package/dist/relay.d.ts +8 -0
- package/dist/relay.js +72 -12
- package/dist/sticky-routing.d.ts +2 -0
- package/dist/sticky-routing.js +19 -1
- package/dist/thinking-binding.d.ts +7 -0
- package/dist/thinking-binding.js +54 -0
- package/package.json +4 -1
package/dist/auth.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { assertNotCustodyTombstone } from './claustrum.js';
|
|
1
2
|
import { AUTHORIZE_URLS, AXIOS_USER_AGENT, CLIENT_ID, CODE_CALLBACK_URL, OAUTH_SCOPES, REFRESH_SCOPE, TOKEN_URL, } from './constants.js';
|
|
3
|
+
import { isTransientNetworkError } from './network-errors.js';
|
|
2
4
|
import { generatePKCE } from './pkce.js';
|
|
3
5
|
export function parseRetryAfterHeader(value) {
|
|
4
6
|
if (!value)
|
|
@@ -33,17 +35,11 @@ export class ClaudeOAuthRefreshError extends Error {
|
|
|
33
35
|
this.retryAfter = parseRetryAfterHeader(retryAfterHeader ?? undefined);
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
|
-
function isTransientNetworkError(error) {
|
|
37
|
-
if (!(error instanceof Error))
|
|
38
|
-
return false;
|
|
39
|
-
const code = error.code;
|
|
40
|
-
return (error.message.includes('fetch failed') ||
|
|
41
|
-
code === 'ECONNRESET' ||
|
|
42
|
-
code === 'ECONNREFUSED' ||
|
|
43
|
-
code === 'ETIMEDOUT' ||
|
|
44
|
-
code === 'UND_ERR_CONNECT_TIMEOUT');
|
|
45
|
-
}
|
|
46
38
|
export async function refreshClaudeOAuthToken(input) {
|
|
39
|
+
assertNotCustodyTombstone(input.refreshToken, 'anthropic');
|
|
40
|
+
if (typeof input.refreshToken !== 'string' || !input.refreshToken.trim()) {
|
|
41
|
+
throw new Error('Local OAuth refresh credential is unavailable');
|
|
42
|
+
}
|
|
47
43
|
const fetchImpl = input.fetchImpl ?? fetch;
|
|
48
44
|
const maxRetries = input.maxRetries ?? 2;
|
|
49
45
|
const baseDelayMs = input.baseDelayMs ?? 500;
|
package/dist/cachekeep.d.ts
CHANGED
|
@@ -76,8 +76,15 @@ export type CacheKeepTarget = {
|
|
|
76
76
|
consecutiveFailures: number;
|
|
77
77
|
dayKey: string;
|
|
78
78
|
oauthAccountId?: string;
|
|
79
|
+
oauthAccountIdentity?: string;
|
|
80
|
+
accountStoragePath?: string;
|
|
79
81
|
isSubagent: boolean;
|
|
80
82
|
};
|
|
83
|
+
export type CacheKeepPrewarmAttempt = {
|
|
84
|
+
id: number;
|
|
85
|
+
/** One deadline covers credential authorization and the HTTP attempt. */
|
|
86
|
+
signal?: AbortSignal;
|
|
87
|
+
};
|
|
81
88
|
export type CacheKeepPrewarmResult = {
|
|
82
89
|
ok: true;
|
|
83
90
|
usage?: {
|
|
@@ -96,6 +103,7 @@ export declare class CacheKeepManager {
|
|
|
96
103
|
private readonly targets;
|
|
97
104
|
private timer;
|
|
98
105
|
private tickPromise;
|
|
106
|
+
private nextPrewarmAttemptId;
|
|
99
107
|
constructor(options: {
|
|
100
108
|
loadStorage: () => Promise<AccountStorage | null>;
|
|
101
109
|
fetchImpl?: typeof fetch;
|
|
@@ -103,7 +111,7 @@ export declare class CacheKeepManager {
|
|
|
103
111
|
prewarmTimeoutMs?: number;
|
|
104
112
|
setIntervalImpl?: typeof globalThis.setInterval;
|
|
105
113
|
clearIntervalImpl?: typeof globalThis.clearInterval;
|
|
106
|
-
prepareHeaders?: (headers: Headers, target: CacheKeepTarget) => Promise<Headers> | Headers;
|
|
114
|
+
prepareHeaders?: (headers: Headers, target: CacheKeepTarget, attempt: CacheKeepPrewarmAttempt) => Promise<Headers | undefined> | Headers | undefined;
|
|
107
115
|
onTrackedSessionsChanged?: (sessions: readonly CacheKeepTrackedSession[]) => Promise<void> | void;
|
|
108
116
|
prepareBody?: (bodyText: string, target: CacheKeepTarget) => string | Promise<string>;
|
|
109
117
|
onResponse?: (input: {
|
|
@@ -112,6 +120,11 @@ export declare class CacheKeepManager {
|
|
|
112
120
|
status: number;
|
|
113
121
|
data: unknown;
|
|
114
122
|
receivedAt: number;
|
|
123
|
+
attempt: CacheKeepPrewarmAttempt;
|
|
124
|
+
}) => void | Promise<void>;
|
|
125
|
+
onComplete?: (input: {
|
|
126
|
+
target: CacheKeepTarget;
|
|
127
|
+
attempt: CacheKeepPrewarmAttempt;
|
|
115
128
|
}) => void | Promise<void>;
|
|
116
129
|
});
|
|
117
130
|
start(): void;
|
|
@@ -137,6 +150,8 @@ export declare class CacheKeepManager {
|
|
|
137
150
|
storage: AccountStorage | null;
|
|
138
151
|
cacheMode: string;
|
|
139
152
|
oauthAccountId?: string;
|
|
153
|
+
oauthAccountIdentity?: string;
|
|
154
|
+
accountStoragePath?: string;
|
|
140
155
|
isSubagent?: boolean;
|
|
141
156
|
}): {
|
|
142
157
|
tracked: boolean;
|
|
@@ -151,6 +166,8 @@ export declare class CacheKeepManager {
|
|
|
151
166
|
headers: Headers;
|
|
152
167
|
bodyText: string;
|
|
153
168
|
oauthAccountId?: string;
|
|
169
|
+
oauthAccountIdentity?: string;
|
|
170
|
+
accountStoragePath?: string;
|
|
154
171
|
isSubagent?: boolean;
|
|
155
172
|
}): Promise<CacheKeepPrewarmResult>;
|
|
156
173
|
tick(): Promise<void>;
|
package/dist/cachekeep.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { signRequestBody } from './cch.js';
|
|
1
|
+
import { signRequestBody, stripBillingLineageFromBody } from './cch.js';
|
|
2
2
|
import { orderClaudeCodeBody } from './claude-code.js';
|
|
3
3
|
import { dumpDirectRequest, dumpResponseArtifact } from './dump.js';
|
|
4
4
|
import { logger } from './logger.js';
|
|
@@ -216,6 +216,7 @@ export async function buildCacheKeepPrewarmBody(bodyText) {
|
|
|
216
216
|
return { ok: false, reason: 'body has no explicit cache breakpoints' };
|
|
217
217
|
}
|
|
218
218
|
const warm = structuredClone(body);
|
|
219
|
+
stripBillingLineageFromBody(warm);
|
|
219
220
|
warm.max_tokens = 0;
|
|
220
221
|
delete warm.stream;
|
|
221
222
|
const thinking = warm.thinking;
|
|
@@ -247,6 +248,7 @@ export class CacheKeepManager {
|
|
|
247
248
|
targets = new Map();
|
|
248
249
|
timer = null;
|
|
249
250
|
tickPromise = null;
|
|
251
|
+
nextPrewarmAttemptId = 0;
|
|
250
252
|
constructor(options) {
|
|
251
253
|
this.options = options;
|
|
252
254
|
}
|
|
@@ -374,6 +376,8 @@ export class CacheKeepManager {
|
|
|
374
376
|
consecutiveFailures: 0,
|
|
375
377
|
dayKey: today,
|
|
376
378
|
oauthAccountId: input.oauthAccountId,
|
|
379
|
+
oauthAccountIdentity: input.oauthAccountIdentity,
|
|
380
|
+
accountStoragePath: input.accountStoragePath,
|
|
377
381
|
isSubagent: input.isSubagent ?? false,
|
|
378
382
|
});
|
|
379
383
|
this.pruneTargets(now, today);
|
|
@@ -396,6 +400,8 @@ export class CacheKeepManager {
|
|
|
396
400
|
consecutiveFailures: 0,
|
|
397
401
|
dayKey: '',
|
|
398
402
|
oauthAccountId: input.oauthAccountId,
|
|
403
|
+
oauthAccountIdentity: input.oauthAccountIdentity,
|
|
404
|
+
accountStoragePath: input.accountStoragePath,
|
|
399
405
|
isSubagent: input.isSubagent ?? false,
|
|
400
406
|
};
|
|
401
407
|
return this.sendPrewarm(target);
|
|
@@ -454,96 +460,116 @@ export class CacheKeepManager {
|
|
|
454
460
|
this.publishTrackedSessions();
|
|
455
461
|
}
|
|
456
462
|
async sendPrewarm(target) {
|
|
457
|
-
|
|
458
|
-
|
|
463
|
+
const attempt = {
|
|
464
|
+
id: ++this.nextPrewarmAttemptId,
|
|
465
|
+
signal: AbortSignal.timeout(this.options.prewarmTimeoutMs ?? CACHE_KEEP_PREWARM_TIMEOUT_MS),
|
|
466
|
+
};
|
|
467
|
+
try {
|
|
468
|
+
let bodyText = target.bodyText;
|
|
469
|
+
if (this.options.prepareBody) {
|
|
470
|
+
try {
|
|
471
|
+
bodyText = await this.options.prepareBody(bodyText, target);
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
logger.warn('cachekeep', 'prepare body failed', {
|
|
475
|
+
session: target.id,
|
|
476
|
+
error: error instanceof Error ? error.message : String(error),
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
const preparedTarget = { ...target, bodyText };
|
|
481
|
+
const prewarm = await buildCacheKeepPrewarmBody(bodyText);
|
|
482
|
+
if (!prewarm.ok)
|
|
483
|
+
return prewarm;
|
|
484
|
+
const fetchImpl = this.options.fetchImpl ?? fetch;
|
|
485
|
+
const prewarmTarget = { ...preparedTarget, bodyText: prewarm.bodyText };
|
|
486
|
+
const headers = this.options.prepareHeaders
|
|
487
|
+
? await this.options.prepareHeaders(new Headers(target.headers), prewarmTarget, attempt)
|
|
488
|
+
: new Headers(target.headers);
|
|
489
|
+
if (!headers) {
|
|
490
|
+
return {
|
|
491
|
+
ok: false,
|
|
492
|
+
reason: 'OAuth cache prewarm credential is unavailable',
|
|
493
|
+
transient: true,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
headers.delete('content-length');
|
|
497
|
+
headers.delete('transfer-encoding');
|
|
498
|
+
let response;
|
|
459
499
|
try {
|
|
460
|
-
|
|
500
|
+
response = await fetchImpl(target.url, {
|
|
501
|
+
method: 'POST',
|
|
502
|
+
headers,
|
|
503
|
+
body: prewarm.bodyText,
|
|
504
|
+
signal: attempt.signal,
|
|
505
|
+
});
|
|
461
506
|
}
|
|
462
507
|
catch (error) {
|
|
463
|
-
|
|
508
|
+
return {
|
|
509
|
+
ok: false,
|
|
510
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
511
|
+
transient: true,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
const receivedAt = this.options.now?.() ?? Date.now();
|
|
515
|
+
const raw = await response.text().catch(() => '');
|
|
516
|
+
let data = null;
|
|
517
|
+
try {
|
|
518
|
+
data = raw ? JSON.parse(raw) : null;
|
|
519
|
+
}
|
|
520
|
+
catch { }
|
|
521
|
+
try {
|
|
522
|
+
await this.options.onResponse?.({
|
|
523
|
+
target,
|
|
524
|
+
bodyText: prewarm.bodyText,
|
|
525
|
+
status: response.status,
|
|
526
|
+
data,
|
|
527
|
+
receivedAt,
|
|
528
|
+
attempt,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
catch { }
|
|
532
|
+
try {
|
|
533
|
+
const dumpHandle = await dumpDirectRequest({
|
|
534
|
+
affinity: target.id,
|
|
535
|
+
route: 'cachekeep',
|
|
536
|
+
status: response.status,
|
|
537
|
+
bodyText: prewarm.bodyText,
|
|
538
|
+
url: target.url,
|
|
539
|
+
method: 'POST',
|
|
540
|
+
headers,
|
|
541
|
+
tag: 'cachekeep',
|
|
542
|
+
});
|
|
543
|
+
await dumpResponseArtifact(dumpHandle, {
|
|
544
|
+
status: response.status,
|
|
545
|
+
message: data,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
catch (error) {
|
|
549
|
+
logger.debug('cachekeep', 'dump failed', {
|
|
464
550
|
session: target.id,
|
|
465
551
|
error: error instanceof Error ? error.message : String(error),
|
|
466
552
|
});
|
|
467
553
|
}
|
|
554
|
+
if (!response.ok) {
|
|
555
|
+
return {
|
|
556
|
+
ok: false,
|
|
557
|
+
reason: raw || `HTTP ${response.status}`,
|
|
558
|
+
status: response.status,
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
const objectData = data && typeof data === 'object' && !Array.isArray(data)
|
|
562
|
+
? data
|
|
563
|
+
: null;
|
|
564
|
+
const usage = objectData?.usage;
|
|
565
|
+
return { ok: true, ...(usage && { usage }) };
|
|
468
566
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
const prewarmTarget = { ...preparedTarget, bodyText: prewarm.bodyText };
|
|
475
|
-
const headers = this.options.prepareHeaders
|
|
476
|
-
? await this.options.prepareHeaders(new Headers(target.headers), prewarmTarget)
|
|
477
|
-
: new Headers(target.headers);
|
|
478
|
-
headers.delete('content-length');
|
|
479
|
-
headers.delete('transfer-encoding');
|
|
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
|
-
}
|
|
535
|
-
if (!response.ok) {
|
|
536
|
-
return {
|
|
537
|
-
ok: false,
|
|
538
|
-
reason: raw || `HTTP ${response.status}`,
|
|
539
|
-
status: response.status,
|
|
540
|
-
};
|
|
567
|
+
finally {
|
|
568
|
+
try {
|
|
569
|
+
await this.options.onComplete?.({ target, attempt });
|
|
570
|
+
}
|
|
571
|
+
catch { }
|
|
541
572
|
}
|
|
542
|
-
const objectData = data && typeof data === 'object' && !Array.isArray(data)
|
|
543
|
-
? data
|
|
544
|
-
: null;
|
|
545
|
-
const usage = objectData?.usage;
|
|
546
|
-
return { ok: true, ...(usage && { usage }) };
|
|
547
573
|
}
|
|
548
574
|
async prewarm(target, now) {
|
|
549
575
|
const result = await this.sendPrewarm(target);
|
package/dist/cch.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export interface BillingLineageFields {
|
|
2
|
+
previousRequestId?: string;
|
|
3
|
+
promptId?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function isValidAnthropicRequestId(value: string): boolean;
|
|
6
|
+
export declare function isValidBillingPromptId(value: string): boolean;
|
|
1
7
|
type Message = {
|
|
2
8
|
role?: string;
|
|
3
9
|
content?: string | Array<{
|
|
@@ -7,10 +13,18 @@ type Message = {
|
|
|
7
13
|
};
|
|
8
14
|
export declare const CCH_PATTERN: RegExp;
|
|
9
15
|
/**
|
|
10
|
-
* Extract
|
|
11
|
-
*
|
|
16
|
+
* Extract the visible prompt from the first user message. Claude Code prepends
|
|
17
|
+
* reminder blocks and derives its version suffix from the final text block.
|
|
12
18
|
*/
|
|
13
19
|
export declare function extractFirstUserMessageText(messages: Message[]): string;
|
|
20
|
+
export declare class ClaudeCodeFirstUserTextTracker {
|
|
21
|
+
#private;
|
|
22
|
+
readonly limit: number;
|
|
23
|
+
constructor(limit?: number);
|
|
24
|
+
resolve(sessionId: string, messages: Message[], pin?: boolean): string;
|
|
25
|
+
has(sessionId: string): boolean;
|
|
26
|
+
clear(): void;
|
|
27
|
+
}
|
|
14
28
|
/**
|
|
15
29
|
* Compute Claude Code's cch token over the final serialized request body.
|
|
16
30
|
*
|
|
@@ -23,16 +37,16 @@ export declare function resetBillingHeaderCCH(bodyString: string): string;
|
|
|
23
37
|
export declare function extractBillingHeaderCCH(bodyString: string): string | null;
|
|
24
38
|
export declare function signRequestBody(bodyString: string): Promise<string>;
|
|
25
39
|
/**
|
|
26
|
-
* Compute
|
|
27
|
-
*
|
|
28
|
-
* The captured Claude Code version uses its observed build hash. Custom/future
|
|
29
|
-
* versions get a deterministic version-only suffix so date changes never rotate
|
|
30
|
-
* the billing header and bust prompt-cache prefixes.
|
|
40
|
+
* Compute Claude Code's 3-character suffix for cc_version.
|
|
31
41
|
*/
|
|
32
|
-
export declare function
|
|
42
|
+
export declare function computeCcVersionSuffix(firstUserText: string, version?: string): string;
|
|
33
43
|
/**
|
|
34
44
|
* Build the billing header with a cch placeholder.
|
|
35
45
|
* signRequestBody() must run after final request serialization to replace it.
|
|
36
46
|
*/
|
|
37
|
-
export declare function buildBillingHeaderValue(
|
|
47
|
+
export declare function buildBillingHeaderValue(messages: Message[], version: string | undefined, entrypoint: string, pinnedFirstUserText?: string, lineage?: BillingLineageFields): string;
|
|
48
|
+
/** Remove request-scoped Claude Code lineage from a billing header string. */
|
|
49
|
+
export declare function stripBillingLineageFields(value: string): string;
|
|
50
|
+
/** Remove request-scoped Claude Code lineage before reusing a request body. */
|
|
51
|
+
export declare function stripBillingLineageFromBody(body: unknown): number;
|
|
38
52
|
export {};
|
package/dist/cch.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import xxhashInit from 'xxhash-wasm';
|
|
3
|
-
import {
|
|
4
|
-
const
|
|
3
|
+
import { CCH_POSITIONS, CCH_SALT, CLAUDE_CODE_VERSION } from './constants.js';
|
|
4
|
+
const ANTHROPIC_REQUEST_ID_PATTERN = /^req_[A-Za-z0-9_-]{8,128}$/;
|
|
5
|
+
const PROMPT_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
6
|
+
const BILLING_LINEAGE_FIELD_PATTERN = / cc_(?:prev_req|prompt_id)=[^;\r\n]*;/g;
|
|
7
|
+
export function isValidAnthropicRequestId(value) {
|
|
8
|
+
return ANTHROPIC_REQUEST_ID_PATTERN.test(value);
|
|
9
|
+
}
|
|
10
|
+
export function isValidBillingPromptId(value) {
|
|
11
|
+
return PROMPT_ID_PATTERN.test(value);
|
|
12
|
+
}
|
|
13
|
+
const CCH_SEED = 0x4d659218e32a3268n;
|
|
5
14
|
const CCH_PLACEHOLDER = 'cch=00000;';
|
|
6
15
|
export const CCH_PATTERN = /\bcch=([0-9a-f]{5});/;
|
|
7
16
|
const BILLING_HEADER_CCH_PATTERN = /("system":\[\{"type":"text","text":"x-anthropic-billing-header: cc_version=[^;"]+; cc_entrypoint=[^;"]+; )cch=([0-9a-f]{5});/;
|
|
@@ -18,8 +27,8 @@ async function ensureXxhash() {
|
|
|
18
27
|
await xxhashPromise;
|
|
19
28
|
}
|
|
20
29
|
/**
|
|
21
|
-
* Extract
|
|
22
|
-
*
|
|
30
|
+
* Extract the visible prompt from the first user message. Claude Code prepends
|
|
31
|
+
* reminder blocks and derives its version suffix from the final text block.
|
|
23
32
|
*/
|
|
24
33
|
export function extractFirstUserMessageText(messages) {
|
|
25
34
|
const userMsg = messages.find((message) => message.role === 'user');
|
|
@@ -29,12 +38,48 @@ export function extractFirstUserMessageText(messages) {
|
|
|
29
38
|
if (typeof content === 'string')
|
|
30
39
|
return content;
|
|
31
40
|
if (Array.isArray(content)) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
const textBlocks = content.filter((block) => block.type === 'text');
|
|
42
|
+
const commandBlock = textBlocks.find((block) => block.text?.includes('<command-name>'));
|
|
43
|
+
if (commandBlock?.text) {
|
|
44
|
+
return commandBlock.text.slice(commandBlock.text.indexOf('<command-name>'));
|
|
45
|
+
}
|
|
46
|
+
const visibleBlock = textBlocks.findLast((block) => typeof block.text === 'string' && block.text.length > 0);
|
|
47
|
+
if (visibleBlock?.text)
|
|
48
|
+
return visibleBlock.text;
|
|
35
49
|
}
|
|
36
50
|
return '';
|
|
37
51
|
}
|
|
52
|
+
export class ClaudeCodeFirstUserTextTracker {
|
|
53
|
+
limit;
|
|
54
|
+
#values = new Map();
|
|
55
|
+
constructor(limit = 1_000) {
|
|
56
|
+
this.limit = limit;
|
|
57
|
+
}
|
|
58
|
+
resolve(sessionId, messages, pin = true) {
|
|
59
|
+
const existing = this.#values.get(sessionId);
|
|
60
|
+
if (existing !== undefined) {
|
|
61
|
+
this.#values.delete(sessionId);
|
|
62
|
+
this.#values.set(sessionId, existing);
|
|
63
|
+
return existing;
|
|
64
|
+
}
|
|
65
|
+
const value = extractFirstUserMessageText(messages);
|
|
66
|
+
if (pin) {
|
|
67
|
+
if (!this.#values.has(sessionId) && this.#values.size >= this.limit) {
|
|
68
|
+
const oldest = this.#values.keys().next().value;
|
|
69
|
+
if (oldest !== undefined)
|
|
70
|
+
this.#values.delete(oldest);
|
|
71
|
+
}
|
|
72
|
+
this.#values.set(sessionId, value);
|
|
73
|
+
}
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
has(sessionId) {
|
|
77
|
+
return this.#values.has(sessionId);
|
|
78
|
+
}
|
|
79
|
+
clear() {
|
|
80
|
+
this.#values.clear();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
38
83
|
/**
|
|
39
84
|
* Compute Claude Code's cch token over the final serialized request body.
|
|
40
85
|
*
|
|
@@ -61,29 +106,67 @@ export async function signRequestBody(bodyString) {
|
|
|
61
106
|
if (!BILLING_HEADER_CCH_PATTERN.test(bodyString))
|
|
62
107
|
return bodyString;
|
|
63
108
|
const unsignedBodyString = resetBillingHeaderCCH(bodyString);
|
|
64
|
-
const
|
|
109
|
+
const canonicalBody = JSON.parse(unsignedBodyString);
|
|
110
|
+
if ('model' in canonicalBody)
|
|
111
|
+
canonicalBody.model = '';
|
|
112
|
+
delete canonicalBody.max_tokens;
|
|
113
|
+
const token = await computeCCH(new TextEncoder().encode(JSON.stringify(canonicalBody)));
|
|
65
114
|
return unsignedBodyString.replace(BILLING_HEADER_CCH_PLACEHOLDER_PATTERN, `$1cch=${token};`);
|
|
66
115
|
}
|
|
67
116
|
/**
|
|
68
|
-
* Compute
|
|
69
|
-
*
|
|
70
|
-
* The captured Claude Code version uses its observed build hash. Custom/future
|
|
71
|
-
* versions get a deterministic version-only suffix so date changes never rotate
|
|
72
|
-
* the billing header and bust prompt-cache prefixes.
|
|
117
|
+
* Compute Claude Code's 3-character suffix for cc_version.
|
|
73
118
|
*/
|
|
74
|
-
export function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
119
|
+
export function computeCcVersionSuffix(firstUserText, version = CLAUDE_CODE_VERSION) {
|
|
120
|
+
const sampledText = CCH_POSITIONS.map((position) => firstUserText[position] ?? '0').join('');
|
|
121
|
+
return createHash('sha256')
|
|
122
|
+
.update(`${CCH_SALT}${sampledText}${version}`)
|
|
123
|
+
.digest('hex')
|
|
124
|
+
.slice(0, 3);
|
|
78
125
|
}
|
|
79
126
|
/**
|
|
80
127
|
* Build the billing header with a cch placeholder.
|
|
81
128
|
* signRequestBody() must run after final request serialization to replace it.
|
|
82
129
|
*/
|
|
83
|
-
export function buildBillingHeaderValue(
|
|
84
|
-
const suffix =
|
|
85
|
-
|
|
130
|
+
export function buildBillingHeaderValue(messages, version = CLAUDE_CODE_VERSION, entrypoint, pinnedFirstUserText, lineage) {
|
|
131
|
+
const suffix = computeCcVersionSuffix(pinnedFirstUserText ?? extractFirstUserMessageText(messages), version);
|
|
132
|
+
let value = 'x-anthropic-billing-header: ' +
|
|
86
133
|
`cc_version=${version}.${suffix}; ` +
|
|
87
134
|
`cc_entrypoint=${entrypoint}; ` +
|
|
88
|
-
'cch=00000;'
|
|
135
|
+
'cch=00000;';
|
|
136
|
+
if (lineage?.previousRequestId &&
|
|
137
|
+
isValidAnthropicRequestId(lineage.previousRequestId)) {
|
|
138
|
+
value += ` cc_prev_req=${lineage.previousRequestId};`;
|
|
139
|
+
}
|
|
140
|
+
if (lineage?.promptId && isValidBillingPromptId(lineage.promptId)) {
|
|
141
|
+
value += ` cc_prompt_id=${lineage.promptId};`;
|
|
142
|
+
}
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
/** Remove request-scoped Claude Code lineage from a billing header string. */
|
|
146
|
+
export function stripBillingLineageFields(value) {
|
|
147
|
+
return value.replace(BILLING_LINEAGE_FIELD_PATTERN, '');
|
|
148
|
+
}
|
|
149
|
+
/** Remove request-scoped Claude Code lineage before reusing a request body. */
|
|
150
|
+
export function stripBillingLineageFromBody(body) {
|
|
151
|
+
if (!body || typeof body !== 'object')
|
|
152
|
+
return 0;
|
|
153
|
+
const system = body.system;
|
|
154
|
+
if (!Array.isArray(system))
|
|
155
|
+
return 0;
|
|
156
|
+
let stripped = 0;
|
|
157
|
+
for (const block of system) {
|
|
158
|
+
if (!block || typeof block !== 'object')
|
|
159
|
+
continue;
|
|
160
|
+
const text = block.text;
|
|
161
|
+
if (typeof text !== 'string' ||
|
|
162
|
+
!text.startsWith('x-anthropic-billing-header:')) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const clean = stripBillingLineageFields(text);
|
|
166
|
+
if (clean === text)
|
|
167
|
+
continue;
|
|
168
|
+
block.text = clean;
|
|
169
|
+
stripped += 1;
|
|
170
|
+
}
|
|
171
|
+
return stripped;
|
|
89
172
|
}
|
package/dist/claude-code.d.ts
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
+
export type ProviderAccountUuid = string & {
|
|
2
|
+
readonly __providerAccountUuid: unique symbol;
|
|
3
|
+
};
|
|
1
4
|
export type ClaudeCodeIdentity = {
|
|
2
5
|
deviceId: string;
|
|
3
6
|
accountIdentity?: string;
|
|
4
|
-
accountUuid?:
|
|
7
|
+
accountUuid?: ProviderAccountUuid;
|
|
5
8
|
sessionId: string;
|
|
6
9
|
};
|
|
7
10
|
export declare function setBounded<K, V>(map: Map<K, V>, key: K, value: V, limit?: number): void;
|
|
8
11
|
export declare function getClaudeCodeIdentity(seed: string): ClaudeCodeIdentity;
|
|
9
12
|
export declare function resetClaudeCodeIdentityCachesForTest(): void;
|
|
13
|
+
/** Use the identity already verified by the credential custodian, without an
|
|
14
|
+
* additional bootstrap request carrying a previously authorized token. */
|
|
15
|
+
export declare function getClaudeCodeIdentityForVerifiedAccount(accountIdentity: string, accountUuid: ProviderAccountUuid): ClaudeCodeIdentity;
|
|
10
16
|
export declare function resolveClaudeCodeIdentity(accessToken: string, model: string | undefined, accountIdentity: string | undefined): Promise<ClaudeCodeIdentity>;
|
|
11
17
|
export declare function buildClaudeCodeMetadataUserId(identity: ClaudeCodeIdentity): string | null;
|
|
12
18
|
export declare function applyClaudeCodeMetadata(body: Record<string, unknown>, identity: ClaudeCodeIdentity): boolean;
|
|
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'];
|
|
19
|
+
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', 'mid-conversation-system-2026-04-07', 'effort-2025-11-24', 'fallback-credit-2026-06-01', 'extended-cache-ttl-2025-04-11', 'cache-diagnosis-2026-04-07'];
|
|
14
20
|
export declare function selectClaudeCodeBetas(body?: Record<string, unknown> | null, extraBetas?: string[]): string;
|
|
15
21
|
export declare function applyClaudeCodeHeaders(headers: Headers, accessToken: string, options?: {
|
|
16
22
|
body?: Record<string, unknown> | null;
|
|
@@ -18,5 +24,4 @@ export declare function applyClaudeCodeHeaders(headers: Headers, accessToken: st
|
|
|
18
24
|
extraBetas?: string[];
|
|
19
25
|
}): Headers;
|
|
20
26
|
export declare function orderClaudeCodeBody<T extends Record<string, unknown>>(body: T): T;
|
|
21
|
-
export declare function claudeCodeBuildHash(): string;
|
|
22
27
|
export declare function claudeCodeEntrypoint(): string;
|
package/dist/claude-code.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomBytes, randomUUID } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { 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
5
|
export function setBounded(map, key, value, limit = IDENTITY_CACHE_LIMIT) {
|
|
@@ -101,7 +101,9 @@ async function fetchClaudeCodeAccountUuid(accessToken, model) {
|
|
|
101
101
|
return null;
|
|
102
102
|
const data = (await response.json().catch(() => null));
|
|
103
103
|
const accountUuid = data?.oauth_account?.account_uuid;
|
|
104
|
-
return typeof accountUuid === 'string' && accountUuid
|
|
104
|
+
return typeof accountUuid === 'string' && accountUuid
|
|
105
|
+
? accountUuid
|
|
106
|
+
: null;
|
|
105
107
|
}
|
|
106
108
|
catch {
|
|
107
109
|
return null;
|
|
@@ -110,6 +112,18 @@ async function fetchClaudeCodeAccountUuid(accessToken, model) {
|
|
|
110
112
|
clearTimeout(timeout);
|
|
111
113
|
}
|
|
112
114
|
}
|
|
115
|
+
/** Use the identity already verified by the credential custodian, without an
|
|
116
|
+
* additional bootstrap request carrying a previously authorized token. */
|
|
117
|
+
export function getClaudeCodeIdentityForVerifiedAccount(accountIdentity, accountUuid) {
|
|
118
|
+
if (!accountIdentity.trim() || !accountUuid.trim()) {
|
|
119
|
+
throw new Error('A verified provider account identity is required');
|
|
120
|
+
}
|
|
121
|
+
const base = identityCache.get(accountCacheKey(accountUuid, accountIdentity)) ??
|
|
122
|
+
getClaudeCodeIdentity(explicitCacheKey(accountIdentity));
|
|
123
|
+
const identity = { ...base, accountIdentity, accountUuid };
|
|
124
|
+
cacheAccountIdentity(identity, accountIdentity, accountUuid);
|
|
125
|
+
return identity;
|
|
126
|
+
}
|
|
113
127
|
export async function resolveClaudeCodeIdentity(accessToken, model, accountIdentity) {
|
|
114
128
|
const stableAccountIdentity = accountIdentity?.trim() || undefined;
|
|
115
129
|
const cacheKey = stableAccountIdentity
|
|
@@ -130,8 +144,9 @@ export async function resolveClaudeCodeIdentity(accessToken, model, accountIdent
|
|
|
130
144
|
setBounded(identityCache, cacheKey, identity);
|
|
131
145
|
setBounded(identityCache, accessToken, identity);
|
|
132
146
|
}
|
|
133
|
-
if (!accessToken.startsWith('sk-ant-oat'))
|
|
134
|
-
return identity;
|
|
147
|
+
if (!accessToken.startsWith('sk-ant-oat')) {
|
|
148
|
+
return clearCachedAccountUuid(cacheKey, identity);
|
|
149
|
+
}
|
|
135
150
|
const now = Date.now();
|
|
136
151
|
// A slot-stable identity survives account replacement; bootstrap is the
|
|
137
152
|
// account lookup that must be repeated for each credential presented to it.
|
|
@@ -232,6 +247,8 @@ export function applyClaudeCodeMetadata(body, identity) {
|
|
|
232
247
|
metadata.user_id = userId;
|
|
233
248
|
return true;
|
|
234
249
|
}
|
|
250
|
+
// Claude Code 2.1.258 sends redact-thinking-2026-02-12, but it suppresses
|
|
251
|
+
// thinking-block content that OpenCode displays; deliberately omitted after A/B proof on 2026-09-01.
|
|
235
252
|
export const CLAUDE_CODE_FULL_AGENT_BETAS = [
|
|
236
253
|
'oauth-2025-04-20',
|
|
237
254
|
'interleaved-thinking-2025-05-14',
|
|
@@ -241,6 +258,9 @@ export const CLAUDE_CODE_FULL_AGENT_BETAS = [
|
|
|
241
258
|
'claude-code-20250219',
|
|
242
259
|
'advisor-tool-2026-03-01',
|
|
243
260
|
'advanced-tool-use-2025-11-20',
|
|
261
|
+
'mid-conversation-system-2026-04-07',
|
|
262
|
+
'effort-2025-11-24',
|
|
263
|
+
'fallback-credit-2026-06-01',
|
|
244
264
|
'extended-cache-ttl-2025-04-11',
|
|
245
265
|
'cache-diagnosis-2026-04-07',
|
|
246
266
|
];
|
|
@@ -378,9 +398,6 @@ export function orderClaudeCodeBody(body) {
|
|
|
378
398
|
}
|
|
379
399
|
return ordered;
|
|
380
400
|
}
|
|
381
|
-
export function claudeCodeBuildHash() {
|
|
382
|
-
return CLAUDE_CODE_BUILD_HASH;
|
|
383
|
-
}
|
|
384
401
|
export function claudeCodeEntrypoint() {
|
|
385
402
|
return CLAUDE_CODE_ENTRYPOINT;
|
|
386
403
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ClaustrumEnrollmentConnection, type ClaustrumEnrollmentPaths, type ClaustrumEnrollmentStatus } from './claustrum-enrollment.js';
|
|
2
|
+
export declare function getHostClaustrumEnrollmentPaths(host: 'opencode' | 'pi', env?: NodeJS.ProcessEnv, cwd?: string): ClaustrumEnrollmentPaths;
|
|
3
|
+
export interface ClaustrumEnrollmentAdoption {
|
|
4
|
+
status(): ClaustrumEnrollmentStatus;
|
|
5
|
+
reconcileNow(): Promise<void>;
|
|
6
|
+
resetTerminal(): Promise<'reset' | 'idle' | 'refused-pending' | 'refused-approved' | 'busy'>;
|
|
7
|
+
release(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare function adoptClaustrumEnrollment(options: {
|
|
10
|
+
proposedName: string;
|
|
11
|
+
paths: ClaustrumEnrollmentPaths;
|
|
12
|
+
connect: () => Promise<ClaustrumEnrollmentConnection>;
|
|
13
|
+
onStatus?: (status: ClaustrumEnrollmentStatus) => void;
|
|
14
|
+
setTimeoutImpl?: typeof setTimeout;
|
|
15
|
+
clearTimeoutImpl?: typeof clearTimeout;
|
|
16
|
+
pollIntervalMs?: number;
|
|
17
|
+
}): ClaustrumEnrollmentAdoption;
|