@cortexkit/pi-anthropic-auth 1.21.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/README.md +20 -3
- package/dist/commands.d.ts +2 -1
- package/dist/commands.js +45 -4
- package/dist/convert.d.ts +6 -2
- package/dist/convert.js +132 -29
- package/dist/custody.d.ts +17 -0
- package/dist/custody.js +66 -0
- package/dist/effort-history.d.ts +27 -0
- package/dist/effort-history.js +103 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +143 -19
- package/dist/paths.d.ts +11 -0
- package/dist/paths.js +42 -2
- package/dist/stream.d.ts +4 -2
- package/dist/stream.js +316 -47
- package/package.json +5 -5
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, isApiKeyAccount, isCache1hPersistentlyEnabled, isCacheKeepHybridActive, isDumpPersistentlyEnabled, isFastModePersistentlyEnabled, isKillswitchEnabled, isOAuthAccount, isPermanentRefreshError, isValidApiBaseURL, killswitchPassesPolicy, loadAccounts, mergeAnthropicBetas, QuotaManager, quotaSnapshotHasStandardWindows, quotaSnapshotModelScopeIsExhausted, quotaSnapshotPassesModelScope, quotaSnapshotPassesPolicy, refreshBackoffActive, resolveClaudeCodeIdentity, STICKY_ROUTING_MAIN_ACCOUNT_ID, StickySessionRouter, sendViaRelay, setDumpEnabled, shouldFallbackStatus, stickyQuotaSnapshotIsFresh, stickyRetryAfterWithJitter, stickyRouteFamilyForModel, } 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 {
|
|
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({
|
|
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,26 +143,64 @@ 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
|
|
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);
|
|
92
193
|
headers.delete('anthropic-beta');
|
|
93
|
-
applyClaudeCodeHeaders(headers, accessToken, {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
194
|
+
applyClaudeCodeHeaders(headers, accessToken, {
|
|
195
|
+
body,
|
|
196
|
+
identity,
|
|
197
|
+
extraBetas: [
|
|
198
|
+
CACHE_KEEP_EXTENDED_TTL_BETA,
|
|
199
|
+
...(hasThinkingBindingControls(body)
|
|
200
|
+
? [THINKING_BINDING_CONTROLS_BETA]
|
|
201
|
+
: []),
|
|
202
|
+
],
|
|
203
|
+
});
|
|
97
204
|
if (body.speed === 'fast') {
|
|
98
205
|
headers.set('anthropic-beta', mergeAnthropicBetas(headers.get('anthropic-beta'), [FAST_MODE_BETA]));
|
|
99
206
|
}
|
|
@@ -103,6 +210,14 @@ const cacheKeepManager = new CacheKeepManager({
|
|
|
103
210
|
}
|
|
104
211
|
return headers;
|
|
105
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
|
+
},
|
|
106
221
|
});
|
|
107
222
|
export async function getPiTrackedCacheKeepSessions() {
|
|
108
223
|
return getPiCacheKeepRegistry().list(cacheKeepManager.trackedSessions());
|
|
@@ -158,7 +273,16 @@ function updateUsage(model, output, usage) {
|
|
|
158
273
|
}
|
|
159
274
|
export function buildExplicitBaseMessagesUrl(baseURL) {
|
|
160
275
|
const url = new URL(baseURL);
|
|
161
|
-
|
|
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
|
+
}
|
|
162
286
|
url.searchParams.set('beta', 'true');
|
|
163
287
|
return url;
|
|
164
288
|
}
|
|
@@ -177,6 +301,7 @@ export function configureApiRouteHeaders(account, fastMode) {
|
|
|
177
301
|
if (fastMode) {
|
|
178
302
|
headers.set('anthropic-beta', mergeAnthropicBetas(headers.get('anthropic-beta'), [FAST_MODE_BETA]));
|
|
179
303
|
}
|
|
304
|
+
applyCustomHeaders(headers);
|
|
180
305
|
return headers;
|
|
181
306
|
}
|
|
182
307
|
export async function* parseSse(response) {
|
|
@@ -224,19 +349,47 @@ async function sendAnthropicRequest(options) {
|
|
|
224
349
|
const accountIdentity = options.oauthAccountId === STICKY_ROUTING_MAIN_ACCOUNT_ID
|
|
225
350
|
? storage?.mainAccountId
|
|
226
351
|
: options.oauthAccountId;
|
|
227
|
-
const
|
|
228
|
-
?
|
|
352
|
+
const scoped = !options.apiAccount && getClaustrumMode(storage) === 'claustrum'
|
|
353
|
+
? getPiScopedRuntime(options.storagePath)
|
|
354
|
+
: undefined;
|
|
355
|
+
const routeId = options.oauthAccountId ?? STICKY_ROUTING_MAIN_ACCOUNT_ID;
|
|
356
|
+
const roster = scoped
|
|
357
|
+
? (scoped.snapshot() ?? (await scoped.refresh()))
|
|
229
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;
|
|
230
370
|
const { body, bodyText } = await buildAnthropicRequest(options.model.id, options.context, options.streamOptions, {
|
|
231
371
|
enabled: isCache1hPersistentlyEnabled(storage),
|
|
232
372
|
mode: getCache1hPersistentMode(storage),
|
|
233
|
-
}, isFastModePersistentlyEnabled(storage), identity
|
|
373
|
+
}, isFastModePersistentlyEnabled(storage), identity, {
|
|
374
|
+
effortTransitions: options.apiAccount ? [] : options.effortTransitions,
|
|
375
|
+
thinkingPrefixMismatchBehavior: options.apiAccount
|
|
376
|
+
? 'account-default'
|
|
377
|
+
: getThinkingPrefixMismatchBehavior(storage),
|
|
378
|
+
});
|
|
234
379
|
const fastMode = body.speed === 'fast';
|
|
235
380
|
const headers = options.apiAccount
|
|
236
381
|
? configureApiRouteHeaders(options.apiAccount, fastMode)
|
|
237
|
-
: applyClaudeCodeHeaders(new Headers(), options.accessToken ?? '', {
|
|
382
|
+
: applyClaudeCodeHeaders(new Headers(), scoped ? '' : (options.accessToken ?? ''), {
|
|
238
383
|
body,
|
|
239
384
|
identity,
|
|
385
|
+
extraBetas: [
|
|
386
|
+
...(hasThinkingBindingControls(body)
|
|
387
|
+
? [THINKING_BINDING_CONTROLS_BETA]
|
|
388
|
+
: []),
|
|
389
|
+
...(usesMidConversationOutputConfig(body)
|
|
390
|
+
? [MID_CONVERSATION_OUTPUT_CONFIG_BETA]
|
|
391
|
+
: []),
|
|
392
|
+
],
|
|
240
393
|
});
|
|
241
394
|
if (!options.apiAccount && fastMode) {
|
|
242
395
|
headers.set('anthropic-beta', mergeAnthropicBetas(headers.get('anthropic-beta'), [FAST_MODE_BETA]));
|
|
@@ -257,12 +410,31 @@ async function sendAnthropicRequest(options) {
|
|
|
257
410
|
headers,
|
|
258
411
|
bodyText,
|
|
259
412
|
storage,
|
|
260
|
-
cacheMode: isCacheKeepHybridActive(storage)
|
|
413
|
+
cacheMode: !options.apiAccount && isCacheKeepHybridActive(storage)
|
|
414
|
+
? 'hybrid'
|
|
415
|
+
: 'disabled',
|
|
261
416
|
oauthAccountId: options.oauthAccountId,
|
|
417
|
+
oauthAccountIdentity: verifiedUuid ?? identity?.accountUuid,
|
|
418
|
+
accountStoragePath: options.storagePath,
|
|
262
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
|
+
};
|
|
263
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}`);
|
|
264
434
|
try {
|
|
265
435
|
const response = await fetch(input, init);
|
|
436
|
+
if (attempt)
|
|
437
|
+
await report(attempt, response.status, 'direct');
|
|
266
438
|
await dumpDirectRequest({
|
|
267
439
|
affinity: relayAffinity,
|
|
268
440
|
route: options.route ??
|
|
@@ -299,6 +471,19 @@ async function sendAnthropicRequest(options) {
|
|
|
299
471
|
body: bodyText,
|
|
300
472
|
fallback: directFetch,
|
|
301
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,
|
|
302
487
|
});
|
|
303
488
|
}
|
|
304
489
|
function quotaSnapshotIsExhausted(quota) {
|
|
@@ -329,17 +514,62 @@ async function firstStreamingError(response) {
|
|
|
329
514
|
}
|
|
330
515
|
async function executeWithFallback(options) {
|
|
331
516
|
await ensurePiMainAccountId(options.storagePath);
|
|
332
|
-
|
|
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));
|
|
333
534
|
const { quotaManager, fallbackManager: manager } = getPiRoutingServices(options.storagePath, storage);
|
|
334
|
-
|
|
535
|
+
let mainAccountId = scoped
|
|
536
|
+
? roster?.primary?.accountId
|
|
537
|
+
: storage?.mainAccountId;
|
|
335
538
|
quotaManager.seedMainFromStorage(storage, mainAccountId);
|
|
336
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
|
+
}
|
|
337
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
|
+
}
|
|
338
567
|
const mainEntry = quotaManager.getMain(mainAccountId);
|
|
339
568
|
let mainQuota = mainEntry?.quota;
|
|
340
|
-
if (
|
|
569
|
+
if (primaryAvailable() &&
|
|
570
|
+
!stickyQuotaSnapshotIsFresh(mainEntry?.quota, storage, Date.now(), modelId)) {
|
|
341
571
|
try {
|
|
342
|
-
mainQuota = await quotaManager.refreshMain(mainAccountId,
|
|
572
|
+
mainQuota = await quotaManager.refreshMain(mainAccountId, primaryCredential);
|
|
343
573
|
}
|
|
344
574
|
catch { }
|
|
345
575
|
}
|
|
@@ -348,10 +578,12 @@ async function executeWithFallback(options) {
|
|
|
348
578
|
});
|
|
349
579
|
const usableById = new Map(usableFallbacks.map((account) => [account.id, account]));
|
|
350
580
|
const allRoutes = [];
|
|
351
|
-
if (
|
|
581
|
+
if (primaryAvailable() &&
|
|
582
|
+
(scoped ||
|
|
583
|
+
!isPermanentRefreshError(storage?.refresh?.mainLastRefreshError))) {
|
|
352
584
|
allRoutes.push({
|
|
353
585
|
id: STICKY_ROUTING_MAIN_ACCOUNT_ID,
|
|
354
|
-
access:
|
|
586
|
+
access: primaryCredential,
|
|
355
587
|
quota: mainQuota,
|
|
356
588
|
order: 0,
|
|
357
589
|
});
|
|
@@ -360,18 +592,20 @@ async function executeWithFallback(options) {
|
|
|
360
592
|
if (configured.enabled === false || !isOAuthAccount(configured))
|
|
361
593
|
continue;
|
|
362
594
|
const account = usableById.get(configured.id) ?? configured;
|
|
363
|
-
if (
|
|
595
|
+
if (scoped
|
|
596
|
+
? !scopedReady(account)
|
|
597
|
+
: !account.access || isPermanentRefreshError(account.lastRefreshError))
|
|
364
598
|
continue;
|
|
365
599
|
let accountQuota = quotaManager.getFallback(account.id, account)?.quota ?? account.quota;
|
|
366
600
|
if (!stickyQuotaSnapshotIsFresh(accountQuota, storage, Date.now(), modelId)) {
|
|
367
601
|
try {
|
|
368
|
-
accountQuota = await quotaManager.refreshFallback(account.id, account.access, account);
|
|
602
|
+
accountQuota = await quotaManager.refreshFallback(account.id, scoped ? '' : (account.access ?? ''), account);
|
|
369
603
|
}
|
|
370
604
|
catch { }
|
|
371
605
|
}
|
|
372
606
|
allRoutes.push({
|
|
373
607
|
id: account.id,
|
|
374
|
-
access: account.access,
|
|
608
|
+
access: scoped ? '' : (account.access ?? ''),
|
|
375
609
|
quota: accountQuota,
|
|
376
610
|
order: index + 1,
|
|
377
611
|
account,
|
|
@@ -381,7 +615,7 @@ async function executeWithFallback(options) {
|
|
|
381
615
|
const refreshError = route.id === STICKY_ROUTING_MAIN_ACCOUNT_ID
|
|
382
616
|
? storage?.refresh?.mainLastRefreshError
|
|
383
617
|
: route.account?.lastRefreshError;
|
|
384
|
-
if (isPermanentRefreshError(refreshError))
|
|
618
|
+
if (!scoped && isPermanentRefreshError(refreshError))
|
|
385
619
|
return [];
|
|
386
620
|
if (stickyQuotaSnapshotIsFresh(route.quota, storage, Date.now(), modelId) &&
|
|
387
621
|
decideStickyQuotaFailure({ quota: route.quota, modelId }).action ===
|
|
@@ -424,8 +658,10 @@ async function executeWithFallback(options) {
|
|
|
424
658
|
return { allRoutes, candidates, retainAccountIds };
|
|
425
659
|
}
|
|
426
660
|
async function primaryQuotaRefreshConfirmsExhausted() {
|
|
661
|
+
if (!primaryAvailable())
|
|
662
|
+
return false;
|
|
427
663
|
try {
|
|
428
|
-
const quota = await quotaManager.refreshMain(mainAccountId,
|
|
664
|
+
const quota = await quotaManager.refreshMain(mainAccountId, primaryCredential);
|
|
429
665
|
const entry = quotaManager.getMain(mainAccountId);
|
|
430
666
|
return Boolean(entry &&
|
|
431
667
|
entry.refreshAfter > Date.now() &&
|
|
@@ -436,8 +672,10 @@ async function executeWithFallback(options) {
|
|
|
436
672
|
}
|
|
437
673
|
}
|
|
438
674
|
async function primaryQuotaRefreshConfirmsModelScopeExhausted() {
|
|
675
|
+
if (!primaryAvailable())
|
|
676
|
+
return false;
|
|
439
677
|
try {
|
|
440
|
-
const quota = await quotaManager.refreshMain(mainAccountId,
|
|
678
|
+
const quota = await quotaManager.refreshMain(mainAccountId, primaryCredential);
|
|
441
679
|
const entry = quotaManager.getMain(mainAccountId);
|
|
442
680
|
return Boolean(entry &&
|
|
443
681
|
entry.refreshAfter > Date.now() &&
|
|
@@ -448,11 +686,15 @@ async function executeWithFallback(options) {
|
|
|
448
686
|
}
|
|
449
687
|
}
|
|
450
688
|
function primaryCachedModelScopeExhausted() {
|
|
689
|
+
if (!primaryAvailable())
|
|
690
|
+
return false;
|
|
451
691
|
const entry = quotaManager.getMain(mainAccountId);
|
|
452
692
|
return Boolean(entry &&
|
|
453
693
|
quotaSnapshotModelScopeIsExhausted(entry.quota, options.model.id));
|
|
454
694
|
}
|
|
455
695
|
function primaryFreshModelScopeExhausted() {
|
|
696
|
+
if (!primaryAvailable())
|
|
697
|
+
return false;
|
|
456
698
|
const entry = quotaManager.getMain(mainAccountId);
|
|
457
699
|
return Boolean(entry &&
|
|
458
700
|
!quotaManager.isMainStale(options.model.id) &&
|
|
@@ -464,17 +706,21 @@ async function executeWithFallback(options) {
|
|
|
464
706
|
});
|
|
465
707
|
const usableOAuthById = new Map(usableOAuth.map((account) => [account.id, account]));
|
|
466
708
|
for (const configured of storage?.accounts ?? []) {
|
|
709
|
+
if (configured.enabled === false)
|
|
710
|
+
continue;
|
|
467
711
|
let response = null;
|
|
468
712
|
let account = isOAuthAccount(configured)
|
|
469
713
|
? usableOAuthById.get(configured.id)
|
|
470
714
|
: configured;
|
|
471
715
|
if (!account) {
|
|
472
716
|
if (!isOAuthAccount(configured) ||
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
configured.
|
|
476
|
-
|
|
477
|
-
|
|
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))))
|
|
478
724
|
continue;
|
|
479
725
|
account = configured;
|
|
480
726
|
const quota = quotaManager.getFallback(configured.id, configured)?.quota ??
|
|
@@ -491,11 +737,12 @@ async function executeWithFallback(options) {
|
|
|
491
737
|
continue;
|
|
492
738
|
}
|
|
493
739
|
if (isOAuthAccount(account)) {
|
|
494
|
-
if (routeOptions.apiOnly === true ||
|
|
740
|
+
if (routeOptions.apiOnly === true ||
|
|
741
|
+
(scoped ? !scopedReady(account) : !account.access))
|
|
495
742
|
continue;
|
|
496
743
|
response = await sendAnthropicRequest({
|
|
497
744
|
...options,
|
|
498
|
-
accessToken: account.access,
|
|
745
|
+
accessToken: scoped ? undefined : account.access,
|
|
499
746
|
oauthAccountId: account.id,
|
|
500
747
|
});
|
|
501
748
|
}
|
|
@@ -530,15 +777,19 @@ async function executeWithFallback(options) {
|
|
|
530
777
|
const router = getPiStickyRouter(options.storagePath);
|
|
531
778
|
const initialInputBytes = Math.max(1, Buffer.byteLength(JSON.stringify(options.context)));
|
|
532
779
|
let routes = await buildStickyRoutes(options.model.id);
|
|
533
|
-
const mainPermanentlyUnavailable =
|
|
780
|
+
const mainPermanentlyUnavailable = scoped
|
|
781
|
+
? !primaryAvailable()
|
|
782
|
+
: isPermanentRefreshError(storage?.refresh?.mainLastRefreshError);
|
|
534
783
|
const incompleteQuotaPool = (routes.allRoutes.length === 0 && !mainPermanentlyUnavailable) ||
|
|
535
|
-
routes.allRoutes.some((candidate) => !candidate.quota
|
|
536
|
-
!stickyQuotaSnapshotIsFresh(candidate.quota, storage, Date.now(), options.model.id));
|
|
784
|
+
routes.allRoutes.some((candidate) => !quotaObservationIsFresh(candidate.quota, options.model.id));
|
|
537
785
|
let resolution = await router.resolve({
|
|
538
786
|
sessionId,
|
|
539
787
|
family: stickyRouteFamilyForModel(options.model.id),
|
|
540
788
|
modelId: options.model.id,
|
|
541
|
-
|
|
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,
|
|
542
793
|
retainAccountIds: routes.retainAccountIds,
|
|
543
794
|
storage,
|
|
544
795
|
inputBytes: initialInputBytes,
|
|
@@ -553,7 +804,9 @@ async function executeWithFallback(options) {
|
|
|
553
804
|
}
|
|
554
805
|
if (!resolution) {
|
|
555
806
|
return createStickyNoRouteResponse({
|
|
556
|
-
mainRefreshError:
|
|
807
|
+
mainRefreshError: scoped
|
|
808
|
+
? undefined
|
|
809
|
+
: storage?.refresh?.mainLastRefreshError,
|
|
557
810
|
fallbackReauthLabels: getFallbackReauthLabels(storage),
|
|
558
811
|
routeQuotas: routes.allRoutes.flatMap((route) => route.quota ? [route.quota] : []),
|
|
559
812
|
modelId: options.model.id,
|
|
@@ -600,11 +853,12 @@ async function executeWithFallback(options) {
|
|
|
600
853
|
}
|
|
601
854
|
let permanentAuthFailure = preflight instanceof Response &&
|
|
602
855
|
preflight.status === 401 &&
|
|
603
|
-
route.id === STICKY_ROUTING_MAIN_ACCOUNT_ID;
|
|
856
|
+
(Boolean(scoped) || route.id === STICKY_ROUTING_MAIN_ACCOUNT_ID);
|
|
604
857
|
if (preflight instanceof Response &&
|
|
605
858
|
preflight.status === 401 &&
|
|
606
859
|
route.account &&
|
|
607
|
-
storage
|
|
860
|
+
storage &&
|
|
861
|
+
!scoped) {
|
|
608
862
|
const authRouteId = route.id;
|
|
609
863
|
try {
|
|
610
864
|
const refreshed = await manager.refreshAccount(route.account, storage, {
|
|
@@ -730,11 +984,24 @@ async function executeWithFallback(options) {
|
|
|
730
984
|
if (fallback)
|
|
731
985
|
return fallback;
|
|
732
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
|
+
}
|
|
733
1000
|
if (isKillswitchEnabled(storage)) {
|
|
734
1001
|
let mainQuota = quotaManager.getMain(mainAccountId)?.quota;
|
|
735
1002
|
if (!mainQuota || quotaManager.isMainStale(options.model.id)) {
|
|
736
1003
|
try {
|
|
737
|
-
mainQuota = await quotaManager.refreshMain(mainAccountId,
|
|
1004
|
+
mainQuota = await quotaManager.refreshMain(mainAccountId, primaryCredential);
|
|
738
1005
|
}
|
|
739
1006
|
catch { }
|
|
740
1007
|
}
|
|
@@ -761,7 +1028,7 @@ async function executeWithFallback(options) {
|
|
|
761
1028
|
}
|
|
762
1029
|
const primary = await sendAnthropicRequest({
|
|
763
1030
|
...options,
|
|
764
|
-
accessToken:
|
|
1031
|
+
accessToken: primaryCredential,
|
|
765
1032
|
oauthAccountId: STICKY_ROUTING_MAIN_ACCOUNT_ID,
|
|
766
1033
|
});
|
|
767
1034
|
const primaryPreflight = await firstStreamingError(primary);
|
|
@@ -786,22 +1053,24 @@ async function executeWithFallback(options) {
|
|
|
786
1053
|
}
|
|
787
1054
|
return primaryPreflight instanceof Response ? primaryPreflight : primary;
|
|
788
1055
|
}
|
|
789
|
-
export function streamCortexKitAnthropic(model, context, options) {
|
|
1056
|
+
export function streamCortexKitAnthropic(model, context, options, effortTransitions) {
|
|
790
1057
|
const stream = createAssistantMessageEventStream();
|
|
791
1058
|
void (async () => {
|
|
792
1059
|
const output = createOutput(model);
|
|
793
1060
|
stream.push({ type: 'start', partial: output });
|
|
794
1061
|
try {
|
|
795
1062
|
const accessToken = options?.apiKey ?? '';
|
|
796
|
-
if (!accessToken)
|
|
797
|
-
throw new Error('Missing Anthropic OAuth access token');
|
|
798
1063
|
const storagePath = getPiAccountStoragePath();
|
|
1064
|
+
if (!accessToken &&
|
|
1065
|
+
getClaustrumMode(await loadAccounts(storagePath)) !== 'claustrum')
|
|
1066
|
+
throw new Error('Missing Anthropic OAuth access token');
|
|
799
1067
|
const response = await executeWithFallback({
|
|
800
1068
|
model,
|
|
801
1069
|
context,
|
|
802
1070
|
streamOptions: options,
|
|
803
1071
|
primaryAccessToken: accessToken,
|
|
804
1072
|
storagePath,
|
|
1073
|
+
effortTransitions,
|
|
805
1074
|
});
|
|
806
1075
|
if (!response.ok) {
|
|
807
1076
|
throw new Error(`Anthropic request failed: HTTP ${response.status} ${await response.text()}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/pi-anthropic-auth",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.23.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
"prepublishOnly": "bun run build"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@cortexkit/anthropic-auth-core": "1.
|
|
39
|
+
"@cortexkit/anthropic-auth-core": "1.23.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@earendil-works/pi-ai": "
|
|
43
|
-
"@earendil-works/pi-coding-agent": "
|
|
44
|
-
"@earendil-works/pi-tui": "
|
|
42
|
+
"@earendil-works/pi-ai": ">=0.86.1",
|
|
43
|
+
"@earendil-works/pi-coding-agent": ">=0.86.1",
|
|
44
|
+
"@earendil-works/pi-tui": ">=0.86.1"
|
|
45
45
|
}
|
|
46
46
|
}
|