@cortexkit/pi-anthropic-auth 1.7.0 → 1.8.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 CHANGED
@@ -2,6 +2,8 @@
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 4.8, Claude Opus 4.5, and Claude Sonnet 4.5. Fable/Mythos reasoning uses Anthropic adaptive thinking with `output_config.effort`; the package does not send rejected manual `thinking.budget_tokens` for those models.
6
+
5
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.
6
8
 
7
9
  ## Install
@@ -71,7 +73,7 @@ The sidecar uses the same JSON shape as the OpenCode package, including `routing
71
73
  /claude-quota
72
74
  ```
73
75
 
74
- `/claude-quota` reports sidecar fallback account quota state from `~/.pi/agent/anthropic-auth.json`. `/claude-routing fallback-first` prefers usable fallback accounts before the main account; `/claude-routing main-first` restores the default. `/claude-cachekeep HH-HH` keeps recently used hybrid-mode session caches warm during the configured local time window by sending `max_tokens: 0` pre-warm requests about five minutes before the 1-hour TTL expires. `/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`, and `claude-opus-4-8`).
76
+ `/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. 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 HH-HH` keeps recently used hybrid-mode session caches warm during the configured local time window by sending `max_tokens: 0` pre-warm requests about five minutes before the 1-hour TTL expires. `/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`, and `claude-opus-4-8`).
75
77
 
76
78
  ## Relay
77
79
 
package/dist/convert.d.ts CHANGED
@@ -11,6 +11,9 @@ export type AnthropicRequestBody = {
11
11
  type: 'enabled';
12
12
  budget_tokens: number;
13
13
  };
14
+ output_config?: {
15
+ effort: string;
16
+ };
14
17
  cache_control?: {
15
18
  type: 'ephemeral';
16
19
  };
