diff --git a/src/antigravity_provider/router/adapters/opencode_adapter.py b/src/antigravity_provider/router/adapters/opencode_adapter.py index 36ab490..628de7c 100644 --- a/src/antigravity_provider/router/adapters/opencode_adapter.py +++ b/src/antigravity_provider/router/adapters/opencode_adapter.py @@ -37,7 +37,27 @@ class OpenCodeGoAdapter(BaseProviderAdapter): if "api_key" in profile.auth_config and profile.auth_config["api_key"]: return profile.auth_config["api_key"] - # 2. Account-specific environment variable (e.g. OPENCODE_GO_KEY_OPENGO_1, OPENCODE_GO_KEY_1) + # 2. Профиль в хранилище учётных данных. + # Мастер подключения сохраняет ключ именно сюда, а адаптер его не читал: + # смотрел только YAML-конфиг и переменные окружения. Из-за этого любой + # аккаунт OpenCode, подключённый через интерфейс, не работал никогда — + # маршрутизация падала с «No API key found», хотя ключ лежал на диске. + # Остальные адаптеры (grok, claude, codex) читают профиль; этот — нет. + try: + from ..profile_manager import ProfileAuthManager + + creds = ProfileAuthManager.load_profile_auth("opencode-go", profile.profile_id) + if creds: + for key in ("api_key", "access_token", "token"): + value = creds.get(key) + if isinstance(value, dict): + value = value.get("api_key") or value.get("access_token") + if isinstance(value, str) and value.strip(): + return value.strip() + except Exception: + pass + + # 3. Account-specific environment variable (e.g. OPENCODE_GO_KEY_OPENGO_1, OPENCODE_GO_KEY_1) suffix = profile.profile_id.upper().replace("-", "_") for candidate in ( f"OPENCODE_GO_KEY_{suffix}", @@ -56,7 +76,7 @@ class OpenCodeGoAdapter(BaseProviderAdapter): if candidate in os.environ and os.environ[candidate].strip(): return os.environ[candidate].strip() - # 3. Global OpenCode Go keys + # 4. Global OpenCode Go keys for global_env in ("OPENCODE_GO_API_KEY", "OPENCODE_ZEN_API_KEY", "OPENCODE_API_KEY"): if global_env in os.environ and os.environ[global_env].strip(): return os.environ[global_env].strip() diff --git a/tests/test_opencode_credentials.py b/tests/test_opencode_credentials.py new file mode 100644 index 0000000..30ca557 --- /dev/null +++ b/tests/test_opencode_credentials.py @@ -0,0 +1,66 @@ +"""Адаптер OpenCode Go обязан читать ключ из хранилища профилей. + +Дефект: мастер подключения сохраняет ключ через ProfileAuthManager, а +_resolve_api_key смотрел только в auth_config из YAML и в переменные +окружения. Аккаунт, подключённый через интерфейс, не работал никогда — +маршрутизация падала с «No API key found», хотя ключ лежал на диске, а +карточка показывала «Работает». + +Остальные адаптеры (grok, claude, codex) хранилище читают; этот — нет. +""" + +from __future__ import annotations + +from types import SimpleNamespace + +import pytest + +from antigravity_provider.router.adapters.opencode_adapter import OpenCodeGoAdapter + + +@pytest.fixture +def profile(): + return SimpleNamespace(profile_id="opengo-1", account_id="acc-1", auth_config={}) + + +def test_api_key_is_read_from_profile_store(monkeypatch, profile): + import antigravity_provider.router.profile_manager as pm + + monkeypatch.setattr( + pm.ProfileAuthManager, + "load_profile_auth", + staticmethod(lambda provider, pid: {"api_key": "stored-key-123", "auth_mode": "api_key"}), + ) + for var in ("OPENCODE_GO_API_KEY", "OPENCODE_ZEN_API_KEY", "OPENCODE_API_KEY"): + monkeypatch.delenv(var, raising=False) + + assert OpenCodeGoAdapter()._resolve_api_key(profile) == "stored-key-123" + + +def test_health_check_passes_when_only_profile_store_has_key(monkeypatch, profile): + """Именно это расхождение показывало «Работает» на нерабочем аккаунте.""" + import antigravity_provider.router.profile_manager as pm + + monkeypatch.setattr( + pm.ProfileAuthManager, + "load_profile_auth", + staticmethod(lambda provider, pid: {"api_key": "stored-key-123"}), + ) + for var in ("OPENCODE_GO_API_KEY", "OPENCODE_ZEN_API_KEY", "OPENCODE_API_KEY"): + monkeypatch.delenv(var, raising=False) + + assert OpenCodeGoAdapter().health_check(profile) is True + + +def test_missing_credentials_still_report_no_key(monkeypatch, profile): + """Пустое хранилище не должно давать ложноположительный результат.""" + import antigravity_provider.router.profile_manager as pm + + monkeypatch.setattr( + pm.ProfileAuthManager, "load_profile_auth", staticmethod(lambda provider, pid: {}) + ) + for var in ("OPENCODE_GO_API_KEY", "OPENCODE_ZEN_API_KEY", "OPENCODE_API_KEY"): + monkeypatch.delenv(var, raising=False) + + assert OpenCodeGoAdapter()._resolve_api_key(profile) is None + assert OpenCodeGoAdapter().health_check(profile) is False