Skip to content

Commit 50b2c79

Browse files
feat(a2a): add client_config param and deprecate a2a_client_factory (strands-agents#2103)
Co-authored-by: agent-of-mkmeral <217235299+strands-agent@users.noreply.github.com> Co-authored-by: agent-of-mkmeral <agent-of-mkmeral@users.noreply.github.com>
1 parent d27b8ff commit 50b2c79

2 files changed

Lines changed: 395 additions & 58 deletions

File tree

src/strands/agent/a2a_agent.py

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
A2AAgent can be used to get the Agent Card and interact with the agent.
77
"""
88

9+
import dataclasses
910
import logging
11+
import warnings
1012
from collections.abc import AsyncIterator
1113
from contextlib import asynccontextmanager
1214
from 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

Comments
 (0)