diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index ee9c556046..af95c63b0a 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -6052,6 +6052,7 @@ class _ProfileProjection(NamedTuple): reasoning_extra: Dict[str, Any] top_level: Dict[str, Any] handles_reasoning: bool + messages_wire: bool = False def _project_provider_profile( @@ -6062,11 +6063,13 @@ def _project_provider_profile( reasoning_extra: Dict[str, Any] = {} top_level: Dict[str, Any] = {} handles_reasoning = False + messages_wire = False try: from providers import get_provider_profile from providers.base import ProviderProfile profile = get_provider_profile(provider_norm) if profile is not None: + messages_wire = profile.api_mode == "anthropic_messages" body = profile.build_extra_body(model=model, base_url=effective_base, reasoning_config=reasoning_config) or {} reasoning_extra, top_level = profile.build_api_kwargs_extras( reasoning_config=reasoning_config, supports_reasoning=reasoning_config is not None, @@ -6082,7 +6085,7 @@ def _project_provider_profile( ) except Exception as exc: logger.debug("_build_call_kwargs: provider profile projection failed for %s: %s", provider, exc) - return _ProfileProjection(body, reasoning_extra, top_level, handles_reasoning) + return _ProfileProjection(body, reasoning_extra, top_level, handles_reasoning, messages_wire) def _merge_aux_extra_body( @@ -6148,11 +6151,13 @@ def _build_call_kwargs( kwargs["extra_body"] = merged_extra # Anthropic Messages adapters take reasoning via a private kwarg that plain OpenAI SDK clients # would reject; Portal Claude is dual-wire, so include it only when the catalog id selects - # /v1/messages. + # /v1/messages. A profile declaring api_mode=anthropic_messages (commandcode-anthropic) is on + # that wire regardless of URL shape — once it overrides build_api_kwargs_extras the generic + # ``extra_body.reasoning`` fallback the adapter used to read is gone, so this is its only path. if reasoning_config and isinstance(reasoning_config, dict): raw_base = base_url or "" if ( - provider_norm == "anthropic" or _nous_on_messages_wire(provider_norm, model) + provider_norm == "anthropic" or projection.messages_wire or _nous_on_messages_wire(provider_norm, model) or _endpoint_speaks_anthropic_messages(raw_base) or _is_anthropic_compat_endpoint(provider_norm, raw_base) ): kwargs["_reasoning_config"] = dict(reasoning_config) diff --git a/tests/agent/test_auxiliary_client.py b/tests/agent/test_auxiliary_client.py index ecd99c70a3..c59bb8aa1c 100644 --- a/tests/agent/test_auxiliary_client.py +++ b/tests/agent/test_auxiliary_client.py @@ -2946,6 +2946,26 @@ class TestAnthropicAuxiliaryReasoningTranslation: ) assert "_reasoning_config" not in openai_wire_kwargs + def test_anthropic_messages_profile_keeps_reasoning_reachable(self): + # commandcode-anthropic: OpenAI-shaped URL, anthropic_messages api_mode, and a profile + # class that overrides build_api_kwargs_extras (so the generic extra_body.reasoning + # fallback the adapter used to read is suppressed). The adapter must still be told. + import model_tools # noqa: F401 — triggers provider discovery + import providers + + assert providers.get_provider_profile("commandcode-anthropic") is not None + rc = {"enabled": False} + kwargs = _build_call_kwargs( + "commandcode-anthropic", "claude-haiku-4-5-20251001", [{"role": "user", "content": "hi"}], + reasoning_config=rc, base_url="https://api.commandcode.ai/provider/v1", + ) + assert kwargs["_reasoning_config"] == rc + chat_kwargs = _build_call_kwargs( + "commandcode", "Qwen/Qwen3.7-Max", [{"role": "user", "content": "hi"}], + reasoning_config=rc, base_url="https://api.commandcode.ai/provider/v1", + ) + assert "_reasoning_config" not in chat_kwargs + class TestAuxiliaryProviderProfileReasoning: """Auxiliary calls must reuse provider-profile reasoning wire shapes."""