From 9c57a7c607d129072a29b14705ad2b6a556e095b Mon Sep 17 00:00:00 2001 From: Hermes Team Date: Tue, 1 Sep 2026 03:05:44 +0700 Subject: [PATCH] =?UTF-8?q?fix(antigravity):=20=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=87=D0=B8=D0=B9=20=D0=B0=D0=BA=D0=BA=D0=B0=D1=83=D0=BD=D1=82?= =?UTF-8?q?=20=D0=BF=D0=B0=D0=B4=D0=B0=D0=BB=20=D0=B8=D0=B7-=D0=B7=D0=B0?= =?UTF-8?q?=20=D0=BD=D0=B5=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D1=83=D1=80=D0=BE=D0=B2=D0=BD=D1=8F=20=D1=83=D1=81?= =?UTF-8?q?=D0=B8=D0=BB=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Новое сообщение об отказе agy показало причину. У рабочего аккаунта victor.trushenko@gmail.com каталог получался (11 моделей), а вызов падал: invalid model selection (--model "gemini-3.7-flash" --effort ""): gemini-3.7-flash requires --effort (available: low, medium, high) Каталог agy отдаёт идентификаторы с уровнем усилия внутри имени (gemini-3.7-flash-high, -medium, -low), а в профиле хранится голое имя. Вызов уходил с пустым --effort, и agy отказывался работать. Теперь уровень определяется: если он зашит в имени, отделяется от модели; если нет — берётся из каталога, предпочтительно medium. Проверено, что claude-sonnet-4-6 при этом не разбирается ошибочно: -6 уровнем не является. Отдельно: восемь профилей Antigravity отвечают «Please sign in to view available models» при HOME=~/.hermes/agy_profiles/. Это пустые заготовки без учётных данных, а не поломка: agy запускается с подменённым HOME ради изоляции аккаунтов, и в незаполненном каталоге ключей нет. Подписочные аккаунты Codex и Claude: отказ по API-ключу теперь объясняет, что такие аккаунты подключаются входом по ссылке, а не ключом. Проверка обращается к каталогу моделей, где платформенному ключу нужны права api.model.read, и подписочный токен там получает 401 или 403 при исправном аккаунте. 610 passed, ruff clean, релизный гейт 10/10. Co-Authored-By: Claude Opus 5 --- src/antigravity_provider/agy_subprocess.py | 25 +++++++++++++++++++ .../router/connection_preflight.py | 18 ++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/antigravity_provider/agy_subprocess.py b/src/antigravity_provider/agy_subprocess.py index d2d42e8..1769767 100644 --- a/src/antigravity_provider/agy_subprocess.py +++ b/src/antigravity_provider/agy_subprocess.py @@ -756,6 +756,31 @@ def agy_generate( "--disable-slash-commands", "--print-timeout", f"{timeout}s", ] + # Каталог agy отдаёт идентификаторы с уровнем усилия: gemini-3.7-flash-high, + # -medium, -low. В профиле же хранится голое имя, и вызов уходил с пустым + # --effort: agy отвечал «gemini-3.7-flash requires --effort (available: low, + # medium, high)» и работа не начиналась. + if agy_model and not agy_effort: + base, _, tail = str(agy_model).rpartition("-") + if base and tail in ("low", "medium", "high"): + # Уровень зашит в самом имени — отделяем его. + agy_model, agy_effort = base, tail + else: + known = _AGY_EFFORT_MAP.get(agy_model) or set() + if known: + # Берём средний уровень, если он есть: он и по названию средний, + # и по расходу квоты. Иначе — любой доступный, по порядку. + for candidate in ("medium", "high", "low"): + if candidate in known: + agy_effort = candidate + break + if not agy_effort: + agy_effort = sorted(known)[0] + logger.info( + "agy_generate: у модели %s не задан уровень усилия, выбран %s из %s", + agy_model, agy_effort, sorted(known), + ) + if agy_model: cmd.extend(["--model", agy_model]) if agy_effort: diff --git a/src/antigravity_provider/router/connection_preflight.py b/src/antigravity_provider/router/connection_preflight.py index 951b716..d4ebb21 100644 --- a/src/antigravity_provider/router/connection_preflight.py +++ b/src/antigravity_provider/router/connection_preflight.py @@ -30,6 +30,20 @@ def validate_connection(provider, token="", base_url="", preferred_model=""): raise ValueError("Для этого провайдера используйте вход через авторизацию") if provider not in ("local", "vllm", "ollama") and not token: raise ValueError("Не указан API-ключ") + + # Подписочные аккаунты Codex (ChatGPT) и Claude работают по OAuth, а не + # по платформенному ключу. Проверка ниже обращается к каталогу моделей, + # где платформенному ключу нужны права api.model.read: подписочный + # токен там получает 403 или 401, хотя аккаунт исправен и в Hermes + # работает. Предупреждаем об этом заранее, чтобы отказ не выглядел + # поломкой аккаунта. + subscription_note = "" + if provider in ("openai-codex", "codex", "claude", "anthropic"): + subscription_note = ( + " Если это аккаунт по подписке (ChatGPT Plus/Pro или Claude Max), " + "ключ здесь не подойдёт: такие аккаунты подключаются входом по ссылке " + "на предыдущем шаге мастера, а не API-ключом." + ) headers = {"Content-Type": "application/json"} if provider == "claude": headers.update({"x-api-key": token, "anthropic-version": "2023-06-01"}) @@ -139,7 +153,9 @@ def validate_connection(provider, token="", base_url="", preferred_model=""): "data": {"models": []}, } if isinstance(exc, urllib.error.HTTPError): - reason = exc.reason or 'провайдер отклонил запрос' + reason = (exc.reason or 'провайдер отклонил запрос') + if exc.code in (401, 403) and subscription_note: + reason = str(reason) + subscription_note try: body = json.loads(exc.read(4096).decode('utf-8', errors='replace')) detail = body.get('error') or body.get('detail')