@cortexkit/pi-anthropic-auth 1.20.0 → 1.22.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 +6 -3
- package/dist/commands.js +28 -1
- package/dist/convert.d.ts +5 -2
- package/dist/convert.js +65 -25
- package/dist/effort-history.d.ts +12 -0
- package/dist/effort-history.js +63 -0
- package/dist/index.js +45 -16
- package/dist/stream.d.ts +2 -2
- package/dist/stream.js +147 -33
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Pi package for CortexKit Anthropic OAuth support. It overrides Pi's built-in `anthropic` provider with a CortexKit provider extension backed by the shared `@cortexkit/anthropic-auth-core` package.
|
|
4
4
|
|
|
5
|
-
The Pi provider catalog includes Claude Fable 5 (`claude-fable-5`), limited-access Claude Mythos 5 (`claude-mythos-5`), Claude Opus 5 (`claude-opus-5`), Claude Opus 4.8, Claude Opus 4.5, Claude Sonnet 4.5, and Claude Sonnet 5 (`claude-sonnet-5`). Fable/Mythos reasoning uses Anthropic adaptive thinking with `thinking.display: "summarized"` and `output_config.effort`; the package does not send rejected manual `thinking.budget_tokens` for those models.
|
|
5
|
+
The Pi provider catalog includes Claude Fable 5 (`claude-fable-5`) and 5.1 (`claude-fable-5-1`), limited-access Claude Mythos 5 (`claude-mythos-5`) and 5.1 (`claude-mythos-5-1`), Claude Opus 5 (`claude-opus-5`), Claude Opus 4.8, Claude Opus 4.5, Claude Sonnet 4.5, and Claude Sonnet 5 (`claude-sonnet-5`). Fable/Mythos reasoning uses Anthropic adaptive thinking with `thinking.display: "summarized"` and `output_config.effort`; the package does not send rejected manual `thinking.budget_tokens` for those models. OAuth Fable 5.1 sessions preserve Pi thinking-level changes as mid-conversation effort markers, so switching effort does not rewrite the cached prefix. Replayed signed/redacted thinking follows the configured prefix-mismatch behavior.
|
|
6
6
|
|
|
7
7
|
This package is part of the CortexKit Anthropic Auth monorepo, which supports both OpenCode (`@cortexkit/opencode-anthropic-auth`) and Pi (`@cortexkit/pi-anthropic-auth`) through the same shared core logic.
|
|
8
8
|
|
|
@@ -42,7 +42,7 @@ Pi state is stored separately from OpenCode at:
|
|
|
42
42
|
|
|
43
43
|
Override the path with `PI_ANTHROPIC_AUTH_FILE`. The package also respects `PI_AGENT_DIR` when deriving the default sidecar path.
|
|
44
44
|
|
|
45
|
-
The sidecar uses the same JSON shape as the OpenCode package, including `routing`, `claudeCache`, `cacheKeep`, `prime`, `claudeFast`, `dump`, `relay`, and fallback `accounts` blocks. Runtime OAuth/quota state is stored in `anthropic-auth-state.json`; sticky session assignments are stored separately in `anthropic-auth-routing-state.json` with SHA-256-hashed session IDs.
|
|
45
|
+
The sidecar uses the same JSON shape as the OpenCode package, including `routing`, `claudeCache`, `cacheKeep`, `prime`, `claudeFast`, `thinkingBinding`, `dump`, `relay`, and fallback `accounts` blocks. `thinkingBinding.prefixMismatchBehavior` accepts `account-default` (default), `error`, or `drop_block` for OAuth Fable 5.1 replay. Runtime OAuth/quota state is stored in `anthropic-auth-state.json`; sticky session assignments are stored separately in `anthropic-auth-routing-state.json` with SHA-256-hashed session IDs.
|
|
46
46
|
|
|
47
47
|
## Commands
|
|
48
48
|
|
|
@@ -77,10 +77,13 @@ The sidecar uses the same JSON shape as the OpenCode package, including `routing
|
|
|
77
77
|
/claude-routing sticky-balanced
|
|
78
78
|
/claude-routing reset
|
|
79
79
|
|
|
80
|
+
/claude-account
|
|
81
|
+
/claude-account reset-backoff
|
|
82
|
+
|
|
80
83
|
/claude-quota
|
|
81
84
|
```
|
|
82
85
|
|
|
83
|
-
`/claude-quota` reports sidecar OAuth fallback quota state from `~/.pi/agent/anthropic-auth.json`. `/claude-routing fallback-first` prefers usable OAuth fallback accounts before the main account; `/claude-routing main-first` restores the default. `/claude-routing sticky-balanced` assigns each Pi session to an OAuth account according to current 5-hour, 7-day, and matching model-scoped quota headroom, then persists that assignment across transient failures and process restarts. `/claude-routing reset` clears the current Pi session's assignment. Direct Opus sessions prefer usable accounts whose Fable quota is exhausted. API-key routes use the same sidecar shape as OpenCode and are sent directly to their configured Anthropic-compatible base URL, such as Kie's `https://api.kie.ai/claude`, but Pi only uses them after the main OAuth model response reports HTTP 429 or a streaming rate-limit error and a live quota check confirms 0% remaining. `/claude-cachekeep always` keeps active hybrid caches warm while Pi remains open; `/claude-cachekeep HH-HH` limits prewarms to a local time window. Both send `max_tokens: 0` pre-warm requests about five minutes before the 1-hour TTL expires. Running `/claude-cachekeep` without arguments lists live tracked sessions across Pi processes through a temporary lease registry that stores only session IDs and cache timing. `/claude-fast on` adds Anthropic `speed: "fast"` plus the `fast-mode-2026-02-01` beta header for supported Opus models (`claude-opus-4-6`, `claude-opus-4-7`, `claude-opus-4-8`, and `claude-opus-5`).
|
|
86
|
+
`/claude-account reset-backoff` clears the main OAuth refresh backoff and its matching quota backoff. Persisted refresh backoffs are bound to the refresh token that produced them, so replacing a rejected credential immediately escapes its stale latch. `/claude-quota` reports sidecar OAuth fallback quota state from `~/.pi/agent/anthropic-auth.json`. `/claude-routing fallback-first` prefers usable OAuth fallback accounts before the main account; `/claude-routing main-first` restores the default. `/claude-routing sticky-balanced` assigns each Pi session to an OAuth account according to current 5-hour, 7-day, and matching model-scoped quota headroom, then persists that assignment across transient failures and process restarts. `/claude-routing reset` clears the current Pi session's assignment. Direct Opus sessions prefer usable accounts whose Fable quota is exhausted. API-key routes use the same sidecar shape as OpenCode and are sent directly to their configured Anthropic-compatible base URL, such as Kie's `https://api.kie.ai/claude`, but Pi only uses them after the main OAuth model response reports HTTP 429 or a streaming rate-limit error and a live quota check confirms 0% remaining. `/claude-cachekeep always` keeps active hybrid caches warm while Pi remains open; `/claude-cachekeep HH-HH` limits prewarms to a local time window. Both send `max_tokens: 0` pre-warm requests about five minutes before the 1-hour TTL expires. Running `/claude-cachekeep` without arguments lists live tracked sessions across Pi processes through a temporary lease registry that stores only session IDs and cache timing. `/claude-fast on` adds Anthropic `speed: "fast"` plus the `fast-mode-2026-02-01` beta header for supported Opus models (`claude-opus-4-6`, `claude-opus-4-7`, `claude-opus-4-8`, and `claude-opus-5`).
|
|
84
87
|
|
|
85
88
|
### Claude quota window priming
|
|
86
89
|
|
package/dist/commands.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { buildClaudeQuotaSummary, buildFallbackQuotaSummaries, buildPrimeAccountStatuses, CLAUDE_ACCOUNT_COMMAND_NAME, CLAUDE_CACHE_KEEP_COMMAND_NAME, CLAUDE_PRIME_COMMAND_NAME, CLAUDE_ROUTING_COMMAND_NAME, createEmptyStorage, executeAccountCommand, executeCache1hCommand, executeCacheKeepCommand, executeDumpCommand, executeFastModeCommand, executeLoggingCommand, executePrimeCommand, executeRoutingCommand, getCache1hPersistentMode, getCacheKeepWindow, getPersistedLogLevel, getRoutingMode, isCache1hPersistentlyEnabled, isCacheKeepAlways, isCacheKeepHybridActive, isCacheKeepPersistentlyEnabled, isDumpPersistentlyEnabled, isFastModePersistentlyEnabled, isPrimePersistentlyEnabled, loadAccounts, parseCache1hCommandAction, parseCacheKeepCommandAction, parseDumpCommandAction, parseFastModeCommandAction, parseLoggingCommandAction, parseRoutingCommandAction, removeAccountPersistent, reorderAccountsPersistent, setAccountEnabledPersistent, setCache1hPersistentEnabled, setCache1hPersistentMode, setCacheKeepPersistentAlways, setCacheKeepPersistentEnabled, setCacheKeepPersistentWindow, setCacheKeepSubagentsEnabled, setDumpEnabled, setDumpPersistentEnabled, setFastModePersistentEnabled, setLogLevelPersistent, setRoutingMode, } from '@cortexkit/anthropic-auth-core';
|
|
1
|
+
import { buildClaudeQuotaSummary, buildFallbackQuotaSummaries, buildPrimeAccountStatuses, CLAUDE_ACCOUNT_COMMAND_NAME, CLAUDE_CACHE_KEEP_COMMAND_NAME, CLAUDE_PRIME_COMMAND_NAME, CLAUDE_ROUTING_COMMAND_NAME, createEmptyStorage, detectClaustrumConnection, executeAccountCommand, executeCache1hCommand, executeCacheKeepCommand, executeDumpCommand, executeFastModeCommand, executeLoggingCommand, executePrimeCommand, executeRoutingCommand, getCache1hPersistentMode, getCacheKeepWindow, getPersistedLogLevel, getRoutingMode, isCache1hPersistentlyEnabled, isCacheKeepAlways, isCacheKeepHybridActive, isCacheKeepPersistentlyEnabled, isDumpPersistentlyEnabled, isFastModePersistentlyEnabled, isPrimePersistentlyEnabled, loadAccounts, mergeMainQuotaErrorClearedAt, mergeMainRefreshErrorClearedAt, parseAccountCommandAction, parseCache1hCommandAction, parseCacheKeepCommandAction, parseDumpCommandAction, parseFastModeCommandAction, parseLoggingCommandAction, parseRoutingCommandAction, removeAccountPersistent, reorderAccountsPersistent, saveAccountState, setAccountEnabledPersistent, setCache1hPersistentEnabled, setCache1hPersistentMode, setCacheKeepPersistentAlways, setCacheKeepPersistentEnabled, setCacheKeepPersistentWindow, setCacheKeepSubagentsEnabled, setDumpEnabled, setDumpPersistentEnabled, setFastModePersistentEnabled, setLogLevelPersistent, setRoutingMode, } from '@cortexkit/anthropic-auth-core';
|
|
2
2
|
import { getPiAccountStoragePath } from './paths.js';
|
|
3
3
|
import { clearPiStickyRoutingSession, getPiTrackedCacheKeepSessions, } from './stream.js';
|
|
4
4
|
function notify(ctx, message, kind = 'info') {
|
|
@@ -152,9 +152,13 @@ export function registerCommands(pi) {
|
|
|
152
152
|
handler: async (args, ctx) => {
|
|
153
153
|
const path = getPiAccountStoragePath();
|
|
154
154
|
const storage = await loadAccounts(path);
|
|
155
|
+
const action = parseAccountCommandAction(args ?? '');
|
|
155
156
|
const result = executeAccountCommand({
|
|
156
157
|
argumentsText: args ?? '',
|
|
157
158
|
storage: storage ?? createEmptyStorage(),
|
|
159
|
+
claustrum: action.type === 'status'
|
|
160
|
+
? await detectClaustrumConnection()
|
|
161
|
+
: undefined,
|
|
158
162
|
});
|
|
159
163
|
if (!result.updated) {
|
|
160
164
|
notify(ctx, result.text);
|
|
@@ -181,6 +185,29 @@ export function registerCommands(pi) {
|
|
|
181
185
|
await reorderAccountsPersistent(newOrder, path);
|
|
182
186
|
}
|
|
183
187
|
}
|
|
188
|
+
else if (mutationAction === 'reset-backoff') {
|
|
189
|
+
const nextStorage = storage ?? createEmptyStorage();
|
|
190
|
+
nextStorage.refresh = nextStorage.refresh ?? {};
|
|
191
|
+
nextStorage.refresh.mainLastRefreshError = undefined;
|
|
192
|
+
nextStorage.refresh.mainRefreshErrorClearedAt =
|
|
193
|
+
mergeMainRefreshErrorClearedAt(nextStorage.refresh.mainRefreshErrorClearedAt, Date.now());
|
|
194
|
+
const mainIdentity = nextStorage.mainAccountId;
|
|
195
|
+
const quotaError = nextStorage.quota?.mainLastQuotaApiError;
|
|
196
|
+
if (!quotaError?.accountIdentity ||
|
|
197
|
+
!mainIdentity ||
|
|
198
|
+
quotaError.accountIdentity === mainIdentity) {
|
|
199
|
+
nextStorage.quota = nextStorage.quota ?? {};
|
|
200
|
+
nextStorage.quota.mainLastQuotaApiError = undefined;
|
|
201
|
+
const nextQuotaErrorGeneration = (nextStorage.quota.mainQuotaErrorGeneration ?? 0) + 1;
|
|
202
|
+
nextStorage.quota.mainQuotaErrorGeneration = Math.max(nextStorage.quota.mainQuotaErrorGeneration ?? 0, nextQuotaErrorGeneration);
|
|
203
|
+
nextStorage.quota.mainQuotaErrorClearedAt =
|
|
204
|
+
mergeMainQuotaErrorClearedAt(nextStorage.quota.mainQuotaErrorClearedAt, Date.now());
|
|
205
|
+
}
|
|
206
|
+
await saveAccountState(nextStorage, path, {
|
|
207
|
+
mainRefresh: true,
|
|
208
|
+
mainQuota: true,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
184
211
|
notify(ctx, result.text);
|
|
185
212
|
},
|
|
186
213
|
});
|
package/dist/convert.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Cache1hMode, type ClaudeCodeIdentity } from '@cortexkit/anthropic-auth-core';
|
|
1
|
+
import { type Cache1hMode, type ClaudeCodeIdentity, type MidConversationEffortTransition, type ThinkingPrefixMismatchBehavior } from '@cortexkit/anthropic-auth-core';
|
|
2
2
|
import type { Context, SimpleStreamOptions, Tool } from '@earendil-works/pi-ai';
|
|
3
3
|
export type AnthropicRequestBody = {
|
|
4
4
|
model: string;
|
|
@@ -26,7 +26,10 @@ export declare function fromClaudeCodeToolName(name: string, tools?: Tool[]): st
|
|
|
26
26
|
export declare function buildAnthropicRequest(modelId: string, context: Context, options: SimpleStreamOptions | undefined, cache: {
|
|
27
27
|
enabled: boolean;
|
|
28
28
|
mode: Cache1hMode;
|
|
29
|
-
}, fastModeEnabled?: boolean, identity?: ClaudeCodeIdentity
|
|
29
|
+
}, fastModeEnabled?: boolean, identity?: ClaudeCodeIdentity, controls?: {
|
|
30
|
+
effortTransitions?: readonly MidConversationEffortTransition[];
|
|
31
|
+
thinkingPrefixMismatchBehavior?: ThinkingPrefixMismatchBehavior;
|
|
32
|
+
}): Promise<{
|
|
30
33
|
body: AnthropicRequestBody;
|
|
31
34
|
bodyText: string;
|
|
32
35
|
}>;
|
package/dist/convert.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { applyClaudeCodeMetadata, buildBillingHeaderValue, CLAUDE_CODE_ENTRYPOINT, CLAUDE_CODE_IDENTITY, CLAUDE_FABLE_MYTHOS_5_SUMMARIZED_THINKING, CLAUDE_OPUS_5_ADAPTIVE_THINKING, CLAUDE_SONNET_5_ADAPTIVE_THINKING, isClaudeFableOrMythos5Model, isClaudeOpus5Model, isClaudeSonnet5Model, isFastModeSupportedModel, isOpenAIReasoningSignature, orderClaudeCodeBody, signRequestBody, } from '@cortexkit/anthropic-auth-core';
|
|
1
|
+
import { applyClaudeCodeMetadata, applyMidConversationOutputConfig, applyThinkingBindingControls, buildBillingHeaderValue, CLAUDE_CODE_ENTRYPOINT, CLAUDE_CODE_IDENTITY, CLAUDE_FABLE_MYTHOS_5_SUMMARIZED_THINKING, CLAUDE_OPUS_5_ADAPTIVE_THINKING, CLAUDE_SONNET_5_ADAPTIVE_THINKING, ClaudeCodeFirstUserTextTracker, isClaudeFableOrMythos5Model, isClaudeOpus5Model, isClaudeSonnet5Model, isFastModeSupportedModel, isOpenAIReasoningSignature, orderClaudeCodeBody, signRequestBody, } from '@cortexkit/anthropic-auth-core';
|
|
2
2
|
// Anchor identifying Pi's documentation paragraph — the only part of the prompt
|
|
3
3
|
// that Anthropic currently rejects in system[]. Unknown prompt shapes take the
|
|
4
4
|
// service-preserving fallback below instead of returning the full prompt there.
|
|
5
5
|
const PI_DOCS_ANCHOR = 'Pi documentation';
|
|
6
|
+
const firstUserTextTracker = new ClaudeCodeFirstUserTextTracker();
|
|
7
|
+
const ANTHROPIC_REPLAY_APIS = new Set([
|
|
8
|
+
'anthropic-messages',
|
|
9
|
+
'cortexkit-anthropic-messages',
|
|
10
|
+
]);
|
|
6
11
|
const CLAUDE_CODE_TOOLS = new Map([
|
|
7
12
|
'Read',
|
|
8
13
|
'Write',
|
|
@@ -105,7 +110,7 @@ function convertTextAndImages(content) {
|
|
|
105
110
|
}
|
|
106
111
|
return blocks;
|
|
107
112
|
}
|
|
108
|
-
function convertMessages(messages) {
|
|
113
|
+
function convertMessages(messages, targetModelId) {
|
|
109
114
|
const result = [];
|
|
110
115
|
for (let index = 0; index < messages.length; index++) {
|
|
111
116
|
const message = messages[index];
|
|
@@ -140,33 +145,63 @@ function convertMessages(messages) {
|
|
|
140
145
|
if (block.type === 'text' && block.text.trim()) {
|
|
141
146
|
blocks.push({ type: 'text', text: sanitize(block.text) });
|
|
142
147
|
}
|
|
143
|
-
else if (block.type === 'thinking'
|
|
148
|
+
else if (block.type === 'thinking') {
|
|
144
149
|
const thinking = block;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
150
|
+
const signature = thinking.thinkingSignature;
|
|
151
|
+
const hasSignature = typeof signature === 'string' && signature.trim().length > 0;
|
|
152
|
+
const signatureBelongsToTarget = message.provider === 'anthropic' &&
|
|
153
|
+
ANTHROPIC_REPLAY_APIS.has(message.api) &&
|
|
154
|
+
message.model === targetModelId &&
|
|
155
|
+
!isOpenAIReasoningSignature(signature);
|
|
156
|
+
// Match Pi's built-in origin check: only replay signatures from the
|
|
157
|
+
// same Anthropic API/model. Preserve visible foreign reasoning as
|
|
158
|
+
// ordinary text, but drop opaque redacted/OpenAI encrypted state.
|
|
159
|
+
if (hasSignature && !signatureBelongsToTarget) {
|
|
160
|
+
if (!thinking.redacted &&
|
|
161
|
+
!isOpenAIReasoningSignature(signature) &&
|
|
162
|
+
thinking.thinking.trim()) {
|
|
163
|
+
blocks.push({ type: 'text', text: sanitize(thinking.thinking) });
|
|
164
|
+
}
|
|
149
165
|
continue;
|
|
150
166
|
}
|
|
151
|
-
if (thinking.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
167
|
+
if (thinking.redacted) {
|
|
168
|
+
if (hasSignature) {
|
|
169
|
+
blocks.push({
|
|
170
|
+
type: 'redacted_thinking',
|
|
171
|
+
data: signature,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (!hasSignature) {
|
|
177
|
+
// Anthropic rejects unsigned thinking blocks. Preserve readable
|
|
178
|
+
// output from interrupted streams as ordinary assistant text.
|
|
179
|
+
if (thinking.thinking.trim()) {
|
|
180
|
+
blocks.push({
|
|
181
|
+
type: 'text',
|
|
182
|
+
text: sanitize(thinking.thinking),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (hasLoneSurrogate(thinking.thinking)) {
|
|
188
|
+
// Signature authenticates the thinking bytes exactly. Sanitizing the
|
|
189
|
+
// text would invalidate it, so downgrade malformed signed thinking
|
|
190
|
+
// to plain text and omit the signature instead.
|
|
191
|
+
if (thinking.thinking.trim()) {
|
|
192
|
+
blocks.push({
|
|
193
|
+
type: 'text',
|
|
194
|
+
text: sanitize(thinking.thinking),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
157
199
|
blocks.push({
|
|
158
200
|
type: 'thinking',
|
|
159
201
|
thinking: thinking.thinking,
|
|
160
|
-
signature
|
|
202
|
+
signature,
|
|
161
203
|
});
|
|
162
204
|
}
|
|
163
|
-
else {
|
|
164
|
-
// Either unsigned, or signed-but-contains a lone surrogate. In the
|
|
165
|
-
// latter case we cannot keep the signature: sanitizing breaks it and
|
|
166
|
-
// sending the raw lone surrogate is an invalid-UTF8 400. Drop the
|
|
167
|
-
// signature and downgrade to sanitized text.
|
|
168
|
-
blocks.push({ type: 'text', text: sanitize(thinking.thinking) });
|
|
169
|
-
}
|
|
170
205
|
}
|
|
171
206
|
else if (block.type === 'toolCall') {
|
|
172
207
|
blocks.push({
|
|
@@ -317,8 +352,8 @@ function applyCacheMode(body, enabled, mode) {
|
|
|
317
352
|
body.cache_control = { type: 'ephemeral' };
|
|
318
353
|
addTtl(body);
|
|
319
354
|
}
|
|
320
|
-
export async function buildAnthropicRequest(modelId, context, options, cache, fastModeEnabled = false, identity) {
|
|
321
|
-
const messages = convertMessages(context.messages);
|
|
355
|
+
export async function buildAnthropicRequest(modelId, context, options, cache, fastModeEnabled = false, identity, controls = {}) {
|
|
356
|
+
const messages = convertMessages(context.messages, modelId);
|
|
322
357
|
// Strip trailing assistant messages — Anthropic rejects prefill on some models
|
|
323
358
|
while (messages.length &&
|
|
324
359
|
messages[messages.length - 1]?.role === 'assistant') {
|
|
@@ -327,7 +362,9 @@ export async function buildAnthropicRequest(modelId, context, options, cache, fa
|
|
|
327
362
|
const system = [
|
|
328
363
|
{
|
|
329
364
|
type: 'text',
|
|
330
|
-
text: buildBillingHeaderValue(messages, undefined, CLAUDE_CODE_ENTRYPOINT
|
|
365
|
+
text: buildBillingHeaderValue(messages, undefined, CLAUDE_CODE_ENTRYPOINT, options?.sessionId
|
|
366
|
+
? firstUserTextTracker.resolve(options.sessionId, messages)
|
|
367
|
+
: undefined),
|
|
331
368
|
},
|
|
332
369
|
{ type: 'text', text: CLAUDE_CODE_IDENTITY },
|
|
333
370
|
];
|
|
@@ -414,10 +451,13 @@ export async function buildAnthropicRequest(modelId, context, options, cache, fa
|
|
|
414
451
|
};
|
|
415
452
|
}
|
|
416
453
|
}
|
|
454
|
+
applyMidConversationOutputConfig(body, controls.effortTransitions ?? []);
|
|
417
455
|
addEphemeralCacheControl(body);
|
|
418
456
|
applyCacheMode(body, cache.enabled, cache.mode);
|
|
419
|
-
if (identity)
|
|
457
|
+
if (identity) {
|
|
458
|
+
applyThinkingBindingControls(body, controls.thinkingPrefixMismatchBehavior ?? 'account-default');
|
|
420
459
|
applyClaudeCodeMetadata(body, identity);
|
|
460
|
+
}
|
|
421
461
|
const unsigned = JSON.stringify(orderClaudeCodeBody(body));
|
|
422
462
|
const bodyText = await signRequestBody(unsigned);
|
|
423
463
|
return { body, bodyText };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type MidConversationEffortTransition } from '@cortexkit/anthropic-auth-core';
|
|
2
|
+
type SessionEntryLike = {
|
|
3
|
+
id?: unknown;
|
|
4
|
+
type?: unknown;
|
|
5
|
+
thinkingLevel?: unknown;
|
|
6
|
+
message?: {
|
|
7
|
+
role?: unknown;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
/** Build Pi's active, compaction-aware Fable 5.1 effort timeline. */
|
|
11
|
+
export declare function collectPiEffortHistory(contextEntries: readonly SessionEntryLike[], fullBranch: readonly SessionEntryLike[]): MidConversationEffortTransition[];
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { normalizeAdaptiveEffort, } from '@cortexkit/anthropic-auth-core';
|
|
2
|
+
function entryRole(entry) {
|
|
3
|
+
if (entry.type === 'compaction' || entry.type === 'branch_summary') {
|
|
4
|
+
return 'user';
|
|
5
|
+
}
|
|
6
|
+
if (entry.type === 'custom_message')
|
|
7
|
+
return 'user';
|
|
8
|
+
return entry.type === 'message' ? entry.message?.role : undefined;
|
|
9
|
+
}
|
|
10
|
+
function appendTransition(transitions, afterAssistantMessages, effort) {
|
|
11
|
+
const previous = transitions.at(-1);
|
|
12
|
+
if (previous?.effort === effort)
|
|
13
|
+
return;
|
|
14
|
+
if (previous?.afterAssistantMessages === afterAssistantMessages) {
|
|
15
|
+
previous.effort = effort;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
transitions.push({ afterAssistantMessages, effort });
|
|
19
|
+
}
|
|
20
|
+
/** Build Pi's active, compaction-aware Fable 5.1 effort timeline. */
|
|
21
|
+
export function collectPiEffortHistory(contextEntries, fullBranch) {
|
|
22
|
+
let activeEffort = 'high';
|
|
23
|
+
let assistantMessages = 0;
|
|
24
|
+
const transitions = [];
|
|
25
|
+
const first = contextEntries[0];
|
|
26
|
+
const compactionIndex = first?.type === 'compaction'
|
|
27
|
+
? fullBranch.findIndex((entry) => entry.id === first.id)
|
|
28
|
+
: -1;
|
|
29
|
+
if (compactionIndex >= 0) {
|
|
30
|
+
for (let index = 0; index <= compactionIndex; index++) {
|
|
31
|
+
const entry = fullBranch[index];
|
|
32
|
+
if (entry?.type !== 'thinking_level_change')
|
|
33
|
+
continue;
|
|
34
|
+
activeEffort = normalizeAdaptiveEffort(entry.thinkingLevel) ?? 'high';
|
|
35
|
+
}
|
|
36
|
+
appendTransition(transitions, 0, activeEffort);
|
|
37
|
+
}
|
|
38
|
+
const branchIndexById = new Map(fullBranch.map((entry, index) => [entry.id, index]));
|
|
39
|
+
for (const entry of contextEntries) {
|
|
40
|
+
if (entry === first && entry.type === 'compaction')
|
|
41
|
+
continue;
|
|
42
|
+
const branchIndex = branchIndexById.get(entry.id);
|
|
43
|
+
const retainedBeforeCompaction = compactionIndex >= 0 &&
|
|
44
|
+
typeof branchIndex === 'number' &&
|
|
45
|
+
branchIndex < compactionIndex;
|
|
46
|
+
if (entry.type === 'thinking_level_change') {
|
|
47
|
+
if (!retainedBeforeCompaction) {
|
|
48
|
+
activeEffort = normalizeAdaptiveEffort(entry.thinkingLevel) ?? 'high';
|
|
49
|
+
}
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const role = entryRole(entry);
|
|
53
|
+
if (role === 'assistant') {
|
|
54
|
+
assistantMessages++;
|
|
55
|
+
}
|
|
56
|
+
else if (role === 'user' ||
|
|
57
|
+
role === 'toolResult' ||
|
|
58
|
+
role === 'tool_result') {
|
|
59
|
+
appendTransition(transitions, assistantMessages, activeEffort);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return transitions;
|
|
63
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { authorize, CLAUDE_FABLE_MYTHOS_5_CONTEXT_WINDOW, CLAUDE_FABLE_MYTHOS_5_MAX_OUTPUT_TOKENS, CLAUDE_FABLE_MYTHOS_5_MODEL_SPECS, CLAUDE_FABLE_MYTHOS_5_PRICING, exchange, refreshClaudeOAuthToken, } from '@cortexkit/anthropic-auth-core';
|
|
1
|
+
import { authorize, CLAUDE_FABLE_MYTHOS_5_1_PRICING, CLAUDE_FABLE_MYTHOS_5_CONTEXT_WINDOW, CLAUDE_FABLE_MYTHOS_5_MAX_OUTPUT_TOKENS, CLAUDE_FABLE_MYTHOS_5_MODEL_SPECS, CLAUDE_FABLE_MYTHOS_5_PRICING, exchange, isClaudeFableOrMythos51Model, refreshClaudeOAuthToken, } from '@cortexkit/anthropic-auth-core';
|
|
2
2
|
import { registerCommands } from './commands.js';
|
|
3
|
+
import { collectPiEffortHistory } from './effort-history.js';
|
|
3
4
|
import { streamCortexKitAnthropic } from './stream.js';
|
|
4
5
|
async function loginAnthropic(callbacks) {
|
|
5
6
|
const auth = await authorize('max');
|
|
@@ -32,25 +33,51 @@ async function refreshAnthropicToken(credentials) {
|
|
|
32
33
|
}
|
|
33
34
|
export default function cortexKitPiAnthropicAuth(pi) {
|
|
34
35
|
registerCommands(pi);
|
|
36
|
+
const effortHistoryBySession = new Map();
|
|
37
|
+
pi.on('turn_start', async (_event, ctx) => {
|
|
38
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
39
|
+
if (!sessionId)
|
|
40
|
+
return;
|
|
41
|
+
const transitions = collectPiEffortHistory(ctx.sessionManager.buildContextEntries(), ctx.sessionManager.getBranch());
|
|
42
|
+
effortHistoryBySession.delete(sessionId);
|
|
43
|
+
effortHistoryBySession.set(sessionId, transitions);
|
|
44
|
+
while (effortHistoryBySession.size > 128) {
|
|
45
|
+
const oldest = effortHistoryBySession.keys().next().value;
|
|
46
|
+
if (oldest)
|
|
47
|
+
effortHistoryBySession.delete(oldest);
|
|
48
|
+
else
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
pi.on('session_shutdown', async (_event, ctx) => {
|
|
53
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
54
|
+
if (sessionId)
|
|
55
|
+
effortHistoryBySession.delete(sessionId);
|
|
56
|
+
});
|
|
35
57
|
pi.registerProvider('anthropic', {
|
|
36
58
|
name: 'Anthropic (CortexKit OAuth)',
|
|
37
59
|
baseUrl: 'https://api.anthropic.com',
|
|
38
60
|
api: 'cortexkit-anthropic-messages',
|
|
39
61
|
models: [
|
|
40
|
-
...Object.values(CLAUDE_FABLE_MYTHOS_5_MODEL_SPECS).map((model) =>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
62
|
+
...Object.values(CLAUDE_FABLE_MYTHOS_5_MODEL_SPECS).map((model) => {
|
|
63
|
+
const pricing = isClaudeFableOrMythos51Model(model.id)
|
|
64
|
+
? CLAUDE_FABLE_MYTHOS_5_1_PRICING
|
|
65
|
+
: CLAUDE_FABLE_MYTHOS_5_PRICING;
|
|
66
|
+
return {
|
|
67
|
+
id: model.id,
|
|
68
|
+
name: model.name,
|
|
69
|
+
reasoning: true,
|
|
70
|
+
input: textImageInput(),
|
|
71
|
+
cost: {
|
|
72
|
+
input: pricing.input,
|
|
73
|
+
output: pricing.output,
|
|
74
|
+
cacheRead: pricing.cacheRead,
|
|
75
|
+
cacheWrite: pricing.cacheWrite5m,
|
|
76
|
+
},
|
|
77
|
+
contextWindow: CLAUDE_FABLE_MYTHOS_5_CONTEXT_WINDOW,
|
|
78
|
+
maxTokens: CLAUDE_FABLE_MYTHOS_5_MAX_OUTPUT_TOKENS,
|
|
79
|
+
};
|
|
80
|
+
}),
|
|
54
81
|
{
|
|
55
82
|
id: 'claude-opus-5',
|
|
56
83
|
name: 'Claude Opus 5',
|
|
@@ -103,6 +130,8 @@ export default function cortexKitPiAnthropicAuth(pi) {
|
|
|
103
130
|
refreshToken: refreshAnthropicToken,
|
|
104
131
|
getApiKey: (credentials) => credentials.access,
|
|
105
132
|
},
|
|
106
|
-
streamSimple: streamCortexKitAnthropic,
|
|
133
|
+
streamSimple: (model, context, options) => streamCortexKitAnthropic(model, context, options, options?.sessionId
|
|
134
|
+
? effortHistoryBySession.get(options.sessionId)
|
|
135
|
+
: undefined),
|
|
107
136
|
});
|
|
108
137
|
}
|
package/dist/stream.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ApiKeyAccount } from '@cortexkit/anthropic-auth-core';
|
|
1
|
+
import { type ApiKeyAccount, 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
3
|
export declare function clearPiStickyRoutingSession(storagePath: string, sessionId: string): Promise<void>;
|
|
4
4
|
export declare function getPiTrackedCacheKeepSessions(): Promise<import("@cortexkit/anthropic-auth-core").CacheKeepTrackedSession[]>;
|
|
@@ -16,5 +16,5 @@ export declare function buildExplicitBaseMessagesUrl(baseURL: string): URL;
|
|
|
16
16
|
export declare function configureApiRouteHeaders(account: ApiKeyAccount, fastMode: boolean): Headers;
|
|
17
17
|
export declare function parseSse(response: Response): AsyncGenerator<AnthropicEvent>;
|
|
18
18
|
export declare function primaryResponseAllowsApiFallback(preflight: Response | string): boolean;
|
|
19
|
-
export declare function streamCortexKitAnthropic(model: Model<Api>, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream;
|
|
19
|
+
export declare function streamCortexKitAnthropic(model: Model<Api>, context: Context, options?: SimpleStreamOptions, effortTransitions?: readonly MidConversationEffortTransition[]): AssistantMessageEventStream;
|
|
20
20
|
export {};
|
package/dist/stream.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { applyClaudeCodeHeaders, CACHE_KEEP_EXTENDED_TTL_BETA, CacheKeepManager, CacheKeepSessionRegistry, createStickyNoRouteResponse, decideStickyQuotaFailure, dumpDirectRequest, FAST_MODE_BETA, FallbackAccountManager, getCache1hPersistentMode, getDefaultCacheKeepRegistryDirectory, getFallbackReauthLabels, getRelayConfig, getRoutingMode, getStickyRoutingStatePath, isApiKeyAccount, isCache1hPersistentlyEnabled, isCacheKeepHybridActive, isDumpPersistentlyEnabled, isFastModePersistentlyEnabled, isKillswitchEnabled, isOAuthAccount, isPermanentRefreshError, isValidApiBaseURL, killswitchPassesPolicy, loadAccounts, mergeAnthropicBetas, QuotaManager, quotaSnapshotModelScopeIsExhausted, quotaSnapshotPassesModelScope, quotaSnapshotPassesPolicy, resolveClaudeCodeIdentity, STICKY_ROUTING_MAIN_ACCOUNT_ID, StickySessionRouter, sendViaRelay, setDumpEnabled, shouldFallbackStatus, stickyQuotaSnapshotIsFresh, stickyRetryAfterWithJitter, stickyRouteFamilyForModel, } from '@cortexkit/anthropic-auth-core';
|
|
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';
|
|
2
2
|
import { calculateCost, createAssistantMessageEventStream, } from '@earendil-works/pi-ai';
|
|
3
3
|
import { buildAnthropicRequest, fromClaudeCodeToolName } from './convert.js';
|
|
4
4
|
import { getPiAccountStoragePath } from './paths.js';
|
|
@@ -10,6 +10,7 @@ let cacheKeepRegistryDirectory;
|
|
|
10
10
|
const stickyRouters = new Map();
|
|
11
11
|
const quotaManagers = new Map();
|
|
12
12
|
const fallbackManagers = new Map();
|
|
13
|
+
const mainAccountIdInitializations = new Map();
|
|
13
14
|
const PI_SERVICE_CACHE_LIMIT = 16;
|
|
14
15
|
function setBoundedService(map, key, value) {
|
|
15
16
|
map.delete(key);
|
|
@@ -21,6 +22,19 @@ function setBoundedService(map, key, value) {
|
|
|
21
22
|
map.delete(oldest);
|
|
22
23
|
}
|
|
23
24
|
}
|
|
25
|
+
function ensurePiMainAccountId(storagePath) {
|
|
26
|
+
let initialization = mainAccountIdInitializations.get(storagePath);
|
|
27
|
+
if (!initialization) {
|
|
28
|
+
initialization = getOrCreateMainAccountId(storagePath).catch((error) => {
|
|
29
|
+
if (mainAccountIdInitializations.get(storagePath) === initialization) {
|
|
30
|
+
mainAccountIdInitializations.delete(storagePath);
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
33
|
+
});
|
|
34
|
+
setBoundedService(mainAccountIdInitializations, storagePath, initialization);
|
|
35
|
+
}
|
|
36
|
+
return initialization;
|
|
37
|
+
}
|
|
24
38
|
function getPiRoutingServices(storagePath, storage) {
|
|
25
39
|
let quotaManager = quotaManagers.get(storagePath);
|
|
26
40
|
let fallbackManager = fallbackManagers.get(storagePath);
|
|
@@ -71,12 +85,21 @@ const cacheKeepManager = new CacheKeepManager({
|
|
|
71
85
|
return headers;
|
|
72
86
|
try {
|
|
73
87
|
const body = JSON.parse(target.bodyText);
|
|
74
|
-
const
|
|
88
|
+
const storagePath = getPiAccountStoragePath();
|
|
89
|
+
await ensurePiMainAccountId(storagePath);
|
|
90
|
+
const storage = await loadAccounts(storagePath);
|
|
91
|
+
const identity = await resolveClaudeCodeIdentity(accessToken, typeof body.model === 'string' ? body.model : undefined, target.oauthAccountId ?? storage?.mainAccountId);
|
|
75
92
|
headers.delete('anthropic-beta');
|
|
76
|
-
applyClaudeCodeHeaders(headers, accessToken, {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
applyClaudeCodeHeaders(headers, accessToken, {
|
|
94
|
+
body,
|
|
95
|
+
identity,
|
|
96
|
+
extraBetas: [
|
|
97
|
+
CACHE_KEEP_EXTENDED_TTL_BETA,
|
|
98
|
+
...(hasThinkingBindingControls(body)
|
|
99
|
+
? [THINKING_BINDING_CONTROLS_BETA]
|
|
100
|
+
: []),
|
|
101
|
+
],
|
|
102
|
+
});
|
|
80
103
|
if (body.speed === 'fast') {
|
|
81
104
|
headers.set('anthropic-beta', mergeAnthropicBetas(headers.get('anthropic-beta'), [FAST_MODE_BETA]));
|
|
82
105
|
}
|
|
@@ -201,21 +224,38 @@ export async function* parseSse(response) {
|
|
|
201
224
|
}
|
|
202
225
|
}
|
|
203
226
|
async function sendAnthropicRequest(options) {
|
|
227
|
+
await ensurePiMainAccountId(options.storagePath);
|
|
204
228
|
const storage = await loadAccounts(options.storagePath);
|
|
205
229
|
setDumpEnabled(isDumpPersistentlyEnabled(storage));
|
|
230
|
+
const accountIdentity = options.oauthAccountId === STICKY_ROUTING_MAIN_ACCOUNT_ID
|
|
231
|
+
? storage?.mainAccountId
|
|
232
|
+
: options.oauthAccountId;
|
|
206
233
|
const identity = options.accessToken
|
|
207
|
-
? await resolveClaudeCodeIdentity(options.accessToken, options.model.id)
|
|
234
|
+
? await resolveClaudeCodeIdentity(options.accessToken, options.model.id, accountIdentity)
|
|
208
235
|
: undefined;
|
|
209
236
|
const { body, bodyText } = await buildAnthropicRequest(options.model.id, options.context, options.streamOptions, {
|
|
210
237
|
enabled: isCache1hPersistentlyEnabled(storage),
|
|
211
238
|
mode: getCache1hPersistentMode(storage),
|
|
212
|
-
}, isFastModePersistentlyEnabled(storage), identity
|
|
239
|
+
}, isFastModePersistentlyEnabled(storage), identity, {
|
|
240
|
+
effortTransitions: options.apiAccount ? [] : options.effortTransitions,
|
|
241
|
+
thinkingPrefixMismatchBehavior: options.apiAccount
|
|
242
|
+
? 'account-default'
|
|
243
|
+
: getThinkingPrefixMismatchBehavior(storage),
|
|
244
|
+
});
|
|
213
245
|
const fastMode = body.speed === 'fast';
|
|
214
246
|
const headers = options.apiAccount
|
|
215
247
|
? configureApiRouteHeaders(options.apiAccount, fastMode)
|
|
216
248
|
: applyClaudeCodeHeaders(new Headers(), options.accessToken ?? '', {
|
|
217
249
|
body,
|
|
218
250
|
identity,
|
|
251
|
+
extraBetas: [
|
|
252
|
+
...(hasThinkingBindingControls(body)
|
|
253
|
+
? [THINKING_BINDING_CONTROLS_BETA]
|
|
254
|
+
: []),
|
|
255
|
+
...(usesMidConversationOutputConfig(body)
|
|
256
|
+
? [MID_CONVERSATION_OUTPUT_CONFIG_BETA]
|
|
257
|
+
: []),
|
|
258
|
+
],
|
|
219
259
|
});
|
|
220
260
|
if (!options.apiAccount && fastMode) {
|
|
221
261
|
headers.set('anthropic-beta', mergeAnthropicBetas(headers.get('anthropic-beta'), [FAST_MODE_BETA]));
|
|
@@ -307,16 +347,18 @@ async function firstStreamingError(response) {
|
|
|
307
347
|
return response;
|
|
308
348
|
}
|
|
309
349
|
async function executeWithFallback(options) {
|
|
350
|
+
await ensurePiMainAccountId(options.storagePath);
|
|
310
351
|
const storage = await loadAccounts(options.storagePath);
|
|
311
352
|
const { quotaManager, fallbackManager: manager } = getPiRoutingServices(options.storagePath, storage);
|
|
312
|
-
|
|
353
|
+
const mainAccountId = storage?.mainAccountId;
|
|
354
|
+
quotaManager.seedMainFromStorage(storage, mainAccountId);
|
|
313
355
|
quotaManager.seedFallbacksFromAccounts((storage?.accounts ?? []).filter(isOAuthAccount));
|
|
314
356
|
async function buildStickyRoutes(modelId) {
|
|
315
|
-
const mainEntry = quotaManager.getMain(
|
|
357
|
+
const mainEntry = quotaManager.getMain(mainAccountId);
|
|
316
358
|
let mainQuota = mainEntry?.quota;
|
|
317
359
|
if (!stickyQuotaSnapshotIsFresh(mainEntry?.quota, storage, Date.now(), modelId)) {
|
|
318
360
|
try {
|
|
319
|
-
mainQuota = await quotaManager.refreshMain(options.primaryAccessToken);
|
|
361
|
+
mainQuota = await quotaManager.refreshMain(mainAccountId, options.primaryAccessToken);
|
|
320
362
|
}
|
|
321
363
|
catch { }
|
|
322
364
|
}
|
|
@@ -339,11 +381,10 @@ async function executeWithFallback(options) {
|
|
|
339
381
|
const account = usableById.get(configured.id) ?? configured;
|
|
340
382
|
if (!account.access || isPermanentRefreshError(account.lastRefreshError))
|
|
341
383
|
continue;
|
|
342
|
-
let accountQuota = quotaManager.getFallback(account.id, account
|
|
343
|
-
account.quota;
|
|
384
|
+
let accountQuota = quotaManager.getFallback(account.id, account)?.quota ?? account.quota;
|
|
344
385
|
if (!stickyQuotaSnapshotIsFresh(accountQuota, storage, Date.now(), modelId)) {
|
|
345
386
|
try {
|
|
346
|
-
accountQuota = await quotaManager.refreshFallback(account.id, account.access);
|
|
387
|
+
accountQuota = await quotaManager.refreshFallback(account.id, account.access, account);
|
|
347
388
|
}
|
|
348
389
|
catch { }
|
|
349
390
|
}
|
|
@@ -374,19 +415,26 @@ async function executeWithFallback(options) {
|
|
|
374
415
|
}));
|
|
375
416
|
const usableIds = new Set(usableFallbacks.map((account) => account.id));
|
|
376
417
|
const candidates = allRoutes.flatMap((route) => {
|
|
377
|
-
|
|
378
|
-
|
|
418
|
+
const quota = quotaSnapshotHasStandardWindows(route.quota)
|
|
419
|
+
? route.quota
|
|
420
|
+
: undefined;
|
|
379
421
|
const accountId = route.id === STICKY_ROUTING_MAIN_ACCOUNT_ID ? undefined : route.id;
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
(
|
|
422
|
+
const quotaState = quota
|
|
423
|
+
? { kind: 'known', quota }
|
|
424
|
+
: { kind: 'unknown' };
|
|
425
|
+
const passesKillswitch = !isKillswitchEnabled(storage) ||
|
|
426
|
+
killswitchPassesPolicy(quotaState.kind === 'known' ? quotaState.quota : undefined, storage, accountId, modelId);
|
|
427
|
+
const passes = passesKillswitch &&
|
|
428
|
+
(quotaState.kind === 'unknown' ||
|
|
429
|
+
(quotaSnapshotPassesPolicy(quotaState.quota, storage) &&
|
|
430
|
+
quotaSnapshotPassesModelScope(quotaState.quota, modelId) &&
|
|
431
|
+
(route.id === STICKY_ROUTING_MAIN_ACCOUNT_ID ||
|
|
432
|
+
usableIds.has(route.id))));
|
|
385
433
|
return passes
|
|
386
434
|
? [
|
|
387
435
|
{
|
|
388
436
|
accountId: route.id,
|
|
389
|
-
quota:
|
|
437
|
+
quota: quotaState,
|
|
390
438
|
order: route.order,
|
|
391
439
|
},
|
|
392
440
|
]
|
|
@@ -396,8 +444,8 @@ async function executeWithFallback(options) {
|
|
|
396
444
|
}
|
|
397
445
|
async function primaryQuotaRefreshConfirmsExhausted() {
|
|
398
446
|
try {
|
|
399
|
-
const quota = await quotaManager.refreshMain(options.primaryAccessToken);
|
|
400
|
-
const entry = quotaManager.getMain(
|
|
447
|
+
const quota = await quotaManager.refreshMain(mainAccountId, options.primaryAccessToken);
|
|
448
|
+
const entry = quotaManager.getMain(mainAccountId);
|
|
401
449
|
return Boolean(entry &&
|
|
402
450
|
entry.refreshAfter > Date.now() &&
|
|
403
451
|
quotaSnapshotIsExhausted(quota));
|
|
@@ -408,8 +456,8 @@ async function executeWithFallback(options) {
|
|
|
408
456
|
}
|
|
409
457
|
async function primaryQuotaRefreshConfirmsModelScopeExhausted() {
|
|
410
458
|
try {
|
|
411
|
-
const quota = await quotaManager.refreshMain(options.primaryAccessToken);
|
|
412
|
-
const entry = quotaManager.getMain(
|
|
459
|
+
const quota = await quotaManager.refreshMain(mainAccountId, options.primaryAccessToken);
|
|
460
|
+
const entry = quotaManager.getMain(mainAccountId);
|
|
413
461
|
return Boolean(entry &&
|
|
414
462
|
entry.refreshAfter > Date.now() &&
|
|
415
463
|
quotaSnapshotModelScopeIsExhausted(quota, options.model.id));
|
|
@@ -419,12 +467,12 @@ async function executeWithFallback(options) {
|
|
|
419
467
|
}
|
|
420
468
|
}
|
|
421
469
|
function primaryCachedModelScopeExhausted() {
|
|
422
|
-
const entry = quotaManager.getMain(
|
|
470
|
+
const entry = quotaManager.getMain(mainAccountId);
|
|
423
471
|
return Boolean(entry &&
|
|
424
472
|
quotaSnapshotModelScopeIsExhausted(entry.quota, options.model.id));
|
|
425
473
|
}
|
|
426
474
|
function primaryFreshModelScopeExhausted() {
|
|
427
|
-
const entry = quotaManager.getMain(
|
|
475
|
+
const entry = quotaManager.getMain(mainAccountId);
|
|
428
476
|
return Boolean(entry &&
|
|
429
477
|
!quotaManager.isMainStale(options.model.id) &&
|
|
430
478
|
quotaSnapshotModelScopeIsExhausted(entry.quota, options.model.id));
|
|
@@ -436,17 +484,38 @@ async function executeWithFallback(options) {
|
|
|
436
484
|
const usableOAuthById = new Map(usableOAuth.map((account) => [account.id, account]));
|
|
437
485
|
for (const configured of storage?.accounts ?? []) {
|
|
438
486
|
let response = null;
|
|
439
|
-
|
|
487
|
+
let account = isOAuthAccount(configured)
|
|
440
488
|
? usableOAuthById.get(configured.id)
|
|
441
489
|
: configured;
|
|
442
|
-
if (!account)
|
|
490
|
+
if (!account) {
|
|
491
|
+
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)))
|
|
497
|
+
continue;
|
|
498
|
+
account = configured;
|
|
499
|
+
const quota = quotaManager.getFallback(configured.id, configured)?.quota ??
|
|
500
|
+
configured.quota;
|
|
501
|
+
if (quotaSnapshotHasStandardWindows(quota))
|
|
502
|
+
continue;
|
|
503
|
+
if (isKillswitchEnabled(storage) &&
|
|
504
|
+
!killswitchPassesPolicy(quota, storage, configured.id, options.model.id))
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
else if (isOAuthAccount(account) &&
|
|
508
|
+
isKillswitchEnabled(storage) &&
|
|
509
|
+
!killswitchPassesPolicy(quotaManager.getFallback(account.id, account)?.quota ?? account.quota, storage, account.id, options.model.id)) {
|
|
443
510
|
continue;
|
|
511
|
+
}
|
|
444
512
|
if (isOAuthAccount(account)) {
|
|
445
513
|
if (routeOptions.apiOnly === true || !account.access)
|
|
446
514
|
continue;
|
|
447
515
|
response = await sendAnthropicRequest({
|
|
448
516
|
...options,
|
|
449
517
|
accessToken: account.access,
|
|
518
|
+
oauthAccountId: account.id,
|
|
450
519
|
});
|
|
451
520
|
}
|
|
452
521
|
else if (routeOptions.includeApiRoutes === true &&
|
|
@@ -587,8 +656,8 @@ async function executeWithFallback(options) {
|
|
|
587
656
|
try {
|
|
588
657
|
quota =
|
|
589
658
|
route.id === STICKY_ROUTING_MAIN_ACCOUNT_ID
|
|
590
|
-
? await quotaManager.refreshMain(route.access)
|
|
591
|
-
: await quotaManager.refreshFallback(route.id, route.access);
|
|
659
|
+
? await quotaManager.refreshMain(mainAccountId, route.access)
|
|
660
|
+
: await quotaManager.refreshFallback(route.id, route.access, route.account);
|
|
592
661
|
}
|
|
593
662
|
catch {
|
|
594
663
|
// Retain affinity when the quota probe itself is unavailable.
|
|
@@ -680,9 +749,39 @@ async function executeWithFallback(options) {
|
|
|
680
749
|
if (fallback)
|
|
681
750
|
return fallback;
|
|
682
751
|
}
|
|
752
|
+
if (isKillswitchEnabled(storage)) {
|
|
753
|
+
let mainQuota = quotaManager.getMain(mainAccountId)?.quota;
|
|
754
|
+
if (!mainQuota || quotaManager.isMainStale(options.model.id)) {
|
|
755
|
+
try {
|
|
756
|
+
mainQuota = await quotaManager.refreshMain(mainAccountId, options.primaryAccessToken);
|
|
757
|
+
}
|
|
758
|
+
catch { }
|
|
759
|
+
}
|
|
760
|
+
if (!killswitchPassesPolicy(mainQuota, storage, undefined, options.model.id)) {
|
|
761
|
+
if (!fallbackFirst) {
|
|
762
|
+
const fallback = await tryFallbackAccounts();
|
|
763
|
+
if (fallback)
|
|
764
|
+
return fallback;
|
|
765
|
+
}
|
|
766
|
+
return new Response(JSON.stringify({
|
|
767
|
+
type: 'error',
|
|
768
|
+
error: {
|
|
769
|
+
type: 'rate_limit_error',
|
|
770
|
+
message: 'Killswitch blocked all OAuth routes',
|
|
771
|
+
},
|
|
772
|
+
}), {
|
|
773
|
+
status: 429,
|
|
774
|
+
headers: {
|
|
775
|
+
'content-type': 'application/json',
|
|
776
|
+
'retry-after': '60',
|
|
777
|
+
},
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
}
|
|
683
781
|
const primary = await sendAnthropicRequest({
|
|
684
782
|
...options,
|
|
685
783
|
accessToken: options.primaryAccessToken,
|
|
784
|
+
oauthAccountId: STICKY_ROUTING_MAIN_ACCOUNT_ID,
|
|
686
785
|
});
|
|
687
786
|
const primaryPreflight = await firstStreamingError(primary);
|
|
688
787
|
if (primaryPreflight instanceof Response) {
|
|
@@ -706,7 +805,7 @@ async function executeWithFallback(options) {
|
|
|
706
805
|
}
|
|
707
806
|
return primaryPreflight instanceof Response ? primaryPreflight : primary;
|
|
708
807
|
}
|
|
709
|
-
export function streamCortexKitAnthropic(model, context, options) {
|
|
808
|
+
export function streamCortexKitAnthropic(model, context, options, effortTransitions) {
|
|
710
809
|
const stream = createAssistantMessageEventStream();
|
|
711
810
|
void (async () => {
|
|
712
811
|
const output = createOutput(model);
|
|
@@ -722,6 +821,7 @@ export function streamCortexKitAnthropic(model, context, options) {
|
|
|
722
821
|
streamOptions: options,
|
|
723
822
|
primaryAccessToken: accessToken,
|
|
724
823
|
storagePath,
|
|
824
|
+
effortTransitions,
|
|
725
825
|
});
|
|
726
826
|
if (!response.ok) {
|
|
727
827
|
throw new Error(`Anthropic request failed: HTTP ${response.status} ${await response.text()}`);
|
|
@@ -758,6 +858,20 @@ export function streamCortexKitAnthropic(model, context, options) {
|
|
|
758
858
|
partial: output,
|
|
759
859
|
});
|
|
760
860
|
}
|
|
861
|
+
else if (block?.type === 'redacted_thinking') {
|
|
862
|
+
output.content.push({
|
|
863
|
+
type: 'thinking',
|
|
864
|
+
thinking: '[Reasoning redacted]',
|
|
865
|
+
thinkingSignature: String(block.data ?? ''),
|
|
866
|
+
redacted: true,
|
|
867
|
+
index: event.index,
|
|
868
|
+
});
|
|
869
|
+
stream.push({
|
|
870
|
+
type: 'thinking_start',
|
|
871
|
+
contentIndex: output.content.length - 1,
|
|
872
|
+
partial: output,
|
|
873
|
+
});
|
|
874
|
+
}
|
|
761
875
|
else if (block?.type === 'tool_use') {
|
|
762
876
|
output.content.push({
|
|
763
877
|
type: 'toolCall',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/pi-anthropic-auth",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"prepublishOnly": "bun run build"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@cortexkit/anthropic-auth-core": "1.
|
|
39
|
+
"@cortexkit/anthropic-auth-core": "1.22.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@earendil-works/pi-ai": "*",
|