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 4f72973e3e..d5efe0631a 100644 --- a/src/google/adk/integrations/skill_registry/gcp_skill_registry.py +++ b/src/google/adk/integrations/skill_registry/gcp_skill_registry.py @@ -154,9 +154,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 e6d0c75cad..cdfbb7ae67 100644 --- a/tests/unittests/integrations/skill_registry/test_gcp_skill_registry.py +++ b/tests/unittests/integrations/skill_registry/test_gcp_skill_registry.py @@ -16,6 +16,7 @@ import io import os +import ssl from unittest import mock import zipfile @@ -455,7 +456,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" @@ -491,3 +494,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()