pi-claude-agent-sdk 0.8.4 → 0.8.5
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 +3 -3
- package/src/claude-executable.ts +149 -0
- package/src/index.ts +18 -3
- package/src/models.ts +8 -0
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ pi install npm:pi-claude-agent-sdk
|
|
|
21
21
|
|
|
22
22
|
## Provider
|
|
23
23
|
|
|
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
|
|
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** (the SDK's bundled CLI is 2.1.257). If you point `provider.pathToClaudeCodeExecutable` at an older binary, or a later model outruns the bundle, the bridge uses a current `claude` on PATH when it finds one.
|
|
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
|
|
|
@@ -49,7 +49,7 @@ Config: `~/.pi/agent/claude-bridge.json` (global) or the project Pi config direc
|
|
|
49
49
|
- `longContextExtraUsage` — set to `true` to enable 1M models that cost money through Extra Usage. It enables Sonnet 4.6 with 1M on every plan and Opus 4.6 with 1M on Pro. Not needed for Opus 4.7 or 4.8.
|
|
50
50
|
- `strictMcpConfig` — block MCP servers from `~/.claude.json` / `.mcp.json` (default `true`). Cloud MCP (Gmail/Drive via claude.ai OAuth) is always blocked.
|
|
51
51
|
- `autoMemoryEnabled` — enable Claude Code's auto-memory system (default `false`)
|
|
52
|
-
- `pathToClaudeCodeExecutable` — path to the `claude` binary. Useful if your OS/filesystem has the SDK's bundled musl/glibc binaries in a place where they can't run. For example, with Nix you can set the binary to e.g. `"/home/you/.nix-profile/bin/claude"`.
|
|
52
|
+
- `pathToClaudeCodeExecutable` — path to the `claude` binary. Useful if your OS/filesystem has the SDK's bundled musl/glibc binaries in a place where they can't run, or to pin a specific CLI. For example, with Nix you can set the binary to e.g. `"/home/you/.nix-profile/bin/claude"`.
|
|
53
53
|
|
|
54
54
|
**Extension providers and models.json:** pi's `modelOverrides` in `~/.pi/agent/models.json` do not currently apply to extension-registered providers (like claude-bridge). Overriding `contextWindow` or other fields requires editing `src/models.ts` directly.
|
|
55
55
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-claude-agent-sdk",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Pi extension that uses Claude Code (via Agent SDK) as a model provider.",
|
|
6
6
|
"keywords": [
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"type": "module",
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@anthropic-ai/claude-agent-sdk": "^0.
|
|
43
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.257",
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
45
45
|
"cc-session-io": "^0.4.0",
|
|
46
46
|
"change-case": "^5.4.4"
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@earendil-works/pi-coding-agent": ">=0.82.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@anthropic-ai/sdk": "^0.
|
|
53
|
+
"@anthropic-ai/sdk": "^0.93.0",
|
|
54
54
|
"@earendil-works/pi-ai": "^0.83.0",
|
|
55
55
|
"@earendil-works/pi-coding-agent": "^0.83.0",
|
|
56
56
|
"@types/node": "^24.13.2",
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// Pick a Claude Code CLI that can actually serve the selected model.
|
|
2
|
+
// The Agent SDK bundles its own binary (`claudeCodeVersion` in its package.json).
|
|
3
|
+
// Some models (Fable 5.1) 400 against an older CLI; when the bundle is below
|
|
4
|
+
// the model's minimum we use a current `claude` on PATH, matching what the
|
|
5
|
+
// README already told people to set via pathToClaudeCodeExecutable.
|
|
6
|
+
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import { accessSync, constants, readFileSync } from "node:fs";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
import { delimiter, dirname, join } from "node:path";
|
|
11
|
+
import { minClaudeCodeVersionForModel } from "./models.js";
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
|
|
15
|
+
export type ClaudeVersion = { major: number; minor: number; patch: number };
|
|
16
|
+
|
|
17
|
+
export type ClaudeExecutableSource = "configured" | "bundled" | "path";
|
|
18
|
+
|
|
19
|
+
export type ClaudeExecutableResolution = {
|
|
20
|
+
/** Absolute path for the SDK, or undefined to use the bundled CLI. */
|
|
21
|
+
path?: string;
|
|
22
|
+
source: ClaudeExecutableSource;
|
|
23
|
+
/** Set when no available CLI meets the model's minimum version. */
|
|
24
|
+
error?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type ClaudeExecutableDeps = {
|
|
28
|
+
bundledVersion?: () => ClaudeVersion | undefined;
|
|
29
|
+
findOnPath?: () => string | undefined;
|
|
30
|
+
readVersion?: (executable: string) => ClaudeVersion | undefined;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const versionCache = new Map<string, ClaudeVersion | undefined>();
|
|
34
|
+
|
|
35
|
+
export function parseClaudeVersion(text: string): ClaudeVersion | undefined {
|
|
36
|
+
const match = text.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
37
|
+
if (!match) return undefined;
|
|
38
|
+
return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function formatClaudeVersion(version: ClaudeVersion): string {
|
|
42
|
+
return `${version.major}.${version.minor}.${version.patch}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function compareClaudeVersion(a: ClaudeVersion, b: ClaudeVersion): number {
|
|
46
|
+
return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function readBundledClaudeCodeVersion(): ClaudeVersion | undefined {
|
|
50
|
+
try {
|
|
51
|
+
// The SDK does not export ./package.json; read the file next to its entry.
|
|
52
|
+
const pkgPath = join(dirname(require.resolve("@anthropic-ai/claude-agent-sdk")), "package.json");
|
|
53
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { claudeCodeVersion?: string };
|
|
54
|
+
return parseClaudeVersion(pkg.claudeCodeVersion ?? "");
|
|
55
|
+
} catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function findExecutableOnPath(name: string, env: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
61
|
+
const pathEnv = env.PATH ?? env.Path;
|
|
62
|
+
if (!pathEnv) return undefined;
|
|
63
|
+
const exts = process.platform === "win32"
|
|
64
|
+
? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";").filter(Boolean)
|
|
65
|
+
: [""];
|
|
66
|
+
const names = process.platform === "win32" && !exts.some((ext) => name.toUpperCase().endsWith(ext.toUpperCase()))
|
|
67
|
+
? [name, ...exts.map((ext) => name + ext)]
|
|
68
|
+
: [name];
|
|
69
|
+
for (const dir of pathEnv.split(delimiter)) {
|
|
70
|
+
if (!dir) continue;
|
|
71
|
+
for (const candidateName of names) {
|
|
72
|
+
const candidate = join(dir, candidateName);
|
|
73
|
+
try {
|
|
74
|
+
accessSync(candidate, constants.X_OK);
|
|
75
|
+
return candidate;
|
|
76
|
+
} catch {}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function readClaudeCliVersion(executable: string): ClaudeVersion | undefined {
|
|
83
|
+
if (versionCache.has(executable)) return versionCache.get(executable);
|
|
84
|
+
try {
|
|
85
|
+
const result = spawnSync(executable, ["--version"], {
|
|
86
|
+
encoding: "utf8",
|
|
87
|
+
timeout: 8000,
|
|
88
|
+
env: process.env,
|
|
89
|
+
windowsHide: true,
|
|
90
|
+
});
|
|
91
|
+
const version = parseClaudeVersion(`${result.stdout ?? ""}\n${result.stderr ?? ""}`);
|
|
92
|
+
versionCache.set(executable, version);
|
|
93
|
+
return version;
|
|
94
|
+
} catch {
|
|
95
|
+
versionCache.set(executable, undefined);
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function tooOldError(modelId: string, min: ClaudeVersion, found: string): string {
|
|
101
|
+
return `${modelId} requires Claude Code ${formatClaudeVersion(min)} or newer (${found}). Install a current claude on PATH, or set provider.pathToClaudeCodeExecutable in ~/.pi/agent/claude-bridge.json.`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function resolveClaudeCodeExecutable(
|
|
105
|
+
modelId: string,
|
|
106
|
+
configured?: string,
|
|
107
|
+
deps: ClaudeExecutableDeps = {},
|
|
108
|
+
): ClaudeExecutableResolution {
|
|
109
|
+
const bundledVersion = deps.bundledVersion ?? readBundledClaudeCodeVersion;
|
|
110
|
+
const findOnPath = deps.findOnPath ?? (() => findExecutableOnPath("claude"));
|
|
111
|
+
const readVersion = deps.readVersion ?? readClaudeCliVersion;
|
|
112
|
+
const minText = minClaudeCodeVersionForModel(modelId);
|
|
113
|
+
const min = minText ? parseClaudeVersion(minText) : undefined;
|
|
114
|
+
|
|
115
|
+
if (configured) {
|
|
116
|
+
if (min) {
|
|
117
|
+
const version = readVersion(configured);
|
|
118
|
+
if (version && compareClaudeVersion(version, min) < 0) {
|
|
119
|
+
return {
|
|
120
|
+
source: "configured",
|
|
121
|
+
error: tooOldError(modelId, min, `provider.pathToClaudeCodeExecutable is ${formatClaudeVersion(version)} at ${configured}`),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { path: configured, source: "configured" };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!min) return { source: "bundled" };
|
|
129
|
+
|
|
130
|
+
const bundled = bundledVersion();
|
|
131
|
+
if (bundled && compareClaudeVersion(bundled, min) >= 0) {
|
|
132
|
+
return { source: "bundled" };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const pathClaude = findOnPath();
|
|
136
|
+
if (pathClaude) {
|
|
137
|
+
const version = readVersion(pathClaude);
|
|
138
|
+
if (version && compareClaudeVersion(version, min) >= 0) {
|
|
139
|
+
return { path: pathClaude, source: "path" };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (bundled) {
|
|
144
|
+
return { source: "bundled", error: tooOldError(modelId, min, `bundled CLI is ${formatClaudeVersion(bundled)}`) };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Bundle version unknown and PATH missing/unreadable: let the SDK try.
|
|
148
|
+
return { source: "bundled" };
|
|
149
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import { collectCarriedAttachments, placeCarriedAttachments, type CarriedAttachment } from "./attachments.js";
|
|
25
25
|
import { createToolServer } from "./mcp-server.js";
|
|
26
26
|
import { CC_CHILD_ENV, resolveClaudeChildEnv, type AnthropicAuthRegistry } from "./child-env.js";
|
|
27
|
+
import { resolveClaudeCodeExecutable } from "./claude-executable.js";
|
|
27
28
|
|
|
28
29
|
// Compat (#2): use factory if available (pi-ai ≥0.66), else fall back to constructor (gsd-pi etc.)
|
|
29
30
|
const _piAi = piAi as any;
|
|
@@ -87,7 +88,7 @@ function debug(...args: unknown[]) {
|
|
|
87
88
|
// Per-query CLI debug capture. When CLAUDE_BRIDGE_DEBUG=1, ask the Claude Code
|
|
88
89
|
// CLI subprocess to write its own debug log to a file we choose, and also
|
|
89
90
|
// forward its stderr into our debug stream. Drops straight into the real SDK's
|
|
90
|
-
// Options — see @anthropic-ai/claude-agent-sdk sdk.d.ts:
|
|
91
|
+
// Options — see @anthropic-ai/claude-agent-sdk sdk.d.ts:2091 (debug, debugFile,
|
|
91
92
|
// stderr). Without this, CC's internal view of the world is invisible to us
|
|
92
93
|
// and "No conversation found" / empty-error reports are unactionable.
|
|
93
94
|
let nextCliDebugSeq = 1;
|
|
@@ -430,7 +431,9 @@ async function runIsolatedSummary(
|
|
|
430
431
|
const promptText = extractIsolatedSummaryPrompt(context.messages);
|
|
431
432
|
const cwd = (options as { cwd?: string } | undefined)?.cwd ?? process.cwd();
|
|
432
433
|
const compactProviderSettings = loadConfig(cwd).provider;
|
|
433
|
-
const
|
|
434
|
+
const claudeExecutableResolution = resolveClaudeCodeExecutable(model.id, compactProviderSettings?.pathToClaudeCodeExecutable);
|
|
435
|
+
if (claudeExecutableResolution.error) throw new Error(claudeExecutableResolution.error);
|
|
436
|
+
const claudeExecutable = claudeExecutableResolution.path;
|
|
434
437
|
const cliModel = claudeCodeModelId(model, longContextSettings);
|
|
435
438
|
debug(`compact summary: spawn model=${cliModel} registeredModel=${model.id} promptLen=${promptText.length}`);
|
|
436
439
|
const childEnv = await resolveClaudeChildEnv(piModelRegistry);
|
|
@@ -1492,6 +1495,19 @@ function streamClaudeAgentSdk(model: Model<any>, context: Context, options?: Sim
|
|
|
1492
1495
|
const queryCtx = isReentrant ? new QueryContext() : ctx();
|
|
1493
1496
|
debug(`provider: fresh query setup, isReentrant=${isReentrant}, activeContexts=${activeQueryContexts.size}`);
|
|
1494
1497
|
|
|
1498
|
+
// Fail before claiming a stream if this model needs a newer CLI than we have.
|
|
1499
|
+
const claudeExecutableResolution = resolveClaudeCodeExecutable(model.id, providerSettings.pathToClaudeCodeExecutable);
|
|
1500
|
+
if (claudeExecutableResolution.error) {
|
|
1501
|
+
debug(`provider: ${claudeExecutableResolution.error}`);
|
|
1502
|
+
stream.push({ type: "error", reason: "error", error: newAssistantOutput(model, "", "error", claudeExecutableResolution.error) });
|
|
1503
|
+
stream.end();
|
|
1504
|
+
return stream;
|
|
1505
|
+
}
|
|
1506
|
+
const claudeExecutable = claudeExecutableResolution.path;
|
|
1507
|
+
if (claudeExecutableResolution.source === "path") {
|
|
1508
|
+
debug(`provider: using PATH claude ${claudeExecutable} (bundled CLI is too old for ${model.id})`);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1495
1511
|
// Resolved first: an unaccountable system prompt throws, and doing that before
|
|
1496
1512
|
// anything is claimed or reset leaves no half-built query behind — in particular
|
|
1497
1513
|
// no stream claimed on the shared context that nobody will ever end.
|
|
@@ -1561,7 +1577,6 @@ function streamClaudeAgentSdk(model: Model<any>, context: Context, options?: Sim
|
|
|
1561
1577
|
// programmatically and ignore filesystem MCP entries — applied unconditionally because
|
|
1562
1578
|
// settingSources is left at CC's default, which loads all sources.
|
|
1563
1579
|
const strictMcpConfigEnabled = providerSettings.strictMcpConfig !== false;
|
|
1564
|
-
const claudeExecutable = providerSettings.pathToClaudeCodeExecutable;
|
|
1565
1580
|
|
|
1566
1581
|
// Prefer the model's own thinkingLevelMap when present (pi-ai 0.72+ ships
|
|
1567
1582
|
// per-model overrides — e.g. opus-4-7 wants xhigh→xhigh, not xhigh→max).
|
package/src/models.ts
CHANGED
|
@@ -120,6 +120,14 @@ export function thinkingBoundToPrefix(modelId: string): boolean {
|
|
|
120
120
|
return modelId === "claude-fable-5-1" || modelId.startsWith("claude-fable-5-1[");
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/** Minimum Claude Code CLI version that will accept this model. Undefined
|
|
124
|
+
* means the SDK's bundled CLI is fine. Fable 5.1 400s on 2.1.141 with
|
|
125
|
+
* "version 2.1.251 or newer is required". */
|
|
126
|
+
export function minClaudeCodeVersionForModel(modelId: string): string | undefined {
|
|
127
|
+
if (modelId === "claude-fable-5-1" || modelId.startsWith("claude-fable-5-1[")) return "2.1.251";
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
123
131
|
export function resolveModel<T extends { id: string }>(models: T[], input: string): T | undefined {
|
|
124
132
|
const lower = input.toLowerCase();
|
|
125
133
|
// Exact match first: otherwise `claude-fable-5` would hit `claude-fable-5-1`
|