diff --git a/codex-launcher_3.11.12_all.deb b/codex-launcher_3.11.12_all.deb index bcbda64..47c4f64 100644 Binary files a/codex-launcher_3.11.12_all.deb and b/codex-launcher_3.11.12_all.deb differ diff --git a/src/translate-proxy.py b/src/translate-proxy.py index fff710d..20a6794 100755 --- a/src/translate-proxy.py +++ b/src/translate-proxy.py @@ -320,9 +320,10 @@ _active_requests = {} _active_requests_lock = threading.Lock() _pool = uuid.uuid4().hex[:8] -_antigravity_version = "1.18.3" +_antigravity_version = "2.0.1" _antigravity_version_checked = 0 _antigravity_version_lock = threading.Lock() +_antigravity_version_validated = False _last_user_urls = collections.deque(maxlen=20) _conn_pool_lock = threading.Lock() @@ -798,49 +799,137 @@ _ANTIGRAVITY_LOOP_TRACKER_LOCK = threading.Lock() def _antigravity_loop_key(session_id): return f"ag:{session_id}" +def _validate_antigravity_version(version, access_token=None, project_id=None): + if not version or not re.match(r"^\d+\.\d+\.\d+$", version): + return False + try: + if not access_token: + access_token = _refresh_oauth_token() + if not project_id: + token_path = os.path.join(os.path.expanduser("~"), ".cache", "codex-proxy", "google-antigravity-oauth-token.json") + try: + with open(token_path) as f: + project_id = json.load(f).get("project_id", "") + except Exception: + pass + if not access_token or not project_id: + return True + import platform as _plat + _os_name = _plat.system().lower() + _os_arch = _plat.machine().lower().replace("x86_64", "x64").replace("aarch64", "arm64") + ua = f"antigravity/{version} {_os_name}/{_os_arch}" + body = { + "project": project_id, + "model": "gemini-3-flash", + "requestType": "agent", + "userAgent": ua, + "requestId": f"probe-{uuid.uuid4().hex[:8]}", + "request": { + "contents": [{"role": "user", "parts": [{"text": "hi"}]}], + "sessionId": f"probe{int(time.time()*1000)}", + "safetySettings": [ + {"category": "HARM_CATEGORY_HARASSMENT", "threshold": "OFF"}, + {"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "OFF"}, + {"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "OFF"}, + {"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "OFF"}, + {"category": "HARM_CATEGORY_CIVIC_INTEGRITY", "threshold": "OFF"}, + ], + "generationConfig": {"maxOutputTokens": 32, "stopSequences": ["\n\nHuman:", "[DONE]"]}, + } + } + url = "https://daily-cloudcode-pa.googleapis.com/v1internal:streamGenerateContent?alt=sse" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {access_token}", + "User-Agent": ua, + } + req = urllib.request.Request(url, data=json.dumps(body).encode(), headers=headers) + resp = urllib.request.urlopen(req, timeout=15) + data = resp.read().decode() + if "no longer supported" in data.lower(): + print(f"[antigravity-version] version {version} rejected (deprecated)", file=sys.stderr) + return False + return True + except urllib.error.HTTPError as e: + if e.code == 404: + print(f"[antigravity-version] version {version} rejected (404)", file=sys.stderr) + return False + return True + except Exception as e: + print(f"[antigravity-version] probe error for {version}: {e}", file=sys.stderr) + return True + def _fetch_antigravity_version(): cache_path = os.path.join(os.path.expanduser("~"), ".cache", "codex-proxy", "antigravity-version.json") try: with open(cache_path) as f: cached = json.load(f) - if cached.get("version") and cached.get("checked_at", 0) > time.time() - 6 * 3600: + if cached.get("version") and cached.get("validated") and cached.get("checked_at", 0) > time.time() - 6 * 3600: return cached["version"] except Exception: pass - urls = [ + + access_token = None + project_id = None + try: + access_token = _refresh_oauth_token() + token_path = os.path.join(os.path.expanduser("~"), ".cache", "codex-proxy", "google-antigravity-oauth-token.json") + with open(token_path) as f: + project_id = json.load(f).get("project_id", "") + except Exception: + pass + + sources = [ ("https://antigravity-auto-updater-974169037036.us-central1.run.app", None), ("https://antigravity.google/changelog", 5000), ] - for url, limit in urls: + + candidates = [] + for url, limit in sources: try: req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) resp = urllib.request.urlopen(req, timeout=5) text = resp.read().decode(errors="replace") if limit: text = text[:limit] - m = re.search(r"\d+\.\d+\.\d+", text) - if m: - version = m.group(0) - try: - os.makedirs(os.path.dirname(cache_path), exist_ok=True) - with open(cache_path, "w", encoding="utf-8") as f: - json.dump({"version": version, "checked_at": time.time()}, f) - except Exception: - pass - return version + for m in re.finditer(r"\d+\.\d+\.\d+", text): + ver = m.group(0) + if ver not in candidates: + candidates.append(ver) except Exception: pass - return _antigravity_version + + for ver in candidates: + if _validate_antigravity_version(ver, access_token, project_id): + print(f"[antigravity-version] fetched version {ver} validated", file=sys.stderr) + try: + os.makedirs(os.path.dirname(cache_path), exist_ok=True) + with open(cache_path, "w", encoding="utf-8") as f: + json.dump({"version": ver, "validated": True, "checked_at": time.time()}, f) + except Exception: + pass + return ver + + fallback = "2.0.1" + print(f"[antigravity-version] all candidates failed, using fallback {fallback}", file=sys.stderr) + try: + os.makedirs(os.path.dirname(cache_path), exist_ok=True) + with open(cache_path, "w", encoding="utf-8") as f: + json.dump({"version": fallback, "validated": False, "checked_at": time.time()}, f) + except Exception: + pass + return fallback def _ensure_antigravity_version(): - global _antigravity_version, _antigravity_version_checked - if time.time() - _antigravity_version_checked < 6 * 3600: + global _antigravity_version, _antigravity_version_checked, _antigravity_version_validated + if _antigravity_version_validated and time.time() - _antigravity_version_checked < 6 * 3600: return _antigravity_version with _antigravity_version_lock: - if time.time() - _antigravity_version_checked < 6 * 3600: + if _antigravity_version_validated and time.time() - _antigravity_version_checked < 6 * 3600: return _antigravity_version _antigravity_version = _fetch_antigravity_version() _antigravity_version_checked = time.time() + _antigravity_version_validated = True return _antigravity_version _antigravity_client_version = "1.110.0" @@ -5643,10 +5732,7 @@ class Handler(http.server.BaseHTTPRequestHandler): _os_name = _plat.system().lower() _os_arch = _plat.machine().lower().replace("x86_64", "x64").replace("aarch64", "arm64") _fetched_ver = _ensure_antigravity_version() - _fallback_ver = "1.15.8" - _versions = [_fetched_ver, _fallback_ver] if _fetched_ver != _fallback_ver else [_fallback_ver] - _version_404s = set() - _ag_ua = f"antigravity/{_versions[0]} {_os_name}/{_os_arch}" + _ag_ua = f"antigravity/{_fetched_ver} {_os_name}/{_os_arch}" headers = { "Content-Type": "application/json", "Authorization": f"Bearer {access_token}", @@ -5739,40 +5825,42 @@ class Handler(http.server.BaseHTTPRequestHandler): return self.send_json(502, {"error": {"type": "proxy_error", "message": str(e)}}) continue - if _all_404 and upstream is None and len(_versions) > 1: - _fallback_ver = _versions[1] - print(f"[{self._session_id}] [antigravity-v2] all 404, retrying with fallback version {_fallback_ver}", file=sys.stderr) - _ag_ua_fb = f"antigravity/{_fallback_ver} {_os_name}/{_os_arch}" - headers["User-Agent"] = _ag_ua_fb - wrapped["userAgent"] = _ag_ua_fb - body_b = json.dumps(wrapped).encode() - for ep in ordered: - action = "streamGenerateContent" if stream else "generateContent" - url_suffix = f"v1internal:{action}?alt=sse" if stream else f"v1internal:{action}" - target = f"{ep}/{url_suffix}" - req = urllib.request.Request(target, data=body_b, headers=headers) - try: - upstream = urllib.request.urlopen(req, timeout=_upstream_timeout(body, stream)) - chosen_ep = ep - with _antigravity_endpoint_lock: - _antigravity_preferred_endpoint = ep - break - except urllib.error.HTTPError as e: - err_body = e.read().decode() - err_class = _classify_antigravity_error(e.code, err_body) - print(f"[{self._session_id}] [antigravity-v2-fallback] {ep.replace('https://','')} {e.code} class={err_class}", file=sys.stderr) - if e.code == 400: - return self.send_json(e.code, {"error": {"type": "upstream_error", "message": _sanitize_err_body(err_body)}}) - if err_class in ("auth_permanent", "service_disabled", "forbidden", "account_banned", "validation_required"): - return self.send_json(e.code, {"error": {"type": "upstream_error", "message": _sanitize_err_body(err_body)}}) - if ep == ordered[-1]: - return self.send_json(e.code, {"error": {"type": "upstream_error", "message": _sanitize_err_body(err_body)}}) - continue - except Exception as e: - print(f"[{self._session_id}] [antigravity-v2-fallback] {ep.replace('https://','')} conn failed: {e}", file=sys.stderr) - if ep == ordered[-1]: - return self.send_json(502, {"error": {"type": "proxy_error", "message": str(e)}}) - continue + if _all_404 and upstream is None: + print(f"[{self._session_id}] [antigravity-v2] all endpoints 404, invalidating version cache and re-fetching", file=sys.stderr) + global _antigravity_version_validated + with _antigravity_version_lock: + _antigravity_version_validated = False + _antigravity_version_checked = 0 + _new_ver = _ensure_antigravity_version() + if _new_ver != _fetched_ver: + print(f"[{self._session_id}] [antigravity-v2] version changed {_fetched_ver} -> {_new_ver}, retrying", file=sys.stderr) + _ag_ua_new = f"antigravity/{_new_ver} {_os_name}/{_os_arch}" + headers["User-Agent"] = _ag_ua_new + wrapped["userAgent"] = _ag_ua_new + body_b = json.dumps(wrapped).encode() + for ep in ordered: + action = "streamGenerateContent" if stream else "generateContent" + url_suffix = f"v1internal:{action}?alt=sse" if stream else f"v1internal:{action}" + target = f"{ep}/{url_suffix}" + req = urllib.request.Request(target, data=body_b, headers=headers) + try: + upstream = urllib.request.urlopen(req, timeout=_upstream_timeout(body, stream)) + chosen_ep = ep + with _antigravity_endpoint_lock: + _antigravity_preferred_endpoint = ep + break + except urllib.error.HTTPError as e: + err_body = e.read().decode() + print(f"[{self._session_id}] [antigravity-v2-retry] {ep.replace('https://','')} {e.code}", file=sys.stderr) + if e.code == 400: + return self.send_json(e.code, {"error": {"type": "upstream_error", "message": _sanitize_err_body(err_body)}}) + if ep == ordered[-1]: + return self.send_json(e.code, {"error": {"type": "upstream_error", "message": _sanitize_err_body(err_body)}}) + continue + except Exception as e: + if ep == ordered[-1]: + return self.send_json(502, {"error": {"type": "proxy_error", "message": str(e)}}) + continue if upstream is None: return self.send_json(502, {"error": {"type": "proxy_error", "message": "All endpoints failed"}})