pi-claude-agent-sdk 0.8.3 → 0.8.4
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 +2 -2
- package/package.json +1 -1
- package/src/convert.ts +8 -2
- package/src/index.ts +7 -4
- package/src/models.ts +56 -8
package/README.md
CHANGED
|
@@ -21,13 +21,13 @@ pi install npm:pi-claude-agent-sdk
|
|
|
21
21
|
|
|
22
22
|
## Provider
|
|
23
23
|
|
|
24
|
-
Use `/model` to select `claude-bridge/claude-fable-5`, `claude-bridge/claude-opus-5`, `claude-bridge/claude-opus-4-8`, `claude-bridge/claude-opus-4-7`, `claude-bridge/claude-opus-4-6`, `claude-bridge/claude-sonnet-5`, `claude-bridge/claude-sonnet-4-6`, or `claude-bridge/claude-haiku-4-5`.
|
|
24
|
+
Use `/model` to select `claude-bridge/claude-fable-5-1`, `claude-bridge/claude-fable-5`, `claude-bridge/claude-opus-5`, `claude-bridge/claude-opus-4-8`, `claude-bridge/claude-opus-4-7`, `claude-bridge/claude-opus-4-6`, `claude-bridge/claude-sonnet-5`, `claude-bridge/claude-sonnet-4-6`, or `claude-bridge/claude-haiku-4-5`. The `fable` shortcut resolves to Fable 5.1. Fable 5.1 needs Claude Code **2.1.251 or newer**; if the SDK's bundled CLI is older, set `provider.pathToClaudeCodeExecutable` to a current `claude` binary.
|
|
25
25
|
|
|
26
26
|
Behind the scenes, pi's tools are bridged to Claude Code but it should all work like normal in pi. Bash commands get a 120-second default timeout (matching Claude Code's default) since pi's bash has no timeout by default. Skills in pi are copied over to Claude Code's system prompt so should work as they would with any other pi provider. Steering works mid-turn: a message sent while Claude is running a tool reaches it at that tool boundary, not after the whole turn finishes.
|
|
27
27
|
|
|
28
28
|
**Authentication:** the bridge requires an Anthropic OAuth credential (or API key) configured in Pi and uses Pi's normal token refresh. Claude Code login and inherited Claude/Anthropic authentication settings are deliberately ignored, so configure Anthropic authentication in Pi before using the provider.
|
|
29
29
|
|
|
30
|
-
**1M Context:** Opus 5, Opus 4.8, and Opus 4.7 get 1M context by default. Opus 4.6 only gets 1M if you're on a Max plan or pay for Extra Usage. Sonnet 4.6 only gets 1M if you pay for Extra Usage. You will need to set `provider.plan` and/or `provider.longContextExtraUsage` for 1M context in Opus 4.6/Sonnet 4.6 as described in [Configuration](#configuration).
|
|
30
|
+
**1M Context:** Fable 5.1, Fable 5, Opus 5, Opus 4.8, and Opus 4.7 get 1M context by default. Opus 4.6 only gets 1M if you're on a Max plan or pay for Extra Usage. Sonnet 4.6 only gets 1M if you pay for Extra Usage. You will need to set `provider.plan` and/or `provider.longContextExtraUsage` for 1M context in Opus 4.6/Sonnet 4.6 as described in [Configuration](#configuration).
|
|
31
31
|
|
|
32
32
|
## Configuration
|
|
33
33
|
|
package/package.json
CHANGED
package/src/convert.ts
CHANGED
|
@@ -116,10 +116,16 @@ export type DroppedContent = {
|
|
|
116
116
|
other: Map<string, number>;
|
|
117
117
|
};
|
|
118
118
|
|
|
119
|
-
/** Convert pi message array to Anthropic API format.
|
|
119
|
+
/** Convert pi message array to Anthropic API format.
|
|
120
|
+
*
|
|
121
|
+
* `dropThinking` strips every thinking block, including ones we minted. Fable 5.1
|
|
122
|
+
* binds thinking to the conversation prefix, so a rebuild that keeps the blocks
|
|
123
|
+
* and rewrites system/tools is a 400. Removing a leading run of thinking is the
|
|
124
|
+
* documented-safe alternative. */
|
|
120
125
|
export function convertPiMessages(
|
|
121
126
|
messages: PiMessage[],
|
|
122
127
|
customToolNameToSdk?: Map<string, string>,
|
|
128
|
+
dropThinking = false,
|
|
123
129
|
): { anthropicMessages: SessionMessage[]; sanitizedIds: Map<string, string>; dropped: DroppedContent } {
|
|
124
130
|
const anthropicMessages = [];
|
|
125
131
|
const sanitizedIds = new Map();
|
|
@@ -160,7 +166,7 @@ export function convertPiMessages(
|
|
|
160
166
|
// by any other provider — including pi's own Anthropic provider — is
|
|
161
167
|
// not ours to hand back, and Anthropic rejects ones it can't verify.
|
|
162
168
|
const sig = block.thinkingSignature;
|
|
163
|
-
if (msg.provider === PROVIDER_ID && sig) {
|
|
169
|
+
if (!dropThinking && msg.provider === PROVIDER_ID && sig) {
|
|
164
170
|
blocks.push({ type: "thinking", thinking: block.thinking ?? "", signature: sig });
|
|
165
171
|
} else {
|
|
166
172
|
dropped.thinking++;
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { appendFileSync, mkdirSync, realpathSync, statSync } from "fs";
|
|
|
9
9
|
import { homedir } from "os";
|
|
10
10
|
import { dirname, join } from "path";
|
|
11
11
|
import { PROVIDER_ID, messageContentToText, convertPiMessages } from "./convert.js";
|
|
12
|
-
import { applyLongContext, buildModels, claudeCodeModelId, type LongContextSettings } from "./models.js";
|
|
12
|
+
import { adaptiveThinkingAlwaysOn, applyLongContext, buildModels, claudeCodeModelId, thinkingBoundToPrefix, type LongContextSettings } from "./models.js";
|
|
13
13
|
import { MCP_SERVER_NAME, MCP_TOOL_PREFIX } from "./skills.js";
|
|
14
14
|
import { verifyWrittenSession as _verifyWrittenSession } from "./session-verify.js";
|
|
15
15
|
import { extractAllToolResults as _extractAllToolResults, type McpResult } from "./extract-tool-results.js";
|
|
@@ -215,8 +215,9 @@ function convertAndImportMessages(
|
|
|
215
215
|
messages: Context["messages"],
|
|
216
216
|
customToolNameToSdk?: Map<string, string>,
|
|
217
217
|
carried?: readonly CarriedAttachment[],
|
|
218
|
+
dropThinking = false,
|
|
218
219
|
): void {
|
|
219
|
-
const { anthropicMessages, sanitizedIds, dropped } = convertPiMessages(messages, customToolNameToSdk);
|
|
220
|
+
const { anthropicMessages, sanitizedIds, dropped } = convertPiMessages(messages, customToolNameToSdk, dropThinking);
|
|
220
221
|
|
|
221
222
|
debug(`convertAndImportMessages: ${messages.length} pi msgs → ${anthropicMessages.length} anthropic msgs`);
|
|
222
223
|
debug(`convertAndImportMessages: imported roles:`, anthropicMessages.map((m, i) => {
|
|
@@ -676,7 +677,7 @@ function syncSharedSession(
|
|
|
676
677
|
...(preserveId ? { sessionId: previousSessionId } : {}),
|
|
677
678
|
...(modelId ? { model: modelId } : {}),
|
|
678
679
|
});
|
|
679
|
-
convertAndImportMessages(session, priorMessages, customToolNameToSdk, carried);
|
|
680
|
+
convertAndImportMessages(session, priorMessages, customToolNameToSdk, carried, thinkingBoundToPrefix(modelId ?? ""));
|
|
680
681
|
session.save();
|
|
681
682
|
// records, not messages: `messages` filters out the attachment records that
|
|
682
683
|
// carrying an `@file` expansion across a rebuild writes into the same file.
|
|
@@ -1574,7 +1575,9 @@ function streamClaudeAgentSdk(model: Model<any>, context: Context, options?: Sim
|
|
|
1574
1575
|
if (strictMcpConfigEnabled) extraArgs["strict-mcp-config"] = null;
|
|
1575
1576
|
// Opus 4.7 defaults thinking.display to "omitted" (empty thinking text in stream).
|
|
1576
1577
|
// Force summarized so thinking_delta events arrive. See anthropics/claude-agent-sdk-python#830.
|
|
1577
|
-
|
|
1578
|
+
// Fable 5 / 5.1 always think and also default to omitted, even when we pass no effort
|
|
1579
|
+
// (CC then uses the model default, high).
|
|
1580
|
+
if (effort || adaptiveThinkingAlwaysOn(model.id)) extraArgs["thinking-display"] = "summarized";
|
|
1578
1581
|
|
|
1579
1582
|
// Suppress claude.ai cloud MCP servers (Figma/Canva/etc. auto-discovered via OAuth
|
|
1580
1583
|
// when the user is logged into Anthropic). These are a separate code path from
|
package/src/models.ts
CHANGED
|
@@ -2,14 +2,42 @@
|
|
|
2
2
|
// `resolveModel` returns the first partial match, so `opus` resolves to the first-listed opus entry.
|
|
3
3
|
// Extracted from index.ts so tests can import without activating the extension.
|
|
4
4
|
|
|
5
|
-
export const MODEL_IDS_IN_ORDER = ["claude-fable-5", "claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-5", "claude-sonnet-4-6", "claude-haiku-4-5"];
|
|
5
|
+
export const MODEL_IDS_IN_ORDER = ["claude-fable-5-1", "claude-fable-5", "claude-opus-5", "claude-opus-4-8", "claude-opus-4-7", "claude-opus-4-6", "claude-sonnet-5", "claude-sonnet-4-6", "claude-haiku-4-5"];
|
|
6
|
+
|
|
7
|
+
const TWO_HUNDRED_K_CONTEXT = 200_000;
|
|
8
|
+
const ONE_M_CONTEXT = 1_000_000;
|
|
9
|
+
|
|
10
|
+
/** Catalog stubs for IDs Claude Code already serves that the installed pi-ai has
|
|
11
|
+
* not listed yet. Prefer pi-ai when it has the entry. Fable 5.1 shipped
|
|
12
|
+
* 2026-09-01; pi-ai 0.84.4 (2026-08-28) does not include it. */
|
|
13
|
+
export const FALLBACK_MODELS: Record<string, {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
reasoning: boolean;
|
|
17
|
+
input: string[];
|
|
18
|
+
contextWindow: number;
|
|
19
|
+
maxTokens: number;
|
|
20
|
+
thinkingLevelMap?: Record<string, string | null>;
|
|
21
|
+
}> = {
|
|
22
|
+
"claude-fable-5-1": {
|
|
23
|
+
id: "claude-fable-5-1",
|
|
24
|
+
name: "Claude Fable 5.1",
|
|
25
|
+
reasoning: true,
|
|
26
|
+
input: ["text", "image"],
|
|
27
|
+
contextWindow: ONE_M_CONTEXT,
|
|
28
|
+
maxTokens: 128_000,
|
|
29
|
+
// Same shape as pi-ai's claude-fable-5: adaptive thinking, xhigh visible.
|
|
30
|
+
thinkingLevelMap: { off: null, xhigh: "xhigh", max: "max" },
|
|
31
|
+
},
|
|
32
|
+
};
|
|
6
33
|
|
|
7
34
|
// Project pi-ai's model entries down to the fields pi's registerProvider expects,
|
|
8
|
-
// and keep MODEL_IDS_IN_ORDER ordering. IDs missing from pi-ai are silently dropped
|
|
9
|
-
// Context-dependent display labels are applied
|
|
35
|
+
// and keep MODEL_IDS_IN_ORDER ordering. IDs missing from pi-ai are silently dropped
|
|
36
|
+
// unless FALLBACK_MODELS has a stub. Context-dependent display labels are applied
|
|
37
|
+
// after plan/long-context config is known.
|
|
10
38
|
export function buildModels<T extends { id: string; [key: string]: any }>(piAiModels: T[]) {
|
|
11
39
|
return MODEL_IDS_IN_ORDER
|
|
12
|
-
.map((id) => piAiModels.find((m) => m.id === id))
|
|
40
|
+
.map((id) => piAiModels.find((m) => m.id === id) ?? FALLBACK_MODELS[id])
|
|
13
41
|
.filter((m) => m != null)
|
|
14
42
|
// Forward thinkingLevelMap so pi-ai's per-model overrides (e.g. opus-4-8
|
|
15
43
|
// mapping xhigh→xhigh and max→max) are visible to the effort lookup.
|
|
@@ -32,9 +60,6 @@ export type ClaudeCodeRuntimeModel = {
|
|
|
32
60
|
contextWindow: number;
|
|
33
61
|
};
|
|
34
62
|
|
|
35
|
-
const TWO_HUNDRED_K_CONTEXT = 200_000;
|
|
36
|
-
const ONE_M_CONTEXT = 1_000_000;
|
|
37
|
-
|
|
38
63
|
// Measured Claude Agent SDK subscription/OAuth behavior. Do not infer this from
|
|
39
64
|
// pi-ai's advertised contextWindow: bare Opus 4.7 serves 1M, bare Opus 4.8 does
|
|
40
65
|
// not, and [1m] entitlement differs by model. See diag/CONTEXT-SIZE.md.
|
|
@@ -53,6 +78,11 @@ export function resolveClaudeCodeRuntimeModel(modelId: string, settings: LongCon
|
|
|
53
78
|
contextWindow: useOneM ? ONE_M_CONTEXT : TWO_HUNDRED_K_CONTEXT,
|
|
54
79
|
};
|
|
55
80
|
}
|
|
81
|
+
case "claude-fable-5-1":
|
|
82
|
+
// 1M is the default and the maximum, billed at standard rates across the
|
|
83
|
+
// whole window (no Extra Usage). CC still takes the [1m] suffix to request
|
|
84
|
+
// that window, same as Fable 5 / Opus 5.
|
|
85
|
+
return { cliModelId: "claude-fable-5-1[1m]", contextWindow: ONE_M_CONTEXT };
|
|
56
86
|
case "claude-fable-5":
|
|
57
87
|
return { cliModelId: "claude-fable-5[1m]", contextWindow: ONE_M_CONTEXT };
|
|
58
88
|
case "claude-sonnet-5":
|
|
@@ -74,9 +104,27 @@ export function claudeCodeModelId(model: { id: string }, settings: LongContextSe
|
|
|
74
104
|
return resolveClaudeCodeRuntimeModel(model.id, settings).cliModelId;
|
|
75
105
|
}
|
|
76
106
|
|
|
107
|
+
/** Adaptive thinking is always on. `thinking: enabled` with budget_tokens and
|
|
108
|
+
* `disabled` both 400; omit thinking or send adaptive. `thinking.display`
|
|
109
|
+
* defaults to omitted, so the stream has no thinking text unless we ask. */
|
|
110
|
+
export function adaptiveThinkingAlwaysOn(modelId: string): boolean {
|
|
111
|
+
return modelId === "claude-fable-5-1" || modelId.startsWith("claude-fable-5-1[")
|
|
112
|
+
|| modelId === "claude-fable-5" || modelId.startsWith("claude-fable-5[");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Fable 5.1 binds each thinking block to the conversation prefix. Replaying a
|
|
116
|
+
* block after a rebuild (new system prompt or tools) 400s with "The block is
|
|
117
|
+
* bound to a different conversation". Resume is fine; rebuilds must drop
|
|
118
|
+
* thinking. https://platform.claude.com/docs/en/models/fable-5-1/whats-new-fable-5-1#editing-earlier-turns-invalidates-thinking-blocks */
|
|
119
|
+
export function thinkingBoundToPrefix(modelId: string): boolean {
|
|
120
|
+
return modelId === "claude-fable-5-1" || modelId.startsWith("claude-fable-5-1[");
|
|
121
|
+
}
|
|
122
|
+
|
|
77
123
|
export function resolveModel<T extends { id: string }>(models: T[], input: string): T | undefined {
|
|
78
124
|
const lower = input.toLowerCase();
|
|
79
|
-
|
|
125
|
+
// Exact match first: otherwise `claude-fable-5` would hit `claude-fable-5-1`
|
|
126
|
+
// via includes() when the newer id is listed first for the `fable` shortcut.
|
|
127
|
+
return models.find((m) => m.id === lower) ?? models.find((m) => m.id.includes(lower));
|
|
80
128
|
}
|
|
81
129
|
|
|
82
130
|
// Produce the model metadata registered with pi. The registered contextWindow must
|