diff --git a/hermes_cli/models_catalog_static.py b/hermes_cli/models_catalog_static.py index 6de68d5104..9aa47963a0 100644 --- a/hermes_cli/models_catalog_static.py +++ b/hermes_cli/models_catalog_static.py @@ -229,15 +229,17 @@ _PROVIDER_MODELS: dict[str, list[str]] = { "grok-4.6", "grok-4.5", "grok-build-0.1", "muse-spark-1.2", "minimax-m3", "minimax-m2.7", "minimax-m2.5", "glm-5.3", "glm-5.3-flash", "glm-5.2", "glm-5.1", "glm-5", "kimi-k2.7-code", "deepseek-v4-pro", "deepseek-v4-flash", "deepseek-v4-flash-free", "qwen3.6-plus", "qwen3.5-plus", "big-pickle", "mimo-v2.5-free", - "hy3-free", "laguna-s-2.1-free", "nemotron-3-ultra-free", "nemotron-3.5-lightning-free", + "nemotron-3-ultra-free", "nemotron-3.5-lightning-free", "muse-spark-1.2-contributor-free", "muse-spark-1.3-contributor-free", ], # OpenCode keyless free tier — OFFLINE FLOOR only. provider_model_ids("opencode-free") # revalidates live against GET /zen/v1/models and filters to the anonymous tier, so this list # may lag the relay (intentional). Known-delisted models are REMOVED (the offline fallback must - # not offer a model that 401s, e.g. x-preview-f-free). + # not offer a model that 401s; x-preview-f-free delisted 2026-08-26, hy3-free and + # laguna-s-2.1-free delisted 2026-09-09 — both dropped from live /zen/v1/models and 401 + # "Model … is not supported" anonymously). "opencode-free": [ - "deepseek-v4-flash-free", "hy3-free", "mimo-v2.5-free", "laguna-s-2.1-free", + "deepseek-v4-flash-free", "mimo-v2.5-free", "nemotron-3-ultra-free", "nemotron-3.5-lightning-free", "muse-spark-1.2-contributor-free", "muse-spark-1.3-contributor-free", ], diff --git a/plugins/model-providers/opencode-free/__init__.py b/plugins/model-providers/opencode-free/__init__.py index e8fa62cc6c..bdac51f8c0 100644 --- a/plugins/model-providers/opencode-free/__init__.py +++ b/plugins/model-providers/opencode-free/__init__.py @@ -40,9 +40,10 @@ opencode_free = OpenCodeFreeProfile( "X-Title": "Hermes Agent", "User-Agent": f"HermesAgent/{_HERMES_VERSION}", }, - # laguna is the fastest non-UA-gated free model; big-pickle 429s every + # laguna-s-2.1-free was delisted by the relay 2026-09-09 (anon 401); the fastest + # surviving free model is the lightning-tier Nemotron. big-pickle 429s every # client except the opencode CLI's own User-Agent. - default_aux_model="laguna-s-2.1-free", + default_aux_model="nemotron-3.5-lightning-free", ) register_provider(opencode_free) diff --git a/tests/agent/test_opencode_session_affinity.py b/tests/agent/test_opencode_session_affinity.py index b2b6cf2536..51c3f3139c 100644 --- a/tests/agent/test_opencode_session_affinity.py +++ b/tests/agent/test_opencode_session_affinity.py @@ -35,7 +35,7 @@ def _agent(provider, model, base_url, api_mode=None): ("opencode-go", "glm-5", "https://opencode.ai/zen/go/v1", None), # chat_completions ("opencode-go", "gpt-5.6-luna", "https://opencode.ai/zen/go/v1", None), # codex_responses ("opencode-go", "minimax-m2.7", "https://opencode.ai/zen/go/v1", "anthropic_messages"), - ("opencode-free", "laguna-s-2.1-free", "https://opencode.ai/zen/v1", None), + ("opencode-free", "nemotron-3.5-lightning-free", "https://opencode.ai/zen/v1", None), ("custom", "glm-5", "https://opencode.ai/zen/go/v1", None), # URL-only detection ], ) diff --git a/tests/hermes_cli/test_model_validation.py b/tests/hermes_cli/test_model_validation.py index 6f92a42c8d..34b925d4f9 100644 --- a/tests/hermes_cli/test_model_validation.py +++ b/tests/hermes_cli/test_model_validation.py @@ -285,7 +285,7 @@ class TestCopilotNormalization: assert opencode_model_api_mode("opencode-zen", "x-preview-f-free") == "chat_completions" assert opencode_model_api_mode("opencode-zen", "opencode-zen/x-preview-f-free") == "chat_completions" # Other free-tier Zen models are chat/completions too. - assert opencode_model_api_mode("opencode-zen", "hy3-free") == "chat_completions" + assert opencode_model_api_mode("opencode-zen", "nemotron-3.5-lightning-free") == "chat_completions" assert opencode_model_api_mode("opencode-zen", "nemotron-3.5-lightning-free") == "chat_completions" # Hy3 on Go is chat/completions (Go endpoint table). assert opencode_model_api_mode("opencode-go", "hy3") == "chat_completions" diff --git a/tests/hermes_cli/test_opencode_free_live_catalog.py b/tests/hermes_cli/test_opencode_free_live_catalog.py index 67ac93b64a..2b6a2ac3a7 100644 --- a/tests/hermes_cli/test_opencode_free_live_catalog.py +++ b/tests/hermes_cli/test_opencode_free_live_catalog.py @@ -32,12 +32,11 @@ from hermes_cli.models import ( _STATIC_FLOOR = list(_PROVIDER_MODELS["opencode-free"]) # The live relay's current free tier. x-preview-f-free was DELISTED 2026-08-26; -# deepseek-v4-flash-free + mimo-v2.5-free are back on the live list. +# hy3-free and laguna-s-2.1-free were DELISTED 2026-09-09 (gone from live +# /models, anon 401 "Model … is not supported"). _LIVE_FREE_MODELS = [ "deepseek-v4-flash-free", - "hy3-free", "mimo-v2.5-free", - "laguna-s-2.1-free", "nemotron-3-ultra-free", "nemotron-3.5-lightning-free", "muse-spark-1.2-contributor-free", @@ -220,3 +219,5 @@ class TestOpencodeFreeFollowUps: def test_static_floor_excludes_delisted_model(self): """The offline floor must not offer a model known to 401 (#95914).""" assert "x-preview-f-free" not in _PROVIDER_MODELS["opencode-free"] + assert "hy3-free" not in _PROVIDER_MODELS["opencode-free"] + assert "laguna-s-2.1-free" not in _PROVIDER_MODELS["opencode-free"] diff --git a/tests/hermes_cli/test_opencode_zen_free_keyless.py b/tests/hermes_cli/test_opencode_zen_free_keyless.py index 00414c5410..f49427816b 100644 --- a/tests/hermes_cli/test_opencode_zen_free_keyless.py +++ b/tests/hermes_cli/test_opencode_zen_free_keyless.py @@ -32,7 +32,7 @@ from hermes_cli.models import ( class TestFreeRuntime: def test_zen_provider_free_model(self): - rt = opencode_zen_free_runtime("opencode-zen", "hy3-free") + rt = opencode_zen_free_runtime("opencode-zen", "nemotron-3.5-lightning-free") assert rt is not None assert rt["base_url"] == "https://opencode.ai/zen/v1" assert rt["api_key"] == OPENCODE_ZEN_FREE_KEYLESS_PLACEHOLDER @@ -42,7 +42,7 @@ class TestFreeRuntime: def test_go_provider_heals_to_zen(self): # Free slugs only exist on the Zen relay; a Go selection must be # routed to Zen (the Go relay rejects the model outright). - rt = opencode_zen_free_runtime("opencode-go", "hy3-free") + rt = opencode_zen_free_runtime("opencode-go", "nemotron-3.5-lightning-free") assert rt is not None assert rt["base_url"] == "https://opencode.ai/zen/v1" @@ -82,13 +82,13 @@ class TestRuntimeProviderKeylessRouting: return resolve_runtime_provider(requested=provider, target_model=model) def test_zen_free_model_resolves_keyless(self): - rt = self._resolve("opencode-zen", "hy3-free") + rt = self._resolve("opencode-zen", "nemotron-3.5-lightning-free") assert rt["api_key"] == OPENCODE_ZEN_FREE_KEYLESS_PLACEHOLDER assert rt["base_url"] == "https://opencode.ai/zen/v1" assert rt["api_mode"] == "chat_completions" def test_go_free_model_resolves_keyless_on_zen(self): - rt = self._resolve("opencode-go", "hy3-free") + rt = self._resolve("opencode-go", "nemotron-3.5-lightning-free") assert rt["api_key"] == OPENCODE_ZEN_FREE_KEYLESS_PLACEHOLDER assert rt["base_url"] == "https://opencode.ai/zen/v1"