pig-claude-bridge 0.0.0-stage → 0.2.1
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/LICENSE +21 -0
- package/README.md +187 -2
- package/SECURITY.md +31 -0
- package/THIRD_PARTY_NOTICES.md +103 -0
- package/UPSTREAM.md +82 -0
- package/docs/compatibility.md +139 -0
- package/extensions/pig-claude-bridge/extension.go +205 -0
- package/extensions/pig-claude-bridge/go.mod +7 -0
- package/extensions/pig-claude-bridge/go.sum +4 -0
- package/extensions/pig-claude-bridge/internal/askclaude/schema.go +218 -0
- package/extensions/pig-claude-bridge/internal/askclaude/tool.go +281 -0
- package/extensions/pig-claude-bridge/internal/askclaude/ui.go +193 -0
- package/extensions/pig-claude-bridge/internal/bridge/askclaude.go +217 -0
- package/extensions/pig-claude-bridge/internal/bridge/consume.go +799 -0
- package/extensions/pig-claude-bridge/internal/bridge/debug.go +73 -0
- package/extensions/pig-claude-bridge/internal/bridge/hooks.go +252 -0
- package/extensions/pig-claude-bridge/internal/bridge/host.go +104 -0
- package/extensions/pig-claude-bridge/internal/bridge/isolated.go +217 -0
- package/extensions/pig-claude-bridge/internal/bridge/notices.go +131 -0
- package/extensions/pig-claude-bridge/internal/bridge/prompt.go +93 -0
- package/extensions/pig-claude-bridge/internal/bridge/provider.go +621 -0
- package/extensions/pig-claude-bridge/internal/bridge/query.go +331 -0
- package/extensions/pig-claude-bridge/internal/bridge/ratelimit.go +367 -0
- package/extensions/pig-claude-bridge/internal/bridge/sessioncwd.go +57 -0
- package/extensions/pig-claude-bridge/internal/bridge/state.go +173 -0
- package/extensions/pig-claude-bridge/internal/bridge/stream.go +540 -0
- package/extensions/pig-claude-bridge/internal/bridge/sync.go +261 -0
- package/extensions/pig-claude-bridge/internal/bridge/toolargs.go +59 -0
- package/extensions/pig-claude-bridge/internal/bridge/usage.go +116 -0
- package/extensions/pig-claude-bridge/internal/ccproto/args.go +201 -0
- package/extensions/pig-claude-bridge/internal/ccproto/control.go +220 -0
- package/extensions/pig-claude-bridge/internal/ccproto/exec.go +103 -0
- package/extensions/pig-claude-bridge/internal/ccproto/messages.go +26 -0
- package/extensions/pig-claude-bridge/internal/ccproto/transport.go +483 -0
- package/extensions/pig-claude-bridge/internal/ccsession/paths.go +85 -0
- package/extensions/pig-claude-bridge/internal/ccsession/records.go +125 -0
- package/extensions/pig-claude-bridge/internal/ccsession/repair.go +102 -0
- package/extensions/pig-claude-bridge/internal/ccsession/session.go +480 -0
- package/extensions/pig-claude-bridge/internal/ccsession/verify.go +63 -0
- package/extensions/pig-claude-bridge/internal/config/config.go +228 -0
- package/extensions/pig-claude-bridge/internal/config/logpaths.go +23 -0
- package/extensions/pig-claude-bridge/internal/convert/agentsmd.go +18 -0
- package/extensions/pig-claude-bridge/internal/convert/attachments.go +127 -0
- package/extensions/pig-claude-bridge/internal/convert/convert.go +356 -0
- package/extensions/pig-claude-bridge/internal/convert/skills.go +63 -0
- package/extensions/pig-claude-bridge/internal/convert/toolresults.go +57 -0
- package/extensions/pig-claude-bridge/internal/convert/transcript.go +213 -0
- package/extensions/pig-claude-bridge/internal/mcptools/schema.go +213 -0
- package/extensions/pig-claude-bridge/internal/mcptools/server.go +183 -0
- package/extensions/pig-claude-bridge/internal/models/catalog_gen.go +47 -0
- package/extensions/pig-claude-bridge/internal/models/models.go +220 -0
- package/extensions/pig-claude-bridge/internal/promptcapture/capture.go +573 -0
- package/package.json +1 -6
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pig-claude-bridge contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,188 @@
|
|
|
1
|
-
#
|
|
1
|
+
# pig-claude-bridge
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A native Go extension for [PiG](https://github.com/MichaelKinsy/PiG) that integrates Claude Code. It is a port of [pi-claude-bridge](https://github.com/elidickinson/pi-claude-bridge) (TypeScript, for Pi) with the same behaviour, and it speaks Claude Code's stdio protocol itself instead of loading the Node Agent SDK. Pi-claude-bridge was originally based on [claude-agent-sdk-pi](https://github.com/prateekmedia/claude-agent-sdk-pi) by Prateek Sunal.
|
|
4
|
+
|
|
5
|
+
1. **Provider**: use Opus/Sonnet/Haiku as models in PiG, with all tool calls flowing through PiG's tool loop
|
|
6
|
+
2. **AskClaude tool**: delegate tasks or questions to Claude Code when using another provider (opt-in, deprecated upstream)
|
|
7
|
+
|
|
8
|
+
**FYI:** Anthropic [announced and then unannounced](https://support.claude.com/en/articles/15036540-use-the-claude-agent-sdk-with-your-claude-plan) a change to how tools that use the Agent SDK are billed. Claude Code started by this extension currently uses your regular subscription quota, like Claude Code itself.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
pig install npm:pig-claude-bridge
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Update it with `pig update npm:pig-claude-bridge`. To follow `main` instead of releases, install `git:github.com/balcsida/pig-claude-bridge`.
|
|
17
|
+
|
|
18
|
+
Requirements:
|
|
19
|
+
|
|
20
|
+
- PiG 0.5.3 or newer.
|
|
21
|
+
- Go 1.26 or newer. PiG builds the extension from source the first time it loads it.
|
|
22
|
+
- Claude Code 2.1.310 or newer, logged in, as `claude` on `PATH` or at the path in `provider.pathToClaudeCodeExecutable`. An older binary works but shows a one-time notice, because the port is verified against 2.1.310.
|
|
23
|
+
- macOS or Linux. Windows is untested (the package is Unix-only; see [docs/compatibility.md](docs/compatibility.md)).
|
|
24
|
+
|
|
25
|
+
To try it without installing: `pig -e /path/to/pig-claude-bridge`.
|
|
26
|
+
|
|
27
|
+
## Provider
|
|
28
|
+
|
|
29
|
+
Use `/model` to select any Claude model in the catalog, e.g. `claude-bridge/claude-opus-5` or `claude-bridge/claude-haiku-4-5`.
|
|
30
|
+
|
|
31
|
+
Behind the scenes, PiG's tools are bridged to Claude Code but everything works like normal in PiG. Bash commands get Claude Code's 120-second default timeout since PiG's bash has none. Skills are forwarded to Claude Code's system prompt, and steering mid-turn reaches Claude at the next tool boundary.
|
|
32
|
+
|
|
33
|
+
The model list is a static table generated from the Anthropic catalog of `@earendil-works/pi-ai` 1.2.0 (`scripts/dev/gen-catalog.mjs`) and registered when the extension loads; it does not follow later catalog updates. Dated snapshot ids are not shown. See [docs/compatibility.md](docs/compatibility.md) (D5).
|
|
34
|
+
|
|
35
|
+
**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).
|
|
36
|
+
|
|
37
|
+
## AskClaude Tool
|
|
38
|
+
|
|
39
|
+
**Deprecated upstream:** AskClaude is an opt-in legacy tool. Set `askClaude.enabled` to `true` (see [Configuration](#configuration)) to use it. A PiG subagent extension can recreate similar functionality.
|
|
40
|
+
|
|
41
|
+
## Configuration
|
|
42
|
+
|
|
43
|
+
Config file `claude-bridge.json`, in two places (the project file is merged over the global one, key by key):
|
|
44
|
+
|
|
45
|
+
- global: PiG's agent directory, `~/.pig/agent` by default (`$PIG_HOME/agent`, or `PIG_CODING_AGENT_DIR`). With `PIG_USE_PI_DIRS=1` PiG shares Pi's tree and this is `~/.pi/agent` (or `PI_CODING_AGENT_DIR`).
|
|
46
|
+
- project: `<project>/.pig/claude-bridge.json`; under `PIG_USE_PI_DIRS=1`, `<project>/.pi/claude-bridge.json`.
|
|
47
|
+
|
|
48
|
+
The extension asks PiG for both directories; nothing is hard-coded. A missing or unparseable file counts as empty; with `CLAUDE_BRIDGE_DEBUG` set, a parse failure is written to the debug log. A key with a value of the wrong type is ignored, except `askClaude.allowFullMode` and `askClaude.defaultMode`, which fail closed (full mode denied, mode `none`).
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"askClaude": {
|
|
53
|
+
"enabled": true,
|
|
54
|
+
"allowFullMode": true,
|
|
55
|
+
"defaultIsolated": false,
|
|
56
|
+
"description": "Custom tool description override"
|
|
57
|
+
},
|
|
58
|
+
"provider": {
|
|
59
|
+
"plan": "max",
|
|
60
|
+
"longContextExtraUsage": false,
|
|
61
|
+
"strictMcpConfig": true,
|
|
62
|
+
"pathToClaudeCodeExecutable": "/home/you/.nix-profile/bin/claude"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`askClaude`:
|
|
68
|
+
- `enabled`: register the AskClaude tool (default `false`).
|
|
69
|
+
- `name`: override the tool's name (default `"AskClaude"`)
|
|
70
|
+
- `label`: override the label (default `"Ask Claude Code"`)
|
|
71
|
+
- `description`: override the tool description
|
|
72
|
+
- `defaultMode`: `"read"` (default), `"none"`, or `"full"`
|
|
73
|
+
- `defaultIsolated`: start each call in a fresh session (default `false`)
|
|
74
|
+
- `allowFullMode`: allow `mode: "full"`; set `false` to lock it out
|
|
75
|
+
- `appendSkills`: forward PiG's skills block into the system prompt (default `true`)
|
|
76
|
+
|
|
77
|
+
`provider`:
|
|
78
|
+
- `plan` (default `"pro"`): set to `"max"` if you have a Max (or Team Premium/Enterprise) Anthropic plan. This enables Opus with 1M context.
|
|
79
|
+
- `longContextExtraUsage`: set to `true` to enable 1M context models even if they cost money through Extra Usage on your plan. It enables Sonnet 4.6 with 1M on every plan and Opus 4.6 with 1M on Pro.
|
|
80
|
+
- `forceTwoHundredK`: array of model ids to pin to 200K context (bare id, no `[1m]` suffix). Use if the catalog declares a model at 1M but Claude Code won't serve it on your plan.
|
|
81
|
+
- `strictMcpConfig`: block MCP servers from `~/.claude.json` / `.mcp.json` (default `true`). Cloud MCP (Gmail/Drive via claude.ai OAuth) is always blocked.
|
|
82
|
+
- `autoMemoryEnabled`: enable Claude Code's auto-memory system (default `false`)
|
|
83
|
+
- `pathToClaudeCodeExecutable`: path to the `claude` binary, used instead of the one on `PATH`. A path ending in `.js`, `.mjs` or `.ts` is run through `node`, as the Agent SDK does.
|
|
84
|
+
|
|
85
|
+
**Notices:** the startup notice, shown at the first bridge turn in the TUI only (it stays pending in RPC, JSON and print mode), points out when `provider.plan` is unset and records `startupNoticeShown` (a date) in the global config. A deprecation notice on first use of AskClaude records its display date as `askClaudeDeprecationShown`. The extension rewrites the global file only to add these keys and leaves every other key as it was; an unparseable global file is left alone.
|
|
86
|
+
|
|
87
|
+
## Why Go
|
|
88
|
+
|
|
89
|
+
The extension is a single Go program run by PiG's extension runner. It needs no Node, no Agent SDK and no package install at run time. Measured idle, before the first prompt (`footprint -p`, physical footprint, descendants of the PiG/Pi process):
|
|
90
|
+
|
|
91
|
+
| side | MiB | processes |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| pig 0.5.3 + this extension (pig 26 + runner 6) | **32** | 2 |
|
|
94
|
+
| pi 1.4.0 + TypeScript pi-claude-bridge | **205** | 1 |
|
|
95
|
+
|
|
96
|
+
Neither side spawns Claude Code until the first prompt. Method and commands: [docs/verification.md](docs/verification.md#footprint-task-25).
|
|
97
|
+
|
|
98
|
+
## Tests
|
|
99
|
+
|
|
100
|
+
Offline, quota-free:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
cd extensions/pig-claude-bridge && gofmt -l . && go vet ./... && go test -race ./...
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Live tests spawn real `pig` and Claude Code processes and spend a little quota; run them by hand, never in CI. Claude Code must be logged in (`claude` on `PATH`, the real `~/.claude`, which must be writable: a sandbox that blocks it makes `--resume` fail with `No conversation found with session ID`). Every live run uses a fresh `PIG_HOME`/`PIG_CODING_AGENT_DIR`, so the alternative provider for the AskClaude, image and session-resume checks must be exported in your shell, together with that provider's API-key variable:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
export CLAUDE_BRIDGE_TESTING_ALT_PROVIDER=<provider>
|
|
110
|
+
export CLAUDE_BRIDGE_TESTING_ALT_MODEL=<model>
|
|
111
|
+
export <PROVIDER_API_KEY_VARIABLE>=...
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Nothing is read from `.env*` files. Without these variables those checks print `SKIP`. Then:
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
scripts/live/verify.sh # quick gate: validate-only, smoke turn, idle footprint
|
|
118
|
+
scripts/live/run-all.sh # everything, with a results table and the deadlock check
|
|
119
|
+
cd tests/live && go test -race -tags live -count=1 -v ./...
|
|
120
|
+
cd extensions/pig-claude-bridge && go test -race -tags live ./internal/ccproto/ # contract tests
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Details, versions and recorded results are in [docs/verification.md](docs/verification.md).
|
|
124
|
+
|
|
125
|
+
## Debugging
|
|
126
|
+
|
|
127
|
+
Set `CLAUDE_BRIDGE_DEBUG=1` to enable debug output:
|
|
128
|
+
|
|
129
|
+
- **Bridge log** at `claude-bridge.log` in PiG's agent directory: provider calls, session sync decisions, tool results, Claude Code stderr. Override the location with `CLAUDE_BRIDGE_DEBUG_PATH`.
|
|
130
|
+
- **Per-query Claude Code logs** at `cc-cli-logs/<timestamp>-<tag>-<seq>.log` beside the bridge log (in `dirname(CLAUDE_BRIDGE_DEBUG_PATH)` when that is set): the subprocess's own debug stream; tag is `provider` or `askclaude`.
|
|
131
|
+
|
|
132
|
+
Both can contain prompt text; see [SECURITY.md](SECURITY.md). 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. The `syncResult:` lines are kept in the same format as the TypeScript bridge's.
|
|
133
|
+
|
|
134
|
+
## Compatibility with other extensions
|
|
135
|
+
|
|
136
|
+
### Which injection routes reach Claude Code
|
|
137
|
+
|
|
138
|
+
The bridge forwards PiG's structured prompt parts (project context files, skills, custom prompt, appended instructions, and custom prompt sections) and drops the rest.
|
|
139
|
+
|
|
140
|
+
| Route | Reaches Claude Code |
|
|
141
|
+
|---|---|
|
|
142
|
+
| `before_agent_start` -> `message` | Yes, as literal prompt text in the user turn |
|
|
143
|
+
| `context` editing the last user message | Yes, as literal prompt text |
|
|
144
|
+
| `--append-system-prompt` | Yes, with the appended instructions |
|
|
145
|
+
| `context_with_system` editing the system message | Yes when it wraps PiG's prompt; the turn fails when it replaces one |
|
|
146
|
+
| `before_agent_start` -> `systemPromptOptions.sections` | Yes, one `<name>` block per section (several sections in one system message are written in name order) |
|
|
147
|
+
| `before_agent_start` -> `systemPrompt` | No, dropped |
|
|
148
|
+
|
|
149
|
+
These rows are the TypeScript bridge's measured results (`diag/capture-proxy.mjs`), carried over; this port reproduces the same capture logic, but the request-body measurement was not repeated under PiG.
|
|
150
|
+
|
|
151
|
+
System-prompt edits work by *wrapping*: replacing PiG'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. To add instructions, use `message`, `context`, or a system-message edit that keeps PiG's prompt intact. When the result still contains the built-in system prompt text of Pi or PiG, or the two documentation paths Anthropic looks for, the bridge stops the turn instead of sending it, since Anthropic may bill such requests as Extra Usage. `CLAUDE_BRIDGE_DEBUG=1` writes the full prompt to the bridge log when this happens.
|
|
152
|
+
|
|
153
|
+
### Hooks written for Claude Code
|
|
154
|
+
|
|
155
|
+
`~/.claude/settings.json` hooks fire inside bridge turns, so a hook injecting Claude-specific guidance duplicates what PiG's extensions already provide. The bridge sets `PI_CODING_AGENT=true` for the Claude Code child; a hook can skip itself on that:
|
|
156
|
+
|
|
157
|
+
```sh
|
|
158
|
+
[ -n "$PI_CODING_AGENT" ] && exit 0
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Hooks do not fire on isolated summary queries.
|
|
162
|
+
|
|
163
|
+
### Compaction and branch summaries
|
|
164
|
+
|
|
165
|
+
PiG runs compaction and branch summaries natively. The bridge routes their `cacheRetention: "none"` calls to isolated Claude Code queries, leaving the live session untouched until PiG completes the history rewrite.
|
|
166
|
+
|
|
167
|
+
### Plan usage for footers and quota extensions
|
|
168
|
+
|
|
169
|
+
Claude Code sends a `rate_limit_event` when a usage window changes. The TypeScript bridge passes it to Pi's `after_provider_response` event as `anthropic-ratelimit-unified-*` headers. PiG's host does not emit that event, so this port writes the headers to the debug log only; the user-visible rate-limit notices stay. The bridge also shows the plan's limits in PiG's footer status bar, for example `Claude 5h 6% ↻3h · 7d 76% ↻8h · Fable 99% ↻8h · reset available`. On each event it asks Claude Code for its `/usage` rows (`get_usage`), which name per-model limits; when that fails it shows the event's own windows. This is difference D1 in [docs/compatibility.md](docs/compatibility.md).
|
|
170
|
+
|
|
171
|
+
## Known issues
|
|
172
|
+
|
|
173
|
+
**A session rebuild re-sends the whole conversation.** The bridge rewrites Claude Code's session from PiG'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.
|
|
174
|
+
|
|
175
|
+
**Files Claude Code edits are not carried across a rebuild.** The edit itself survives in the history as a tool call and result; what is lost is the post-edit file snapshot. `@file` expansions *are* carried.
|
|
176
|
+
|
|
177
|
+
**System prompt changes mid-session may not reach the model.** 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.
|
|
178
|
+
|
|
179
|
+
**Exported Anthropic environment variables override the Claude Code child (pi-claude-bridge 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 PiG process.
|
|
180
|
+
|
|
181
|
+
## More
|
|
182
|
+
|
|
183
|
+
- [docs/compatibility.md](docs/compatibility.md): pins, and every difference from the TypeScript bridge with its reason
|
|
184
|
+
- [docs/verification.md](docs/verification.md): contract tests, live integration results, footprint, cross-resume
|
|
185
|
+
- [UPSTREAM.md](UPSTREAM.md): which TypeScript source each Go file ports, and how to bump the pins
|
|
186
|
+
- [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md), [SECURITY.md](SECURITY.md), [CHANGELOG.md](CHANGELOG.md)
|
|
187
|
+
|
|
188
|
+
MIT licensed, see [LICENSE](LICENSE).
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
## What the extension does
|
|
4
|
+
|
|
5
|
+
**It spawns Claude Code.** Each turn it runs `claude` (from `PATH` or `provider.pathToClaudeCodeExecutable`) as a child process with `--permission-mode bypassPermissions` and speaks its stdio protocol. Bypassing Claude Code's own permission prompts is deliberate: PiG's tool loop is in control. Claude Code's built-in tools are turned off and PiG's tools are served to it through an in-process MCP server, so every tool call is executed by PiG under PiG's own approvals. The exception is the opt-in AskClaude tool, which runs Claude Code with its own native tools except those the chosen mode blocks (in `full` mode none is blocked, so Claude Code can change files without PiG's approvals); `askClaude.allowFullMode: false` locks that out. The child is started with `strictMcpConfig` on by default, so MCP servers from `~/.claude.json` and `.mcp.json` do not load.
|
|
6
|
+
|
|
7
|
+
**It runs `claude --version`** to check the supported minimum (2.1.310). A `pathToClaudeCodeExecutable` ending in `.js`, `.mjs` or `.ts` is run through `node`, for the version check and the query alike.
|
|
8
|
+
|
|
9
|
+
**It kills its Claude Code children** when PiG sends `session_shutdown` (exit, `/new`, `/resume`, `/fork`, `/clone`, reload). A PiG killed with SIGKILL cannot do this.
|
|
10
|
+
|
|
11
|
+
**It writes:**
|
|
12
|
+
|
|
13
|
+
- Claude Code session files under `~/.claude/projects/<project hash>/` (or `$CLAUDE_CONFIG_DIR/projects/...`): a mirror of PiG's conversation, so Claude Code can resume it. They contain your prompts, tool calls and tool results in clear text, like any Claude Code session.
|
|
14
|
+
- With `CLAUDE_BRIDGE_DEBUG=1` only: the bridge debug log `claude-bridge.log` and the per-query `cc-cli-logs/` in PiG's agent directory (or beside `CLAUDE_BRIDGE_DEBUG_PATH`). They can contain prompt text, tool results and, when the bridge rejects a prompt, the full system prompt. Do not attach them to a public issue without reading them first.
|
|
15
|
+
- Always, debug or not: `claude-bridge-diag.log` in the agent directory, one JSON line per "should never happen" path (a system prompt the bridge cannot account for, an empty prompt). Entries hold counts, message roles and error texts; a prompt-capture error quotes the part of the system prompt where it diverged.
|
|
16
|
+
- With `CLAUDE_BRIDGE_RECORD_STREAM=<path>` only: every frame Claude Code sends, appended to that path, which can be anywhere. The frames carry the whole conversation as Claude Code sees it: prompts, replies, thinking, tool calls and tool results.
|
|
17
|
+
- `claude-bridge.json` in the agent directory: only the notice flags `startupNoticeShown` and `askClaudeDeprecationShown`, every other key preserved. An unparseable file is left untouched.
|
|
18
|
+
|
|
19
|
+
**It reads:** `claude-bridge.json` in the agent directory and the project config directory, the session files it wrote (to verify them and to carry file attachments across a rebuild), and the output of `claude --version`.
|
|
20
|
+
|
|
21
|
+
**It never handles API keys.** Authentication is Claude Code's own login. Note that an exported `ANTHROPIC_BASE_URL`, `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN` is inherited by the Claude Code child and redirects it; see the README.
|
|
22
|
+
|
|
23
|
+
**It makes no network connection of its own.** All traffic is Claude Code's, started with `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` as the TypeScript bridge sets it, so its non-essential traffic (update checks, the MCP registry, telemetry) stays off.
|
|
24
|
+
|
|
25
|
+
## Supported versions
|
|
26
|
+
|
|
27
|
+
Only the latest release.
|
|
28
|
+
|
|
29
|
+
## Reporting a vulnerability
|
|
30
|
+
|
|
31
|
+
Please report privately through GitHub security advisories on [balcsida/pig-claude-bridge](https://github.com/balcsida/pig-claude-bridge/security/advisories/new), not in a public issue. Include the version, what you did and what you saw; leave out session files and logs unless they are needed, and redact them if so.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
pig-claude-bridge is MIT licensed (see [LICENSE](LICENSE)). It is a port of, and derives from, the projects below. No source of these projects is vendored; the Go code was written against them, and the license texts are reproduced here as their terms require.
|
|
4
|
+
|
|
5
|
+
## pi-claude-bridge
|
|
6
|
+
|
|
7
|
+
- Source: https://github.com/elidickinson/pi-claude-bridge, ported at commit `6aa276a`
|
|
8
|
+
- License: MIT, Copyright (c) 2026 Eli Dickinson
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
MIT License
|
|
12
|
+
|
|
13
|
+
Copyright (c) 2026 Eli Dickinson
|
|
14
|
+
|
|
15
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
16
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
17
|
+
in the Software without restriction, including without limitation the rights
|
|
18
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
19
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
20
|
+
furnished to do so, subject to the following conditions:
|
|
21
|
+
|
|
22
|
+
The above copyright notice and this permission notice shall be included in all
|
|
23
|
+
copies or substantial portions of the Software.
|
|
24
|
+
|
|
25
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
26
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
27
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
28
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
29
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
30
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
31
|
+
SOFTWARE.
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## claude-agent-sdk-pi
|
|
35
|
+
|
|
36
|
+
- Source: https://github.com/prateekmedia/claude-agent-sdk-pi
|
|
37
|
+
- License: MIT, Prateek Sunal. pi-claude-bridge was originally based on this project, so it is the origin of the bridge. Its copyright line and text were not available when this notice was written; the MIT terms are those reproduced above for pi-claude-bridge, and the copyright line should be taken from that project's own `LICENSE`.
|
|
38
|
+
|
|
39
|
+
## cc-session-io
|
|
40
|
+
|
|
41
|
+
- Source: https://github.com/elidickinson/cc-session-io, version 0.4.0
|
|
42
|
+
- License: MIT, Eli Dickinson (the package's `LICENSE` file reads "Copyright (c) 2024" without a name). `internal/ccsession` is a Go port of the subset the bridge uses (session paths, records, chained writes, tool-pairing repair).
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
MIT License
|
|
46
|
+
|
|
47
|
+
Copyright (c) 2024
|
|
48
|
+
|
|
49
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
50
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
51
|
+
in the Software without restriction, including without limitation the rights
|
|
52
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
53
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
54
|
+
furnished to do so, subject to the following conditions:
|
|
55
|
+
|
|
56
|
+
The above copyright notice and this permission notice shall be included in all
|
|
57
|
+
copies or substantial portions of the Software.
|
|
58
|
+
|
|
59
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
60
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
61
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
62
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
63
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
64
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
65
|
+
SOFTWARE.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## PiG SDK
|
|
69
|
+
|
|
70
|
+
- Source: https://github.com/MichaelKinsy/PiG, `extensions/sdk` v0.5.3, which this extension imports as a Go module
|
|
71
|
+
- License: MIT, Michael Kinsy
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
MIT License
|
|
75
|
+
|
|
76
|
+
Copyright Michael Kinsy
|
|
77
|
+
|
|
78
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
79
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
80
|
+
in the Software without restriction, including without limitation the rights
|
|
81
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
82
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
83
|
+
furnished to do so, subject to the following conditions:
|
|
84
|
+
|
|
85
|
+
The above copyright notice and this permission notice shall be included in all
|
|
86
|
+
copies or substantial portions of the Software.
|
|
87
|
+
|
|
88
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
89
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
90
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
91
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
92
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
93
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
94
|
+
SOFTWARE.
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## golang.org/x/text
|
|
98
|
+
|
|
99
|
+
`internal/ccsession` imports `golang.org/x/text/unicode/norm` (v0.43.0) for the NFC normalisation of project paths. It is licensed under the BSD 3-Clause license of the Go authors; the license text ships with the module.
|
|
100
|
+
|
|
101
|
+
## Claude Agent SDK
|
|
102
|
+
|
|
103
|
+
The protocol client in `internal/ccproto` was written from the Agent SDK's public type definitions and live probes of the installed Claude Code. The SDK itself (`@anthropic-ai/claude-agent-sdk`) is not vendored, bundled or depended on. Claude Code is a separate program, installed and licensed by the user.
|
package/UPSTREAM.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Upstream provenance
|
|
2
|
+
|
|
3
|
+
This extension is a Go port of [pi-claude-bridge](https://github.com/elidickinson/pi-claude-bridge) (TypeScript, MIT). Nothing from the upstream source tree is vendored; the Go code was written against it. Where this port differs on purpose, [docs/compatibility.md](docs/compatibility.md) says so.
|
|
4
|
+
|
|
5
|
+
## Pins
|
|
6
|
+
|
|
7
|
+
| what | pin |
|
|
8
|
+
|---|---|
|
|
9
|
+
| pi-claude-bridge | `6aa276a1498e5376363f5c1f8cf04bfcaee38c93` |
|
|
10
|
+
| cc-session-io | 0.4.0 (MIT), the subset the bridge imports |
|
|
11
|
+
| `@anthropic-ai/claude-agent-sdk` | 0.3.310 (Claude Code 2.1.310). The protocol client was written from the SDK's public type definitions and live probes ([protocol reference](docs/reference/agent-sdk-protocol-0.3.310.md)); the SDK itself is not vendored or depended on |
|
|
12
|
+
| Claude Code | 2.1.310 (`ccproto.MinVersion`) |
|
|
13
|
+
| PiG SDK | `github.com/MichaelKinsy/PiG/extensions/sdk` v0.5.3 ([signatures](docs/reference/pig-sdk-signatures-v0.5.3.md)) |
|
|
14
|
+
| `@modelcontextprotocol/sdk` | 1.29.0, only as the reference for the JSON-RPC responses the in-process MCP server must produce |
|
|
15
|
+
| `@earendil-works/pi-ai` | 1.2.0, source of the generated model catalog |
|
|
16
|
+
| `golang.org/x/text` | v0.43.0, for NFC normalisation of project paths |
|
|
17
|
+
|
|
18
|
+
## File map
|
|
19
|
+
|
|
20
|
+
Paths are relative to `extensions/pig-claude-bridge/`. TypeScript paths are `src/` files of pi-claude-bridge at `6aa276a`; line ranges are indicative. "cc-session-io" means the files of the 0.4.0 package (`dist/chunk-7JUXR4CP.js`, `dist/index.d.ts`).
|
|
21
|
+
|
|
22
|
+
| Go file | source | note |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `extension.go` | `index.ts` 2463-2660 (activation, handlers, registration) | PiG SDK wiring, host adapter (`sdkHost`), provider registration in the factory |
|
|
25
|
+
| `internal/askclaude/schema.go` | `askclaude-schema.ts` | AskClaude parameters, modes and defaults |
|
|
26
|
+
| `internal/askclaude/tool.go` | `index.ts` 2662-2780 and the AskClaude call handler near 1750 | tool definition, remembered/isolated sessions, notices |
|
|
27
|
+
| `internal/askclaude/ui.go` | `askclaude-ui.ts` | action summary and tool-name mapping, rendered as lines (D6) |
|
|
28
|
+
| `internal/bridge/askclaude.go` | `index.ts` `promptAndWait` (2263) | AskClaude query path |
|
|
29
|
+
| `internal/bridge/consume.go` | `index.ts` 1028-1100, 1486-1790 (`consumeQuery`, steering, tool-result delivery, abort, discard) | goroutines and channels instead of the event loop (D3) |
|
|
30
|
+
| `internal/bridge/debug.go` | `index.ts` 36-150 (`debug`, `makeCliDebugOptions`, `diagDump`) | |
|
|
31
|
+
| `internal/bridge/hooks.go` | `index.ts` 2463-2660 (event handlers) | `session_start`, `before_agent_start`, `agent_start`, `turn_start`, compaction/tree, shutdown |
|
|
32
|
+
| `internal/bridge/host.go` | new (Go-only) | `Host` interface and `Deps`, so the PiG SDK stays confined to `extension.go` and the bridge is testable with a fake host |
|
|
33
|
+
| `internal/bridge/isolated.go` | `index.ts` 495-600 (`extractIsolatedSummaryPrompt`, `isolatedStreamFn`, `runIsolatedSummary`) | |
|
|
34
|
+
| `internal/bridge/notices.go` | `index.ts` 914-1018 (`showStartupNoticeOnce`, `reportLeaks`), 1138-1146 (`effortForReasoning`) | |
|
|
35
|
+
| `internal/bridge/prompt.go` | `index.ts` 421-478 (`extractUserPrompt`, `extractUserPromptBlocks`), 1632-1637 (`steerBlocks`) | |
|
|
36
|
+
| `internal/bridge/provider.go` | `index.ts` 1788-2262 (`streamClaudeAgentSdk`) | |
|
|
37
|
+
| `internal/bridge/query.go` | `query-state.ts`, `prompt-stream.ts`, `index.ts` 480-494 (`newAssistantOutput`) | |
|
|
38
|
+
| `internal/bridge/ratelimit.go` | `rate-limit.ts`, `index.ts` 510-550 | headers go to the debug log only (D1) |
|
|
39
|
+
| `internal/bridge/sessioncwd.go` | `session-cwd.ts` | most-recent-pending summary slot (D7) |
|
|
40
|
+
| `internal/bridge/state.go` | `index.ts` 187-340 (`SessionState`, `sessionKey`, `markRebuildForSession`, `armStaleContexts`) | `sponsorMarkRebuildForSession` not ported (D4) |
|
|
41
|
+
| `internal/bridge/stream.go` | `index.ts` 1101-1216, 1215-1485 (`processStreamEvent`, `processAssistantMessage`, finalisation) | |
|
|
42
|
+
| `internal/bridge/sync.go` | `index.ts` 341-420, 600-720 (`syncSharedSession`, `turnStart`, `convertAndImportMessages`, `verifyWrittenSession`, `safeRealpath`), 219-237 (`readCarriedAttachments`) | |
|
|
43
|
+
| `internal/bridge/toolargs.go` | `index.ts` 914-1018 (`piToolNameFor`, `mapToolArgs`) | |
|
|
44
|
+
| `internal/bridge/usage.go` | `usage.ts`, `index.ts` `recordUsage` | |
|
|
45
|
+
| `internal/ccproto/args.go` | `index.ts` 53-57 (`CC_CHILD_ENV`), 2030-2096 (query options); the SDK's argv builder (`sdk.mjs`) | argv pinned by `testdata/sdk-argv-snapshot.json` |
|
|
46
|
+
| `internal/ccproto/control.go` | the SDK's control protocol (`sdk.mjs`), reference sections 2 to 4 | requests, responses, inbound `mcp_message`, interrupt |
|
|
47
|
+
| `internal/ccproto/exec.go` | `index.ts` executable options; the SDK's executable handling | `claude` from `PATH` and version check (D2) |
|
|
48
|
+
| `internal/ccproto/messages.go` | the SDK's `SDKUserMessage` type, reference section 4 | |
|
|
49
|
+
| `internal/ccproto/testdata/fakeclaude/main.go` | new (Go-only) | a fake `claude` the transport tests spawn; not part of the extension binary |
|
|
50
|
+
| `internal/ccproto/transport.go` | the SDK's transport (`sdk.mjs`), reference sections 1.4 and 2 | spawn, NDJSON reader/writer, exit handling, close |
|
|
51
|
+
| `internal/ccsession/paths.go` | cc-session-io `chunk-7JUXR4CP.js` 32-56 | project path normalisation and hashing |
|
|
52
|
+
| `internal/ccsession/records.go` | cc-session-io `index.d.ts`, `chunk-7JUXR4CP.js` 262-281 (base fields) | struct order is the key order cc-session-io writes |
|
|
53
|
+
| `internal/ccsession/repair.go` | cc-session-io `repairWithOrigin` (`chunk-7JUXR4CP.js` 62-121) | |
|
|
54
|
+
| `internal/ccsession/session.go` | cc-session-io session create/open/save/delete and `importMessages` | |
|
|
55
|
+
| `internal/ccsession/verify.go` | `session-verify.ts` | |
|
|
56
|
+
| `internal/config/config.go` | `config.ts` | |
|
|
57
|
+
| `internal/config/logpaths.go` | `log-paths.ts` | |
|
|
58
|
+
| `internal/convert/agentsmd.go` | `agents-md.ts` | |
|
|
59
|
+
| `internal/convert/attachments.go` | `attachments.ts` | |
|
|
60
|
+
| `internal/convert/convert.go` | `convert.ts` | golden-tested against `scripts/dev/golden-conversion.mjs` |
|
|
61
|
+
| `internal/convert/skills.go` | `skills.ts`; `formatSkillsForPrompt` of `@earendil-works/pi-coding-agent` for the block text | |
|
|
62
|
+
| `internal/convert/toolresults.go` | `extract-tool-results.ts` | |
|
|
63
|
+
| `internal/convert/transcript.go` | `transcript.ts` | |
|
|
64
|
+
| `internal/mcptools/schema.go` | `mcp-server.ts` (root-schema flattening) | |
|
|
65
|
+
| `internal/mcptools/server.go` | `mcp-server.ts`; `@modelcontextprotocol/sdk` 1.29.0 for response shapes | |
|
|
66
|
+
| `internal/models/catalog_gen.go` | new (Go-only), generated by `scripts/dev/gen-catalog.mjs` from `@earendil-works/pi-ai` 1.2.0 | static catalog (D5); do not edit |
|
|
67
|
+
| `internal/models/models.go` | `models.ts` | |
|
|
68
|
+
| `internal/promptcapture/capture.go` | `prompt-capture.ts` | |
|
|
69
|
+
|
|
70
|
+
## Bump procedure
|
|
71
|
+
|
|
72
|
+
For a new Claude Code / Agent SDK, pi-claude-bridge commit or cc-session-io version:
|
|
73
|
+
|
|
74
|
+
1. Install the new SDK into a temp directory (not into this repo) and diff its `sdk.d.ts`, the argv builder window and the set of control subtypes against the [protocol reference](docs/reference/agent-sdk-protocol-0.3.310.md). Update the reference where the wire changed.
|
|
75
|
+
2. Re-record the argv and `initialize` snapshot: `scripts/dev/record-sdk-argv.mjs` (writes `internal/ccproto/testdata/sdk-argv-snapshot.json`).
|
|
76
|
+
3. Regenerate the conversion golden: `scripts/dev/golden-conversion.mjs` (writes `internal/convert/testdata/golden-pi-history-310.jsonl`; needs the pi-claude-bridge clone with `node_modules`).
|
|
77
|
+
4. Regenerate the model catalog: `scripts/dev/gen-catalog.mjs` (rewrites `internal/models/catalog_gen.go`).
|
|
78
|
+
5. Run the offline suite: `cd extensions/pig-claude-bridge && gofmt -l . && go vet ./... && go test -race ./...`.
|
|
79
|
+
6. Run the contract tests against the new `claude` by hand (they spend quota): `go test -race -tags live ./internal/ccproto/` (see [docs/verification.md](docs/verification.md)). A failure names the broken assumption; fix the port, not the test, unless the new behaviour is the intended one.
|
|
80
|
+
7. Update the pins here, in [docs/compatibility.md](docs/compatibility.md), `ccproto.MinVersion`, the versions in [docs/verification.md](docs/verification.md), and add a [CHANGELOG.md](CHANGELOG.md) entry.
|
|
81
|
+
|
|
82
|
+
For a pi-claude-bridge commit, also diff `src/` between the pinned commit and the new one and port the changes file by file using the map above.
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Compatibility
|
|
2
|
+
|
|
3
|
+
What this port is pinned to, and every place where it differs from the TypeScript bridge (pi-claude-bridge at `6aa276a`) with the reason. Each item states the TypeScript behaviour, the port's behaviour and why. Evidence for the behaviours that hold is in [verification.md](verification.md); provenance of each file is in [../UPSTREAM.md](../UPSTREAM.md).
|
|
4
|
+
|
|
5
|
+
## Pins
|
|
6
|
+
|
|
7
|
+
| what | pin |
|
|
8
|
+
|---|---|
|
|
9
|
+
| PiG | 0.5.3 (`pig 0.5.3+1.4.0.74f46864`) |
|
|
10
|
+
| PiG Go SDK | `github.com/MichaelKinsy/PiG/extensions/sdk` v0.5.3, Go 1.26 floor |
|
|
11
|
+
| Claude Code | 2.1.310 (the port warns when the binary is older) |
|
|
12
|
+
| Agent SDK protocol | `@anthropic-ai/claude-agent-sdk` 0.3.310 (Claude Code 2.1.310); [reference](reference/agent-sdk-protocol-0.3.310.md). 0.3.308 to 0.3.310 changes nothing on the wire that the bridge uses; the one new control subtype, survey feedback, is never sent |
|
|
13
|
+
| pi-claude-bridge | `6aa276a`, https://github.com/elidickinson/pi-claude-bridge |
|
|
14
|
+
| cc-session-io | 0.4.0 (MIT) |
|
|
15
|
+
| Pi compatibility surface | pi 1.4.0 (the Pi that PiG 0.5.3 tracks) |
|
|
16
|
+
| `@modelcontextprotocol/sdk` | 1.31.0 (the TypeScript declares `^1.29.0`), only as the reference for the JSON-RPC responses the in-process MCP server produces |
|
|
17
|
+
| Model catalog | `@earendil-works/pi-ai` 1.2.0, generated into `internal/models/catalog_gen.go` |
|
|
18
|
+
|
|
19
|
+
## Principal differences (D1 to D7)
|
|
20
|
+
|
|
21
|
+
**D1. Rate-limit headers are not delivered to other extensions.** TypeScript: `rate_limit_event` frames become `anthropic-ratelimit-unified-*` headers handed to Pi's `after_provider_response` event (and the `onResponse` callback), so footers and quota extensions can read them. Port: the headers go to the debug log only. Reason: PiG's host does not emit `after_provider_response` (a documented host gap) and the config-route `streamSimple` has no `onResponse`. The user-visible notices (rejected with reset time, `allowed_warning` at 5% steps) are kept. To be removed when PiG closes the gap.
|
|
22
|
+
|
|
23
|
+
**D2. The `claude` binary comes from `PATH` with a version notice.** TypeScript: the Agent SDK launches its bundled binary unless `pathToClaudeCodeExecutable` is set. Port: `provider.pathToClaudeCodeExecutable`, else `claude` on `PATH`; a one-time notice appears when the binary is older than 2.1.310. Reason: downloading the SDK's bundled binary from npm's platform packages would add download and checksum code. A missing binary fails the call with a notice (the TypeScript fell back to the bundled binary).
|
|
24
|
+
|
|
25
|
+
**D3. Handlers run concurrently.** TypeScript: a single event loop; shared state needed no locking. Port: PiG runs one goroutine per host request, so session map, per-session state, prompt captures, the latest rate-limit event and notices are guarded by mutexes, parked tool calls are channels, abort is context cancellation, and every handler recovers panics. The race detector runs on the whole suite.
|
|
26
|
+
|
|
27
|
+
**D4. Subagent mark sponsorship is not ported.** TypeScript: `sponsorMarkRebuildForSession` lets a subagent hand a rebuild mark to the module instance that serves the session, because several module instances share one process. Port: PiG subagents are separate processes, so there is nothing to sponsor; the function has no counterpart. The matching test, `unit-session-cwd` "shares the store across module instances", has no Go counterpart either.
|
|
28
|
+
|
|
29
|
+
**D5. The model list is a static catalog registered in the factory.** TypeScript: the list comes from pi-ai's Anthropic catalog at run time and follows package updates. Port: `scripts/dev/gen-catalog.mjs` generates `internal/models/catalog_gen.go` from `@earendil-works/pi-ai` 1.2.0 and the provider is registered when the extension loads, with nothing registered at `session_start`. Reason: spike S1 showed that registration from `session_start` is too late for `defaultProvider`/`defaultModel` resolution at startup, so the catalog must be known in the factory. A new Claude model needs a regenerated catalog (see [../UPSTREAM.md](../UPSTREAM.md)).
|
|
30
|
+
|
|
31
|
+
**D6. AskClaude renders as lines.** TypeScript: `askclaude-ui.ts` builds pi-tui components (colour, "(to expand)" affordance). Port: PiG's `ToolDefinition.RenderCall`/`RenderResult` return lines, so the action summary is plain lines without colour, with "… (to expand)" as text, and truncation counts runes.
|
|
32
|
+
|
|
33
|
+
**D7. Summary attribution by the most recent pending compaction.** TypeScript: `session-cwd.ts` keys the pending summary session by the identity of the request's `AbortSignal`. Port: Go has no such identity, so a single most-recent-pending slot is set at `session_before_compact` / `session_before_tree` and the isolated summary query reads it; it is not cleared by the read (Pi makes several summary calls per operation, compaction makes two) and `forgetSummarySession` clears it only if no other session has been remembered since. With two sessions summarising at once in one process, the later one wins. A compaction that fails or is aborted never reaches `session_compact`, so the port also forgets the pending session at PiG's `session_compact_failed` (fired after a failed or aborted compaction, PiG `compaction.md`); without it the stale slot would hand that session's cwd to a later summary from another session. The TypeScript needs no such hook because a cancelled compaction's signal is never looked up again. A cancelled tree navigation has no failure event, so its slot stays until the next `session_before_compact` or `session_before_tree` replaces it.
|
|
34
|
+
|
|
35
|
+
## Rulings that changed the plan
|
|
36
|
+
|
|
37
|
+
- Work in place on branch `feat/native-port`, no worktree: the repo is new and single-purpose.
|
|
38
|
+
- Spikes S1 and S3 ran headlessly through PiG's RPC/JSON modes, not the TUI.
|
|
39
|
+
- `Host` is passed per call and never stored on `Bridge`; `Deps` carries `AgentDir` and `ConfigDirName`. PiG's `sdk.Context` is per request, so a stored adapter would go stale and race. The fake PiG host of the spec is replaced by the `bridge.Host` interface and a `fakeHost`, because the SDK is confined to `extension.go`.
|
|
40
|
+
- `IsolatedStream` is exported from the start.
|
|
41
|
+
- The extension directory is `extensions/pig-claude-bridge`: PiG refuses `name_mismatch` when the directory differs from the identity given to `sdk.New`.
|
|
42
|
+
- `ProviderConfig` uses the TypeScript bridge's own values `baseUrl "claude-bridge"`, `apiKey "not-used"`, `api "claude-bridge"` (index.ts:2636-2644); they are parity and satisfy PiG's `api`/`baseUrl` requirements seen in S1.
|
|
43
|
+
- `Start` dispatches inbound control requests (notably `mcp_message`) while the `initialize` response is outstanding, and drops `control_response` frames whose `request_id` is not one of its own (the CLI echoes every response under `--verbose`). Both found by spike S2.
|
|
44
|
+
- `golang.org/x/text/unicode/norm` is the one dependency beyond the PiG SDK: cc-session-io NFC-normalises project paths and the standard library has no NFC; without it session paths of decomposed-Unicode directories (common on macOS) would not match.
|
|
45
|
+
- `MapPiToolNameToSdk` returns `(string, error)` and `ConvertPiMessages` gains an `error` result, carrying the TypeScript throw texts verbatim; the provider turns them into a stream error event. `ResolveOrDerive` likewise returns `(*Capture, error)`, `(nil, nil)` for an empty prompt. `ProjectPromptCapture` takes an explicit `debugLogPath` instead of a package-level hook.
|
|
46
|
+
- Where the plan contradicted the TypeScript, the TypeScript won: `describeRateLimitFailure(rej, failure)` does no text recognition, `RateLimitInfo` carries `Utilization` and `SurpassedThreshold`, and the notice logic runs at the `rate_limit_event` frame of the consume loop (the TypeScript timing).
|
|
47
|
+
- The golden comparison of the conversion checks each record's top-level key sequence exactly (struct order) and compares values canonically (recursively sorted keys, Go escaping). PiG hands the bridge decoded maps, so insertion order inside free-form JSON (tool inputs, results) cannot be preserved; Claude Code does not depend on it. The golden is therefore weaker than "byte for byte" for nested free-form values only.
|
|
48
|
+
- The live tests use the real Claude Code login (a scratch `CLAUDE_CONFIG_DIR` has no auth), with a scratch working directory and reads limited to the session files under that directory's project hash. The contract tests live inside the Go module (`internal/ccproto/live_contract_test.go`, build tag `live`) because they import an internal package; the live harness is a Go port under `tests/live/` (own zero-dependency module) with shell scripts under `scripts/live/`.
|
|
49
|
+
- A lock held without `defer` on a path the panic recovery depends on, a per-call goroutine on the no-run-signal path, and two leaked descriptors per spawned `claude` (the extension process lives across many turns) were treated as defects, not style, and fixed.
|
|
50
|
+
|
|
51
|
+
## Other differences, by area
|
|
52
|
+
|
|
53
|
+
### Config
|
|
54
|
+
|
|
55
|
+
- `askClaude.enabled: "false"` (a string) disables the tool. The TypeScript treats any non-empty string as truthy.
|
|
56
|
+
- A mistyped `askClaude.allowFullMode` denies full mode and a mistyped `askClaude.defaultMode` resolves to `none` (as the TypeScript, which denies on any non-`true` value); other mistyped keys are skipped where the TypeScript passes them through.
|
|
57
|
+
- The notice date comes from `time.Now()`; the config file is rewritten only to add the notice key, preserving key order.
|
|
58
|
+
- A config file that fails to parse is skipped as in the TypeScript, but the failure (`claude-bridge: failed to parse <path>: <err>`) goes to the debug log, when `CLAUDE_BRIDGE_DEBUG` is set, instead of `console.error`: an extension must not write to stderr.
|
|
59
|
+
|
|
60
|
+
### Prompt capture
|
|
61
|
+
|
|
62
|
+
- Custom prompt sections arriving in one system message are rendered in name order. PiG delivers decoded maps without key order; the TypeScript keeps insertion order. No test pins the two-section case.
|
|
63
|
+
- `PlaceCarriedAttachments` returns `(attachments, skipped)`, as the TypeScript object does. The audit-record test appends uuid-less lines directly and the test helper uses a fixed `/work` project path.
|
|
64
|
+
- Two Pi-builder-dependent prompt-capture tests became literal-string assertions (the TypeScript tests depended on Pi's prompt builder). `CollectPromptSkills` guards against inheritance cycles by visiting each capture once (`visited` set), listing ancestors first and each skill file once; at a cycle it stops silently where the TypeScript throws `Cyclic prompt inheritance` (projection still fails on one).
|
|
65
|
+
|
|
66
|
+
### Tool schemas and the MCP server
|
|
67
|
+
|
|
68
|
+
- Advertised tool schemas serialise `properties` (and combinator-description key lists) in sorted order because `InputSchema` is `map[string]any`; Pi declares them in insertion order. `required` keeps its order.
|
|
69
|
+
- Invalid `tools/call` params carry a fixed description instead of the SDK's zod text.
|
|
70
|
+
- The MCP `initialize` result is serialised with sorted keys; the SDK writes its own key order. The values are the same.
|
|
71
|
+
|
|
72
|
+
### Session files and logs
|
|
73
|
+
|
|
74
|
+
- `syncSharedSession` takes `host` and `modelID`.
|
|
75
|
+
- The `APPEND` log line prints the advanced cursor (TypeScript prints the pre-advance cursor).
|
|
76
|
+
- The `[moduleInstanceId]` bracket is dropped from log lines (the TypeScript `diag/*.mjs` parsers expect it); the deadlock-check regex was adapted. The `imported roles:` debug line and the sanitized-id list are dropped.
|
|
77
|
+
- Stderr tail counts runes where the SDK counts UTF-16 units.
|
|
78
|
+
- An empty `CLAUDE_CONFIG_DIR` counts as unset (`??` in JavaScript would use it), and with `HOME` unset the Claude config dir falls back to the account's home directory instead of a relative `.claude`.
|
|
79
|
+
- Verifying an empty session file reports `err=no records` where cc-session-io reports `"undefined" is not valid JSON`.
|
|
80
|
+
- An attachment whose `AfterIndex` is out of range is skipped without cc-session-io's console warning (no stderr in an extension).
|
|
81
|
+
- `pending_permission_requests` redelivery is not implemented: it is never sent without `canUseTool`.
|
|
82
|
+
- The package is Unix-only (`syscall.WaitStatus`).
|
|
83
|
+
|
|
84
|
+
### Provider stream and notices
|
|
85
|
+
|
|
86
|
+
- `Details.executionTime` is an `int64`.
|
|
87
|
+
- The "Claude Code returned an error result" replacement is unreachable, as in the TypeScript (the loop stops at the error result). An error result drops the mirror, while a process exit or panic marks rebuild (the TypeScript drops the mirror on exit too).
|
|
88
|
+
- Abort lifetime is proven by the live smoke's tool round trip. Abort follows `ctx.Signal()`, PiG's run-scoped signal, where the plan said `ctx.Done()`: the per-request `Done()` is cancelled when each provider call ends, which would kill a query parked at a tool call between calls; `Done()` is used only when a context carries no signal.
|
|
89
|
+
- Abort is honoured while Claude Code starts: the call's cancellation reaches the spawn until the `initialize` answer arrives (spike S2 saw about 4 s, inferred to be user `SessionStart` hooks), so an abort then ends the turn as aborted instead of waiting; after that, the stream's own abort wiring takes over, so a parked query still survives between calls. The isolated and AskClaude paths spawn under the call's context. `claude --version` is bounded by 10 s and runs once at a time; a probe that times out is logged and does not fail the query, and an abort during the probe ends the turn as aborted without caching a result. Only the leading `x.y.z` of the version output is read.
|
|
90
|
+
- The cost of a turn leaves out pi-ai's `cost.tiers` and `cacheWrite1h`. The registered models carry zero rates and no tiers, so the cost is zero either way.
|
|
91
|
+
- Every stream event carries a deep copy of the turn so far, where the TypeScript hands out the live object: PiG may read an event after the bridge moves on. The copying grows with the square of the turn's length.
|
|
92
|
+
- The startup notice is shown only when PiG's mode is `tui`, as in the TypeScript, and stays pending otherwise. The mode is read from each call's context (`Host.Mode()`); the TypeScript remembers it from `session_start`. `reportHeldRateLimit` runs at the start of each call rather than inside the consume loop.
|
|
93
|
+
- Prompt-too-long: the CLI exits 1 with empty stderr after the input ends; the bridge surfaces the `is_error` text (`resultErrorText`), not the bare exit.
|
|
94
|
+
|
|
95
|
+
### Rate limits
|
|
96
|
+
|
|
97
|
+
- Headers go to the debug log only (D1).
|
|
98
|
+
- The reset time renders as en-US `3:04:05 PM` with a plain space, where modern Node's `toLocaleTimeString` uses U+202F.
|
|
99
|
+
- The `onResponse`-throw case has no PiG counterpart.
|
|
100
|
+
- Non-string elements of a result's `errors` are rendered with Go's `fmt.Sprint`, which differs from JavaScript's `String()` for objects.
|
|
101
|
+
|
|
102
|
+
### Isolated queries
|
|
103
|
+
|
|
104
|
+
- The executable is resolved process-wide. The TypeScript reloads config per summary cwd, so a project-level `pathToClaudeCodeExecutable` cannot differ per session here.
|
|
105
|
+
- A missing binary fails the call with a notice (D2).
|
|
106
|
+
- Input is ended right after the user write on the isolated path.
|
|
107
|
+
|
|
108
|
+
### AskClaude
|
|
109
|
+
|
|
110
|
+
- `enabled: "false"` as above; rendering as lines (D6); rune-based truncation; `Definition(b, cfg, agentDir, host, requestContext)` has the signature the import cycle forces; the deprecation notice date comes from `time.Now()`.
|
|
111
|
+
- A mid-run abort returns the partial text gathered so far, as the TypeScript does; an abort before Claude Code starts fails the tool with `Error: Aborted`.
|
|
112
|
+
|
|
113
|
+
### Protocol client
|
|
114
|
+
|
|
115
|
+
- There is no Node Agent SDK: the client spawns `claude` with `--permission-mode bypassPermissions` and the SDK's argv, and speaks the newline-delimited JSON control protocol. Argv and the `initialize` request are pinned by a snapshot recorded from the Node SDK 0.3.310 (`scripts/dev/record-sdk-argv.mjs`).
|
|
116
|
+
- Every Claude Code child gets `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`, overwriting an inherited value, as the TypeScript sets it on its own `process.env` at load (`index.ts:2465`) for every child to inherit. The argv snapshot's recorder builds the environment without `process.env`, so the snapshot comparisons leave this variable out and `TestBuildEnv` pins it.
|
|
117
|
+
- `Close` whose context ends before the child exits kills the child at once; the SDK's close always escalates SIGTERM after 2 s and SIGKILL 5 s later.
|
|
118
|
+
- A `pathToClaudeCodeExecutable` ending in `.js`, `.mjs` or `.ts` runs as `node <path> …`, for `--version` and the query, as the SDK does; without `node` on `PATH` the call fails with a notice.
|
|
119
|
+
- Between turns no Claude Code process is alive in either bridge: the query ends at the end of a turn and the next turn resumes the session. PiG's `new_session` replaces the extension runner (D70) and the pre-`/new` runner was observed gone after the next prompt.
|
|
120
|
+
- Claude Code children are killed at `session_shutdown`, the counterpart of the SDK's `killAll` at process exit: every live query is aborted, then every child still running gets SIGKILL (`ccproto.Shutdown`). This matters because Claude Code parked at a tool call does not exit when its stdin ends and keeps calling the API. Per PiG's docs (`extensions.md`, `rpc.md`, `cli-integration.md`) `session_shutdown` is sent on exit (end of input, SIGTERM and SIGHUP), on `/new`, `/resume`, `/fork` and `/clone`, on the RPC `new_session`, `switch_session`, `fork` and `clone`, and on reload. Not covered: a PiG killed with SIGKILL or crashing sends no `session_shutdown`, and its Claude Code children outlive it. `TestIntSessionNewMidTurn` (live) checks the mid-turn `new_session` case; see [verification.md](verification.md).
|
|
121
|
+
|
|
122
|
+
### Live-test findings
|
|
123
|
+
|
|
124
|
+
- The live midturn compaction test caught a parity gap: the port passed the bare tag `session_compact` and dropped the event's `reason` and `willRetry`. Fixed: the tag is `session_compact:<reason>:willRetry=<bool>` as in the TypeScript, unit-tested, and the midturn test passed on its rerun. `session_tree` is bare in both.
|
|
125
|
+
- The Go port of `int-session-rebuild` lives beside the contract tests (`internal/ccproto/live_rebuild_test.go`), not under `tests/live`. Its first case (open, clear, re-add) is covered by `TestResumeRereadsTheJSONLFromDiskOnEveryCall` because `ccsession` has no Open+Clear primitive.
|
|
126
|
+
- `int-attachment-rebuild` and `int-parallel-tool-results` are not RPC tests; their bodies are `-tags live` tests in `internal/ccproto` and `tests/live` runs them.
|
|
127
|
+
- The alternative provider for the AskClaude, image and session-resume tests must come from `CLAUDE_BRIDGE_TESTING_ALT_PROVIDER`, `CLAUDE_BRIDGE_TESTING_ALT_MODEL` and that provider's API-key variable, because every live run uses a fresh `PIG_HOME`/`PIG_CODING_AGENT_DIR` (the TypeScript suite used the real agent dir). Without them those checks skip.
|
|
128
|
+
- The deadlock-check log pattern, the shutdown test (a third case for the stdin close) and the subagent test (our own agent definition, run through `@tintinweb/pi-subagents`) are adapted as listed in [verification.md](verification.md).
|
|
129
|
+
|
|
130
|
+
## Pi-only tests that are not ported
|
|
131
|
+
|
|
132
|
+
The TypeScript suite has 45 `tests/unit-*.mjs` files. Those that test Pi-only wiring with no PiG counterpart are not ported:
|
|
133
|
+
|
|
134
|
+
- `unit-provider-registration` and `unit-pi-streamfn-inventory` (Pi's `registerProvider` and stream-function wiring; PiG registers the provider through the SDK factory, covered by `extension_test.go` and `pig install --validate-only`).
|
|
135
|
+
- `unit-session-cwd` "shares the store across module instances" (D4).
|
|
136
|
+
- Four `unit-queued-steer` cases that need a live SDK loopback.
|
|
137
|
+
- The `sponsorMarkRebuildForSession` cases (D4).
|
|
138
|
+
|
|
139
|
+
The remaining unit tests are ported to Go with the same fixtures; where a test reached into `index.ts` through its `__test` seams, `internal/bridge` has an equivalent internal seam.
|