@cortexkit/anthropic-auth-core 1.9.2 → 1.9.4
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.js +79 -8
- package/dist/auth.js +5 -4
- package/dist/claude-code.d.ts +1 -1
- package/dist/claude-code.js +6 -3
- package/dist/constants.d.ts +8 -6
- package/dist/constants.js +8 -6
- package/dist/dump.d.ts +12 -0
- package/dist/dump.js +98 -15
- package/dist/models.d.ts +1 -0
- package/dist/models.js +19 -0
- package/dist/quota-manager.d.ts +11 -3
- package/dist/quota-manager.js +53 -22
- package/package.json +1 -1
package/dist/accounts.js
CHANGED
|
@@ -289,6 +289,46 @@ function accountRuntimeState(account) {
|
|
|
289
289
|
quota: account.quota,
|
|
290
290
|
});
|
|
291
291
|
}
|
|
292
|
+
function quotaSnapshotCheckedAt(quota) {
|
|
293
|
+
return Math.max(quota?.five_hour?.checkedAt ?? 0, quota?.seven_day?.checkedAt ?? 0);
|
|
294
|
+
}
|
|
295
|
+
function mergeAccountRuntimeState(existing, incoming) {
|
|
296
|
+
if (!isRecord(existing))
|
|
297
|
+
return incoming;
|
|
298
|
+
const existingEntry = existing;
|
|
299
|
+
const existingQuotaCheckedAt = quotaSnapshotCheckedAt(existingEntry.quota);
|
|
300
|
+
const incomingQuotaCheckedAt = quotaSnapshotCheckedAt(incoming.quota);
|
|
301
|
+
if (existingQuotaCheckedAt > incomingQuotaCheckedAt) {
|
|
302
|
+
const tokenChanged = Boolean(existingEntry.access &&
|
|
303
|
+
incoming.access &&
|
|
304
|
+
existingEntry.access !== incoming.access);
|
|
305
|
+
const existingRefreshAt = existingEntry.lastRefreshedAt ?? 0;
|
|
306
|
+
const incomingRefreshAt = incoming.lastRefreshedAt ?? 0;
|
|
307
|
+
if (tokenChanged && incomingRefreshAt <= existingRefreshAt) {
|
|
308
|
+
const merged = { ...existingEntry };
|
|
309
|
+
if (typeof incoming.lastUsed === 'number' &&
|
|
310
|
+
(!(typeof existingEntry.lastUsed === 'number') ||
|
|
311
|
+
incoming.lastUsed > existingEntry.lastUsed)) {
|
|
312
|
+
merged.lastUsed = incoming.lastUsed;
|
|
313
|
+
}
|
|
314
|
+
return merged;
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
...existingEntry,
|
|
318
|
+
...incoming,
|
|
319
|
+
quota: existingEntry.quota,
|
|
320
|
+
lastQuotaRefreshError: existingEntry.lastQuotaRefreshError,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
const merged = { ...existingEntry, ...incoming };
|
|
324
|
+
if (!('lastQuotaRefreshError' in incoming)) {
|
|
325
|
+
delete merged.lastQuotaRefreshError;
|
|
326
|
+
}
|
|
327
|
+
if (!('lastRefreshError' in incoming)) {
|
|
328
|
+
delete merged.lastRefreshError;
|
|
329
|
+
}
|
|
330
|
+
return merged;
|
|
331
|
+
}
|
|
292
332
|
function configFromStorage(storage) {
|
|
293
333
|
const refresh = storage.refresh
|
|
294
334
|
? objectWithDefinedEntries({
|
|
@@ -361,6 +401,14 @@ export async function saveAccounts(storage, path = getAccountStoragePath()) {
|
|
|
361
401
|
}
|
|
362
402
|
function applyMainQuotaStatePatch(state, storage) {
|
|
363
403
|
state.main = state.main ?? {};
|
|
404
|
+
const existingCheckedAt = typeof state.main.quotaCheckedAt === 'number'
|
|
405
|
+
? state.main.quotaCheckedAt
|
|
406
|
+
: quotaSnapshotCheckedAt(state.main.quota);
|
|
407
|
+
const incomingCheckedAt = typeof storage.quota?.mainQuotaCheckedAt === 'number'
|
|
408
|
+
? storage.quota.mainQuotaCheckedAt
|
|
409
|
+
: quotaSnapshotCheckedAt(storage.quota?.mainQuota);
|
|
410
|
+
if (existingCheckedAt > incomingCheckedAt)
|
|
411
|
+
return;
|
|
364
412
|
state.main.quota = storage.quota?.mainQuota;
|
|
365
413
|
state.main.quotaCheckedAt = storage.quota?.mainQuotaCheckedAt;
|
|
366
414
|
state.main.quotaToken = storage.quota?.mainQuotaToken;
|
|
@@ -402,7 +450,7 @@ export async function saveAccountState(storage, path = getAccountStoragePath(),
|
|
|
402
450
|
for (const account of storage.accounts) {
|
|
403
451
|
if (ids && !ids.has(account.id))
|
|
404
452
|
continue;
|
|
405
|
-
next.accounts[account.id] = accountRuntimeState(account);
|
|
453
|
+
next.accounts[account.id] = mergeAccountRuntimeState(next.accounts[account.id], accountRuntimeState(account));
|
|
406
454
|
}
|
|
407
455
|
if (ids) {
|
|
408
456
|
for (const id of ids) {
|
|
@@ -876,15 +924,18 @@ function tokenNeedsRefresh(account, storage, now) {
|
|
|
876
924
|
!account.expires ||
|
|
877
925
|
account.expires - now <= refreshBeforeExpiryMs(storage));
|
|
878
926
|
}
|
|
879
|
-
function
|
|
927
|
+
function quotaSnapshotIsFresh(quota, storage, now) {
|
|
880
928
|
if (!quotaEnabled(storage))
|
|
881
|
-
return
|
|
929
|
+
return true;
|
|
882
930
|
const maxAge = getQuotaCheckIntervalMs(storage);
|
|
883
|
-
return ['five_hour', 'seven_day'].
|
|
884
|
-
const window =
|
|
885
|
-
return
|
|
931
|
+
return ['five_hour', 'seven_day'].every((key) => {
|
|
932
|
+
const window = quota?.[key];
|
|
933
|
+
return Boolean(window && now - window.checkedAt < maxAge);
|
|
886
934
|
});
|
|
887
935
|
}
|
|
936
|
+
function quotaIsStale(account, storage, now) {
|
|
937
|
+
return !quotaSnapshotIsFresh(account.quota, storage, now);
|
|
938
|
+
}
|
|
888
939
|
function cachedQuotaWindowStillRelevant(window, now) {
|
|
889
940
|
if (!window)
|
|
890
941
|
return false;
|
|
@@ -1012,13 +1063,14 @@ export class FallbackAccountManager {
|
|
|
1012
1063
|
seedFallbackQuota(account, storage) {
|
|
1013
1064
|
if (!this.quotaManager)
|
|
1014
1065
|
return;
|
|
1015
|
-
if (this.quotaManager.getFallback(account.id, account.access))
|
|
1016
|
-
return;
|
|
1017
1066
|
if (!account.quota)
|
|
1018
1067
|
return;
|
|
1019
1068
|
const checkedAt = Math.max(account.quota.five_hour?.checkedAt ?? 0, account.quota.seven_day?.checkedAt ?? 0);
|
|
1020
1069
|
if (checkedAt <= 0)
|
|
1021
1070
|
return;
|
|
1071
|
+
const existing = this.quotaManager.getFallback(account.id, account.access);
|
|
1072
|
+
if (existing && existing.checkedAt >= checkedAt)
|
|
1073
|
+
return;
|
|
1022
1074
|
const checkInterval = getQuotaCheckIntervalMs(storage);
|
|
1023
1075
|
this.quotaManager.setFallback(account.id, {
|
|
1024
1076
|
quota: account.quota,
|
|
@@ -1402,6 +1454,7 @@ export class FallbackAccountManager {
|
|
|
1402
1454
|
fetchImpl: this.fetchImpl,
|
|
1403
1455
|
now: this.now,
|
|
1404
1456
|
});
|
|
1457
|
+
const fetchStartedAt = this.now();
|
|
1405
1458
|
try {
|
|
1406
1459
|
target.quota = await fetchSnapshot(target.access);
|
|
1407
1460
|
}
|
|
@@ -1417,6 +1470,24 @@ export class FallbackAccountManager {
|
|
|
1417
1470
|
// 401 does not arm QuotaManager backoff, so this retry proceeds.
|
|
1418
1471
|
target.quota = await fetchSnapshot(target.access);
|
|
1419
1472
|
}
|
|
1473
|
+
const latestStorage = await this.load();
|
|
1474
|
+
const latestAccount = latestStorage?.accounts.find((candidate) => candidate.id === target.id && isOAuthAccount(candidate));
|
|
1475
|
+
if (latestStorage &&
|
|
1476
|
+
latestAccount &&
|
|
1477
|
+
latestAccount.access !== target.access) {
|
|
1478
|
+
this.seedFallbackQuota(latestAccount, latestStorage);
|
|
1479
|
+
updateStoredAccount(storage, latestAccount);
|
|
1480
|
+
return latestAccount;
|
|
1481
|
+
}
|
|
1482
|
+
if (latestStorage &&
|
|
1483
|
+
latestAccount?.access === target.access &&
|
|
1484
|
+
latestAccount.quota &&
|
|
1485
|
+
quotaSnapshotCheckedAt(latestAccount.quota) >= fetchStartedAt &&
|
|
1486
|
+
quotaSnapshotIsFresh(latestAccount.quota, latestStorage, this.now())) {
|
|
1487
|
+
this.seedFallbackQuota(latestAccount, latestStorage);
|
|
1488
|
+
updateStoredAccount(storage, latestAccount);
|
|
1489
|
+
return latestAccount;
|
|
1490
|
+
}
|
|
1420
1491
|
target.lastQuotaRefreshError = undefined;
|
|
1421
1492
|
updateStoredAccount(storage, target);
|
|
1422
1493
|
// Sync to shared QuotaManager so all consumers see the same cache. The
|
package/dist/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AUTHORIZE_URLS, CLIENT_ID, CODE_CALLBACK_URL, OAUTH_SCOPES, TOKEN_URL, } from "./constants.js";
|
|
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
3
|
function parseRetryAfterHeader(value) {
|
|
4
4
|
if (!value)
|
|
@@ -50,13 +50,14 @@ export async function refreshClaudeOAuthToken(input) {
|
|
|
50
50
|
method: 'POST',
|
|
51
51
|
headers: {
|
|
52
52
|
'Content-Type': 'application/json',
|
|
53
|
-
Accept: 'application/json',
|
|
54
|
-
'User-Agent':
|
|
53
|
+
Accept: 'application/json, text/plain, */*',
|
|
54
|
+
'User-Agent': AXIOS_USER_AGENT,
|
|
55
55
|
},
|
|
56
56
|
body: JSON.stringify({
|
|
57
57
|
grant_type: 'refresh_token',
|
|
58
58
|
refresh_token: input.refreshToken,
|
|
59
59
|
client_id: CLIENT_ID,
|
|
60
|
+
scope: REFRESH_SCOPE,
|
|
60
61
|
}),
|
|
61
62
|
});
|
|
62
63
|
if (!response.ok) {
|
|
@@ -120,7 +121,7 @@ async function exchangeCode(callback, verifier, redirectUri) {
|
|
|
120
121
|
headers: {
|
|
121
122
|
'Content-Type': 'application/json',
|
|
122
123
|
Accept: 'application/json, text/plain, */*',
|
|
123
|
-
'User-Agent':
|
|
124
|
+
'User-Agent': AXIOS_USER_AGENT,
|
|
124
125
|
},
|
|
125
126
|
body: JSON.stringify({
|
|
126
127
|
code: callback.code,
|
package/dist/claude-code.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export declare function getClaudeCodeIdentity(seed: string): ClaudeCodeIdentity;
|
|
|
7
7
|
export declare function resolveClaudeCodeIdentity(accessToken: string, model?: string): Promise<ClaudeCodeIdentity>;
|
|
8
8
|
export declare function buildClaudeCodeMetadataUserId(identity: ClaudeCodeIdentity): string | null;
|
|
9
9
|
export declare function applyClaudeCodeMetadata(body: Record<string, unknown>, identity: ClaudeCodeIdentity): boolean;
|
|
10
|
-
export declare const CLAUDE_CODE_FULL_AGENT_BETAS: readonly ["
|
|
10
|
+
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"];
|
|
11
11
|
export declare function selectClaudeCodeBetas(body?: Record<string, unknown> | null, extraBetas?: string[]): string;
|
|
12
12
|
export declare function applyClaudeCodeHeaders(headers: Headers, accessToken: string, options?: {
|
|
13
13
|
body?: Record<string, unknown> | null;
|
package/dist/claude-code.js
CHANGED
|
@@ -130,21 +130,21 @@ export function applyClaudeCodeMetadata(body, identity) {
|
|
|
130
130
|
return true;
|
|
131
131
|
}
|
|
132
132
|
export const CLAUDE_CODE_FULL_AGENT_BETAS = [
|
|
133
|
-
'claude-code-20250219',
|
|
134
133
|
'oauth-2025-04-20',
|
|
135
134
|
'interleaved-thinking-2025-05-14',
|
|
135
|
+
'thinking-token-count-2026-05-13',
|
|
136
136
|
'context-management-2025-06-27',
|
|
137
137
|
'prompt-caching-scope-2026-01-05',
|
|
138
|
+
'claude-code-20250219',
|
|
138
139
|
'advisor-tool-2026-03-01',
|
|
139
140
|
'advanced-tool-use-2025-11-20',
|
|
140
|
-
'context-1m-2025-08-07',
|
|
141
|
-
'effort-2025-11-24',
|
|
142
141
|
'extended-cache-ttl-2025-04-11',
|
|
143
142
|
'cache-diagnosis-2026-04-07',
|
|
144
143
|
];
|
|
145
144
|
const CLAUDE_CODE_STRUCTURED_OUTPUT_BETAS = [
|
|
146
145
|
'oauth-2025-04-20',
|
|
147
146
|
'interleaved-thinking-2025-05-14',
|
|
147
|
+
'thinking-token-count-2026-05-13',
|
|
148
148
|
'context-management-2025-06-27',
|
|
149
149
|
'prompt-caching-scope-2026-01-05',
|
|
150
150
|
'advisor-tool-2026-03-01',
|
|
@@ -154,9 +154,12 @@ const CLAUDE_CODE_STRUCTURED_OUTPUT_BETAS = [
|
|
|
154
154
|
const CLAUDE_CODE_BASE_BETAS = [
|
|
155
155
|
'oauth-2025-04-20',
|
|
156
156
|
'interleaved-thinking-2025-05-14',
|
|
157
|
+
'thinking-token-count-2026-05-13',
|
|
157
158
|
'context-management-2025-06-27',
|
|
158
159
|
'prompt-caching-scope-2026-01-05',
|
|
159
160
|
'advisor-tool-2026-03-01',
|
|
161
|
+
'advanced-tool-use-2025-11-20',
|
|
162
|
+
'extended-cache-ttl-2025-04-11',
|
|
160
163
|
'cache-diagnosis-2026-04-07',
|
|
161
164
|
];
|
|
162
165
|
function hasStructuredOutput(body) {
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
export declare const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
2
2
|
export declare const AUTHORIZE_URLS: {
|
|
3
3
|
readonly console: "https://platform.claude.com/oauth/authorize";
|
|
4
|
-
readonly max: "https://claude.
|
|
4
|
+
readonly max: "https://claude.com/cai/oauth/authorize";
|
|
5
5
|
};
|
|
6
6
|
export declare const CODE_CALLBACK_URL = "https://platform.claude.com/oauth/code/callback";
|
|
7
7
|
export declare const TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
|
|
8
|
+
export declare const AXIOS_USER_AGENT = "axios/1.15.2";
|
|
8
9
|
export declare const OAUTH_SCOPES: string[];
|
|
10
|
+
export declare const REFRESH_SCOPE = "user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload";
|
|
9
11
|
export declare const TOOL_PREFIX = "mcp_";
|
|
10
12
|
export declare const REQUIRED_BETAS: string[];
|
|
11
13
|
export declare const FAST_MODE_BETA = "fast-mode-2026-02-01";
|
|
12
14
|
export declare function mergeAnthropicBetas(existing: string | null | undefined, betas: string[]): string;
|
|
13
15
|
export declare function isFastModeSupportedModel(model: unknown): boolean;
|
|
14
16
|
export declare const OPENCODE_IDENTITY_PREFIX = "You are OpenCode";
|
|
15
|
-
export declare const CLAUDE_CODE_IDENTITY = "You are
|
|
17
|
+
export declare const CLAUDE_CODE_IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude.";
|
|
16
18
|
export declare const PARALLEL_TOOL_CALLS_SYSTEM_PROMPT: string;
|
|
17
19
|
export declare const CCH_SALT = "59cf53e54c78";
|
|
18
20
|
export declare const CCH_POSITIONS: number[];
|
|
19
|
-
export declare const CLAUDE_CODE_VERSION = "2.1.
|
|
20
|
-
export declare const CLAUDE_CODE_BUILD_HASH = "
|
|
21
|
-
export declare const CLAUDE_CODE_ENTRYPOINT = "
|
|
21
|
+
export declare const CLAUDE_CODE_VERSION = "2.1.177";
|
|
22
|
+
export declare const CLAUDE_CODE_BUILD_HASH = "3bf";
|
|
23
|
+
export declare const CLAUDE_CODE_ENTRYPOINT = "cli";
|
|
22
24
|
export declare const CLAUDE_CODE_STAINLESS_PACKAGE_VERSION = "0.94.0";
|
|
23
25
|
export declare const CLAUDE_CODE_STAINLESS_RUNTIME_VERSION = "v24.3.0";
|
|
24
|
-
export declare const USER_AGENT = "claude-cli/2.1.
|
|
26
|
+
export declare const USER_AGENT = "claude-cli/2.1.177 (external, cli)";
|
|
25
27
|
export declare const CACHE_1H_MODES: readonly ["explicit", "automatic", "hybrid"];
|
|
26
28
|
export type Cache1hMode = (typeof CACHE_1H_MODES)[number];
|
|
27
29
|
export declare const DEFAULT_CACHE_1H_MODE: Cache1hMode;
|
package/dist/constants.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export const CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
|
|
2
2
|
export const AUTHORIZE_URLS = {
|
|
3
3
|
console: 'https://platform.claude.com/oauth/authorize',
|
|
4
|
-
max: 'https://claude.
|
|
4
|
+
max: 'https://claude.com/cai/oauth/authorize',
|
|
5
5
|
};
|
|
6
6
|
export const CODE_CALLBACK_URL = 'https://platform.claude.com/oauth/code/callback';
|
|
7
7
|
export const TOKEN_URL = 'https://platform.claude.com/v1/oauth/token';
|
|
8
|
+
export const AXIOS_USER_AGENT = 'axios/1.15.2';
|
|
8
9
|
export const OAUTH_SCOPES = [
|
|
9
10
|
'org:create_api_key',
|
|
10
11
|
'user:profile',
|
|
@@ -13,6 +14,7 @@ export const OAUTH_SCOPES = [
|
|
|
13
14
|
'user:mcp_servers',
|
|
14
15
|
'user:file_upload',
|
|
15
16
|
];
|
|
17
|
+
export const REFRESH_SCOPE = 'user:profile user:inference user:sessions:claude_code user:mcp_servers user:file_upload';
|
|
16
18
|
export const TOOL_PREFIX = 'mcp_';
|
|
17
19
|
export const REQUIRED_BETAS = [
|
|
18
20
|
'oauth-2025-04-20',
|
|
@@ -33,7 +35,7 @@ export function isFastModeSupportedModel(model) {
|
|
|
33
35
|
model.startsWith('claude-opus-4-8')));
|
|
34
36
|
}
|
|
35
37
|
export const OPENCODE_IDENTITY_PREFIX = 'You are OpenCode';
|
|
36
|
-
export const CLAUDE_CODE_IDENTITY = "You are
|
|
38
|
+
export const CLAUDE_CODE_IDENTITY = "You are Claude Code, Anthropic's official CLI for Claude.";
|
|
37
39
|
export const PARALLEL_TOOL_CALLS_SYSTEM_PROMPT = [
|
|
38
40
|
'<use_parallel_tool_calls>',
|
|
39
41
|
'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.',
|
|
@@ -43,12 +45,12 @@ export const PARALLEL_TOOL_CALLS_SYSTEM_PROMPT = [
|
|
|
43
45
|
].join('\n');
|
|
44
46
|
export const CCH_SALT = '59cf53e54c78';
|
|
45
47
|
export const CCH_POSITIONS = [4, 7, 20];
|
|
46
|
-
export const CLAUDE_CODE_VERSION = '2.1.
|
|
47
|
-
export const CLAUDE_CODE_BUILD_HASH = '
|
|
48
|
-
export const CLAUDE_CODE_ENTRYPOINT = '
|
|
48
|
+
export const CLAUDE_CODE_VERSION = '2.1.177';
|
|
49
|
+
export const CLAUDE_CODE_BUILD_HASH = '3bf';
|
|
50
|
+
export const CLAUDE_CODE_ENTRYPOINT = 'cli';
|
|
49
51
|
export const CLAUDE_CODE_STAINLESS_PACKAGE_VERSION = '0.94.0';
|
|
50
52
|
export const CLAUDE_CODE_STAINLESS_RUNTIME_VERSION = 'v24.3.0';
|
|
51
|
-
export const USER_AGENT = 'claude-cli/2.1.
|
|
53
|
+
export const USER_AGENT = 'claude-cli/2.1.177 (external, cli)';
|
|
52
54
|
export const CACHE_1H_MODES = ['explicit', 'automatic', 'hybrid'];
|
|
53
55
|
export const DEFAULT_CACHE_1H_MODE = 'explicit';
|
|
54
56
|
/**
|
package/dist/dump.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
type DumpHeaders = ConstructorParameters<typeof Headers>[0];
|
|
1
2
|
export declare const CLAUDE_DUMP_COMMAND_NAME = "claude-dump";
|
|
2
3
|
export type DumpCommandAction = {
|
|
3
4
|
type: 'status';
|
|
@@ -20,6 +21,16 @@ export declare function executeDumpCommand(input: {
|
|
|
20
21
|
argumentsText: string;
|
|
21
22
|
enabled?: boolean;
|
|
22
23
|
}): string;
|
|
24
|
+
export declare function dumpDirectRequest(input: {
|
|
25
|
+
affinity?: string | null;
|
|
26
|
+
route?: string;
|
|
27
|
+
status?: number;
|
|
28
|
+
error?: string;
|
|
29
|
+
bodyText: string;
|
|
30
|
+
url?: string;
|
|
31
|
+
method?: string;
|
|
32
|
+
headers?: DumpHeaders;
|
|
33
|
+
}): Promise<void>;
|
|
23
34
|
export declare function dumpRelayRequest(input: {
|
|
24
35
|
affinity: string;
|
|
25
36
|
transport: 'http' | 'websocket';
|
|
@@ -31,3 +42,4 @@ export declare function dumpRelayRequest(input: {
|
|
|
31
42
|
payload: unknown;
|
|
32
43
|
relayBytes: number;
|
|
33
44
|
}): Promise<void>;
|
|
45
|
+
export {};
|
package/dist/dump.js
CHANGED
|
@@ -14,6 +14,8 @@ const DUMP_DIR_ENV = 'OPENCODE_ANTHROPIC_AUTH_DUMP_DIR';
|
|
|
14
14
|
const DEFAULT_DUMP_DIR = join(tmpdir(), 'opencode-anthropic-auth-dumps');
|
|
15
15
|
let dumpEnabled = false;
|
|
16
16
|
let nextDumpId = 0;
|
|
17
|
+
const DIRECT_DUMP_PREVIOUS_BODY_LIMIT = 100;
|
|
18
|
+
const directDumpPreviousBodies = new Map();
|
|
17
19
|
export function isDumpEnabled() {
|
|
18
20
|
return dumpEnabled;
|
|
19
21
|
}
|
|
@@ -22,6 +24,8 @@ export function setDumpEnabled(enabled) {
|
|
|
22
24
|
}
|
|
23
25
|
export function resetDumpState() {
|
|
24
26
|
dumpEnabled = false;
|
|
27
|
+
nextDumpId = 0;
|
|
28
|
+
directDumpPreviousBodies.clear();
|
|
25
29
|
}
|
|
26
30
|
export function getDumpDirectory() {
|
|
27
31
|
return process.env[DUMP_DIR_ENV] || DEFAULT_DUMP_DIR;
|
|
@@ -44,7 +48,7 @@ export function buildDumpStatusSummary(input) {
|
|
|
44
48
|
`- Enabled: ${enabled ? 'enabled' : 'disabled'}`,
|
|
45
49
|
`- Directory: ${getDumpDirectory()}`,
|
|
46
50
|
'- Persisted: ~/.config/opencode/anthropic-auth.json',
|
|
47
|
-
'- Captures: final rewritten Anthropic body
|
|
51
|
+
'- Captures: final rewritten Anthropic body in direct and relay modes; request/relay metadata is redacted',
|
|
48
52
|
'- Warning: body dumps may contain prompt/session content; turn this off after debugging',
|
|
49
53
|
].join('\n');
|
|
50
54
|
}
|
|
@@ -87,6 +91,35 @@ function dumpFileSessionSegment(affinity) {
|
|
|
87
91
|
return 'session-unknown';
|
|
88
92
|
return normalized.length <= 80 ? normalized : normalized.slice(0, 80);
|
|
89
93
|
}
|
|
94
|
+
function dumpRequestSegment(input) {
|
|
95
|
+
if (input.transport !== 'direct') {
|
|
96
|
+
return `${input.transport}-p${input.protocol}-${input.mode}`;
|
|
97
|
+
}
|
|
98
|
+
const route = input.route ? `-${dumpFileSessionSegment(input.route)}` : '';
|
|
99
|
+
return `direct${route}`;
|
|
100
|
+
}
|
|
101
|
+
function directDumpPreviousKey(input) {
|
|
102
|
+
const affinity = input.affinity?.trim();
|
|
103
|
+
if (affinity)
|
|
104
|
+
return `session:${affinity}`;
|
|
105
|
+
return `request:${input.route ?? 'direct'}:${input.url ?? ''}`;
|
|
106
|
+
}
|
|
107
|
+
function rememberDirectDumpBody(key, bodyText) {
|
|
108
|
+
if (!directDumpPreviousBodies.has(key)) {
|
|
109
|
+
while (directDumpPreviousBodies.size >= DIRECT_DUMP_PREVIOUS_BODY_LIMIT) {
|
|
110
|
+
const oldest = directDumpPreviousBodies.keys().next().value;
|
|
111
|
+
if (oldest === undefined)
|
|
112
|
+
break;
|
|
113
|
+
directDumpPreviousBodies.delete(oldest);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
directDumpPreviousBodies.set(key, bodyText);
|
|
117
|
+
}
|
|
118
|
+
function headersToRecord(headers) {
|
|
119
|
+
if (headers == null)
|
|
120
|
+
return undefined;
|
|
121
|
+
return Object.fromEntries(new Headers(headers).entries());
|
|
122
|
+
}
|
|
90
123
|
function hashText(value) {
|
|
91
124
|
return createHash('sha256').update(value).digest('hex');
|
|
92
125
|
}
|
|
@@ -189,42 +222,92 @@ function redactForDump(value) {
|
|
|
189
222
|
}
|
|
190
223
|
return redacted;
|
|
191
224
|
}
|
|
192
|
-
|
|
225
|
+
async function dumpRequest(input) {
|
|
193
226
|
if (!dumpEnabled)
|
|
194
227
|
return;
|
|
195
228
|
nextDumpId += 1;
|
|
196
|
-
const
|
|
229
|
+
const affinity = input.affinity?.trim() || 'session-unknown';
|
|
230
|
+
const id = `${new Date().toISOString().replace(/[:.]/g, '-')}-${String(nextDumpId).padStart(5, '0')}-${dumpFileSessionSegment(affinity)}-${dumpRequestSegment(input)}`;
|
|
197
231
|
const dumpDir = getDumpDirectory();
|
|
198
232
|
const prefix = join(dumpDir, id);
|
|
233
|
+
const files = {
|
|
234
|
+
body: `${prefix}.body.json`,
|
|
235
|
+
metadata: `${prefix}.meta.json`,
|
|
236
|
+
};
|
|
237
|
+
if (input.payload !== undefined)
|
|
238
|
+
files.relay = `${prefix}.relay.json`;
|
|
239
|
+
if (input.request !== undefined)
|
|
240
|
+
files.request = `${prefix}.request.json`;
|
|
199
241
|
try {
|
|
200
242
|
await mkdir(dumpDir, { recursive: true });
|
|
201
243
|
const metadata = {
|
|
202
244
|
id,
|
|
203
245
|
createdAt: new Date().toISOString(),
|
|
204
|
-
session: shortAffinity(
|
|
246
|
+
session: shortAffinity(affinity),
|
|
205
247
|
transport: input.transport,
|
|
206
248
|
protocol: input.protocol,
|
|
207
249
|
mode: input.mode,
|
|
250
|
+
route: input.route,
|
|
208
251
|
status: input.status,
|
|
252
|
+
error: input.error,
|
|
209
253
|
bodyBytes: input.bodyText.length,
|
|
210
254
|
relayBytes: input.relayBytes,
|
|
211
255
|
bodyHash: hashText(input.bodyText),
|
|
212
256
|
diff: diffSummary(input.previousBodyText, input.bodyText),
|
|
213
257
|
body: bodyStructureSummary(input.bodyText),
|
|
214
|
-
files
|
|
215
|
-
body: `${prefix}.body.json`,
|
|
216
|
-
relay: `${prefix}.relay.json`,
|
|
217
|
-
metadata: `${prefix}.meta.json`,
|
|
218
|
-
},
|
|
258
|
+
files,
|
|
219
259
|
};
|
|
220
|
-
|
|
221
|
-
writeFile(
|
|
222
|
-
writeFile(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
260
|
+
const writes = [
|
|
261
|
+
writeFile(files.body, input.bodyText, 'utf8'),
|
|
262
|
+
writeFile(files.metadata, `${JSON.stringify(metadata, null, 2)}\n`, 'utf8'),
|
|
263
|
+
];
|
|
264
|
+
if (input.payload !== undefined && files.relay) {
|
|
265
|
+
writes.push(writeFile(files.relay, `${JSON.stringify(redactForDump(input.payload), null, 2)}\n`, 'utf8'));
|
|
266
|
+
}
|
|
267
|
+
if (input.request !== undefined && files.request) {
|
|
268
|
+
writes.push(writeFile(files.request, `${JSON.stringify(redactForDump({
|
|
269
|
+
...input.request,
|
|
270
|
+
headers: headersToRecord(input.request.headers),
|
|
271
|
+
}), null, 2)}\n`, 'utf8'));
|
|
272
|
+
}
|
|
273
|
+
await Promise.all(writes);
|
|
274
|
+
relayLog(`dumped request id=${id} session=${shortAffinity(affinity)} body=${files.body} meta=${files.metadata}`);
|
|
226
275
|
}
|
|
227
276
|
catch (error) {
|
|
228
277
|
relayLog(`dump failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
229
278
|
}
|
|
230
279
|
}
|
|
280
|
+
export async function dumpDirectRequest(input) {
|
|
281
|
+
if (!dumpEnabled)
|
|
282
|
+
return;
|
|
283
|
+
const previousKey = directDumpPreviousKey(input);
|
|
284
|
+
const previousBodyText = directDumpPreviousBodies.get(previousKey);
|
|
285
|
+
await dumpRequest({
|
|
286
|
+
affinity: input.affinity,
|
|
287
|
+
transport: 'direct',
|
|
288
|
+
route: input.route,
|
|
289
|
+
status: input.status,
|
|
290
|
+
error: input.error,
|
|
291
|
+
bodyText: input.bodyText,
|
|
292
|
+
previousBodyText,
|
|
293
|
+
request: {
|
|
294
|
+
url: input.url,
|
|
295
|
+
method: input.method,
|
|
296
|
+
headers: input.headers,
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
rememberDirectDumpBody(previousKey, input.bodyText);
|
|
300
|
+
}
|
|
301
|
+
export async function dumpRelayRequest(input) {
|
|
302
|
+
await dumpRequest({
|
|
303
|
+
affinity: input.affinity,
|
|
304
|
+
transport: input.transport,
|
|
305
|
+
protocol: input.protocol,
|
|
306
|
+
mode: input.mode,
|
|
307
|
+
status: input.status,
|
|
308
|
+
bodyText: input.bodyText,
|
|
309
|
+
previousBodyText: input.previousBodyText,
|
|
310
|
+
payload: input.payload,
|
|
311
|
+
relayBytes: input.relayBytes,
|
|
312
|
+
});
|
|
313
|
+
}
|
package/dist/models.d.ts
CHANGED
package/dist/models.js
CHANGED
|
@@ -33,3 +33,22 @@ export function isClaudeFableOrMythos5Model(model) {
|
|
|
33
33
|
return (typeof model === 'string' &&
|
|
34
34
|
CLAUDE_FABLE_MYTHOS_5_MODEL_IDS.some((id) => model === id || model.startsWith(`${id}-`)));
|
|
35
35
|
}
|
|
36
|
+
export function isOpenAIReasoningSignature(value) {
|
|
37
|
+
if (typeof value !== 'string')
|
|
38
|
+
return false;
|
|
39
|
+
if (value.startsWith('gAAAA'))
|
|
40
|
+
return true;
|
|
41
|
+
if (!value.startsWith('{'))
|
|
42
|
+
return false;
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(value);
|
|
45
|
+
return (parsed.type === 'reasoning' &&
|
|
46
|
+
typeof parsed.id === 'string' &&
|
|
47
|
+
parsed.id.startsWith('rs_') &&
|
|
48
|
+
typeof parsed.encrypted_content === 'string' &&
|
|
49
|
+
parsed.encrypted_content.startsWith('gAAAA'));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
package/dist/quota-manager.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export type QuotaManagerOptions = {
|
|
|
21
21
|
storage: AccountStorage | null;
|
|
22
22
|
fetchImpl?: typeof fetch;
|
|
23
23
|
now?: () => number;
|
|
24
|
-
onMainQuotaFetched?: (quota: OAuthQuotaSnapshot, checkedAt: number, tokenFingerprint: string) => void;
|
|
24
|
+
onMainQuotaFetched?: (quota: OAuthQuotaSnapshot, checkedAt: number, tokenFingerprint: string, fetchStartedAt: number) => void;
|
|
25
25
|
onApiError?: (error: AccountOperationError) => void;
|
|
26
26
|
};
|
|
27
27
|
export declare class QuotaManager {
|
|
@@ -75,10 +75,18 @@ export declare class QuotaManager {
|
|
|
75
75
|
*/
|
|
76
76
|
needsRefresh(requestCount: number): boolean;
|
|
77
77
|
updateStorage(storage: AccountStorage | null): void;
|
|
78
|
+
/**
|
|
79
|
+
* Seed/update the main quota cache from persisted state. This is deliberately
|
|
80
|
+
* callable after every disk load so another plugin process's fresh quota write
|
|
81
|
+
* can stop this process from showing "checking…" or making a redundant quota
|
|
82
|
+
* API call.
|
|
83
|
+
*/
|
|
84
|
+
seedMainFromStorage(storage: AccountStorage | null, accessToken?: string): void;
|
|
85
|
+
private seedMainBackoffFromStorage;
|
|
78
86
|
/**
|
|
79
87
|
* Seed fallback cache entries from persisted account.quota data.
|
|
80
|
-
*
|
|
81
|
-
*
|
|
88
|
+
* Updates older in-memory entries so a fresh quota write from another plugin
|
|
89
|
+
* process prevents redundant checks and stale sidebar writes.
|
|
82
90
|
*/
|
|
83
91
|
seedFallbacksFromAccounts(accounts: OAuthAccount[]): void;
|
|
84
92
|
/**
|
package/dist/quota-manager.js
CHANGED
|
@@ -56,20 +56,8 @@ export class QuotaManager {
|
|
|
56
56
|
// that produced it. refreshMain() drops this seed if the live token's
|
|
57
57
|
// fingerprint differs (main-account switch), preventing stale wrong-account
|
|
58
58
|
// quota from being served during backoff.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
this.main = {
|
|
62
|
-
quota: persisted.quota,
|
|
63
|
-
refreshAfter: getQuotaNextRefreshAt(persisted.quota, opts.storage, persisted.checkedAt),
|
|
64
|
-
checkedAt: persisted.checkedAt,
|
|
65
|
-
};
|
|
66
|
-
this.mainTokenFp = persisted.tokenFingerprint ?? null;
|
|
67
|
-
}
|
|
68
|
-
// Seed backoff state from persisted storage
|
|
69
|
-
const persistedError = opts.storage?.quota?.mainLastQuotaApiError;
|
|
70
|
-
if (persistedError && quotaBackoffActive(persistedError, this.now())) {
|
|
71
|
-
this.mainLastApiError = persistedError;
|
|
72
|
-
}
|
|
59
|
+
this.seedMainFromStorage(opts.storage);
|
|
60
|
+
this.seedMainBackoffFromStorage(opts.storage);
|
|
73
61
|
}
|
|
74
62
|
// =========================================================================
|
|
75
63
|
// Get (synchronous, from cache)
|
|
@@ -230,24 +218,65 @@ export class QuotaManager {
|
|
|
230
218
|
// =========================================================================
|
|
231
219
|
updateStorage(storage) {
|
|
232
220
|
this.storage = storage;
|
|
221
|
+
this.seedMainFromStorage(storage);
|
|
222
|
+
this.seedMainBackoffFromStorage(storage);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Seed/update the main quota cache from persisted state. This is deliberately
|
|
226
|
+
* callable after every disk load so another plugin process's fresh quota write
|
|
227
|
+
* can stop this process from showing "checking…" or making a redundant quota
|
|
228
|
+
* API call.
|
|
229
|
+
*/
|
|
230
|
+
seedMainFromStorage(storage, accessToken) {
|
|
231
|
+
const persisted = getPersistedMainQuota(storage);
|
|
232
|
+
if (!persisted)
|
|
233
|
+
return;
|
|
234
|
+
const accessTokenFp = accessToken ? tokenFingerprint(accessToken) : null;
|
|
235
|
+
if (accessTokenFp &&
|
|
236
|
+
persisted.tokenFingerprint &&
|
|
237
|
+
persisted.tokenFingerprint !== accessTokenFp) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const entry = {
|
|
241
|
+
quota: persisted.quota,
|
|
242
|
+
refreshAfter: getQuotaNextRefreshAt(persisted.quota, storage, persisted.checkedAt),
|
|
243
|
+
checkedAt: persisted.checkedAt,
|
|
244
|
+
};
|
|
245
|
+
if (this.main &&
|
|
246
|
+
this.main.checkedAt >= entry.checkedAt &&
|
|
247
|
+
(!accessTokenFp ||
|
|
248
|
+
!this.mainTokenFp ||
|
|
249
|
+
this.mainTokenFp === accessTokenFp)) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
this.main = entry;
|
|
253
|
+
this.mainTokenFp = persisted.tokenFingerprint ?? null;
|
|
254
|
+
}
|
|
255
|
+
seedMainBackoffFromStorage(storage) {
|
|
256
|
+
const persistedError = storage?.quota?.mainLastQuotaApiError;
|
|
257
|
+
this.mainLastApiError =
|
|
258
|
+
persistedError && quotaBackoffActive(persistedError, this.now())
|
|
259
|
+
? persistedError
|
|
260
|
+
: undefined;
|
|
233
261
|
}
|
|
234
262
|
/**
|
|
235
263
|
* Seed fallback cache entries from persisted account.quota data.
|
|
236
|
-
*
|
|
237
|
-
*
|
|
264
|
+
* Updates older in-memory entries so a fresh quota write from another plugin
|
|
265
|
+
* process prevents redundant checks and stale sidebar writes.
|
|
238
266
|
*/
|
|
239
267
|
seedFallbacksFromAccounts(accounts) {
|
|
240
268
|
const checkInterval = getQuotaCheckIntervalMs(this.storage);
|
|
241
269
|
for (const account of accounts) {
|
|
242
270
|
if (account.enabled === false)
|
|
243
271
|
continue;
|
|
244
|
-
if (this.getFallback(account.id, account.access))
|
|
245
|
-
continue;
|
|
246
272
|
if (!account.quota)
|
|
247
273
|
continue;
|
|
248
274
|
const checkedAt = Math.max(account.quota.five_hour?.checkedAt ?? 0, account.quota.seven_day?.checkedAt ?? 0);
|
|
249
275
|
if (checkedAt <= 0)
|
|
250
276
|
continue;
|
|
277
|
+
const existing = this.getFallback(account.id, account.access);
|
|
278
|
+
if (existing && existing.checkedAt >= checkedAt)
|
|
279
|
+
continue;
|
|
251
280
|
this.setFallback(account.id, {
|
|
252
281
|
quota: account.quota,
|
|
253
282
|
refreshAfter: checkedAt + checkInterval,
|
|
@@ -327,11 +356,13 @@ export class QuotaManager {
|
|
|
327
356
|
ttlMs: 30_000,
|
|
328
357
|
});
|
|
329
358
|
if (!fileLock) {
|
|
330
|
-
|
|
331
|
-
|
|
359
|
+
const cached = this.main;
|
|
360
|
+
if (cached && this.now() < cached.refreshAfter)
|
|
361
|
+
return cached.quota;
|
|
332
362
|
throw new Error('Quota refresh is already in progress');
|
|
333
363
|
}
|
|
334
364
|
try {
|
|
365
|
+
const fetchStartedAt = this.now();
|
|
335
366
|
const quota = await fetchOAuthQuotaSnapshot({
|
|
336
367
|
accessToken,
|
|
337
368
|
fetchImpl: this.fetchImpl,
|
|
@@ -345,7 +376,7 @@ export class QuotaManager {
|
|
|
345
376
|
checkedAt: now,
|
|
346
377
|
};
|
|
347
378
|
this.mainLastApiError = undefined;
|
|
348
|
-
this.onMainQuotaFetched?.(quota, now, this.mainTokenFp);
|
|
379
|
+
this.onMainQuotaFetched?.(quota, now, this.mainTokenFp, fetchStartedAt);
|
|
349
380
|
return quota;
|
|
350
381
|
}
|
|
351
382
|
catch (error) {
|
|
@@ -377,7 +408,7 @@ export class QuotaManager {
|
|
|
377
408
|
});
|
|
378
409
|
if (!fileLock) {
|
|
379
410
|
const cached = this.getFallback(accountId, accessToken);
|
|
380
|
-
if (cached)
|
|
411
|
+
if (cached && this.now() < cached.refreshAfter)
|
|
381
412
|
return cached.quota;
|
|
382
413
|
throw new Error('Quota refresh is already in progress');
|
|
383
414
|
}
|