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 (
|
|
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
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
package/src/interceptor/addon.py
CHANGED
|
@@ -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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
61
|
+
# Only touch inference and model-listing paths
|
|
42
62
|
if not flow.request.path.startswith(_REDIRECT_PREFIXES):
|
|
43
63
|
return
|
|
44
64
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
#
|
|
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
|