pi-claude-agent-sdk 0.8.6 → 0.9.2

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
@@ -19,15 +19,21 @@ Use Opus/Sonnet/Haiku as models in pi, with all tool calls flowing through pi's
19
19
  pi install npm:pi-claude-agent-sdk
20
20
  ```
21
21
 
22
+ Requires pi 0.86.1 or newer.
23
+
22
24
  ## Provider
23
25
 
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.
26
+ Use `/model` to select any Claude model in pi-ai's catalog, e.g. `claude-bridge/claude-fable-5-1`, `claude-bridge/claude-opus-5`, or `claude-bridge/claude-haiku-4-5`.
27
+
28
+ Fable 5.1 needs Claude Code **2.1.251 or newer**. If an explicitly configured CLI is too old, the bridge can use a current `claude` on PATH.
29
+
30
+ Behind the scenes, pi's tools are bridged to Claude Code but everything works like normal in pi. Bash commands get Claude Code's 120-second default timeout since pi's bash has none. Skills are forwarded to Claude Code's system prompt, and steering mid-turn reaches Claude at the next tool boundary.
25
31
 
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.
32
+ **Authentication:** the bridge requires an Anthropic OAuth credential (or API key) configured in Pi and uses Pi's token refresh, renewing an OAuth token with less than two hours left before it starts a Claude Code turn (a turn keeps the token it started with). Claude Code login and inherited Claude/Anthropic authentication settings are deliberately ignored, so configure Anthropic authentication in Pi before using the provider.
27
33
 
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.
34
+ The model list comes from pi-ai's Anthropic catalog automatically — when pi-ai adds a new Claude model, it appears in `/model` after updating the package, no bridge update needed. Dated snapshot ids (e.g. `claude-opus-4-5-20251101`) are not shown.
29
35
 
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).
36
+ **1M Context:** Fable 5/5.1, Opus 5.5/5/4.8/4.7, and Sonnet 5.5/5 get 1M context. Opus 4.6 gets 1M only on a Max plan or with Extra Usage, and Sonnet 4.6 only with Extra Usage — set `provider.plan` and/or `provider.longContextExtraUsage` as described in [Configuration](#configuration).
31
37
 
32
38
  ## Configuration
33
39
 
@@ -47,11 +53,12 @@ Config: `~/.pi/agent/claude-bridge.json` (global) or the project Pi config direc
47
53
  `provider`:
48
54
  - `plan` (default `"max"`) — Max (or Team Premium/Enterprise). Set to `"pro"` on a Pro plan so Opus 4.6 stays at 200K context. If it's unset, the first interactive session points this out once, then records `startupNoticeShown` (the date, `YYYY-MM-DD`) in the global config so it doesn't nag again.
49
55
  - `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.
56
+ - `forceTwoHundredK` — array of model ids to pin to 200K context (bare id, no `[1m]` suffix).
50
57
  - `strictMcpConfig` — block MCP servers from `~/.claude.json` / `.mcp.json` (default `true`). Cloud MCP (Gmail/Drive via claude.ai OAuth) is always blocked.
51
58
  - `autoMemoryEnabled` — enable Claude Code's auto-memory system (default `false`)
