diff --git a/src/google/adk/integrations/skill_registry/gcp_skill_registry.py b/src/google/adk/integrations/skill_registry/gcp_skill_registry.py index a129cb427c4..0da9b89f3ea 100644 --- a/src/google/adk/integrations/skill_registry/gcp_skill_registry.py +++ b/src/google/adk/integrations/skill_registry/gcp_skill_registry.py @@ -158,9 +158,13 @@ async def _make_request( def _create_httpx_client(self) -> httpx.AsyncClient: """Creates a new httpx.AsyncClient with appropriate SSL/mTLS configuration.""" + # The Agent Registry media download (alt=media) replies with a 302 to a + # short-lived GCS signed URL, so the client must follow redirects; httpx + # drops the Authorization header on cross-origin redirects, so the OAuth + # token is not forwarded to the signed-URL host. if self._ssl_context is not None: - return httpx.AsyncClient(verify=self._ssl_context) - return httpx.AsyncClient() + return httpx.AsyncClient(verify=self._ssl_context, follow_redirects=True) + return httpx.AsyncClient(follow_redirects=True) async def get_skill(self, *, name: str) -> models.Skill: """Fetches a skill from the registry. diff --git a/tests/unittests/integrations/skill_registry/test_gcp_skill_registry.py b/tests/unittests/integrations/skill_registry/test_gcp_skill_registry.py index 62c9f46e963..9795c53962a 100644 --- a/tests/unittests/integrations/skill_registry/test_gcp_skill_registry.py +++ b/tests/unittests/integrations/skill_registry/test_gcp_skill_registry.py @@ -17,6 +17,7 @@ import io import logging import os +import ssl from unittest import mock import zipfile @@ -542,7 +543,9 @@ async def mock_get(url, *unused_args, **kwargs): skill = await registry.get_skill(name="my-skill") # Verify AsyncClient was instantiated with verify=mock_ssl_context - mock_client_class.assert_called_with(verify=mock_ssl_context) + mock_client_class.assert_called_with( + verify=mock_ssl_context, follow_redirects=True + ) assert skill.frontmatter.name == "my-skill" @@ -578,3 +581,22 @@ async def test_use_custom_credentials(): }), params={"search_string": "query"}, ) + + +@pytest.mark.asyncio +async def test_create_httpx_client_follows_redirects(): + """Clients follow the 302 redirect issued by the media download endpoint.""" + registry = gcp_skill_registry.GCPSkillRegistry() + + client = registry._create_httpx_client() + try: + assert client.follow_redirects is True + finally: + await client.aclose() + + registry._ssl_context = ssl.create_default_context() + client = registry._create_httpx_client() + try: + assert client.follow_redirects is True + finally: + await client.aclose()