ai-cc-router 0.4.2 → 0.4.3

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.
@@ -205,7 +205,9 @@ export function writeAddonScript(target, secret) {
205
205
  src = readFileSync(bundled, "utf-8");
206
206
  }
207
207
  else {
208
- // Inline fallback — minimal addon (only redirects /v1/messages and /v1/models)
208
+ // Inline fallback — minimal addon (handles /v1/messages and /v1/models
209
+ // for both api.anthropic.com traffic and requests already pointed at the
210
+ // CC-Router target, injecting the secret in both cases).
209
211
  src = `
210
212
  import os
211
213
  from mitmproxy import http
@@ -218,19 +220,27 @@ _target_parsed = urlparse(_target)
218
220
  if not _target_parsed.scheme or not _target_parsed.netloc:
219
221
  raise RuntimeError(f"CC_ROUTER_TARGET is not a valid URL: {_target_raw!r}")
220
222
 
223
+ _target_host = (_target_parsed.hostname or "").lower()
224
+ _target_port = _target_parsed.port or (443 if _target_parsed.scheme == "https" else 80)
225
+
221
226
  _secret = os.environ.get("CC_ROUTER_SECRET", "")
222
227
 
223
228
  _REDIRECT_PREFIXES = ("/v1/messages", "/v1/models")
224
229
 
225
230
  def request(flow: http.HTTPFlow) -> None:
226
- if flow.request.pretty_host != "api.anthropic.com":
231
+ host = (flow.request.pretty_host or "").lower()
232
+ port = flow.request.port
233
+ is_anthropic = host == "api.anthropic.com"
234
+ is_target = host == _target_host and port == _target_port
235
+ if not is_anthropic and not is_target:
227
236
  return
228
237
  if not flow.request.path.startswith(_REDIRECT_PREFIXES):
229
238
  return
230
- flow.request.scheme = _target_parsed.scheme
231
- flow.request.host = _target_parsed.hostname or "localhost"
232
- flow.request.port = _target_parsed.port or (443 if _target_parsed.scheme == "https" else 80)
233
- flow.request.headers["host"] = flow.request.host + (f":{flow.request.port}" if flow.request.port not in (80, 443) else "")
239
+ if is_anthropic:
240
+ flow.request.scheme = _target_parsed.scheme
241
+ flow.request.host = _target_host or "localhost"
242
+ flow.request.port = _target_port
243
+ flow.request.headers["host"] = flow.request.host + (f":{flow.request.port}" if flow.request.port not in (80, 443) else "")
234
244
  if _secret:
235
245
  flow.request.headers["x-api-key"] = _secret
236
246
  `.trimStart();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-cc-router",
3
- "version": "0.4.2",
3
+ "version": "0.4.3",
4
4
  "description": "Round-robin proxy for Claude Max OAuth tokens — use multiple Claude Max accounts with Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,14 +1,25 @@
1
- # mitmproxy addon — redirects ONLY /v1/messages traffic to CC-Router.
1
+ # mitmproxy addon — redirects ONLY /v1/messages traffic to CC-Router and
2
+ # injects the proxy secret as an auth header.
2
3
  #
3
- # Claude Desktop sends many types of requests to api.anthropic.com:
4
- # /v1/messages → LLM inference (this is what we redirect)
5
- # /v1/messages/count_tokens → token counting (redirect too)
6
- # /v1/oauth/* → session auth (must NOT redirect)
7
- # /v1/environments/* → bridge/cowork (must NOT redirect)
8
- # /v1/models → model listing (redirect — CC-Router proxies this)
9
- # /api/* → desktop features (must NOT redirect)
4
+ # There are TWO cases to handle:
10
5
  #
11
- # Only /v1/messages* and /v1/models are safe to redirect because CC-Router
6
+ # 1. Requests to api.anthropic.com (Claude Desktop native features)
7
+ # → rewrite host/port to CC-Router target + inject x-api-key
8
+ #
9
+ # 2. Requests already pointed at the CC-Router target host (Claude Code
10
+ # inside Desktop Cowork/Agent mode, which reads ~/.claude/settings.json
11
+ # and goes direct to ANTHROPIC_BASE_URL)
12
+ # → inject x-api-key (no rewrite needed)
13
+ #
14
+ # Claude Desktop sends many types of requests:
15
+ # /v1/messages → LLM inference (redirect + auth)
16
+ # /v1/messages/count_tokens → token counting (redirect + auth)
17
+ # /v1/oauth/* → session auth (must NOT touch)
18
+ # /v1/environments/* → bridge/cowork (must NOT touch)
19
+ # /v1/models → model listing (redirect + auth)
20
+ # /api/* → desktop features (must NOT touch)
21
+ #
22
+ # Only /v1/messages* and /v1/models are safe to touch because CC-Router
12
23
  # injects its own OAuth token. Everything else carries the user's own
13
24
  # session token for features CC-Router doesn't handle.
14
25
 
@@ -24,7 +35,10 @@ _target_parsed = urlparse(_target)
24
35
  if not _target_parsed.scheme or not _target_parsed.netloc:
25
36
  raise RuntimeError(f"CC_ROUTER_TARGET is not a valid URL: {_target_raw!r}")
26
37
 
27
- # Optional proxy secret — when set, injected as x-api-key on redirected requests
38
+ _target_host = (_target_parsed.hostname or "").lower()
39
+ _target_port = _target_parsed.port or (443 if _target_parsed.scheme == "https" else 80)
40
+
41
+ # Optional proxy secret — when set, injected as x-api-key on routed requests
28
42
  _secret = os.environ.get("CC_ROUTER_SECRET", "")
29
43
 
30
44
  # Paths that CC-Router can handle (it injects its own OAuth token)
@@ -35,22 +49,30 @@ _REDIRECT_PREFIXES = (
35
49
 
36
50
 
37
51
  def request(flow: http.HTTPFlow) -> None:
38
- if flow.request.pretty_host != "api.anthropic.com":
52
+ host = (flow.request.pretty_host or "").lower()
53
+ port = flow.request.port
54
+ is_anthropic = host == "api.anthropic.com"
55
+ is_target = host == _target_host and port == _target_port
56
+
57
+ # Not a host we care about — pass through untouched
58
+ if not is_anthropic and not is_target:
39
59
  return
40
60
 
41
- # Only redirect inference and model-listing paths
61
+ # Only touch inference and model-listing paths
42
62
  if not flow.request.path.startswith(_REDIRECT_PREFIXES):
43
63
  return
44
64
 
45
- flow.request.scheme = _target_parsed.scheme
46
- flow.request.host = _target_parsed.hostname or "localhost"
47
- flow.request.port = _target_parsed.port or (443 if _target_parsed.scheme == "https" else 80)
48
- flow.request.headers["host"] = flow.request.host + (
49
- f":{flow.request.port}"
50
- if flow.request.port not in (80, 443)
51
- else ""
52
- )
65
+ # Case 1: rewrite api.anthropic.com CC-Router target
66
+ if is_anthropic:
67
+ flow.request.scheme = _target_parsed.scheme
68
+ flow.request.host = _target_host or "localhost"
69
+ flow.request.port = _target_port
70
+ flow.request.headers["host"] = flow.request.host + (
71
+ f":{flow.request.port}"
72
+ if flow.request.port not in (80, 443)
73
+ else ""
74
+ )
53
75
 
54
- # Authenticate against the proxy if a secret is configured
76
+ # Case 1 and 2: authenticate against the proxy if a secret is configured
55
77
  if _secret:
56
78
  flow.request.headers["x-api-key"] = _secret