fix: repair OpenCode model routing and selection (#4508)
OpenCode Zen and Go are mixed-API-surface providers — different models behind them use different API surfaces (GPT on Zen uses codex_responses, Claude on Zen uses anthropic_messages, MiniMax on Go uses anthropic_messages, GLM/Kimi on Go use chat_completions). Changes: - Add normalize_opencode_model_id() and opencode_model_api_mode() to models.py for model ID normalization and API surface routing - Add _provider_supports_explicit_api_mode() to runtime_provider.py to prevent stale api_mode from leaking across provider switches - Wire opencode routing into all three api_mode resolution paths: pool entry, api_key provider, and explicit runtime - Add api_mode field to ModelSwitchResult for propagation through the switch pipeline - Consolidate _PROVIDER_MODELS from main.py into models.py (single source of truth, eliminates duplicate dict) - Add opencode normalization to setup wizard and model picker flows - Add opencode block to _normalize_model_for_provider in CLI - Add opencode-zen/go fallback model lists to setup.py Tests: 160 targeted tests pass (26 new tests covering normalization, api_mode routing per provider/model, persistence, and setup wizard normalization). Based on PR #3017 by SaM13997. Co-authored-by: SaM13997 <139419381+SaM13997@users.noreply.github.com>
This commit is contained in:
@@ -210,3 +210,50 @@ class TestProviderPersistsAfterModelSave:
|
||||
assert model.get("base_url") == "acp://copilot"
|
||||
assert model.get("default") == "gpt-5.4"
|
||||
assert model.get("api_mode") == "chat_completions"
|
||||
|
||||
def test_opencode_go_models_are_selectable_and_persist_normalized(self, config_home, monkeypatch):
|
||||
from hermes_cli.main import _model_flow_api_key_provider
|
||||
from hermes_cli.config import load_config
|
||||
|
||||
monkeypatch.setenv("OPENCODE_GO_API_KEY", "test-key")
|
||||
|
||||
with patch("hermes_cli.models.fetch_api_models", return_value=["opencode-go/kimi-k2.5", "opencode-go/minimax-m2.7"]), \
|
||||
patch("hermes_cli.auth._prompt_model_selection", return_value="kimi-k2.5"), \
|
||||
patch("hermes_cli.auth.deactivate_provider"), \
|
||||
patch("builtins.input", return_value=""):
|
||||
_model_flow_api_key_provider(load_config(), "opencode-go", "opencode-go/kimi-k2.5")
|
||||
|
||||
import yaml
|
||||
config = yaml.safe_load((config_home / "config.yaml").read_text()) or {}
|
||||
model = config.get("model")
|
||||
assert isinstance(model, dict)
|
||||
assert model.get("provider") == "opencode-go"
|
||||
assert model.get("default") == "kimi-k2.5"
|
||||
assert model.get("api_mode") == "chat_completions"
|
||||
|
||||
def test_opencode_go_same_provider_switch_recomputes_api_mode(self, config_home, monkeypatch):
|
||||
from hermes_cli.main import _model_flow_api_key_provider
|
||||
from hermes_cli.config import load_config
|
||||
|
||||
monkeypatch.setenv("OPENCODE_GO_API_KEY", "test-key")
|
||||
(config_home / "config.yaml").write_text(
|
||||
"model:\n"
|
||||
" default: kimi-k2.5\n"
|
||||
" provider: opencode-go\n"
|
||||
" base_url: https://opencode.ai/zen/go/v1\n"
|
||||
" api_mode: chat_completions\n"
|
||||
)
|
||||
|
||||
with patch("hermes_cli.models.fetch_api_models", return_value=["opencode-go/kimi-k2.5", "opencode-go/minimax-m2.5"]), \
|
||||
patch("hermes_cli.auth._prompt_model_selection", return_value="minimax-m2.5"), \
|
||||
patch("hermes_cli.auth.deactivate_provider"), \
|
||||
patch("builtins.input", return_value=""):
|
||||
_model_flow_api_key_provider(load_config(), "opencode-go", "kimi-k2.5")
|
||||
|
||||
import yaml
|
||||
config = yaml.safe_load((config_home / "config.yaml").read_text()) or {}
|
||||
model = config.get("model")
|
||||
assert isinstance(model, dict)
|
||||
assert model.get("provider") == "opencode-go"
|
||||
assert model.get("default") == "minimax-m2.5"
|
||||
assert model.get("api_mode") == "anthropic_messages"
|
||||
|
||||
Reference in New Issue
Block a user