fix(models): delist hy3-free and laguna-s-2.1-free — OpenCode relay dropped them (anon 401)
The OpenCode Zen relay no longer serves hy3-free (since ~2026-08-31) or
laguna-s-2.1-free (new, verified 2026-09-09): both are gone from the live
GET /zen/v1/models catalog and anonymous chat completions return
401 {"type":"ModelError","message":"Model <id> is not supported"}
(2 probes >=60s apart, x-opencode-session header present).
- hermes_cli/models_catalog_static.py: remove both slugs from the
opencode-free offline floor and the opencode-zen discovery floor;
document the delist dates in the catalog comment.
- plugins/model-providers/opencode-free: default_aux_model moves from the
dead laguna-s-2.1-free to nemotron-3.5-lightning-free (fastest surviving
anonymous model).
- tests: swap fixtures off the dead slugs; extend the floor-exclusion
invariant to cover both.
The live revalidation path already hides them when the relay is reachable;
this fixes the OFFLINE floor and the aux default, which would otherwise
offer/route to models that 401.
This commit is contained in:
@@ -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",
|
||||
],
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user