Skip to content

Commit f145fc9

Browse files
committed
Merge branch '1.0-dev' into auto-update-a2a-types-0a9f629e801d4ae89f94991fc28afe9429c91cbc
2 parents 20526d7 + 0111633 commit f145fc9

22 files changed

Lines changed: 841 additions & 83 deletions

CHANGELOG.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,40 @@
1-
# Changelog
1+
# Changelog
2+
3+
## [0.3.15](https://github.com/a2aproject/a2a-python/compare/v0.3.14...v0.3.15) (2025-11-19)
4+
5+
6+
### Features
7+
8+
* Add client-side extension support ([#525](https://github.com/a2aproject/a2a-python/issues/525)) ([9a92bd2](https://github.com/a2aproject/a2a-python/commit/9a92bd238e7560b195165ac5f78742981760525e))
9+
* **rest, jsonrpc:** Add client-side extension support ([9a92bd2](https://github.com/a2aproject/a2a-python/commit/9a92bd238e7560b195165ac5f78742981760525e))
10+
11+
## [0.3.14](https://github.com/a2aproject/a2a-python/compare/v0.3.13...v0.3.14) (2025-11-17)
12+
13+
14+
### Features
15+
16+
* **jsonrpc:** add option to disable oversized payload check in JSONRPC applications ([ba142df](https://github.com/a2aproject/a2a-python/commit/ba142df821d1c06be0b96e576fd43015120fcb0b))
17+
18+
## [0.3.13](https://github.com/a2aproject/a2a-python/compare/v0.3.12...v0.3.13) (2025-11-13)
19+
20+
21+
### Bug Fixes
22+
23+
* return entire history when history_length=0 ([#537](https://github.com/a2aproject/a2a-python/issues/537)) ([acdc0de](https://github.com/a2aproject/a2a-python/commit/acdc0de4fa03d34a6b287ab252ff51b19c3016b5))
24+
25+
## [0.3.12](https://github.com/a2aproject/a2a-python/compare/v0.3.11...v0.3.12) (2025-11-12)
26+
27+
28+
### Bug Fixes
29+
30+
* **grpc:** Add `extensions` to `Artifact` converters. ([#523](https://github.com/a2aproject/a2a-python/issues/523)) ([c03129b](https://github.com/a2aproject/a2a-python/commit/c03129b99a663ae1f1ae72f20e4ead7807ede941))
31+
32+
## [0.3.11](https://github.com/a2aproject/a2a-python/compare/v0.3.10...v0.3.11) (2025-11-07)
33+
34+
35+
### Bug Fixes
36+
37+
* add metadata to send message request ([12b4a1d](https://github.com/a2aproject/a2a-python/commit/12b4a1d565a53794f5b55c8bd1728221c906ed41))
238

339
## [0.3.12](https://github.com/a2aproject/a2a-python/compare/v0.3.11...v0.3.12) (2025-11-12)
440

src/a2a/client/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
CredentialService,
88
InMemoryContextCredentialStore,
99
)
10+
from a2a.client.base_client import BaseClient
1011
from a2a.client.card_resolver import A2ACardResolver
1112
from a2a.client.client import Client, ClientConfig, ClientEvent, Consumer
1213
from a2a.client.client_factory import ClientFactory, minimal_agent_card
@@ -51,6 +52,7 @@ def __init__(self, *args, **kwargs):
5152
'A2AClientTimeoutError',
5253
'A2AGrpcClient',
5354
'AuthInterceptor',
55+
'BaseClient',
5456
'Client',
5557
'ClientCallContext',
5658
'ClientCallInterceptor',

src/a2a/client/base_client.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ async def send_message(
5151
*,
5252
context: ClientCallContext | None = None,
5353
request_metadata: dict[str, Any] | None = None,
54+
extensions: list[str] | None = None,
5455
) -> AsyncIterator[ClientEvent | Message]:
5556
"""Sends a message to the agent.
5657
@@ -62,6 +63,7 @@ async def send_message(
6263
request: The message to send to the agent.
6364
context: The client call context.
6465
request_metadata: Extensions Metadata attached to the request.
66+
extensions: List of extensions to be activated.
6567
6668
Yields:
6769
An async iterator of `ClientEvent` or a final `Message` response.
@@ -81,7 +83,7 @@ async def send_message(
8183

8284
if not self._config.streaming or not self._card.capabilities.streaming:
8385
response = await self._transport.send_message(
84-
params, context=context
86+
params, context=context, extensions=extensions
8587
)
8688
result = (
8789
(response, None) if isinstance(response, Task) else response
@@ -91,7 +93,9 @@ async def send_message(
9193
return
9294

9395
tracker = ClientTaskManager()
94-
stream = self._transport.send_message_streaming(params, context=context)
96+
stream = self._transport.send_message_streaming(
97+
params, context=context, extensions=extensions
98+
)
9599

96100
first_event = await anext(stream)
97101
# The response from a server may be either exactly one Message or a
@@ -128,17 +132,21 @@ async def get_task(
128132
request: TaskQueryParams,
129133
*,
130134
context: ClientCallContext | None = None,
135+
extensions: list[str] | None = None,
131136
) -> Task:
132137
"""Retrieves the current state and history of a specific task.
133138
134139
Args:
135140
request: The `TaskQueryParams` object specifying the task ID.
136141
context: The client call context.
142+
extensions: List of extensions to be activated.
137143
138144
Returns:
139145
A `Task` object representing the current state of the task.
140146
"""
141-
return await self._transport.get_task(request, context=context)
147+
return await self._transport.get_task(
148+
request, context=context, extensions=extensions
149+
)
142150

143151
async def list_tasks(
144152
self,
@@ -154,57 +162,70 @@ async def cancel_task(
154162
request: TaskIdParams,
155163
*,
156164
context: ClientCallContext | None = None,
165+
extensions: list[str] | None = None,
157166
) -> Task:
158167
"""Requests the agent to cancel a specific task.
159168
160169
Args:
161170
request: The `TaskIdParams` object specifying the task ID.
162171
context: The client call context.
172+
extensions: List of extensions to be activated.
163173
164174
Returns:
165175
A `Task` object containing the updated task status.
166176
"""
167-
return await self._transport.cancel_task(request, context=context)
177+
return await self._transport.cancel_task(
178+
request, context=context, extensions=extensions
179+
)
168180

169181
async def set_task_callback(
170182
self,
171183
request: TaskPushNotificationConfig,
172184
*,
173185
context: ClientCallContext | None = None,
186+
extensions: list[str] | None = None,
174187
) -> TaskPushNotificationConfig:
175188
"""Sets or updates the push notification configuration for a specific task.
176189
177190
Args:
178191
request: The `TaskPushNotificationConfig` object with the new configuration.
179192
context: The client call context.
193+
extensions: List of extensions to be activated.
180194
181195
Returns:
182196
The created or updated `TaskPushNotificationConfig` object.
183197
"""
184-
return await self._transport.set_task_callback(request, context=context)
198+
return await self._transport.set_task_callback(
199+
request, context=context, extensions=extensions
200+
)
185201

186202
async def get_task_callback(
187203
self,
188204
request: GetTaskPushNotificationConfigParams,
189205
*,
190206
context: ClientCallContext | None = None,
207+
extensions: list[str] | None = None,
191208
) -> TaskPushNotificationConfig:
192209
"""Retrieves the push notification configuration for a specific task.
193210
194211
Args:
195212
request: The `GetTaskPushNotificationConfigParams` object specifying the task.
196213
context: The client call context.
214+
extensions: List of extensions to be activated.
197215
198216
Returns:
199217
A `TaskPushNotificationConfig` object containing the configuration.
200218
"""
201-
return await self._transport.get_task_callback(request, context=context)
219+
return await self._transport.get_task_callback(
220+
request, context=context, extensions=extensions
221+
)
202222

203223
async def resubscribe(
204224
self,
205225
request: TaskIdParams,
206226
*,
207227
context: ClientCallContext | None = None,
228+
extensions: list[str] | None = None,
208229
) -> AsyncIterator[ClientEvent]:
209230
"""Resubscribes to a task's event stream.
210231
@@ -213,6 +234,7 @@ async def resubscribe(
213234
Args:
214235
request: Parameters to identify the task to resubscribe to.
215236
context: The client call context.
237+
extensions: List of extensions to be activated.
216238
217239
Yields:
218240
An async iterator of `ClientEvent` objects.
@@ -230,12 +252,15 @@ async def resubscribe(
230252
# we should never see Message updates, despite the typing of the service
231253
# definition indicating it may be possible.
232254
async for event in self._transport.resubscribe(
233-
request, context=context
255+
request, context=context, extensions=extensions
234256
):
235257
yield await self._process_response(tracker, event)
236258

237259
async def get_card(
238-
self, *, context: ClientCallContext | None = None
260+
self,
261+
*,
262+
context: ClientCallContext | None = None,
263+
extensions: list[str] | None = None,
239264
) -> AgentCard:
240265
"""Retrieves the agent's card.
241266
@@ -244,11 +269,14 @@ async def get_card(
244269
245270
Args:
246271
context: The client call context.
272+
extensions: List of extensions to be activated.
247273
248274
Returns:
249275
The `AgentCard` for the agent.
250276
"""
251-
card = await self._transport.get_card(context=context)
277+
card = await self._transport.get_card(
278+
context=context, extensions=extensions
279+
)
252280
self._card = card
253281
return card
254282

src/a2a/client/client.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ class ClientConfig:
6969
)
7070
"""Push notification callbacks to use for every request."""
7171

72+
extensions: list[str] = dataclasses.field(default_factory=list)
73+
"""A list of extension URIs the client supports."""
74+
7275

7376
UpdateEvent = TaskStatusUpdateEvent | TaskArtifactUpdateEvent | None
7477
# Alias for emitted events from client
@@ -113,6 +116,7 @@ async def send_message(
113116
*,
114117
context: ClientCallContext | None = None,
115118
request_metadata: dict[str, Any] | None = None,
119+
extensions: list[str] | None = None,
116120
) -> AsyncIterator[ClientEvent | Message]:
117121
"""Sends a message to the server.
118122
@@ -131,6 +135,7 @@ async def get_task(
131135
request: TaskQueryParams,
132136
*,
133137
context: ClientCallContext | None = None,
138+
extensions: list[str] | None = None,
134139
) -> Task:
135140
"""Retrieves the current state and history of a specific task."""
136141

@@ -149,6 +154,7 @@ async def cancel_task(
149154
request: TaskIdParams,
150155
*,
151156
context: ClientCallContext | None = None,
157+
extensions: list[str] | None = None,
152158
) -> Task:
153159
"""Requests the agent to cancel a specific task."""
154160

@@ -158,6 +164,7 @@ async def set_task_callback(
158164
request: TaskPushNotificationConfig,
159165
*,
160166
context: ClientCallContext | None = None,
167+
extensions: list[str] | None = None,
161168
) -> TaskPushNotificationConfig:
162169
"""Sets or updates the push notification configuration for a specific task."""
163170

@@ -167,6 +174,7 @@ async def get_task_callback(
167174
request: GetTaskPushNotificationConfigParams,
168175
*,
169176
context: ClientCallContext | None = None,
177+
extensions: list[str] | None = None,
170178
) -> TaskPushNotificationConfig:
171179
"""Retrieves the push notification configuration for a specific task."""
172180

@@ -176,14 +184,18 @@ async def resubscribe(
176184
request: TaskIdParams,
177185
*,
178186
context: ClientCallContext | None = None,
187+
extensions: list[str] | None = None,
179188
) -> AsyncIterator[ClientEvent]:
180189
"""Resubscribes to a task's event stream."""
181190
return
182191
yield
183192

184193
@abstractmethod
185194
async def get_card(
186-
self, *, context: ClientCallContext | None = None
195+
self,
196+
*,
197+
context: ClientCallContext | None = None,
198+
extensions: list[str] | None = None,
187199
) -> AgentCard:
188200
"""Retrieves the agent's card."""
189201

src/a2a/client/client_factory.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ def _register_defaults(
8080
card,
8181
url,
8282
interceptors,
83+
config.extensions or None,
8384
),
8485
)
8586
if TransportProtocol.http_json in supported:
@@ -90,6 +91,7 @@ def _register_defaults(
9091
card,
9192
url,
9293
interceptors,
94+
config.extensions or None,
9395
),
9496
)
9597
if TransportProtocol.grpc in supported:
@@ -113,6 +115,7 @@ async def connect( # noqa: PLR0913
113115
relative_card_path: str | None = None,
114116
resolver_http_kwargs: dict[str, Any] | None = None,
115117
extra_transports: dict[str, TransportProducer] | None = None,
118+
extensions: list[str] | None = None,
116119
) -> Client:
117120
"""Convenience method for constructing a client.
118121
@@ -142,6 +145,7 @@ async def connect( # noqa: PLR0913
142145
A2AAgentCardResolver.get_agent_card as the http_kwargs parameter.
143146
extra_transports: Additional transport protocols to enable when
144147
constructing the client.
148+
extensions: List of extensions to be activated.
145149
146150
Returns:
147151
A `Client` object.
@@ -166,7 +170,7 @@ async def connect( # noqa: PLR0913
166170
factory = cls(client_config)
167171
for label, generator in (extra_transports or {}).items():
168172
factory.register(label, generator)
169-
return factory.create(card, consumers, interceptors)
173+
return factory.create(card, consumers, interceptors, extensions)
170174

171175
def register(self, label: str, generator: TransportProducer) -> None:
172176
"""Register a new transport producer for a given transport label."""
@@ -177,6 +181,7 @@ def create(
177181
card: AgentCard,
178182
consumers: list[Consumer] | None = None,
179183
interceptors: list[ClientCallInterceptor] | None = None,
184+
extensions: list[str] | None = None,
180185
) -> Client:
181186
"""Create a new `Client` for the provided `AgentCard`.
182187
@@ -186,6 +191,7 @@ def create(
186191
interceptors: A list of interceptors to use for each request. These
187192
are used for things like attaching credentials or http headers
188193
to all outbound requests.
194+
extensions: List of extensions to be activated.
189195
190196
Returns:
191197
A `Client` object.
@@ -226,12 +232,21 @@ def create(
226232
if consumers:
227233
all_consumers.extend(consumers)
228234

235+
all_extensions = self._config.extensions.copy()
236+
if extensions:
237+
all_extensions.extend(extensions)
238+
self._config.extensions = all_extensions
239+
229240
transport = self._registry[transport_protocol](
230241
card, transport_url, self._config, interceptors or []
231242
)
232243

233244
return BaseClient(
234-
card, self._config, transport, all_consumers, interceptors or []
245+
card,
246+
self._config,
247+
transport,
248+
all_consumers,
249+
interceptors or [],
235250
)
236251

237252

0 commit comments

Comments
 (0)