From 1b6a6676c622f8a7dc44f507d9b399edcfdfba97 Mon Sep 17 00:00:00 2001 From: Ivan Shymko Date: Wed, 8 Apr 2026 10:20:24 +0000 Subject: [PATCH 1/3] fix(client): do not mutate SendMessageRequest in BaseClient.send_message --- src/a2a/client/base_client.py | 10 +++++++-- tests/client/test_base_client.py | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/a2a/client/base_client.py b/src/a2a/client/base_client.py index 53fd38cdb..eb9d30c60 100644 --- a/src/a2a/client/base_client.py +++ b/src/a2a/client/base_client.py @@ -66,7 +66,7 @@ async def send_message( Yields: An async iterator of `StreamResponse` """ - self._apply_client_config(request) + request = self._apply_client_config(request) if not self._config.streaming or not self._card.capabilities.streaming: response = await self._execute_with_interceptors( input_data=request, @@ -100,7 +100,12 @@ async def send_message( ): yield event - def _apply_client_config(self, request: SendMessageRequest) -> None: + def _apply_client_config( + self, request: SendMessageRequest + ) -> SendMessageRequest: + request_copy = SendMessageRequest() + request_copy.CopyFrom(request) + request = request_copy request.configuration.return_immediately |= self._config.polling if ( not request.configuration.HasField('task_push_notification_config') @@ -116,6 +121,7 @@ def _apply_client_config(self, request: SendMessageRequest) -> None: request.configuration.accepted_output_modes.extend( self._config.accepted_output_modes ) + return request async def _process_stream( self, diff --git a/tests/client/test_base_client.py b/tests/client/test_base_client.py index ed49469a7..d37e3deb4 100644 --- a/tests/client/test_base_client.py +++ b/tests/client/test_base_client.py @@ -208,6 +208,41 @@ async def test_send_message_non_streaming_agent_capability_false( response = events[0] assert response.task.id == 'task-789' + @pytest.mark.asyncio + async def test_send_message_does_not_mutate_request( + self, + base_client: BaseClient, + mock_transport: MagicMock, + sample_message: Message, + ): + base_client._config.streaming = False + base_client._config.polling = True + base_client._config.accepted_output_modes = ['application/json'] + base_client._config.push_notification_configs = [ + TaskPushNotificationConfig( + task_id='task-1', + ) + ] + + task = Task( + id='task-no-mutate', + context_id='ctx-no-mutate', + status=TaskStatus(state=TaskState.TASK_STATE_COMPLETED), + ) + response = SendMessageResponse() + response.task.CopyFrom(task) + mock_transport.send_message.return_value = response + + request = SendMessageRequest(message=sample_message) + + original = SendMessageRequest() + original.CopyFrom(request) + + events = [event async for event in base_client.send_message(request)] + assert len(events) == 1 + + assert request == original + @pytest.mark.asyncio async def test_send_message_callsite_config_overrides_non_streaming( self, From 4d922d4009aa2a11b400b36757193b423f5a7940 Mon Sep 17 00:00:00 2001 From: Ivan Shymko Date: Wed, 8 Apr 2026 10:27:51 +0000 Subject: [PATCH 2/3] Resolve Code Assist comment --- src/a2a/client/base_client.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/a2a/client/base_client.py b/src/a2a/client/base_client.py index eb9d30c60..170b6b82f 100644 --- a/src/a2a/client/base_client.py +++ b/src/a2a/client/base_client.py @@ -103,25 +103,28 @@ async def send_message( def _apply_client_config( self, request: SendMessageRequest ) -> SendMessageRequest: - request_copy = SendMessageRequest() - request_copy.CopyFrom(request) - request = request_copy - request.configuration.return_immediately |= self._config.polling + modified_request = SendMessageRequest() + modified_request.CopyFrom(request) + modified_request.configuration.return_immediately |= ( + self._config.polling + ) if ( - not request.configuration.HasField('task_push_notification_config') + not modified_request.configuration.HasField( + 'task_push_notification_config' + ) and self._config.push_notification_configs ): - request.configuration.task_push_notification_config.CopyFrom( + modified_request.configuration.task_push_notification_config.CopyFrom( self._config.push_notification_configs[0] ) if ( - not request.configuration.accepted_output_modes + not modified_request.configuration.accepted_output_modes and self._config.accepted_output_modes ): - request.configuration.accepted_output_modes.extend( + modified_request.configuration.accepted_output_modes.extend( self._config.accepted_output_modes ) - return request + return modified_request async def _process_stream( self, From 5273a68ee252e362d155d6cab9dcf82a36df3b70 Mon Sep 17 00:00:00 2001 From: Ivan Shymko Date: Wed, 8 Apr 2026 10:37:29 +0000 Subject: [PATCH 3/3] Update --- src/a2a/client/base_client.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/a2a/client/base_client.py b/src/a2a/client/base_client.py index 170b6b82f..342e01f06 100644 --- a/src/a2a/client/base_client.py +++ b/src/a2a/client/base_client.py @@ -105,21 +105,19 @@ def _apply_client_config( ) -> SendMessageRequest: modified_request = SendMessageRequest() modified_request.CopyFrom(request) - modified_request.configuration.return_immediately |= ( - self._config.polling - ) - if ( + if self._config.polling: + modified_request.configuration.return_immediately = True + if self._config.push_notification_configs and ( not modified_request.configuration.HasField( 'task_push_notification_config' ) - and self._config.push_notification_configs ): modified_request.configuration.task_push_notification_config.CopyFrom( self._config.push_notification_configs[0] ) if ( - not modified_request.configuration.accepted_output_modes - and self._config.accepted_output_modes + self._config.accepted_output_modes + and not modified_request.configuration.accepted_output_modes ): modified_request.configuration.accepted_output_modes.extend( self._config.accepted_output_modes