Skip to content

Commit 044408f

Browse files
lkawkasokolivaa2a-botTadakiAsechitadaki0601
authored
chore: Merge main into 1.0-dev (#566)
Co-authored-by: Iva Sokolaj <102302011+sokoliva@users.noreply.github.com> Co-authored-by: Agent2Agent (A2A) Bot <a2a-bot@google.com> Co-authored-by: Tadaki Asechi <127199356+TadakiAsechi@users.noreply.github.com> Co-authored-by: tadaki <tadaki.asechi@gmail.com> Co-authored-by: Holt Skinner <13262395+holtskinner@users.noreply.github.com> Co-authored-by: TadakiAsechi <tadaki.asechi@icould.com> Co-authored-by: TadakiAsechi <tadaki.asechi@iclould.com>
1 parent d5818e5 commit 044408f

File tree

5 files changed

+133
-2
lines changed

5 files changed

+133
-2
lines changed

CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,18 @@
1-
# Changelog
1+
# Changelog
2+
3+
## [0.3.17](https://github.com/a2aproject/a2a-python/compare/v0.3.16...v0.3.17) (2025-11-24)
4+
5+
6+
### Features
7+
8+
* **client:** allow specifying `history_length` via call-site `MessageSendConfiguration` in `BaseClient.send_message` ([53bbf7a](https://github.com/a2aproject/a2a-python/commit/53bbf7ae3ad58fb0c10b14da05cf07c0a7bd9651))
9+
10+
## [0.3.16](https://github.com/a2aproject/a2a-python/compare/v0.3.15...v0.3.16) (2025-11-21)
11+
12+
13+
### Bug Fixes
14+
15+
* Ensure metadata propagation for `Task` `ToProto` and `FromProto` conversion ([#557](https://github.com/a2aproject/a2a-python/issues/557)) ([fc31d03](https://github.com/a2aproject/a2a-python/commit/fc31d03e8c6acb68660f6d1924262e16933c5d50))
216

317
## [0.3.15](https://github.com/a2aproject/a2a-python/compare/v0.3.14...v0.3.15) (2025-11-19)
418

src/a2a/client/base_client.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ async def send_message(
4949
self,
5050
request: Message,
5151
*,
52+
configuration: MessageSendConfiguration | None = None,
5253
context: ClientCallContext | None = None,
5354
request_metadata: dict[str, Any] | None = None,
5455
extensions: list[str] | None = None,
@@ -61,14 +62,15 @@ async def send_message(
6162
6263
Args:
6364
request: The message to send to the agent.
65+
configuration: Optional per-call overrides for message sending behavior.
6466
context: The client call context.
6567
request_metadata: Extensions Metadata attached to the request.
6668
extensions: List of extensions to be activated.
6769
6870
Yields:
6971
An async iterator of `ClientEvent` or a final `Message` response.
7072
"""
71-
config = MessageSendConfiguration(
73+
base_config = MessageSendConfiguration(
7274
accepted_output_modes=self._config.accepted_output_modes,
7375
blocking=not self._config.polling,
7476
push_notification_config=(
@@ -77,6 +79,15 @@ async def send_message(
7779
else None
7880
),
7981
)
82+
if configuration is not None:
83+
update_data = configuration.model_dump(
84+
exclude_unset=True,
85+
by_alias=False,
86+
)
87+
config = base_config.model_copy(update=update_data)
88+
else:
89+
config = base_config
90+
8091
params = MessageSendParams(
8192
message=request, configuration=config, metadata=request_metadata
8293
)

src/a2a/utils/proto_utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ def task(cls, task: types.Task) -> a2a_pb2.Task:
204204
if task.history
205205
else None
206206
),
207+
metadata=cls.metadata(task.metadata),
207208
)
208209

209210
@classmethod
@@ -689,6 +690,7 @@ def task(cls, task: a2a_pb2.Task) -> types.Task:
689690
status=cls.task_status(task.status),
690691
artifacts=[cls.artifact(a) for a in task.artifacts],
691692
history=[cls.message(h) for h in task.history],
693+
metadata=cls.metadata(task.metadata),
692694
)
693695

694696
@classmethod

tests/client/test_base_client.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
AgentCapabilities,
1010
AgentCard,
1111
Message,
12+
MessageSendConfiguration,
1213
Part,
1314
Role,
1415
Task,
@@ -125,3 +126,78 @@ async def test_send_message_non_streaming_agent_capability_false(
125126
assert not mock_transport.send_message_streaming.called
126127
assert len(events) == 1
127128
assert events[0][0].id == 'task-789'
129+
130+
131+
@pytest.mark.asyncio
132+
async def test_send_message_callsite_config_overrides_non_streaming(
133+
base_client: BaseClient, mock_transport: MagicMock, sample_message: Message
134+
):
135+
base_client._config.streaming = False
136+
mock_transport.send_message.return_value = Task(
137+
id='task-cfg-ns-1',
138+
context_id='ctx-cfg-ns-1',
139+
status=TaskStatus(state=TaskState.completed),
140+
)
141+
142+
cfg = MessageSendConfiguration(
143+
history_length=2,
144+
blocking=False,
145+
accepted_output_modes=['application/json'],
146+
)
147+
events = [
148+
event
149+
async for event in base_client.send_message(
150+
sample_message, configuration=cfg
151+
)
152+
]
153+
154+
mock_transport.send_message.assert_called_once()
155+
assert not mock_transport.send_message_streaming.called
156+
assert len(events) == 1
157+
task, _ = events[0]
158+
assert task.id == 'task-cfg-ns-1'
159+
160+
params = mock_transport.send_message.call_args[0][0]
161+
assert params.configuration.history_length == 2
162+
assert params.configuration.blocking is False
163+
assert params.configuration.accepted_output_modes == ['application/json']
164+
165+
166+
@pytest.mark.asyncio
167+
async def test_send_message_callsite_config_overrides_streaming(
168+
base_client: BaseClient, mock_transport: MagicMock, sample_message: Message
169+
):
170+
base_client._config.streaming = True
171+
base_client._card.capabilities.streaming = True
172+
173+
async def create_stream(*args, **kwargs):
174+
yield Task(
175+
id='task-cfg-s-1',
176+
context_id='ctx-cfg-s-1',
177+
status=TaskStatus(state=TaskState.completed),
178+
)
179+
180+
mock_transport.send_message_streaming.return_value = create_stream()
181+
182+
cfg = MessageSendConfiguration(
183+
history_length=0,
184+
blocking=True,
185+
accepted_output_modes=['text/plain'],
186+
)
187+
events = [
188+
event
189+
async for event in base_client.send_message(
190+
sample_message, configuration=cfg
191+
)
192+
]
193+
194+
mock_transport.send_message_streaming.assert_called_once()
195+
assert not mock_transport.send_message.called
196+
assert len(events) == 1
197+
task, _ = events[0]
198+
assert task.id == 'task-cfg-s-1'
199+
200+
params = mock_transport.send_message_streaming.call_args[0][0]
201+
assert params.configuration.history_length == 0
202+
assert params.configuration.blocking is True
203+
assert params.configuration.accepted_output_modes == ['text/plain']

tests/utils/test_proto_utils.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def sample_task(sample_message: types.Message) -> types.Task:
5454
],
5555
)
5656
],
57+
metadata={'source': 'test'},
5758
)
5859

5960

@@ -600,3 +601,30 @@ def test_large_integer_roundtrip_with_utilities(self):
600601
assert final_result['nested']['another_large'] == 12345678901234567890
601602
assert isinstance(final_result['nested']['another_large'], int)
602603
assert final_result['nested']['normal'] == 'text'
604+
605+
def test_task_conversion_roundtrip(
606+
self, sample_task: types.Task, sample_message: types.Message
607+
):
608+
"""Test conversion of Task to proto and back."""
609+
proto_task = proto_utils.ToProto.task(sample_task)
610+
assert isinstance(proto_task, a2a_pb2.Task)
611+
612+
roundtrip_task = proto_utils.FromProto.task(proto_task)
613+
assert roundtrip_task.id == 'task-1'
614+
assert roundtrip_task.context_id == 'ctx-1'
615+
assert roundtrip_task.status == types.TaskStatus(
616+
state=types.TaskState.working, message=sample_message
617+
)
618+
assert roundtrip_task.history == [sample_message]
619+
assert roundtrip_task.artifacts == [
620+
types.Artifact(
621+
artifact_id='art-1',
622+
description='',
623+
metadata={},
624+
name='',
625+
parts=[
626+
types.Part(root=types.TextPart(text='Artifact content'))
627+
],
628+
)
629+
]
630+
assert roundtrip_task.metadata == {'source': 'test'}

0 commit comments

Comments
 (0)