Skip to content

Commit 23ff66e

Browse files
enjoykumawatGWeale
authored andcommitted
fix: guard against None converter results in RemoteA2aAgent
Merge #5199 ## Summary Fixes #5187 - **Problem:** `RemoteA2aAgent._handle_a2a_response` and `_handle_a2a_response_v2` crash with `AttributeError` when converter functions (`convert_a2a_task_to_event`, `convert_a2a_message_to_event`, or config-based converters) return `None`. The code immediately accesses `.custom_metadata` on the result without checking for `None`. - **Fix:** Added `None`-checks after every converter call in both handler methods. When a converter returns `None`, the handler now gracefully returns `None` (skip the event) instead of crashing. - **Tests:** Added `TestHandleNoneConverterResults` with three test cases covering the `None` return path for message converters and task converters in both v1 and v2 handlers. ## Test plan - [x] `python -m pytest tests/unittests/agents/test_remote_a2a_agent.py -v` -- all 98 tests pass - [x] New tests specifically verify that `_handle_a2a_response` returns `None` when `convert_a2a_message_to_event` returns `None` - [x] New tests verify that `_handle_a2a_response` returns `None` when `convert_a2a_task_to_event` returns `None` - [x] New tests verify that `_handle_a2a_response_v2` returns `None` when `a2a_message_converter` returns `None` Co-authored-by: George Weale <gweale@google.com> COPYBARA_INTEGRATE_REVIEW=#5199 from enjoykumawat:fix/remote-a2a-agent-none-check 8550233 PiperOrigin-RevId: 933978968
1 parent 30493ba commit 23ff66e

2 files changed

Lines changed: 52 additions & 0 deletions

File tree

src/google/adk/agents/remote_a2a_agent.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,8 @@ async def _handle_a2a_response(
533533
event = convert_a2a_message_to_event(
534534
update.status.message, self.name, ctx, self._a2a_part_converter
535535
)
536+
if not event:
537+
return None
536538
if event.content is not None and update.status.state in (
537539
TaskState.submitted,
538540
TaskState.working,
@@ -559,6 +561,8 @@ async def _handle_a2a_response(
559561
# for now.
560562
return None
561563

564+
if not event:
565+
return None
562566
event.custom_metadata = event.custom_metadata or {}
563567
event.custom_metadata[A2A_METADATA_PREFIX + "task_id"] = task.id
564568
if task.context_id:
@@ -571,6 +575,8 @@ async def _handle_a2a_response(
571575
event = convert_a2a_message_to_event(
572576
a2a_response, self.name, ctx, self._a2a_part_converter
573577
)
578+
if not event:
579+
return None
574580
event.custom_metadata = event.custom_metadata or {}
575581

576582
if a2a_response.context_id:
@@ -641,6 +647,8 @@ async def _handle_a2a_response_v2(
641647
event = self._config.a2a_message_converter(
642648
a2a_response, self.name, ctx, self._config.a2a_part_converter
643649
)
650+
if not event:
651+
return None
644652
event.custom_metadata = event.custom_metadata or {}
645653

646654
if a2a_response.context_id:

tests/unittests/agents/test_remote_a2a_agent.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,29 @@ async def test_handle_a2a_response_success_with_message(self):
910910
assert result.custom_metadata is not None
911911
assert A2A_METADATA_PREFIX + "context_id" in result.custom_metadata
912912

913+
@pytest.mark.asyncio
914+
async def test_handle_a2a_response_message_converter_returns_none(self):
915+
"""Test _handle_a2a_response returns None when message converter returns None."""
916+
mock_a2a_message = Mock(spec=A2AMessage)
917+
mock_a2a_message.context_id = "context-123"
918+
919+
with patch(
920+
"google.adk.agents.remote_a2a_agent.convert_a2a_message_to_event"
921+
) as mock_convert:
922+
mock_convert.return_value = None
923+
924+
result = await self.agent._handle_a2a_response(
925+
mock_a2a_message, self.mock_context
926+
)
927+
928+
assert result is None
929+
mock_convert.assert_called_once_with(
930+
mock_a2a_message,
931+
self.agent.name,
932+
self.mock_context,
933+
self.mock_a2a_part_converter,
934+
)
935+
913936
@pytest.mark.asyncio
914937
async def test_handle_a2a_response_with_task_completed_and_no_update(self):
915938
"""Test successful A2A response handling with non-streaming task and no update."""
@@ -1925,6 +1948,27 @@ async def test_handle_a2a_response_impl_update_converter_returns_none(self):
19251948
self.mock_config.a2a_part_converter,
19261949
)
19271950

1951+
@pytest.mark.asyncio
1952+
async def test_handle_a2a_response_impl_message_converter_returns_none(self):
1953+
"""Test _handle_a2a_response_v2 returns None when message converter returns None."""
1954+
mock_a2a_message = Mock(spec=A2AMessage)
1955+
mock_a2a_message.metadata = {}
1956+
mock_a2a_message.context_id = "context-123"
1957+
1958+
self.mock_config.a2a_message_converter.return_value = None
1959+
1960+
result = await self.agent._handle_a2a_response_v2(
1961+
mock_a2a_message, self.mock_context
1962+
)
1963+
1964+
assert result is None
1965+
self.mock_config.a2a_message_converter.assert_called_once_with(
1966+
mock_a2a_message,
1967+
self.agent.name,
1968+
self.mock_context,
1969+
self.mock_config.a2a_part_converter,
1970+
)
1971+
19281972
@pytest.mark.asyncio
19291973
async def test_handle_a2a_response_impl_unknown_response_type(self):
19301974
"""Test _handle_a2a_response_impl with unknown response type."""

0 commit comments

Comments
 (0)