@cortexkit/pi-anthropic-auth 1.22.0 → 2.0.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.
@@ -0,0 +1,347 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { authorize, buildClaudeQuotaSummary, buildPrimeAccountStatuses, DEFAULT_KILLSWITCH_THRESHOLDS, exchange, executeCacheKeepCommand, executePrimeCommand, formatEnrollmentStatus, formatNativeAccountState, formatOAuthAccountTier, getCache1hPersistentMode, getCacheKeepWindow, getPersistedLogLevel, getRoutingMode, isCache1hPersistentlyEnabled, isCacheKeepAlways, isCacheKeepHybridActive, isCacheKeepPersistentlyEnabled, isDumpPersistentlyEnabled, isFastModePersistentlyEnabled, isKillswitchEnabled, isPrimePersistentlyEnabled, setDumpEnabled, } from '@cortexkit/anthropic-auth-core';
3
+ import { createPiCustodyCommands } from './custody.js';
4
+ import { getPiNativeRuntime } from './native.js';
5
+ import { getPiAccountStoragePath } from './paths.js';
6
+ import { clearPiStickyRoutingSession, getPiTrackedCacheKeepSessions, } from './stream.js';
7
+ import { fetchPiOAuth } from './transport.js';
8
+ function record(value) {
9
+ return value && typeof value === 'object' && !Array.isArray(value)
10
+ ? value
11
+ : {};
12
+ }
13
+ export function createPiNativeCommands(storagePath = getPiAccountStoragePath()) {
14
+ const native = getPiNativeRuntime(storagePath);
15
+ const custody = createPiCustodyCommands();
16
+ const pending = new Map();
17
+ const setSection = async (key, patch) => {
18
+ await (await native.service()).updateSettings((settings) => ({
19
+ ...settings,
20
+ [key]: { ...record(settings[key]), ...patch },
21
+ }));
22
+ };
23
+ const setLimits = async (entries) => {
24
+ const snapshot = await native.view();
25
+ await (await native.service()).updateSettings((settings) => {
26
+ const limits = record(settings.killswitch);
27
+ let accounts = { ...record(limits.accounts) };
28
+ let main = limits.main;
29
+ for (const entry of entries) {
30
+ const threshold = {
31
+ five_hour: entry.fh,
32
+ seven_day: entry.sd,
33
+ ...(entry.scoped !== undefined && { scoped: entry.scoped }),
34
+ };
35
+ if (entry.account === 'main' || entry.account === 'all')
36
+ main = threshold;
37
+ if (entry.account === 'all') {
38
+ for (const account of snapshot.accounts)
39
+ if (account.id !== 'main')
40
+ accounts = { ...accounts, [account.id]: threshold };
41
+ }
42
+ else if (entry.account !== 'main')
43
+ accounts = { ...accounts, [entry.account]: threshold };
44
+ }
45
+ return {
46
+ ...settings,
47
+ killswitch: { ...limits, enabled: true, main, accounts },
48
+ };
49
+ });
50
+ };
51
+ const dispatch = async (request, options) => {
52
+ const runtime = await native.service();
53
+ switch (request.action) {
54
+ case 'enable':
55
+ case 'disable':
56
+ await runtime.setEnabled(request.values.id, request.action === 'enable');
57
+ return { ok: true, text: `Native account ${request.action}d.` };
58
+ case 'remove':
59
+ await runtime.remove(request.values.id);
60
+ return { ok: true, text: 'Native account removed.' };
61
+ case 'move-up':
62
+ case 'move-down': {
63
+ const order = (await native.view()).accounts.map((account) => account.id);
64
+ const index = order.indexOf(request.values.id);
65
+ if (index < 0)
66
+ return { ok: false, text: 'Native account not found.' };
67
+ const target = index + (request.action === 'move-up' ? -1 : 1);
68
+ if (target < 0 || target >= order.length)
69
+ return {
70
+ ok: true,
71
+ text: 'Native account is already at that end of the order.',
72
+ };
73
+ const id = order[index];
74
+ const other = order[target];
75
+ if (!id || !other)
76
+ return { ok: false, text: 'Native account order is unavailable.' };
77
+ order[index] = other;
78
+ order[target] = id;
79
+ await runtime.reorder(order, request.values.id);
80
+ return { ok: true, text: 'Native account order updated.' };
81
+ }
82
+ case 'add-apikey':
83
+ await runtime.addApi({
84
+ apiKey: request.values.apiKey,
85
+ ...(request.values.baseURL && { baseURL: request.values.baseURL }),
86
+ ...(request.values.authHeader && {
87
+ authHeader: request.values.authHeader,
88
+ }),
89
+ ...(request.values.label && { label: request.values.label }),
90
+ });
91
+ return {
92
+ ok: true,
93
+ text: 'API-key fallback saved in the native pool. It is used only after confirmed general OAuth exhaustion.',
94
+ };
95
+ case 'reset-backoff': {
96
+ const snapshot = await native.view();
97
+ const main = snapshot.accounts.find((account) => account.id === 'main');
98
+ if (main?.type !== 'oauth')
99
+ return {
100
+ ok: false,
101
+ text: 'Native main OAuth account is unavailable.',
102
+ };
103
+ const fence = main.source === 'vault'
104
+ ? await runtime.authorizeVault('main', options.signal)
105
+ : await runtime.captureLocalSubject('main');
106
+ const reset = await runtime.resetBackoff('main', fence, 'all');
107
+ return {
108
+ ok: reset,
109
+ text: reset
110
+ ? 'Native main OAuth refresh and quota backoff cleared.'
111
+ : 'Native account changed before backoff could be cleared.',
112
+ };
113
+ }
114
+ case 'enrollment-reset': {
115
+ const result = await custody.reset();
116
+ return { ok: !result.text.startsWith('Refused:'), text: result.text };
117
+ }
118
+ case 'add-oauth-start': {
119
+ if ((await native.view()).mode !== 'local')
120
+ return {
121
+ ok: false,
122
+ text: 'Local OAuth login is disabled in Claustrum mode. Use offline setup.',
123
+ };
124
+ const authorization = await authorize('max');
125
+ pending.delete(request.sessionId);
126
+ pending.set(request.sessionId, {
127
+ authorization,
128
+ expires: Date.now() + 10 * 60_000,
129
+ });
130
+ while (pending.size > 32) {
131
+ const first = pending.keys().next().value;
132
+ if (first === undefined)
133
+ break;
134
+ pending.delete(first);
135
+ }
136
+ return {
137
+ ok: true,
138
+ text: `Open this OAuth URL, then complete Add OAuth finish in the same session:\n${authorization.url}`,
139
+ };
140
+ }
141
+ case 'add-oauth-finish': {
142
+ const flow = pending.get(request.sessionId);
143
+ pending.delete(request.sessionId);
144
+ if (!flow || flow.expires <= Date.now())
145
+ return {
146
+ ok: false,
147
+ text: 'No pending OAuth login in this session. Start a new login.',
148
+ };
149
+ const snapshot = await native.view();
150
+ if (snapshot.mode !== 'local')
151
+ return {
152
+ ok: false,
153
+ text: 'Local OAuth login is disabled in Claustrum mode. Use offline setup.',
154
+ };
155
+ const result = await exchange(request.values.code, flow.authorization.verifier, flow.authorization.redirectUri, flow.authorization.state);
156
+ if (result.type !== 'success')
157
+ return { ok: false, text: 'Anthropic OAuth exchange failed.' };
158
+ const account = await runtime.loginOAuth({
159
+ routeId: snapshot.accounts.some((account) => account.id === 'main')
160
+ ? randomUUID()
161
+ : 'main',
162
+ credential: {
163
+ access: result.access,
164
+ refresh: result.refresh,
165
+ expires: result.expires,
166
+ },
167
+ ...(request.values.label && { label: request.values.label }),
168
+ });
169
+ const admission = await runtime.authorizeLocal(account.id, {
170
+ signal: options.signal,
171
+ });
172
+ if (admission.status !== 'usable')
173
+ return {
174
+ ok: false,
175
+ text: 'OAuth material was saved in the native pool, but account validation did not complete. It cannot serve until native validation succeeds.',
176
+ };
177
+ return {
178
+ ok: true,
179
+ text: 'OAuth account validated and saved in the native pool. Pi host auth was not changed.',
180
+ };
181
+ }
182
+ case 'quota-refresh': {
183
+ const snapshot = await native.view();
184
+ for (const account of snapshot.accounts) {
185
+ if (!account.enabled || account.type !== 'oauth')
186
+ continue;
187
+ await runtime.fetchQuota(account.id, fetchPiOAuth, options.signal);
188
+ await runtime.fetchProfile(account.id, fetchPiOAuth, options.signal);
189
+ }
190
+ return { ok: true, text: 'Native OAuth quota and profile refreshed.' };
191
+ }
192
+ case 'routing-mode':
193
+ await setSection('routing', { mode: request.values.mode });
194
+ return { ok: true, text: 'Native routing mode updated.' };
195
+ case 'routing-reset':
196
+ await clearPiStickyRoutingSession(storagePath, request.sessionId);
197
+ return {
198
+ ok: true,
199
+ text: 'This session’s sticky assignment was cleared.',
200
+ };
201
+ case 'killswitch-on':
202
+ await runtime.updateSettings((settings) => {
203
+ const limits = record(settings.killswitch);
204
+ return {
205
+ ...settings,
206
+ killswitch: {
207
+ ...limits,
208
+ enabled: true,
209
+ main: limits.main ?? { ...DEFAULT_KILLSWITCH_THRESHOLDS },
210
+ },
211
+ };
212
+ });
213
+ return { ok: true, text: 'Native killswitch enabled.' };
214
+ case 'killswitch-off':
215
+ await setSection('killswitch', { enabled: false });
216
+ return { ok: true, text: 'Native killswitch disabled.' };
217
+ case 'killswitch-set':
218
+ await setLimits(request.values.entries);
219
+ return { ok: true, text: 'Native killswitch thresholds updated.' };
220
+ case 'cache-on':
221
+ case 'cache-off':
222
+ await setSection('claudeCache', {
223
+ enabled: request.action === 'cache-on',
224
+ });
225
+ return { ok: true, text: 'Native prompt cache setting updated.' };
226
+ case 'cache-mode':
227
+ await setSection('claudeCache', { mode: request.values.mode });
228
+ return { ok: true, text: 'Native prompt cache mode updated.' };
229
+ case 'cachekeep-always':
230
+ await runtime.updateSettings((settings) => {
231
+ const { startHour: _start, endHour: _end, ...keep } = record(settings.cacheKeep);
232
+ return {
233
+ ...settings,
234
+ cacheKeep: { ...keep, enabled: true, always: true },
235
+ };
236
+ });
237
+ return { ok: true, text: 'Native CacheKeep is always enabled.' };
238
+ case 'cachekeep-off':
239
+ await setSection('cacheKeep', { enabled: false });
240
+ return { ok: true, text: 'Native CacheKeep disabled.' };
241
+ case 'cachekeep-window':
242
+ await setSection('cacheKeep', {
243
+ enabled: true,
244
+ always: false,
245
+ startHour: request.values.startHour,
246
+ endHour: request.values.endHour,
247
+ });
248
+ return { ok: true, text: 'Native CacheKeep local time window updated.' };
249
+ case 'cachekeep-subagents':
250
+ await setSection('cacheKeep', {
251
+ subagents: request.values.enabled === 'on',
252
+ });
253
+ return { ok: true, text: 'Native CacheKeep subagent setting updated.' };
254
+ case 'dump-on':
255
+ case 'dump-off': {
256
+ const enabled = request.action === 'dump-on';
257
+ await setSection('dump', { enabled });
258
+ setDumpEnabled(enabled);
259
+ return { ok: true, text: 'Native request dumping setting updated.' };
260
+ }
261
+ case 'logging-level':
262
+ // The native runtime stores the level and applies it to the logger.
263
+ await runtime.setLoggingLevel(request.values.level);
264
+ return { ok: true, text: 'Native logging level updated.' };
265
+ case 'fast-on':
266
+ case 'fast-off':
267
+ await setSection('claudeFast', {
268
+ enabled: request.action === 'fast-on',
269
+ });
270
+ return { ok: true, text: 'Native fast mode setting updated.' };
271
+ case 'prime-on':
272
+ case 'prime-off':
273
+ case 'start-fire':
274
+ return {
275
+ ok: false,
276
+ text: 'Pi supports Prime status only and has no lane-start action.',
277
+ };
278
+ }
279
+ };
280
+ return {
281
+ readAccounts: async () => (await native.service()).read(),
282
+ dispatch,
283
+ async readStatus(command) {
284
+ const runtime = await native.service();
285
+ const snapshot = await runtime.read();
286
+ if (snapshot.mode === 'claustrum' && !(await runtime.vault.read()))
287
+ return 'Claustrum roster is unavailable; no verified account view has been discovered.';
288
+ const storage = snapshot.policyStorage;
289
+ switch (command) {
290
+ case 'account':
291
+ return [
292
+ `Custody: ${snapshot.mode}`,
293
+ ...snapshot.accounts.map((account) => `${account.id}: ${account.label ?? account.id} (${account.type}, ${account.enabled ? 'enabled' : 'disabled'}, ${account.source}${account.state ? `, ${formatNativeAccountState(account)}` : ''}${formatOAuthAccountTier(account.profile) ? `, ${formatOAuthAccountTier(account.profile)}` : ''}); identity: ${account.accountIdentity ?? 'unverified'}${account.lastRefreshError ? `; refresh: ${account.lastRefreshError.permanent ? 'requires login' : 'backoff'}` : ''}`),
294
+ ...formatEnrollmentStatus(await custody.status(), snapshot.mode === 'claustrum'),
295
+ ].join('\n');
296
+ case 'quota':
297
+ return buildClaudeQuotaSummary({
298
+ accounts: snapshot.accounts
299
+ .filter((account) => account.type === 'oauth')
300
+ .map((account) => ({
301
+ name: account.label ?? account.id,
302
+ role: account.id === 'main' ? 'main' : 'fallback',
303
+ enabled: account.enabled,
304
+ quota: account.quota,
305
+ lastRefreshedAt: account.lastRefreshedAt,
306
+ tierLabel: formatOAuthAccountTier(account.profile),
307
+ error: account.lastQuotaRefreshError?.message ??
308
+ account.lastRefreshError?.message,
309
+ })),
310
+ });
311
+ case 'routing':
312
+ return `Routing: ${getRoutingMode(storage)}`;
313
+ case 'killswitch':
314
+ return `Killswitch: ${isKillswitchEnabled(storage) ? 'enabled' : 'disabled'}\n${JSON.stringify(storage.killswitch ?? {})}`;
315
+ case 'cache':
316
+ return `1-hour cache: ${isCache1hPersistentlyEnabled(storage) ? 'enabled' : 'disabled'}; mode: ${getCache1hPersistentMode(storage)}`;
317
+ case 'cachekeep': {
318
+ const sessions = await getPiTrackedCacheKeepSessions();
319
+ return executeCacheKeepCommand({
320
+ argumentsText: '',
321
+ enabled: isCacheKeepPersistentlyEnabled(storage),
322
+ always: isCacheKeepAlways(storage),
323
+ hybridActive: isCacheKeepHybridActive(storage),
324
+ window: getCacheKeepWindow(storage),
325
+ trackedSessions: sessions.length,
326
+ trackedSessionDetails: sessions,
327
+ nextPrewarmAt: sessions.length
328
+ ? Math.min(...sessions.map((session) => session.nextPrewarmAt))
329
+ : undefined,
330
+ });
331
+ }
332
+ case 'dump':
333
+ return `Request dumping: ${isDumpPersistentlyEnabled(storage) ? 'enabled' : 'disabled'}`;
334
+ case 'logging':
335
+ return `Logging: ${getPersistedLogLevel(storage) ?? 'info'}`;
336
+ case 'fast':
337
+ return `Fast mode: ${isFastModePersistentlyEnabled(storage) ? 'enabled' : 'disabled'}`;
338
+ case 'prime':
339
+ return executePrimeCommand({
340
+ argumentsText: 'status',
341
+ enabled: isPrimePersistentlyEnabled(storage),
342
+ accounts: buildPrimeAccountStatuses(storage, { now: Date.now() }),
343
+ }).text;
344
+ }
345
+ },
346
+ };
347
+ }
@@ -0,0 +1,71 @@
1
+ import { type NativeAccountRuntime, type NativeAccountSnapshot, type NativeCustodyClient, type NativeCustodyReceipt, type NativeCustodyReporterSource, type NativeRefreshResult } from '@cortexkit/anthropic-auth-core';
2
+ export type PiNativeAttempt = {
3
+ source: 'local';
4
+ accountIdentity: string;
5
+ admission: Extract<NativeRefreshResult, {
6
+ status: 'usable';
7
+ }>;
8
+ } | {
9
+ source: 'vault';
10
+ accountIdentity: string;
11
+ receipt: NativeCustodyReceipt;
12
+ };
13
+ export declare function piAttemptAccessToken(attempt: PiNativeAttempt): string;
14
+ /**
15
+ * Core's refusal for an install that offline setup has never migrated, such
16
+ * as a 1.x install upgraded in place. Core does not export the error class,
17
+ * so it is matched by name and code; no other error matches.
18
+ */
19
+ export declare function isNativeMigrationRequired(error: unknown): error is Error;
20
+ /** Checks Pi host authentication and enrollment before delegating credential authorization and metadata writes to the shared account runtime. */
21
+ /** How often Pi asks the vault for account changes, matching OpenCode's default. Model requests no longer rediscover a healthy roster, so this poll is how Pi notices a re-login or a newly added account. */
22
+ export declare const PI_VAULT_POLL_INTERVAL_MS = 2000;
23
+ export interface PiVaultPollOptions {
24
+ /** 0 disables the poll. */
25
+ intervalMs: number;
26
+ setInterval?: typeof globalThis.setInterval;
27
+ clearInterval?: typeof globalThis.clearInterval;
28
+ }
29
+ export declare class PiNativeRuntime {
30
+ private readonly paths;
31
+ private readonly initialization;
32
+ private vaultWatch;
33
+ private closed;
34
+ private closing;
35
+ readonly usagePublications: {
36
+ publish(write: () => Promise<unknown>): void;
37
+ drain(): Promise<void>;
38
+ };
39
+ constructor(storagePath: string, options?: {
40
+ connect?: () => Promise<NativeCustodyClient>;
41
+ vaultPoll?: PiVaultPollOptions;
42
+ });
43
+ service(): Promise<NativeAccountRuntime>;
44
+ routingPath(): Promise<string>;
45
+ routingStorage(): Promise<{
46
+ path: string;
47
+ vaultSeed?: NativeAccountSnapshot['routingSeed'];
48
+ }>;
49
+ /**
50
+ * Return the account runtime's migration-refusal error when local credentials
51
+ * have not been migrated by offline setup, or undefined when no such refusal
52
+ * occurs. Bypass the Pi login check in service() so older installations with
53
+ * a stored Pi login are recognized too. An enrolled vault may initialize its
54
+ * public settings on this read; local storage is never changed.
55
+ */
56
+ migrationRefusal(): Promise<Error | undefined>;
57
+ /** Read the published inventory even when no account can serve, so /claude can show its sign-in remedy. */
58
+ view(signal?: AbortSignal): Promise<NativeAccountSnapshot>;
59
+ /** Admit a model request without rediscovering a healthy roster. */
60
+ requestView(signal?: AbortSignal): Promise<NativeAccountSnapshot>;
61
+ authorize(routeId: string, signal?: AbortSignal, modelId?: string): Promise<PiNativeAttempt>;
62
+ retry(routeId: string, served: PiNativeAttempt, signal?: AbortSignal, modelId?: string): Promise<PiNativeAttempt | undefined>;
63
+ report(served: PiNativeAttempt, status: number, source: NativeCustodyReporterSource): Promise<void>;
64
+ markUsed(served: PiNativeAttempt, servingRuntime?: NativeAccountRuntime): Promise<void>;
65
+ close(): Promise<void>;
66
+ }
67
+ export declare function getPiNativeRuntime(storagePath?: string, options?: {
68
+ connect?: () => Promise<NativeCustodyClient>;
69
+ vaultPoll?: PiVaultPollOptions;
70
+ }): PiNativeRuntime;
71
+ export declare function closePiNativeRuntime(storagePath?: string): Promise<void>;