@cortexkit/anthropic-auth-core 1.3.0 → 1.4.1

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.
@@ -98,6 +98,8 @@ export declare function acquireRefreshFileLock(options: {
98
98
  ttlMs: number;
99
99
  path?: string;
100
100
  now?: () => number;
101
+ renew?: boolean;
102
+ renewIntervalMs?: number;
101
103
  }): Promise<{
102
104
  release: () => Promise<void>;
103
105
  } | null>;
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 = 2 * 60_000;
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 { CCH_PATTERN, signRequestBody } from "./cch.js";
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 unsignedBodyText = JSON.stringify(orderClaudeCodeBody(warm)).replace(CCH_PATTERN, 'cch=00000;');
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 (!CCH_PATTERN.test(bodyString))
56
+ if (!BILLING_HEADER_CCH_PATTERN.test(bodyString))
48
57
  return bodyString;
49
- const token = await computeCCH(new TextEncoder().encode(bodyString));
50
- return bodyString.replace(CCH_PATTERN, `cch=${token};`);
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.
@@ -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,19 @@ 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') || model.startsWith('claude-opus-4-7')));
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 independent tools in parallel whenever possible. For example, when reading 3 known files, run 3 tool calls in parallel to read all 3 files into context at the same time. When running multiple independent read-only commands like `ls` or `list_dir`, run all of those commands in parallel. Err on the side of maximizing parallel tool calls for independent work rather than running too many tools sequentially.',
40
+ '',
41
+ 'Do not parallelize tool calls when one call depends on the output of another call. If a tool call needs an ID, filename, search result, or any other value returned by a previous tool call, run the first call, wait for its result, then run the dependent call using the real returned value. Never invent placeholder IDs, guessed task IDs, or other guessed values for dependent tool calls just to place them in the same parallel block.',
42
+ '</use_parallel_tool_calls>',
43
+ ].join('\n');
35
44
  export const CCH_SALT = '59cf53e54c78';
36
45
  export const CCH_POSITIONS = [4, 7, 20];
37
46
  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 DUMP_DIR = join(tmpdir(), 'opencode-anthropic-auth-dumps');
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 DUMP_DIR;
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: ${DUMP_DIR}`,
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.match(/\bcch=([0-9a-f]{5});/)?.[1] ?? null;
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 prefix = join(DUMP_DIR, id);
188
+ const dumpDir = getDumpDirectory();
189
+ const prefix = join(dumpDir, id);
187
190
  try {
188
- await mkdir(DUMP_DIR, { recursive: true });
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-7',
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');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/anthropic-auth-core",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",