@cortexkit/pi-anthropic-auth 1.22.0 → 1.23.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/paths.d.ts CHANGED
@@ -1,2 +1,13 @@
1
1
  export declare function getPiConfigDir(): string;
2
2
  export declare function getPiAccountStoragePath(): string;
3
+ export declare function getPiClaustrumConnectionOptions(storagePath?: string): {
4
+ storagePath: string;
5
+ identity: {
6
+ project_root: string;
7
+ harness: string;
8
+ session: string;
9
+ };
10
+ connectionFile: string | undefined;
11
+ };
12
+ /** Read-only metadata inspection; never create or rewrite Pi's credential store. */
13
+ export declare function hasPiLocalAnthropicOAuth(): Promise<boolean>;
package/dist/paths.js CHANGED
@@ -1,10 +1,50 @@
1
+ import { readFile } from 'node:fs/promises';
1
2
  import { homedir } from 'node:os';
2
3
  import { join } from 'node:path';
3
- import { ACCOUNT_FILE_NAME } from '@cortexkit/anthropic-auth-core';
4
+ import { ACCOUNT_FILE_NAME, parseJsonRedacted, primeStorageFingerprint, } from '@cortexkit/anthropic-auth-core';
4
5
  export function getPiConfigDir() {
5
- return process.env.PI_AGENT_DIR?.trim() || join(homedir(), '.pi', 'agent');
6
+ return (process.env.PI_CODING_AGENT_DIR?.trim() ||
7
+ process.env.PI_AGENT_DIR?.trim() ||
8
+ join(homedir(), '.pi', 'agent'));
6
9
  }
7
10
  export function getPiAccountStoragePath() {
8
11
  return (process.env.PI_ANTHROPIC_AUTH_FILE?.trim() ||
9
12
  join(getPiConfigDir(), ACCOUNT_FILE_NAME));
10
13
  }
14
+ export function getPiClaustrumConnectionOptions(storagePath = getPiAccountStoragePath()) {
15
+ return {
16
+ storagePath,
17
+ identity: {
18
+ project_root: process.cwd(),
19
+ harness: 'pi',
20
+ session: `store-${primeStorageFingerprint(storagePath)}`,
21
+ },
22
+ connectionFile: process.env.PI_ANTHROPIC_AUTH_CLAUSTRUM_CONNECTION_FILE?.trim() ||
23
+ process.env.CLAUSTRUM_SUBC_CONNECTION?.trim() ||
24
+ undefined,
25
+ };
26
+ }
27
+ /** Read-only metadata inspection; never create or rewrite Pi's credential store. */
28
+ export async function hasPiLocalAnthropicOAuth() {
29
+ try {
30
+ const value = parseJsonRedacted(await readFile(join(getPiConfigDir(), 'auth.json'), 'utf8'));
31
+ if (!value || typeof value !== 'object' || Array.isArray(value))
32
+ throw new Error('Invalid authentication store');
33
+ const credential = value.anthropic;
34
+ if (credential === undefined)
35
+ return false;
36
+ if (!credential ||
37
+ typeof credential !== 'object' ||
38
+ Array.isArray(credential))
39
+ throw new Error('Invalid Anthropic authentication entry');
40
+ const type = credential.type;
41
+ if (type !== 'oauth' && type !== 'api_key')
42
+ throw new Error('Unknown Anthropic authentication entry');
43
+ return type === 'oauth';
44
+ }
45
+ catch (error) {
46
+ if (error.code === 'ENOENT')
47
+ return false;
48
+ throw new Error('Cannot safely inspect Pi authentication metadata');
49
+ }
50
+ }
package/dist/stream.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- import { type ApiKeyAccount, type MidConversationEffortTransition } from '@cortexkit/anthropic-auth-core';
1
+ import { type ApiKeyAccount, ClaustrumScopedRuntime, type ClaustrumScopedRuntimeOptions, type MidConversationEffortTransition } from '@cortexkit/anthropic-auth-core';
2
2
  import { type Api, type AssistantMessageEventStream, type Context, type Model, type SimpleStreamOptions } from '@earendil-works/pi-ai';
3
+ export declare function getPiScopedRuntime(storagePath?: string, overrides?: Partial<Pick<ClaustrumScopedRuntimeOptions, 'connect' | 'pollIntervalMs' | 'setTimeoutImpl' | 'clearTimeoutImpl'>>): ClaustrumScopedRuntime;
4
+ export declare function closePiScopedRuntime(storagePath?: string): void;
3
5
  export declare function clearPiStickyRoutingSession(storagePath: string, sessionId: string): Promise<void>;
4
6
  export declare function getPiTrackedCacheKeepSessions(): Promise<import("@cortexkit/anthropic-auth-core").CacheKeepTrackedSession[]>;
