Skip to content

Commit 1490d7e

Browse files
committed
Address review comments
1 parent 0b86ad2 commit 1490d7e

File tree

3 files changed

+131
-193
lines changed

3 files changed

+131
-193
lines changed

examples/helloworld/test_client.py

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,74 @@
44

55
import httpx
66

7-
from a2a.client import A2AClient
8-
from a2a.types import (
9-
SendMessageRequest,
10-
MessageSendParams,
11-
SendStreamingMessageRequest,
12-
)
7+
from a2a.client import A2ACardResolver, A2AClient
8+
from a2a.types import (AgentCard, MessageSendParams, SendMessageRequest,
9+
SendStreamingMessageRequest)
1310

1411

1512
async def main() -> None:
13+
PUBLIC_AGENT_CARD_PATH = "/.well-known/agent.json"
14+
EXTENDED_AGENT_CARD_PATH = "/agent/authenticatedExtendedCard"
15+
1616
# Configure logging to show INFO level messages
1717
logging.basicConfig(level=logging.INFO)
18+
logger = logging.getLogger(__name__) # Get a logger instance
19+
20+
base_url = 'http://localhost:9999'
1821

1922
async with httpx.AsyncClient() as httpx_client:
20-
client = await A2AClient.get_client_from_agent_card_url(
21-
httpx_client, 'http://localhost:9999'
23+
# Initialize A2ACardResolver
24+
resolver = A2ACardResolver(
25+
httpx_client=httpx_client,
26+
base_url=base_url,
27+
# agent_card_path uses default, extended_agent_card_path also uses default
28+
)
29+
30+
# Fetch Public Agent Card and Initialize Client
31+
public_agent_card: AgentCard | None = None
32+
extended_agent_card: AgentCard | None = None
33+
final_agent_card_to_use: AgentCard | None = None
34+
35+
try:
36+
logger.info(f"Attempting to fetch public agent card from: {base_url}{PUBLIC_AGENT_CARD_PATH}")
37+
public_agent_card = await resolver.get_agent_card() # Fetches from default public path
38+
logger.info("Successfully fetched public agent card:")
39+
logger.info(public_agent_card.model_dump_json(indent=2, exclude_none=True))
40+
41+
# --- Conditional Step: Fetch Extended Agent Card
42+
if public_agent_card and public_agent_card.supportsAuthenticatedExtendedCard:
43+
logger.info(f"\nPublic card supports authenticated extended card. Attempting to fetch from: {base_url}{EXTENDED_AGENT_CARD_PATH}")
44+
auth_headers_dict = {"Authorization": "Bearer dummy-token-for-extended-card"}
45+
extended_agent_card = await resolver.get_agent_card(
46+
relative_card_path=EXTENDED_AGENT_CARD_PATH, # Or resolver.extended_agent_card_path
47+
http_kwargs={"headers": auth_headers_dict}
48+
)
49+
logger.info("Successfully fetched authenticated extended agent card:")
50+
logger.info(extended_agent_card.model_dump_json(indent=2, exclude_none=True))
51+
else:
52+
logger.info("\nPublic card does not support authenticated extended card, or public card not fetched.")
53+
54+
except Exception as e:
55+
logger.error(f"Error during agent card fetching: {e}", exc_info=True)
56+
# If public card fetching failed, or extended card fetching failed after public card indicated support,
57+
# we might not have a card to use.
58+
59+
# Determine which card to use and Initialize Client
60+
if extended_agent_card:
61+
final_agent_card_to_use = extended_agent_card
62+
logger.info("\nUsing AUTHENTICATED EXTENDED agent card for client initialization.")
63+
elif public_agent_card:
64+
final_agent_card_to_use = public_agent_card
65+
logger.info("\nUsing PUBLIC agent card for client initialization.")
66+
else:
67+
logger.error("\nNo agent card successfully fetched. Cannot initialize client.")
68+
return # Cannot proceed
69+
70+
client = A2AClient(
71+
httpx_client=httpx_client, agent_card=final_agent_card_to_use
2272
)
73+
logger.info("A2AClient initialized.")
74+
2375
send_message_payload: dict[str, Any] = {
2476
'message': {
2577
'role': 'user',

src/a2a/client/client.py

Lines changed: 39 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,19 @@ def __init__(
4444
self.httpx_client = httpx_client
4545

4646
async def get_agent_card(
47-
self, http_kwargs: dict[str, Any] | None = None
47+
self,
48+
relative_card_path: str | None = None,
49+
http_kwargs: dict[str, Any] | None = None,
4850
) -> AgentCard:
49-
# Fetch the initial public agent card
50-
public_card_url = f'{self.base_url}/{self.agent_card_path}'
51-
"""Fetches the agent card from the specified URL.
51+
"""Fetches an agent card from a specified path relative to the base_url.
52+
53+
If relative_card_path is None, it defaults to the resolver's configured
54+
agent_card_path (for the public agent card).
5255
5356
Args:
57+
relative_card_path: Optional path to the agent card endpoint,
58+
relative to the base URL. If None, uses the default public
59+
agent card path.
5460
http_kwargs: Optional dictionary of keyword arguments to pass to the
5561
underlying httpx.get request.
5662
@@ -62,89 +68,45 @@ async def get_agent_card(
6268
A2AClientJSONError: If the response body cannot be decoded as JSON
6369
or validated against the AgentCard schema.
6470
"""
71+
if relative_card_path is None:
72+
# Use the default public agent card path configured during initialization
73+
path_segment = self.agent_card_path
74+
else:
75+
path_segment = relative_card_path.lstrip('/')
76+
77+
target_url = f'{self.base_url}/{path_segment}'
78+
6579
try:
6680
response = await self.httpx_client.get(
67-
public_card_url,
81+
target_url,
6882
**(http_kwargs or {}),
6983
)
7084
response.raise_for_status()
71-
public_agent_card_data = response.json()
85+
agent_card_data = response.json()
7286
logger.info(
73-
'Successfully fetched public agent card data: %s',
74-
public_agent_card_data,
75-
) # Added for verbosity
76-
# print(f"DEBUG: Fetched public agent card data:\n{json.dumps(public_agent_card_data, indent=2)}") # Added for direct output
77-
agent_card = AgentCard.model_validate(public_agent_card_data)
87+
'Successfully fetched agent card data from %s: %s',
88+
target_url,
89+
agent_card_data,
90+
)
91+
agent_card = AgentCard.model_validate(agent_card_data)
7892
except httpx.HTTPStatusError as e:
7993
raise A2AClientHTTPError(
8094
e.response.status_code,
81-
f'Failed to fetch public agent card from {public_card_url}: {e}',
95+
f'Failed to fetch agent card from {target_url}: {e}',
8296
) from e
8397
except json.JSONDecodeError as e:
8498
raise A2AClientJSONError(
85-
f'Failed to parse JSON for public agent card from {public_card_url}: {e}'
99+
f'Failed to parse JSON for agent card from {target_url}: {e}'
86100
) from e
87101
except httpx.RequestError as e:
88102
raise A2AClientHTTPError(
89103
503,
90-
f'Network communication error fetching public agent card from {public_card_url}: {e}',
104+
f'Network communication error fetching agent card from {target_url}: {e}',
105+
) from e
106+
except ValidationError as e: # Pydantic validation error
107+
raise A2AClientJSONError(
108+
f'Failed to validate agent card structure from {target_url}: {e.json()}'
91109
) from e
92-
93-
# Check for supportsAuthenticatedExtendedCard
94-
if agent_card.supportsAuthenticatedExtendedCard:
95-
# Construct URL for the extended card.
96-
# The extended card URL is relative to the agent's base URL specified *in* the agent card.
97-
if not agent_card.url:
98-
logger.warning(
99-
'Agent card (from %s) indicates support for an extended card '
100-
"but does not specify its own base 'url' field. "
101-
'Cannot fetch extended card. Proceeding with public card.',
102-
public_card_url,
103-
)
104-
return agent_card
105-
106-
extended_card_base_url = agent_card.url.rstrip('/')
107-
full_extended_card_url = (
108-
f'{extended_card_base_url}/{self.extended_agent_card_path}'
109-
)
110-
111-
logger.info(
112-
'Attempting to fetch extended agent card from %s',
113-
full_extended_card_url,
114-
)
115-
try:
116-
# Make another GET request for the extended card
117-
# Note: Authentication headers will be added here when auth is implemented.
118-
extended_response = await self.httpx_client.get(
119-
full_extended_card_url,
120-
**(http_kwargs or {}), # Passing original http_kwargs
121-
)
122-
extended_response.raise_for_status()
123-
extended_agent_card_data = extended_response.json()
124-
logger.info(
125-
'Successfully fetched extended agent card data: %s',
126-
extended_agent_card_data,
127-
) # Added for verbosity
128-
129-
# This new card data replaces the old one entirely
130-
agent_card = AgentCard.model_validate(extended_agent_card_data)
131-
logger.info(
132-
'Successfully fetched and using extended agent card from %s',
133-
full_extended_card_url,
134-
)
135-
except (
136-
httpx.HTTPStatusError,
137-
httpx.RequestError,
138-
json.JSONDecodeError,
139-
ValidationError,
140-
) as e:
141-
logger.warning(
142-
'Failed to fetch or parse extended agent card from %s. Error: %s. '
143-
'Proceeding with the initially fetched public agent card.',
144-
full_extended_card_url,
145-
e,
146-
)
147-
# Fallback to the already parsed public_agent_card (which is 'agent_card' at this point)
148110

149111
return agent_card
150112

@@ -187,14 +149,17 @@ async def get_client_from_agent_card_url(
187149
agent_card_path: str = '/.well-known/agent.json',
188150
http_kwargs: dict[str, Any] | None = None,
189151
) -> 'A2AClient':
190-
"""Fetches the AgentCard and initializes an A2A client.
152+
"""Fetches the public AgentCard and initializes an A2A client.
153+
154+
This method will always fetch the public agent card. If an authenticated
155+
or extended agent card is required, the A2ACardResolver should be used
156+
directly to fetch the specific card, and then the A2AClient should be
157+
instantiated with it.
191158
192159
Args:
193160
httpx_client: An async HTTP client instance (e.g., httpx.AsyncClient).
194161
base_url: The base URL of the agent's host.
195162
agent_card_path: The path to the agent card endpoint, relative to the base URL.
196-
http_kwargs: Optional dictionary of keyword arguments to pass to the
197-
underlying httpx.get request when fetching the agent card.
198163
199164
Returns:
200165
An initialized `A2AClient` instance.
@@ -205,7 +170,7 @@ async def get_client_from_agent_card_url(
205170
"""
206171
agent_card: AgentCard = await A2ACardResolver(
207172
httpx_client, base_url=base_url, agent_card_path=agent_card_path
208-
).get_agent_card(http_kwargs=http_kwargs)
173+
).get_agent_card(http_kwargs=http_kwargs) # Fetches public card by default
209174
return A2AClient(httpx_client=httpx_client, agent_card=agent_card)
210175

211176
async def send_message(

0 commit comments

Comments
 (0)