pig-claude-bridge 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -190,6 +190,8 @@ Claude Code sends a `rate_limit_event` when a usage window changes. The TypeScri
190
190
 
191
191
  **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.
192
192
 
193
+ **Claude Code's feature flags and experiments are off.** The bridge starts Claude Code with `CLAUDE_CODE_DISABLE_FEATURE_FLAGS=1` (and `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`, which implies it), so the GrowthBook flags and A/B experiments Claude Code evaluates at startup (`tengu_*`, cached in `~/.claude.json`) all take their built-in defaults in bridge sessions. That keeps a client-side experiment (a prompt or tool tweak, a limit, a feature toggle) from changing a session unannounced; a change made on the server side of a model is outside what any client can switch off.
194
+
193
195
  **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.
194
196
 
195
197
  ## More
package/UPSTREAM.md CHANGED
@@ -6,11 +6,11 @@ This extension is a Go port of [pi-claude-bridge](https://github.com/elidickinso
6
6
 
7
7
  | what | pin |
8
8
  |---|---|
9
- | pi-claude-bridge | `6aa276a1498e5376363f5c1f8cf04bfcaee38c93` |
9
+ | pi-claude-bridge | `6aa276a1498e5376363f5c1f8cf04bfcaee38c93`, plus these later commits ported on their own: f502f5d (silent-turn reminder, MCP description limit), 2edde93 (interrupted-turn resume), 5c4a268 (default output style), e93d51d (parent-session env markers), 0b096a8 (thinking off). The 0.10.0 session-sync rework (#222) and the AskClaude split (0.11.0) are not ported |
10
10
  | cc-session-io | 0.4.0 (MIT), the subset the bridge imports |
11
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
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)) |
13
+ | PiG SDK | `github.com/MichaelKinsy/PiG/extensions/sdk` v0.7.0 (PiG 0.7.0); the [signatures](docs/reference/pig-sdk-signatures-v0.5.3.md) recorded at v0.5.3 still match every symbol the bridge uses |
14
14
  | `@modelcontextprotocol/sdk` | 1.29.0, only as the reference for the JSON-RPC responses the in-process MCP server must produce |
15
15
  | `@earendil-works/pi-ai` | 1.2.0, source of the generated model catalog |
16
16
  | `golang.org/x/text` | v0.43.0, for NFC normalisation of project paths |
@@ -6,11 +6,11 @@ What this port is pinned to, and every place where it differs from the TypeScrip
6
6
 
7
7
  | what | pin |
8
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) |
9
+ | PiG | 0.7.0 (`pig 0.7.0+1.5.0`) |
10
+ | PiG Go SDK | `github.com/MichaelKinsy/PiG/extensions/sdk` v0.7.0, Go 1.26 floor |
11
+ | Claude Code | 2.1.310 minimum (the port warns when the binary is older); verified against 2.1.319 |
12
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 |
13
+ | pi-claude-bridge | `6aa276a`, https://github.com/elidickinson/pi-claude-bridge, plus the later commits listed in [../UPSTREAM.md](../UPSTREAM.md) (see "Ported after 6aa276a" below) |
14
14
  | cc-session-io | 0.4.0 (MIT) |
15
15
  | Pi compatibility surface | pi 1.4.0 (the Pi that PiG 0.5.3 tracks) |
16
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 |
@@ -127,6 +127,19 @@ What this port is pinned to, and every place where it differs from the TypeScrip
127
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
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
129
 
