Skip to content

Commit 53bbf7a

Browse files
TadakiAsechitadaki0601holtskinnerTadakiAsechiTadakiAsechi
authored
feat(client): allow specifying history_length via call-site MessageSendConfiguration in BaseClient.send_message (#500)
## Description This PR implements the client-side enhancement described in #499. It allows callers to specify `history_length` (and other message configuration options) directly via the optional configuration parameter in `BaseClient.send_message`, complementing the server-side support added in #497. ### Summary of Changes - Added optional argument `configuration: MessageSendConfiguration | None` to `BaseClient.send_message`. - When provided, merges call-site configuration with `ClientConfig` defaults. - Allows partial overrides (e.g., setting `historyLength` only). - Updated docstrings to reflect the new parameter behavior. - Added unit tests covering both non-streaming and streaming scenarios. ### Motivation Previously, `BaseClient.send_message` built `MessageSendConfiguration` internally from `ClientConfig`, but `ClientConfig` does not include `history_length`. This prevented clients from specifying it on a per-call basis, unlike `get_task`, which already supports `history_length` via `TaskQueryParams`. After this change, client behavior becomes consistent across both methods. ### Related Issues Fixes #499 Complements #497 (server-side historyLength support) Release-As: 0.3.17 --------- Co-authored-by: tadaki <[email protected]> Co-authored-by: Holt Skinner <[email protected]> Co-authored-by: TadakiAsechi <[email protected]> Co-authored-by: TadakiAsechi <[email protected]>
1 parent dbc73e8 commit 53bbf7a

File tree

2 files changed

+88
-1
lines changed

2 files changed

+88
-1
lines changed

src/a2a/client/base_client.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ async def send_message(
4747
self,
4848
request: Message,
4949
*,
50+
configuration: MessageSendConfiguration | None = None,
5051
context: ClientCallContext | None = None,
5152
request_metadata: dict[str, Any] | None = None,
5253
extensions: list[str] | None = None,
@@ -59,14 +60,15 @@ async def send_message(
5960
6061
Args:
6162
request: The message to send to the agent.
63+
configuration: Optional per-call overrides for message sending behavior.
6264
context: The client call context.
6365
request_metadata: Extensions Metadata attached to the request.
6466
extensions: List of extensions to be activated.
6567
6668
Yields:
6769
An async iterator of `ClientEvent` or a final `Message` response.
6870
"""
69-
config = MessageSendConfiguration(
71+
base_config = MessageSendConfiguration(
7072
accepted_output_modes=self._config.accepted_output_modes,
7173
blocking=not self._config.polling,
7274
push_notification_config=(
@@ -75,6 +77,15 @@ async def send_message(
7577
else None
7678
),
7779
)
80+
if configuration is not None:
81+
update_data = configuration.model_dump(
82+
exclude_unset=True,
83+
by_alias=False,
84+
)
85+
config = base_config.model_copy(update=update_data)
86+
else:
87+
config = base_config
88+
7889
params = MessageSendParams(
7990
message=request, configuration=config, metadata=request_metadata
8091
)

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']

0 commit comments

Comments
 (0)