@cortexkit/pi-anthropic-auth 1.21.0 → 1.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -3
- package/dist/commands.d.ts +2 -1
- package/dist/commands.js +45 -4
- package/dist/convert.d.ts +6 -2
- package/dist/convert.js +132 -29
- package/dist/custody.d.ts +17 -0
- package/dist/custody.js +66 -0
- package/dist/effort-history.d.ts +27 -0
- package/dist/effort-history.js +103 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +143 -19
- package/dist/paths.d.ts +11 -0
- package/dist/paths.js +42 -2
- package/dist/stream.d.ts +4 -2
- package/dist/stream.js +316 -47
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -2,10 +2,20 @@
|
|
|
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 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.
|
|
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
|
|
|
9
|
+
## Setup wizard (recommended)
|
|
10
|
+
|
|
11
|
+
Use the unified setup wizard to install the extension and optionally configure Claustrum custody:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bunx @cortexkit/opencode-anthropic-auth setup
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
9
19
|
## Install
|
|
10
20
|
|
|
11
21
|
Install with Pi's package manager:
|
|
@@ -42,7 +52,7 @@ Pi state is stored separately from OpenCode at:
|
|
|
42
52
|
|
|
43
53
|
Override the path with `PI_ANTHROPIC_AUTH_FILE`. The package also respects `PI_AGENT_DIR` when deriving the default sidecar path.
|
|
44
54
|
|
|
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.
|
|
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.
|
|
46
56
|
|
|
47
57
|
## Commands
|
|
48
58
|
|
|
@@ -77,10 +87,13 @@ The sidecar uses the same JSON shape as the OpenCode package, including `routing
|
|
|
77
87
|
/claude-routing sticky-balanced
|
|
78
88
|
/claude-routing reset
|
|
79
89
|
|
|
90
|
+
/claude-account
|
|
91
|
+
/claude-account reset-backoff
|
|
92
|
+
|
|
80
93
|
/claude-quota
|
|
81
94
|
```
|
|
82
95
|
|
|
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`).
|
|
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`).
|
|
84
97
|
|
|
85
98
|
### Claude quota window priming
|
|
86
99
|
|
|
@@ -92,6 +105,10 @@ Prime marker identities live in `anthropic-auth-state.json`. Plugin-owned refres
|
|
|
92
105
|
|
|
93
106
|
Pi's `/claude-prime` command displays status only. The `on` and `off` arguments are ignored; toggling priming requires OpenCode.
|
|
94
107
|
|
|
108
|
+
## Anthropic-compatible proxy overrides
|
|
109
|
+
|
|
110
|
+
API-key fallback routes honor `ANTHROPIC_CUSTOM_HEADERS`, `ANTHROPIC_MODEL`, and `ANTHROPIC_DEFAULT_{SONNET,OPUS,HAIKU,FABLE}_MODEL`. These variables never alter OAuth requests. Custom headers may add proxy metadata but cannot replace route authentication, Anthropic protocol headers, body framing, or internal correlation headers; a configuration containing an invalid or protected header is ignored as a whole. Versioned provider base paths are preserved without duplicating `/v1`.
|
|
111
|
+
|
|
95
112
|
## Relay
|
|
96
113
|
|
|
97
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:
|
package/dist/commands.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
2
|
-
|
|
2
|
+
import type { PiCustodyCommands } from './custody.ts';
|
|
3
|
+
export declare function registerCommands(pi: ExtensionAPI, custody?: PiCustodyCommands): void;
|
package/dist/commands.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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, 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
2
|
import { getPiAccountStoragePath } from './paths.js';
|
|
3
3
|
import { clearPiStickyRoutingSession, getPiTrackedCacheKeepSessions, } from './stream.js';
|
|
4
4
|
function notify(ctx, message, kind = 'info') {
|
|
5
5
|
ctx.ui.notify(message, kind);
|
|
6
6
|
}
|
|
7
|
-
export function registerCommands(pi) {
|
|
7
|
+
export function registerCommands(pi, custody) {
|
|
8
8
|
pi.registerCommand('claude-cache', {
|
|
9
9
|
description: 'Show or configure Claude 1-hour prompt cache mode',
|
|
10
10
|
handler: async (args, ctx) => {
|
|
@@ -152,12 +152,30 @@ export function registerCommands(pi) {
|
|
|
152
152
|
handler: async (args, ctx) => {
|
|
153
153
|
const path = getPiAccountStoragePath();
|
|
154
154
|
const storage = await loadAccounts(path);
|
|
155
|
-
const
|
|
155
|
+
const action = parseAccountCommandAction(args ?? '');
|
|
156
|
+
const result = await executeAccountCommand({
|
|
156
157
|
argumentsText: args ?? '',
|
|
157
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,
|
|
158
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
|
+
}
|
|
159
177
|
if (!result.updated) {
|
|
160
|
-
notify(ctx, result.text);
|
|
178
|
+
notify(ctx, result.text, result.text.startsWith('Refused:') ? 'error' : 'info');
|
|
161
179
|
return;
|
|
162
180
|
}
|
|
163
181
|
// Wire persistent mutations via core helpers
|
|
@@ -181,6 +199,29 @@ export function registerCommands(pi) {
|
|
|
181
199
|
await reorderAccountsPersistent(newOrder, path);
|
|
182
200
|
}
|
|
183
201
|
}
|
|
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,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
184
225
|
notify(ctx, result.text);
|
|
185
226
|
},
|
|
186
227
|
});
|
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;
|
|
@@ -19,6 +19,7 @@ export type AnthropicRequestBody = {
|
|
|
19
19
|
};
|
|
20
20
|
cache_control?: {
|
|
21
21
|
type: 'ephemeral';
|
|
22
|
+
ttl?: '1h';
|
|
22
23
|
};
|
|
23
24
|
speed?: 'fast';
|
|
24
25
|
};
|
|
@@ -26,7 +27,10 @@ export declare function fromClaudeCodeToolName(name: string, tools?: Tool[]): st
|
|
|
26
27
|
export declare function buildAnthropicRequest(modelId: string, context: Context, options: SimpleStreamOptions | undefined, cache: {
|
|
27
28
|
enabled: boolean;
|
|
28
29
|
mode: Cache1hMode;
|
|
29
|
-
}, fastModeEnabled?: boolean, identity?: ClaudeCodeIdentity
|
|
30
|
+
}, fastModeEnabled?: boolean, identity?: ClaudeCodeIdentity, controls?: {
|
|
31
|
+
effortTransitions?: readonly MidConversationEffortTransition[];
|
|
32
|
+
thinkingPrefixMismatchBehavior?: ThinkingPrefixMismatchBehavior;
|
|
33
|
+
}): Promise<{
|
|
30
34
|
body: AnthropicRequestBody;
|
|
31
35
|
bodyText: string;
|
|
32
36
|
}>;
|
package/dist/convert.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
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_5_ADAPTIVE_THINKING, CLAUDE_OPUS_5_ADAPTIVE_THINKING, CLAUDE_SONNET_5_ADAPTIVE_THINKING, ClaudeCodeFirstUserTextTracker, isClaudeFableOrMythos5Model, isClaudeOpus5Model, isClaudeOpus55Model, isClaudeSonnet5Model, isFastModeSupportedModel, isOpenAIReasoningSignature, orderClaudeCodeBody, signRequestBody, } from '@cortexkit/anthropic-auth-core';
|
|
2
|
+
import { collapseSystemMessages, getCurrentSystemPrompt, getCurrentTools, normalizeContext, } from '@earendil-works/pi-ai';
|
|
2
3
|
// Anchor identifying Pi's documentation paragraph — the only part of the prompt
|
|
3
4
|
// that Anthropic currently rejects in system[]. Unknown prompt shapes take the
|
|
4
5
|
// service-preserving fallback below instead of returning the full prompt there.
|
|
5
6
|
const PI_DOCS_ANCHOR = 'Pi documentation';
|
|
7
|
+
const firstUserTextTracker = new ClaudeCodeFirstUserTextTracker();
|
|
6
8
|
const ANTHROPIC_REPLAY_APIS = new Set([
|
|
7
9
|
'anthropic-messages',
|
|
8
10
|
'cortexkit-anthropic-messages',
|
|
@@ -291,6 +293,49 @@ function addEphemeralCacheControl(body) {
|
|
|
291
293
|
break;
|
|
292
294
|
}
|
|
293
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Flatten a host system prompt to text.
|
|
298
|
+
*
|
|
299
|
+
* Pi types `Context.systemPrompt` as `string`, but Oh My Pi 18.x types it as
|
|
300
|
+
* `string[]` — "ordered system prompt blocks" — and hands that array to the
|
|
301
|
+
* provider unflattened, where `.trim()` is not a function and no request was
|
|
302
|
+
* ever built (issue #201).
|
|
303
|
+
*
|
|
304
|
+
* Blocks join on a blank line, which is how the host's own providers flatten
|
|
305
|
+
* them (`normalizeSystemPrompts(prompt).join('\n\n')`), so the paragraph split
|
|
306
|
+
* below classifies the same text on either host. The host asks providers to
|
|
307
|
+
* preserve its entries as distinct blocks, and that is deliberately not done
|
|
308
|
+
* here: an unrecognized prompt shape is carried in messages[] precisely because
|
|
309
|
+
* placing the host prompt in top-level system[] is the request shape Anthropic
|
|
310
|
+
* rejects with 400 "You're out of extra usage" (see splitPiSystemPrompt).
|
|
311
|
+
* Joining loses no text and no paragraph boundary; re-emitting the entries as
|
|
312
|
+
* system[] blocks would reintroduce that rejection.
|
|
313
|
+
*
|
|
314
|
+
* Only a `{ type: 'text', text }` block is read. The host declares strings, so
|
|
315
|
+
* that branch is for the next drift in this same field: returning '' there
|
|
316
|
+
* would silently ship requests with no host prompt at all, which is worse than
|
|
317
|
+
* the crash this replaces. `type` is checked rather than reading any object's
|
|
318
|
+
* `text` field, so a tool, image, or other structured block never has its
|
|
319
|
+
* metadata flattened into the prompt.
|
|
320
|
+
*/
|
|
321
|
+
function systemPromptText(prompt) {
|
|
322
|
+
if (typeof prompt === 'string')
|
|
323
|
+
return prompt;
|
|
324
|
+
if (!Array.isArray(prompt))
|
|
325
|
+
return '';
|
|
326
|
+
const parts = [];
|
|
327
|
+
for (const block of prompt) {
|
|
328
|
+
if (typeof block === 'string') {
|
|
329
|
+
parts.push(block);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const record = block;
|
|
333
|
+
if (record?.type === 'text' && typeof record.text === 'string') {
|
|
334
|
+
parts.push(record.text);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return parts.join('\n\n');
|
|
338
|
+
}
|
|
294
339
|
function splitPiSystemPrompt(prompt) {
|
|
295
340
|
const sanitized = sanitize(prompt);
|
|
296
341
|
const paragraphs = sanitized.split(/\n\n+/);
|
|
@@ -323,35 +368,83 @@ function prependCachedPromptBlock(messages, text) {
|
|
|
323
368
|
firstUser.content.unshift(promptBlock);
|
|
324
369
|
}
|
|
325
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Visit every object that can legitimately hold an Anthropic cache breakpoint:
|
|
373
|
+
* the request root, each `system[]` block, each tool, and each message with its
|
|
374
|
+
* content blocks — exactly where addEphemeralCacheControl and
|
|
375
|
+
* prependCachedPromptBlock place them.
|
|
376
|
+
*
|
|
377
|
+
* Deliberately not a deep walk. A tool's `input_schema` and a replayed
|
|
378
|
+
* `tool_use.input` are arbitrary caller data, so a nested field named
|
|
379
|
+
* `cache_control` there is a tool parameter or argument, not a breakpoint:
|
|
380
|
+
* deleting it or writing `ttl` into it would corrupt the tool contract.
|
|
381
|
+
*/
|
|
382
|
+
function walkCacheControlHolders(body, visit) {
|
|
383
|
+
const holders = [body];
|
|
384
|
+
if (body.system)
|
|
385
|
+
holders.push(...body.system);
|
|
386
|
+
if (body.tools)
|
|
387
|
+
holders.push(...body.tools);
|
|
388
|
+
for (const message of body.messages) {
|
|
389
|
+
holders.push(message);
|
|
390
|
+
if (Array.isArray(message.content))
|
|
391
|
+
holders.push(...message.content);
|
|
392
|
+
}
|
|
393
|
+
for (const holder of holders) {
|
|
394
|
+
if (!holder || typeof holder !== 'object')
|
|
395
|
+
continue;
|
|
396
|
+
const record = holder;
|
|
397
|
+
const cacheControl = record.cache_control;
|
|
398
|
+
if (cacheControl && typeof cacheControl === 'object')
|
|
399
|
+
visit(record);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Anthropic accepts at most four cache breakpoints per request. This provider
|
|
404
|
+
* composes its own body and has already placed exactly four
|
|
405
|
+
* (addEphemeralCacheControl's last tool, last system block and last user block,
|
|
406
|
+
* plus the cached prompt block on the first user message), so the top-level
|
|
407
|
+
* control this used to add on top of them made five cache_control sites in one
|
|
408
|
+
* body — the shape behind "A maximum of 4 blocks with cache_control may be
|
|
409
|
+
* provided. Found 5." on a request that never reached the model (issue #201).
|
|
410
|
+
*
|
|
411
|
+
* Each mode now places its own breakpoints and nothing else, as the OpenCode
|
|
412
|
+
* rewrite path does (`applyAutomaticCache1h` / `applyHybridCache1h` both clear
|
|
413
|
+
* every breakpoint first):
|
|
414
|
+
* - `automatic`: the top-level control alone, at 1h.
|
|
415
|
+
* - `hybrid`: the four block breakpoints extended to 1h, no top-level control.
|
|
416
|
+
* That is the placement OpenCode's hybrid anchors reconstruct by hand and
|
|
417
|
+
* this converter emits natively.
|
|
418
|
+
* - `explicit`: the same four breakpoints, TTL only.
|
|
419
|
+
*/
|
|
326
420
|
function applyCacheMode(body, enabled, mode) {
|
|
327
421
|
if (!enabled)
|
|
328
422
|
return;
|
|
329
423
|
if (mode === 'automatic') {
|
|
330
|
-
body
|
|
424
|
+
walkCacheControlHolders(body, (holder) => {
|
|
425
|
+
delete holder.cache_control;
|
|
426
|
+
});
|
|
427
|
+
body.cache_control = { type: 'ephemeral', ttl: '1h' };
|
|
331
428
|
return;
|
|
332
429
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
for (const item of value)
|
|
338
|
-
addTtl(item);
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
const record = value;
|
|
342
|
-
const cacheControl = record.cache_control;
|
|
343
|
-
if (cacheControl && typeof cacheControl === 'object') {
|
|
344
|
-
;
|
|
345
|
-
cacheControl.ttl = '1h';
|
|
346
|
-
}
|
|
347
|
-
for (const child of Object.values(record))
|
|
348
|
-
addTtl(child);
|
|
349
|
-
};
|
|
350
|
-
if (mode === 'hybrid')
|
|
351
|
-
body.cache_control = { type: 'ephemeral' };
|
|
352
|
-
addTtl(body);
|
|
430
|
+
walkCacheControlHolders(body, (holder) => {
|
|
431
|
+
;
|
|
432
|
+
holder.cache_control.ttl = '1h';
|
|
433
|
+
});
|
|
353
434
|
}
|
|
354
|
-
export async function buildAnthropicRequest(modelId, context, options, cache, fastModeEnabled = false, identity) {
|
|
435
|
+
export async function buildAnthropicRequest(modelId, context, options, cache, fastModeEnabled = false, identity, controls = {}) {
|
|
436
|
+
// Pi 0.86 passes a normalized transcript to providers. Resolve instructions,
|
|
437
|
+
// named sections and tool changes through its own replay helpers; raw host
|
|
438
|
+
// prompts (including ordered OMP blocks) still enter through normalization.
|
|
439
|
+
const transcript = collapseSystemMessages(normalizeContext({
|
|
440
|
+
...context,
|
|
441
|
+
systemPrompt: systemPromptText(context.systemPrompt),
|
|
442
|
+
}));
|
|
443
|
+
context = {
|
|
444
|
+
messages: transcript.messages,
|
|
445
|
+
systemPrompt: getCurrentSystemPrompt(transcript.messages),
|
|
446
|
+
tools: getCurrentTools(transcript.messages),
|
|
447
|
+
};
|
|
355
448
|
const messages = convertMessages(context.messages, modelId);
|
|
356
449
|
// Strip trailing assistant messages — Anthropic rejects prefill on some models
|
|
357
450
|
while (messages.length &&
|
|
@@ -361,11 +454,14 @@ export async function buildAnthropicRequest(modelId, context, options, cache, fa
|
|
|
361
454
|
const system = [
|
|
362
455
|
{
|
|
363
456
|
type: 'text',
|
|
364
|
-
text: buildBillingHeaderValue(messages, undefined, CLAUDE_CODE_ENTRYPOINT
|
|
457
|
+
text: buildBillingHeaderValue(messages, undefined, CLAUDE_CODE_ENTRYPOINT, options?.sessionId
|
|
458
|
+
? firstUserTextTracker.resolve(options.sessionId, messages)
|
|
459
|
+
: undefined),
|
|
365
460
|
},
|
|
366
461
|
{ type: 'text', text: CLAUDE_CODE_IDENTITY },
|
|
367
462
|
];
|
|
368
|
-
|
|
463
|
+
const systemPrompt = systemPromptText(context.systemPrompt);
|
|
464
|
+
if (systemPrompt.trim()) {
|
|
369
465
|
// Pi's prompt cannot sit whole in the top-level system[] array: two lines of
|
|
370
466
|
// its documentation paragraph (the docs/*.md enumeration and the "follow .md
|
|
371
467
|
// cross-references" instruction) are each independently sufficient to make
|
|
@@ -390,7 +486,7 @@ export async function buildAnthropicRequest(modelId, context, options, cache, fa
|
|
|
390
486
|
// cache_control is set explicitly because addEphemeralCacheControl's
|
|
391
487
|
// message-level breakpoint only fires for array content on the *last* user
|
|
392
488
|
// message, which is not this one after the first turn.
|
|
393
|
-
const prompt = splitPiSystemPrompt(
|
|
489
|
+
const prompt = splitPiSystemPrompt(systemPrompt);
|
|
394
490
|
if (prompt.systemText) {
|
|
395
491
|
system.push({ type: 'text', text: prompt.systemText });
|
|
396
492
|
}
|
|
@@ -412,7 +508,8 @@ export async function buildAnthropicRequest(modelId, context, options, cache, fa
|
|
|
412
508
|
const isFableOrMythos5 = isClaudeFableOrMythos5Model(modelId);
|
|
413
509
|
const isSonnet5 = isClaudeSonnet5Model(modelId);
|
|
414
510
|
const isOpus5 = isClaudeOpus5Model(modelId);
|
|
415
|
-
|
|
511
|
+
const isOpus55 = isClaudeOpus55Model(modelId);
|
|
512
|
+
// Sonnet 5, Opus 5, and Opus 5.5 share Fable/Mythos's adaptive-summarized contract: make
|
|
416
513
|
// adaptive thinking visible (display defaults to "omitted") and map reasoning
|
|
417
514
|
// to output_config effort. Pi's typed options cannot express
|
|
418
515
|
// thinking-disabled, so there is no disable case here (see transform.ts for
|
|
@@ -425,11 +522,14 @@ export async function buildAnthropicRequest(modelId, context, options, cache, fa
|
|
|
425
522
|
else if (isSonnet5) {
|
|
426
523
|
body.thinking = { ...CLAUDE_SONNET_5_ADAPTIVE_THINKING };
|
|
427
524
|
}
|
|
525
|
+
else if (isOpus55) {
|
|
526
|
+
body.thinking = { ...CLAUDE_OPUS_5_5_ADAPTIVE_THINKING };
|
|
527
|
+
}
|
|
428
528
|
else if (isOpus5) {
|
|
429
529
|
body.thinking = { ...CLAUDE_OPUS_5_ADAPTIVE_THINKING };
|
|
430
530
|
}
|
|
431
531
|
if (options?.reasoning) {
|
|
432
|
-
if (isFableOrMythos5 || isSonnet5 || isOpus5) {
|
|
532
|
+
if (isFableOrMythos5 || isSonnet5 || isOpus5 || isOpus55) {
|
|
433
533
|
body.output_config = { effort: options.reasoning };
|
|
434
534
|
}
|
|
435
535
|
else {
|
|
@@ -448,10 +548,13 @@ export async function buildAnthropicRequest(modelId, context, options, cache, fa
|
|
|
448
548
|
};
|
|
449
549
|
}
|
|
450
550
|
}
|
|
551
|
+
applyMidConversationOutputConfig(body, controls.effortTransitions ?? []);
|
|
451
552
|
addEphemeralCacheControl(body);
|
|
452
553
|
applyCacheMode(body, cache.enabled, cache.mode);
|
|
453
|
-
if (identity)
|
|
554
|
+
if (identity) {
|
|
555
|
+
applyThinkingBindingControls(body, controls.thinkingPrefixMismatchBehavior ?? 'account-default');
|
|
454
556
|
applyClaudeCodeMetadata(body, identity);
|
|
557
|
+
}
|
|
455
558
|
const unsigned = JSON.stringify(orderClaudeCodeBody(body));
|
|
456
559
|
const bodyText = await signRequestBody(unsigned);
|
|
457
560
|
return { body, bodyText };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type AccountCommandResult, type ClaustrumEnrollmentStatus, type ClaustrumMode, type ClaustrumScopedClient } from '@cortexkit/anthropic-auth-core';
|
|
2
|
+
export declare function requirePiEnrollment(): Promise<{
|
|
3
|
+
state: 'approved';
|
|
4
|
+
proposedName: string;
|
|
5
|
+
approvedName?: string;
|
|
6
|
+
tokenGeneration: number;
|
|
7
|
+
}>;
|
|
8
|
+
export interface PiCustodyCommands {
|
|
9
|
+
transition(mode: ClaustrumMode): Promise<AccountCommandResult>;
|
|
10
|
+
status(): Promise<ClaustrumEnrollmentStatus>;
|
|
11
|
+
reset(): Promise<AccountCommandResult>;
|
|
12
|
+
}
|
|
13
|
+
export declare function createPiCustodyCommands(options: {
|
|
14
|
+
reconfigure: () => Promise<void>;
|
|
15
|
+
storagePath?: string;
|
|
16
|
+
connect?: () => Promise<ClaustrumScopedClient>;
|
|
17
|
+
}): PiCustodyCommands;
|
package/dist/custody.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { CLAUSTRUM_PI_ENROLLMENT_NAME, ClaustrumScopedCustody, connectClaustrumScopedClient, getHostClaustrumEnrollmentPaths, readClaustrumEnrollmentStatus, resetClaustrumEnrollmentState, setClaustrumModePersistent, } from '@cortexkit/anthropic-auth-core';
|
|
2
|
+
import { getPiAccountStoragePath, getPiClaustrumConnectionOptions, hasPiLocalAnthropicOAuth, } from './paths.js';
|
|
3
|
+
export async function requirePiEnrollment() {
|
|
4
|
+
const status = await readClaustrumEnrollmentStatus(getHostClaustrumEnrollmentPaths('pi'), CLAUSTRUM_PI_ENROLLMENT_NAME);
|
|
5
|
+
if (status.state !== 'approved' ||
|
|
6
|
+
status.proposedName !== CLAUSTRUM_PI_ENROLLMENT_NAME) {
|
|
7
|
+
throw new Error('Pi requires its own approved Claustrum enrollment; run setup');
|
|
8
|
+
}
|
|
9
|
+
return status;
|
|
10
|
+
}
|
|
11
|
+
export function createPiCustodyCommands(options) {
|
|
12
|
+
const storagePath = options.storagePath ?? getPiAccountStoragePath();
|
|
13
|
+
const paths = () => getHostClaustrumEnrollmentPaths('pi');
|
|
14
|
+
return {
|
|
15
|
+
status: () => readClaustrumEnrollmentStatus(paths(), CLAUSTRUM_PI_ENROLLMENT_NAME),
|
|
16
|
+
async reset() {
|
|
17
|
+
const result = await resetClaustrumEnrollmentState(paths(), CLAUSTRUM_PI_ENROLLMENT_NAME);
|
|
18
|
+
const messages = {
|
|
19
|
+
reset: 'Terminal enrollment state cleared. Run setup to enroll Pi again.',
|
|
20
|
+
idle: 'Pi has no enrollment state to reset.',
|
|
21
|
+
'refused-pending': 'Refused: Pi enrollment is still pending.',
|
|
22
|
+
'refused-approved': 'Refused: Pi already has an approved enrollment token.',
|
|
23
|
+
busy: 'Pi enrollment is busy in another process.',
|
|
24
|
+
};
|
|
25
|
+
return { text: messages[result] };
|
|
26
|
+
},
|
|
27
|
+
async transition(mode) {
|
|
28
|
+
if (mode === 'claustrum') {
|
|
29
|
+
if (await hasPiLocalAnthropicOAuth()) {
|
|
30
|
+
return {
|
|
31
|
+
text: 'Refused: Pi still has a local Anthropic OAuth credential. Run `bunx @cortexkit/opencode-anthropic-auth setup` to approve its removal and enable Claustrum.',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// A slash command never invokes the administrative CLI or approves itself.
|
|
35
|
+
// Existing enrollment and grants must be verified before committing mode.
|
|
36
|
+
await requirePiEnrollment();
|
|
37
|
+
const client = await (options.connect?.() ??
|
|
38
|
+
connectClaustrumScopedClient(getPiClaustrumConnectionOptions(storagePath)));
|
|
39
|
+
const custody = new ClaustrumScopedCustody({
|
|
40
|
+
client,
|
|
41
|
+
tokenPath: paths().tokenPath,
|
|
42
|
+
});
|
|
43
|
+
try {
|
|
44
|
+
const inventory = await custody.discover();
|
|
45
|
+
const active = inventory.accounts.filter((account) => account.state === 'active');
|
|
46
|
+
if (!active.length)
|
|
47
|
+
return {
|
|
48
|
+
text: 'Refused: Claustrum has no accessible active Anthropic accounts.',
|
|
49
|
+
};
|
|
50
|
+
for (const account of active)
|
|
51
|
+
await custody.authorize(account);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
custody.close();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
await setClaustrumModePersistent(mode, storagePath);
|
|
58
|
+
await options.reconfigure();
|
|
59
|
+
return {
|
|
60
|
+
text: mode === 'claustrum'
|
|
61
|
+
? 'Pi now uses Claustrum for Anthropic authentication.'
|
|
62
|
+
: 'Pi now uses local Anthropic authentication. Use /login anthropic if needed.',
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type MidConversationEffortTransition } from '@cortexkit/anthropic-auth-core';
|
|
2
|
+
type SessionEntryLike = {
|
|
3
|
+
id?: unknown;
|
|
4
|
+
type?: unknown;
|
|
5
|
+
thinkingLevel?: unknown;
|
|
6
|
+
firstKeptEntryId?: unknown;
|
|
7
|
+
message?: {
|
|
8
|
+
role?: unknown;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Reproduce the host's context-entry view — the entries the next request will
|
|
13
|
+
* actually carry — from the branch path, ordered root to leaf.
|
|
14
|
+
*
|
|
15
|
+
* `SessionManager.buildContextEntries()` computes this on Pi hosts but does not
|
|
16
|
+
* exist on Oh My Pi 18.x, where calling it threw on every turn (issue #200).
|
|
17
|
+
* `getBranch()` is present on both hosts and is the same path that method walks,
|
|
18
|
+
* so the compaction trim is applied here instead of asking the host for it.
|
|
19
|
+
*
|
|
20
|
+
* A branch without compaction is already the context. After a compaction the
|
|
21
|
+
* context is the compaction entry, then the tail retained from
|
|
22
|
+
* `firstKeptEntryId`, then everything appended after the compaction.
|
|
23
|
+
*/
|
|
24
|
+
export declare function deriveContextEntries<Entry extends SessionEntryLike>(branch: readonly Entry[]): Entry[];
|
|
25
|
+
/** Build Pi's active, compaction-aware Fable 5.1 effort timeline. */
|
|
26
|
+
export declare function collectPiEffortHistory(contextEntries: readonly SessionEntryLike[], fullBranch: readonly SessionEntryLike[]): MidConversationEffortTransition[];
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { normalizeAdaptiveEffort, } from '@cortexkit/anthropic-auth-core';
|
|
2
|
+
/**
|
|
3
|
+
* Reproduce the host's context-entry view — the entries the next request will
|
|
4
|
+
* actually carry — from the branch path, ordered root to leaf.
|
|
5
|
+
*
|
|
6
|
+
* `SessionManager.buildContextEntries()` computes this on Pi hosts but does not
|
|
7
|
+
* exist on Oh My Pi 18.x, where calling it threw on every turn (issue #200).
|
|
8
|
+
* `getBranch()` is present on both hosts and is the same path that method walks,
|
|
9
|
+
* so the compaction trim is applied here instead of asking the host for it.
|
|
10
|
+
*
|
|
11
|
+
* A branch without compaction is already the context. After a compaction the
|
|
12
|
+
* context is the compaction entry, then the tail retained from
|
|
13
|
+
* `firstKeptEntryId`, then everything appended after the compaction.
|
|
14
|
+
*/
|
|
15
|
+
export function deriveContextEntries(branch) {
|
|
16
|
+
let compactionIndex = -1;
|
|
17
|
+
for (let index = 0; index < branch.length; index++) {
|
|
18
|
+
if (branch[index]?.type === 'compaction')
|
|
19
|
+
compactionIndex = index;
|
|
20
|
+
}
|
|
21
|
+
const compaction = branch[compactionIndex];
|
|
22
|
+
if (!compaction)
|
|
23
|
+
return branch.slice();
|
|
24
|
+
const contextEntries = [compaction];
|
|
25
|
+
let retaining = false;
|
|
26
|
+
for (let index = 0; index < compactionIndex; index++) {
|
|
27
|
+
const entry = branch[index];
|
|
28
|
+
if (!entry)
|
|
29
|
+
continue;
|
|
30
|
+
if (entry.id === compaction.firstKeptEntryId)
|
|
31
|
+
retaining = true;
|
|
32
|
+
if (retaining)
|
|
33
|
+
contextEntries.push(entry);
|
|
34
|
+
}
|
|
35
|
+
for (let index = compactionIndex + 1; index < branch.length; index++) {
|
|
36
|
+
const entry = branch[index];
|
|
37
|
+
if (entry)
|
|
38
|
+
contextEntries.push(entry);
|
|
39
|
+
}
|
|
40
|
+
return contextEntries;
|
|
41
|
+
}
|
|
42
|
+
function entryRole(entry) {
|
|
43
|
+
if (entry.type === 'compaction' || entry.type === 'branch_summary') {
|
|
44
|
+
return 'user';
|
|
45
|
+
}
|
|
46
|
+
if (entry.type === 'custom_message')
|
|
47
|
+
return 'user';
|
|
48
|
+
return entry.type === 'message' ? entry.message?.role : undefined;
|
|
49
|
+
}
|
|
50
|
+
function appendTransition(transitions, afterAssistantMessages, effort) {
|
|
51
|
+
const previous = transitions.at(-1);
|
|
52
|
+
if (previous?.effort === effort)
|
|
53
|
+
return;
|
|
54
|
+
if (previous?.afterAssistantMessages === afterAssistantMessages) {
|
|
55
|
+
previous.effort = effort;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
transitions.push({ afterAssistantMessages, effort });
|
|
59
|
+
}
|
|
60
|
+
/** Build Pi's active, compaction-aware Fable 5.1 effort timeline. */
|
|
61
|
+
export function collectPiEffortHistory(contextEntries, fullBranch) {
|
|
62
|
+
let activeEffort = 'high';
|
|
63
|
+
let assistantMessages = 0;
|
|
64
|
+
const transitions = [];
|
|
65
|
+
const first = contextEntries[0];
|
|
66
|
+
const compactionIndex = first?.type === 'compaction'
|
|
67
|
+
? fullBranch.findIndex((entry) => entry.id === first.id)
|
|
68
|
+
: -1;
|
|
69
|
+
if (compactionIndex >= 0) {
|
|
70
|
+
for (let index = 0; index <= compactionIndex; index++) {
|
|
71
|
+
const entry = fullBranch[index];
|
|
72
|
+
if (entry?.type !== 'thinking_level_change')
|
|
73
|
+
continue;
|
|
74
|
+
activeEffort = normalizeAdaptiveEffort(entry.thinkingLevel) ?? 'high';
|
|
75
|
+
}
|
|
76
|
+
appendTransition(transitions, 0, activeEffort);
|
|
77
|
+
}
|
|
78
|
+
const branchIndexById = new Map(fullBranch.map((entry, index) => [entry.id, index]));
|
|
79
|
+
for (const entry of contextEntries) {
|
|
80
|
+
if (entry === first && entry.type === 'compaction')
|
|
81
|
+
continue;
|
|
82
|
+
const branchIndex = branchIndexById.get(entry.id);
|
|
83
|
+
const retainedBeforeCompaction = compactionIndex >= 0 &&
|
|
84
|
+
typeof branchIndex === 'number' &&
|
|
85
|
+
branchIndex < compactionIndex;
|
|
86
|
+
if (entry.type === 'thinking_level_change') {
|
|
87
|
+
if (!retainedBeforeCompaction) {
|
|
88
|
+
activeEffort = normalizeAdaptiveEffort(entry.thinkingLevel) ?? 'high';
|
|
89
|
+
}
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const role = entryRole(entry);
|
|
93
|
+
if (role === 'assistant') {
|
|
94
|
+
assistantMessages++;
|
|
95
|
+
}
|
|
96
|
+
else if (role === 'user' ||
|
|
97
|
+
role === 'toolResult' ||
|
|
98
|
+
role === 'tool_result') {
|
|
99
|
+
appendTransition(transitions, assistantMessages, activeEffort);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return transitions;
|
|
103
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
+
import { type ClaustrumScopedClient } from '@cortexkit/anthropic-auth-core';
|
|
1
2
|
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
2
|
-
export default function cortexKitPiAnthropicAuth(pi: ExtensionAPI
|
|
3
|
+
export default function cortexKitPiAnthropicAuth(pi: ExtensionAPI, options?: {
|
|
4
|
+
connectScoped?: () => Promise<ClaustrumScopedClient>;
|
|
5
|
+
pollIntervalMs?: number;
|
|
6
|
+
}): Promise<void>;
|