fix(models): gate OpenCode Go discovery by core version
This commit is contained in:
1 parent
df345af9eb
commit
dde61be8b2
2 files changed
+76
-5
No files matched your search
@@ -8388,6 +8388,26 @@ def _read_live_provider_model_ids(provider_id: str) -> list[str]:
|
||||
return []
|
||||
|
||||
|
||||
def _hermes_cli_supports_opencode_go_live_catalog() -> bool:
|
||||
"""Whether the installed Agent has Go-specific model discovery.
|
||||
|
||||
Hermes core versions before 0.20.5 route ``opencode-go`` through a
|
||||
generic public catalog. That lookup can return a convincing non-empty
|
||||
list containing models the Go relay rejects with 404, so absence or an
|
||||
unparseable/prerelease version must fail closed to WebUI's static Go list.
|
||||
"""
|
||||
try:
|
||||
import hermes_cli
|
||||
|
||||
version = str(getattr(hermes_cli, "__version__", "")).strip()
|
||||
except Exception:
|
||||
return False
|
||||
match = re.fullmatch(r"v?(\d+)\.(\d+)\.(\d+)", version)
|
||||
if not match:
|
||||
return False
|
||||
return tuple(int(part) for part in match.groups()) >= (0, 20, 5)
|
||||
|
||||
|
||||
def _models_from_live_provider_ids(provider_id: str, live_ids: list[str]) -> list[dict]:
|
||||
"""Convert Hermes CLI model ids into WebUI picker model entries."""
|
||||
formatter = _format_ollama_label if provider_id in ("ollama", "ollama-cloud") else None
|
||||
@@ -9785,6 +9805,14 @@ def get_available_models(*, prefer_cache: bool = False, force_refresh: bool = Fa
|
||||
if not raw_models:
|
||||
if pid == "moa":
|
||||
raw_models = _moa_preset_models_from_config(cfg)
|
||||
elif (
|
||||
pid == "opencode-go"
|
||||
and not _hermes_cli_supports_opencode_go_live_catalog()
|
||||
):
|
||||
# Before core v0.20.5 this resolver returned the
|
||||
# generic public catalog, including models that
|
||||
# 404 on the Go tier. Use the curated Go fallback.
|
||||
raw_models = []
|
||||
else:
|
||||
raw_models = _models_from_live_provider_ids(
|
||||
pid,
|
||||
|
||||
@@ -32,10 +32,18 @@ def _scrub_provider_env(monkeypatch):
|
||||
monkeypatch.delenv(name, raising=False)
|
||||
|
||||
|
||||
def _install_fake_hermes_cli(monkeypatch, *, provider_id: str, live_ids, raise_on_lookup: bool = False):
|
||||
def _install_fake_hermes_cli(
|
||||
monkeypatch,
|
||||
*,
|
||||
provider_id: str,
|
||||
live_ids,
|
||||
core_version: str = "0.20.5",
|
||||
raise_on_lookup: bool = False,
|
||||
):
|
||||
"""Install a hermes_cli stub that reports one authenticated provider."""
|
||||
fake_pkg = types.ModuleType("hermes_cli")
|
||||
fake_pkg.__path__ = []
|
||||
fake_pkg.__version__ = core_version
|
||||
|
||||
fake_models = types.ModuleType("hermes_cli.models")
|
||||
fake_models.list_available_providers = lambda: [
|
||||
@@ -123,6 +131,26 @@ def test_opencode_go_probes_live_catalog(monkeypatch, tmp_path):
|
||||
assert all(m.get("label") for m in group["models"])
|
||||
|
||||
|
||||
def test_opencode_go_old_core_ignores_nonempty_generic_catalog(monkeypatch, tmp_path):
|
||||
"""Pre-v0.20.5 cores must not leak their generic public catalog into Go."""
|
||||
_scrub_provider_env(monkeypatch)
|
||||
calls = _install_fake_hermes_cli(
|
||||
monkeypatch,
|
||||
provider_id="opencode-go",
|
||||
live_ids=["go-ineligible-old-core-sentinel"],
|
||||
core_version="0.20.4",
|
||||
)
|
||||
_configure(monkeypatch, tmp_path, provider="opencode-go")
|
||||
|
||||
result = config.get_available_models()
|
||||
group = _provider_group(result, "opencode-go")
|
||||
|
||||
assert calls == []
|
||||
ids = _ids(group)
|
||||
assert "go-ineligible-old-core-sentinel" not in ids
|
||||
assert "kimi-k3" in ids
|
||||
|
||||
|
||||
def test_opencode_go_static_fallback_when_probe_fails(monkeypatch, tmp_path):
|
||||
"""Offline / CLI failure must fall back to the curated static list."""
|
||||
_scrub_provider_env(monkeypatch)
|
||||
@@ -168,9 +196,9 @@ def test_opencode_go_config_allowlist_still_wins(monkeypatch, tmp_path):
|
||||
|
||||
|
||||
# ── Static fallback list contract ─────────────────────────────────────
|
||||
# The remaining tests pin the offline fallback list itself. It is an exact
|
||||
# mirror of Hermes core's curated ``opencode-go`` list
|
||||
# (hermes_cli/models.py) — core owns the sync duty against the live
|
||||
# The remaining tests pin the offline fallback list itself to Hermes core's
|
||||
# v0.20.5 release contract (commit fcbd1076, tag v2026.8.19). Core owns the
|
||||
# sync duty against the live
|
||||
# https://opencode.ai/zen/go/v1/models endpoint and
|
||||
# https://opencode.ai/docs/go/, and WebUI only mirrors. This catches drift
|
||||
# in either direction: ids core added (kimi-k3, gpt-5.6-luna, glm-5.3,
|
||||
@@ -228,11 +256,26 @@ def _opencode_go_static_models():
|
||||
raise AssertionError("_PROVIDER_MODELS assignment not found")
|
||||
|
||||
|
||||
def test_opencode_go_static_models_match_core_curated_list():
|
||||
def test_opencode_go_static_models_match_v0205_release_snapshot():
|
||||
models = _opencode_go_static_models()
|
||||
assert [model["id"] for model in models] == EXPECTED_OPENCODE_GO_MODEL_IDS
|
||||
|
||||
|
||||
def test_opencode_go_static_models_match_installed_core_curated_list():
|
||||
"""Exercise the actual Agent contract for the supported release pair."""
|
||||
import pytest
|
||||
|
||||
hermes_cli = pytest.importorskip(
|
||||
"hermes_cli", reason="hermes-agent is not a WebUI test dependency"
|
||||
)
|
||||
if not config._hermes_cli_supports_opencode_go_live_catalog():
|
||||
pytest.skip(f"installed hermes-agent {hermes_cli.__version__} predates v0.20.5")
|
||||
models_mod = pytest.importorskip("hermes_cli.models")
|
||||
assert [model["id"] for model in _opencode_go_static_models()] == models_mod._PROVIDER_MODELS[
|
||||
"opencode-go"
|
||||
]
|
||||
|
||||
|
||||
def test_opencode_go_recent_additions_have_human_labels():
|
||||
labels = {model["id"]: model["label"] for model in _opencode_go_static_models()}
|
||||
assert labels["gpt-5.6-luna"] == "GPT 5.6 Luna"
|
||||
|
||||
Reference in new issue
Block a user