11import json
2-
2+ import logging
33from collections .abc import AsyncGenerator
44from typing import Any
55from uuid import uuid4
66
77import httpx
8-
98from httpx_sse import SSEError , aconnect_sse
9+ from pydantic import ValidationError
1010
1111from a2a .client .errors import A2AClientHTTPError , A2AClientJSONError
12- from a2a .types import (
13- AgentCard ,
14- CancelTaskRequest ,
15- CancelTaskResponse ,
16- GetTaskPushNotificationConfigRequest ,
17- GetTaskPushNotificationConfigResponse ,
18- GetTaskRequest ,
19- GetTaskResponse ,
20- SendMessageRequest ,
21- SendMessageResponse ,
22- SendStreamingMessageRequest ,
23- SendStreamingMessageResponse ,
24- SetTaskPushNotificationConfigRequest ,
25- SetTaskPushNotificationConfigResponse ,
26- )
12+ from a2a .types import (AgentCard , CancelTaskRequest , CancelTaskResponse ,
13+ GetTaskPushNotificationConfigRequest ,
14+ GetTaskPushNotificationConfigResponse , GetTaskRequest ,
15+ GetTaskResponse , SendMessageRequest ,
16+ SendMessageResponse , SendStreamingMessageRequest ,
17+ SendStreamingMessageResponse ,
18+ SetTaskPushNotificationConfigRequest ,
19+ SetTaskPushNotificationConfigResponse )
2720from a2a .utils .telemetry import SpanKind , trace_class
2821
22+ logger = logging .getLogger (__name__ )
2923
3024class A2ACardResolver :
3125 """Agent Card resolver."""
@@ -48,11 +42,19 @@ def __init__(
4842 self .httpx_client = httpx_client
4943
5044 async def get_agent_card (
51- self , http_kwargs : dict [str , Any ] | None = None
45+ self ,
46+ relative_card_path : str | None = None ,
47+ http_kwargs : dict [str , Any ] | None = None ,
5248 ) -> AgentCard :
53- """Fetches the agent card from the specified URL.
49+ """Fetches an agent card from a specified path relative to the base_url.
50+
51+ If relative_card_path is None, it defaults to the resolver's configured
52+ agent_card_path (for the public agent card).
5453
5554 Args:
55+ relative_card_path: Optional path to the agent card endpoint,
56+ relative to the base URL. If None, uses the default public
57+ agent card path.
5658 http_kwargs: Optional dictionary of keyword arguments to pass to the
5759 underlying httpx.get request.
5860
@@ -64,21 +66,47 @@ async def get_agent_card(
6466 A2AClientJSONError: If the response body cannot be decoded as JSON
6567 or validated against the AgentCard schema.
6668 """
69+ if relative_card_path is None :
70+ # Use the default public agent card path configured during initialization
71+ path_segment = self .agent_card_path
72+ else :
73+ path_segment = relative_card_path .lstrip ('/' )
74+
75+ target_url = f'{ self .base_url } /{ path_segment } '
76+
6777 try :
6878 response = await self .httpx_client .get (
69- f' { self . base_url } / { self . agent_card_path } ' ,
79+ target_url ,
7080 ** (http_kwargs or {}),
7181 )
7282 response .raise_for_status ()
73- return AgentCard .model_validate (response .json ())
83+ agent_card_data = response .json ()
84+ logger .info (
85+ 'Successfully fetched agent card data from %s: %s' ,
86+ target_url ,
87+ agent_card_data ,
88+ )
89+ agent_card = AgentCard .model_validate (agent_card_data )
7490 except httpx .HTTPStatusError as e :
75- raise A2AClientHTTPError (e .response .status_code , str (e )) from e
91+ raise A2AClientHTTPError (
92+ e .response .status_code ,
93+ f'Failed to fetch agent card from { target_url } : { e } ' ,
94+ ) from e
7695 except json .JSONDecodeError as e :
77- raise A2AClientJSONError (str (e )) from e
96+ raise A2AClientJSONError (
97+ f'Failed to parse JSON for agent card from { target_url } : { e } '
98+ ) from e
7899 except httpx .RequestError as e :
79100 raise A2AClientHTTPError (
80- 503 , f'Network communication error: { e } '
101+ 503 ,
102+ f'Network communication error fetching agent card from { target_url } : { e } ' ,
81103 ) from e
104+ except ValidationError as e : # Pydantic validation error
105+ raise A2AClientJSONError (
106+ f'Failed to validate agent card structure from { target_url } : { e .json ()} '
107+ ) from e
108+
109+ return agent_card
82110
83111
84112@trace_class (kind = SpanKind .CLIENT )
@@ -119,15 +147,19 @@ async def get_client_from_agent_card_url(
119147 agent_card_path : str = '/.well-known/agent.json' ,
120148 http_kwargs : dict [str , Any ] | None = None ,
121149 ) -> 'A2AClient' :
122- """Fetches the AgentCard and initializes an A2A client.
150+ """Fetches the public AgentCard and initializes an A2A client.
151+
152+ This method will always fetch the public agent card. If an authenticated
153+ or extended agent card is required, the A2ACardResolver should be used
154+ directly to fetch the specific card, and then the A2AClient should be
155+ instantiated with it.
123156
124157 Args:
125158 httpx_client: An async HTTP client instance (e.g., httpx.AsyncClient).
126159 base_url: The base URL of the agent's host.
127160 agent_card_path: The path to the agent card endpoint, relative to the base URL.
128161 http_kwargs: Optional dictionary of keyword arguments to pass to the
129162 underlying httpx.get request when fetching the agent card.
130-
131163 Returns:
132164 An initialized `A2AClient` instance.
133165
@@ -137,7 +169,7 @@ async def get_client_from_agent_card_url(
137169 """
138170 agent_card : AgentCard = await A2ACardResolver (
139171 httpx_client , base_url = base_url , agent_card_path = agent_card_path
140- ).get_agent_card (http_kwargs = http_kwargs )
172+ ).get_agent_card (http_kwargs = http_kwargs ) # Fetches public card by default
141173 return A2AClient (httpx_client = httpx_client , agent_card = agent_card )
142174
143175 async def send_message (
0 commit comments