@cortexkit/anthropic-auth-core 1.3.0 → 1.4.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/accounts.d.ts +2 -0
- package/dist/accounts.js +40 -1
- package/dist/cachekeep.js +2 -3
- package/dist/cch.d.ts +2 -0
- package/dist/cch.js +13 -3
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +8 -1
- package/dist/dump.js +9 -6
- package/dist/fast.js +1 -1
- package/package.json +1 -1
package/dist/accounts.d.ts
CHANGED
package/dist/accounts.js
CHANGED
|
@@ -5,6 +5,8 @@ import { dirname, join } from 'node:path';
|
|
|
5
5
|
import { ClaudeOAuthRefreshError, refreshClaudeOAuthToken } from "./auth.js";
|
|
6
6
|
import { CACHE_1H_MODES, CLAUDE_CODE_VERSION, DEFAULT_CACHE_1H_MODE, } from "./constants.js";
|
|
7
7
|
import { log } from "./logger.js";
|
|
8
|
+
const setRefreshLockRenewalTimeout = globalThis.setTimeout.bind(globalThis);
|
|
9
|
+
const clearRefreshLockRenewalTimeout = globalThis.clearTimeout.bind(globalThis);
|
|
8
10
|
export const ACCOUNT_FILE_NAME = 'anthropic-auth.json';
|
|
9
11
|
export const QUOTA_URL = 'https://api.anthropic.com/api/oauth/usage';
|
|
10
12
|
const DEFAULT_FALLBACK_ON = [401, 403, 429];
|
|
@@ -22,7 +24,7 @@ const DEFAULT_MINIMUM_REMAINING = {
|
|
|
22
24
|
const DEFAULT_FAIL_CLOSED_ON_UNKNOWN_QUOTA = true;
|
|
23
25
|
const BACKGROUND_TICK_MS = 60_000;
|
|
24
26
|
const BACKGROUND_TICK_JITTER_MS = 60_000;
|
|
25
|
-
const FALLBACK_REFRESH_LOCK_TTL_MS =
|
|
27
|
+
const FALLBACK_REFRESH_LOCK_TTL_MS = 10 * 60_000;
|
|
26
28
|
const FALLBACK_REFRESH_JOIN_WAIT_MS = 10_000;
|
|
27
29
|
const FALLBACK_REFRESH_JOIN_POLL_MS = 100;
|
|
28
30
|
function getConfigDir() {
|
|
@@ -171,6 +173,8 @@ export async function acquireRefreshFileLock(options) {
|
|
|
171
173
|
const legacyOwnerPath = join(lockPath, 'owner.json');
|
|
172
174
|
const ownerId = randomUUID();
|
|
173
175
|
const now = options.now ?? Date.now;
|
|
176
|
+
let renewTimer = null;
|
|
177
|
+
let released = false;
|
|
174
178
|
async function readOwner() {
|
|
175
179
|
try {
|
|
176
180
|
return JSON.parse(await readFile(lockPath, 'utf8'));
|
|
@@ -182,6 +186,9 @@ export async function acquireRefreshFileLock(options) {
|
|
|
182
186
|
return JSON.parse(await readFile(legacyOwnerPath, 'utf8'));
|
|
183
187
|
}
|
|
184
188
|
}
|
|
189
|
+
async function writeOwner() {
|
|
190
|
+
await writeFile(lockPath, `${JSON.stringify({ ownerId, expiresAt: now() + options.ttlMs })}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
191
|
+
}
|
|
185
192
|
async function tryAcquire() {
|
|
186
193
|
try {
|
|
187
194
|
await writeFile(lockPath, `${JSON.stringify({ ownerId, expiresAt: now() + options.ttlMs })}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
@@ -194,6 +201,31 @@ export async function acquireRefreshFileLock(options) {
|
|
|
194
201
|
throw error;
|
|
195
202
|
}
|
|
196
203
|
}
|
|
204
|
+
function scheduleRenewal() {
|
|
205
|
+
if (!options.renew || released)
|
|
206
|
+
return;
|
|
207
|
+
const intervalMs = options.renewIntervalMs ?? Math.max(1_000, Math.floor(options.ttlMs / 3));
|
|
208
|
+
renewTimer = setRefreshLockRenewalTimeout(() => {
|
|
209
|
+
void (async () => {
|
|
210
|
+
try {
|
|
211
|
+
const owner = await readOwner();
|
|
212
|
+
const currentNow = now();
|
|
213
|
+
if (released ||
|
|
214
|
+
owner?.ownerId !== ownerId ||
|
|
215
|
+
Number(owner?.expiresAt) <= currentNow) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
await writeOwner();
|
|
219
|
+
scheduleRenewal();
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// If renewal fails, contenders will wait until the last written expiry.
|
|
223
|
+
}
|
|
224
|
+
})();
|
|
225
|
+
}, intervalMs);
|
|
226
|
+
if ('unref' in renewTimer)
|
|
227
|
+
renewTimer.unref();
|
|
228
|
+
}
|
|
197
229
|
if (!(await tryAcquire())) {
|
|
198
230
|
try {
|
|
199
231
|
const owner = await readOwner();
|
|
@@ -214,8 +246,14 @@ export async function acquireRefreshFileLock(options) {
|
|
|
214
246
|
if (!(await tryAcquire()))
|
|
215
247
|
return null;
|
|
216
248
|
}
|
|
249
|
+
scheduleRenewal();
|
|
217
250
|
return {
|
|
218
251
|
release: async () => {
|
|
252
|
+
released = true;
|
|
253
|
+
if (renewTimer) {
|
|
254
|
+
clearRefreshLockRenewalTimeout(renewTimer);
|
|
255
|
+
renewTimer = null;
|
|
256
|
+
}
|
|
219
257
|
try {
|
|
220
258
|
const owner = await readOwner();
|
|
221
259
|
if (owner?.ownerId !== ownerId)
|
|
@@ -851,6 +889,7 @@ export class FallbackAccountManager {
|
|
|
851
889
|
ttlMs: FALLBACK_REFRESH_LOCK_TTL_MS,
|
|
852
890
|
path: this.configPath,
|
|
853
891
|
now: this.now,
|
|
892
|
+
renew: true,
|
|
854
893
|
});
|
|
855
894
|
if (!fileLock) {
|
|
856
895
|
log('[refresh] fallback oauth refresh skipped file lock', {
|
package/dist/cachekeep.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { signRequestBody } from "./cch.js";
|
|
2
2
|
import { orderClaudeCodeBody } from "./claude-code.js";
|
|
3
3
|
export const CLAUDE_CACHE_KEEP_COMMAND_NAME = 'claude-cachekeep';
|
|
4
4
|
export const CACHE_KEEP_TTL_MS = 60 * 60_000;
|
|
@@ -169,8 +169,7 @@ export async function buildCacheKeepPrewarmBody(bodyText) {
|
|
|
169
169
|
if (toolChoice?.type === 'tool' || toolChoice?.type === 'any') {
|
|
170
170
|
delete warm.tool_choice;
|
|
171
171
|
}
|
|
172
|
-
const
|
|
173
|
-
const signedBodyText = await signRequestBody(unsignedBodyText);
|
|
172
|
+
const signedBodyText = await signRequestBody(JSON.stringify(orderClaudeCodeBody(warm)));
|
|
174
173
|
return { ok: true, bodyText: signedBodyText };
|
|
175
174
|
}
|
|
176
175
|
export class CacheKeepManager {
|
package/dist/cch.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ 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 resetBillingHeaderCCH(bodyString: string): string;
|
|
22
|
+
export declare function extractBillingHeaderCCH(bodyString: string): string | null;
|
|
21
23
|
export declare function signRequestBody(bodyString: string): Promise<string>;
|
|
22
24
|
/**
|
|
23
25
|
* Compute a stable 3-character suffix for cc_version.
|
package/dist/cch.js
CHANGED
|
@@ -2,7 +2,10 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import xxhashInit from 'xxhash-wasm';
|
|
3
3
|
import { CLAUDE_CODE_BUILD_HASH, CLAUDE_CODE_VERSION } from "./constants.js";
|
|
4
4
|
const CCH_SEED = 0x6e52736ac806831en;
|
|
5
|
+
const CCH_PLACEHOLDER = 'cch=00000;';
|
|
5
6
|
export const CCH_PATTERN = /\bcch=([0-9a-f]{5});/;
|
|
7
|
+
const BILLING_HEADER_CCH_PATTERN = /("system":\[\{"type":"text","text":"x-anthropic-billing-header: cc_version=[^;"]+; cc_entrypoint=[^;"]+; )cch=([0-9a-f]{5});/;
|
|
8
|
+
const BILLING_HEADER_CCH_PLACEHOLDER_PATTERN = /("system":\[\{"type":"text","text":"x-anthropic-billing-header: cc_version=[^;"]+; cc_entrypoint=[^;"]+; )cch=00000;/;
|
|
6
9
|
let xxhashPromise = null;
|
|
7
10
|
let xxhash64Raw = null;
|
|
8
11
|
async function ensureXxhash() {
|
|
@@ -43,11 +46,18 @@ export async function computeCCH(bodyBytes) {
|
|
|
43
46
|
const hash = xxhash64Raw?.(bodyBytes, CCH_SEED) ?? 0n;
|
|
44
47
|
return (hash & 0xfffffn).toString(16).padStart(5, '0');
|
|
45
48
|
}
|
|
49
|
+
export function resetBillingHeaderCCH(bodyString) {
|
|
50
|
+
return bodyString.replace(BILLING_HEADER_CCH_PATTERN, `$1${CCH_PLACEHOLDER}`);
|
|
51
|
+
}
|
|
52
|
+
export function extractBillingHeaderCCH(bodyString) {
|
|
53
|
+
return BILLING_HEADER_CCH_PATTERN.exec(bodyString)?.[2] ?? null;
|
|
54
|
+
}
|
|
46
55
|
export async function signRequestBody(bodyString) {
|
|
47
|
-
if (!
|
|
56
|
+
if (!BILLING_HEADER_CCH_PATTERN.test(bodyString))
|
|
48
57
|
return bodyString;
|
|
49
|
-
const
|
|
50
|
-
|
|
58
|
+
const unsignedBodyString = resetBillingHeaderCCH(bodyString);
|
|
59
|
+
const token = await computeCCH(new TextEncoder().encode(unsignedBodyString));
|
|
60
|
+
return unsignedBodyString.replace(BILLING_HEADER_CCH_PLACEHOLDER_PATTERN, `$1cch=${token};`);
|
|
51
61
|
}
|
|
52
62
|
/**
|
|
53
63
|
* Compute a stable 3-character suffix for cc_version.
|
package/dist/constants.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export declare function mergeAnthropicBetas(existing: string | null | undefined,
|
|
|
13
13
|
export declare function isFastModeSupportedModel(model: unknown): boolean;
|
|
14
14
|
export declare const OPENCODE_IDENTITY_PREFIX = "You are OpenCode";
|
|
15
15
|
export declare const CLAUDE_CODE_IDENTITY = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
|
|
16
|
+
export declare const PARALLEL_TOOL_CALLS_SYSTEM_PROMPT: string;
|
|
16
17
|
export declare const CCH_SALT = "59cf53e54c78";
|
|
17
18
|
export declare const CCH_POSITIONS: number[];
|
|
18
19
|
export declare const CLAUDE_CODE_VERSION = "2.1.141";
|
package/dist/constants.js
CHANGED
|
@@ -28,10 +28,17 @@ export function mergeAnthropicBetas(existing, betas) {
|
|
|
28
28
|
}
|
|
29
29
|
export function isFastModeSupportedModel(model) {
|
|
30
30
|
return (typeof model === 'string' &&
|
|
31
|
-
(model.startsWith('claude-opus-4-6') ||
|
|
31
|
+
(model.startsWith('claude-opus-4-6') ||
|
|
32
|
+
model.startsWith('claude-opus-4-7') ||
|
|
33
|
+
model.startsWith('claude-opus-4-8')));
|
|
32
34
|
}
|
|
33
35
|
export const OPENCODE_IDENTITY_PREFIX = 'You are OpenCode';
|
|
34
36
|
export const CLAUDE_CODE_IDENTITY = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
|
|
37
|
+
export const PARALLEL_TOOL_CALLS_SYSTEM_PROMPT = [
|
|
38
|
+
'<use_parallel_tool_calls>',
|
|
39
|
+
'For maximum efficiency, whenever you perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially. Prioritize calling tools in parallel whenever possible. For example, when reading 3 files, run 3 tool calls in parallel to read all 3 files into context at the same time. When running multiple read-only commands like `ls` or `list_dir`, always run all of the commands in parallel. Err on the side of maximizing parallel tool calls rather than running too many tools sequentially.',
|
|
40
|
+
'</use_parallel_tool_calls>',
|
|
41
|
+
].join('\n');
|
|
35
42
|
export const CCH_SALT = '59cf53e54c78';
|
|
36
43
|
export const CCH_POSITIONS = [4, 7, 20];
|
|
37
44
|
export const CLAUDE_CODE_VERSION = '2.1.141';
|
package/dist/dump.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
+
import { extractBillingHeaderCCH } from "./cch.js";
|
|
5
6
|
import { relayLog } from "./logger.js";
|
|
6
7
|
export const CLAUDE_DUMP_COMMAND_NAME = 'claude-dump';
|
|
7
8
|
const DUMP_STATUS_TITLE = '## Claude Dump Status';
|
|
@@ -9,7 +10,8 @@ const DUMP_ENABLED_TITLE = '## Claude Dump Enabled';
|
|
|
9
10
|
const DUMP_DISABLED_TITLE = '## Claude Dump Disabled';
|
|
10
11
|
const DUMP_USAGE_TITLE = '## Claude Dump Usage';
|
|
11
12
|
const DUMP_USAGE = 'Usage: `/claude-dump`, `/claude-dump on`, or `/claude-dump off`.';
|
|
12
|
-
const
|
|
13
|
+
const DUMP_DIR_ENV = 'OPENCODE_ANTHROPIC_AUTH_DUMP_DIR';
|
|
14
|
+
const DEFAULT_DUMP_DIR = join(tmpdir(), 'opencode-anthropic-auth-dumps');
|
|
13
15
|
let dumpEnabled = false;
|
|
14
16
|
let nextDumpId = 0;
|
|
15
17
|
export function isDumpEnabled() {
|
|
@@ -22,7 +24,7 @@ export function resetDumpState() {
|
|
|
22
24
|
dumpEnabled = false;
|
|
23
25
|
}
|
|
24
26
|
export function getDumpDirectory() {
|
|
25
|
-
return
|
|
27
|
+
return process.env[DUMP_DIR_ENV] || DEFAULT_DUMP_DIR;
|
|
26
28
|
}
|
|
27
29
|
export function parseDumpCommandAction(argumentsText) {
|
|
28
30
|
const normalized = argumentsText.trim().split(/\s+/).filter(Boolean);
|
|
@@ -40,7 +42,7 @@ export function buildDumpStatusSummary(input) {
|
|
|
40
42
|
DUMP_STATUS_TITLE,
|
|
41
43
|
'',
|
|
42
44
|
`- Enabled: ${enabled ? 'enabled' : 'disabled'}`,
|
|
43
|
-
`- Directory: ${
|
|
45
|
+
`- Directory: ${getDumpDirectory()}`,
|
|
44
46
|
'- Persisted: ~/.config/opencode/anthropic-auth.json',
|
|
45
47
|
'- Captures: final rewritten Anthropic body plus redacted relay payload metadata',
|
|
46
48
|
'- Warning: body dumps may contain prompt/session content; turn this off after debugging',
|
|
@@ -80,7 +82,7 @@ function hashText(value) {
|
|
|
80
82
|
return createHash('sha256').update(value).digest('hex');
|
|
81
83
|
}
|
|
82
84
|
function cchToken(bodyText) {
|
|
83
|
-
return bodyText
|
|
85
|
+
return extractBillingHeaderCCH(bodyText);
|
|
84
86
|
}
|
|
85
87
|
function diffSummary(previousBodyText, bodyText) {
|
|
86
88
|
if (previousBodyText == null)
|
|
@@ -183,9 +185,10 @@ export async function dumpRelayRequest(input) {
|
|
|
183
185
|
return;
|
|
184
186
|
nextDumpId += 1;
|
|
185
187
|
const id = `${new Date().toISOString().replace(/[:.]/g, '-')}-${String(nextDumpId).padStart(5, '0')}-${input.transport}-p${input.protocol}-${input.mode}`;
|
|
186
|
-
const
|
|
188
|
+
const dumpDir = getDumpDirectory();
|
|
189
|
+
const prefix = join(dumpDir, id);
|
|
187
190
|
try {
|
|
188
|
-
await mkdir(
|
|
191
|
+
await mkdir(dumpDir, { recursive: true });
|
|
189
192
|
const metadata = {
|
|
190
193
|
id,
|
|
191
194
|
createdAt: new Date().toISOString(),
|
package/dist/fast.js
CHANGED
|
@@ -32,7 +32,7 @@ export function buildFastModeStatusSummary(input) {
|
|
|
32
32
|
`- Enabled: ${enabled ? 'enabled' : 'disabled'}`,
|
|
33
33
|
'- Persisted: ~/.config/opencode/anthropic-auth.json',
|
|
34
34
|
'- Scope: adds Anthropic fast mode to supported Opus requests',
|
|
35
|
-
'- Supported models: claude-opus-4-6 and claude-opus-4-
|
|
35
|
+
'- Supported models: claude-opus-4-6, claude-opus-4-7, and claude-opus-4-8',
|
|
36
36
|
'- Request changes: adds `speed: "fast"` and the `fast-mode-2026-02-01` beta header',
|
|
37
37
|
'- Note: fast and standard speeds do not share prompt-cache prefixes',
|
|
38
38
|
].join('\n');
|