@cortexkit/pi-anthropic-auth 1.19.1 → 1.20.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,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 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`), 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.
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`, `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`, `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.
46
46
 
47
47
  ## Commands
48
48
 
@@ -59,6 +59,10 @@ The sidecar uses the same JSON shape as the OpenCode package, including `routing
59
59
  /claude-cachekeep 09-23
60
60
  /claude-cachekeep off
61
61
 
62
+ /claude-prime
63
+ /claude-prime on
64
+ /claude-prime off
65
+
62
66
  /claude-dump
63
67
  /claude-dump on
64
68
  /claude-dump off
@@ -78,6 +82,16 @@ The sidecar uses the same JSON shape as the OpenCode package, including `routing
78
82
 
79
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`).
80
84
 
85
+ ### Claude quota window priming
86
+
87
+ Priming is off by default. When enabled from OpenCode with `/claude-prime on`, it watches each OAuth account's 5-hour quota reset and sends one minimal `claude-haiku-4-5` request about one minute after a confirmed reset. This starts the next window without waiting for a normal prompt. Usage is measured from response accounting.
88
+
89
+ For an idle account with no cached reset time, one bootstrap request establishes the first observed window. Atomic temporary-file claims limit multiple processes sharing an account config to one request per account and reset.
90
+
91
+ Prime marker identities live in `anthropic-auth-state.json`. Plugin-owned refresh rotations preserve the main account's lineage, while a host credential replacement creates a new lineage. An existing main lineage without a refresh-token binding attaches to the current credential on its first check without changing identity. On upgrade, an existing fallback account receives an identity during its first prime check; that one-time marker change can send one extra request in the current window.
92
+
93
+ Pi's `/claude-prime` command displays status only. The `on` and `off` arguments are ignored; toggling priming requires OpenCode.
94
+
81
95
  ## Relay
82
96
 
83
97
  The Pi package can use the same user-owned Cloudflare relay config as the OpenCode package. The relay setup helper currently lives in the OpenCode package CLI:
package/dist/commands.js CHANGED
@@ -1,6 +1,6 @@
1
- import { buildClaudeQuotaSummary, buildFallbackQuotaSummaries, CLAUDE_ACCOUNT_COMMAND_NAME, CLAUDE_CACHE_KEEP_COMMAND_NAME, CLAUDE_ROUTING_COMMAND_NAME, createEmptyStorage, executeAccountCommand, executeCache1hCommand, executeCacheKeepCommand, executeDumpCommand, executeFastModeCommand, executeLoggingCommand, executeRoutingCommand, getCache1hPersistentMode, getCacheKeepWindow, getPersistedLogLevel, getRoutingMode, isCache1hPersistentlyEnabled, isCacheKeepAlways, isCacheKeepHybridActive, isCacheKeepPersistentlyEnabled, isDumpPersistentlyEnabled, isFastModePersistentlyEnabled, 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';
2
- import { getPiAccountStoragePath } from "./paths.js";
3
- import { clearPiStickyRoutingSession, getPiTrackedCacheKeepSessions, } from "./stream.js";
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';
2
+ import { getPiAccountStoragePath } from './paths.js';
3
+ import { clearPiStickyRoutingSession, getPiTrackedCacheKeepSessions, } from './stream.js';
4
4
  function notify(ctx, message, kind = 'info') {
5
5
  ctx.ui.notify(message, kind);
6
6
  }
@@ -200,4 +200,21 @@ export function registerCommands(pi) {
200
200
  notify(ctx, executeLoggingCommand({ argumentsText: args ?? '', level }));
201
201
  },
202
202
  });
