diff --git a/src/antigravity_provider/agy_subprocess.py b/src/antigravity_provider/agy_subprocess.py index bf59474..2961a29 100644 --- a/src/antigravity_provider/agy_subprocess.py +++ b/src/antigravity_provider/agy_subprocess.py @@ -46,12 +46,32 @@ def _find_agy_exe() -> str: if env and Path(env).is_file(): return env - # 2. Standard location based on hermes home parent + # 2. Стандартные места установки. + # + # Раньше проверялась только раскладка Windows (%LOCALAPPDATA%/agy/bin), а в + # Linux agy ставится в ~/.local/bin. Хаб запускается с урезанным окружением, + # где этого каталога в PATH нет, и вход в Antigravity падал с «agy executable + # not found» при установленной и работающей утилите. from antigravity_provider.paths import get_hermes_home + exe_name = "agy.exe" if os.name == "nt" else "agy" - candidate = get_hermes_home().parent / "agy" / "bin" / exe_name - if candidate.is_file(): - return str(candidate) + candidates = [ + get_hermes_home().parent / "agy" / "bin" / exe_name, + Path.home() / ".local" / "bin" / exe_name, + Path("/usr/local/bin") / exe_name, + Path("/usr/bin") / exe_name, + Path("/snap/bin") / exe_name, + ] + checked = [] + for candidate in candidates: + checked.append(str(candidate)) + try: + if candidate.is_file(): + return str(candidate) + except OSError as exc: + # Каталог может быть закрыт правами: это «не смогли проверить», + # а не «файла нет». + checked[-1] = f"{candidate} (нет доступа: {exc.strerror or exc})" # 3. PATH found = shutil.which("agy") or shutil.which("agy.exe") @@ -59,8 +79,9 @@ def _find_agy_exe() -> str: return found raise FileNotFoundError( - "agy executable not found. Set the AGY_EXE_PATH environment " - "variable, install agy, or ensure it is on PATH." + "Утилита agy не найдена. Проверено: " + + "; ".join(checked) + + "; и PATH процесса. Задайте путь переменной AGY_EXE_PATH либо установите agy." ) diff --git a/src/antigravity_provider/router/connection_preflight.py b/src/antigravity_provider/router/connection_preflight.py index e1baee9..951b716 100644 --- a/src/antigravity_provider/router/connection_preflight.py +++ b/src/antigravity_provider/router/connection_preflight.py @@ -97,6 +97,41 @@ def validate_connection(provider, token="", base_url="", preferred_model=""): except Exception as exc: exc_str = str(exc) exc_lower = exc_str.lower() + # У Ollama Cloud локального сервера нет вовсе: там только ключ. Если + # ключ задан, а локальный адрес не отвечает, это не отказ подключения — + # аккаунт облачный. Проверяем его по общедоступному каталогу и + # принимаем, честно сказав, что локальные модели недоступны. + if provider == "ollama" and token and any( + k in exc_lower for k in ("connection refused", "winerror 10061", "errno 111", "refused", "failed to connect", "target machine actively refused") + ): + try: + cloud = urllib.request.Request( + "https://ollama.com/api/tags", + headers={"Accept": "application/json", "Authorization": f"Bearer {token}"}, + ) + with urllib.request.urlopen(cloud, timeout=15) as response: + payload = json.load(response) + cloud_models = sorted({ + m.get("name") for m in (payload.get("models") or []) + if isinstance(m, dict) and isinstance(m.get("name"), str) + }) + return { + "ok": True, + "message": ( + f"Облачный аккаунт Ollama принят. Моделей в каталоге: {len(cloud_models)}. " + f"Локальный сервер по адресу {base_url} не отвечает — локальные модели недоступны." + ), + "data": {"models": cloud_models, "base_url": base_url, "cloud_only": True}, + } + except Exception as cloud_exc: + return { + "ok": False, + "message": ( + f"Локальный сервер {base_url} не отвечает, и облачный каталог Ollama недоступен: " + f"{str(cloud_exc)[:160]}" + ), + "data": {"models": []}, + } if provider == "ollama" and any(k in exc_lower for k in ("connection refused", "winerror 10061", "errno 111", "111", "refused", "failed to connect", "target machine actively refused")): return { "ok": False,