@cortexkit/anthropic-auth-core 1.10.3 → 1.11.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/auth.d.ts CHANGED
@@ -1,8 +1,16 @@
1
+ export declare function parseRetryAfterHeader(value: string | undefined | null): number | undefined;
1
2
  export declare class ClaudeOAuthRefreshError extends Error {
2
3
  readonly status: number;
3
4
  readonly body: string;
4
5
  /** Parsed Retry-After value in seconds, if the server provided one. */
5
6
  readonly retryAfter: number | undefined;
7
+ /**
8
+ * Duck-typed marker: any error carrying `isRefreshError: true` arms the
9
+ * refresh backoff in consumers that receive it (recordQuotaRefreshError).
10
+ * Provider-agnostic — shared-core extraction of anthropic-auth and
11
+ * openai-auth relies on this field instead of instanceof.
12
+ */
13
+ readonly isRefreshError = true;
6
14
  constructor(status: number, body: string, retryAfterHeader?: string | null);
7
15
  }
8
16
  export type ClaudeOAuthRefreshResult = {
package/dist/auth.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { AUTHORIZE_URLS, AXIOS_USER_AGENT, CLIENT_ID, CODE_CALLBACK_URL, OAUTH_SCOPES, REFRESH_SCOPE, TOKEN_URL, } from "./constants.js";
2
2
  import { generatePKCE } from "./pkce.js";
3
- function parseRetryAfterHeader(value) {
3
+ export function parseRetryAfterHeader(value) {
4
4
  if (!value)
5
5
  return undefined;
6
6
  const seconds = Number(value);
@@ -18,6 +18,13 @@ export class ClaudeOAuthRefreshError extends Error {
18
18
  body;
19
19
  /** Parsed Retry-After value in seconds, if the server provided one. */
20
20
  retryAfter;
21
+ /**
22
+ * Duck-typed marker: any error carrying `isRefreshError: true` arms the
23
+ * refresh backoff in consumers that receive it (recordQuotaRefreshError).
24
+ * Provider-agnostic — shared-core extraction of anthropic-auth and
25
+ * openai-auth relies on this field instead of instanceof.
26
+ */
27
+ isRefreshError = true;
21
28
  constructor(status, body, retryAfterHeader) {
22
29
  super(`Claude OAuth refresh failed: ${status} — ${body}`);
23
30
  this.status = status;
@@ -21,6 +21,9 @@ export type CacheKeepCommandAction = {
21
21
  endHour: number;
22
22
  } | {
23
23
  type: 'usage';
24
+ } | {
25
+ type: 'subagents';
26
+ enabled: boolean;
24
27
  };
25
28
  export type CacheKeepStatus = {
26
29
  enabled: boolean;
@@ -68,7 +71,6 @@ export declare class CacheKeepManager {
68
71
  fetchImpl?: typeof fetch;
69
72
  now?: () => number;
70
73
  prepareHeaders?: (headers: Headers, target: CacheKeepTarget) => Promise<Headers> | Headers;
71
- log?: (message: string, data?: Record<string, unknown>) => void;
72
74
  });
73
75
  start(): void;
74
76
  stop(): void;
package/dist/cachekeep.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { signRequestBody } from "./cch.js";
2
2
  import { orderClaudeCodeBody } from "./claude-code.js";
3
+ import { logger } from "./logger.js";
3
4
  export const CLAUDE_CACHE_KEEP_COMMAND_NAME = 'claude-cachekeep';
4
5
  export const CACHE_KEEP_TTL_MS = 60 * 60_000;
5
6
  export const CACHE_KEEP_PREWARM_LEAD_MS = 5 * 60_000;
@@ -22,6 +23,10 @@ export function parseCacheKeepCommandAction(input) {
22
23
  return { type: 'status' };
23
24
  if (trimmed === 'off')
24
25
  return { type: 'disable' };
26
+ if (trimmed === 'subagents on')
27
+ return { type: 'subagents', enabled: true };
28
+ if (trimmed === 'subagents off')
29
+ return { type: 'subagents', enabled: false };
25
30
  const match = /^(\d{1,2})-(\d{1,2})$/.exec(trimmed);
26
31
  if (!match)
27
32
  return { type: 'usage' };
@@ -132,6 +137,15 @@ export function executeCacheKeepCommand(input) {
132
137
  }),
133
138
  ].join('\n');
134
139
  }
140
+ if (action.type === 'subagents') {
141
+ return [
142
+ STATUS_TITLE,
143
+ '',
144
+ buildCacheKeepStatusSummary(status),
145
+ '',
146
+ `Subagent tracking: ${action.enabled ? 'enabled' : 'disabled'}`,
147
+ ].join('\n');
148
+ }
135
149
  return [USAGE_TITLE, '', USAGE, '', buildCacheKeepStatusSummary(status)].join('\n');
136
150
  }
137
151
  function hasExplicitCacheControl(value) {
@@ -185,9 +199,10 @@ export class CacheKeepManager {
185
199
  start() {
186
200
  if (this.timer)
187
201
  return;
202
+ logger.debug('cachekeep', 'started');
188
203
  this.timer = setInterval(() => {
189
204
  void this.tick().catch((error) => {
190
- this.options.log?.('[cachekeep] tick failed', {
205
+ logger.warn('cachekeep', 'tick failed', {
191
206
  error: error instanceof Error ? error.message : String(error),
192
207
  });
193
208
  });
@@ -196,8 +211,10 @@ export class CacheKeepManager {
196
211
  this.timer.unref();
197
212
  }
198
213
  stop() {
199
- if (this.timer)
200
- clearInterval(this.timer);
214
+ if (!this.timer)
215
+ return;
216
+ logger.debug('cachekeep', 'stopped');
217
+ clearInterval(this.timer);
201
218
  this.timer = null;
202
219
  }
203
220
  stats(window, now = this.options.now?.() ?? Date.now()) {
@@ -289,6 +306,7 @@ export class CacheKeepManager {
289
306
  return;
290
307
  if (!isWithinCacheKeepWindow(window, new Date(now)))
291
308
  return;
309
+ logger.debug('cachekeep', 'fired', { targets: this.targets.size });
292
310
  const dueAt = now + CACHE_KEEP_PREWARM_LEAD_MS;
293
311
  for (const target of this.targets.values()) {
294
312
  if (target.cacheExpiresAt > dueAt)
@@ -299,7 +317,7 @@ export class CacheKeepManager {
299
317
  async prewarm(target, now) {
300
318
  const prewarm = await buildCacheKeepPrewarmBody(target.bodyText);
301
319
  if (!prewarm.ok) {
302
- this.options.log?.('[cachekeep] prewarm skipped', {
320
+ logger.debug('cachekeep', 'prewarm skipped', {
303
321
  session: target.id,
304
322
  reason: prewarm.reason,
305
323
  });
@@ -319,7 +337,7 @@ export class CacheKeepManager {
319
337
  body: prewarm.bodyText,
320
338
  });
321
339
  if (!response.ok) {
322
- this.options.log?.('[cachekeep] prewarm failed', {
340
+ logger.warn('cachekeep', 'prewarm failed', {
323
341
  session: target.id,
324
342
  status: response.status,
325
343
  body: await response.text().catch(() => ''),
@@ -327,8 +345,17 @@ export class CacheKeepManager {
327
345
  target.cacheExpiresAt = now + CACHE_KEEP_PREWARM_LEAD_MS + 5 * 60_000;
328
346
  return;
329
347
  }
330
- await response.body?.cancel().catch(() => { });
348
+ const data = (await response.json().catch(() => null));
349
+ const usage = data?.usage;
331
350
  target.cacheExpiresAt = now + CACHE_KEEP_TTL_MS;
332
- this.options.log?.('[cachekeep] prewarm succeeded', { session: target.id });
351
+ if (usage) {
352
+ logger.debug('cachekeep', 'prewarm succeeded', {
353
+ session: target.id,
354
+ usage,
355
+ });
356
+ }
357
+ else {
358
+ logger.debug('cachekeep', 'prewarm succeeded', { session: target.id });
359
+ }
333
360
  }
334
361
  }
@@ -0,0 +1,56 @@
1
+ import type { AccountStorage } from '../accounts.ts';
2
+ export declare const CLAUDE_ACCOUNT_COMMAND_NAME = "claude-account";
3
+ export type AccountCommandAction = {
4
+ type: 'status';
5
+ } | {
6
+ type: 'enable';
7
+ id: string;
8
+ } | {
9
+ type: 'disable';
10
+ id: string;
11
+ } | {
12
+ type: 'remove';
13
+ id: string;
14
+ } | {
15
+ type: 'move-up';
16
+ id: string;
17
+ } | {
18
+ type: 'move-down';
19
+ id: string;
20
+ } | {
21
+ type: 'add-apikey';
22
+ apiKey: string;
23
+ label?: string;
24
+ baseURL?: string;
25
+ authHeader?: 'authorization-bearer' | 'x-api-key';
26
+ } | {
27
+ type: 'add-oauth-start';
28
+ } | {
29
+ type: 'add-oauth-finish';
30
+ code: string;
31
+ label?: string;
32
+ } | {
33
+ type: 'usage';
34
+ };
35
+ export declare function parseAccountCommandAction(argumentsText: string): AccountCommandAction;
36
+ export interface AccountListItem {
37
+ id: string;
38
+ label: string;
39
+ role: 'main' | 'fallback';
40
+ enabled: boolean;
41
+ quotaPercent: number | null;
42
+ }
43
+ export declare function buildAccountList(storage: AccountStorage): AccountListItem[];
44
+ export declare function executeAccountCommand(input: {
45
+ argumentsText: string;
46
+ storage: AccountStorage;
47
+ }): {
48
+ text: string;
49
+ updated?: {
50
+ id: string;
51
+ action: 'enable' | 'disable' | 'remove' | 'reorder';
52
+ enabled?: boolean;
53
+ previousOrder?: string[];
54
+ newOrder?: string[];
55
+ };
56
+ };
@@ -0,0 +1,219 @@
1
+ export const CLAUDE_ACCOUNT_COMMAND_NAME = 'claude-account';
2
+ export function parseAccountCommandAction(argumentsText) {
3
+ const parts = argumentsText.trim().split(/\s+/).filter(Boolean);
4
+ if (parts.length === 0)
5
+ return { type: 'status' };
6
+ const action = parts[0];
7
+ const rest = parts.slice(1).join(' ');
8
+ if (action === 'enable' && rest)
9
+ return { type: 'enable', id: rest };
10
+ if (action === 'disable' && rest)
11
+ return { type: 'disable', id: rest };
12
+ if (action === 'remove' && rest)
13
+ return { type: 'remove', id: rest };
14
+ if (action === 'move-up' && rest)
15
+ return { type: 'move-up', id: rest };
16
+ if (action === 'move-down' && rest)
17
+ return { type: 'move-down', id: rest };
18
+ if (action === 'add-apikey' && rest) {
19
+ let remaining = rest;
20
+ let baseURL;
21
+ let authHeader;
22
+ // Parse --base-url flag
23
+ const baseUrlMatch = remaining.match(/--base-url\s+(\S+)/);
24
+ if (baseUrlMatch) {
25
+ baseURL = baseUrlMatch[1];
26
+ remaining = remaining.replace(baseUrlMatch[0], '').trim();
27
+ }
28
+ // Parse --auth-header flag
29
+ const authMatch = remaining.match(/--auth-header\s+(authorization-bearer|x-api-key)/);
30
+ if (authMatch) {
31
+ authHeader = authMatch[1];
32
+ remaining = remaining.replace(authMatch[0], '').trim();
33
+ }
34
+ // Parse --label flag
35
+ let label;
36
+ const labelMatch = remaining.match(/--label\s+(.+)/);
37
+ if (labelMatch) {
38
+ label = labelMatch[1]?.trim() || undefined;
39
+ remaining = remaining.replace(labelMatch[0], '').trim();
40
+ }
41
+ // First remaining token is the API key; rest (if any) is label
42
+ const firstSpace = remaining.indexOf(' ');
43
+ if (firstSpace === -1) {
44
+ if (!remaining)
45
+ return { type: 'usage' };
46
+ return {
47
+ type: 'add-apikey',
48
+ apiKey: remaining,
49
+ baseURL,
50
+ authHeader,
51
+ label,
52
+ };
53
+ }
54
+ const apiKey = remaining.slice(0, firstSpace);
55
+ const tail = remaining.slice(firstSpace + 1).trim();
56
+ return {
57
+ type: 'add-apikey',
58
+ apiKey,
59
+ label: label ?? (tail || undefined),
60
+ baseURL,
61
+ authHeader,
62
+ };
63
+ }
64
+ if (action === 'add-oauth-start')
65
+ return { type: 'add-oauth-start' };
66
+ if (action === 'add-oauth-finish' && rest) {
67
+ let remaining = rest;
68
+ // Parse --label flag (mirrors add-apikey). The OAuth code is opaque (may
69
+ // contain a #state segment) so the label is collected via the flag, never
70
+ // positionally.
71
+ let label;
72
+ const labelMatch = remaining.match(/--label\s+(.+)/);
73
+ if (labelMatch) {
74
+ label = labelMatch[1]?.trim() || undefined;
75
+ remaining = remaining.replace(labelMatch[0], '').trim();
76
+ }
77
+ if (!remaining)
78
+ return { type: 'usage' };
79
+ return { type: 'add-oauth-finish', code: remaining, label };
80
+ }
81
+ return { type: 'usage' };
82
+ }
83
+ export function buildAccountList(storage) {
84
+ const list = [];
85
+ const fiveHour = storage.quota?.mainQuota?.five_hour;
86
+ list.push({
87
+ id: 'main',
88
+ label: storage.main?.provider === 'anthropic' ? 'OpenCode anthropic' : 'Main',
89
+ role: 'main',
90
+ enabled: true,
91
+ quotaPercent: fiveHour?.usedPercent ?? null,
92
+ });
93
+ for (const account of storage.accounts) {
94
+ const fiveHourQuota = account.quota?.five_hour;
95
+ list.push({
96
+ id: account.id,
97
+ label: account.label ?? account.id,
98
+ role: 'fallback',
99
+ enabled: account.enabled !== false,
100
+ quotaPercent: fiveHourQuota?.usedPercent ?? null,
101
+ });
102
+ }
103
+ return list;
104
+ }
105
+ const USAGE_TEXT = [
106
+ 'Usage:',
107
+ ' /claude-account Show account list',
108
+ ' /claude-account enable <id> Enable a fallback account',
109
+ ' /claude-account disable <id> Disable a fallback account',
110
+ ' /claude-account remove <id> Remove a fallback account',
111
+ ' /claude-account move-up <id> Move a fallback account up',
112
+ ' /claude-account move-down <id> Move a fallback account down',
113
+ ' /claude-account add-apikey <key> Add an API key fallback account',
114
+ ' /claude-account add-oauth-start Start OAuth device flow',
115
+ ' /claude-account add-oauth-finish <code> Complete OAuth flow',
116
+ ].join('\n');
117
+ export function executeAccountCommand(input) {
118
+ const action = parseAccountCommandAction(input.argumentsText);
119
+ const accounts = input.storage.accounts;
120
+ const mainId = 'main';
121
+ if (action.type === 'status') {
122
+ const list = buildAccountList(input.storage);
123
+ const lines = ['## Claude Accounts', ''];
124
+ for (const a of list) {
125
+ const pct = a.quotaPercent != null ? ` ${Math.round(a.quotaPercent)}%` : '';
126
+ const status = !a.enabled ? ' (disabled)' : '';
127
+ lines.push(`- **${a.label}** [${a.role}]${status}${pct}`);
128
+ }
129
+ lines.push('', USAGE_TEXT);
130
+ return { text: lines.join('\n') };
131
+ }
132
+ if (action.type === 'usage') {
133
+ return { text: USAGE_TEXT };
134
+ }
135
+ if (action.type === 'add-apikey') {
136
+ return { text: 'add-apikey' };
137
+ }
138
+ if (action.type === 'add-oauth-start') {
139
+ return { text: 'add-oauth-start' };
140
+ }
141
+ if (action.type === 'add-oauth-finish') {
142
+ return { text: 'add-oauth-finish' };
143
+ }
144
+ const id = action.id;
145
+ if (id === mainId) {
146
+ return {
147
+ text: `Cannot ${action.type} the main account (OpenCode managed).`,
148
+ };
149
+ }
150
+ if (action.type === 'enable' || action.type === 'disable') {
151
+ const target = accounts.find((a) => a.id === id);
152
+ if (!target) {
153
+ return { text: `Account "${id}" not found.` };
154
+ }
155
+ return {
156
+ text: `Account "${target.label ?? id}" ${action.type}d.`,
157
+ updated: {
158
+ id,
159
+ action: action.type,
160
+ enabled: action.type === 'enable',
161
+ },
162
+ };
163
+ }
164
+ if (action.type === 'remove') {
165
+ const target = accounts.find((a) => a.id === id);
166
+ if (!target) {
167
+ return { text: `Account "${id}" not found.` };
168
+ }
169
+ return {
170
+ text: `Account "${target.label ?? id}" removed.`,
171
+ updated: { id, action: 'remove' },
172
+ };
173
+ }
174
+ if (action.type === 'move-up') {
175
+ const idx = accounts.findIndex((a) => a.id === id);
176
+ if (idx < 0) {
177
+ return { text: `Account "${id}" not found.` };
178
+ }
179
+ if (idx === 0) {
180
+ return {
181
+ text: `Account "${accounts[idx]?.label ?? id}" is already first.`,
182
+ };
183
+ }
184
+ const previousOrder = accounts.map((a) => a.id);
185
+ const newAccounts = [...accounts];
186
+ [newAccounts[idx - 1], newAccounts[idx]] = [
187
+ newAccounts[idx],
188
+ newAccounts[idx - 1],
189
+ ];
190
+ const newOrder = newAccounts.map((a) => a.id);
191
+ return {
192
+ text: `Account "${accounts[idx]?.label ?? id}" moved up.`,
193
+ updated: { id, action: 'reorder', previousOrder, newOrder },
194
+ };
195
+ }
196
+ if (action.type === 'move-down') {
197
+ const idx = accounts.findIndex((a) => a.id === id);
198
+ if (idx < 0) {
199
+ return { text: `Account "${id}" not found.` };
200
+ }
201
+ if (idx === accounts.length - 1) {
202
+ return {
203
+ text: `Account "${accounts[idx]?.label ?? id}" is already last.`,
204
+ };
205
+ }
206
+ const previousOrder = accounts.map((a) => a.id);
207
+ const newAccounts = [...accounts];
208
+ [newAccounts[idx], newAccounts[idx + 1]] = [
209
+ newAccounts[idx + 1],
210
+ newAccounts[idx],
211
+ ];
212
+ const newOrder = newAccounts.map((a) => a.id);
213
+ return {
214
+ text: `Account "${accounts[idx]?.label ?? id}" moved down.`,
215
+ updated: { id, action: 'reorder', previousOrder, newOrder },
216
+ };
217
+ }
218
+ return { text: USAGE_TEXT };
219
+ }
package/dist/dump.js CHANGED
@@ -3,7 +3,7 @@ import { mkdir, writeFile } from 'node:fs/promises';
3
3
  import { tmpdir } from 'node:os';
4
4
  import { join } from 'node:path';
5
5
  import { extractBillingHeaderCCH } from "./cch.js";
6
- import { relayLog } from "./logger.js";
6
+ import { isSecretKey, relayLog } from "./logger.js";
7
7
  export const CLAUDE_DUMP_COMMAND_NAME = 'claude-dump';
8
8
  const DUMP_STATUS_TITLE = '## Claude Dump Status';
9
9
  const DUMP_ENABLED_TITLE = '## Claude Dump Enabled';
@@ -214,7 +214,8 @@ function redactForDump(value) {
214
214
  if (lower === 'authorization' ||
215
215
  lower === 'x-api-key' ||
216
216
  lower === 'cookie' ||
217
- lower === 'set-cookie') {
217
+ lower === 'set-cookie' ||
218
+ isSecretKey(key)) {
218
219
  redacted[key] = '[redacted]';
219
220
  continue;
220
221
  }
package/dist/index.d.ts CHANGED
@@ -4,13 +4,16 @@ export * from './cache1h.ts';
4
4
  export * from './cachekeep.ts';
5
5
  export * from './cch.ts';
6
6
  export * from './claude-code.ts';
7
+ export * from './commands/account.ts';
7
8
  export * from './constants.ts';
8
9
  export * from './dump.ts';
9
10
  export * from './fast.ts';
10
11
  export * from './killswitch.ts';
11
12
  export * from './logger.ts';
13
+ export * from './logging.ts';
12
14
  export * from './models.ts';
13
15
  export * from './pkce.ts';
16
+ export * from './provider.ts';
14
17
  export * from './quota-manager.ts';
15
18
  export * from './quotas.ts';
16
19
  export * from './relay.ts';
package/dist/index.js CHANGED
@@ -4,13 +4,16 @@ export * from "./cache1h.js";
4
4
  export * from "./cachekeep.js";
5
5
  export * from "./cch.js";
6
6
  export * from "./claude-code.js";
7
+ export * from "./commands/account.js";
7
8
  export * from "./constants.js";
8
9
  export * from "./dump.js";
9
10
  export * from "./fast.js";
10
11
  export * from "./killswitch.js";
11
12
  export * from "./logger.js";
13
+ export * from "./logging.js";
12
14
  export * from "./models.js";
13
15
  export * from "./pkce.js";
16
+ export * from "./provider.js";
14
17
  export * from "./quota-manager.js";
15
18
  export * from "./quotas.js";
16
19
  export * from "./relay.js";
package/dist/logger.d.ts CHANGED
@@ -1,3 +1,27 @@
1
+ export type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace';
2
+ declare function parseLogLevel(value: string | undefined): LogLevel | null;
3
+ export declare function setLogLevel(level: LogLevel): void;
4
+ export declare function getLogLevel(): LogLevel;
5
+ export { parseLogLevel };
6
+ export declare function shouldEmit(level: LogLevel): boolean;
7
+ export declare function getLogFilePath(): string;
8
+ export declare const SECRET_KEY_EXACT: RegExp;
9
+ export declare function isSecretKey(key: string): boolean;
10
+ export declare function redactPayload(payload?: Record<string, unknown>): Record<string, unknown> | undefined;
11
+ export declare function formatLogLine(level: LogLevel, channel: string, message: string, payload?: Record<string, unknown>): string;
12
+ export type LogTestRecord = {
13
+ level: LogLevel;
14
+ channel: string;
15
+ message: string;
16
+ payload?: Record<string, unknown>;
17
+ };
18
+ export declare function __setLogTestSink(sink: ((record: LogTestRecord) => void) | null): void;
19
+ export declare const logger: {
20
+ error(channel: string, message: string, payload?: Record<string, unknown>): void;
21
+ warn(channel: string, message: string, payload?: Record<string, unknown>): void;
22
+ info(channel: string, message: string, payload?: Record<string, unknown>): void;
23
+ debug(channel: string, message: string, payload?: Record<string, unknown>): void;
24
+ trace(channel: string, message: string, payload?: Record<string, unknown>): void;
25
+ };
1
26
  export declare function log(message: string, data?: unknown): void;
2
27
  export declare function relayLog(message: string, data?: unknown): void;
3
- export declare function getLogFilePath(): string;