diff --git a/agent/auxiliary_client.py b/agent/auxiliary_client.py index 55cdd252a..5e8a60e76 100644 --- a/agent/auxiliary_client.py +++ b/agent/auxiliary_client.py @@ -1993,6 +1993,39 @@ def resolve_provider_client( "directly supported", provider) return None, None + elif pconfig.auth_type == "aws_sdk": + # AWS SDK providers (Bedrock) — use the Anthropic Bedrock client via + # boto3's credential chain (IAM roles, SSO, env vars, instance metadata). + try: + from agent.bedrock_adapter import has_aws_credentials, resolve_bedrock_region + from agent.anthropic_adapter import build_anthropic_bedrock_client + except ImportError: + logger.warning("resolve_provider_client: bedrock requested but " + "boto3 or anthropic SDK not installed") + return None, None + + if not has_aws_credentials(): + logger.debug("resolve_provider_client: bedrock requested but " + "no AWS credentials found") + return None, None + + region = resolve_bedrock_region() + default_model = "anthropic.claude-haiku-4-5-20251001-v1:0" + final_model = _normalize_resolved_model(model or default_model, provider) + try: + real_client = build_anthropic_bedrock_client(region) + except ImportError as exc: + logger.warning("resolve_provider_client: cannot create Bedrock " + "client: %s", exc) + return None, None + client = AnthropicAuxiliaryClient( + real_client, final_model, api_key="aws-sdk", + base_url=f"https://bedrock-runtime.{region}.amazonaws.com", + ) + logger.debug("resolve_provider_client: bedrock (%s, %s)", final_model, region) + return (_to_async_client(client, final_model) if async_mode + else (client, final_model)) + elif pconfig.auth_type in ("oauth_device_code", "oauth_external"): # OAuth providers — route through their specific try functions if provider == "nous": diff --git a/tests/agent/test_bedrock_integration.py b/tests/agent/test_bedrock_integration.py index 094d25207..954075ab7 100644 --- a/tests/agent/test_bedrock_integration.py +++ b/tests/agent/test_bedrock_integration.py @@ -493,3 +493,98 @@ class TestBedrockModelIdDetection: preserve_dots=False, ) assert kwargs["model"] == "anthropic.claude-opus-4-7" + + +# --------------------------------------------------------------------------- +# auxiliary_client Bedrock resolution — fix for #13919 +# --------------------------------------------------------------------------- +# Before the fix, resolve_provider_client("bedrock", ...) fell through to the +# "unhandled auth_type" warning and returned (None, None), breaking all +# auxiliary tasks (compression, memory, summarization) for Bedrock users. + + +class TestAuxiliaryClientBedrockResolution: + """Verify resolve_provider_client handles Bedrock's aws_sdk auth type.""" + + def test_bedrock_returns_client_with_credentials(self, monkeypatch): + """With valid AWS credentials, Bedrock should return a usable client.""" + monkeypatch.setenv("AWS_ACCESS_KEY_ID", "AKIAIOSFODNN7EXAMPLE") + monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY") + monkeypatch.setenv("AWS_REGION", "us-west-2") + + mock_anthropic_bedrock = MagicMock() + with patch("agent.anthropic_adapter.build_anthropic_bedrock_client", + return_value=mock_anthropic_bedrock): + from agent.auxiliary_client import resolve_provider_client, AnthropicAuxiliaryClient + client, model = resolve_provider_client("bedrock", None) + + assert client is not None, ( + "resolve_provider_client('bedrock') returned None — " + "aws_sdk auth type is not handled" + ) + assert isinstance(client, AnthropicAuxiliaryClient) + assert model is not None + assert client.api_key == "aws-sdk" + assert "us-west-2" in client.base_url + + def test_bedrock_returns_none_without_credentials(self, monkeypatch): + """Without AWS credentials, Bedrock should return (None, None) gracefully.""" + with patch("agent.bedrock_adapter.has_aws_credentials", return_value=False): + from agent.auxiliary_client import resolve_provider_client + client, model = resolve_provider_client("bedrock", None) + + assert client is None + assert model is None + + def test_bedrock_uses_configured_region(self, monkeypatch): + """Bedrock client base_url should reflect AWS_REGION.""" + monkeypatch.setenv("AWS_ACCESS_KEY_ID", "AKIAIOSFODNN7EXAMPLE") + monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY") + monkeypatch.setenv("AWS_REGION", "eu-central-1") + + with patch("agent.anthropic_adapter.build_anthropic_bedrock_client", + return_value=MagicMock()): + from agent.auxiliary_client import resolve_provider_client + client, _ = resolve_provider_client("bedrock", None) + + assert client is not None + assert "eu-central-1" in client.base_url + + def test_bedrock_respects_explicit_model(self, monkeypatch): + """When caller passes an explicit model, it should be used.""" + monkeypatch.setenv("AWS_ACCESS_KEY_ID", "AKIAIOSFODNN7EXAMPLE") + monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY") + + with patch("agent.anthropic_adapter.build_anthropic_bedrock_client", + return_value=MagicMock()): + from agent.auxiliary_client import resolve_provider_client + _, model = resolve_provider_client( + "bedrock", "us.anthropic.claude-sonnet-4-5-20250929-v1:0" + ) + + assert "claude-sonnet" in model + + def test_bedrock_async_mode(self, monkeypatch): + """Async mode should return an AsyncAnthropicAuxiliaryClient.""" + monkeypatch.setenv("AWS_ACCESS_KEY_ID", "AKIAIOSFODNN7EXAMPLE") + monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY") + + with patch("agent.anthropic_adapter.build_anthropic_bedrock_client", + return_value=MagicMock()): + from agent.auxiliary_client import resolve_provider_client, AsyncAnthropicAuxiliaryClient + client, model = resolve_provider_client("bedrock", None, async_mode=True) + + assert client is not None + assert isinstance(client, AsyncAnthropicAuxiliaryClient) + + def test_bedrock_default_model_is_haiku(self, monkeypatch): + """Default auxiliary model for Bedrock should be Haiku (fast, cheap).""" + monkeypatch.setenv("AWS_ACCESS_KEY_ID", "AKIAIOSFODNN7EXAMPLE") + monkeypatch.setenv("AWS_SECRET_ACCESS_KEY", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY") + + with patch("agent.anthropic_adapter.build_anthropic_bedrock_client", + return_value=MagicMock()): + from agent.auxiliary_client import resolve_provider_client + _, model = resolve_provider_client("bedrock", None) + + assert "haiku" in model.lower()