5
7
  type AnthropicEvent = {
package/dist/stream.js CHANGED
@@ -1,7 +1,8 @@
1
- import { applyClaudeCodeHeaders, CACHE_KEEP_EXTENDED_TTL_BETA, CacheKeepManager, CacheKeepSessionRegistry, createStickyNoRouteResponse, decideStickyQuotaFailure, dumpDirectRequest, FAST_MODE_BETA, FallbackAccountManager, getCache1hPersistentMode, getDefaultCacheKeepRegistryDirectory, getFallbackReauthLabels, getOrCreateMainAccountId, getRelayConfig, getRoutingMode, getStickyRoutingStatePath, getThinkingPrefixMismatchBehavior, hasThinkingBindingControls, isApiKeyAccount, isCache1hPersistentlyEnabled, isCacheKeepHybridActive, isDumpPersistentlyEnabled, isFastModePersistentlyEnabled, isKillswitchEnabled, isOAuthAccount, isPermanentRefreshError, isValidApiBaseURL, killswitchPassesPolicy, loadAccounts, MID_CONVERSATION_OUTPUT_CONFIG_BETA, mergeAnthropicBetas, QuotaManager, quotaSnapshotHasStandardWindows, quotaSnapshotModelScopeIsExhausted, quotaSnapshotPassesModelScope, quotaSnapshotPassesPolicy, refreshBackoffActive, resolveClaudeCodeIdentity, STICKY_ROUTING_MAIN_ACCOUNT_ID, StickySessionRouter, sendViaRelay, setDumpEnabled, shouldFallbackStatus, stickyQuotaSnapshotIsFresh, stickyRetryAfterWithJitter, stickyRouteFamilyForModel, THINKING_BINDING_CONTROLS_BETA, tokenFingerprint, usesMidConversationOutputConfig, } from '@cortexkit/anthropic-auth-core';
1
+ import { applyClaudeCodeHeaders, applyCustomHeaders, CACHE_KEEP_EXTENDED_TTL_BETA, CacheKeepManager, CacheKeepSessionRegistry, ClaustrumScopedRuntime, connectClaustrumScopedClient, createStickyNoRouteResponse, decideStickyQuotaFailure, dumpDirectRequest, FAST_MODE_BETA, FallbackAccountManager, fetchOAuthQuotaSnapshot, getCache1hPersistentMode, getClaudeCodeIdentityForVerifiedAccount, getClaustrumMode, getDefaultCacheKeepRegistryDirectory, getFallbackReauthLabels, getHostClaustrumEnrollmentPaths, getOrCreateMainAccountId, getQuotaCheckIntervalMs, getRelayConfig, getRoutingMode, getScopedQuotaWindowForModel, getStickyRoutingStatePath, getThinkingPrefixMismatchBehavior, hasThinkingBindingControls, isApiKeyAccount, isCache1hPersistentlyEnabled, isCacheKeepHybridActive, isDumpPersistentlyEnabled, isFastModePersistentlyEnabled, isKillswitchEnabled, isOAuthAccount, isPermanentRefreshError, isValidApiBaseURL, killswitchPassesPolicy, loadAccounts, logger, MID_CONVERSATION_OUTPUT_CONFIG_BETA, mergeAnthropicBetas, QuotaManager, quotaSnapshotHasStandardWindows, quotaSnapshotModelScopeIsExhausted, quotaSnapshotPassesModelScope, quotaSnapshotPassesPolicy, refreshBackoffActive, resolveClaudeCodeIdentity, STICKY_ROUTING_MAIN_ACCOUNT_ID, StickySessionRouter, sendViaRelay, setDumpEnabled, shouldFallbackStatus, stickyQuotaSnapshotIsFresh, stickyRetryAfterWithJitter, stickyRouteFamilyForModel, THINKING_BINDING_CONTROLS_BETA, tokenFingerprint, usesMidConversationOutputConfig, } from '@cortexkit/anthropic-auth-core';
2
2
  import { calculateCost, createAssistantMessageEventStream, } from '@earendil-works/pi-ai';
3
3
  import { buildAnthropicRequest, fromClaudeCodeToolName } from './convert.js';
4
- import { getPiAccountStoragePath } from './paths.js';
4
+ import { requirePiEnrollment } from './custody.js';
5
+ import { getPiAccountStoragePath, getPiClaustrumConnectionOptions, } from './paths.js';
5
6
  function errorText(error) {
6
7
  return error instanceof Error ? error.message : String(error);
7
8
  }
@@ -12,6 +13,46 @@ const quotaManagers = new Map();
12
13
  const fallbackManagers = new Map();
13
14
  const mainAccountIdInitializations = new Map();
14
15
  const PI_SERVICE_CACHE_LIMIT = 16;
16
+ const scopedRuntimes = new Map();
17
+ export function getPiScopedRuntime(storagePath = getPiAccountStoragePath(), overrides = {}) {
18
+ const existing = scopedRuntimes.get(storagePath);
19
+ if (existing)
20
+ return existing;
21
+ let warned = false;
22
+ const { connect, ...runtimeOverrides } = overrides;
23
+ const runtime = new ClaustrumScopedRuntime({
24
+ storagePath,
25
+ tokenPath: getHostClaustrumEnrollmentPaths('pi').tokenPath,
26
+ connect: async () => {
27
+ await requirePiEnrollment();
28
+ return connect
29
+ ? connect()
30
+ : connectClaustrumScopedClient(getPiClaustrumConnectionOptions(storagePath));
31
+ },
32
+ onRoster: () => {
33
+ warned = false;
34
+ },
35
+ onError: () => {
36
+ if (!warned)
37
+ logger.warn('claustrum', 'Pi scoped credential service unavailable');
38
+ warned = true;
39
+ },
40
+ ...runtimeOverrides,
41
+ });
42
+ while (scopedRuntimes.size >= PI_SERVICE_CACHE_LIMIT) {
43
+ const oldest = scopedRuntimes.keys().next().value;
44
+ if (oldest === undefined)
45
+ break;
46
+ scopedRuntimes.get(oldest)?.close();
47
+ scopedRuntimes.delete(oldest);
48
+ }
49
+ scopedRuntimes.set(storagePath, runtime);
50
+ return runtime;
51
+ }
52
+ export function closePiScopedRuntime(storagePath = getPiAccountStoragePath()) {
53
+ scopedRuntimes.get(storagePath)?.close();
54
+ scopedRuntimes.delete(storagePath);
55
+ }
15
56
  function setBoundedService(map, key, value) {
16
57
  map.delete(key);
17
58
  map.set(key, value);
@@ -39,10 +80,38 @@ function getPiRoutingServices(storagePath, storage) {
39
80
  let quotaManager = quotaManagers.get(storagePath);
40
81
  let fallbackManager = fallbackManagers.get(storagePath);
41
82
  if (!quotaManager || !fallbackManager) {
42
- quotaManager = new QuotaManager({ storage });
83
+ quotaManager = new QuotaManager({
84
+ storage,
85
+ fetchQuotaSnapshot: async (request) => {
86
+ const current = await loadAccounts(storagePath);
87
+ if (getClaustrumMode(current) === 'claustrum') {
88
+ const id = request.kind === 'main' ? 'main' : request.accountId;
89
+ if (!id)
90
+ throw new Error('Claustrum quota request has no account identity');
91
+ return getPiScopedRuntime(storagePath).fetchQuota(id);
92
+ }
93
+ if (!request.accessToken)
94
+ throw new Error('Missing local OAuth quota credential');
95
+ return fetchOAuthQuotaSnapshot({ accessToken: request.accessToken });
96
+ },
97
+ });
43
98
  fallbackManager = new FallbackAccountManager({
44
99
  configPath: storagePath,
45
100
  quotaManager,
101
+ isFallbackAccountVaultEnabled: (_id, current) => getClaustrumMode(current) === 'claustrum',
102
+ isFallbackAccountVaultServed: (id, current) => {
103
+ const account = current.accounts.find((candidate) => candidate.id === id);
104
+ return (getClaustrumMode(current) === 'claustrum' &&
105
+ Boolean(account &&
106
+ isOAuthAccount(account) &&
107
+ account.claustrumScopedCredentialId &&
108
+ account.claustrumScopedState === 'active'));
109
+ },
110
+ resolveFallbackAccessToken: (account, current) => getClaustrumMode(current) === 'claustrum'
111
+ ? undefined
112
+ : account.access
113
+ ? { token: account.access, source: 'sidecar' }
114
+ : undefined,
46
115
  });
47
116
  setBoundedService(quotaManagers, storagePath, quotaManager);
48
117
  setBoundedService(fallbackManagers, storagePath, fallbackManager);
@@ -74,18 +143,50 @@ function getPiCacheKeepRegistry() {
74
143
  }
75
144
  return cacheKeepRegistry;
76
145
  }
146
+ const scopedPrewarmAttempts = new Map();
77
147
  const cacheKeepManager = new CacheKeepManager({
78
148
  loadStorage: () => loadAccounts(getPiAccountStoragePath()),
79
149
  onTrackedSessionsChanged: (sessions) => getPiCacheKeepRegistry().publish(sessions),
80
- prepareHeaders: async (headers, target) => {
150
+ prepareHeaders: async (headers, target, attempt) => {
151
+ const storagePath = target.accountStoragePath ?? getPiAccountStoragePath();
152
+ const current = await loadAccounts(storagePath);
153
+ if (target.accountStoragePath && !current)
154
+ return undefined;
155
+ if (getClaustrumMode(current) === 'claustrum') {
156
+ const runtime = getPiScopedRuntime(storagePath);
157
+ const id = target.oauthAccountId ?? STICKY_ROUTING_MAIN_ACCOUNT_ID;
158
+ const receipt = await runtime.authorize(id, attempt.signal);
159
+ if (!target.oauthAccountIdentity ||
160
+ receipt.accountId !== target.oauthAccountIdentity) {
161
+ throw new Error('CacheKeep account identity changed');
162
+ }
163
+ const body = JSON.parse(target.bodyText);
164
+ const identity = getClaudeCodeIdentityForVerifiedAccount(id === STICKY_ROUTING_MAIN_ACCOUNT_ID
165
+ ? (current?.mainAccountId ?? id)
166
+ : id, receipt.accountId);
167
+ headers.delete('anthropic-beta');
168
+ applyClaudeCodeHeaders(headers, receipt.accessToken, {
169
+ body,
170
+ identity,
171
+ extraBetas: [
172
+ CACHE_KEEP_EXTENDED_TTL_BETA,
173
+ ...(hasThinkingBindingControls(body)
174
+ ? [THINKING_BINDING_CONTROLS_BETA]
175
+ : []),
176
+ ],
177
+ });
178
+ if (body.speed === 'fast')
179
+ headers.set('anthropic-beta', mergeAnthropicBetas(headers.get('anthropic-beta'), [FAST_MODE_BETA]));
180
+ scopedPrewarmAttempts.set(attempt.id, { runtime, receipt });
181
+ return headers;
182
+ }
81
183
  const authorization = headers.get('authorization') ?? '';
82
184
  const match = /^Bearer\s+(.+)$/i.exec(authorization);
83
185
  const accessToken = match?.[1];
84
186
  if (!accessToken)
85
- return headers;
187
+ return undefined;
86
188
  try {
87
189
  const body = JSON.parse(target.bodyText);
88
- const storagePath = getPiAccountStoragePath();
89
190
  await ensurePiMainAccountId(storagePath);
90
191
  const storage = await loadAccounts(storagePath);
91
192
  const identity = await resolveClaudeCodeIdentity(accessToken, typeof body.model === 'string' ? body.model : undefined, target.oauthAccountId ?? storage?.mainAccountId);
@@ -109,6 +210,14 @@ const cacheKeepManager = new CacheKeepManager({
109
210
  }
110
211
  return headers;
111
212
  },
213
+ onResponse: async ({ attempt, status }) => {
214
+ const entry = scopedPrewarmAttempts.get(attempt.id);
215
+ if (entry)
216
+ await entry.runtime.reportFailure(entry.receipt, status, 'direct');
217
+ },
218
+ onComplete: ({ attempt }) => {
219
+ scopedPrewarmAttempts.delete(attempt.id);
220
+ },
112
221
  });
113
222
  export async function getPiTrackedCacheKeepSessions() {
114
223
  return getPiCacheKeepRegistry().list(cacheKeepManager.trackedSessions());
@@ -164,7 +273,16 @@ function updateUsage(model, output, usage) {
164
273
  }
165
274
  export function buildExplicitBaseMessagesUrl(baseURL) {
166
275
  const url = new URL(baseURL);
167
- url.pathname = `${url.pathname.replace(/\/$/, '')}/v1/messages`;
276
+ const basePath = url.pathname.replace(/\/$/, '');
277
+ if (/\/v\d[^/]*\/messages$/.test(basePath)) {
278
+ url.pathname = basePath;
279
+ }
280
+ else if (/\/v\d[^/]*$/.test(basePath)) {
281
+ url.pathname = `${basePath}/messages`;
282
+ }
283
+ else {
284
+ url.pathname = `${basePath}/v1/messages`;
285
+ }
168
286
  url.searchParams.set('beta', 'true');
169
287
  return url;
170
288
  }
@@ -183,6 +301,7 @@ export function configureApiRouteHeaders(account, fastMode) {
183
301
  if (fastMode) {
184
302
  headers.set('anthropic-beta', mergeAnthropicBetas(headers.get('anthropic-beta'), [FAST_MODE_BETA]));
185
303
  }
304
+ applyCustomHeaders(headers);
186
305
  return headers;
187
306
  }
188
307
  export async function* parseSse(response) {
@@ -230,9 +349,24 @@ async function sendAnthropicRequest(options) {
230
349
  const accountIdentity = options.oauthAccountId === STICKY_ROUTING_MAIN_ACCOUNT_ID
231
350
  ? storage?.mainAccountId
232
351
  : options.oauthAccountId;
233
- const identity = options.accessToken
234
- ? await resolveClaudeCodeIdentity(options.accessToken, options.model.id, accountIdentity)
352
+ const scoped = !options.apiAccount && getClaustrumMode(storage) === 'claustrum'
353
+ ? getPiScopedRuntime(options.storagePath)
235
354
  : undefined;
355
+ const routeId = options.oauthAccountId ?? STICKY_ROUTING_MAIN_ACCOUNT_ID;
356
+ const roster = scoped
357
+ ? (scoped.snapshot() ?? (await scoped.refresh()))
358
+ : undefined;
359
+ const verifiedUuid = routeId === STICKY_ROUTING_MAIN_ACCOUNT_ID
360
+ ? roster?.primary?.accountId
361
+ : roster?.accounts.find((account) => account.id === routeId)
362
+ ?.anthropicAccountUuid;
363
+ if (scoped && !verifiedUuid)
364
+ throw new Error('Claustrum route has no verified provider identity');
365
+ const identity = scoped && verifiedUuid
366
+ ? getClaudeCodeIdentityForVerifiedAccount(accountIdentity ?? routeId, verifiedUuid)
367
+ : options.accessToken
368
+ ? await resolveClaudeCodeIdentity(options.accessToken, options.model.id, accountIdentity)
369
+ : undefined;
236
370
  const { body, bodyText } = await buildAnthropicRequest(options.model.id, options.context, options.streamOptions, {
237
371
  enabled: isCache1hPersistentlyEnabled(storage),
238
372
  mode: getCache1hPersistentMode(storage),
@@ -245,7 +379,7 @@ async function sendAnthropicRequest(options) {
245
379
  const fastMode = body.speed === 'fast';
246
380
  const headers = options.apiAccount
247
381
  ? configureApiRouteHeaders(options.apiAccount, fastMode)
248
- : applyClaudeCodeHeaders(new Headers(), options.accessToken ?? '', {
382
+ : applyClaudeCodeHeaders(new Headers(), scoped ? '' : (options.accessToken ?? ''), {
249
383
  body,
250
384
  identity,
251
385
  extraBetas: [
@@ -276,12 +410,31 @@ async function sendAnthropicRequest(options) {
276
410
  headers,
277
411
  bodyText,
278
412
  storage,
279
- cacheMode: isCacheKeepHybridActive(storage) ? 'hybrid' : 'disabled',
413
+ cacheMode: !options.apiAccount && isCacheKeepHybridActive(storage)
414
+ ? 'hybrid'
415
+ : 'disabled',
280
416
  oauthAccountId: options.oauthAccountId,
417
+ oauthAccountIdentity: verifiedUuid ?? identity?.accountUuid,
418
+ accountStoragePath: options.storagePath,
281
419
  });
420
+ const report = async (attempt, status, source) => {
421
+ if (!scoped || status !== 401)
422
+ return;
423
+ await scoped.reportFailure(attempt, status, source).catch(() => {
424
+ logger.warn('claustrum', 'Pi scoped auth-failure report unavailable');
425
+ });
426
+ };
282
427
  const directFetch = async () => {
428
+ // Every physical attempt, including relay-to-direct fallback, is authorized.
429
+ const attempt = scoped
430
+ ? await scoped.authorize(routeId, options.streamOptions?.signal)
431
+ : undefined;
432
+ if (attempt)
433
+ headers.set('authorization', `Bearer ${attempt.accessToken}`);
283
434
  try {
284
435
  const response = await fetch(input, init);
436
+ if (attempt)
437
+ await report(attempt, response.status, 'direct');
285
438
  await dumpDirectRequest({
286
439
  affinity: relayAffinity,
287
440
  route: options.route ??
@@ -318,6 +471,19 @@ async function sendAnthropicRequest(options) {
318
471
  body: bodyText,
319
472
  fallback: directFetch,
320
473
  affinity: relayAffinity,
474
+ authorizeAttempt: scoped
475
+ ? async () => {
476
+ const attempt = await scoped.authorize(routeId, options.streamOptions?.signal);
477
+ const authorizedHeaders = new Headers(headers);
478
+ authorizedHeaders.set('authorization', `Bearer ${attempt.accessToken}`);
479
+ return {
480
+ headers: authorizedHeaders,
481
+ onUpstreamStatus: (status) => {
482
+ void report(attempt, status, 'relay_status_field');
483
+ },
484
+ };
485
+ }
486
+ : undefined,
321
487
  });
322
488
  }
323
489
  function quotaSnapshotIsExhausted(quota) {
@@ -348,17 +514,62 @@ async function firstStreamingError(response) {
348
514
  }
349
515
  async function executeWithFallback(options) {
350
516
  await ensurePiMainAccountId(options.storagePath);
351
- const storage = await loadAccounts(options.storagePath);
517
+ let storage = await loadAccounts(options.storagePath);
518
+ const scoped = getClaustrumMode(storage) === 'claustrum'
519
+ ? getPiScopedRuntime(options.storagePath)
520
+ : undefined;
521
+ let roster = scoped ? await scoped.refresh() : undefined;
522
+ if (scoped && !roster)
523
+ throw new Error('Claustrum custody changed during request preparation');
524
+ if (roster)
525
+ storage = roster.storage;
526
+ const primaryCredential = scoped ? '' : options.primaryAccessToken;
527
+ const scopedReady = (account) => Boolean(scoped &&
528
+ account.enabled !== false &&
529
+ account.claustrumScopedCredentialId &&
530
+ account.claustrumScopedState === 'active');
531
+ const primaryAvailable = () => !scoped ||
532
+ (roster?.primary?.state === 'active' &&
533
+ !storage?.claustrum?.disabledAccountIdentities?.includes(roster.primary.accountId));
352
534
  const { quotaManager, fallbackManager: manager } = getPiRoutingServices(options.storagePath, storage);
353
- const mainAccountId = storage?.mainAccountId;
535
+ let mainAccountId = scoped
536
+ ? roster?.primary?.accountId
537
+ : storage?.mainAccountId;
354
538
  quotaManager.seedMainFromStorage(storage, mainAccountId);
355
539
  quotaManager.seedFallbacksFromAccounts((storage?.accounts ?? []).filter(isOAuthAccount));
540
+ function quotaObservationIsFresh(quota, modelId) {
541
+ if (stickyQuotaSnapshotIsFresh(quota, storage, Date.now(), modelId))
542
+ return true;
543
+ // A successful windowless usage response is not the same as a failed
544
+ // probe. Pi intentionally admits the former as unknown-capacity OAuth.
545
+ if (!quota ||
546
+ quota.five_hour ||
547
+ quota.seven_day ||
548
+ quota.source !== 'poll' ||
549
+ quota.checkedAt === undefined)
550
+ return false;
551
+ const age = getQuotaCheckIntervalMs(storage);
552
+ const scopedWindow = getScopedQuotaWindowForModel(quota, modelId);
553
+ return (Date.now() - quota.checkedAt < age &&
554
+ (!scopedWindow || Date.now() - scopedWindow.checkedAt < age));
555
+ }
356
556
  async function buildStickyRoutes(modelId) {
557
+ if (scoped) {
558
+ roster = await scoped.refresh();
559
+ if (!roster)
560
+ throw new Error('Claustrum custody changed during routing');
561
+ storage = roster.storage;
562
+ mainAccountId = roster.primary?.accountId;
563
+ quotaManager.updateStorage(storage);
564
+ quotaManager.seedMainFromStorage(storage, mainAccountId);
565
+ quotaManager.seedFallbacksFromAccounts(storage.accounts.filter(isOAuthAccount));
566
+ }
357
567
  const mainEntry = quotaManager.getMain(mainAccountId);
358
568
  let mainQuota = mainEntry?.quota;
359
- if (!stickyQuotaSnapshotIsFresh(mainEntry?.quota, storage, Date.now(), modelId)) {
569
+ if (primaryAvailable() &&
570
+ !stickyQuotaSnapshotIsFresh(mainEntry?.quota, storage, Date.now(), modelId)) {
360
571
  try {
361
- mainQuota = await quotaManager.refreshMain(mainAccountId, options.primaryAccessToken);
572
+ mainQuota = await quotaManager.refreshMain(mainAccountId, primaryCredential);
362
573
  }
363
574
  catch { }
364
575
  }
@@ -367,10 +578,12 @@ async function executeWithFallback(options) {
367
578
  });
368
579
  const usableById = new Map(usableFallbacks.map((account) => [account.id, account]));
369
580
  const allRoutes = [];
370
- if (!isPermanentRefreshError(storage?.refresh?.mainLastRefreshError)) {
581
+ if (primaryAvailable() &&
582
+ (scoped ||
583
+ !isPermanentRefreshError(storage?.refresh?.mainLastRefreshError))) {
371
584
  allRoutes.push({
372
585
  id: STICKY_ROUTING_MAIN_ACCOUNT_ID,
373
- access: options.primaryAccessToken,
586
+ access: primaryCredential,
374
587
  quota: mainQuota,
375
588
  order: 0,
376
589
  });
@@ -379,18 +592,20 @@ async function executeWithFallback(options) {
379
592
  if (configured.enabled === false || !isOAuthAccount(configured))
380
593
  continue;
381
594
  const account = usableById.get(configured.id) ?? configured;
382
- if (!account.access || isPermanentRefreshError(account.lastRefreshError))
595
+ if (scoped
596
+ ? !scopedReady(account)
597
+ : !account.access || isPermanentRefreshError(account.lastRefreshError))
383
598
  continue;
384
599
  let accountQuota = quotaManager.getFallback(account.id, account)?.quota ?? account.quota;
385
600
  if (!stickyQuotaSnapshotIsFresh(accountQuota, storage, Date.now(), modelId)) {
386
601
  try {
387
- accountQuota = await quotaManager.refreshFallback(account.id, account.access, account);
602
+ accountQuota = await quotaManager.refreshFallback(account.id, scoped ? '' : (account.access ?? ''), account);
388
603
  }
389
604
  catch { }
390
605
  }
391
606
  allRoutes.push({
392
607
  id: account.id,
393
- access: account.access,
608
+ access: scoped ? '' : (account.access ?? ''),
394
609
  quota: accountQuota,
395
610
  order: index + 1,
396
611
  account,
@@ -400,7 +615,7 @@ async function executeWithFallback(options) {
400
615
  const refreshError = route.id === STICKY_ROUTING_MAIN_ACCOUNT_ID
401
616
  ? storage?.refresh?.mainLastRefreshError
402
617
  : route.account?.lastRefreshError;
403
- if (isPermanentRefreshError(refreshError))
618
+ if (!scoped && isPermanentRefreshError(refreshError))
404
619
  return [];
405
620
  if (stickyQuotaSnapshotIsFresh(route.quota, storage, Date.now(), modelId) &&
406
621
  decideStickyQuotaFailure({ quota: route.quota, modelId }).action ===
@@ -443,8 +658,10 @@ async function executeWithFallback(options) {
443
658
  return { allRoutes, candidates, retainAccountIds };
444
659
  }
445
660
  async function primaryQuotaRefreshConfirmsExhausted() {
661
+ if (!primaryAvailable())
662
+ return false;
446
663
  try {
447
- const quota = await quotaManager.refreshMain(mainAccountId, options.primaryAccessToken);
664
+ const quota = await quotaManager.refreshMain(mainAccountId, primaryCredential);
448
665
  const entry = quotaManager.getMain(mainAccountId);
449
666
  return Boolean(entry &&
450
667
  entry.refreshAfter > Date.now() &&
@@ -455,8 +672,10 @@ async function executeWithFallback(options) {
455
672
  }
456
673
  }
457
674
  async function primaryQuotaRefreshConfirmsModelScopeExhausted() {
675
+ if (!primaryAvailable())
676
+ return false;
458
677
  try {
459
- const quota = await quotaManager.refreshMain(mainAccountId, options.primaryAccessToken);
678
+ const quota = await quotaManager.refreshMain(mainAccountId, primaryCredential);
460
679
  const entry = quotaManager.getMain(mainAccountId);
461
680
  return Boolean(entry &&
462
681
  entry.refreshAfter > Date.now() &&
@@ -467,11 +686,15 @@ async function executeWithFallback(options) {
467
686
  }
468
687
  }
469
688
  function primaryCachedModelScopeExhausted() {
689
+ if (!primaryAvailable())
690
+ return false;
470
691
  const entry = quotaManager.getMain(mainAccountId);
471
692
  return Boolean(entry &&
472
693
  quotaSnapshotModelScopeIsExhausted(entry.quota, options.model.id));
473
694
  }
474
695
  function primaryFreshModelScopeExhausted() {
696
+ if (!primaryAvailable())
697
+ return false;
475
698
  const entry = quotaManager.getMain(mainAccountId);
476
699
  return Boolean(entry &&
477
700
  !quotaManager.isMainStale(options.model.id) &&
@@ -483,17 +706,21 @@ async function executeWithFallback(options) {
483
706
  });
484
707
  const usableOAuthById = new Map(usableOAuth.map((account) => [account.id, account]));
485
708
  for (const configured of storage?.accounts ?? []) {
709
+ if (configured.enabled === false)
710
+ continue;
486
711
  let response = null;
487
712
  let account = isOAuthAccount(configured)
488
713
  ? usableOAuthById.get(configured.id)
489
714
  : configured;
490
715
  if (!account) {
491
716
  if (!isOAuthAccount(configured) ||
492
- !configured.access ||
493
- (configured.expires !== undefined &&
494
- configured.expires <= Date.now()) ||
495
- isPermanentRefreshError(configured.lastRefreshError) ||
496
- refreshBackoffActive(configured.lastRefreshError, configured.id, Date.now(), tokenFingerprint(configured.refresh)))
717
+ (scoped
718
+ ? !scopedReady(configured)
719
+ : !configured.access ||
720
+ (configured.expires !== undefined &&
721
+ configured.expires <= Date.now()) ||
722
+ isPermanentRefreshError(configured.lastRefreshError) ||
723
+ refreshBackoffActive(configured.lastRefreshError, configured.id, Date.now(), tokenFingerprint(configured.refresh))))
497
724
  continue;
498
725
  account = configured;
499
726
  const quota = quotaManager.getFallback(configured.id, configured)?.quota ??
@@ -510,11 +737,12 @@ async function executeWithFallback(options) {
510
737
  continue;
511
738
  }
512
739
  if (isOAuthAccount(account)) {
513
- if (routeOptions.apiOnly === true || !account.access)
740
+ if (routeOptions.apiOnly === true ||
741
+ (scoped ? !scopedReady(account) : !account.access))
514
742
  continue;
515
743
  response = await sendAnthropicRequest({
516
744
  ...options,
517
- accessToken: account.access,
745
+ accessToken: scoped ? undefined : account.access,
518
746
  oauthAccountId: account.id,
519
747
  });
520
748
  }
@@ -549,15 +777,19 @@ async function executeWithFallback(options) {
549
777
  const router = getPiStickyRouter(options.storagePath);
550
778
  const initialInputBytes = Math.max(1, Buffer.byteLength(JSON.stringify(options.context)));
551
779
  let routes = await buildStickyRoutes(options.model.id);
552
- const mainPermanentlyUnavailable = isPermanentRefreshError(storage?.refresh?.mainLastRefreshError);
780
+ const mainPermanentlyUnavailable = scoped
781
+ ? !primaryAvailable()
782
+ : isPermanentRefreshError(storage?.refresh?.mainLastRefreshError);
553
783
  const incompleteQuotaPool = (routes.allRoutes.length === 0 && !mainPermanentlyUnavailable) ||
554
- routes.allRoutes.some((candidate) => !candidate.quota ||
555
- !stickyQuotaSnapshotIsFresh(candidate.quota, storage, Date.now(), options.model.id));
784
+ routes.allRoutes.some((candidate) => !quotaObservationIsFresh(candidate.quota, options.model.id));
556
785
  let resolution = await router.resolve({
557
786
  sessionId,
558
787
  family: stickyRouteFamilyForModel(options.model.id),
559
788
  modelId: options.model.id,
560
- candidates: routes.candidates,
789
+ affinityModelId: options.model.id,
790
+ // Existing affinity can survive a transient probe failure, but an
791
+ // incomplete pool cannot create a new balanced assignment.
792
+ candidates: incompleteQuotaPool ? [] : routes.candidates,
561
793
  retainAccountIds: routes.retainAccountIds,
562
794
  storage,
563
795
  inputBytes: initialInputBytes,
@@ -572,7 +804,9 @@ async function executeWithFallback(options) {
572
804
  }
573
805
  if (!resolution) {
574
806
  return createStickyNoRouteResponse({
575
- mainRefreshError: storage?.refresh?.mainLastRefreshError,
807
+ mainRefreshError: scoped
808
+ ? undefined
809
+ : storage?.refresh?.mainLastRefreshError,
576
810
  fallbackReauthLabels: getFallbackReauthLabels(storage),
577
811
  routeQuotas: routes.allRoutes.flatMap((route) => route.quota ? [route.quota] : []),
578
812
  modelId: options.model.id,
@@ -619,11 +853,12 @@ async function executeWithFallback(options) {
619
853
  }
620
854
  let permanentAuthFailure = preflight instanceof Response &&
621
855
  preflight.status === 401 &&
622
- route.id === STICKY_ROUTING_MAIN_ACCOUNT_ID;
856
+ (Boolean(scoped) || route.id === STICKY_ROUTING_MAIN_ACCOUNT_ID);
623
857
  if (preflight instanceof Response &&
624
858
  preflight.status === 401 &&
625
859
  route.account &&
626
- storage) {
860
+ storage &&
861
+ !scoped) {
627
862
  const authRouteId = route.id;
628
863
  try {
629
864
  const refreshed = await manager.refreshAccount(route.account, storage, {
@@ -749,11 +984,24 @@ async function executeWithFallback(options) {
749
984
  if (fallback)
750
985
  return fallback;
751
986
  }
987
+ if (!primaryAvailable()) {
988
+ if (!fallbackFirst) {
989
+ const fallback = await tryFallbackAccounts();
990
+ if (fallback)
991
+ return fallback;
992
+ }
993
+ return createStickyNoRouteResponse({
994
+ routeQuotas: (storage?.accounts ?? [])
995
+ .filter(isOAuthAccount)
996
+ .flatMap((account) => (account.quota ? [account.quota] : [])),
997
+ modelId: options.model.id,
998
+ });
999
+ }
752
1000
  if (isKillswitchEnabled(storage)) {
753
1001
  let mainQuota = quotaManager.getMain(mainAccountId)?.quota;
754
1002
  if (!mainQuota || quotaManager.isMainStale(options.model.id)) {
755
1003
  try {
756
- mainQuota = await quotaManager.refreshMain(mainAccountId, options.primaryAccessToken);
1004
+ mainQuota = await quotaManager.refreshMain(mainAccountId, primaryCredential);
757
1005
  }
758
1006
  catch { }
759
1007
  }
@@ -780,7 +1028,7 @@ async function executeWithFallback(options) {
780
1028
  }
781
1029
  const primary = await sendAnthropicRequest({
782
1030
  ...options,
783
- accessToken: options.primaryAccessToken,
1031
+ accessToken: primaryCredential,
784
1032
  oauthAccountId: STICKY_ROUTING_MAIN_ACCOUNT_ID,
785
1033
  });
786
1034
  const primaryPreflight = await firstStreamingError(primary);
@@ -812,9 +1060,10 @@ export function streamCortexKitAnthropic(model, context, options, effortTransiti
812
1060
  stream.push({ type: 'start', partial: output });
813
1061
  try {
814
1062
  const accessToken = options?.apiKey ?? '';
815
- if (!accessToken)
816
- throw new Error('Missing Anthropic OAuth access token');
817
1063
  const storagePath = getPiAccountStoragePath();
1064
+ if (!accessToken &&
1065
+ getClaustrumMode(await loadAccounts(storagePath)) !== 'claustrum')
1066
+ throw new Error('Missing Anthropic OAuth access token');
818
1067
  const response = await executeWithFallback({
819
1068
  model,
820
1069
  context,