203
+ pi.registerCommand(CLAUDE_PRIME_COMMAND_NAME, {
204
+ description: "Show each OAuth account's five-hour quota window state",
205
+ handler: async (_args, ctx) => {
206
+ // Pi has no live PrimeManager; render persisted counters and the
207
+ // next-due timestamp from stored quota snapshots. Always pass
208
+ // `argumentsText: 'status'` to the pure executor so on/off args
209
+ // are display-only — Pi cannot toggle the setting.
210
+ const path = getPiAccountStoragePath();
211
+ const storage = await loadAccounts(path);
212
+ const accounts = buildPrimeAccountStatuses(storage, { now: Date.now() });
213
+ notify(ctx, executePrimeCommand({
214
+ argumentsText: 'status',
215
+ enabled: isPrimePersistentlyEnabled(storage),
216
+ accounts,
217
+ }).text);
218
+ },
219
+ });
203
220
  }
package/dist/convert.js CHANGED
@@ -1,4 +1,8 @@
1
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';
2
+ // Anchor identifying Pi's documentation paragraph — the only part of the prompt
3
+ // that Anthropic currently rejects in system[]. Unknown prompt shapes take the
4
+ // service-preserving fallback below instead of returning the full prompt there.
5
+ const PI_DOCS_ANCHOR = 'Pi documentation';
2
6
  const CLAUDE_CODE_TOOLS = new Map([
3
7
  'Read',
4
8
  'Write',
@@ -253,6 +257,38 @@ function addEphemeralCacheControl(body) {
253
257
  break;
254
258
  }
255
259
  }
260
+ function splitPiSystemPrompt(prompt) {
261
+ const sanitized = sanitize(prompt);
262
+ const paragraphs = sanitized.split(/\n\n+/);
263
+ const docs = paragraphs.filter((paragraph) => paragraph.includes(PI_DOCS_ANCHOR));
264
+ // If Pi changes the heading or prompt layout, fail away from the request shape
265
+ // known to trigger billing rejection: carry the complete host prompt in the
266
+ // first user message until the new shape can be classified precisely.
267
+ if (docs.length === 0)
268
+ return { messageText: sanitized };
269
+ const keep = paragraphs.filter((paragraph) => !paragraph.includes(PI_DOCS_ANCHOR));
270
+ return {
271
+ ...(keep.length ? { systemText: keep.join('\n\n') } : {}),
272
+ messageText: docs.join('\n\n'),
273
+ };
274
+ }
275
+ function prependCachedPromptBlock(messages, text) {
276
+ const firstUser = messages.find((message) => message.role === 'user');
277
+ if (!firstUser)
278
+ return;
279
+ const promptBlock = {
280
+ type: 'text',
281
+ text,
282
+ cache_control: { type: 'ephemeral' },
283
+ };
284
+ if (typeof firstUser.content === 'string') {
285
+ firstUser.content = [promptBlock, { type: 'text', text: firstUser.content }];
286
+ return;
287
+ }
288
+ if (Array.isArray(firstUser.content)) {
289
+ firstUser.content.unshift(promptBlock);
290
+ }
291
+ }
256
292
  function applyCacheMode(body, enabled, mode) {
257
293
  if (!enabled)
258
294
  return;
@@ -296,7 +332,35 @@ export async function buildAnthropicRequest(modelId, context, options, cache, fa
296
332
  { type: 'text', text: CLAUDE_CODE_IDENTITY },
297
333
  ];
298
334
  if (context.systemPrompt?.trim()) {
299
- system.push({ type: 'text', text: sanitize(context.systemPrompt) });
335
+ // Pi's prompt cannot sit whole in the top-level system[] array: two lines of
336
+ // its documentation paragraph (the docs/*.md enumeration and the "follow .md
337
+ // cross-references" instruction) are each independently sufficient to make
338
+ // Anthropic reject the request with 400 "You're out of extra usage". Entry
339
+ // count and payload size are ruled out — 2697 bytes of neutral filler in the
340
+ // same position is accepted, and the same text is accepted inside messages[].
341
+ //
342
+ // For the recognized Pi prompt, keep the identity, tool contract, and
343
+ // guidelines in system[], where they carry system weight and survive context
344
+ // compaction, and carry only the documentation paragraph in messages[]. An
345
+ // unknown future prompt shape is moved whole rather than risking this 400.
346
+ //
347
+ // It goes in as its own content block ahead of the user's text, not merged
348
+ // into it and not as a separate message. A cache prefix matches contiguously
349
+ // from the start of the request, so a block boundary here lets the prefix end
350
+ // before the user's words: a new conversation with a different first message
351
+ // still reads the paragraph from cache instead of re-writing ~1.1k tokens.
352
+ // A role: "system" message cannot be used at messages[0] — Anthropic rejects
353
+ // that — and placing one after the first user message puts it behind content
354
+ // that varies, which defeats the caching.
355
+ //
356
+ // cache_control is set explicitly because addEphemeralCacheControl's
357
+ // message-level breakpoint only fires for array content on the *last* user
358
+ // message, which is not this one after the first turn.
359
+ const prompt = splitPiSystemPrompt(context.systemPrompt);
360
+ if (prompt.systemText) {
361
+ system.push({ type: 'text', text: prompt.systemText });
362
+ }
363
+ prependCachedPromptBlock(messages, prompt.messageText);
300
364
  }
301
365
  const body = {
302
366
  model: modelId,
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
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
- import { registerCommands } from "./commands.js";
3
- import { streamCortexKitAnthropic } from "./stream.js";
2
+ import { registerCommands } from './commands.js';
3
+ import { streamCortexKitAnthropic } from './stream.js';
4
4
  async function loginAnthropic(callbacks) {
5
5
  const auth = await authorize('max');
6
6
  callbacks.onAuth({ url: auth.url });
@@ -51,6 +51,15 @@ export default function cortexKitPiAnthropicAuth(pi) {
51
51
  contextWindow: CLAUDE_FABLE_MYTHOS_5_CONTEXT_WINDOW,
52
52
  maxTokens: CLAUDE_FABLE_MYTHOS_5_MAX_OUTPUT_TOKENS,
53
53
  })),
54
+ {
55
+ id: 'claude-opus-5',
56
+ name: 'Claude Opus 5',
57
+ reasoning: true,
58
+ input: textImageInput(),
59
+ cost: { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
60
+ contextWindow: 1_000_000,
61
+ maxTokens: 128_000,
62
+ },
54
63
  {
55
64
  id: 'claude-opus-4-8',
56
65
  name: 'Claude Opus 4.8',
package/dist/stream.js CHANGED
@@ -1,7 +1,7 @@
1
- import { applyClaudeCodeHeaders, CACHE_KEEP_EXTENDED_TTL_BETA, CacheKeepManager, CacheKeepSessionRegistry, createStickyNoRouteResponse, decideStickyQuotaFailure, dumpDirectRequest, FAST_MODE_BETA, FallbackAccountManager, getCache1hPersistentMode, getDefaultCacheKeepRegistryDirectory, 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, 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';
2
2
  import { calculateCost, createAssistantMessageEventStream, } from '@earendil-works/pi-ai';
3
- import { buildAnthropicRequest, fromClaudeCodeToolName } from "./convert.js";
4
- import { getPiAccountStoragePath } from "./paths.js";
3
+ import { buildAnthropicRequest, fromClaudeCodeToolName } from './convert.js';
4
+ import { getPiAccountStoragePath } from './paths.js';
5
5
  function errorText(error) {
6
6
  return error instanceof Error ? error.message : String(error);
7
7
  }
@@ -504,6 +504,7 @@ async function executeWithFallback(options) {
504
504
  if (!resolution) {
505
505
  return createStickyNoRouteResponse({
506
506
  mainRefreshError: storage?.refresh?.mainLastRefreshError,
507
+ fallbackReauthLabels: getFallbackReauthLabels(storage),
507
508
  routeQuotas: routes.allRoutes.flatMap((route) => route.quota ? [route.quota] : []),
508
509
  modelId: options.model.id,
509
510
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/pi-anthropic-auth",
3
- "version": "1.19.1",
3
+ "version": "1.20.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.19.1"
39
+ "@cortexkit/anthropic-auth-core": "1.20.0"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "@earendil-works/pi-ai": "*",