package/dist/convert.js CHANGED
@@ -1,4 +1,4 @@
1
- import { applyClaudeCodeMetadata, buildBillingHeaderValue, CLAUDE_CODE_ENTRYPOINT, CLAUDE_CODE_IDENTITY, isFastModeSupportedModel, orderClaudeCodeBody, signRequestBody, } from '@cortexkit/anthropic-auth-core';
1
+ import { applyClaudeCodeMetadata, buildBillingHeaderValue, CLAUDE_CODE_ENTRYPOINT, CLAUDE_CODE_IDENTITY, isClaudeFableOrMythos5Model, isFastModeSupportedModel, orderClaudeCodeBody, signRequestBody, } from '@cortexkit/anthropic-auth-core';
2
2
  const CLAUDE_CODE_TOOLS = new Map([
3
3
  'Read',
4
4
  'Write',
@@ -264,19 +264,24 @@ export async function buildAnthropicRequest(modelId, context, options, cache, fa
264
264
  body.speed = 'fast';
265
265
  }
266
266
  if (options?.reasoning) {
267
- const budgets = {
268
- minimal: 1024,
269
- low: 4096,
270
- medium: 10_240,
271
- high: 20_480,
272
- xhigh: 32_000,
273
- };
274
- body.thinking = {
275
- type: 'enabled',
276
- budget_tokens: options.thinkingBudgets?.[options.reasoning] ??
277
- budgets[options.reasoning] ??
278
- 10_240,
279
- };
267
+ if (isClaudeFableOrMythos5Model(modelId)) {
268
+ body.output_config = { effort: options.reasoning };
269
+ }
270
+ else {
271
+ const budgets = {
272
+ minimal: 1024,
273
+ low: 4096,
274
+ medium: 10_240,
275
+ high: 20_480,
276
+ xhigh: 32_000,
277
+ };
278
+ body.thinking = {
279
+ type: 'enabled',
280
+ budget_tokens: options.thinkingBudgets?.[options.reasoning] ??
281
+ budgets[options.reasoning] ??
282
+ 10_240,
283
+ };
284
+ }
280
285
  }
281
286
  addEphemeralCacheControl(body);
282
287
  applyCacheMode(body, cache.enabled, cache.mode);
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { authorize, exchange, refreshClaudeOAuthToken, } from '@cortexkit/anthropic-auth-core';
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';
2
2
  import { registerCommands } from "./commands.js";
3
3
  import { streamCortexKitAnthropic } from "./stream.js";
4
4
  async function loginAnthropic(callbacks) {
@@ -17,6 +17,9 @@ async function loginAnthropic(callbacks) {
17
17
  expires: result.expires,
18
18
  };
19
19
  }
20
+ function textImageInput() {
21
+ return ['text', 'image'];
22
+ }
20
23
  async function refreshAnthropicToken(credentials) {
21
24
  const refreshed = await refreshClaudeOAuthToken({
22
25
  refreshToken: credentials.refresh,
@@ -34,11 +37,25 @@ export default function cortexKitPiAnthropicAuth(pi) {
34
37
  baseUrl: 'https://api.anthropic.com',
35
38
  api: 'cortexkit-anthropic-messages',
36
39
  models: [
40
+ ...Object.values(CLAUDE_FABLE_MYTHOS_5_MODEL_SPECS).map((model) => ({
41
+ id: model.id,
42
+ name: model.name,
43
+ reasoning: true,
44
+ input: textImageInput(),
45
+ cost: {
46
+ input: CLAUDE_FABLE_MYTHOS_5_PRICING.input,
47
+ output: CLAUDE_FABLE_MYTHOS_5_PRICING.output,
48
+ cacheRead: CLAUDE_FABLE_MYTHOS_5_PRICING.cacheRead,
49
+ cacheWrite: CLAUDE_FABLE_MYTHOS_5_PRICING.cacheWrite5m,
50
+ },
51
+ contextWindow: CLAUDE_FABLE_MYTHOS_5_CONTEXT_WINDOW,
52
+ maxTokens: CLAUDE_FABLE_MYTHOS_5_MAX_OUTPUT_TOKENS,
53
+ })),
37
54
  {
38
55
  id: 'claude-opus-4-8',
39
56
  name: 'Claude Opus 4.8',
40
57
  reasoning: true,
41
- input: ['text', 'image'],
58
+ input: textImageInput(),
42
59
  cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
43
60
  contextWindow: 1_000_000,
44
61
  maxTokens: 128_000,
@@ -47,7 +64,7 @@ export default function cortexKitPiAnthropicAuth(pi) {
47
64
  id: 'claude-opus-4-5',
48
65
  name: 'Claude Opus 4.5',
49
66
  reasoning: true,
50
- input: ['text', 'image'],
67
+ input: textImageInput(),
51
68
  cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
52
69
  contextWindow: 200_000,
53
70
  maxTokens: 64_000,
@@ -56,7 +73,7 @@ export default function cortexKitPiAnthropicAuth(pi) {
56
73
  id: 'claude-sonnet-4-5',
57
74
  name: 'Claude Sonnet 4.5',
58
75
  reasoning: true,
59
- input: ['text', 'image'],
76
+ input: textImageInput(),
60
77
  cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
61
78
  contextWindow: 200_000,
62
79
  maxTokens: 64_000,
package/dist/stream.d.ts CHANGED
@@ -1,2 +1,6 @@
1
+ import { type ApiKeyAccount } from '@cortexkit/anthropic-auth-core';
1
2
  import { type Api, type AssistantMessageEventStream, type Context, type Model, type SimpleStreamOptions } from '@earendil-works/pi-ai';
3
+ export declare function buildExplicitBaseMessagesUrl(baseURL: string): URL;
4
+ export declare function configureApiRouteHeaders(account: ApiKeyAccount, fastMode: boolean): Headers;
5
+ export declare function primaryResponseAllowsApiFallback(preflight: Response | string): boolean;
2
6
  export declare function streamCortexKitAnthropic(model: Model<Api>, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream;
package/dist/stream.js CHANGED
@@ -1,4 +1,4 @@
1
- import { applyClaudeCodeHeaders, CACHE_KEEP_EXTENDED_TTL_BETA, CacheKeepManager, FAST_MODE_BETA, FallbackAccountManager, getCache1hPersistentMode, getRelayConfig, getRoutingMode, isCache1hPersistentlyEnabled, isCacheKeepHybridActive, isFastModePersistentlyEnabled, loadAccounts, mergeAnthropicBetas, resolveClaudeCodeIdentity, sendViaRelay, shouldFallbackStatus, } from '@cortexkit/anthropic-auth-core';
1
+ import { applyClaudeCodeHeaders, CACHE_KEEP_EXTENDED_TTL_BETA, CacheKeepManager, FAST_MODE_BETA, FallbackAccountManager, getCache1hPersistentMode, getRelayConfig, getRoutingMode, isApiKeyAccount, isCache1hPersistentlyEnabled, isCacheKeepHybridActive, isFastModePersistentlyEnabled, isOAuthAccount, isValidApiBaseURL, loadAccounts, mergeAnthropicBetas, QuotaManager, resolveClaudeCodeIdentity, sendViaRelay, shouldFallbackStatus, } 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";
@@ -77,49 +77,89 @@ function updateUsage(model, output, usage) {
77
77
  output.usage.cacheWrite;
78
78
  calculateCost(model, output.usage);
79
79
  }
80
+ export function buildExplicitBaseMessagesUrl(baseURL) {
81
+ const url = new URL(baseURL);
82
+ url.pathname = `${url.pathname.replace(/\/$/, '')}/v1/messages`;
83
+ url.searchParams.set('beta', 'true');
84
+ return url;
85
+ }
86
+ export function configureApiRouteHeaders(account, fastMode) {
87
+ const headers = new Headers();
88
+ headers.set('accept', 'application/json');
89
+ headers.set('content-type', 'application/json');
90
+ headers.set('anthropic-version', '2023-06-01');
91
+ headers.set('anthropic-beta', mergeAnthropicBetas(null, []));
92
+ if (account.authHeader === 'x-api-key') {
93
+ headers.set('x-api-key', account.apiKey ?? '');
94
+ }
95
+ else {
96
+ headers.set('authorization', `Bearer ${account.apiKey ?? ''}`);
97
+ }
98
+ if (fastMode) {
99
+ headers.set('anthropic-beta', mergeAnthropicBetas(headers.get('anthropic-beta'), [FAST_MODE_BETA]));
100
+ }
101
+ return headers;
102
+ }
80
103
  async function* parseSse(response) {
81
104
  if (!response.body)
82
105
  return;
83
106
  const reader = response.body.getReader();
84
107
  const decoder = new TextDecoder();
85
108
  let buffer = '';
86
- while (true) {
87
- const { done, value } = await reader.read();
88
- if (done)
89
- break;
90
- buffer += decoder.decode(value, { stream: true });
91
- let boundary = buffer.indexOf('\n\n');
92
- while (boundary !== -1) {
93
- const frame = buffer.slice(0, boundary);
94
- buffer = buffer.slice(boundary + 2);
95
- boundary = buffer.indexOf('\n\n');
96
- for (const line of frame.split('\n')) {
97
- if (!line.startsWith('data:'))
98
- continue;
99
- const data = line.slice(5).trim();
100
- if (!data || data === '[DONE]')
101
- continue;
102
- yield JSON.parse(data);
109
+ let completed = false;
110
+ try {
111
+ while (true) {
112
+ const { done, value } = await reader.read();
113
+ if (done) {
114
+ completed = true;
115
+ break;
116
+ }
117
+ buffer += decoder.decode(value, { stream: true });
118
+ let boundary = buffer.indexOf('\n\n');
119
+ while (boundary !== -1) {
120
+ const frame = buffer.slice(0, boundary);
121
+ buffer = buffer.slice(boundary + 2);
122
+ boundary = buffer.indexOf('\n\n');
123
+ for (const line of frame.split('\n')) {
124
+ if (!line.startsWith('data:'))
125
+ continue;
126
+ const data = line.slice(5).trim();
127
+ if (!data || data === '[DONE]')
128
+ continue;
129
+ yield JSON.parse(data);
130
+ }
103
131
  }
104
132
  }
105
133
  }
134
+ finally {
135
+ if (!completed)
136
+ await reader.cancel().catch(() => { });
137
+ reader.releaseLock();
138
+ }
106
139
  }
107
140
  async function sendAnthropicRequest(options) {
108
141
  const storage = await loadAccounts(options.storagePath);
109
- const identity = await resolveClaudeCodeIdentity(options.accessToken, options.model.id);
142
+ const identity = options.accessToken
143
+ ? await resolveClaudeCodeIdentity(options.accessToken, options.model.id)
144
+ : undefined;
110
145
  const { body, bodyText } = await buildAnthropicRequest(options.model.id, options.context, options.streamOptions, {
111
146
  enabled: isCache1hPersistentlyEnabled(storage),
112
147
  mode: getCache1hPersistentMode(storage),
113
148
  }, isFastModePersistentlyEnabled(storage), identity);
114
- const headers = applyClaudeCodeHeaders(new Headers(), options.accessToken, {
115
- body,
116
- identity,
117
- });
118
- if (body.speed === 'fast') {
149
+ const fastMode = body.speed === 'fast';
150
+ const headers = options.apiAccount
151
+ ? configureApiRouteHeaders(options.apiAccount, fastMode)
152
+ : applyClaudeCodeHeaders(new Headers(), options.accessToken ?? '', {
153
+ body,
154
+ identity,
155
+ });
156
+ if (!options.apiAccount && fastMode) {
119
157
  headers.set('anthropic-beta', mergeAnthropicBetas(headers.get('anthropic-beta'), [FAST_MODE_BETA]));
120
158
  }
121
159
  const relayAffinity = options.streamOptions?.sessionId ?? null;
122
- const input = new URL('/v1/messages?beta=true', options.model.baseUrl);
160
+ const input = options.apiAccount
161
+ ? buildExplicitBaseMessagesUrl(options.apiAccount.baseURL)
162
+ : new URL('/v1/messages?beta=true', options.model.baseUrl);
123
163
  const init = {
124
164
  method: 'POST',
125
165
  headers,
@@ -134,6 +174,8 @@ async function sendAnthropicRequest(options) {
134
174
  storage,
135
175
  cacheMode: isCacheKeepHybridActive(storage) ? 'hybrid' : 'disabled',
136
176
  });
177
+ if (options.apiAccount)
178
+ return fetch(input, init);
137
179
  return sendViaRelay({
138
180
  config: getRelayConfig(storage),
139
181
  input,
@@ -144,6 +186,13 @@ async function sendAnthropicRequest(options) {
144
186
  affinity: relayAffinity,
145
187
  });
146
188
  }
189
+ function quotaSnapshotIsExhausted(quota) {
190
+ return Object.values(quota ?? {}).some((window) => window && window.remainingPercent <= 0);
191
+ }
192
+ export function primaryResponseAllowsApiFallback(preflight) {
193
+ return (preflight === 'rate_limit_error' ||
194
+ (preflight instanceof Response && preflight.status === 429));
195
+ }
147
196
  async function firstStreamingError(response) {
148
197
  if (!response.ok)
149
198
  return response;
@@ -168,14 +217,49 @@ async function executeWithFallback(options) {
168
217
  configPath: options.storagePath,
169
218
  });
170
219
  const storage = await loadAccounts(options.storagePath);
171
- async function tryFallbackAccounts() {
172
- for (const account of await manager.getUsableFallbackAccounts(storage)) {
173
- if (!account.access)
220
+ const quotaManager = new QuotaManager({ storage });
221
+ async function primaryQuotaRefreshConfirmsExhausted() {
222
+ try {
223
+ const quota = await quotaManager.refreshMain(options.primaryAccessToken);
224
+ const entry = quotaManager.getMain(options.primaryAccessToken);
225
+ return Boolean(entry &&
226
+ entry.refreshAfter > Date.now() &&
227
+ quotaSnapshotIsExhausted(quota));
228
+ }
229
+ catch {
230
+ return false;
231
+ }
232
+ }
233
+ async function tryFallbackAccounts(routeOptions = {}) {
234
+ const usableOAuth = await manager.getUsableFallbackAccounts(storage);
235
+ const usableOAuthById = new Map(usableOAuth.map((account) => [account.id, account]));
236
+ for (const configured of storage?.accounts ?? []) {
237
+ let response = null;
238
+ const account = isOAuthAccount(configured)
239
+ ? usableOAuthById.get(configured.id)
240
+ : configured;
241
+ if (!account)
242
+ continue;
243
+ if (isOAuthAccount(account)) {
244
+ if (!account.access)
245
+ continue;
246
+ response = await sendAnthropicRequest({
247
+ ...options,
248
+ accessToken: account.access,
249
+ });
250
+ }
251
+ else if (routeOptions.includeApiRoutes === true &&
252
+ isApiKeyAccount(account) &&
253
+ account.enabled !== false &&
254
+ account.apiKey &&
255
+ isValidApiBaseURL(account.baseURL)) {
256
+ response = await sendAnthropicRequest({
257
+ ...options,
258
+ apiAccount: account,
259
+ });
260
+ }
261
+ if (!response)
174
262
  continue;
175
- const response = await sendAnthropicRequest({
176
- ...options,
177
- accessToken: account.access,
178
- });
179
263
  const preflight = await firstStreamingError(response);
180
264
  if (preflight instanceof Response && preflight.ok) {
181
265
  await manager.markUsed(account);
@@ -185,6 +269,7 @@ async function executeWithFallback(options) {
185
269
  !shouldFallbackStatus(preflight.status, storage)) {
186
270
  return preflight;
187
271
  }
272
+ await response.body?.cancel().catch(() => { });
188
273
  }
189
274
  return null;
190
275
  }
@@ -203,10 +288,18 @@ async function executeWithFallback(options) {
203
288
  if (!shouldFallbackStatus(primaryPreflight.status, storage))
204
289
  return primaryPreflight;
205
290
  }
206
- if (!fallbackFirst) {
207
- const fallback = await tryFallbackAccounts();
208
- if (fallback)
291
+ const allowApiFallback = primaryResponseAllowsApiFallback(primaryPreflight) &&
292
+ (await primaryQuotaRefreshConfirmsExhausted());
293
+ if (!fallbackFirst || allowApiFallback) {
294
+ const fallback = await tryFallbackAccounts({
295
+ includeApiRoutes: allowApiFallback,
296
+ });
297
+ if (fallback) {
298
+ if (primaryPreflight instanceof Response) {
299
+ await primaryPreflight.body?.cancel().catch(() => { });
300
+ }
209
301
  return fallback;
302
+ }
210
303
  }
211
304
  return primaryPreflight instanceof Response ? primaryPreflight : primary;
212
305
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/pi-anthropic-auth",
3
- "version": "1.7.0",
3
+ "version": "1.8.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.7.0"
39
+ "@cortexkit/anthropic-auth-core": "1.8.0"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@earendil-works/pi-ai": "*",