52
59
  - `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
60
 
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.
61
+ **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 — to pin a model to 200K, use `provider.forceTwoHundredK` instead.
55
62
 
56
63
  ## Tests
57
64
 
@@ -65,13 +72,57 @@ Integration tests spawn real `pi` and Claude Code subprocesses, so they need wri
65
72
 
66
73
  Set `CLAUDE_BRIDGE_DEBUG=1` to enable debug output:
67
74
 
68
- - **Bridge log** at `~/.pi/agent/claude-bridge.log` — every provider call, session sync decision, tool result delivery, and CC's stderr. Override location with `CLAUDE_BRIDGE_DEBUG_PATH`.
69
- - **Per-query Claude Code CLI logs** at `~/.pi/agent/cc-cli-logs/<timestamp>-<tag>-<seq>.log` — the CC subprocess's own debug stream, one file per `query()` call. Tags are `provider` (resumable main turn) or `standalone` (compaction, branch summary, and tool-free extension-owned no-cache completion). Useful when a resume fails or CC misbehaves internally — shows the CLI's own view of session loading, API requests, and tool calls.
75
+ - **Bridge log** at `claude-bridge.log` in pi's agent dir (`PI_CODING_AGENT_DIR`, default `~/.pi/agent`) — provider calls, session sync decisions, tool results, CC stderr. Override location with `CLAUDE_BRIDGE_DEBUG_PATH`.
76
+ - **Per-query CC CLI logs** at `cc-cli-logs/<timestamp>-<tag>-<seq>.log` in the same directory — the subprocess's own debug stream; tag is `provider` or `standalone`. Shows CC's view of session loading, API requests, and tool calls.
70
77
 
71
78
  When filing a bug about a session-resume failure (e.g. "No conversation found"), the most useful attachments are the `syncResult:` lines from the bridge log plus the matching `cc-cli-logs/` file for the failing query.
72
79
 
80
+ ## Compatibility with other extensions
81
+
82
+ ### Which injection routes reach Claude Code
83
+
84
+ The bridge forwards pi's structured parts — project context files, skills, custom prompt, appended instructions — and drops the rest. Measured against the request body (`diag/capture-proxy.mjs`):
85
+
86
+ | Route | Reaches Claude Code |
87
+ |---|---|
88
+ | `before_agent_start` -> `message` | Yes, as literal prompt text in the user turn |
89
+ | `context` editing the last user message | Yes, as literal prompt text |
90
+ | `--append-system-prompt` | Yes, with pi's appended instructions |
91
+ | `context_with_system` editing the system message | Yes when it wraps pi's prompt; the turn fails when it replaces one |
92
+ | `before_agent_start` -> `systemPrompt` | No, dropped |
93
+
94
+ Two traps. Returning `systemPrompt` from `before_agent_start` makes pi replace the whole system prompt, discarding any `context_with_system` edit in the same run — only one reaches the request. And system-prompt edits work by *wrapping*: replacing pi's prompt leaves the bridge with nothing to match, so it refuses the turn rather than send Claude Code a request missing your context files, skills and custom instructions. The error names the closest known prompt and where it diverged.
95
+
96
+ To add instructions, use `message`, `context`, or a system-message edit that keeps pi's prompt intact.
97
+
98
+ ### Hooks written for Claude Code
99
+
100
+ `~/.claude/settings.json` hooks fire inside bridge turns, so a hook injecting Claude-specific guidance duplicates what pi's extensions already provide. pi sets `PI_CODING_AGENT=true` for child processes, including the Claude Code child; a hook can skip itself on that:
101
+
102
+ ```sh
103
+ [ -n "$PI_CODING_AGENT" ] && exit 0
104
+ ```
105
+
106
+ Hooks do not fire on the compact-summary side query.
107
+
108
+ ### System prompt rejections
109
+
110
+ Other extensions can change the system prompt. When the result still contains pi's built-in system prompt text, or the two documentation paths that Anthropic looks for (`docs/custom-provider.md` in the same prompt with `docs/packages.md`), the bridge stops the turn instead of sending it, since Anthropic may otherwise bill these requests as Extra Usage. Fix the source extension before retrying; `CLAUDE_BRIDGE_DEBUG=1` writes the full prompt to the bridge log when this happens.
111
+
112
+ ### Using claude bridge with @gotgenes/pi-subagents
113
+
114
+ Requires the following in `~/.pi/agent/subagents.json`:
115
+
116
+ ```json
117
+ {"promptInheritance": {"claude-bridge": "portable"}}
118
+ ```
119
+
73
120
  ## Known issues
74
121
 
75
- **Sessions get rebuilt more often than they need to be, and a rebuild is expensive.** The bridge rewrites Claude Code's session from pi's history whenever pi's messages move underneath it — after an abort, `/compact`, tree navigation, or an API error. Measured over this repo's own bridge log, a rebuild boundary loses the prompt cache roughly 58% of the time against 26% for a plain resume, so an abort-heavy session costs noticeably more than a clean one. Aborts alone are 46% of rebuilds.
122
+ **A session rebuild re-sends the whole conversation.** The bridge rewrites Claude Code's session from pi's history whenever the two diverge — after an abort, `/compact`, tree navigation, an API error, or on returning to a session — and the next request usually misses the prompt cache for everything past the system prompt. Abort-heavy sessions cost noticeably more.
123
+
124
+ **Files Claude Code edits are not carried across a rebuild.** The edit itself survives in the history as a tool call and result — what's lost is the post-edit file snapshot. `@file` expansions *are* carried.
125
+
126
+ **System prompt changes mid-session may not reach the model.** The bridge keeps Claude Code's default prompt recording: project context (AGENTS.md/CLAUDE.md), skills, and extension-written instructions are captured on the first request and reused on resume. This keeps the cached prefix stable, but later changes may not take effect until a rebuild or compaction. Start a new session if updated instructions must take effect immediately.
76
127
 
77
- **Files Claude Code edits are not carried across a rebuild.** CC records the post-edit contents as an `edited_text_file` attachment; those aren't carried, because they hang off a tool-result record rather than a prompt and so have no stable position to restore them to. The edit itself survives — it's in the history as a tool call and its result — so this costs Claude the file snapshot, not the knowledge that it made the change. `@file` expansions *are* carried.
128
+ **Exported Anthropic environment variables override the Claude Code child (issue #107).** An exported `ANTHROPIC_BASE_URL`, `ANTHROPIC_API_KEY`, or `ANTHROPIC_AUTH_TOKEN` redirects Claude Code to that gateway and every turn fails with its auth error. Unset them for the pi process.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-claude-agent-sdk",
3
- "version": "0.8.6",
3
+ "version": "0.9.2",
4
4
  "private": false,
5
5
  "description": "Pi extension that uses Claude Code (via Agent SDK) as a model provider.",
6
6
  "keywords": [
@@ -34,25 +34,25 @@
34
34
  ],
35
35
  "scripts": {
36
36
  "test:unit": "node --import tsx --import ./tests/lib/setup.mjs --test tests/unit-*.mjs",
37
- "test": "set -a && [ -f .env.test ] && . .env.test; set +a && npm run test:unit && tests/int-smoke.sh && tests/int-multi-turn.sh && tests/int-cache.sh && node --import tsx --test tests/int-*.mjs",
37
+ "test": "set -a && [ -f .env.test ] && . .env.test; set +a; CLAUDE_BRIDGE_TEST_LOG_DIR=$(mktemp -d); export CLAUDE_BRIDGE_TEST_LOG_DIR; npm run test:unit && tests/int-smoke.sh && tests/int-multi-turn.sh && tests/int-cache.sh && node --import tsx --test tests/int-*.mjs && node tests/lib/check-deadlock-logs.mjs \"$CLAUDE_BRIDGE_TEST_LOG_DIR\"",
38
38
  "test:usage": "tests/usage-test.sh",
39
39
  "typecheck": "tsc --noEmit"
40
40
  },
41
41
  "type": "module",
42
42
  "dependencies": {
43
- "@anthropic-ai/claude-agent-sdk": "^0.3.257",
43
+ "@anthropic-ai/claude-agent-sdk": "^0.3.284",
44
44
  "@modelcontextprotocol/sdk": "^1.29.0",
45
45
  "cc-session-io": "^0.4.0",
46
46
  "change-case": "^5.4.4"
47
47
  },
48
48
  "peerDependencies": {
49
- "@earendil-works/pi-ai": ">=0.82.1",
50
- "@earendil-works/pi-coding-agent": ">=0.82.1"
49
+ "@earendil-works/pi-ai": ">=0.86.1",
50
+ "@earendil-works/pi-coding-agent": ">=0.86.1"
51
51
  },
52
52
  "devDependencies": {
53
- "@anthropic-ai/sdk": "^0.93.0",
54
- "@earendil-works/pi-ai": "^0.83.0",
55
- "@earendil-works/pi-coding-agent": "^0.83.0",
53
+ "@anthropic-ai/sdk": "^0.124.0",
54
+ "@earendil-works/pi-ai": "^0.99.1",
55
+ "@earendil-works/pi-coding-agent": "^0.99.1",
56
56
  "@types/node": "^24.13.2",
57
57
  "tsx": "^4.22.4",
58
58
  "typebox": "^1.3.7",
package/src/child-env.ts CHANGED
@@ -65,10 +65,49 @@ export function buildClaudeChildEnv(
65
65
  return env;
66
66
  }
67
67
 
68
+ /** Remaining validity an OAuth token must have before it is handed to a child.
69
+ *
70
+ * A child keeps the token it was spawned with for its whole turn and cannot
71
+ * refresh it (it never sees the refresh token), so a turn that outlives the
72
+ * token fails with "401 OAuth access token has expired". Pi's default refresh
73
+ * window is five minutes; asking for more refreshes early instead. Anthropic
74
+ * access tokens last about eight hours, so this costs one extra refresh per
75
+ * cycle and leaves only turns longer than this exposed.
76
+ */
77
+ export const CHILD_OAUTH_MIN_VALIDITY_MS = 2 * 60 * 60 * 1000;
78
+
79
+ interface AnthropicAuthRuntime {
80
+ getAuth(provider: string, overrides?: { minOAuthValidityMs?: number }): Promise<AuthResult | undefined>;
81
+ }
82
+
83
+ // Latched when the provider issues tokens shorter than the minimum: pi refreshes
84
+ // and then rejects the result, so retrying would refresh on every child spawn.
85
+ let minValidityUnsatisfiable = false;
86
+
87
+ /** Pi's public getProviderAuth() takes no overrides, but is a pass-through to
88
+ * ModelRuntime.getAuth(), which accepts minOAuthValidityMs. Reach the runtime
89
+ * when it is there and fall back to the public call otherwise. */
90
+ async function resolveAnthropicAuth(registry: AnthropicAuthRegistry): Promise<AuthResult | undefined> {
91
+ const runtime = (registry as { runtime?: Partial<AnthropicAuthRuntime> }).runtime;
92
+ if (!minValidityUnsatisfiable && typeof runtime?.getAuth === "function") {
93
+ try {
94
+ return await runtime.getAuth("anthropic", { minOAuthValidityMs: CHILD_OAUTH_MIN_VALIDITY_MS });
95
+ } catch (err) {
96
+ if (/expires too soon/i.test(err instanceof Error ? err.message : String(err))) minValidityUnsatisfiable = true;
97
+ }
98
+ }
99
+ return registry.getProviderAuth("anthropic");
100
+ }
101
+
68
102
  export async function resolveClaudeChildEnv(
69
103
  registry: AnthropicAuthRegistry | null | undefined,
70
104
  base: NodeJS.ProcessEnv = process.env,
71
105
  ): Promise<NodeJS.ProcessEnv> {
72
- const resolved = registry ? await registry.getProviderAuth("anthropic") : undefined;
106
+ const resolved = registry ? await resolveAnthropicAuth(registry) : undefined;
73
107
  return buildClaudeChildEnv(base, resolved);
74
108
  }
109
+
110
+ /** Test hook: clear the unsatisfiable-minimum latch. */
111
+ export function resetChildAuthState(): void {
112
+ minValidityUnsatisfiable = false;
113
+ }
package/src/config.ts CHANGED
@@ -22,6 +22,9 @@ export interface Config {
22
22
  // Anthropic billing). Enables Sonnet 4.6 [1m] on every plan and Opus 4.6
23
23
  // [1m] on Pro.
24
24
  longContextExtraUsage?: boolean;
25
+ // Model ids (e.g. "claude-future-9") whose declared 1M context Claude Code
26
+ // does not actually serve; pins them to the bare id at 200K.
27
+ forceTwoHundredK?: string[];
25
28
  };
26
29
  }
27
30