opencode-claude-max-proxy 1.0.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 +205 -0
- package/bin/claude-proxy.ts +8 -0
- package/package.json +60 -0
- package/src/logger.ts +10 -0
- package/src/proxy/server.ts +193 -0
- package/src/proxy/types.ts +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# opencode-claude-max-proxy
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/opencode-claude-max-proxy)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://github.com/rynfar/opencode-claude-max-proxy/stargazers)
|
|
6
|
+
|
|
7
|
+
Use your **Claude Max subscription** with OpenCode.
|
|
8
|
+
|
|
9
|
+
## The Problem
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
Your options are:
|
|
14
|
+
1. Use Claude's official apps only (limited to their UI)
|
|
15
|
+
2. Pay again for API access on top of your Max subscription
|
|
16
|
+
3. **Use this proxy**
|
|
17
|
+
|
|
18
|
+
## The Solution
|
|
19
|
+
|
|
20
|
+
This proxy bridges the gap using Anthropic's own tools:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
OpenCode → Proxy (localhost:3456) → Claude Agent SDK → Your Claude Max Subscription
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
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.
|
|
27
|
+
|
|
28
|
+
**Your Max subscription. Anthropic's official SDK. Zero additional cost.**
|
|
29
|
+
|
|
30
|
+
## Is This Allowed?
|
|
31
|
+
|
|
32
|
+
**Yes.** Here's why:
|
|
33
|
+
|
|
34
|
+
| Concern | Reality |
|
|
35
|
+
|---------|---------|
|
|
36
|
+
| "Bypassing restrictions" | No. We use Anthropic's public SDK exactly as documented |
|
|
37
|
+
| "Violating TOS" | No. The SDK is designed for programmatic Claude access |
|
|
38
|
+
| "Unauthorized access" | No. You authenticate with `claude login` using your own account |
|
|
39
|
+
| "Reverse engineering" | No. We call `query()` from their npm package, that's it |
|
|
40
|
+
|
|
41
|
+
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.
|
|
42
|
+
|
|
43
|
+
**~200 lines of TypeScript. No hacks. No magic. Just format translation.**
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
| Feature | Description |
|
|
48
|
+
|---------|-------------|
|
|
49
|
+
| **Zero API costs** | Uses your Claude Max subscription, not per-token billing |
|
|
50
|
+
| **Full compatibility** | Works with any Anthropic model in OpenCode |
|
|
51
|
+
| **Streaming support** | Real-time SSE streaming just like the real API |
|
|
52
|
+
| **Auto-start** | Optional launchd service for macOS |
|
|
53
|
+
| **Simple setup** | Two commands to get running |
|
|
54
|
+
|
|
55
|
+
## Prerequisites
|
|
56
|
+
|
|
57
|
+
1. **Claude Max subscription** - [Subscribe here](https://claude.ai/settings/subscription)
|
|
58
|
+
|
|
59
|
+
2. **Claude CLI** installed and authenticated:
|
|
60
|
+
```bash
|
|
61
|
+
npm install -g @anthropic-ai/claude-code
|
|
62
|
+
claude login
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
3. **Bun** runtime:
|
|
66
|
+
```bash
|
|
67
|
+
curl -fsSL https://bun.sh/install | bash
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
git clone https://github.com/rynfar/opencode-claude-max-proxy
|
|
74
|
+
cd opencode-claude-max-proxy
|
|
75
|
+
bun install
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Usage
|
|
79
|
+
|
|
80
|
+
### Start the Proxy
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
bun run proxy
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Run OpenCode
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
ANTHROPIC_API_KEY=dummy ANTHROPIC_BASE_URL=http://127.0.0.1:3456 opencode
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Select any `anthropic/claude-*` model (opus, sonnet, haiku).
|
|
93
|
+
|
|
94
|
+
### One-liner
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
bun run proxy & ANTHROPIC_API_KEY=dummy ANTHROPIC_BASE_URL=http://127.0.0.1:3456 opencode
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Auto-start on macOS
|
|
101
|
+
|
|
102
|
+
Set up the proxy to run automatically on login:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
cat > ~/Library/LaunchAgents/com.claude-max-proxy.plist << EOF
|
|
106
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
107
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
108
|
+
<plist version="1.0">
|
|
109
|
+
<dict>
|
|
110
|
+
<key>Label</key>
|
|
111
|
+
<string>com.claude-max-proxy</string>
|
|
112
|
+
<key>ProgramArguments</key>
|
|
113
|
+
<array>
|
|
114
|
+
<string>$(which bun)</string>
|
|
115
|
+
<string>run</string>
|
|
116
|
+
<string>proxy</string>
|
|
117
|
+
</array>
|
|
118
|
+
<key>WorkingDirectory</key>
|
|
119
|
+
<string>$(pwd)</string>
|
|
120
|
+
<key>RunAtLoad</key>
|
|
121
|
+
<true/>
|
|
122
|
+
<key>KeepAlive</key>
|
|
123
|
+
<true/>
|
|
124
|
+
</dict>
|
|
125
|
+
</plist>
|
|
126
|
+
EOF
|
|
127
|
+
|
|
128
|
+
launchctl load ~/Library/LaunchAgents/com.claude-max-proxy.plist
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Then add an alias to `~/.zshrc`:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
echo "alias oc='ANTHROPIC_API_KEY=dummy ANTHROPIC_BASE_URL=http://127.0.0.1:3456 opencode'" >> ~/.zshrc
|
|
135
|
+
source ~/.zshrc
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Now just run `oc` to start OpenCode with Claude Max.
|
|
139
|
+
|
|
140
|
+
## Model Mapping
|
|
141
|
+
|
|
142
|
+
| OpenCode Model | Claude SDK |
|
|
143
|
+
|----------------|------------|
|
|
144
|
+
| `anthropic/claude-opus-*` | opus |
|
|
145
|
+
| `anthropic/claude-sonnet-*` | sonnet |
|
|
146
|
+
| `anthropic/claude-haiku-*` | haiku |
|
|
147
|
+
|
|
148
|
+
## Configuration
|
|
149
|
+
|
|
150
|
+
| Environment Variable | Default | Description |
|
|
151
|
+
|---------------------|---------|-------------|
|
|
152
|
+
| `CLAUDE_PROXY_PORT` | 3456 | Proxy server port |
|
|
153
|
+
| `CLAUDE_PROXY_HOST` | 127.0.0.1 | Proxy server host |
|
|
154
|
+
|
|
155
|
+
## How It Works
|
|
156
|
+
|
|
157
|
+
1. **OpenCode** sends a request to `http://127.0.0.1:3456/messages` (thinking it's the Anthropic API)
|
|
158
|
+
2. **Proxy** receives the request and extracts the messages
|
|
159
|
+
3. **Proxy** calls `query()` from the Claude Agent SDK with your prompt
|
|
160
|
+
4. **Claude Agent SDK** authenticates using your Claude CLI login (tied to your Max subscription)
|
|
161
|
+
5. **Claude** processes the request using your subscription
|
|
162
|
+
6. **Proxy** streams the response back in Anthropic SSE format
|
|
163
|
+
7. **OpenCode** receives the response as if it came from the real API
|
|
164
|
+
|
|
165
|
+
The proxy is ~200 lines of TypeScript. No magic, no hacks.
|
|
166
|
+
|
|
167
|
+
## FAQ
|
|
168
|
+
|
|
169
|
+
### Why do I need `ANTHROPIC_API_KEY=dummy`?
|
|
170
|
+
|
|
171
|
+
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.
|
|
172
|
+
|
|
173
|
+
### Does this work with other tools besides OpenCode?
|
|
174
|
+
|
|
175
|
+
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`.
|
|
176
|
+
|
|
177
|
+
### What about rate limits?
|
|
178
|
+
|
|
179
|
+
Your Claude Max subscription has its own usage limits. This proxy doesn't add any additional limits.
|
|
180
|
+
|
|
181
|
+
### Is my data sent anywhere else?
|
|
182
|
+
|
|
183
|
+
No. The proxy runs locally on your machine. Your requests go directly to Claude through the official SDK.
|
|
184
|
+
|
|
185
|
+
## Troubleshooting
|
|
186
|
+
|
|
187
|
+
### "Authentication failed"
|
|
188
|
+
|
|
189
|
+
Run `claude login` to authenticate with the Claude CLI.
|
|
190
|
+
|
|
191
|
+
### "Connection refused"
|
|
192
|
+
|
|
193
|
+
Make sure the proxy is running: `bun run proxy`
|
|
194
|
+
|
|
195
|
+
### Proxy keeps dying
|
|
196
|
+
|
|
197
|
+
Use the launchd service (see Auto-start section) which automatically restarts the proxy.
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT
|
|
202
|
+
|
|
203
|
+
## Credits
|
|
204
|
+
|
|
205
|
+
Built with the [Claude Agent SDK](https://www.npmjs.com/package/@anthropic-ai/claude-agent-sdk) by Anthropic.
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-claude-max-proxy",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Use your Claude Max subscription with OpenCode via proxy server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/proxy/server.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"claude-max-proxy": "./bin/claude-proxy.ts"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/proxy/server.ts"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"start": "bun run ./bin/claude-proxy.ts",
|
|
15
|
+
"proxy": "bun run ./bin/claude-proxy.ts",
|
|
16
|
+
"test": "bun test",
|
|
17
|
+
"typecheck": "tsc --noEmit"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.0",
|
|
21
|
+
"hono": "^4.11.4"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/bun": "^1.2.21",
|
|
25
|
+
"@types/node": "^22.0.0",
|
|
26
|
+
"typescript": "^5.8.2"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"bin/",
|
|
30
|
+
"src/proxy/",
|
|
31
|
+
"src/logger.ts",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"keywords": [
|
|
35
|
+
"opencode",
|
|
36
|
+
"opencode-ai",
|
|
37
|
+
"opencode-plugin",
|
|
38
|
+
"claude",
|
|
39
|
+
"claude-max",
|
|
40
|
+
"claude-code",
|
|
41
|
+
"anthropic",
|
|
42
|
+
"ai-coding",
|
|
43
|
+
"ai-assistant",
|
|
44
|
+
"proxy",
|
|
45
|
+
"claude-agent-sdk",
|
|
46
|
+
"llm",
|
|
47
|
+
"coding-assistant"
|
|
48
|
+
],
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "git+https://github.com/rynfar/opencode-claude-max-proxy.git"
|
|
52
|
+
},
|
|
53
|
+
"homepage": "https://github.com/rynfar/opencode-claude-max-proxy#readme",
|
|
54
|
+
"bugs": {
|
|
55
|
+
"url": "https://github.com/rynfar/opencode-claude-max-proxy/issues"
|
|
56
|
+
},
|
|
57
|
+
"author": "rynfar",
|
|
58
|
+
"license": "MIT",
|
|
59
|
+
"private": false
|
|
60
|
+
}
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const shouldLog = () => process.env["OPENCODE_CLAUDE_PROVIDER_DEBUG"]
|
|
2
|
+
|
|
3
|
+
export const claudeLog = (message: string, extra?: Record<string, unknown>) => {
|
|
4
|
+
if (!shouldLog()) return
|
|
5
|
+
const parts = ["[opencode-claude-code-provider]", message]
|
|
6
|
+
if (extra && Object.keys(extra).length > 0) {
|
|
7
|
+
parts.push(JSON.stringify(extra))
|
|
8
|
+
}
|
|
9
|
+
console.debug(parts.join(" "))
|
|
10
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { Hono } from "hono"
|
|
2
|
+
import { cors } from "hono/cors"
|
|
3
|
+
import { query } from "@anthropic-ai/claude-agent-sdk"
|
|
4
|
+
import type { Context } from "hono"
|
|
5
|
+
import type { ProxyConfig } from "./types"
|
|
6
|
+
import { DEFAULT_PROXY_CONFIG } from "./types"
|
|
7
|
+
import { claudeLog } from "../logger"
|
|
8
|
+
|
|
9
|
+
function mapModelToClaudeModel(model: string): "sonnet" | "opus" | "haiku" {
|
|
10
|
+
if (model.includes("opus")) return "opus"
|
|
11
|
+
if (model.includes("haiku")) return "haiku"
|
|
12
|
+
return "sonnet"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createProxyServer(config: Partial<ProxyConfig> = {}) {
|
|
16
|
+
const finalConfig = { ...DEFAULT_PROXY_CONFIG, ...config }
|
|
17
|
+
const app = new Hono()
|
|
18
|
+
|
|
19
|
+
app.use("*", cors())
|
|
20
|
+
|
|
21
|
+
app.get("/", (c) => {
|
|
22
|
+
return c.json({
|
|
23
|
+
status: "ok",
|
|
24
|
+
service: "claude-max-proxy",
|
|
25
|
+
version: "1.0.0",
|
|
26
|
+
format: "anthropic",
|
|
27
|
+
endpoints: ["/v1/messages", "/messages"]
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const handleMessages = async (c: Context) => {
|
|
32
|
+
try {
|
|
33
|
+
const body = await c.req.json()
|
|
34
|
+
const model = mapModelToClaudeModel(body.model || "sonnet")
|
|
35
|
+
const stream = body.stream ?? true
|
|
36
|
+
|
|
37
|
+
claudeLog("proxy.anthropic.request", { model, stream, messageCount: body.messages?.length })
|
|
38
|
+
|
|
39
|
+
const prompt = body.messages
|
|
40
|
+
?.map((m: { role: string; content: string | Array<{ type: string; text?: string }> }) => {
|
|
41
|
+
const role = m.role === "assistant" ? "Assistant" : "Human"
|
|
42
|
+
let content: string
|
|
43
|
+
if (typeof m.content === "string") {
|
|
44
|
+
content = m.content
|
|
45
|
+
} else if (Array.isArray(m.content)) {
|
|
46
|
+
content = m.content
|
|
47
|
+
.filter((block) => block.type === "text" && block.text)
|
|
48
|
+
.map((block) => block.text)
|
|
49
|
+
.join("")
|
|
50
|
+
} else {
|
|
51
|
+
content = String(m.content)
|
|
52
|
+
}
|
|
53
|
+
return `${role}: ${content}`
|
|
54
|
+
})
|
|
55
|
+
.join("\n\n") || ""
|
|
56
|
+
|
|
57
|
+
if (!stream) {
|
|
58
|
+
let fullContent = ""
|
|
59
|
+
const response = query({
|
|
60
|
+
prompt,
|
|
61
|
+
options: { maxTurns: 1, model }
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
for await (const message of response) {
|
|
65
|
+
if (message.type === "assistant") {
|
|
66
|
+
for (const block of message.message.content) {
|
|
67
|
+
if (block.type === "text") {
|
|
68
|
+
fullContent += block.text
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return c.json({
|
|
75
|
+
id: `msg_${Date.now()}`,
|
|
76
|
+
type: "message",
|
|
77
|
+
role: "assistant",
|
|
78
|
+
content: [{ type: "text", text: fullContent }],
|
|
79
|
+
model: body.model,
|
|
80
|
+
stop_reason: "end_turn",
|
|
81
|
+
usage: { input_tokens: 0, output_tokens: 0 }
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const encoder = new TextEncoder()
|
|
86
|
+
const readable = new ReadableStream({
|
|
87
|
+
async start(controller) {
|
|
88
|
+
try {
|
|
89
|
+
controller.enqueue(encoder.encode(`event: message_start\ndata: ${JSON.stringify({
|
|
90
|
+
type: "message_start",
|
|
91
|
+
message: {
|
|
92
|
+
id: `msg_${Date.now()}`,
|
|
93
|
+
type: "message",
|
|
94
|
+
role: "assistant",
|
|
95
|
+
content: [],
|
|
96
|
+
model: body.model,
|
|
97
|
+
stop_reason: null,
|
|
98
|
+
usage: { input_tokens: 0, output_tokens: 0 }
|
|
99
|
+
}
|
|
100
|
+
})}\n\n`))
|
|
101
|
+
|
|
102
|
+
controller.enqueue(encoder.encode(`event: content_block_start\ndata: ${JSON.stringify({
|
|
103
|
+
type: "content_block_start",
|
|
104
|
+
index: 0,
|
|
105
|
+
content_block: { type: "text", text: "" }
|
|
106
|
+
})}\n\n`))
|
|
107
|
+
|
|
108
|
+
const response = query({
|
|
109
|
+
prompt,
|
|
110
|
+
options: { maxTurns: 1, model }
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
for await (const message of response) {
|
|
114
|
+
if (message.type === "assistant") {
|
|
115
|
+
for (const block of message.message.content) {
|
|
116
|
+
if (block.type === "text") {
|
|
117
|
+
controller.enqueue(encoder.encode(`event: content_block_delta\ndata: ${JSON.stringify({
|
|
118
|
+
type: "content_block_delta",
|
|
119
|
+
index: 0,
|
|
120
|
+
delta: { type: "text_delta", text: block.text }
|
|
121
|
+
})}\n\n`))
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
controller.enqueue(encoder.encode(`event: content_block_stop\ndata: ${JSON.stringify({
|
|
128
|
+
type: "content_block_stop",
|
|
129
|
+
index: 0
|
|
130
|
+
})}\n\n`))
|
|
131
|
+
|
|
132
|
+
controller.enqueue(encoder.encode(`event: message_delta\ndata: ${JSON.stringify({
|
|
133
|
+
type: "message_delta",
|
|
134
|
+
delta: { stop_reason: "end_turn" },
|
|
135
|
+
usage: { output_tokens: 0 }
|
|
136
|
+
})}\n\n`))
|
|
137
|
+
|
|
138
|
+
controller.enqueue(encoder.encode(`event: message_stop\ndata: ${JSON.stringify({
|
|
139
|
+
type: "message_stop"
|
|
140
|
+
})}\n\n`))
|
|
141
|
+
|
|
142
|
+
controller.close()
|
|
143
|
+
} catch (error) {
|
|
144
|
+
claudeLog("proxy.anthropic.error", { error: error instanceof Error ? error.message : String(error) })
|
|
145
|
+
controller.enqueue(encoder.encode(`event: error\ndata: ${JSON.stringify({
|
|
146
|
+
type: "error",
|
|
147
|
+
error: { type: "api_error", message: error instanceof Error ? error.message : "Unknown error" }
|
|
148
|
+
})}\n\n`))
|
|
149
|
+
controller.close()
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
return new Response(readable, {
|
|
155
|
+
headers: {
|
|
156
|
+
"Content-Type": "text/event-stream",
|
|
157
|
+
"Cache-Control": "no-cache",
|
|
158
|
+
Connection: "keep-alive"
|
|
159
|
+
}
|
|
160
|
+
})
|
|
161
|
+
} catch (error) {
|
|
162
|
+
claudeLog("proxy.error", { error: error instanceof Error ? error.message : String(error) })
|
|
163
|
+
return c.json({
|
|
164
|
+
type: "error",
|
|
165
|
+
error: {
|
|
166
|
+
type: "api_error",
|
|
167
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
168
|
+
}
|
|
169
|
+
}, 500)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
app.post("/v1/messages", handleMessages)
|
|
174
|
+
app.post("/messages", handleMessages)
|
|
175
|
+
|
|
176
|
+
return { app, config: finalConfig }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function startProxyServer(config: Partial<ProxyConfig> = {}) {
|
|
180
|
+
const { app, config: finalConfig } = createProxyServer(config)
|
|
181
|
+
|
|
182
|
+
const server = Bun.serve({
|
|
183
|
+
port: finalConfig.port,
|
|
184
|
+
hostname: finalConfig.host,
|
|
185
|
+
fetch: app.fetch
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
console.log(`Claude Max Proxy (Anthropic API) running at http://${finalConfig.host}:${finalConfig.port}`)
|
|
189
|
+
console.log(`\nTo use with OpenCode, run:`)
|
|
190
|
+
console.log(` ANTHROPIC_API_KEY=dummy ANTHROPIC_BASE_URL=http://${finalConfig.host}:${finalConfig.port} opencode`)
|
|
191
|
+
|
|
192
|
+
return server
|
|
193
|
+
}
|