Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
UC_MODEL_MAP optional JSON, e.g. {"claude-opus-4-8":"my-model"}
UC_LOG optional log file path (default stderr)
UC_VERBOSE default 0
UC_BROWSER_UA User-Agent for openai_compat upstreams (default: modern
Chrome UA). Fixes CF 403 "browser_signature_banned" on
providers like crof.ai (same keys work in droid/factory).
Override with env or per-route "headers".

ROUTE SHAPE (config.json "routes" object)
-----------------------------------------
Expand Down Expand Up @@ -146,6 +150,15 @@
DIRECTIVES = {"planner": None, "strip": True} # filled from config in main()
_ROUTE_ALIASES = {} # normalized token -> concrete route id

# BROWSER_UA: browser UA for openai_compat (and classifier) calls.
# CF-protected providers (e.g. crof.ai) ban Python-urllib (error 1010
# "browser_signature_banned"). Matches droid/factory clients.
# Override: UC_BROWSER_UA=... or route "headers".
BROWSER_UA = os.environ.get(
"UC_BROWSER_UA",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
)

try:
UC_MODEL_MAP = json.loads(os.environ.get("UC_MODEL_MAP", "") or "{}")
if not isinstance(UC_MODEL_MAP, dict):
Expand Down Expand Up @@ -1694,7 +1707,8 @@ def _classifier_complete(slot, system_prompt, user_content, timeout):
payload[bk] = _expand_env(bv) if isinstance(bv, str) else bv
data = json.dumps(payload).encode("utf-8")
headers = {"Content-Type": "application/json", "Accept": "application/json",
"Content-Length": str(len(data))}
"Content-Length": str(len(data)), "User-Agent": BROWSER_UA,
"Accept-Language": "en-US,en;q=0.9"}
auth = slot.get("auth")
if auth and auth != "passthrough":
Handler._apply_auth_header(headers, auth)
Expand All @@ -1715,7 +1729,7 @@ def _classifier_complete(slot, system_prompt, user_content, timeout):
"messages": [{"role": "user", "content": user_content}]}
data = json.dumps(payload).encode("utf-8")
headers = {"Content-Type": "application/json", "Content-Length": str(len(data)),
"anthropic-version": "2023-06-01"}
"anthropic-version": "2023-06-01", "User-Agent": BROWSER_UA}
auth = slot.get("auth")
if auth and auth != "passthrough":
Handler._apply_auth_header(headers, auth)
Expand Down Expand Up @@ -1999,6 +2013,7 @@ def _handle_models(self) -> bool:
fwd_headers = {k: v for k, v in self.headers.items()
if k.lower() not in _HOP_BY_HOP}
fwd_headers["Accept-Encoding"] = "identity"
fwd_headers.setdefault("User-Agent", BROWSER_UA)
url = UPSTREAM + self.path
base = {"data": [], "has_more": False, "first_id": None, "last_id": None}
try:
Expand Down Expand Up @@ -2083,6 +2098,7 @@ def _proxy(self, method: str):
for hk, hv in (route.get("headers") or {}).items():
fwd_headers[hk] = hv
fwd_headers["Accept-Encoding"] = "identity"
fwd_headers.setdefault("User-Agent", BROWSER_UA)
if body:
fwd_headers["Content-Length"] = str(len(body))
req = urllib.request.Request(url, data=body or None,
Expand Down Expand Up @@ -2140,6 +2156,8 @@ def _handle_openai_compat(self, body: bytes, route: dict):
"Content-Type": "application/json",
"Accept": "text/event-stream" if want_stream else "application/json",
"Content-Length": str(len(payload)),
"User-Agent": BROWSER_UA,
"Accept-Language": "en-US,en;q=0.9",
}
auth_override = route.get("auth")
if auth_override and auth_override != "passthrough":
Expand Down