From 1f51ef14d05abf16752ad24a72730c1cf2a892b9 Mon Sep 17 00:00:00 2001 From: zapabob Date: Sat, 30 May 2026 01:24:20 +0900 Subject: [PATCH] fix: detect shared OpenCode API key --- CHANGELOG.md | 4 ++++ api/config.py | 5 +++-- tests/test_opencode_providers.py | 32 +++++++++++++++++++++++++++++--- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f81547f..63a3292f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## [Unreleased] +### Fixed + +- Detect a shared `OPENCODE_API_KEY` as enabling both OpenCode Zen and OpenCode Go provider groups, matching Hermes Agent bridge environments that expose one OpenCode credential. + ## [v0.51.159] — 2026-05-29 — Release EE (stage-batch41 — 5-PR low-risk cleanup: Gateway tool-progress forwarding + shutdown diagnostics + CLI snippet-limit parity + numpad-Enter submit + sync-chat notes guardrail) ### Changed diff --git a/api/config.py b/api/config.py index e8b37b81..85c47dde 100644 --- a/api/config.py +++ b/api/config.py @@ -3437,6 +3437,7 @@ def get_available_models() -> dict: "XIAOMI_API_KEY", "OPENCODE_ZEN_API_KEY", "OPENCODE_GO_API_KEY", + "OPENCODE_API_KEY", "MINIMAX_API_KEY", "MINIMAX_CN_API_KEY", "XAI_API_KEY", @@ -3478,9 +3479,9 @@ def get_available_models() -> dict: detected_providers.add("x-ai") if all_env.get("MISTRAL_API_KEY"): detected_providers.add("mistralai") - if all_env.get("OPENCODE_ZEN_API_KEY"): + if all_env.get("OPENCODE_ZEN_API_KEY") or all_env.get("OPENCODE_API_KEY"): detected_providers.add("opencode-zen") - if all_env.get("OPENCODE_GO_API_KEY"): + if all_env.get("OPENCODE_GO_API_KEY") or all_env.get("OPENCODE_API_KEY"): detected_providers.add("opencode-go") # AWS Bedrock uses IAM credentials rather than a single API key. # Detect when both access key and secret are available (#2720). diff --git a/tests/test_opencode_providers.py b/tests/test_opencode_providers.py index f7f6e720..7f111016 100644 --- a/tests/test_opencode_providers.py +++ b/tests/test_opencode_providers.py @@ -3,7 +3,6 @@ Tests for OpenCode Zen and OpenCode Go provider support. Verifies provider registration in display/model catalogs and env-var fallback detection. """ -import os import sys import types import pytest @@ -96,6 +95,29 @@ def test_opencode_go_detected_via_env_key(monkeypatch): _models_with_env_key(monkeypatch, "OPENCODE_GO_API_KEY", "OpenCode Go") +def test_shared_opencode_api_key_detects_zen_and_go(monkeypatch): + """A shared OpenCode bridge key should enable both OpenCode surfaces.""" + fake_mod = types.ModuleType("hermes_cli.models") + fake_mod.list_available_providers = None + monkeypatch.setitem(sys.modules, "hermes_cli.models", fake_mod) + monkeypatch.delattr(fake_mod, "list_available_providers") + + old_cfg = dict(config.cfg) + config.cfg["model"] = {} + config.cfg.pop("custom_providers", None) + monkeypatch.delenv("OPENCODE_ZEN_API_KEY", raising=False) + monkeypatch.delenv("OPENCODE_GO_API_KEY", raising=False) + monkeypatch.setenv("OPENCODE_API_KEY", "test-key") + try: + result = config.get_available_models() + providers = {g["provider"] for g in result["groups"]} + assert "OpenCode Zen" in providers + assert "OpenCode Go" in providers + finally: + config.cfg.clear() + config.cfg.update(old_cfg) + + def test_openai_codex_model_catalog_includes_gpt54(): """openai-codex catalog must include gpt-5.4 and the standard Codex lineup.""" assert "openai-codex" in config._PROVIDER_MODELS @@ -117,7 +139,9 @@ def test_live_models_handler_delegates_to_provider_model_ids(): rather than maintain its own per-provider fetch logic. """ import pathlib - routes_src = (pathlib.Path(__file__).parent.parent / "api" / "routes.py").read_text() + routes_src = ( + pathlib.Path(__file__).parent.parent / "api" / "routes.py" + ).read_text(encoding="utf-8") assert "provider_model_ids" in routes_src, ( "_handle_live_models must call hermes_cli.models.provider_model_ids() " "to delegate all provider-specific live-fetch logic to the agent" @@ -139,7 +163,9 @@ def test_live_models_ui_no_longer_skips_any_provider(): handles them all (with graceful fallback to static lists). """ import pathlib - ui_src = (pathlib.Path(__file__).parent.parent / "static" / "ui.js").read_text() + ui_src = ( + pathlib.Path(__file__).parent.parent / "static" / "ui.js" + ).read_text(encoding="utf-8") # The old exclusion list must be gone assert "includes(provider)" not in ui_src or "anthropic" not in ui_src[:ui_src.find("includes(provider)")+100], ( "_fetchLiveModels must not skip anthropic, google, or gemini — "