130
+ ## Ported after 6aa276a
131
+
132
+ Upstream pi-claude-bridge moved on to 0.11.0 (acbe45b, 100 commits) after the pin. These were ported on their own, each with the TypeScript commit; the rest is listed below.
133
+
134
+ - **Child environment (f502f5d, 2edde93).** `CLAUDE_CODE_SILENT_TURN_REMINDER=0`, `CLAUDE_CODE_MAX_MCP_DESCRIPTION_LENGTH=16384` and `CLAUDE_CODE_RESUME_INTERRUPTED_TURN_MAX_AGE_MS=1` join `CCChildEnv`. The argv snapshot predates them, so `TestArgvMatchesNodeSdkSnapshot` sets them aside and `TestBuildEnv` pins their values.
135
+ - **Parent-session markers (e93d51d, #198).** When `CLAUDECODE` is set in PiG's environment, the parent Claude Code session's markers are dropped from the child env. The TypeScript also moved `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` off its own `process.env`; the port never set it there.
136
+ - **Default output style (5c4a268, #220).** The provider's `--settings` carries `outputStyle: "default"`. Pinned live by `TestOutputStyleDefaultOverridesAUserLevelOutputStyle`.
137
+ - **Thinking off (0b096a8, #152).** No reasoning level means `--thinking=disabled` on the provider and isolated paths, for reasoning models whose `thinkingLevelMap` does not mark `off` as `null`.
138
+
139
+ Port-only, with no TypeScript counterpart: **`CLAUDE_CODE_DISABLE_FEATURE_FLAGS=1`** in `CCChildEnv`. In Claude Code 2.1.319 feature-flag evaluation (`Ph()`) requires both no disable variable and telemetry on, and `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` counts as telemetry off, so the TypeScript already runs with flags off; the explicit variable also keeps the disk cache closed should `CLAUDE_CODE_GB_DISK_CACHE_WHEN_TELEMETRY_OFF` be exported, and `TestDisableFeatureFlagsTurnsFlagEvaluationOff` pins the behaviour.
140
+
141
+ Not ported from 0.10.0 and 0.11.0: the content-digest session sync (#222: forks and trims instead of rebuilds, atomic session state, `/fork` and clone continuing the parent's Claude Code session), `CLAUDE_BRIDGE_TRACE_DIR`, provider `env` from `auth.json`, the Extra Usage billing notice, the empty-tool-result placeholder (#227), Claude Code's normalised tool args (#242), the `settingSources` change that stops loading a repo's `.claude/settings*.json` (#198), the AskClaude split into its own package, and the removal of the `provider.plan` notice (the port detects the plan instead). D1 to D7 above still hold.
142
+
130
143
  ## Pi-only tests that are not ported
131
144
 
132
145
  The TypeScript suite has 45 `tests/unit-*.mjs` files. Those that test Pi-only wiring with no PiG counterpart are not ported:
@@ -2,6 +2,6 @@ module github.com/balcsida/pig-claude-bridge/extensions/pig-claude-bridge
2
2
 
3
3
  go 1.26.0
4
4
 
5
- require github.com/MichaelKinsy/PiG/extensions/sdk v0.5.3
5
+ require github.com/MichaelKinsy/PiG/extensions/sdk v0.7.0
6
6
 
7
7
  require golang.org/x/text v0.43.0
@@ -1,4 +1,4 @@
1
- github.com/MichaelKinsy/PiG/extensions/sdk v0.5.3 h1:mgLZchfHrmjoyuKNzP5rpn/D/6hx6ZFoDfwHsqcU8Yo=
2
- github.com/MichaelKinsy/PiG/extensions/sdk v0.5.3/go.mod h1:4j0XhCW0EPOBWvqYE7XKjTvIAahI7K1VyyW3KpjTKj8=
1
+ github.com/MichaelKinsy/PiG/extensions/sdk v0.7.0 h1:5esSYPrwspz6Xo6XMAVJu05qyrp/7wijF5ZqgG0x6kw=
2
+ github.com/MichaelKinsy/PiG/extensions/sdk v0.7.0/go.mod h1:4j0XhCW0EPOBWvqYE7XKjTvIAahI7K1VyyW3KpjTKj8=
3
3
  golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
4
4
  golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
@@ -100,14 +100,15 @@ func (b *Bridge) runIsolatedSummary(ctx context.Context, host Host, model, piCon
100
100
  cwd := b.sessionCwdFor(owner)
101
101
  cliModel := models.ClaudeCodeModelID(str(model["id"]), b.longContext)
102
102
  effort := effortForReasoning(model, options["reasoning"])
103
- b.debugf("compact summary: spawn model=%s registeredModel=%v effort=%s promptLen=%d", cliModel, model["id"], orDefault(effort), len([]rune(promptText)))
103
+ thinkingOff := thinkingDisabledForReasoning(model, options["reasoning"])
104
+ b.debugf("compact summary: spawn model=%s registeredModel=%v effort=%s thinkingDisabled=%t promptLen=%d", cliModel, model["id"], orDefault(effort), thinkingOff, len([]rune(promptText)))
104
105
 
105
106
  if tools == nil {
106
107
  tools = []string{}
107
108
  }
108
109
  debugFile, onStderr := b.cliDebugOptions(tag)
109
110
  opts := ccproto.Options{
110
- Executable: exe, Cwd: cwd, Model: cliModel, Effort: effort, MaxTurns: 1,
111
+ Executable: exe, Cwd: cwd, Model: cliModel, Effort: effort, ThinkingDisabled: thinkingOff, MaxTurns: 1,
111
112
  Tools: tools, ToolsSet: true, AllowedTools: allowedTools,
112
113
  SettingSourcesSet: true, Skills: []string{},
113
114
  SystemPrompt: str(piContext["systemPrompt"]), SystemPromptSet: true,
@@ -155,6 +155,21 @@ var validEfforts = map[string]bool{"low": true, "medium": true, "high": true, "x
155
155
  // the level is unsupported on that model: no effort is sent, so Claude Code's own
156
156
  // default applies. A map value is trusted only when it names a level Claude Code
157
157
  // accepts. "" means no effort.
158
+ // thinkingDisabledForReasoning reports whether Claude Code should be told thinking is off. PiG
159
+ // sends no reasoning level when thinking is off, and Claude Code then applies its own default
160
+ // (thinking on). As pi-ai's Anthropic provider: only for reasoning models, and not when the
161
+ // model's thinkingLevelMap marks "off" unsupported (null), as on models that always think.
162
+ func thinkingDisabledForReasoning(model map[string]any, reasoning any) bool {
163
+ if level, _ := reasoning.(string); level != "" {
164
+ return false
165
+ }
166
+ if on, _ := model["reasoning"].(bool); !on {
167
+ return false
168
+ }
169
+ off, present := obj(model["thinkingLevelMap"])["off"]
170
+ return !(present && off == nil)
171
+ }
172
+
158
173
  func effortForReasoning(model map[string]any, reasoning any) string {
159
174
  level, _ := reasoning.(string)
160
175
  if level == "" {
@@ -347,11 +347,14 @@ func (b *Bridge) freshQuery(ctx context.Context, host Host, stream *sdk.ModelEve
347
347
  settings := config.ClaudeCodeSettings(providerSettings)
348
348
  settings["claudeMdExcludes"] = claudeMdExcludes
349
349
  settings["includeGitInstructions"] = false
350
+ // A user's output style is persona text written for standalone Claude Code; PiG owns the persona here.
351
+ settings["outputStyle"] = "default"
350
352
  b.applyFastMode(settings)
351
353
  debugFile, onStderr := b.cliDebugOptions("provider")
352
354
  opts := ccproto.Options{
353
355
  Executable: exe, Cwd: cwd, Effort: effort, Resume: syncResult.SessionID,
354
- Tools: []string{}, ToolsSet: true, PermissionMode: "bypassPermissions", IncludePartialMessages: true,
356
+ ThinkingDisabled: thinkingDisabledForReasoning(model, options["reasoning"]),
357
+ Tools: []string{}, ToolsSet: true, PermissionMode: "bypassPermissions", IncludePartialMessages: true,
355
358
  Settings: settings, DebugFile: debugFile, AppendSystemPrompt: systemPromptAppend, ExtraArgs: extraArgs,
356
359
  }
357
360
  handlers := ccproto.Handlers{OnStderr: onStderr}
@@ -12,11 +12,44 @@ import (
12
12
  // sdkVersion is the Node Agent SDK version whose behaviour is mirrored.
13
13
  const sdkVersion = "0.3.310"
14
14
 
15
- // CCChildEnv is applied to every Claude Code child (index.ts CC_CHILD_ENV).
15
+ // CCChildEnv is applied to every Claude Code child (index.ts CC_CHILD_ENV, plus the entries
16
+ // pi-claude-bridge added in f502f5d and 2edde93, and CLAUDE_CODE_DISABLE_FEATURE_FLAGS, which is this port's own).
16
17
  var CCChildEnv = map[string]string{
17
18
  "ENABLE_CLAUDEAI_MCP_SERVERS": "0",
18
19
  "DISABLE_AUTO_COMPACT": "1",
19
20
  "CLAUDE_CODE_ELEGANT_MEADOW": "0",
21
+ // On Opus 5.5, Fable 5.1, Mythos 5.1 and Sonnet 5.5, Claude Code adds "The user hasn't heard from
22
+ // you in a while..." after 5 assistant messages with no visible text. PiG decides what the model is
23
+ // told; the message is not in PiG's history and the user never sees it.
24
+ "CLAUDE_CODE_SILENT_TURN_REMINDER": "0",
25
+ // Claude Code cuts MCP tool descriptions mid-sentence at 4096 chars; 16384 is its own larger limit.
26
+ "CLAUDE_CODE_MAX_MCP_DESCRIPTION_LENGTH": "16384",
27
+ // A resumed session that ends at a tool result reads to Claude Code as an interrupted turn, and behind
28
+ // a server flag it inserts a "Continue from where you left off." user message ahead of PiG's prompt.
29
+ // PiG supplies every prompt, so every turn counts as too old to resume.
30
+ "CLAUDE_CODE_RESUME_INTERRUPTED_TURN_MAX_AGE_MS": "1",
31
+ // No GrowthBook feature flags or experiments (tengu_*): every flag takes its built-in default, and the
32
+ // cached assignments in ~/.claude.json are not read either. CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC
33
+ // already implies this in 2.1.319 (flag evaluation is off whenever telemetry is); the explicit
34
+ // variable pins it. Pinned by TestDisableFeatureFlagsTurnsFlagEvaluationOff.
35
+ "CLAUDE_CODE_DISABLE_FEATURE_FLAGS": "1",
36
+ }
37
+
38
+ // EnvAddedAfterSnapshot lists the CCChildEnv keys that testdata/sdk-argv-snapshot.json (recorded at
39
+ // pi-claude-bridge 6aa276a) does not have: added upstream later (f502f5d, 2edde93) or by this port
40
+ // (CLAUDE_CODE_DISABLE_FEATURE_FLAGS). Snapshot tests set them aside; TestBuildEnv pins their values.
41
+ var EnvAddedAfterSnapshot = []string{"CLAUDE_CODE_SILENT_TURN_REMINDER", "CLAUDE_CODE_MAX_MCP_DESCRIPTION_LENGTH",
42
+ "CLAUDE_CODE_RESUME_INTERRUPTED_TURN_MAX_AGE_MS", "CLAUDE_CODE_DISABLE_FEATURE_FLAGS"}
43
+
44
+ // parentSessionVars are set by Claude Code on its own child processes, or exported by that parent. When PiG
45
+ // runs inside a Claude Code session they describe that session, not ours: the SDK stamps its own entrypoint
46
+ // and version only when they are unset, and Claude Code's Bash tool runs CLAUDE_ENV_FILE's script.
47
+ // AI_AGENT is left alone. Dropped only when CLAUDECODE says PiG was started from inside Claude Code (index.ts e93d51d).
48
+ var parentSessionVars = []string{
49
+ "CLAUDECODE", "CLAUDE_CODE_CHILD_SESSION", "CLAUDE_CODE_SESSION_ID", "CLAUDE_CODE_SESSION_ATTENDED",
50
+ "CLAUDE_PID", "CLAUDE_EFFORT", "TRACEPARENT", "CLAUDE_CODE_INVOKED_SKILLS", "CLAUDE_ENV_FILE",
51
+ "CLAUDE_CODE_ENTRYPOINT", "CLAUDE_AGENT_SDK_VERSION", "CLAUDE_CODE_EXECPATH",
52
+ "CLAUDE_CODE_SSE_PORT", "CLAUDE_CODE_MESSAGING_SOCKET", "CLAUDE_CODE_MESSAGING_TOKEN",
20
53
  }
21
54
 
22
55
  // ExtraArg is one extraArgs entry; a nil Value emits the bare flag.
@@ -32,7 +65,10 @@ type Options struct {
32
65
  // Model is the SDK's model option (--model=<id>). The provider and AskClaude pass their
33
66
  // model through ExtraArgs instead (two tokens, as the TypeScript's extraArgs.model); only
34
67
  // the isolated query sets Model.
35
- Model, Effort, Resume string
68
+ Model, Effort, Resume string
69
+ // ThinkingDisabled is the SDK's thinking:{type:"disabled"} (--thinking=disabled): PiG sends no
70
+ // reasoning level when thinking is off, and Claude Code would otherwise apply its own default.
71
+ ThinkingDisabled bool
36
72
  MaxTurns int
37
73
  StrictMcpConfig, NoSessionPersistence bool
38
74
  IncludePartialMessages bool
@@ -65,6 +101,9 @@ func sdkOption(key, value string) []string {
65
101
  // last as two-token pairs (extraArgs first, settings after). SDK MCP servers and the system prompt never appear on argv.
66
102
  func BuildArgs(o Options) []string {
67
103
  args := []string{"--output-format", "stream-json", "--verbose", "--input-format", "stream-json"}
104
+ if o.ThinkingDisabled {
105
+ args = append(args, "--thinking=disabled")
106
+ }
68
107
  if o.Effort != "" {
69
108
  args = append(args, "--effort="+o.Effort)
70
109
  }
@@ -127,13 +166,23 @@ func settingsJSON(settings map[string]any) string {
127
166
  }
128
167
 
129
168
  // BuildEnv returns the child environment: base plus o.Env, minus NODE_OPTIONS, DEBUG, TRACEPARENT and
130
- // TRACESTATE, with the SDK identity variables added when unset, then CCChildEnv,
131
- // CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 (index.ts sets it on process.env at load) and PI_CODING_AGENT=true.
169
+ // TRACESTATE (and parentSessionVars when CLAUDECODE is set), with the SDK identity variables added when
170
+ // unset, then CCChildEnv, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 (the TypeScript sets it for the child
171
+ // only since e93d51d; before, on process.env at load) and PI_CODING_AGENT=true.
132
172
  // Later entries win over earlier duplicates in the returned slice (execve semantics for the consumer).
133
173
  func BuildEnv(base []string, o Options) []string {
134
174
  strip := map[string]bool{"NODE_OPTIONS": true, "DEBUG": true, "TRACEPARENT": true, "TRACESTATE": true}
135
- env := make([]string, 0, len(base)+len(o.Env)+8)
136
- for _, kv := range append(append([]string{}, base...), o.Env...) {
175
+ inherited := append(append([]string{}, base...), o.Env...)
176
+ if slices.ContainsFunc(inherited, func(kv string) bool {
177
+ k, v, _ := strings.Cut(kv, "=")
178
+ return k == "CLAUDECODE" && v != ""
179
+ }) {
180
+ for _, k := range parentSessionVars {
181
+ strip[k] = true
182
+ }
183
+ }
184
+ env := make([]string, 0, len(inherited)+8)
185
+ for _, kv := range inherited {
137
186
  if !strip[strings.SplitN(kv, "=", 2)[0]] {
138
187
  env = append(env, kv)
139
188
  }
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"pig-claude-bridge","version":"0.3.0","description":"Claude Code in PiG: Claude models as a provider and an AskClaude tool. A native Go port of pi-claude-bridge.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/balcsida/pig-claude-bridge.git"},"files":["extensions","!**/*_test.go","!**/testdata","docs/compatibility.md","UPSTREAM.md","THIRD_PARTY_NOTICES.md","SECURITY.md"],"keywords":["pig-package"],"pi":{"extensions":["extensions/pig-claude-bridge"]}}
1
+ {"name":"pig-claude-bridge","version":"0.4.0","description":"Claude Code in PiG: Claude models as a provider and an AskClaude tool. A native Go port of pi-claude-bridge.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/balcsida/pig-claude-bridge.git"},"files":["extensions","!**/*_test.go","!**/testdata","docs/compatibility.md","UPSTREAM.md","THIRD_PARTY_NOTICES.md","SECURITY.md"],"keywords":["pig-package"],"pi":{"extensions":["extensions/pig-claude-bridge"]}}