opencode-claude-max-proxy 1.0.2 → 1.8.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
@@ -4,199 +4,358 @@
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
  [![GitHub stars](https://img.shields.io/github/stars/rynfar/opencode-claude-max-proxy.svg)](https://github.com/rynfar/opencode-claude-max-proxy/stargazers)
6
6
 
7
- Use your **Claude Max subscription** with [OpenCode](https://github.com/opencode-ai/opencode). Built to work with [Oh-My-OpenCode](https://github.com/code-yeongyu/oh-my-opencode) and its full agent orchestration system.
7
+ A transparent proxy that lets you use your **Claude Max subscription** with [OpenCode](https://opencode.ai) with full multi-model agent delegation.
8
8
 
9
- ![OpenCode with Claude Max](screenshot.png)
9
+ ## The Idea
10
10
 
11
- ## The Problem
11
+ OpenCode speaks the Anthropic API. Claude Max gives you unlimited Claude through the [Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk). This proxy bridges the two — but the interesting part isn't the bridging, it's **how tool execution and agent delegation work**.
12
12
 
13
- Anthropic doesn't allow Claude Max subscribers to use their subscription with third-party tools like OpenCode. If you want to use Claude in OpenCode, you have to pay for API access separately - even though you're already paying for "unlimited" Claude.
13
+ Most proxy approaches try to handle everything internally: the SDK runs tools, manages subagents, and streams back a finished result. The problem? The SDK only knows about Claude models. If you're using [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) with agents routed to GPT-5.4, Gemini, or other providers, the SDK can't reach them. Your carefully configured multi-model agent setup gets flattened to "everything runs on Claude."
14
14
 
15
- Your options are:
16
- 1. Use Claude's official apps only (limited to their UI)
17
- 2. Pay again for API access on top of your Max subscription
18
- 3. **Use this proxy**
15
+ This proxy takes a different approach: **passthrough delegation**. Instead of handling tools internally, the proxy intercepts every tool call *before the SDK executes it*, stops the turn, and forwards the raw `tool_use` back to OpenCode. OpenCode handles execution — including routing `Task` calls through its own agent system with full model routing. The result comes back as a standard `tool_result`, the proxy resumes the SDK session, and Claude continues.
19
16
 
20
- ## The Solution
21
-
22
- This proxy bridges the gap using Anthropic's own tools:
17
+ The effect: Claude thinks it's calling tools normally. OpenCode thinks it's talking to the Anthropic API. But behind the scenes, your oracle agent runs on GPT-5.4, your explore agent runs on Gemini, and the main session runs on Claude Max — exactly as configured.
23
18
 
24
19
  ```
25
- OpenCode → Proxy (localhost:3456) → Claude Agent SDK → Your Claude Max Subscription
20
+ ┌──────────┐ ┌───────────────┐ ┌──────────────┐
21
+ │ │ 1. Request │ │ SDK Auth │ │
22
+ │ OpenCode │ ─────────────► │ Proxy │ ───────────► │ Claude Max │
23
+ │ │ │ (localhost) │ │ │
24
+ │ │ 4. tool_use │ │ 2. Generate │ │
25
+ │ │ ◄───────────── │ PreToolUse │ ◄─────────── │ Response │
26
+ │ │ │ hook blocks │ │ │
27
+ │ │ 5. Execute │ │ └──────────────┘
28
+ │ │ ─── Task ───► │ │
29
+ │ │ (routes to │ │ ┌──────────────┐
30
+ │ │ GPT-5.4, │ 6. Resume │ │ oh-my- │
31
+ │ │ Gemini,etc) │ ◄──────────── │ │ opencode │
32
+ │ │ │ tool_result │ │ agents │
33
+ │ │ 7. Final │ │ │ │
34
+ │ │ ◄───────────── │ │ │ oracle: │
35
+ │ │ response │ │ │ GPT-5.4 │
36
+ └──────────┘ └───────────────┘ │ explore: │
37
+ │ Gemini │
38
+ │ librarian: │
39
+ │ Sonnet │
40
+ └──────────────┘
26
41
  ```
27
42
 
28
- The [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) is Anthropic's **official npm package** that lets developers build with Claude using their Max subscription. This proxy simply translates OpenCode's API requests into SDK calls.
29
-
30
- **Your Max subscription. Anthropic's official SDK. Zero additional cost.**
31
-
32
- ## Is This Allowed?
33
-
34
- **Yes.** Here's why:
43
+ ## How Passthrough Works
35
44
 
36
- | Concern | Reality |
37
- |---------|---------|
38
- | "Bypassing restrictions" | No. We use Anthropic's public SDK exactly as documented |
39
- | "Violating TOS" | No. The SDK is designed for programmatic Claude access |
40
- | "Unauthorized access" | No. You authenticate with `claude login` using your own account |
41
- | "Reverse engineering" | No. We call `query()` from their npm package, that's it |
45
+ The key insight is the Claude Agent SDK's `PreToolUse` hook — an officially supported callback that fires before any tool executes. Combined with `maxTurns: 1`, it gives us precise control over the execution boundary:
42
46
 
43
- The Claude Agent SDK exists specifically to let Max subscribers use Claude programmatically. We're just translating the request format so OpenCode can use it.
47
+ 1. **Claude generates a response** with `tool_use` blocks (Read a file, delegate to an agent, run a command)
48
+ 2. **The PreToolUse hook fires** for each tool call — we capture the tool name, input, and ID, then return `decision: "block"` to prevent SDK-internal execution
49
+ 3. **The SDK stops** (blocked tool + maxTurns:1 = turn complete) and we have the full tool_use payload
50
+ 4. **The proxy returns it to OpenCode** as a standard Anthropic API response with `stop_reason: "tool_use"`
51
+ 5. **OpenCode handles everything** — file reads, shell commands, and crucially, `Task` delegation through its own agent system with full model routing
52
+ 6. **OpenCode sends `tool_result` back**, the proxy resumes the SDK session, and Claude continues
44
53
 
45
- **~200 lines of TypeScript. No hacks. No magic. Just format translation.**
54
+ No monkey-patching. No forked SDKs. No fragile stream rewriting. Just a hook and a turn limit.
46
55
 
47
- ## Features
56
+ ## Quick Start
48
57
 
49
- | Feature | Description |
50
- |---------|-------------|
51
- | **Zero API costs** | Uses your Claude Max subscription, not per-token billing |
52
- | **Full compatibility** | Works with any Anthropic model in OpenCode |
53
- | **Streaming support** | Real-time SSE streaming just like the real API |
54
- | **Auto-start** | Optional launchd service for macOS |
55
- | **Simple setup** | Two commands to get running |
58
+ ### Prerequisites
56
59
 
57
- ## Prerequisites
60
+ 1. **Claude Max subscription** — [Subscribe here](https://claude.ai/settings/subscription)
61
+ 2. **Claude CLI** authenticated: `npm install -g @anthropic-ai/claude-code && claude login`
58
62
 
59
- 1. **Claude Max subscription** - [Subscribe here](https://claude.ai/settings/subscription)
63
+ ### Option A: npm Install
60
64
 
61
- 2. **Claude CLI** installed and authenticated:
62
- ```bash
63
- npm install -g @anthropic-ai/claude-code
64
- claude login
65
- ```
65
+ ```bash
66
+ npm install -g opencode-claude-max-proxy
66
67
 
67
- 3. **Bun** runtime:
68
- ```bash
69
- curl -fsSL https://bun.sh/install | bash
70
- ```
68
+ # Start in passthrough mode (recommended)
69
+ CLAUDE_PROXY_PASSTHROUGH=1 claude-max-proxy
70
+ ```
71
71
 
72
- ## Installation
72
+ ### Option B: From Source
73
73
 
74
74
  ```bash
75
75
  git clone https://github.com/rynfar/opencode-claude-max-proxy
76
76
  cd opencode-claude-max-proxy
77
77
  bun install
78
+
79
+ # Start in passthrough mode (recommended)
80
+ CLAUDE_PROXY_PASSTHROUGH=1 bun run proxy
78
81
  ```
79
82
 
80
- ## Usage
83
+ > **Note:** Running from source requires [Bun](https://bun.sh): `curl -fsSL https://bun.sh/install | bash`
81
84
 
82
- ### Start the Proxy
85
+ ### Option C: Docker
83
86
 
84
87
  ```bash
85
- bun run proxy
88
+ git clone https://github.com/rynfar/opencode-claude-max-proxy
89
+ cd opencode-claude-max-proxy
90
+
91
+ # Start the container
92
+ docker compose up -d
93
+
94
+ # Login to Claude inside the container (one-time)
95
+ docker compose exec proxy claude login
96
+
97
+ # Verify
98
+ curl http://127.0.0.1:3456/health
86
99
  ```
87
100
 
88
- ### Run OpenCode
101
+ > **Note:** On macOS, `claude login` must be run inside the container (keychain credentials can't be mounted). On Linux, volume-mounting `~/.claude` may work without re-login.
102
+
103
+ ### Connect OpenCode
89
104
 
90
105
  ```bash
91
106
  ANTHROPIC_API_KEY=dummy ANTHROPIC_BASE_URL=http://127.0.0.1:3456 opencode
92
107
  ```
93
108
 
94
- Select any `anthropic/claude-*` model (opus, sonnet, haiku).
109
+ The `ANTHROPIC_API_KEY` can be any non-empty string the proxy doesn't use it. Authentication is handled by your `claude login` session.
95
110
 
96
- ### One-liner
111
+ ### Shell Alias
97
112
 
98
113
  ```bash
99
- bun run proxy & ANTHROPIC_API_KEY=dummy ANTHROPIC_BASE_URL=http://127.0.0.1:3456 opencode
114
+ # Add to ~/.zshrc or ~/.bashrc
115
+ alias oc='ANTHROPIC_API_KEY=dummy ANTHROPIC_BASE_URL=http://127.0.0.1:3456 opencode'
100
116
  ```
101
117
 
102
- ## Auto-start on macOS
118
+ ## Modes
103
119
 
104
- Set up the proxy to run automatically on login:
120
+ ### Passthrough Mode (recommended)
105
121
 
106
122
  ```bash
107
- cat > ~/Library/LaunchAgents/com.claude-max-proxy.plist << EOF
108
- <?xml version="1.0" encoding="UTF-8"?>
109
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
110
- <plist version="1.0">
111
- <dict>
112
- <key>Label</key>
113
- <string>com.claude-max-proxy</string>
114
- <key>ProgramArguments</key>
115
- <array>
116
- <string>$(which bun)</string>
117
- <string>run</string>
118
- <string>proxy</string>
119
- </array>
120
- <key>WorkingDirectory</key>
121
- <string>$(pwd)</string>
122
- <key>RunAtLoad</key>
123
- <true/>
124
- <key>KeepAlive</key>
125
- <true/>
126
- </dict>
127
- </plist>
128
- EOF
123
+ CLAUDE_PROXY_PASSTHROUGH=1 bun run proxy
124
+ ```
129
125
 
130
- launchctl load ~/Library/LaunchAgents/com.claude-max-proxy.plist
126
+ All tool execution is forwarded to OpenCode. This enables:
127
+
128
+ - **Multi-model agent delegation** — oh-my-opencode routes each agent to its configured model
129
+ - **Full agent system prompts** — not abbreviated descriptions, the real prompts
130
+ - **OpenCode manages everything** — tools, agents, permissions, lifecycle
131
+
132
+ ### Internal Mode (default)
133
+
134
+ ```bash
135
+ bun run proxy
131
136
  ```
132
137
 
133
- Then add an alias to `~/.zshrc`:
138
+ Tools execute inside the proxy via MCP. Subagents run on Claude via the SDK's native agent system. Simpler setup, but all agents use Claude regardless of oh-my-opencode config.
139
+
140
+ | | Passthrough | Internal |
141
+ |---|---|---|
142
+ | Tool execution | OpenCode | Proxy (MCP) |
143
+ | Agent delegation | OpenCode → multi-model | SDK → Claude only |
144
+ | oh-my-opencode models | ✅ Respected | ❌ All Claude |
145
+ | Agent system prompts | ✅ Full | ⚠️ Description only |
146
+ | Setup complexity | Same | Same |
147
+
148
+ ## Works With Any Agent Framework
149
+
150
+ The proxy extracts agent definitions from the `Task` tool description that OpenCode sends in each request. This means it works automatically with:
151
+
152
+ - **Native OpenCode** — `build` and `plan` agents
153
+ - **oh-my-opencode** — `oracle`, `explore`, `librarian`, `sisyphus-junior`, `metis`, `momus`, etc.
154
+ - **Custom agents** — anything you define in `opencode.json`
155
+
156
+ In internal mode, a `PreToolUse` hook fuzzy-matches agent names as a safety net (e.g., `general-purpose` → `general`, `Explore` → `explore`, `code-reviewer` → `oracle`). In passthrough mode, OpenCode handles agent names directly.
157
+
158
+ ## Session Resume
159
+
160
+ The proxy tracks SDK session IDs and resumes conversations on follow-up requests:
161
+
162
+ - **Faster responses** — no re-processing of conversation history
163
+ - **Better context** — the SDK remembers tool results from previous turns
164
+
165
+ Session tracking works two ways:
166
+
167
+ 1. **Header-based** (recommended) — Add the included OpenCode plugin:
168
+ ```json
169
+ { "plugin": ["./path/to/opencode-claude-max-proxy/src/plugin/claude-max-headers.ts"] }
170
+ ```
171
+
172
+ 2. **Fingerprint-based** (automatic fallback) — hashes the first user message to identify returning conversations
173
+
174
+ Sessions are cached for 24 hours.
175
+
176
+ ## Configuration
177
+
178
+ | Variable | Default | Description |
179
+ |----------|---------|-------------|
180
+ | `CLAUDE_PROXY_PASSTHROUGH` | (unset) | Enable passthrough mode — forward all tools to OpenCode |
181
+ | `CLAUDE_PROXY_PORT` | 3456 | Proxy server port |
182
+ | `CLAUDE_PROXY_HOST` | 127.0.0.1 | Proxy server host |
183
+ | `CLAUDE_PROXY_WORKDIR` | (cwd) | Working directory for Claude and tools |
184
+ | `CLAUDE_PROXY_MAX_CONCURRENT` | 1 | Max concurrent SDK sessions (increase with caution) |
185
+ | `CLAUDE_PROXY_IDLE_TIMEOUT_SECONDS` | 120 | Connection idle timeout |
186
+
187
+ ## Concurrency
188
+
189
+ The proxy supports multiple simultaneous OpenCode instances. Each request spawns its own independent SDK subprocess — run as many terminals as you want. All concurrent responses are delivered correctly.
190
+
191
+ **Use the auto-restart supervisor** (recommended):
134
192
 
135
193
  ```bash
136
- echo "alias oc='ANTHROPIC_API_KEY=dummy ANTHROPIC_BASE_URL=http://127.0.0.1:3456 opencode'" >> ~/.zshrc
137
- source ~/.zshrc
194
+ CLAUDE_PROXY_PASSTHROUGH=1 bun run proxy
195
+ # or directly:
196
+ CLAUDE_PROXY_PASSTHROUGH=1 ./bin/claude-proxy-supervisor.sh
138
197
  ```
139
198
 
140
- Now just run `oc` to start OpenCode with Claude Max.
199
+ > **⚠️ Known Issue: Bun SSE Crash ([oven-sh/bun#17947](https://github.com/oven-sh/bun/issues/17947))**
200
+ >
201
+ > The Claude Agent SDK's `cli.js` subprocess is compiled with Bun, which has a known segfault in `structuredCloneForStream` during cleanup of concurrent streaming responses. This affects all runtimes (Bun, Node.js via tsx) because the crash originates in the SDK's child process, not in the proxy itself.
202
+ >
203
+ > **What this means in practice:**
204
+ > - **Sequential requests (1 terminal):** No impact. Never crashes.
205
+ > - **Concurrent requests (2+ terminals):** All responses are delivered correctly. The crash occurs *after* responses complete, during stream cleanup. No work is lost.
206
+ > - **After a crash:** The supervisor restarts the proxy in ~1-3 seconds. If a new request arrives during this window, OpenCode shows "Unable to connect" — just retry.
207
+ >
208
+ > We are monitoring the upstream Bun issue for a fix. Once patched, the supervisor becomes optional.
141
209
 
142
210
  ## Model Mapping
143
211
 
144
212
  | OpenCode Model | Claude SDK |
145
213
  |----------------|------------|
146
214
  | `anthropic/claude-opus-*` | opus |
147
- | `anthropic/claude-sonnet-*` | sonnet |
215
+ | `anthropic/claude-sonnet-*` | sonnet (default) |
148
216
  | `anthropic/claude-haiku-*` | haiku |
149
217
 
150
- ## Configuration
151
-
152
- | Environment Variable | Default | Description |
153
- |---------------------|---------|-------------|
154
- | `CLAUDE_PROXY_PORT` | 3456 | Proxy server port |
155
- | `CLAUDE_PROXY_HOST` | 127.0.0.1 | Proxy server host |
218
+ ## Disclaimer
156
219
 
157
- ## How It Works
220
+ This project is an **unofficial wrapper** around Anthropic's publicly available [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk). It is not affiliated with, endorsed by, or supported by Anthropic.
158
221
 
159
- 1. **OpenCode** sends a request to `http://127.0.0.1:3456/messages` (thinking it's the Anthropic API)
160
- 2. **Proxy** receives the request and extracts the messages
161
- 3. **Proxy** calls `query()` from the Claude Agent SDK with your prompt
162
- 4. **Claude Agent SDK** authenticates using your Claude CLI login (tied to your Max subscription)
163
- 5. **Claude** processes the request using your subscription
164
- 6. **Proxy** streams the response back in Anthropic SSE format
165
- 7. **OpenCode** receives the response as if it came from the real API
222
+ **Use at your own risk.** The authors make no claims regarding compliance with Anthropic's Terms of Service. It is your responsibility to review and comply with [Anthropic's Terms of Service](https://www.anthropic.com/terms) and any applicable usage policies. Terms may change at any time.
166
223
 
167
- The proxy is ~200 lines of TypeScript. No magic, no hacks.
224
+ This project calls `query()` from Anthropic's public npm package using your own authenticated account. No API keys are intercepted, no authentication is bypassed, and no proprietary systems are reverse-engineered.
168
225
 
169
226
  ## FAQ
170
227
 
171
- ### Why do I need `ANTHROPIC_API_KEY=dummy`?
228
+ <details>
229
+ <summary><strong>Why passthrough mode instead of handling tools internally?</strong></summary>
230
+
231
+ Internal tool execution means the SDK handles everything — but the SDK only speaks Claude. If your agents are configured for GPT-5.4 or Gemini via oh-my-opencode, that routing gets lost. Passthrough mode preserves the full OpenCode agent pipeline, including multi-model routing, full system prompts, and agent lifecycle management.
232
+ </details>
233
+
234
+ <details>
235
+ <summary><strong>Does this work without oh-my-opencode?</strong></summary>
236
+
237
+ Yes. Both modes work with native OpenCode (build + plan agents) and with any custom agents defined in your `opencode.json`. oh-my-opencode just adds more agents and model routing — the proxy handles whatever OpenCode sends.
238
+ </details>
172
239
 
173
- OpenCode requires an API key to be set, but we never actually use it. The Claude Agent SDK handles authentication through your Claude CLI login. Any non-empty string works.
240
+ <details>
241
+ <summary><strong>Why do I need ANTHROPIC_API_KEY=dummy?</strong></summary>
174
242
 
175
- ### Does this work with other tools besides OpenCode?
243
+ OpenCode requires an API key to be set, but the proxy never uses it. Authentication is handled by your `claude login` session through the Agent SDK.
244
+ </details>
176
245
 
177
- Yes! Any tool that uses the Anthropic API format can use this proxy. Just point `ANTHROPIC_BASE_URL` to `http://127.0.0.1:3456`.
246
+ <details>
247
+ <summary><strong>What about rate limits?</strong></summary>
178
248
 
179
- ### What about rate limits?
249
+ Your Claude Max subscription has its own usage limits. The proxy doesn't add any additional limits. Concurrent requests are supported.
250
+ </details>
180
251
 
181
- Your Claude Max subscription has its own usage limits. This proxy doesn't add any additional limits.
252
+ <details>
253
+ <summary><strong>Is my data sent anywhere else?</strong></summary>
182
254
 
183
- ### Is my data sent anywhere else?
255
+ No. The proxy runs locally. Requests go directly to Claude through the official SDK. In passthrough mode, tool execution happens in OpenCode on your machine.
256
+ </details>
184
257
 
185
- No. The proxy runs locally on your machine. Your requests go directly to Claude through the official SDK.
258
+ <details>
259
+ <summary><strong>Why does internal mode use MCP tools?</strong></summary>
260
+
261
+ The Claude Agent SDK uses different parameter names for tools than OpenCode (e.g., `file_path` vs `filePath`). Internal mode provides its own MCP tools with SDK-compatible parameter names. Passthrough mode doesn't need this since OpenCode handles tool execution directly.
262
+ </details>
186
263
 
187
264
  ## Troubleshooting
188
265
 
189
- ### "Authentication failed"
266
+ | Problem | Solution |
267
+ |---------|----------|
268
+ | "Authentication failed" | Run `claude login` to authenticate |
269
+ | "Connection refused" | Make sure the proxy is running: `bun run proxy` |
270
+ | "Port 3456 is already in use" | `kill $(lsof -ti :3456)` or use `CLAUDE_PROXY_PORT=4567` |
271
+ | Title generation fails | Set `"small_model": "anthropic/claude-haiku-4-5"` in your OpenCode config |
272
+
273
+ ## Auto-start (macOS)
274
+
275
+ ```bash
276
+ cat > ~/Library/LaunchAgents/com.claude-max-proxy.plist << EOF
277
+ <?xml version="1.0" encoding="UTF-8"?>
278
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
279
+ <plist version="1.0">
280
+ <dict>
281
+ <key>Label</key>
282
+ <string>com.claude-max-proxy</string>
283
+ <key>ProgramArguments</key>
284
+ <array>
285
+ <string>$(pwd)/bin/claude-proxy-supervisor.sh</string>
286
+ </array>
287
+ <key>WorkingDirectory</key>
288
+ <string>$(pwd)</string>
289
+ <key>EnvironmentVariables</key>
290
+ <dict>
291
+ <key>CLAUDE_PROXY_PASSTHROUGH</key>
292
+ <string>1</string>
293
+ </dict>
294
+ <key>RunAtLoad</key>
295
+ <true/>
296
+ <key>KeepAlive</key>
297
+ <true/>
298
+ </dict>
299
+ </plist>
300
+ EOF
301
+
302
+ launchctl load ~/Library/LaunchAgents/com.claude-max-proxy.plist
303
+ ```
190
304
 
191
- Run `claude login` to authenticate with the Claude CLI.
305
+ ## Testing
192
306
 
193
- ### "Connection refused"
307
+ ```bash
308
+ bun test
309
+ ```
194
310
 
195
- Make sure the proxy is running: `bun run proxy`
311
+ 106 tests across 13 files covering:
312
+ - Passthrough tool forwarding and tool_result acceptance
313
+ - PreToolUse hook interception and agent name fuzzy matching
314
+ - SDK agent definition extraction (native OpenCode + oh-my-opencode)
315
+ - MCP tool filtering (internal mode)
316
+ - Session resume (header-based and fingerprint-based)
317
+ - Streaming message deduplication
318
+ - Working directory propagation
319
+ - Concurrent request handling
320
+ - Error classification (auth, rate limit, billing, timeout)
196
321
 
197
- ### Proxy keeps dying
322
+ ### Health Endpoint
198
323
 
199
- Use the launchd service (see Auto-start section) which automatically restarts the proxy.
324
+ ```bash
325
+ curl http://127.0.0.1:3456/health
326
+ ```
327
+
328
+ Returns auth status, subscription type, and proxy mode. Use this to verify the proxy is running and authenticated before connecting OpenCode.
329
+
330
+ ## Architecture
331
+
332
+ ```
333
+ src/
334
+ ├── proxy/
335
+ │ ├── server.ts # HTTP server, passthrough/internal modes, SSE streaming, session resume
336
+ │ ├── agentDefs.ts # Extract SDK agent definitions from OpenCode's Task tool
337
+ │ ├── agentMatch.ts # Fuzzy matching for agent names (6-level priority)
338
+ │ └── types.ts # ProxyConfig types and defaults
339
+ ├── mcpTools.ts # MCP tool definitions for internal mode (read, write, edit, bash, glob, grep)
340
+ ├── logger.ts # Structured logging with AsyncLocalStorage context
341
+ ├── plugin/
342
+ │ └── claude-max-headers.ts # OpenCode plugin for session header injection
343
+ └── __tests__/ # 106 tests across 13 files
344
+ ├── helpers.ts
345
+ ├── integration.test.ts
346
+ ├── proxy-agent-definitions.test.ts
347
+ ├── proxy-agent-fuzzy-match.test.ts
348
+ ├── proxy-error-handling.test.ts
349
+ ├── proxy-mcp-filtering.test.ts
350
+ ├── proxy-passthrough-concept.test.ts
351
+ ├── proxy-pretooluse-hook.test.ts
352
+ ├── proxy-session-resume.test.ts
353
+ ├── proxy-streaming-message.test.ts
354
+ ├── proxy-subagent-support.test.ts
355
+ ├── proxy-tool-forwarding.test.ts
356
+ ├── proxy-transparent-tools.test.ts
357
+ └── proxy-working-directory.test.ts
358
+ ```
200
359
 
201
360
  ## License
202
361
 
@@ -0,0 +1,62 @@
1
+ #!/bin/bash
2
+ # Auto-restart supervisor for claude-max-proxy
3
+ #
4
+ # The Claude Agent SDK's cli.js subprocess (compiled with Bun) can crash
5
+ # during cleanup of concurrent streaming responses — a known Bun bug
6
+ # (oven-sh/bun#17947). All responses are delivered correctly; the crash
7
+ # only occurs after response completion.
8
+ #
9
+ # This supervisor runs the proxy in a subshell with signal isolation,
10
+ # detects crashes, and restarts in ~1 second.
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ cd "$SCRIPT_DIR/.."
14
+
15
+ # Ignore signals that the child's crash might propagate
16
+ trap '' SIGPIPE
17
+
18
+ RESTART_COUNT=0
19
+ MAX_RAPID_RESTARTS=50
20
+ RAPID_WINDOW=60
21
+ LAST_START=0
22
+
23
+ while true; do
24
+ NOW=$(date +%s)
25
+
26
+ if [ $((NOW - LAST_START)) -gt $RAPID_WINDOW ]; then
27
+ RESTART_COUNT=0
28
+ fi
29
+
30
+ if [ $RESTART_COUNT -ge $MAX_RAPID_RESTARTS ]; then
31
+ echo "[supervisor] Too many restarts ($RESTART_COUNT in ${RAPID_WINDOW}s). Stopping."
32
+ exit 1
33
+ fi
34
+
35
+ LAST_START=$NOW
36
+ RESTART_COUNT=$((RESTART_COUNT + 1))
37
+
38
+ if [ $RESTART_COUNT -gt 1 ]; then
39
+ echo "[supervisor] Restarting proxy (restart #$RESTART_COUNT)..."
40
+ else
41
+ echo "[supervisor] Starting proxy..."
42
+ fi
43
+
44
+ # Run in subshell so crashes don't kill the supervisor
45
+ (exec bun run ./bin/claude-proxy.ts)
46
+ EXIT_CODE=$?
47
+
48
+ if [ $EXIT_CODE -eq 0 ]; then
49
+ echo "[supervisor] Proxy exited cleanly."
50
+ break
51
+ fi
52
+
53
+ # Signal-based exits (128+signal)
54
+ if [ $EXIT_CODE -gt 128 ]; then
55
+ SIG=$((EXIT_CODE - 128))
56
+ echo "[supervisor] Proxy killed by signal $SIG. Restarting in 1s..."
57
+ else
58
+ echo "[supervisor] Proxy exited (code $EXIT_CODE). Restarting in 1s..."
59
+ fi
60
+
61
+ sleep 1
62
+ done
@@ -1,8 +1,33 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import { startProxyServer } from "../src/proxy/server"
4
+ import { execSync } from "child_process"
5
+
6
+ // Prevent SDK subprocess crashes from killing the proxy
7
+ process.on("uncaughtException", (err) => {
8
+ console.error(`[PROXY] Uncaught exception (recovered): ${err.message}`)
9
+ })
10
+ process.on("unhandledRejection", (reason) => {
11
+ console.error(`[PROXY] Unhandled rejection (recovered): ${reason instanceof Error ? reason.message : reason}`)
12
+ })
4
13
 
5
14
  const port = parseInt(process.env.CLAUDE_PROXY_PORT || "3456", 10)
6
15
  const host = process.env.CLAUDE_PROXY_HOST || "127.0.0.1"
16
+ const idleTimeoutSeconds = parseInt(process.env.CLAUDE_PROXY_IDLE_TIMEOUT_SECONDS || "120", 10)
17
+
18
+ // Pre-flight auth check
19
+ try {
20
+ const authJson = execSync("claude auth status", { encoding: "utf-8", timeout: 5000 })
21
+ const auth = JSON.parse(authJson)
22
+ if (!auth.loggedIn) {
23
+ console.error("\x1b[31m✗ Not logged in to Claude.\x1b[0m Run: claude login")
24
+ process.exit(1)
25
+ }
26
+ if (auth.subscriptionType !== "max") {
27
+ console.error(`\x1b[33m⚠ Claude subscription: ${auth.subscriptionType || "unknown"} (Max recommended)\x1b[0m`)
28
+ }
29
+ } catch {
30
+ console.error("\x1b[33m⚠ Could not verify Claude auth status. If requests fail, run: claude login\x1b[0m")
31
+ }
7
32
 
8
- await startProxyServer({ port, host })
33
+ await startProxyServer({ port, host, idleTimeoutSeconds })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-claude-max-proxy",
3
- "version": "1.0.2",
3
+ "version": "1.8.0",
4
4
  "description": "Use your Claude Max subscription with OpenCode via proxy server",
5
5
  "type": "module",
6
6
  "main": "./src/proxy/server.ts",
@@ -11,14 +11,17 @@
11
11
  ".": "./src/proxy/server.ts"
12
12
  },
13
13
  "scripts": {
14
- "start": "bun run ./bin/claude-proxy.ts",
15
- "proxy": "bun run ./bin/claude-proxy.ts",
14
+ "start": "./bin/claude-proxy-supervisor.sh",
15
+ "proxy": "./bin/claude-proxy-supervisor.sh",
16
16
  "test": "bun test",
17
- "typecheck": "tsc --noEmit"
17
+ "typecheck": "tsc --noEmit",
18
+ "proxy:direct": "bun run ./bin/claude-proxy.ts"
18
19
  },
19
20
  "dependencies": {
20
- "@anthropic-ai/claude-agent-sdk": "^0.2.0",
21
- "hono": "^4.11.4"
21
+ "@anthropic-ai/claude-agent-sdk": "^0.2.80",
22
+ "glob": "^13.0.0",
23
+ "hono": "^4.11.4",
24
+ "p-queue": "^8.0.1"
22
25
  },
23
26
  "devDependencies": {
24
27
  "@types/bun": "^1.2.21",