@@ -92,13 +92,14 @@ def add_in_progress_task_response(
9292 context_id : str = "test-context" ,
9393 state : TaskState = TaskState .working ,
9494 text : str | None = None ,
95+ role : A2ARole = A2ARole .agent ,
9596 ) -> None :
9697 """Add a mock in-progress Task response (non-terminal)."""
9798 message = None
9899 if text is not None :
99100 message = A2AMessage (
100101 message_id = str (uuid4 ()),
101- role = A2ARole . agent ,
102+ role = role ,
102103 parts = [Part (root = TextPart (text = text ))],
103104 )
104105 status = TaskStatus (state = state , message = message )
@@ -110,6 +111,7 @@ async def send_message(self, message: Any) -> AsyncIterator[Any]:
110111 """Mock send_message method that yields responses."""
111112 self .call_count += 1
112113
114+ # All queued responses are delivered as a single streaming batch per call.
113115 for response in self .responses :
114116 yield response
115117 self .responses .clear ()
@@ -1101,4 +1103,90 @@ async def test_streaming_working_update_without_message_is_skipped(
11011103 assert updates [0 ].contents [0 ].text == "Result"
11021104
11031105
1106+ async def test_streaming_working_update_user_role_mapping (a2a_agent : A2AAgent , mock_a2a_client : MockA2AClient ) -> None :
1107+ """Test that A2ARole.user in status message maps to role='user'."""
1108+ mock_a2a_client .add_in_progress_task_response ("task-u" , context_id = "ctx-u" , text = "User echo" , role = A2ARole .user )
1109+ mock_a2a_client .add_task_response ("task-u" , [{"id" : "art-u" , "content" : "Done" }])
1110+
1111+ updates : list [AgentResponseUpdate ] = []
1112+ async for update in a2a_agent .run ("Hello" , stream = True ):
1113+ updates .append (update )
1114+
1115+ assert len (updates ) == 2
1116+ assert updates [0 ].contents [0 ].text == "User echo"
1117+ assert updates [0 ].role == "user"
1118+
1119+
1120+ async def test_background_with_status_message_yields_continuation_token (
1121+ a2a_agent : A2AAgent , mock_a2a_client : MockA2AClient
1122+ ) -> None :
1123+ """Test that background=True takes precedence over status message content."""
1124+ mock_a2a_client .add_in_progress_task_response ("task-bg" , context_id = "ctx-bg" , text = "Should be ignored" )
1125+
1126+ updates : list [AgentResponseUpdate ] = []
1127+ async for update in a2a_agent .run ("Hello" , stream = True , background = True ):
1128+ updates .append (update )
1129+
1130+ assert len (updates ) == 1
1131+ assert updates [0 ].continuation_token is not None
1132+ assert updates [0 ].continuation_token ["task_id" ] == "task-bg"
1133+ assert updates [0 ].contents == []
1134+
1135+
1136+ async def test_non_streaming_does_not_surface_intermediate_messages (
1137+ a2a_agent : A2AAgent , mock_a2a_client : MockA2AClient
1138+ ) -> None :
1139+ """Test that run(stream=False) does not include intermediate status messages."""
1140+ mock_a2a_client .add_in_progress_task_response ("task-ns" , context_id = "ctx-ns" , text = "Intermediate" )
1141+ mock_a2a_client .add_task_response ("task-ns" , [{"id" : "art-ns" , "content" : "Final" }])
1142+
1143+ response = await a2a_agent .run ("Hello" )
1144+
1145+ assert len (response .messages ) == 1
1146+ assert response .messages [0 ].text == "Final"
1147+
1148+
1149+ async def test_terminal_no_artifacts_after_working_with_content (
1150+ a2a_agent : A2AAgent , mock_a2a_client : MockA2AClient
1151+ ) -> None :
1152+ """Test that a terminal task with no artifacts after working-state messages does not re-emit the working content."""
1153+ mock_a2a_client .add_in_progress_task_response ("task-t" , context_id = "ctx-t" , text = "Working on it..." )
1154+ # Terminal task with no artifacts and no history
1155+ status = TaskStatus (state = TaskState .completed , message = None )
1156+ task = Task (id = "task-t" , context_id = "ctx-t" , status = status )
1157+ mock_a2a_client .responses .append ((task , None ))
1158+
1159+ updates : list [AgentResponseUpdate ] = []
1160+ async for update in a2a_agent .run ("Hello" , stream = True ):
1161+ updates .append (update )
1162+
1163+ assert len (updates ) == 2
1164+ assert updates [0 ].contents [0 ].text == "Working on it..."
1165+ # Terminal task with no artifacts yields an empty-contents update
1166+ assert updates [1 ].contents == []
1167+
1168+
1169+ async def test_streaming_working_update_with_empty_parts_is_skipped (
1170+ a2a_agent : A2AAgent , mock_a2a_client : MockA2AClient
1171+ ) -> None :
1172+ """Test that a working update with status.message but empty parts list is skipped."""
1173+ # Construct a message with an empty parts list (distinct from message=None)
1174+ message = A2AMessage (
1175+ message_id = str (uuid4 ()),
1176+ role = A2ARole .agent ,
1177+ parts = [],
1178+ )
1179+ status = TaskStatus (state = TaskState .working , message = message )
1180+ task = Task (id = "task-ep" , context_id = "ctx-ep" , status = status )
1181+ mock_a2a_client .responses .append ((task , None ))
1182+ mock_a2a_client .add_task_response ("task-ep" , [{"id" : "art-ep" , "content" : "Result" }])
1183+
1184+ updates : list [AgentResponseUpdate ] = []
1185+ async for update in a2a_agent .run ("Hello" , stream = True ):
1186+ updates .append (update )
1187+
1188+ assert len (updates ) == 1
1189+ assert updates [0 ].contents [0 ].text == "Result"
1190+
1191+
11041192# endregion
0 commit comments