From 3589960e03d0429f485f43ee9b6fbe0f33bdc9ac Mon Sep 17 00:00:00 2001 From: Harish Kukreja Date: Fri, 22 May 2026 23:19:53 -0400 Subject: [PATCH] fix(provider): expose OpenCode Go reasoning controls --- .../model-providers/opencode-zen/__init__.py | 63 ++++++- .../test_opencode_go_profile.py | 171 ++++++++++++++++++ 2 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 tests/plugins/model_providers/test_opencode_go_profile.py diff --git a/plugins/model-providers/opencode-zen/__init__.py b/plugins/model-providers/opencode-zen/__init__.py index f720e8f5fad..6f5cda9f15d 100644 --- a/plugins/model-providers/opencode-zen/__init__.py +++ b/plugins/model-providers/opencode-zen/__init__.py @@ -7,9 +7,70 @@ Both use per-model api_mode routing: (this profile) """ +from __future__ import annotations + +from typing import Any + from providers import register_provider from providers.base import ProviderProfile + +def _flat_model_name(model: str | None) -> str: + """Return the bare OpenCode model ID, tolerating aggregator prefixes.""" + return (model or "").strip().rsplit("/", 1)[-1].lower() + + +def _is_kimi_k2_model(model: str | None) -> bool: + return _flat_model_name(model).startswith("kimi-k2") + + +def _is_deepseek_thinking_model(model: str | None) -> bool: + m = _flat_model_name(model) + if m.startswith("deepseek-v") and not m.startswith("deepseek-v3"): + return True + return m == "deepseek-reasoner" + + +class OpenCodeGoProfile(ProviderProfile): + """OpenCode Go - model-specific reasoning controls.""" + + def build_api_kwargs_extras( + self, *, reasoning_config: dict | None = None, model: str | None = None, **context + ) -> tuple[dict[str, Any], dict[str, Any]]: + extra_body: dict[str, Any] = {} + top_level: dict[str, Any] = {} + + if _is_kimi_k2_model(model): + # Kimi K2 uses Moonshot's native binary thinking switch here, not + # OpenRouter's normalized extra_body.reasoning object. + if isinstance(reasoning_config, dict): + enabled = reasoning_config.get("enabled") is not False + extra_body["thinking"] = { + "type": "enabled" if enabled else "disabled" + } + return extra_body, top_level + + if not _is_deepseek_thinking_model(model): + return extra_body, top_level + + enabled = True + if isinstance(reasoning_config, dict) and reasoning_config.get("enabled") is False: + enabled = False + extra_body["thinking"] = {"type": "enabled" if enabled else "disabled"} + + if not enabled: + return extra_body, top_level + + if isinstance(reasoning_config, dict): + effort = (reasoning_config.get("effort") or "").strip().lower() + if effort in {"xhigh", "max"}: + top_level["reasoning_effort"] = "max" + elif effort in {"low", "medium", "high"}: + top_level["reasoning_effort"] = effort + + return extra_body, top_level + + opencode_zen = ProviderProfile( name="opencode-zen", aliases=("opencode", "opencode_zen", "zen"), @@ -18,7 +79,7 @@ opencode_zen = ProviderProfile( default_aux_model="gemini-3-flash", ) -opencode_go = ProviderProfile( +opencode_go = OpenCodeGoProfile( name="opencode-go", aliases=("opencode_go", "go", "opencode-go-sub"), env_vars=("OPENCODE_GO_API_KEY",), diff --git a/tests/plugins/model_providers/test_opencode_go_profile.py b/tests/plugins/model_providers/test_opencode_go_profile.py new file mode 100644 index 00000000000..5e4c3f2b29d --- /dev/null +++ b/tests/plugins/model_providers/test_opencode_go_profile.py @@ -0,0 +1,171 @@ +"""Unit tests for OpenCode Go reasoning-control wiring.""" + +from __future__ import annotations + +import pytest + + +@pytest.fixture +def opencode_go_profile(): + """Resolve the registered OpenCode Go provider profile.""" + import model_tools # noqa: F401 + import providers + + profile = providers.get_provider_profile("opencode-go") + assert profile is not None, "opencode-go provider profile must be registered" + return profile + + +class TestOpenCodeGoKimiReasoning: + """Kimi K2 models use binary thinking controls on OpenCode Go.""" + + def test_high_effort_enables_thinking_without_effort(self, opencode_go_profile): + extra_body, top_level = opencode_go_profile.build_api_kwargs_extras( + reasoning_config={"enabled": True, "effort": "high"}, + model="kimi-k2.6", + ) + assert extra_body == {"thinking": {"type": "enabled"}} + assert top_level == {} + + def test_disabled_emits_thinking_disabled(self, opencode_go_profile): + extra_body, top_level = opencode_go_profile.build_api_kwargs_extras( + reasoning_config={"enabled": False}, + model="kimi-k2.6", + ) + assert extra_body == {"thinking": {"type": "disabled"}} + assert top_level == {} + + def test_minimal_effort_enables_thinking_without_effort(self, opencode_go_profile): + extra_body, top_level = opencode_go_profile.build_api_kwargs_extras( + reasoning_config={"enabled": True, "effort": "minimal"}, + model="kimi-k2.6", + ) + assert extra_body == {"thinking": {"type": "enabled"}} + assert top_level == {} + + @pytest.mark.parametrize( + "effort", + [ + "xhigh", + "max", + ], + ) + def test_strong_efforts_enable_thinking_without_effort( + self, opencode_go_profile, effort + ): + extra_body, _ = opencode_go_profile.build_api_kwargs_extras( + reasoning_config={"enabled": True, "effort": effort}, + model="moonshotai/kimi-k2.6", + ) + assert extra_body == {"thinking": {"type": "enabled"}} + + def test_no_config_preserves_server_default(self, opencode_go_profile): + extra_body, top_level = opencode_go_profile.build_api_kwargs_extras( + reasoning_config=None, + model="kimi-k2.6", + ) + assert extra_body == {} + assert top_level == {} + + +class TestOpenCodeGoDeepSeekThinking: + """DeepSeek V4 models use DeepSeek-style thinking controls on OpenCode Go.""" + + def test_high_effort_emits_thinking_and_effort(self, opencode_go_profile): + extra_body, top_level = opencode_go_profile.build_api_kwargs_extras( + reasoning_config={"enabled": True, "effort": "high"}, + model="deepseek-v4-pro", + ) + assert extra_body == {"thinking": {"type": "enabled"}} + assert top_level == {"reasoning_effort": "high"} + + def test_disabled_emits_thinking_disabled_without_effort(self, opencode_go_profile): + extra_body, top_level = opencode_go_profile.build_api_kwargs_extras( + reasoning_config={"enabled": False, "effort": "high"}, + model="deepseek-v4-pro", + ) + assert extra_body == {"thinking": {"type": "disabled"}} + assert top_level == {} + + def test_no_config_emits_thinking_enabled_without_effort(self, opencode_go_profile): + extra_body, top_level = opencode_go_profile.build_api_kwargs_extras( + reasoning_config=None, + model="deepseek-v4-pro", + ) + assert extra_body == {"thinking": {"type": "enabled"}} + assert top_level == {} + + def test_minimal_effort_enables_thinking_without_effort(self, opencode_go_profile): + extra_body, top_level = opencode_go_profile.build_api_kwargs_extras( + reasoning_config={"enabled": True, "effort": "minimal"}, + model="deepseek-v4-pro", + ) + assert extra_body == {"thinking": {"type": "enabled"}} + assert top_level == {} + + def test_xhigh_and_max_normalize_to_max(self, opencode_go_profile): + for effort in ("xhigh", "max"): + extra_body, top_level = opencode_go_profile.build_api_kwargs_extras( + reasoning_config={"enabled": True, "effort": effort}, + model="deepseek/deepseek-v4-pro", + ) + assert extra_body == {"thinking": {"type": "enabled"}} + assert top_level == {"reasoning_effort": "max"} + + +class TestOpenCodeGoModelGating: + """Other OpenCode Go models must not receive Kimi/DeepSeek controls.""" + + @pytest.mark.parametrize( + "model", + [ + "glm-5.1", + "qwen3.6-plus", + "minimax-m2.7", + "deepseek-v3.1", + "deepseek-chat", + "", + None, + ], + ) + def test_non_target_models_emit_nothing(self, opencode_go_profile, model): + extra_body, top_level = opencode_go_profile.build_api_kwargs_extras( + reasoning_config={"enabled": True, "effort": "high"}, + model=model, + ) + assert extra_body == {} + assert top_level == {} + + +class TestOpenCodeGoFullKwargsIntegration: + """End-to-end transport kwargs include the profile-provided controls.""" + + def test_kimi_reasoning_reaches_extra_body(self, opencode_go_profile): + from agent.transports.chat_completions import ChatCompletionsTransport + + kwargs = ChatCompletionsTransport().build_kwargs( + model="kimi-k2.6", + messages=[{"role": "user", "content": "ping"}], + tools=None, + provider_profile=opencode_go_profile, + reasoning_config={"enabled": True, "effort": "high"}, + base_url="https://opencode.ai/zen/go/v1", + ) + assert kwargs["extra_body"] == {"thinking": {"type": "enabled"}} + assert "reasoning_effort" not in kwargs + + def test_deepseek_thinking_reaches_extra_body_and_top_level( + self, opencode_go_profile + ): + from agent.transports.chat_completions import ChatCompletionsTransport + + kwargs = ChatCompletionsTransport().build_kwargs( + model="deepseek-v4-pro", + messages=[{"role": "user", "content": "ping"}], + tools=None, + provider_profile=opencode_go_profile, + reasoning_config={"enabled": True, "effort": "high"}, + base_url="https://opencode.ai/zen/go/v1", + ) + assert kwargs["extra_body"] == {"thinking": {"type": "enabled"}} + assert kwargs["reasoning_effort"] == "high"