@cortexkit/anthropic-auth-core 1.23.0 → 2.0.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.
- package/README.md +18 -1
- package/dist/accounts.d.ts +4 -163
- package/dist/cachekeep.d.ts +9 -0
- package/dist/claude-code.d.ts +16 -0
- package/dist/claustrum-enrollment.d.ts +2 -0
- package/dist/claustrum.d.ts +0 -209
- package/dist/commands/account.d.ts +2 -3
- package/dist/index.d.ts +45 -4
- package/dist/index.js +27569 -38
- package/dist/internal-types/LICENSE +21 -0
- package/dist/internal-types/commands/builtins.d.ts +71 -0
- package/dist/internal-types/commands/index.d.ts +10 -0
- package/dist/internal-types/commands/menu.d.ts +61 -0
- package/dist/internal-types/commands/model.d.ts +238 -0
- package/dist/internal-types/commands/pi.d.ts +23 -0
- package/dist/internal-types/commands/seam.d.ts +72 -0
- package/dist/internal-types/fs/refresh-file-lock.d.ts +33 -0
- package/dist/internal-types/logger/capture-sink.d.ts +14 -0
- package/dist/internal-types/logger/engine.d.ts +98 -0
- package/dist/internal-types/logger/index.d.ts +6 -0
- package/dist/internal-types/logger/redact.d.ts +11 -0
- package/dist/internal-types/pi-slot/index.d.ts +3 -0
- package/dist/internal-types/pi-slot/slot.d.ts +51 -0
- package/dist/internal-types/quota/codec.d.ts +11 -0
- package/dist/internal-types/quota/format.d.ts +46 -0
- package/dist/internal-types/quota/index.d.ts +10 -0
- package/dist/internal-types/quota/map.d.ts +63 -0
- package/dist/internal-types/quota/merge.d.ts +48 -0
- package/dist/internal-types/quota/projection.d.ts +64 -0
- package/dist/internal-types/store/attribution.d.ts +28 -0
- package/dist/internal-types/store/errors.d.ts +55 -0
- package/dist/internal-types/store/hooks.d.ts +12 -0
- package/dist/internal-types/store/identity.d.ts +39 -0
- package/dist/internal-types/store/index.d.ts +21 -0
- package/dist/internal-types/store/mutate.d.ts +189 -0
- package/dist/internal-types/store/pool.d.ts +197 -0
- package/dist/internal-types/store/provider-state.d.ts +131 -0
- package/dist/internal-types/store/publication.d.ts +47 -0
- package/dist/internal-types/store/pull.d.ts +36 -0
- package/dist/internal-types/store/refresh-lock.d.ts +67 -0
- package/dist/internal-types/store/refresh.d.ts +72 -0
- package/dist/internal-types/store/rows.d.ts +313 -0
- package/dist/internal-types/store/runtime.d.ts +36 -0
- package/dist/internal-types/store/schema.d.ts +443 -0
- package/dist/internal-types/store/settings.d.ts +63 -0
- package/dist/internal-types/store/torn.d.ts +78 -0
- package/dist/migration-source.d.ts +33 -0
- package/dist/models.d.ts +41 -0
- package/dist/native-account-runtime.d.ts +121 -0
- package/dist/native-account-view.d.ts +126 -0
- package/dist/native-command-effects.d.ts +59 -0
- package/dist/native-credential-validation.d.ts +42 -0
- package/dist/native-custody-activation.d.ts +45 -0
- package/dist/native-custody.d.ts +114 -0
- package/dist/native-enrollment-authority.d.ts +2 -0
- package/dist/native-host-auth-read.d.ts +3 -0
- package/dist/native-host-auth-write.d.ts +51 -0
- package/dist/native-host-auth.d.ts +12 -0
- package/dist/native-local-credential-service.d.ts +76 -0
- package/dist/native-local-runtime-readers.d.ts +22 -0
- package/dist/native-lock-observer.d.ts +12 -0
- package/dist/native-menu-executor.d.ts +243 -0
- package/dist/native-menu-model.d.ts +142 -0
- package/dist/native-migration-proof.d.ts +28 -0
- package/dist/native-migration-source.d.ts +47 -0
- package/dist/native-migration.d.ts +66 -0
- package/dist/native-quota-codec.d.ts +32 -0
- package/dist/native-refresh-coordinator.d.ts +240 -0
- package/dist/native-roster-seed.d.ts +115 -0
- package/dist/native-runtime-registry.d.ts +19 -0
- package/dist/native-runtime.d.ts +204 -0
- package/dist/native-settings.d.ts +6 -0
- package/dist/native-transient-account.d.ts +38 -0
- package/dist/native-ui.d.ts +45 -0
- package/dist/native-usage-publication.d.ts +12 -0
- package/dist/native-vault-admission.d.ts +8 -0
- package/dist/native-vault-connection.d.ts +33 -0
- package/dist/native-vault-paths.d.ts +13 -0
- package/dist/native-vault-runtime.d.ts +102 -0
- package/dist/native-vault-seed.d.ts +16 -0
- package/dist/pool-authority.d.ts +159 -0
- package/dist/pool-binding.d.ts +32 -0
- package/dist/pool-paths.d.ts +21 -0
- package/dist/pool-store.d.ts +32 -0
- package/dist/provider-error.d.ts +139 -0
- package/dist/quota-manager.d.ts +18 -7
- package/dist/relay.d.ts +14 -1
- package/dist/request-history.d.ts +34 -0
- package/dist/routing.d.ts +1 -2
- package/dist/sse.d.ts +81 -0
- package/dist/sticky-routing.d.ts +28 -0
- package/package.json +10 -4
- package/dist/accounts.js +0 -3881
- package/dist/auth.js +0 -185
- package/dist/cache1h.js +0 -97
- package/dist/cachekeep-registry.js +0 -124
- package/dist/cachekeep.js +0 -604
- package/dist/cch.js +0 -172
- package/dist/claude-code.js +0 -403
- package/dist/claustrum-enrollment-registry.d.ts +0 -17
- package/dist/claustrum-enrollment-registry.js +0 -194
- package/dist/claustrum-enrollment.js +0 -485
- package/dist/claustrum-scoped-roster.d.ts +0 -23
- package/dist/claustrum-scoped-roster.js +0 -194
- package/dist/claustrum-scoped-runtime.d.ts +0 -28
- package/dist/claustrum-scoped-runtime.js +0 -230
- package/dist/claustrum-scoped.d.ts +0 -39
- package/dist/claustrum-scoped.js +0 -193
- package/dist/claustrum.js +0 -1471
- package/dist/commands/account.js +0 -368
- package/dist/constants.js +0 -83
- package/dist/custom-headers.js +0 -102
- package/dist/dump.js +0 -616
- package/dist/fast.js +0 -66
- package/dist/json.js +0 -10
- package/dist/killswitch.js +0 -132
- package/dist/logger.js +0 -288
- package/dist/logging.js +0 -59
- package/dist/mid-conversation-output-config.js +0 -89
- package/dist/model-remap.js +0 -72
- package/dist/models.js +0 -177
- package/dist/network-errors.js +0 -27
- package/dist/oauth-profile.js +0 -60
- package/dist/pkce.js +0 -17
- package/dist/prime.js +0 -841
- package/dist/provider.js +0 -0
- package/dist/quota-header-feed.js +0 -331
- package/dist/quota-headers.js +0 -121
- package/dist/quota-manager.js +0 -856
- package/dist/quotas.js +0 -141
- package/dist/relay.js +0 -1303
- package/dist/routing.js +0 -94
- package/dist/start.js +0 -29
- package/dist/sticky-routing.js +0 -541
- package/dist/thinking-binding.js +0 -54
- package/dist/token-fingerprint.js +0 -8
package/dist/auth.js
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { assertNotCustodyTombstone } from './claustrum.js';
|
|
2
|
-
import { AUTHORIZE_URLS, AXIOS_USER_AGENT, CLIENT_ID, CODE_CALLBACK_URL, OAUTH_SCOPES, REFRESH_SCOPE, TOKEN_URL, } from './constants.js';
|
|
3
|
-
import { isTransientNetworkError } from './network-errors.js';
|
|
4
|
-
import { generatePKCE } from './pkce.js';
|
|
5
|
-
export function parseRetryAfterHeader(value) {
|
|
6
|
-
if (!value)
|
|
7
|
-
return undefined;
|
|
8
|
-
const seconds = Number(value);
|
|
9
|
-
if (Number.isFinite(seconds) && seconds > 0)
|
|
10
|
-
return Math.ceil(seconds);
|
|
11
|
-
const date = Date.parse(value);
|
|
12
|
-
if (Number.isFinite(date)) {
|
|
13
|
-
const delta = Math.ceil((date - Date.now()) / 1000);
|
|
14
|
-
return delta > 0 ? delta : undefined;
|
|
15
|
-
}
|
|
16
|
-
return undefined;
|
|
17
|
-
}
|
|
18
|
-
export class ClaudeOAuthRefreshError extends Error {
|
|
19
|
-
status;
|
|
20
|
-
body;
|
|
21
|
-
/** Parsed Retry-After value in seconds, if the server provided one. */
|
|
22
|
-
retryAfter;
|
|
23
|
-
/**
|
|
24
|
-
* Duck-typed marker: any error carrying `isRefreshError: true` arms the
|
|
25
|
-
* refresh backoff in consumers that receive it (recordQuotaRefreshError).
|
|
26
|
-
* Provider-agnostic — shared-core extraction of anthropic-auth and
|
|
27
|
-
* openai-auth relies on this field instead of instanceof.
|
|
28
|
-
*/
|
|
29
|
-
isRefreshError = true;
|
|
30
|
-
constructor(status, body, retryAfterHeader) {
|
|
31
|
-
super(`Claude OAuth refresh failed: ${status} — ${body}`);
|
|
32
|
-
this.status = status;
|
|
33
|
-
this.body = body;
|
|
34
|
-
this.name = 'ClaudeOAuthRefreshError';
|
|
35
|
-
this.retryAfter = parseRetryAfterHeader(retryAfterHeader ?? undefined);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
export async function refreshClaudeOAuthToken(input) {
|
|
39
|
-
assertNotCustodyTombstone(input.refreshToken, 'anthropic');
|
|
40
|
-
if (typeof input.refreshToken !== 'string' || !input.refreshToken.trim()) {
|
|
41
|
-
throw new Error('Local OAuth refresh credential is unavailable');
|
|
42
|
-
}
|
|
43
|
-
const fetchImpl = input.fetchImpl ?? fetch;
|
|
44
|
-
const maxRetries = input.maxRetries ?? 2;
|
|
45
|
-
const baseDelayMs = input.baseDelayMs ?? 500;
|
|
46
|
-
const setTimeoutImpl = input.setTimeoutImpl ?? globalThis.setTimeout;
|
|
47
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
48
|
-
try {
|
|
49
|
-
if (attempt > 0) {
|
|
50
|
-
const delay = baseDelayMs * 2 ** (attempt - 1);
|
|
51
|
-
await new Promise((resolve) => setTimeoutImpl(resolve, delay));
|
|
52
|
-
}
|
|
53
|
-
const response = await fetchImpl(TOKEN_URL, {
|
|
54
|
-
method: 'POST',
|
|
55
|
-
headers: {
|
|
56
|
-
'Content-Type': 'application/json',
|
|
57
|
-
Accept: 'application/json, text/plain, */*',
|
|
58
|
-
'User-Agent': AXIOS_USER_AGENT,
|
|
59
|
-
},
|
|
60
|
-
body: JSON.stringify({
|
|
61
|
-
grant_type: 'refresh_token',
|
|
62
|
-
refresh_token: input.refreshToken,
|
|
63
|
-
client_id: CLIENT_ID,
|
|
64
|
-
scope: REFRESH_SCOPE,
|
|
65
|
-
}),
|
|
66
|
-
});
|
|
67
|
-
if (!response.ok) {
|
|
68
|
-
if (response.status >= 500 && attempt < maxRetries) {
|
|
69
|
-
await response.body?.cancel().catch(() => { });
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
const body = await response.text().catch(() => '');
|
|
73
|
-
throw new ClaudeOAuthRefreshError(response.status, body, response.headers.get('retry-after'));
|
|
74
|
-
}
|
|
75
|
-
const json = (await response.json());
|
|
76
|
-
const refreshedAt = input.now?.() ?? Date.now();
|
|
77
|
-
return {
|
|
78
|
-
access: json.access_token,
|
|
79
|
-
refresh: json.refresh_token ?? input.refreshToken,
|
|
80
|
-
expires: refreshedAt + json.expires_in * 1000,
|
|
81
|
-
expiresIn: json.expires_in,
|
|
82
|
-
authLineageId: input.authLineageId,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
catch (error) {
|
|
86
|
-
if (error instanceof ClaudeOAuthRefreshError)
|
|
87
|
-
throw error;
|
|
88
|
-
if (attempt < maxRetries && isTransientNetworkError(error))
|
|
89
|
-
continue;
|
|
90
|
-
throw error;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
throw new Error('Token refresh exhausted all retries');
|
|
94
|
-
}
|
|
95
|
-
function generateState() {
|
|
96
|
-
return crypto.randomUUID().replace(/-/g, '');
|
|
97
|
-
}
|
|
98
|
-
function parseCallbackInput(input) {
|
|
99
|
-
const trimmed = input.trim();
|
|
100
|
-
try {
|
|
101
|
-
const url = new URL(trimmed);
|
|
102
|
-
const code = url.searchParams.get('code');
|
|
103
|
-
const state = url.searchParams.get('state');
|
|
104
|
-
if (code && state) {
|
|
105
|
-
return { code, state };
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
// Fall through to legacy/manual formats.
|
|
110
|
-
}
|
|
111
|
-
const hashSplits = trimmed.split('#');
|
|
112
|
-
if (hashSplits.length === 2 && hashSplits[0] && hashSplits[1]) {
|
|
113
|
-
return { code: hashSplits[0], state: hashSplits[1] };
|
|
114
|
-
}
|
|
115
|
-
const params = new URLSearchParams(trimmed);
|
|
116
|
-
const code = params.get('code');
|
|
117
|
-
const state = params.get('state');
|
|
118
|
-
if (code && state) {
|
|
119
|
-
return { code, state };
|
|
120
|
-
}
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
async function exchangeCode(callback, verifier, redirectUri) {
|
|
124
|
-
const result = await fetch(TOKEN_URL, {
|
|
125
|
-
method: 'POST',
|
|
126
|
-
headers: {
|
|
127
|
-
'Content-Type': 'application/json',
|
|
128
|
-
Accept: 'application/json, text/plain, */*',
|
|
129
|
-
'User-Agent': AXIOS_USER_AGENT,
|
|
130
|
-
},
|
|
131
|
-
body: JSON.stringify({
|
|
132
|
-
code: callback.code,
|
|
133
|
-
state: callback.state,
|
|
134
|
-
grant_type: 'authorization_code',
|
|
135
|
-
client_id: CLIENT_ID,
|
|
136
|
-
redirect_uri: redirectUri,
|
|
137
|
-
code_verifier: verifier,
|
|
138
|
-
}),
|
|
139
|
-
});
|
|
140
|
-
if (!result.ok) {
|
|
141
|
-
return {
|
|
142
|
-
type: 'failed',
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
const json = (await result.json());
|
|
146
|
-
return {
|
|
147
|
-
type: 'success',
|
|
148
|
-
refresh: json.refresh_token,
|
|
149
|
-
access: json.access_token,
|
|
150
|
-
expires: Date.now() + json.expires_in * 1000,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
export async function authorize(mode) {
|
|
154
|
-
const pkce = await generatePKCE();
|
|
155
|
-
const state = generateState();
|
|
156
|
-
const url = new URL(AUTHORIZE_URLS[mode], import.meta.url);
|
|
157
|
-
url.searchParams.set('code', 'true');
|
|
158
|
-
url.searchParams.set('client_id', CLIENT_ID);
|
|
159
|
-
url.searchParams.set('response_type', 'code');
|
|
160
|
-
url.searchParams.set('redirect_uri', CODE_CALLBACK_URL);
|
|
161
|
-
url.searchParams.set('scope', OAUTH_SCOPES.join(' '));
|
|
162
|
-
url.searchParams.set('code_challenge', pkce.challenge);
|
|
163
|
-
url.searchParams.set('code_challenge_method', 'S256');
|
|
164
|
-
url.searchParams.set('state', state);
|
|
165
|
-
return {
|
|
166
|
-
url: url.toString(),
|
|
167
|
-
redirectUri: CODE_CALLBACK_URL,
|
|
168
|
-
state,
|
|
169
|
-
verifier: pkce.verifier,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
export async function exchange(input, verifier, redirectUri, expectedState) {
|
|
173
|
-
const callback = parseCallbackInput(input);
|
|
174
|
-
if (!callback) {
|
|
175
|
-
return {
|
|
176
|
-
type: 'failed',
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
if (expectedState && callback.state !== expectedState) {
|
|
180
|
-
return {
|
|
181
|
-
type: 'failed',
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
return exchangeCode(callback, verifier, redirectUri);
|
|
185
|
-
}
|
package/dist/cache1h.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_CACHE_1H_MODE } from './constants.js';
|
|
2
|
-
export const CACHE_1H_COMMAND_NAME = 'claude-cache';
|
|
3
|
-
const CACHE_1H_STATUS_TITLE = '## Claude Cache Status';
|
|
4
|
-
const CACHE_1H_ENABLED_TITLE = '## Claude Cache Enabled';
|
|
5
|
-
const CACHE_1H_DISABLED_TITLE = '## Claude Cache Disabled';
|
|
6
|
-
const CACHE_1H_USAGE_TITLE = '## Claude Cache Usage';
|
|
7
|
-
const CACHE_1H_USAGE = 'Usage: `/claude-cache`, `/claude-cache on`, `/claude-cache off`, or `/claude-cache mode explicit|automatic|hybrid`.';
|
|
8
|
-
let cache1hEnabled = false;
|
|
9
|
-
let cache1hMode = DEFAULT_CACHE_1H_MODE;
|
|
10
|
-
export function isCache1hEnabled() {
|
|
11
|
-
return cache1hEnabled;
|
|
12
|
-
}
|
|
13
|
-
export function getCache1hMode() {
|
|
14
|
-
return cache1hMode;
|
|
15
|
-
}
|
|
16
|
-
export function setCache1hEnabled(enabled) {
|
|
17
|
-
cache1hEnabled = enabled;
|
|
18
|
-
}
|
|
19
|
-
export function setCache1hMode(mode) {
|
|
20
|
-
cache1hMode = mode;
|
|
21
|
-
}
|
|
22
|
-
export function setCache1hState(input) {
|
|
23
|
-
cache1hEnabled = input.enabled;
|
|
24
|
-
cache1hMode = input.mode;
|
|
25
|
-
}
|
|
26
|
-
export function resetCache1hState() {
|
|
27
|
-
cache1hEnabled = false;
|
|
28
|
-
cache1hMode = DEFAULT_CACHE_1H_MODE;
|
|
29
|
-
}
|
|
30
|
-
export function parseCache1hCommandAction(argumentsText) {
|
|
31
|
-
const normalized = argumentsText.trim().split(/\s+/).filter(Boolean);
|
|
32
|
-
if (normalized.length === 0)
|
|
33
|
-
return { type: 'status' };
|
|
34
|
-
if (normalized.length === 1 && normalized[0] === 'on')
|
|
35
|
-
return { type: 'enable' };
|
|
36
|
-
if (normalized.length === 1 && normalized[0] === 'off')
|
|
37
|
-
return { type: 'disable' };
|
|
38
|
-
if (normalized.length === 2 &&
|
|
39
|
-
normalized[0] === 'mode' &&
|
|
40
|
-
(normalized[1] === 'explicit' ||
|
|
41
|
-
normalized[1] === 'automatic' ||
|
|
42
|
-
normalized[1] === 'hybrid')) {
|
|
43
|
-
return { type: 'mode', mode: normalized[1] };
|
|
44
|
-
}
|
|
45
|
-
return { type: 'usage' };
|
|
46
|
-
}
|
|
47
|
-
export function buildCache1hStatusSummary(input) {
|
|
48
|
-
const enabled = input?.enabled ?? cache1hEnabled;
|
|
49
|
-
const mode = input?.mode ?? cache1hMode;
|
|
50
|
-
return [
|
|
51
|
-
CACHE_1H_STATUS_TITLE,
|
|
52
|
-
'',
|
|
53
|
-
`- Enabled: ${enabled ? 'enabled' : 'disabled'}`,
|
|
54
|
-
`- Mode: ${mode}`,
|
|
55
|
-
'- Persisted: ~/.config/opencode/anthropic-auth.json',
|
|
56
|
-
'- Scope: main sessions only; subagent sessions keep default ephemeral cache behavior',
|
|
57
|
-
'- Modes: explicit = existing OpenCode breakpoints; automatic = top-level cache_control only; hybrid = system + messages[0] + top-level cache_control',
|
|
58
|
-
'- TTL: 1h when enabled for main sessions; default ephemeral cache behavior otherwise',
|
|
59
|
-
].join('\n');
|
|
60
|
-
}
|
|
61
|
-
export function executeCache1hCommand(input) {
|
|
62
|
-
const action = parseCache1hCommandAction(input.argumentsText);
|
|
63
|
-
const enabled = input.enabled ?? cache1hEnabled;
|
|
64
|
-
const mode = input.mode ?? cache1hMode;
|
|
65
|
-
if (action.type === 'status')
|
|
66
|
-
return buildCache1hStatusSummary({ enabled, mode });
|
|
67
|
-
if (action.type === 'enable') {
|
|
68
|
-
return [
|
|
69
|
-
CACHE_1H_ENABLED_TITLE,
|
|
70
|
-
'',
|
|
71
|
-
buildCache1hStatusSummary({ enabled: true, mode }),
|
|
72
|
-
].join('\n');
|
|
73
|
-
}
|
|
74
|
-
if (action.type === 'disable') {
|
|
75
|
-
return [
|
|
76
|
-
CACHE_1H_DISABLED_TITLE,
|
|
77
|
-
'',
|
|
78
|
-
buildCache1hStatusSummary({ enabled: false, mode }),
|
|
79
|
-
].join('\n');
|
|
80
|
-
}
|
|
81
|
-
if (action.type === 'mode') {
|
|
82
|
-
return [
|
|
83
|
-
CACHE_1H_STATUS_TITLE,
|
|
84
|
-
'',
|
|
85
|
-
`Mode updated to \`${action.mode}\`.`,
|
|
86
|
-
'',
|
|
87
|
-
buildCache1hStatusSummary({ enabled, mode: action.mode }),
|
|
88
|
-
].join('\n');
|
|
89
|
-
}
|
|
90
|
-
return [
|
|
91
|
-
CACHE_1H_USAGE_TITLE,
|
|
92
|
-
'',
|
|
93
|
-
CACHE_1H_USAGE,
|
|
94
|
-
'',
|
|
95
|
-
buildCache1hStatusSummary({ enabled, mode }),
|
|
96
|
-
].join('\n');
|
|
97
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { mkdir, readdir, readFile, rename, rm, writeFile, } from 'node:fs/promises';
|
|
3
|
-
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
export const CACHE_KEEP_REGISTRY_LEASE_MS = 3 * 60_000;
|
|
6
|
-
function normalizeSession(value) {
|
|
7
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
8
|
-
return;
|
|
9
|
-
const record = value;
|
|
10
|
-
if (typeof record.id !== 'string' ||
|
|
11
|
-
record.id.length === 0 ||
|
|
12
|
-
typeof record.cacheExpiresAt !== 'number' ||
|
|
13
|
-
!Number.isFinite(record.cacheExpiresAt) ||
|
|
14
|
-
typeof record.nextPrewarmAt !== 'number' ||
|
|
15
|
-
!Number.isFinite(record.nextPrewarmAt)) {
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
return {
|
|
19
|
-
id: record.id,
|
|
20
|
-
cacheExpiresAt: record.cacheExpiresAt,
|
|
21
|
-
nextPrewarmAt: record.nextPrewarmAt,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
function normalizeRecord(value) {
|
|
25
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
26
|
-
return;
|
|
27
|
-
const record = value;
|
|
28
|
-
if (record.version !== 1 ||
|
|
29
|
-
typeof record.updatedAt !== 'number' ||
|
|
30
|
-
!Number.isFinite(record.updatedAt) ||
|
|
31
|
-
!Array.isArray(record.sessions)) {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
return {
|
|
35
|
-
version: 1,
|
|
36
|
-
updatedAt: record.updatedAt,
|
|
37
|
-
sessions: record.sessions.flatMap((session) => {
|
|
38
|
-
const normalized = normalizeSession(session);
|
|
39
|
-
return normalized ? [normalized] : [];
|
|
40
|
-
}),
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
export function getDefaultCacheKeepRegistryDirectory(scope) {
|
|
44
|
-
return join(tmpdir(), 'opencode-anthropic-auth', 'cachekeep-sessions', scope);
|
|
45
|
-
}
|
|
46
|
-
export class CacheKeepSessionRegistry {
|
|
47
|
-
options;
|
|
48
|
-
filePath;
|
|
49
|
-
writeChain = Promise.resolve();
|
|
50
|
-
constructor(options) {
|
|
51
|
-
this.options = options;
|
|
52
|
-
const instanceId = options.instanceId ?? `${process.pid}-${randomUUID()}`;
|
|
53
|
-
this.filePath = join(options.directory, `${instanceId}.json`);
|
|
54
|
-
}
|
|
55
|
-
publish(sessions) {
|
|
56
|
-
const snapshot = sessions.map((session) => ({ ...session }));
|
|
57
|
-
this.writeChain = this.writeChain
|
|
58
|
-
.catch(() => { })
|
|
59
|
-
.then(async () => {
|
|
60
|
-
if (snapshot.length === 0) {
|
|
61
|
-
await rm(this.filePath, { force: true });
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
await mkdir(this.options.directory, { recursive: true, mode: 0o700 });
|
|
65
|
-
const tempPath = `${this.filePath}.${randomUUID()}.tmp`;
|
|
66
|
-
const record = {
|
|
67
|
-
version: 1,
|
|
68
|
-
updatedAt: this.options.now?.() ?? Date.now(),
|
|
69
|
-
sessions: snapshot,
|
|
70
|
-
};
|
|
71
|
-
await writeFile(tempPath, `${JSON.stringify(record)}\n`, {
|
|
72
|
-
mode: 0o600,
|
|
73
|
-
});
|
|
74
|
-
await rename(tempPath, this.filePath);
|
|
75
|
-
});
|
|
76
|
-
return this.writeChain;
|
|
77
|
-
}
|
|
78
|
-
async list(localSessions = []) {
|
|
79
|
-
await this.writeChain.catch(() => { });
|
|
80
|
-
const now = this.options.now?.() ?? Date.now();
|
|
81
|
-
const leaseMs = this.options.leaseMs ?? CACHE_KEEP_REGISTRY_LEASE_MS;
|
|
82
|
-
let names;
|
|
83
|
-
try {
|
|
84
|
-
names = (await readdir(this.options.directory)).filter((name) => name.endsWith('.json'));
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
names = [];
|
|
88
|
-
}
|
|
89
|
-
const records = await Promise.all(names.map(async (name) => {
|
|
90
|
-
try {
|
|
91
|
-
const raw = await readFile(join(this.options.directory, name), 'utf8');
|
|
92
|
-
return normalizeRecord(JSON.parse(raw));
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
return undefined;
|
|
96
|
-
}
|
|
97
|
-
}));
|
|
98
|
-
const sessions = new Map();
|
|
99
|
-
for (const record of records) {
|
|
100
|
-
if (!record ||
|
|
101
|
-
now - record.updatedAt > leaseMs ||
|
|
102
|
-
record.updatedAt > now) {
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
for (const session of record.sessions) {
|
|
106
|
-
const existing = sessions.get(session.id);
|
|
107
|
-
if (!existing || session.cacheExpiresAt > existing.cacheExpiresAt) {
|
|
108
|
-
sessions.set(session.id, session);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
for (const session of localSessions) {
|
|
113
|
-
const existing = sessions.get(session.id);
|
|
114
|
-
if (!existing || session.cacheExpiresAt > existing.cacheExpiresAt) {
|
|
115
|
-
sessions.set(session.id, { ...session });
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return [...sessions.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
119
|
-
}
|
|
120
|
-
async dispose() {
|
|
121
|
-
await this.writeChain.catch(() => { });
|
|
122
|
-
await rm(this.filePath, { force: true });
|
|
123
|
-
}
|
|
124
|
-
}
|