Skip to content

Commit 5912835

Browse files
GWealecopybara-github
authored andcommitted
fix: Add checks for event content and parts before accessing
Close #3769 Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 853327479
1 parent b30c2f4 commit 5912835

File tree

2 files changed

+93
-24
lines changed

2 files changed

+93
-24
lines changed

src/google/adk/agents/remote_a2a_agent.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,8 @@ async def _handle_a2a_response(
420420
TaskState.submitted,
421421
TaskState.working,
422422
)
423+
and event.content is not None
424+
and event.content.parts
423425
):
424426
event.content.parts[0].thought = True
425427
elif (
@@ -431,7 +433,7 @@ async def _handle_a2a_response(
431433
event = convert_a2a_message_to_event(
432434
update.status.message, self.name, ctx, self._a2a_part_converter
433435
)
434-
if event.content and update.status.state in (
436+
if event.content is not None and update.status.state in (
435437
TaskState.submitted,
436438
TaskState.working,
437439
):

tests/unittests/agents/test_remote_a2a_agent.py

Lines changed: 90 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,18 @@
2929
from a2a.types import AgentSkill
3030
from a2a.types import Artifact
3131
from a2a.types import Message as A2AMessage
32-
from a2a.types import Part as A2ATaskStatus
3332
from a2a.types import SendMessageSuccessResponse
3433
from a2a.types import Task as A2ATask
3534
from a2a.types import TaskArtifactUpdateEvent
3635
from a2a.types import TaskState
37-
from a2a.types import TaskStatus
36+
from a2a.types import TaskStatus as A2ATaskStatus
3837
from a2a.types import TaskStatusUpdateEvent
3938
from a2a.types import TextPart
4039
from google.adk.agents.invocation_context import InvocationContext
4140
from google.adk.agents.remote_a2a_agent import A2A_METADATA_PREFIX
4241
from google.adk.agents.remote_a2a_agent import AgentCardResolutionError
4342
from google.adk.agents.remote_a2a_agent import RemoteA2aAgent
43+
import google.adk.agents.remote_a2a_agent as remote_a2a_agent
4444
from google.adk.events.event import Event
4545
from google.adk.sessions.session import Session
4646
from google.genai import types as genai_types
@@ -579,7 +579,7 @@ def test_create_a2a_request_for_user_function_response_success(self):
579579
"google.adk.agents.remote_a2a_agent.convert_event_to_a2a_message"
580580
) as mock_convert:
581581
# Create a proper mock A2A message
582-
mock_a2a_message = Mock(spec=A2AMessage)
582+
mock_a2a_message = create_autospec(A2AMessage, instance=True)
583583
mock_a2a_message.task_id = None # Will be set by the method
584584
mock_convert.return_value = mock_a2a_message
585585

@@ -716,8 +716,10 @@ async def test_handle_a2a_response_with_task_completed_and_no_update(self):
716716
content=genai_types.Content(role="model", parts=[mock_a2a_part]),
717717
)
718718

719-
with patch(
720-
"google.adk.agents.remote_a2a_agent.convert_a2a_task_to_event"
719+
with patch.object(
720+
remote_a2a_agent,
721+
"convert_a2a_task_to_event",
722+
autospec=True,
721723
) as mock_convert:
722724
mock_convert.return_value = mock_event
723725

@@ -821,8 +823,10 @@ async def test_handle_a2a_response_with_task_submitted_and_no_update(self):
821823
content=genai_types.Content(role="model", parts=[mock_a2a_part]),
822824
)
823825

824-
with patch(
825-
"google.adk.agents.remote_a2a_agent.convert_a2a_task_to_event"
826+
with patch.object(
827+
remote_a2a_agent,
828+
"convert_a2a_task_to_event",
829+
autospec=True,
826830
) as mock_convert:
827831
mock_convert.return_value = mock_event
828832

@@ -845,6 +849,59 @@ async def test_handle_a2a_response_with_task_submitted_and_no_update(self):
845849
assert A2A_METADATA_PREFIX + "task_id" in result.custom_metadata
846850
assert A2A_METADATA_PREFIX + "context_id" in result.custom_metadata
847851

852+
@pytest.mark.asyncio
853+
@pytest.mark.parametrize(
854+
"task_state,event_content",
855+
[
856+
pytest.param(
857+
TaskState.submitted,
858+
genai_types.Content(role="model", parts=[]),
859+
id="submitted_empty_parts",
860+
),
861+
pytest.param(
862+
TaskState.working,
863+
None,
864+
id="working_no_content",
865+
),
866+
],
867+
)
868+
async def test_handle_a2a_response_with_task_missing_content(
869+
self, task_state, event_content
870+
):
871+
"""Test streaming A2A response handling when content/parts are missing.
872+
873+
This verifies the fix for issue #3769 where the code could raise when it
874+
tried to read parts[0] without checking for empty/missing content.
875+
"""
876+
mock_a2a_task = create_autospec(A2ATask, instance=True)
877+
mock_a2a_task.id = "task-123"
878+
mock_a2a_task.context_id = "context-123"
879+
mock_a2a_task.status = create_autospec(A2ATaskStatus, instance=True)
880+
mock_a2a_task.status.state = task_state
881+
882+
mock_event = Event(
883+
author=self.agent.name,
884+
invocation_id=self.mock_context.invocation_id,
885+
branch=self.mock_context.branch,
886+
content=event_content,
887+
)
888+
889+
with patch.object(
890+
remote_a2a_agent,
891+
"convert_a2a_task_to_event",
892+
autospec=True,
893+
) as mock_convert:
894+
mock_convert.return_value = mock_event
895+
896+
result = await self.agent._handle_a2a_response(
897+
(mock_a2a_task, None), self.mock_context
898+
)
899+
900+
assert result == mock_event
901+
assert result.custom_metadata is not None
902+
assert A2A_METADATA_PREFIX + "task_id" in result.custom_metadata
903+
assert A2A_METADATA_PREFIX + "context_id" in result.custom_metadata
904+
848905
@pytest.mark.asyncio
849906
async def test_handle_a2a_response_with_task_working_and_no_update(self):
850907
"""Test successful A2A response handling with streaming task and no update."""
@@ -863,8 +920,10 @@ async def test_handle_a2a_response_with_task_working_and_no_update(self):
863920
content=genai_types.Content(role="model", parts=[mock_a2a_part]),
864921
)
865922

866-
with patch(
867-
"google.adk.agents.remote_a2a_agent.convert_a2a_task_to_event"
923+
with patch.object(
924+
remote_a2a_agent,
925+
"convert_a2a_task_to_event",
926+
autospec=True,
868927
) as mock_convert:
869928
mock_convert.return_value = mock_event
870929

@@ -896,7 +955,7 @@ async def test_handle_a2a_response_with_task_status_update_with_message(self):
896955

897956
mock_a2a_message = Mock(spec=A2AMessage)
898957
mock_update = Mock(spec=TaskStatusUpdateEvent)
899-
mock_update.status = Mock(TaskStatus)
958+
mock_update.status = Mock(A2ATaskStatus)
900959
mock_update.status.state = TaskState.completed
901960
mock_update.status.message = mock_a2a_message
902961

@@ -942,7 +1001,7 @@ async def test_handle_a2a_response_with_task_status_working_update_with_message(
9421001

9431002
mock_a2a_message = Mock(spec=A2AMessage)
9441003
mock_update = Mock(spec=TaskStatusUpdateEvent)
945-
mock_update.status = Mock(TaskStatus)
1004+
mock_update.status = Mock(A2ATaskStatus)
9461005
mock_update.status.state = TaskState.working
9471006
mock_update.status.message = mock_a2a_message
9481007

@@ -984,7 +1043,7 @@ async def test_handle_a2a_response_with_task_status_update_no_message(self):
9841043
mock_a2a_task.id = "task-123"
9851044

9861045
mock_update = Mock(spec=TaskStatusUpdateEvent)
987-
mock_update.status = Mock(TaskStatus)
1046+
mock_update.status = Mock(A2ATaskStatus)
9881047
mock_update.status.state = TaskState.completed
9891048
mock_update.status.message = None
9901049

@@ -1014,8 +1073,10 @@ async def test_handle_a2a_response_with_artifact_update(self):
10141073
branch=self.mock_context.branch,
10151074
)
10161075

1017-
with patch(
1018-
"google.adk.agents.remote_a2a_agent.convert_a2a_task_to_event"
1076+
with patch.object(
1077+
remote_a2a_agent,
1078+
"convert_a2a_task_to_event",
1079+
autospec=True,
10191080
) as mock_convert:
10201081
mock_convert.return_value = mock_event
10211082

@@ -1222,8 +1283,10 @@ async def test_handle_a2a_response_with_task_completed_and_no_update(self):
12221283
content=genai_types.Content(role="model", parts=[mock_a2a_part]),
12231284
)
12241285

1225-
with patch(
1226-
"google.adk.agents.remote_a2a_agent.convert_a2a_task_to_event"
1286+
with patch.object(
1287+
remote_a2a_agent,
1288+
"convert_a2a_task_to_event",
1289+
autospec=True,
12271290
) as mock_convert:
12281291
mock_convert.return_value = mock_event
12291292

@@ -1263,8 +1326,10 @@ async def test_handle_a2a_response_with_task_submitted_and_no_update(self):
12631326
content=genai_types.Content(role="model", parts=[mock_a2a_part]),
12641327
)
12651328

1266-
with patch(
1267-
"google.adk.agents.remote_a2a_agent.convert_a2a_task_to_event"
1329+
with patch.object(
1330+
remote_a2a_agent,
1331+
"convert_a2a_task_to_event",
1332+
autospec=True,
12681333
) as mock_convert:
12691334
mock_convert.return_value = mock_event
12701335

@@ -1296,7 +1361,7 @@ async def test_handle_a2a_response_with_task_status_update_with_message(self):
12961361

12971362
mock_a2a_message = Mock(spec=A2AMessage)
12981363
mock_update = Mock(spec=TaskStatusUpdateEvent)
1299-
mock_update.status = Mock(TaskStatus)
1364+
mock_update.status = Mock(A2ATaskStatus)
13001365
mock_update.status.state = TaskState.completed
13011366
mock_update.status.message = mock_a2a_message
13021367

@@ -1342,7 +1407,7 @@ async def test_handle_a2a_response_with_task_status_working_update_with_message(
13421407

13431408
mock_a2a_message = Mock(spec=A2AMessage)
13441409
mock_update = Mock(spec=TaskStatusUpdateEvent)
1345-
mock_update.status = Mock(TaskStatus)
1410+
mock_update.status = Mock(A2ATaskStatus)
13461411
mock_update.status.state = TaskState.working
13471412
mock_update.status.message = mock_a2a_message
13481413

@@ -1384,7 +1449,7 @@ async def test_handle_a2a_response_with_task_status_update_no_message(self):
13841449
mock_a2a_task.id = "task-123"
13851450

13861451
mock_update = Mock(spec=TaskStatusUpdateEvent)
1387-
mock_update.status = Mock(TaskStatus)
1452+
mock_update.status = Mock(A2ATaskStatus)
13881453
mock_update.status.state = TaskState.completed
13891454
mock_update.status.message = None
13901455

@@ -1414,8 +1479,10 @@ async def test_handle_a2a_response_with_artifact_update(self):
14141479
branch=self.mock_context.branch,
14151480
)
14161481

1417-
with patch(
1418-
"google.adk.agents.remote_a2a_agent.convert_a2a_task_to_event"
1482+
with patch.object(
1483+
remote_a2a_agent,
1484+
"convert_a2a_task_to_event",
1485+
autospec=True,
14191486
) as mock_convert:
14201487
mock_convert.return_value = mock_event
14211488

0 commit comments

Comments
 (0)