@cortexkit/anthropic-auth-core
Advanced tools
@@ -101,2 +101,4 @@ import { type Cache1hMode } from './constants.ts'; | ||
| now?: () => number; | ||
| renew?: boolean; | ||
| renewIntervalMs?: number; | ||
| }): Promise<{ | ||
@@ -103,0 +105,0 @@ release: () => Promise<void>; |
+40
-1
@@ -8,2 +8,4 @@ import { createHash, randomUUID } from 'node:crypto'; | ||
| import { log } from "./logger.js"; | ||
| const setRefreshLockRenewalTimeout = globalThis.setTimeout.bind(globalThis); | ||
| const clearRefreshLockRenewalTimeout = globalThis.clearTimeout.bind(globalThis); | ||
| export const ACCOUNT_FILE_NAME = 'anthropic-auth.json'; | ||
@@ -26,3 +28,3 @@ export const QUOTA_URL = 'https://api.anthropic.com/api/oauth/usage'; | ||
| const BACKGROUND_TICK_JITTER_MS = 60_000; | ||
| const FALLBACK_REFRESH_LOCK_TTL_MS = 2 * 60_000; | ||
| const FALLBACK_REFRESH_LOCK_TTL_MS = 10 * 60_000; | ||
| const FALLBACK_REFRESH_JOIN_WAIT_MS = 10_000; | ||
@@ -176,2 +178,4 @@ const FALLBACK_REFRESH_JOIN_POLL_MS = 100; | ||
| const now = options.now ?? Date.now; | ||
| let renewTimer = null; | ||
| let released = false; | ||
| async function readOwner() { | ||
@@ -188,2 +192,5 @@ try { | ||
| } | ||
| async function writeOwner() { | ||
| await writeFile(lockPath, `${JSON.stringify({ ownerId, expiresAt: now() + options.ttlMs })}\n`, { encoding: 'utf8', mode: 0o600 }); | ||
| } | ||
| async function tryAcquire() { | ||
@@ -201,2 +208,27 @@ try { | ||
| } | ||
| function scheduleRenewal() { | ||
| if (!options.renew || released) | ||
| return; | ||
| const intervalMs = options.renewIntervalMs ?? Math.max(1_000, Math.floor(options.ttlMs / 3)); | ||
| renewTimer = setRefreshLockRenewalTimeout(() => { | ||
| void (async () => { | ||
| try { | ||
| const owner = await readOwner(); | ||
| const currentNow = now(); | ||
| if (released || | ||
| owner?.ownerId !== ownerId || | ||
| Number(owner?.expiresAt) <= currentNow) { | ||
| return; | ||
| } | ||
| await writeOwner(); | ||
| scheduleRenewal(); | ||
| } | ||
| catch { | ||
| // If renewal fails, contenders will wait until the last written expiry. | ||
| } | ||
| })(); | ||
| }, intervalMs); | ||
| if ('unref' in renewTimer) | ||
| renewTimer.unref(); | ||
| } | ||
| if (!(await tryAcquire())) { | ||
@@ -222,4 +254,10 @@ try { | ||
| } | ||
| scheduleRenewal(); | ||
| return { | ||
| release: async () => { | ||
| released = true; | ||
| if (renewTimer) { | ||
| clearRefreshLockRenewalTimeout(renewTimer); | ||
| renewTimer = null; | ||
| } | ||
| try { | ||
@@ -860,2 +898,3 @@ const owner = await readOwner(); | ||
| now: this.now, | ||
| renew: true, | ||
| }); | ||
@@ -862,0 +901,0 @@ if (!fileLock) { |
@@ -1,2 +0,2 @@ | ||
| import { CCH_PATTERN, signRequestBody } from "./cch.js"; | ||
| import { signRequestBody } from "./cch.js"; | ||
| import { orderClaudeCodeBody } from "./claude-code.js"; | ||
@@ -172,4 +172,3 @@ export const CLAUDE_CACHE_KEEP_COMMAND_NAME = 'claude-cachekeep'; | ||
| } | ||
| const unsignedBodyText = JSON.stringify(orderClaudeCodeBody(warm)).replace(CCH_PATTERN, 'cch=00000;'); | ||
| const signedBodyText = await signRequestBody(unsignedBodyText); | ||
| const signedBodyText = await signRequestBody(JSON.stringify(orderClaudeCodeBody(warm))); | ||
| return { ok: true, bodyText: signedBodyText }; | ||
@@ -176,0 +175,0 @@ } |
+2
-0
@@ -21,2 +21,4 @@ type Message = { | ||
| export declare function computeCCH(bodyBytes: Uint8Array): Promise<string>; | ||
| export declare function resetBillingHeaderCCH(bodyString: string): string; | ||
| export declare function extractBillingHeaderCCH(bodyString: string): string | null; | ||
| export declare function signRequestBody(bodyString: string): Promise<string>; | ||
@@ -23,0 +25,0 @@ /** |
+13
-3
@@ -5,3 +5,6 @@ import { createHash } from 'node:crypto'; | ||
| const CCH_SEED = 0x6e52736ac806831en; | ||
| const CCH_PLACEHOLDER = 'cch=00000;'; | ||
| export const CCH_PATTERN = /\bcch=([0-9a-f]{5});/; | ||
| const BILLING_HEADER_CCH_PATTERN = /("system":\[\{"type":"text","text":"x-anthropic-billing-header: cc_version=[^;"]+; cc_entrypoint=[^;"]+; )cch=([0-9a-f]{5});/; | ||
| const BILLING_HEADER_CCH_PLACEHOLDER_PATTERN = /("system":\[\{"type":"text","text":"x-anthropic-billing-header: cc_version=[^;"]+; cc_entrypoint=[^;"]+; )cch=00000;/; | ||
| let xxhashPromise = null; | ||
@@ -47,7 +50,14 @@ let xxhash64Raw = null; | ||
| } | ||
| export function resetBillingHeaderCCH(bodyString) { | ||
| return bodyString.replace(BILLING_HEADER_CCH_PATTERN, `$1${CCH_PLACEHOLDER}`); | ||
| } | ||
| export function extractBillingHeaderCCH(bodyString) { | ||
| return BILLING_HEADER_CCH_PATTERN.exec(bodyString)?.[2] ?? null; | ||
| } | ||
| export async function signRequestBody(bodyString) { | ||
| if (!CCH_PATTERN.test(bodyString)) | ||
| if (!BILLING_HEADER_CCH_PATTERN.test(bodyString)) | ||
| return bodyString; | ||
| const token = await computeCCH(new TextEncoder().encode(bodyString)); | ||
| return bodyString.replace(CCH_PATTERN, `cch=${token};`); | ||
| const unsignedBodyString = resetBillingHeaderCCH(bodyString); | ||
| const token = await computeCCH(new TextEncoder().encode(unsignedBodyString)); | ||
| return unsignedBodyString.replace(BILLING_HEADER_CCH_PLACEHOLDER_PATTERN, `$1cch=${token};`); | ||
| } | ||
@@ -54,0 +64,0 @@ /** |
@@ -16,2 +16,3 @@ export declare const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"; | ||
| export declare const CLAUDE_CODE_IDENTITY = "You are a Claude agent, built on Anthropic's Claude Agent SDK."; | ||
| export declare const PARALLEL_TOOL_CALLS_SYSTEM_PROMPT: string; | ||
| export declare const CCH_SALT = "59cf53e54c78"; | ||
@@ -18,0 +19,0 @@ export declare const CCH_POSITIONS: number[]; |
@@ -31,6 +31,13 @@ export const CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e'; | ||
| return (typeof model === 'string' && | ||
| (model.startsWith('claude-opus-4-6') || model.startsWith('claude-opus-4-7'))); | ||
| (model.startsWith('claude-opus-4-6') || | ||
| model.startsWith('claude-opus-4-7') || | ||
| model.startsWith('claude-opus-4-8'))); | ||
| } | ||
| export const OPENCODE_IDENTITY_PREFIX = 'You are OpenCode'; | ||
| export const CLAUDE_CODE_IDENTITY = "You are a Claude agent, built on Anthropic's Claude Agent SDK."; | ||
| export const PARALLEL_TOOL_CALLS_SYSTEM_PROMPT = [ | ||
| '<use_parallel_tool_calls>', | ||
| '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.', | ||
| '</use_parallel_tool_calls>', | ||
| ].join('\n'); | ||
| export const CCH_SALT = '59cf53e54c78'; | ||
@@ -37,0 +44,0 @@ export const CCH_POSITIONS = [4, 7, 20]; |
+9
-6
@@ -5,2 +5,3 @@ import { createHash } from 'node:crypto'; | ||
| import { join } from 'node:path'; | ||
| import { extractBillingHeaderCCH } from "./cch.js"; | ||
| import { relayLog } from "./logger.js"; | ||
@@ -13,3 +14,4 @@ export const CLAUDE_DUMP_COMMAND_NAME = 'claude-dump'; | ||
| const DUMP_USAGE = 'Usage: `/claude-dump`, `/claude-dump on`, or `/claude-dump off`.'; | ||
| const DUMP_DIR = join(tmpdir(), 'opencode-anthropic-auth-dumps'); | ||
| const DUMP_DIR_ENV = 'OPENCODE_ANTHROPIC_AUTH_DUMP_DIR'; | ||
| const DEFAULT_DUMP_DIR = join(tmpdir(), 'opencode-anthropic-auth-dumps'); | ||
| let dumpEnabled = false; | ||
@@ -27,3 +29,3 @@ let nextDumpId = 0; | ||
| export function getDumpDirectory() { | ||
| return DUMP_DIR; | ||
| return process.env[DUMP_DIR_ENV] || DEFAULT_DUMP_DIR; | ||
| } | ||
@@ -46,3 +48,3 @@ export function parseDumpCommandAction(argumentsText) { | ||
| `- Enabled: ${enabled ? 'enabled' : 'disabled'}`, | ||
| `- Directory: ${DUMP_DIR}`, | ||
| `- Directory: ${getDumpDirectory()}`, | ||
| '- Persisted: ~/.config/opencode/anthropic-auth.json', | ||
@@ -87,3 +89,3 @@ '- Captures: final rewritten Anthropic body plus redacted relay payload metadata', | ||
| function cchToken(bodyText) { | ||
| return bodyText.match(/\bcch=([0-9a-f]{5});/)?.[1] ?? null; | ||
| return extractBillingHeaderCCH(bodyText); | ||
| } | ||
@@ -191,5 +193,6 @@ function diffSummary(previousBodyText, bodyText) { | ||
| const id = `${new Date().toISOString().replace(/[:.]/g, '-')}-${String(nextDumpId).padStart(5, '0')}-${input.transport}-p${input.protocol}-${input.mode}`; | ||
| const prefix = join(DUMP_DIR, id); | ||
| const dumpDir = getDumpDirectory(); | ||
| const prefix = join(dumpDir, id); | ||
| try { | ||
| await mkdir(DUMP_DIR, { recursive: true }); | ||
| await mkdir(dumpDir, { recursive: true }); | ||
| const metadata = { | ||
@@ -196,0 +199,0 @@ id, |
+1
-1
@@ -35,3 +35,3 @@ export const CLAUDE_FAST_COMMAND_NAME = 'claude-fast'; | ||
| '- Scope: adds Anthropic fast mode to supported Opus requests', | ||
| '- Supported models: claude-opus-4-6 and claude-opus-4-7', | ||
| '- Supported models: claude-opus-4-6, claude-opus-4-7, and claude-opus-4-8', | ||
| '- Request changes: adds `speed: "fast"` and the `fast-mode-2026-02-01` beta header', | ||
@@ -38,0 +38,0 @@ '- Note: fast and standard speeds do not share prompt-cache prefixes', |
+1
-1
| { | ||
| "name": "@cortexkit/anthropic-auth-core", | ||
| "version": "1.3.0", | ||
| "version": "1.4.0", | ||
| "type": "module", | ||
@@ -5,0 +5,0 @@ "repository": { |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
166156
2.11%4082
1.59%9
12.5%