66A2AAgent can be used to get the Agent Card and interact with the agent.
77"""
88
9+ import dataclasses
910import logging
11+ import warnings
1012from collections .abc import AsyncIterator
1113from contextlib import asynccontextmanager
1214from typing import Any
@@ -38,6 +40,7 @@ def __init__(
3840 name : str | None = None ,
3941 description : str | None = None ,
4042 timeout : int = _DEFAULT_TIMEOUT ,
43+ client_config : ClientConfig | None = None ,
4144 a2a_client_factory : ClientFactory | None = None ,
4245 ):
4346 """Initialize A2A agent.
@@ -47,15 +50,34 @@ def __init__(
4750 name: Agent name. If not provided, will be populated from agent card.
4851 description: Agent description. If not provided, will be populated from agent card.
4952 timeout: Timeout for HTTP operations in seconds (defaults to 300).
50- a2a_client_factory: Optional pre-configured A2A ClientFactory. If provided,
51- it will be used to create the A2A client after discovering the agent card.
52- Note: When providing a custom factory, you are responsible for managing
53- the lifecycle of any httpx client it uses.
53+ client_config: A2A ``ClientConfig`` for authentication and transport settings.
54+ The ``httpx_client`` configured here is used for both card discovery and
55+ message sending, enabling authenticated endpoints (SigV4, OAuth, bearer tokens).
56+ When providing an ``httpx_client``, you are responsible for configuring its timeout.
57+ a2a_client_factory: Deprecated. Use ``client_config`` instead.
58+
59+ Raises:
60+ ValueError: If both ``client_config`` and ``a2a_client_factory`` are provided.
5461 """
62+ if client_config is not None and a2a_client_factory is not None :
63+ raise ValueError (
64+ "Cannot provide both client_config and a2a_client_factory. "
65+ "Use client_config (recommended) or a2a_client_factory (deprecated), not both."
66+ )
67+
68+ if a2a_client_factory is not None :
69+ warnings .warn (
70+ "a2a_client_factory is deprecated. Use client_config instead. "
71+ "a2a_client_factory will be removed in a future version." ,
72+ DeprecationWarning ,
73+ stacklevel = 2 ,
74+ )
75+
5576 self .endpoint = endpoint
5677 self .name = name
5778 self .description = description
5879 self .timeout = timeout
80+ self ._client_config : ClientConfig | None = client_config
5981 self ._agent_card : AgentCard | None = None
6082 self ._a2a_client_factory : ClientFactory | None = a2a_client_factory
6183
@@ -160,26 +182,32 @@ async def stream_async(
160182 async def get_agent_card (self ) -> AgentCard :
161183 """Fetch and return the remote agent's card.
162184
163- This method eagerly fetches the agent card from the remote endpoint,
164- populating name and description if not already set. The card is cached
165- after the first fetch.
185+ Eagerly fetches the agent card from the remote endpoint, populating name and description
186+ if not already set. The card is cached after the first fetch.
187+
188+ When ``client_config`` is provided with an ``httpx_client``, that client is used for
189+ card resolution, enabling authenticated card discovery (e.g., SigV4, OAuth, bearer tokens).
166190
167191 Returns:
168192 The remote agent's AgentCard containing name, description, capabilities, skills, etc.
169193 """
170194 if self ._agent_card is not None :
171195 return self ._agent_card
172196
173- async with httpx . AsyncClient ( timeout = self .timeout ) as client :
174- resolver = A2ACardResolver (httpx_client = client , base_url = self .endpoint )
197+ if self . _client_config is not None and self ._client_config . httpx_client is not None :
198+ resolver = A2ACardResolver (httpx_client = self . _client_config . httpx_client , base_url = self .endpoint )
175199 self ._agent_card = await resolver .get_agent_card ()
200+ else :
201+ async with httpx .AsyncClient (timeout = self .timeout ) as client :
202+ resolver = A2ACardResolver (httpx_client = client , base_url = self .endpoint )
203+ self ._agent_card = await resolver .get_agent_card ()
176204
177205 # Populate name from card if not set
178- if self .name is None and self ._agent_card .name :
206+ if self .name is None and self ._agent_card .name is not None :
179207 self .name = self ._agent_card .name
180208
181209 # Populate description from card if not set
182- if self .description is None and self ._agent_card .description :
210+ if self .description is None and self ._agent_card .description is not None :
183211 self .description = self ._agent_card .description
184212
185213 logger .debug ("agent=<%s>, endpoint=<%s> | discovered agent card" , self .name , self .endpoint )
@@ -189,8 +217,9 @@ async def get_agent_card(self) -> AgentCard:
189217 async def _get_a2a_client (self ) -> AsyncIterator [Any ]:
190218 """Get A2A client for sending messages.
191219
192- If a custom factory was provided, uses that (caller manages httpx lifecycle).
193- Otherwise creates a per-call httpx client with proper cleanup.
220+ If a deprecated factory was provided, delegates to it for client creation.
221+ If client_config was provided, uses it directly — ClientFactory handles defaults.
222+ Otherwise creates a managed httpx client with the agent's timeout.
194223
195224 Yields:
196225 Configured A2A client instance.
@@ -201,6 +230,12 @@ async def _get_a2a_client(self) -> AsyncIterator[Any]:
201230 yield self ._a2a_client_factory .create (agent_card )
202231 return
203232
233+ if self ._client_config is not None :
234+ config = dataclasses .replace (self ._client_config , streaming = True )
235+ yield ClientFactory (config ).create (agent_card )
236+ return
237+
238+ # No client_config — create a managed httpx client, consistent with get_agent_card() path
204239 async with httpx .AsyncClient (timeout = self .timeout ) as httpx_client :
205240 config = ClientConfig (httpx_client = httpx_client , streaming = True )
206241 yield ClientFactory (config ).create (agent_card )
0 commit comments