@cortexkit/pi-anthropic-auth 1.23.0 → 2.0.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
@@ -1,109 +1,101 @@
1
1
  # @cortexkit/pi-anthropic-auth
2
2
 
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.
3
+ Pi package for CortexKit Anthropic OAuth support. It replaces the implementation of Pi's built-in Anthropic provider, keeping the `anthropic` provider ID and model IDs, and is backed by the shared `@cortexkit/anthropic-auth-core` package.
4
4
 
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 5.5 (`claude-opus-5-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. Opus 5.5 features always-on adaptive thinking with summarized display. 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.
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 5.5 (`claude-opus-5-5`), Claude Haiku 5.5 (`claude-haiku-5-5`), Claude Opus 4.8, Claude Opus 4.5, Claude Sonnet 4.5, Claude Sonnet 5 (`claude-sonnet-5`), and Claude Sonnet 5.5 (`claude-sonnet-5-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. Opus 5.5 requires adaptive thinking, while Sonnet 5.5 also supports a `between_tools` mode for applications that explicitly turn off up-front thinking. This Pi provider sends adaptive thinking with summarized display for both models. For Sonnet 5.5 it exposes `low` through `max` effort, but not `minimal` (unsupported by the model) or `off` (Pi's simple-provider options cannot request `between_tools`). OAuth Fable 5.1 sessions add effort changes as system messages instead of rewriting earlier history, preserving cached prompt content.
6
+
7
+ Replayed thinking signatures refer to the messages, instructions and tools that preceded the thinking block. If those earlier inputs change, `thinkingBinding.prefixMismatchBehavior` controls how OAuth Fable 5.1, Opus 5.5, Sonnet 5.5 and Haiku 5.5 requests handle that mismatch: `account-default` sends no local override and uses Anthropic's account settings; `error` rejects the request; `drop_block` removes the affected thinking blocks. These controls require adaptive thinking.
8
+
9
+ Haiku 5.5 has a 1M context, 128K output and summarized adaptive thinking with `low` through `max` effort. Haiku 5.5 does not support `minimal` effort. The model accepts disabled thinking, but Pi's simple-provider options cannot request it, so Pi does not expose an `off` level. Standard input/output rates are $0.10/$0.50 per million tokens; all rates increase fivefold above 100K total input including cache. Pi's native pricing tiers apply the selected rates to the whole request, including both cache lifetimes.
6
10
 
7
11
  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
12
 
9
- ## Setup wizard (recommended)
13
+ > [!IMPORTANT]
14
+ > These instructions describe the upcoming native release, which is not yet published.
10
15
 
11
- Use the unified setup wizard to install the extension and optionally configure Claustrum custody:
16
+ ## Setup (recommended)
17
+
18
+ Setup is an *offline* step: every OpenCode, LiteCode, and Pi process must be closed while it runs. Offline does not mean without network; setup still uses the network, for example to install packages and to sign in to Claude. Quit those processes, then run the setup command from the OpenCode package:
12
19
 
13
20
  ```bash
14
21
  bunx @cortexkit/opencode-anthropic-auth setup
15
22
  ```
16
23
 
24
+ What setup does: it installs this extension with `pi install` and migrates Pi to its local account pool, or, if you choose it, enrolls Pi in the Claustrum vault (Claustrum is a local credential-vault service; `ck` is its command-line tool). Useful flags: `--yes` (no prompts), `--dry-run`, `--login` (sign in to Claude during setup), `--claustrum` / `--no-claustrum`, and `--remove-pi-auth` (consent, with `--yes`, to moving Pi's stored Anthropic login). `--login` and `--claustrum` cannot be combined.
25
+
17
26
  ---
18
27
 
19
28
  ## Install
20
29
 
21
30
  Install with Pi's package manager:
22
31
 
23
- ```bash
24
- pi install npm:@cortexkit/pi-anthropic-auth@1.0.0
25
- ```
26
-
27
- For an unpinned install:
28
-
29
32
  ```bash
30
33
  pi install npm:@cortexkit/pi-anthropic-auth
31
34
  ```
32
35
 
36
+ To pin a release, replace `VERSION` with its version number and keep the quotes: `pi install "npm:@cortexkit/pi-anthropic-auth@VERSION"`.
37
+
33
38
  To try it for one run without changing Pi settings:
34
39
 
35
40
  ```bash
36
41
  pi -e npm:@cortexkit/pi-anthropic-auth
37
42
  ```
38
43
 
39
- Restart Pi after installing, then authenticate through Pi's normal login flow:
44
+ The extension keeps Pi's `anthropic` provider ID and the model IDs listed above. It authenticates from the local account pool or the Claustrum vault, not from Pi's `/login anthropic` credential. Run setup before starting Pi.
40
45
 
41
- ```text
42
- /login anthropic
43
- ```
46
+ ## Accounts and custody
44
47
 
45
- ## Sidecar config
48
+ Pi takes Claude credentials from one source, selected by Pi's own Claustrum enrollment record (`~/.local/state/cortexkit/anthropic-auth/pi-enrollment.json`, or `PI_ANTHROPIC_AUTH_CLAUSTRUM_ENROLLMENT_FILE`):
46
49
 
47
- Pi state is stored separately from OpenCode at:
50
+ - **Local custody** (default): the local account pool at `~/.pi/agent/anthropic-auth-pool.json`, with private credential state in `anthropic-auth-pool-state.json`. The pool holds Pi's Claude OAuth accounts (one main account, tried first by default, plus fallbacks) and API-key fallback routes. Its files are created next to the old sidecar path, so `PI_ANTHROPIC_AUTH_FILE` moves them too. Every Pi process shares this pool; OpenCode keeps a separate one.
51
+ - **Vault custody**: the Claustrum vault, through the `anthropic-auth-pi` enrollment and read access to the `anthropic-native` credential category. Setup grants this with `ck auth grant --principal enrolled:anthropic-auth-pi --selector-kind category --selector anthropic-native --operation read`.
48
52
 
49
- ```text
50
- ~/.pi/agent/anthropic-auth.json
51
- ```
53
+ If the enrollment record cannot be read, Pi refuses the requests this extension would serve until the record is repaired or Pi is disconnected. An approved enrollment keeps Pi on the vault even if its token file is missing, so Pi refuses rather than falling back to local accounts. See [the custody contract](https://github.com/cortexkit/anthropic-auth/blob/main/docs/custody-state-machine.md) for details.
52
54
 
53
- Override the path with `PI_ANTHROPIC_AUTH_FILE`. The package also respects `PI_AGENT_DIR` when deriving the default sidecar path.
55
+ ### Local custody
54
56
 
55
- 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.
57
+ Setup imports your fallback Claude OAuth accounts, paid API-key fallback routes, and settings from the old sidecar file (`~/.pi/agent/anthropic-auth.json`, or `PI_ANTHROPIC_AUTH_FILE`) into the pool; the old file is not read afterwards. Pi normally uses its stored login instead of the extension's credentials, so with your consent setup also moves Pi's stored Anthropic OAuth login into the pool as the main account. Until then, Pi refuses to serve with `Pi still has a host Anthropic OAuth credential. Run offline setup to migrate it and approve its removal before using the native pool.`
56
58
 
57
- ## Commands
59
+ Before importing a stored main OAuth login, migration checks the Claude account UUID in the legacy main profile. That profile's recorded token fingerprint (a hash) must match the hash of the host login's current access token; migration does not query Anthropic for identity. If it cannot, it changes nothing and asks you to run `bunx @cortexkit/opencode-anthropic-auth setup --login --no-claustrum`. After setup completes, you can add or replace logins from Pi: open `/claude` and use **Start OAuth sign-in** and **Finish OAuth sign-in**. The first account becomes the main account; later sign-ins add fallback accounts.
58
60
 
59
- ```text
60
- /claude-cache
61
- /claude-cache on
62
- /claude-cache off
63
- /claude-cache mode explicit
64
- /claude-cache mode automatic
65
- /claude-cache mode hybrid
61
+ ### Vault custody
66
62
 
67
- /claude-cachekeep
68
- /claude-cachekeep always
69
- /claude-cachekeep 09-23
70
- /claude-cachekeep off
63
+ Run `bunx @cortexkit/opencode-anthropic-auth setup --claustrum` with the Claustrum daemon running and `ck` installed. Setup writes Pi's enrollment and, with your consent, Pi's auth file. It does not read, import, or change the local pool, its metadata, or routing assignments, and it never copies local OAuth accounts into the vault. If Pi has a stored Anthropic login (OAuth or API key), setup asks for consent, moves it into a private, owner-only stash (`pi-auth-slot/anthropic.json` in the directory of Pi's enrollment record), and leaves an inert placeholder API key in Pi's auth file. The placeholder is a fixed, non-secret value that no provider accepts.
71
64
 
72
- /claude-prime
73
- /claude-prime on
74
- /claude-prime off
65
+ While enrolled, Pi serves only vault accounts; vault accounts added with `ck auth login --provider anthropic` are discovered without a restart. Local accounts are not used, refreshed, quota-polled, kept warm by cache keepalive, or shown, and they return unchanged after disconnect. If the vault has no usable Claude account, requests refuse: add accounts with `ck auth login --provider anthropic`, or disconnect.
75
66
 
76
- /claude-dump
77
- /claude-dump on
78
- /claude-dump off
67
+ **Disconnect from the vault** in `/claude` asks for confirmation, restores the stashed login byte-for-byte, then ends Pi's enrollment. If Pi's auth file already holds a different login, disconnect stops and keeps both logins and the enrollment so you can resolve them with setup. **Disconnect from the vault** is also offered when the enrollment record cannot be read. If the local pool has never been migrated, stop the hosts and run `setup --no-claustrum` before serving local accounts; disconnect does not perform migration.
79
68
 
80
- /claude-fast
81
- /claude-fast on
82
- /claude-fast off
69
+ ## Settings
83
70
 
84
- /claude-routing
85
- /claude-routing main-first
86
- /claude-routing fallback-first
87
- /claude-routing sticky-balanced
88
- /claude-routing reset
71
+ The pool stores the same settings as the OpenCode package: `routing`, `claudeCache`, `cacheKeep`, `prime`, `claudeFast`, `thinkingBinding`, `dump`, `relay`, and the fallback accounts. While Pi is enrolled in the vault, settings are kept separately in `anthropic-auth-vault-settings.json`. `thinkingBinding.prefixMismatchBehavior` accepts `account-default` (default), `error`, or `drop_block` when a Fable 5.1 OAuth request reuses reasoning with an Anthropic signature from an earlier reply after the preceding messages, instructions, or tools change. Sticky session assignments are stored in `anthropic-auth-pool-routing-state.json` (`anthropic-auth-vault-routing-state.json` while enrolled) with SHA-256-hashed session IDs.
89
72
 
90
- /claude-account
91
- /claude-account reset-backoff
73
+ ## The /claude menu
92
74
 
93
- /claude-quota
94
- ```
75
+ Pi registers one command, `/claude`. It opens a menu; each action collects its own parameters, and typed arguments are rejected with a pointer to the menu. Without a UI, `/claude` prints the status sections only.
76
+
77
+ | Group | Status | Actions |
78
+ | --- | --- | --- |
79
+ | Accounts | Account status and usage | Enable account, Disable account, Remove account, Move account up, Move account down, Reset account backoff, Reset host enrollment, Add API-key fallback, Start OAuth sign-in, Finish OAuth sign-in, Custody setup guidance, Disconnect from the vault |
80
+ | Quota | Quota summary | — |
81
+ | Routing | Routing status | Routing mode (`main-first`, `fallback-first`, `sticky-balanced`), Reset routing session |
82
+ | Cache | Cache status, Cache keep status | Enable cache, Disable cache, Cache mode (`explicit`, `automatic`, `hybrid`), Keep cache warm always, Disable cache keep, Cache keep window, Cache keep for subagents (`on`/`off`) |
83
+ | Diagnostics | Dump status, Logging status | Enable request dumps, Disable request dumps, Logging level (`error`, `warn`, `info`, `debug`, `trace`) |
84
+ | Extras | Fast mode status, Prime quota window status | Enable fast mode, Disable fast mode |
85
+
86
+ In local custody, every Accounts action except **Disconnect from the vault** is offered. While Pi is enrolled, or when its enrollment cannot be read, the Accounts group shows only enable, disable, **Reset account backoff**, and **Disconnect from the vault**. **Remove account**, **Reset host enrollment**, and **Disconnect from the vault** ask for confirmation. Killswitch, quota refresh, Prime on/off, and lane start are OpenCode-only.
95
87
 
96
- `/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. Changing the session model discards the old assignment before quota-based reselection. `/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`, `claude-opus-5`, and `claude-opus-5-5`).
88
+ **Reset account 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 clears the backoff left by the old refresh token. **Quota summary** reports quota for the accounts of the active custody source. **Routing mode** → `fallback-first` prefers usable OAuth fallback accounts before the main account; `main-first` restores the default. `sticky-balanced` assigns each Pi session to an OAuth account according to current 5-hour, 7-day, and model-specific quota headroom, then keeps the session on that account (session affinity), persisting the assignment across transient failures and process restarts. Changing the session model discards the old assignment before quota-based reselection. **Reset routing session** clears the current Pi session's assignment. Direct Opus sessions prefer usable accounts whose Fable quota (the separate weekly limit for Claude Fable models) is exhausted, saving the other accounts for Fable sessions. API-key routes 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. **Keep cache warm always** keeps active hybrid caches warm while Pi remains open; **Cache keep window** limits prewarms to a local time window. Both send `max_tokens: 0` pre-warm requests about five minutes before the 1-hour TTL expires. **Cache keep status** lists live tracked sessions across Pi processes through a temporary lease registry that stores only session IDs and cache timing. **Enable fast mode** adds Anthropic `speed: "fast"` plus the `fast-mode-2026-02-01` beta header for supported Opus models (`claude-opus-4-8`, `claude-opus-5`, and `claude-opus-5-5`).
97
89
 
98
90
  ### Claude quota window priming
99
91
 
100
- 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.
92
+ Priming is off by default. When enabled from OpenCode's `/claude` menu with **Enable Prime**, 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.
101
93
 
102
- 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.
94
+ For an idle account with no cached reset time, one minimal request starts a usage window; the follow-up quota check learns its reset time. When several processes share an account store, each tries to create a claim file for that account and reset; creating the file is atomic, so exactly one process succeeds and sends the request.
103
95
 
104
- 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.
96
+ Claim files live in a temporary directory, separated by account store and credential lineage. Cleanup removes files older than six hours. Ordinary token refresh keeps the lineage; a new sign-in starts a new one. Private runtime state stores cumulative priming usage counters, not a history of primed windows. Legacy claim files are not migrated. Without the old claim, the new scheduler can treat an already primed window as unclaimed and send one extra request.
105
97
 
106
- Pi's `/claude-prime` command displays status only. The `on` and `off` arguments are ignored; toggling priming requires OpenCode.
98
+ Pi's menu shows **Prime quota window status** only; turn priming on or off with **Enable Prime** or **Disable Prime** in OpenCode's `/claude` menu.
107
99
 
108
100
  ## Anthropic-compatible proxy overrides
109
101
 
@@ -111,13 +103,13 @@ API-key fallback routes honor `ANTHROPIC_CUSTOM_HEADERS`, `ANTHROPIC_MODEL`, and
111
103
 
112
104
  ## Relay
113
105
 
114
- 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:
106
+ The Pi package can use a user-owned Cloudflare relay over HTTP or WebSocket transport. When setup migrates Pi, it imports an existing `relay` block from Pi's old sidecar file (`~/.pi/agent/anthropic-auth.json`, or `PI_ANTHROPIC_AUTH_FILE`). The relay setup helper in the OpenCode package CLI provisions a Worker and saves its settings in OpenCode's local account pool; it does not configure Pi:
115
107
 
116
108
  ```bash
117
109
  CLOUDFLARE_API_TOKEN=... CLOUDFLARE_ACCOUNT_ID=... bunx @cortexkit/opencode-anthropic-auth relay setup
118
110
  ```
119
111
 
120
- For Pi, copy the generated `relay` block into `~/.pi/agent/anthropic-auth.json`.
112
+ The `/claude` menu has no relay actions.
121
113
 
122
114
  ## License
123
115
 
@@ -1,3 +1,23 @@
1
- import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
2
- import type { PiCustodyCommands } from './custody.ts';
3
- export declare function registerCommands(pi: ExtensionAPI, custody?: PiCustodyCommands): void;
1
+ import { type NativeMenuCommandId, type NativeMenuDispatch, type NativeUiOptions } from '@cortexkit/anthropic-auth-core';
2
+ import { type ExtensionAPI } from '@earendil-works/pi-coding-agent';
3
+ /** Backend callbacks use native authority; the renderer never receives credentials. */
4
+ export interface PiNativeCommands {
5
+ dispatch: NativeMenuDispatch;
6
+ readStatus: (command: Exclude<NativeMenuCommandId, 'start'>, invocation: {
7
+ sessionId?: string;
8
+ }) => Promise<string>;
9
+ /**
10
+ * Reads Pi's credential custody for each menu open and apply. When left
11
+ * out, the enrollment record the environment points to is read; there is
12
+ * no local fallback.
13
+ */
14
+ readMode?: NativeUiOptions['readMode'];
15
+ readAccounts?: NativeUiOptions['readAccounts'];
16
+ /**
17
+ * Disconnects Pi from the vault. When left out, Core first restores the
18
+ * Anthropic sign-in that enrollment set aside in Pi's own auth file, then
19
+ * ends Pi's enrollment.
20
+ */
21
+ disconnectVault?: NativeUiOptions['disconnectVault'];
22
+ }
23
+ export declare function registerCommands(pi: ExtensionAPI, backend: PiNativeCommands): void;
package/dist/commands.js CHANGED
@@ -1,261 +1,67 @@
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, formatEnrollmentStatus, getCache1hPersistentMode, getCacheKeepWindow, getClaustrumMode, 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
- import { getPiAccountStoragePath } from './paths.js';
3
- import { clearPiStickyRoutingSession, getPiTrackedCacheKeepSessions, } from './stream.js';
4
- function notify(ctx, message, kind = 'info') {
5
- ctx.ui.notify(message, kind);
6
- }
7
- export function registerCommands(pi, custody) {
8
- pi.registerCommand('claude-cache', {
9
- description: 'Show or configure Claude 1-hour prompt cache mode',
10
- handler: async (args, ctx) => {
11
- const path = getPiAccountStoragePath();
12
- const storage = await loadAccounts(path);
13
- const action = parseCache1hCommandAction(args ?? '');
14
- const enabled = isCache1hPersistentlyEnabled(storage);
15
- const mode = getCache1hPersistentMode(storage);
16
- const nextEnabled = action.type === 'enable'
17
- ? true
18
- : action.type === 'disable'
19
- ? false
20
- : enabled;
21
- const nextMode = action.type === 'mode' ? action.mode : mode;
22
- if (action.type === 'enable') {
23
- await setCache1hPersistentEnabled(true, undefined, path);
24
- }
25
- else if (action.type === 'disable') {
26
- await setCache1hPersistentEnabled(false, undefined, path);
27
- }
28
- else if (action.type === 'mode') {
29
- await setCache1hPersistentMode(action.mode, path);
30
- }
31
- notify(ctx, executeCache1hCommand({
32
- argumentsText: args ?? '',
33
- enabled: nextEnabled,
34
- mode: nextMode,
35
- }), action.type === 'usage' ? 'warning' : 'info');
36
- },
37
- });
38
- pi.registerCommand(CLAUDE_CACHE_KEEP_COMMAND_NAME, {
39
- description: 'Keep hybrid Claude cache warm always or during a local time window',
40
- handler: async (args, ctx) => {
41
- const path = getPiAccountStoragePath();
42
- let storage = await loadAccounts(path);
43
- const action = parseCacheKeepCommandAction(args ?? '');
44
- if (action.type === 'window') {
45
- storage = await setCacheKeepPersistentWindow(action.startHour, action.endHour, path);
46
- }
47
- else if (action.type === 'always') {
48
- storage = await setCacheKeepPersistentAlways(path);
49
- }
50
- else if (action.type === 'disable') {
51
- storage = await setCacheKeepPersistentEnabled(false, path);
52
- }
53
- else if (action.type === 'subagents') {
54
- storage = await setCacheKeepSubagentsEnabled(action.enabled, path);
55
- }
56
- const trackedSessionDetails = await getPiTrackedCacheKeepSessions();
57
- const nextPrewarmAt = trackedSessionDetails.length
58
- ? Math.min(...trackedSessionDetails.map((session) => session.nextPrewarmAt))
59
- : undefined;
60
- notify(ctx, executeCacheKeepCommand({
61
- argumentsText: args ?? '',
62
- enabled: isCacheKeepPersistentlyEnabled(storage),
63
- always: isCacheKeepAlways(storage),
64
- window: getCacheKeepWindow(storage),
65
- hybridActive: isCacheKeepHybridActive(storage),
66
- trackedSessions: trackedSessionDetails.length,
67
- trackedSessionDetails,
68
- nextPrewarmAt,
69
- }), action.type === 'usage' ? 'warning' : 'info');
70
- },
71
- });
72
- pi.registerCommand('claude-dump', {
73
- description: 'Show or configure Anthropic request/relay dumping',
74
- handler: async (args, ctx) => {
75
- const path = getPiAccountStoragePath();
76
- const storage = await loadAccounts(path);
77
- const action = parseDumpCommandAction(args ?? '');
78
- const enabled = isDumpPersistentlyEnabled(storage);
79
- const nextEnabled = action.type === 'enable'
80
- ? true
81
- : action.type === 'disable'
82
- ? false
83
- : enabled;
84
- if (action.type === 'enable') {
85
- await setDumpPersistentEnabled(true, path);
86
- setDumpEnabled(true);
87
- }
88
- else if (action.type === 'disable') {
89
- await setDumpPersistentEnabled(false, path);
90
- setDumpEnabled(false);
91
- }
92
- else {
93
- setDumpEnabled(enabled);
94
- }
95
- notify(ctx, executeDumpCommand({ argumentsText: args ?? '', enabled: nextEnabled }), action.type === 'usage' ? 'warning' : 'info');
96
- },
97
- });
98
- pi.registerCommand('claude-fast', {
99
- description: 'Show or configure Anthropic fast mode for supported Opus models',
100
- handler: async (args, ctx) => {
101
- const path = getPiAccountStoragePath();
102
- const storage = await loadAccounts(path);
103
- const action = parseFastModeCommandAction(args ?? '');
104
- const enabled = isFastModePersistentlyEnabled(storage);
105
- const nextEnabled = action.type === 'enable'
106
- ? true
107
- : action.type === 'disable'
108
- ? false
109
- : enabled;
110
- if (action.type === 'enable') {
111
- await setFastModePersistentEnabled(true, path);
112
- }
113
- else if (action.type === 'disable') {
114
- await setFastModePersistentEnabled(false, path);
115
- }
116
- notify(ctx, executeFastModeCommand({
117
- argumentsText: args ?? '',
118
- enabled: nextEnabled,
119
- }), action.type === 'usage' ? 'warning' : 'info');
120
- },
121
- });
122
- pi.registerCommand(CLAUDE_ROUTING_COMMAND_NAME, {
123
- description: 'Show or change Claude account routing mode',
124
- handler: async (args, ctx) => {
125
- const path = getPiAccountStoragePath();
126
- let storage = await loadAccounts(path);
127
- const action = parseRoutingCommandAction(args ?? '');
128
- if (action.type === 'mode') {
129
- storage = await setRoutingMode(action.mode, path);
130
- }
131
- else if (action.type === 'reset') {
132
- await clearPiStickyRoutingSession(path, ctx.sessionManager.getSessionId());
133
- }
134
- notify(ctx, executeRoutingCommand({
135
- argumentsText: args ?? '',
136
- mode: getRoutingMode(storage),
137
- }), action.type === 'usage' ? 'warning' : 'info');
138
- },
139
- });
140
- pi.registerCommand('claude-quota', {
141
- description: 'Show Claude quota state for fallback accounts',
142
- handler: async (_args, ctx) => {
143
- const storage = await loadAccounts(getPiAccountStoragePath());
144
- notify(ctx, buildClaudeQuotaSummary({
145
- accounts: buildFallbackQuotaSummaries(storage),
146
- now: Date.now(),
147
- }));
148
- },
149
- });
150
- pi.registerCommand(CLAUDE_ACCOUNT_COMMAND_NAME, {
151
- description: 'List, enable/disable, reorder, or remove fallback accounts',
1
+ import { join } from 'node:path';
2
+ import { createNativeUi, disconnectNativeVaultForHost, openNativePiAuthSlot, readNativeEnrollmentMode, runPiCommandMenu, } from '@cortexkit/anthropic-auth-core';
3
+ import { getAgentDir } from '@earendil-works/pi-coding-agent';
4
+ /**
5
+ * Pi's default disconnect. The auth file is the one Pi's SDK uses
6
+ * (getAgentDir() honours PI_CODING_AGENT_DIR); Core chooses the private
7
+ * stash and placeholder, so setup and disconnect always agree on them.
8
+ */
9
+ const disconnectPi = () => disconnectNativeVaultForHost({
10
+ host: 'pi',
11
+ env: process.env,
12
+ slot: openNativePiAuthSlot({
13
+ env: process.env,
14
+ authPath: join(getAgentDir(), 'auth.json'),
15
+ }),
16
+ });
17
+ export function registerCommands(pi, backend) {
18
+ pi.registerCommand('claude', {
19
+ description: 'Claude accounts, quota, routing, limits, cache and diagnostics',
152
20
  handler: async (args, ctx) => {
153
- const path = getPiAccountStoragePath();
154
- const storage = await loadAccounts(path);
155
- const action = parseAccountCommandAction(args ?? '');
156
- const result = await executeAccountCommand({
157
- argumentsText: args ?? '',
158
- storage: storage ?? createEmptyStorage(),
159
- path,
160
- transition: custody?.transition ??
161
- (async () => ({
162
- text: 'Refused: Pi custody controller is unavailable.',
163
- })),
164
- resetEnrollment: custody?.reset,
165
- claustrum: action.type === 'status'
166
- ? await detectClaustrumConnection()
167
- : undefined,
168
- });
169
- if (action.type === 'status' && custody) {
170
- try {
171
- result.text += `\n${formatEnrollmentStatus(await custody.status(), getClaustrumMode(storage) === 'claustrum').join('\n')}`;
172
- }
173
- catch {
174
- result.text += '\n- Enrollment: unavailable';
175
- }
176
- }
177
- if (!result.updated) {
178
- notify(ctx, result.text, result.text.startsWith('Refused:') ? 'error' : 'info');
21
+ if (args?.trim()) {
22
+ ctx.ui.notify('Use /claude to open the menu. Action parameters are collected in the selected action; custody changes require offline setup.', 'warning');
179
23
  return;
180
24
  }
181
- // Wire persistent mutations via core helpers
182
- const { id, action: mutationAction } = result.updated;
183
- if (mutationAction === 'enable') {
184
- await setAccountEnabledPersistent(id, true, path);
185
- }
186
- else if (mutationAction === 'disable') {
187
- await setAccountEnabledPersistent(id, false, path);
188
- }
189
- else if (mutationAction === 'remove') {
190
- const existed = await removeAccountPersistent(id, path);
191
- if (!existed) {
192
- notify(ctx, `Account "${id}" not found.`, 'warning');
193
- return;
194
- }
195
- }
196
- else if (mutationAction === 'reorder') {
197
- const newOrder = result.updated.newOrder;
198
- if (newOrder) {
199
- await reorderAccountsPersistent(newOrder, path);
200
- }
25
+ const disconnect = backend.disconnectVault ?? disconnectPi;
26
+ const menu = createNativeUi({
27
+ host: 'pi',
28
+ dispatch: backend.dispatch,
29
+ readStatus: backend.readStatus,
30
+ readAccounts: backend.readAccounts,
31
+ readMode: backend.readMode ??
32
+ (() => readNativeEnrollmentMode('pi', process.env)),
33
+ disconnectVault: async (options) => {
34
+ const result = await disconnect(options);
35
+ // Reload after successful or already-inactive enrollment so Pi sees
36
+ // any restored sign-in. Reload failure cannot undo the completed
37
+ // disconnect, so report it separately without exposing the exception.
38
+ if (result === 'disconnected' || result === 'idle') {
39
+ try {
40
+ await ctx.modelRegistry.refresh();
41
+ }
42
+ catch {
43
+ ctx.ui.notify('Vault enrollment is inactive, but Pi could not reload its saved sign-in. Restart Pi before sending another request.', 'error');
44
+ }
45
+ }
46
+ return result;
47
+ },
48
+ interactive: ctx.hasUI,
49
+ });
50
+ const sessionId = ctx.sessionManager.getSessionId();
51
+ if (ctx.hasUI) {
52
+ await runPiCommandMenu(menu, ctx.ui, { sessionId });
201
53
  }
202
- else if (mutationAction === 'reset-backoff') {
203
- const nextStorage = storage ?? createEmptyStorage();
204
- nextStorage.refresh = nextStorage.refresh ?? {};
205
- nextStorage.refresh.mainLastRefreshError = undefined;
206
- nextStorage.refresh.mainRefreshErrorClearedAt =
207
- mergeMainRefreshErrorClearedAt(nextStorage.refresh.mainRefreshErrorClearedAt, Date.now());
208
- const mainIdentity = nextStorage.mainAccountId;
209
- const quotaError = nextStorage.quota?.mainLastQuotaApiError;
210
- if (!quotaError?.accountIdentity ||
211
- !mainIdentity ||
212
- quotaError.accountIdentity === mainIdentity) {
213
- nextStorage.quota = nextStorage.quota ?? {};
214
- nextStorage.quota.mainLastQuotaApiError = undefined;
215
- const nextQuotaErrorGeneration = (nextStorage.quota.mainQuotaErrorGeneration ?? 0) + 1;
216
- nextStorage.quota.mainQuotaErrorGeneration = Math.max(nextStorage.quota.mainQuotaErrorGeneration ?? 0, nextQuotaErrorGeneration);
217
- nextStorage.quota.mainQuotaErrorClearedAt =
218
- mergeMainQuotaErrorClearedAt(nextStorage.quota.mainQuotaErrorClearedAt, Date.now());
219
- }
220
- await saveAccountState(nextStorage, path, {
221
- mainRefresh: true,
222
- mainQuota: true,
54
+ else {
55
+ // A scripted invocation can inspect status, but cannot answer prompts or
56
+ // silently confirm a destructive action.
57
+ const payload = await menu.open({
58
+ sessionId,
59
+ notify: (message, kind) => ctx.ui.notify(message, kind),
223
60
  });
61
+ ctx.ui.notify(payload.menu.sections
62
+ .map((section) => `${section.title}\n${section.lines.join('\n')}`)
63
+ .join('\n\n'), 'info');
224
64
  }
225
- notify(ctx, result.text);
226
- },
227
- });
228
- pi.registerCommand('claude-logging', {
229
- description: 'Show or set the plugin log level',
230
- handler: async (args, ctx) => {
231
- const path = getPiAccountStoragePath();
232
- const storage = await loadAccounts(path);
233
- const action = parseLoggingCommandAction(args ?? '');
234
- const currentLevel = getPersistedLogLevel(storage) ?? 'info';
235
- // Wire persistent log-level mutation
236
- let level = currentLevel;
237
- if (action.type === 'level') {
238
- await setLogLevelPersistent(action.level, path);
239
- level = action.level;
240
- }
241
- notify(ctx, executeLoggingCommand({ argumentsText: args ?? '', level }));
242
- },
243
- });
244
- pi.registerCommand(CLAUDE_PRIME_COMMAND_NAME, {
245
- description: "Show each OAuth account's five-hour quota window state",
246
- handler: async (_args, ctx) => {
247
- // Pi has no live PrimeManager; render persisted counters and the
248
- // next-due timestamp from stored quota snapshots. Always pass
249
- // `argumentsText: 'status'` to the pure executor so on/off args
250
- // are display-only — Pi cannot toggle the setting.
251
- const path = getPiAccountStoragePath();
252
- const storage = await loadAccounts(path);
253
- const accounts = buildPrimeAccountStatuses(storage, { now: Date.now() });
254
- notify(ctx, executePrimeCommand({
255
- argumentsText: 'status',
256
- enabled: isPrimePersistentlyEnabled(storage),
257
- accounts,
258
- }).text);
259
65
  },
260
66
  });
261
67
  }
package/dist/convert.d.ts CHANGED
@@ -13,6 +13,9 @@ export type AnthropicRequestBody = {
13
13
  } | {
14
14
  type: 'adaptive';
15
15
  display: 'summarized';
16
+ block_binding?: {
17
+ prefix_mismatch_behavior: Exclude<ThinkingPrefixMismatchBehavior, 'account-default'>;
18
+ };
16
19
  };
17
20
  output_config?: {
18
21
  effort: string;
@@ -33,4 +36,5 @@ export declare function buildAnthropicRequest(modelId: string, context: Context,
33
36
  }): Promise<{
34
37
  body: AnthropicRequestBody;
35
38
  bodyText: string;
39
+ hostTools: Tool[];
36
40
  }>;