@cortexkit/anthropic-auth-core 1.20.0 → 1.22.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 +66 -16
- package/dist/accounts.js +693 -223
- package/dist/auth.js +3 -10
- package/dist/cch.d.ts +13 -9
- package/dist/cch.js +57 -19
- package/dist/claude-code.d.ts +4 -3
- package/dist/claude-code.js +130 -25
- package/dist/claustrum.d.ts +109 -0
- package/dist/claustrum.js +529 -0
- package/dist/commands/account.d.ts +5 -1
- package/dist/commands/account.js +16 -2
- package/dist/constants.d.ts +4 -5
- package/dist/constants.js +4 -5
- package/dist/dump.d.ts +1 -0
- package/dist/dump.js +8 -2
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/json.d.ts +1 -0
- package/dist/json.js +10 -0
- package/dist/mid-conversation-output-config.d.ts +13 -0
- package/dist/mid-conversation-output-config.js +89 -0
- package/dist/models.d.ts +27 -1
- package/dist/models.js +43 -0
- 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 +10 -4
- package/dist/quota-header-feed.d.ts +52 -0
- package/dist/quota-header-feed.js +279 -0
- package/dist/quota-headers.js +63 -8
- package/dist/quota-manager.d.ts +41 -28
- package/dist/quota-manager.js +405 -148
- package/dist/sticky-routing.d.ts +14 -2
- package/dist/sticky-routing.js +30 -13
- package/dist/thinking-binding.d.ts +7 -0
- package/dist/thinking-binding.js +54 -0
- package/package.json +3 -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,8 @@ 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');
|
|
47
40
|
const fetchImpl = input.fetchImpl ?? fetch;
|
|
48
41
|
const maxRetries = input.maxRetries ?? 2;
|
|
49
42
|
const baseDelayMs = input.baseDelayMs ?? 500;
|
package/dist/cch.d.ts
CHANGED
|
@@ -7,10 +7,18 @@ type Message = {
|
|
|
7
7
|
};
|
|
8
8
|
export declare const CCH_PATTERN: RegExp;
|
|
9
9
|
/**
|
|
10
|
-
* Extract
|
|
11
|
-
*
|
|
10
|
+
* Extract the visible prompt from the first user message. Claude Code prepends
|
|
11
|
+
* reminder blocks and derives its version suffix from the final text block.
|
|
12
12
|
*/
|
|
13
13
|
export declare function extractFirstUserMessageText(messages: Message[]): string;
|
|
14
|
+
export declare class ClaudeCodeFirstUserTextTracker {
|
|
15
|
+
#private;
|
|
16
|
+
readonly limit: number;
|
|
17
|
+
constructor(limit?: number);
|
|
18
|
+
resolve(sessionId: string, messages: Message[], pin?: boolean): string;
|
|
19
|
+
has(sessionId: string): boolean;
|
|
20
|
+
clear(): void;
|
|
21
|
+
}
|
|
14
22
|
/**
|
|
15
23
|
* Compute Claude Code's cch token over the final serialized request body.
|
|
16
24
|
*
|
|
@@ -23,16 +31,12 @@ export declare function resetBillingHeaderCCH(bodyString: string): string;
|
|
|
23
31
|
export declare function extractBillingHeaderCCH(bodyString: string): string | null;
|
|
24
32
|
export declare function signRequestBody(bodyString: string): Promise<string>;
|
|
25
33
|
/**
|
|
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.
|
|
34
|
+
* Compute Claude Code's 3-character suffix for cc_version.
|
|
31
35
|
*/
|
|
32
|
-
export declare function
|
|
36
|
+
export declare function computeCcVersionSuffix(firstUserText: string, version?: string): string;
|
|
33
37
|
/**
|
|
34
38
|
* Build the billing header with a cch placeholder.
|
|
35
39
|
* signRequestBody() must run after final request serialization to replace it.
|
|
36
40
|
*/
|
|
37
|
-
export declare function buildBillingHeaderValue(
|
|
41
|
+
export declare function buildBillingHeaderValue(messages: Message[], version: string | undefined, entrypoint: string, pinnedFirstUserText?: string): string;
|
|
38
42
|
export {};
|
package/dist/cch.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import xxhashInit from 'xxhash-wasm';
|
|
3
|
-
import {
|
|
4
|
-
const CCH_SEED =
|
|
3
|
+
import { CCH_POSITIONS, CCH_SALT, CLAUDE_CODE_VERSION } from './constants.js';
|
|
4
|
+
const CCH_SEED = 0x4d659218e32a3268n;
|
|
5
5
|
const CCH_PLACEHOLDER = 'cch=00000;';
|
|
6
6
|
export const CCH_PATTERN = /\bcch=([0-9a-f]{5});/;
|
|
7
7
|
const BILLING_HEADER_CCH_PATTERN = /("system":\[\{"type":"text","text":"x-anthropic-billing-header: cc_version=[^;"]+; cc_entrypoint=[^;"]+; )cch=([0-9a-f]{5});/;
|
|
@@ -18,8 +18,8 @@ async function ensureXxhash() {
|
|
|
18
18
|
await xxhashPromise;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* Extract
|
|
22
|
-
*
|
|
21
|
+
* Extract the visible prompt from the first user message. Claude Code prepends
|
|
22
|
+
* reminder blocks and derives its version suffix from the final text block.
|
|
23
23
|
*/
|
|
24
24
|
export function extractFirstUserMessageText(messages) {
|
|
25
25
|
const userMsg = messages.find((message) => message.role === 'user');
|
|
@@ -29,12 +29,48 @@ export function extractFirstUserMessageText(messages) {
|
|
|
29
29
|
if (typeof content === 'string')
|
|
30
30
|
return content;
|
|
31
31
|
if (Array.isArray(content)) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
const textBlocks = content.filter((block) => block.type === 'text');
|
|
33
|
+
const commandBlock = textBlocks.find((block) => block.text?.includes('<command-name>'));
|
|
34
|
+
if (commandBlock?.text) {
|
|
35
|
+
return commandBlock.text.slice(commandBlock.text.indexOf('<command-name>'));
|
|
36
|
+
}
|
|
37
|
+
const visibleBlock = textBlocks.findLast((block) => typeof block.text === 'string' && block.text.length > 0);
|
|
38
|
+
if (visibleBlock?.text)
|
|
39
|
+
return visibleBlock.text;
|
|
35
40
|
}
|
|
36
41
|
return '';
|
|
37
42
|
}
|
|
43
|
+
export class ClaudeCodeFirstUserTextTracker {
|
|
44
|
+
limit;
|
|
45
|
+
#values = new Map();
|
|
46
|
+
constructor(limit = 1_000) {
|
|
47
|
+
this.limit = limit;
|
|
48
|
+
}
|
|
49
|
+
resolve(sessionId, messages, pin = true) {
|
|
50
|
+
const existing = this.#values.get(sessionId);
|
|
51
|
+
if (existing !== undefined) {
|
|
52
|
+
this.#values.delete(sessionId);
|
|
53
|
+
this.#values.set(sessionId, existing);
|
|
54
|
+
return existing;
|
|
55
|
+
}
|
|
56
|
+
const value = extractFirstUserMessageText(messages);
|
|
57
|
+
if (pin) {
|
|
58
|
+
if (!this.#values.has(sessionId) && this.#values.size >= this.limit) {
|
|
59
|
+
const oldest = this.#values.keys().next().value;
|
|
60
|
+
if (oldest !== undefined)
|
|
61
|
+
this.#values.delete(oldest);
|
|
62
|
+
}
|
|
63
|
+
this.#values.set(sessionId, value);
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
has(sessionId) {
|
|
68
|
+
return this.#values.has(sessionId);
|
|
69
|
+
}
|
|
70
|
+
clear() {
|
|
71
|
+
this.#values.clear();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
38
74
|
/**
|
|
39
75
|
* Compute Claude Code's cch token over the final serialized request body.
|
|
40
76
|
*
|
|
@@ -61,27 +97,29 @@ export async function signRequestBody(bodyString) {
|
|
|
61
97
|
if (!BILLING_HEADER_CCH_PATTERN.test(bodyString))
|
|
62
98
|
return bodyString;
|
|
63
99
|
const unsignedBodyString = resetBillingHeaderCCH(bodyString);
|
|
64
|
-
const
|
|
100
|
+
const canonicalBody = JSON.parse(unsignedBodyString);
|
|
101
|
+
if ('model' in canonicalBody)
|
|
102
|
+
canonicalBody.model = '';
|
|
103
|
+
delete canonicalBody.max_tokens;
|
|
104
|
+
const token = await computeCCH(new TextEncoder().encode(JSON.stringify(canonicalBody)));
|
|
65
105
|
return unsignedBodyString.replace(BILLING_HEADER_CCH_PLACEHOLDER_PATTERN, `$1cch=${token};`);
|
|
66
106
|
}
|
|
67
107
|
/**
|
|
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.
|
|
108
|
+
* Compute Claude Code's 3-character suffix for cc_version.
|
|
73
109
|
*/
|
|
74
|
-
export function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
110
|
+
export function computeCcVersionSuffix(firstUserText, version = CLAUDE_CODE_VERSION) {
|
|
111
|
+
const sampledText = CCH_POSITIONS.map((position) => firstUserText[position] ?? '0').join('');
|
|
112
|
+
return createHash('sha256')
|
|
113
|
+
.update(`${CCH_SALT}${sampledText}${version}`)
|
|
114
|
+
.digest('hex')
|
|
115
|
+
.slice(0, 3);
|
|
78
116
|
}
|
|
79
117
|
/**
|
|
80
118
|
* Build the billing header with a cch placeholder.
|
|
81
119
|
* signRequestBody() must run after final request serialization to replace it.
|
|
82
120
|
*/
|
|
83
|
-
export function buildBillingHeaderValue(
|
|
84
|
-
const suffix =
|
|
121
|
+
export function buildBillingHeaderValue(messages, version = CLAUDE_CODE_VERSION, entrypoint, pinnedFirstUserText) {
|
|
122
|
+
const suffix = computeCcVersionSuffix(pinnedFirstUserText ?? extractFirstUserMessageText(messages), version);
|
|
85
123
|
return ('x-anthropic-billing-header: ' +
|
|
86
124
|
`cc_version=${version}.${suffix}; ` +
|
|
87
125
|
`cc_entrypoint=${entrypoint}; ` +
|
package/dist/claude-code.d.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
export type ClaudeCodeIdentity = {
|
|
2
2
|
deviceId: string;
|
|
3
|
+
accountIdentity?: string;
|
|
3
4
|
accountUuid?: string;
|
|
4
5
|
sessionId: string;
|
|
5
6
|
};
|
|
6
7
|
export declare function setBounded<K, V>(map: Map<K, V>, key: K, value: V, limit?: number): void;
|
|
7
8
|
export declare function getClaudeCodeIdentity(seed: string): ClaudeCodeIdentity;
|
|
8
|
-
export declare function
|
|
9
|
+
export declare function resetClaudeCodeIdentityCachesForTest(): void;
|
|
10
|
+
export declare function resolveClaudeCodeIdentity(accessToken: string, model: string | undefined, accountIdentity: string | undefined): Promise<ClaudeCodeIdentity>;
|
|
9
11
|
export declare function buildClaudeCodeMetadataUserId(identity: ClaudeCodeIdentity): string | null;
|
|
10
12
|
export declare function applyClaudeCodeMetadata(body: Record<string, unknown>, identity: ClaudeCodeIdentity): boolean;
|
|
11
|
-
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', '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'];
|
|
12
14
|
export declare function selectClaudeCodeBetas(body?: Record<string, unknown> | null, extraBetas?: string[]): string;
|
|
13
15
|
export declare function applyClaudeCodeHeaders(headers: Headers, accessToken: string, options?: {
|
|
14
16
|
body?: Record<string, unknown> | null;
|
|
@@ -16,5 +18,4 @@ export declare function applyClaudeCodeHeaders(headers: Headers, accessToken: st
|
|
|
16
18
|
extraBetas?: string[];
|
|
17
19
|
}): Headers;
|
|
18
20
|
export declare function orderClaudeCodeBody<T extends Record<string, unknown>>(body: T): T;
|
|
19
|
-
export declare function claudeCodeBuildHash(): string;
|
|
20
21
|
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) {
|
|
@@ -10,6 +10,16 @@ export function setBounded(map, key, value, limit = IDENTITY_CACHE_LIMIT) {
|
|
|
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
|
|
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
|
-
|
|
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);
|
|
150
|
+
return identity;
|
|
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);
|
|
71
156
|
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
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(
|
|
168
|
+
let fetchPromise = bootstrapFetches.get(bootstrapKey);
|
|
80
169
|
if (!fetchPromise) {
|
|
81
170
|
fetchPromise = fetchClaudeCodeAccountUuid(accessToken, model);
|
|
82
|
-
bootstrapFetches
|
|
171
|
+
setBounded(bootstrapFetches, bootstrapKey, fetchPromise);
|
|
83
172
|
}
|
|
84
173
|
const accountUuid = await fetchPromise.finally(() => {
|
|
85
|
-
bootstrapFetches.
|
|
174
|
+
if (bootstrapFetches.get(bootstrapKey) === fetchPromise) {
|
|
175
|
+
bootstrapFetches.delete(bootstrapKey);
|
|
176
|
+
}
|
|
86
177
|
});
|
|
87
|
-
setBounded(bootstrapResults,
|
|
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);
|
|
95
187
|
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
188
|
}
|
|
102
|
-
|
|
103
|
-
|
|
189
|
+
if (!stableAccountIdentity) {
|
|
190
|
+
identity = { ...identity, accountUuid };
|
|
191
|
+
setBounded(identityCache, bootstrapKey, identity);
|
|
192
|
+
return identity;
|
|
193
|
+
}
|
|
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) {
|
|
@@ -129,6 +232,8 @@ export function applyClaudeCodeMetadata(body, identity) {
|
|
|
129
232
|
metadata.user_id = userId;
|
|
130
233
|
return true;
|
|
131
234
|
}
|
|
235
|
+
// Claude Code 2.1.258 sends redact-thinking-2026-02-12, but it suppresses
|
|
236
|
+
// thinking-block content that OpenCode displays; deliberately omitted after A/B proof on 2026-09-01.
|
|
132
237
|
export const CLAUDE_CODE_FULL_AGENT_BETAS = [
|
|
133
238
|
'oauth-2025-04-20',
|
|
134
239
|
'interleaved-thinking-2025-05-14',
|
|
@@ -138,6 +243,9 @@ export const CLAUDE_CODE_FULL_AGENT_BETAS = [
|
|
|
138
243
|
'claude-code-20250219',
|
|
139
244
|
'advisor-tool-2026-03-01',
|
|
140
245
|
'advanced-tool-use-2025-11-20',
|
|
246
|
+
'mid-conversation-system-2026-04-07',
|
|
247
|
+
'effort-2025-11-24',
|
|
248
|
+
'fallback-credit-2026-06-01',
|
|
141
249
|
'extended-cache-ttl-2025-04-11',
|
|
142
250
|
'cache-diagnosis-2026-04-07',
|
|
143
251
|
];
|
|
@@ -275,9 +383,6 @@ export function orderClaudeCodeBody(body) {
|
|
|
275
383
|
}
|
|
276
384
|
return ordered;
|
|
277
385
|
}
|
|
278
|
-
export function claudeCodeBuildHash() {
|
|
279
|
-
return CLAUDE_CODE_BUILD_HASH;
|
|
280
|
-
}
|
|
281
386
|
export function claudeCodeEntrypoint() {
|
|
282
387
|
return CLAUDE_CODE_ENTRYPOINT;
|
|
283
388
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { type BindIdentity, type CatalogEntry, type CloseRouteOptions, type ManagedCallOptions, type ManagedCloseRouteOptions, type RequestOptions, type RouteHandle, type RouteOpenOptions, type RouteTarget, SubcClient, type SubscribeOptions, type Subscription } from '@cortexkit/subc-client';
|
|
2
|
+
export type ClaustrumEndpoint = {
|
|
3
|
+
host: string;
|
|
4
|
+
port: number;
|
|
5
|
+
};
|
|
6
|
+
export type ClaustrumDetection = {
|
|
7
|
+
status: 'available';
|
|
8
|
+
schema: number;
|
|
9
|
+
wireVersion: number;
|
|
10
|
+
endpoints: ClaustrumEndpoint[];
|
|
11
|
+
} | {
|
|
12
|
+
status: 'absent';
|
|
13
|
+
path: string;
|
|
14
|
+
} | {
|
|
15
|
+
status: 'malformed';
|
|
16
|
+
path: string;
|
|
17
|
+
reason: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function getDefaultClaustrumConnectionPath(): string;
|
|
20
|
+
export declare function detectClaustrumConnection(path?: string): Promise<ClaustrumDetection>;
|
|
21
|
+
export declare const CUSTODY_TOMBSTONE_PREFIX = "claustrum-tombstone:v1:";
|
|
22
|
+
export declare function custodyTombstoneKey(provider: string): string;
|
|
23
|
+
export declare function isCustodyTombstoneValue(value: unknown): value is string;
|
|
24
|
+
export declare function isCustodyTombstoneOAuth(auth: unknown, provider: string): boolean;
|
|
25
|
+
export declare class CustodyTombstoneRefreshError extends Error {
|
|
26
|
+
readonly provider: string;
|
|
27
|
+
readonly code = "custody_tombstone_refresh";
|
|
28
|
+
constructor(provider: string);
|
|
29
|
+
}
|
|
30
|
+
export declare function assertNotCustodyTombstone(refreshToken: unknown, provider: string): void;
|
|
31
|
+
export type CustodyHandleAccount = {
|
|
32
|
+
label: string;
|
|
33
|
+
handle: string;
|
|
34
|
+
credentialId: string;
|
|
35
|
+
};
|
|
36
|
+
export type CustodyHandleManifest = {
|
|
37
|
+
provider: string;
|
|
38
|
+
serve: string;
|
|
39
|
+
shape: string;
|
|
40
|
+
accounts: CustodyHandleAccount[];
|
|
41
|
+
};
|
|
42
|
+
export declare function isValidCustodyHandle(value: unknown): value is string;
|
|
43
|
+
export declare function readCustodyHandles(json: unknown, provider: string): CustodyHandleManifest;
|
|
44
|
+
export type ClaustrumClientOptions = {
|
|
45
|
+
connectionFile?: string;
|
|
46
|
+
handshakeTimeoutMs?: number;
|
|
47
|
+
connector?: ClaustrumConnector;
|
|
48
|
+
};
|
|
49
|
+
export type ClaustrumConnector = (options: {
|
|
50
|
+
connectionFile: string;
|
|
51
|
+
handshakeTimeoutMs?: number;
|
|
52
|
+
}) => Promise<SubcClient>;
|
|
53
|
+
export declare class ClaustrumClient {
|
|
54
|
+
#private;
|
|
55
|
+
private constructor();
|
|
56
|
+
static connect(options?: ClaustrumClientOptions): Promise<ClaustrumClient>;
|
|
57
|
+
catalogList(moduleId?: string): Promise<CatalogEntry[]>;
|
|
58
|
+
routeOpen(target: RouteTarget, identity: BindIdentity, options?: Omit<RouteOpenOptions, 'consumerIdentity'>): Promise<RouteHandle>;
|
|
59
|
+
request(handle: RouteHandle, body: unknown, options?: RequestOptions): Promise<unknown>;
|
|
60
|
+
call<Response = unknown>(moduleId: string, method: string, params?: unknown, options?: Omit<ManagedCallOptions, 'consumerIdentity'>): Promise<Response>;
|
|
61
|
+
subscribe(handle: RouteHandle, body: unknown, onEvent: (event: Uint8Array) => void, options?: SubscribeOptions): Subscription;
|
|
62
|
+
closeRoute(handle: RouteHandle, options?: CloseRouteOptions): Promise<void>;
|
|
63
|
+
closeManagedRoute(target: Extract<RouteTarget, {
|
|
64
|
+
kind: 'management_surface' | 'tool_provider';
|
|
65
|
+
}>, identity: BindIdentity, options?: Omit<ManagedCloseRouteOptions, 'consumerIdentity'>): Promise<void>;
|
|
66
|
+
close(): void;
|
|
67
|
+
}
|
|
68
|
+
export declare function connectClaustrumClient(options?: ClaustrumClientOptions): Promise<ClaustrumClient>;
|
|
69
|
+
export declare const CLAUSTRUM_MODULE_ID = "claustrum";
|
|
70
|
+
export declare const DEFAULT_CLAUSTRUM_CREDENTIAL_MIN_TTL_MS = 120000;
|
|
71
|
+
export declare const ERROR_CLASS_WIRE_SET: readonly ['transient', 'permanent', 'auth_required', 'context_overflow'];
|
|
72
|
+
export type ClaustrumCredentialErrorClass = (typeof ERROR_CLASS_WIRE_SET)[number];
|
|
73
|
+
export type ClaustrumCredentialErrorAction = 'gone' | 'reauth' | 'retry' | 'reduce_and_retry';
|
|
74
|
+
export type ClaustrumCredential = {
|
|
75
|
+
payload: string;
|
|
76
|
+
expiresAtMs: number | null;
|
|
77
|
+
recordVersion: number;
|
|
78
|
+
projectId?: string;
|
|
79
|
+
accountId?: string;
|
|
80
|
+
};
|
|
81
|
+
export type ClaustrumServedCredential = Pick<ClaustrumCredential, 'recordVersion'>;
|
|
82
|
+
export type ClaustrumReporterSource = 'direct' | 'relay_status_field' | 'relay_message_parse';
|
|
83
|
+
export declare class ClaustrumCredentialError extends Error {
|
|
84
|
+
readonly code: string;
|
|
85
|
+
readonly action: ClaustrumCredentialErrorAction;
|
|
86
|
+
readonly errorClass: ClaustrumCredentialErrorClass;
|
|
87
|
+
constructor(message: string, code: string, errorClass: ClaustrumCredentialErrorClass, action: ClaustrumCredentialErrorAction);
|
|
88
|
+
}
|
|
89
|
+
export type ClaustrumCredentialCacheOptions = {
|
|
90
|
+
identity?: BindIdentity;
|
|
91
|
+
now?: () => number;
|
|
92
|
+
minTtlMs?: number;
|
|
93
|
+
};
|
|
94
|
+
export declare class ClaustrumCredentialCache {
|
|
95
|
+
#private;
|
|
96
|
+
constructor(client: ClaustrumClient, options?: ClaustrumCredentialCacheOptions);
|
|
97
|
+
get(handle: string, minTtlMs?: number): Promise<ClaustrumCredential>;
|
|
98
|
+
peek(handle: string): ClaustrumCredential | undefined;
|
|
99
|
+
seedForTest(handle: string, credential: ClaustrumCredential): void;
|
|
100
|
+
reduceMinTtlMs(): number;
|
|
101
|
+
reportAuthFailure(handle: string, providerStatus: number, servedCredential: ClaustrumServedCredential, reporterSource?: ClaustrumReporterSource): Promise<void>;
|
|
102
|
+
reportAuthFailure(handle: string, servedCredential: ClaustrumServedCredential, providerStatus?: number, reporterSource?: ClaustrumReporterSource): Promise<void>;
|
|
103
|
+
invalidate(handle: string, recordVersion?: number): void;
|
|
104
|
+
close(): void;
|
|
105
|
+
}
|
|
106
|
+
export type ConnectClaustrumCredentialCacheOptions = ClaustrumClientOptions & ClaustrumCredentialCacheOptions & {
|
|
107
|
+
enabled?: boolean;
|
|
108
|
+
};
|
|
109
|
+
export declare function connectClaustrumCredentialCache(options?: ConnectClaustrumCredentialCacheOptions): Promise<ClaustrumCredentialCache | null>;
|