2929from google .adk .a2a .converters .event_converter import _process_long_running_tool
3030from google .adk .a2a .converters .event_converter import _serialize_metadata_value
3131from google .adk .a2a .converters .event_converter import ARTIFACT_ID_SEPARATOR
32+ from google .adk .a2a .converters .event_converter import convert_a2a_message_to_event
3233from google .adk .a2a .converters .event_converter import convert_a2a_task_to_event
3334from google .adk .a2a .converters .event_converter import convert_event_to_a2a_events
3435from google .adk .a2a .converters .part_converter import convert_genai_part_to_a2a_part
@@ -766,6 +767,7 @@ def test_convert_a2a_task_to_event_with_history_message(self):
766767
767768 # Create mock message and task
768769 mock_message = Mock (spec = Message )
770+ mock_message .role = _compat .ROLE_AGENT
769771 mock_task = Mock (spec = Task )
770772 mock_task .artifacts = None
771773 mock_task .status = None
@@ -859,7 +861,6 @@ def test_convert_a2a_task_to_event_message_conversion_error(self):
859861
860862 def test_convert_a2a_message_to_event_success (self ):
861863 """Test successful conversion of A2A message to event."""
862- from google .adk .a2a .converters .event_converter import convert_a2a_message_to_event
863864
864865 # Use a real A2A part (production reads its metadata); the part_converter
865866 # callback is still mocked to return a canned genai Part.
@@ -868,6 +869,7 @@ def test_convert_a2a_message_to_event_success(self):
868869 mock_convert_part = Mock (return_value = mock_genai_part )
869870
870871 mock_message = Mock (spec = Message , parts = [mock_a2a_part ])
872+ mock_message .role = _compat .ROLE_AGENT
871873
872874 result = convert_a2a_message_to_event (
873875 mock_message ,
@@ -887,7 +889,6 @@ def test_convert_a2a_message_to_event_success(self):
887889
888890 def test_convert_a2a_message_to_event_with_multiple_parts_returned (self ):
889891 """Test message to event conversion when part_converter returns multiple parts."""
890- from google .adk .a2a .converters .event_converter import convert_a2a_message_to_event
891892
892893 # Arrange
893894 mock_a2a_part = _compat .make_text_part ("part 1" )
@@ -896,6 +897,7 @@ def test_convert_a2a_message_to_event_with_multiple_parts_returned(self):
896897 mock_convert_part = Mock (return_value = [mock_genai_part1 , mock_genai_part2 ])
897898
898899 mock_message = Mock (spec = Message , parts = [mock_a2a_part ])
900+ mock_message .role = _compat .ROLE_AGENT
899901
900902 # Act
901903 result = convert_a2a_message_to_event (
@@ -914,10 +916,10 @@ def test_convert_a2a_message_to_event_with_multiple_parts_returned(self):
914916
915917 def test_convert_a2a_message_to_event_with_long_running_tools (self ):
916918 """Test conversion with long-running tools by mocking the entire flow."""
917- from google .adk .a2a .converters .event_converter import convert_a2a_message_to_event
918919
919920 # Create mock parts and message
920921 mock_message = Mock (spec = Message , parts = [Mock ()])
922+ mock_message .role = _compat .ROLE_AGENT
921923
922924 # Mock the part conversion to return None to simulate long-running tool detection logic
923925 mock_convert_part = Mock (return_value = None )
@@ -941,9 +943,9 @@ def test_convert_a2a_message_to_event_with_long_running_tools(self):
941943
942944 def test_convert_a2a_message_to_event_empty_parts (self ):
943945 """Test conversion with empty parts list."""
944- from google .adk .a2a .converters .event_converter import convert_a2a_message_to_event
945946
946947 mock_message = Mock (spec = Message , parts = [])
948+ mock_message .role = _compat .ROLE_AGENT
947949
948950 result = convert_a2a_message_to_event (
949951 mock_message , "test-author" , self .mock_invocation_context
@@ -957,20 +959,19 @@ def test_convert_a2a_message_to_event_empty_parts(self):
957959
958960 def test_convert_a2a_message_to_event_none_message (self ):
959961 """Test converting None message raises ValueError."""
960- from google .adk .a2a .converters .event_converter import convert_a2a_message_to_event
961962
962963 with pytest .raises (ValueError , match = "A2A message cannot be None" ):
963964 convert_a2a_message_to_event (None )
964965
965966 def test_convert_a2a_message_to_event_part_conversion_fails (self ):
966967 """Test handling when part conversion returns None."""
967- from google .adk .a2a .converters .event_converter import convert_a2a_message_to_event
968968
969969 # Setup mock to return None (conversion failure)
970970 mock_a2a_part = Mock ()
971971 mock_convert_part = Mock (return_value = None )
972972
973973 mock_message = Mock (spec = Message , parts = [mock_a2a_part ])
974+ mock_message .role = _compat .ROLE_AGENT
974975
975976 result = convert_a2a_message_to_event (
976977 mock_message ,
@@ -987,7 +988,6 @@ def test_convert_a2a_message_to_event_part_conversion_fails(self):
987988
988989 def test_convert_a2a_message_to_event_part_conversion_exception (self ):
989990 """Test handling when part conversion raises exception."""
990- from google .adk .a2a .converters .event_converter import convert_a2a_message_to_event
991991
992992 # Setup mock to raise exception. The A2A parts are real (production
993993 # reads their metadata); the converter callback drives the behavior.
@@ -1003,6 +1003,7 @@ def test_convert_a2a_message_to_event_part_conversion_exception(self):
10031003 )
10041004
10051005 mock_message = Mock (spec = Message , parts = [mock_a2a_part1 , mock_a2a_part2 ])
1006+ mock_message .role = _compat .ROLE_AGENT
10061007
10071008 result = convert_a2a_message_to_event (
10081009 mock_message ,
@@ -1020,10 +1021,10 @@ def test_convert_a2a_message_to_event_part_conversion_exception(self):
10201021
10211022 def test_convert_a2a_message_to_event_missing_tool_id (self ):
10221023 """Test handling of message conversion when part conversion fails."""
1023- from google .adk .a2a .converters .event_converter import convert_a2a_message_to_event
10241024
10251025 # Create mock parts and message
10261026 mock_message = Mock (spec = Message , parts = [Mock ()])
1027+ mock_message .role = _compat .ROLE_AGENT
10271028
10281029 # Mock the part conversion to return None
10291030 mock_convert_part = Mock (return_value = None )
@@ -1045,9 +1046,9 @@ def test_convert_a2a_message_to_event_missing_tool_id(self):
10451046 @patch ("google.adk.a2a.converters.event_converter.platform_uuid.new_uuid" )
10461047 def test_convert_a2a_message_to_event_default_author (self , mock_uuid ):
10471048 """Test conversion with default author and no invocation context."""
1048- from google .adk .a2a .converters .event_converter import convert_a2a_message_to_event
10491049
10501050 mock_message = Mock (spec = Message , parts = [])
1051+ mock_message .role = _compat .ROLE_AGENT
10511052
10521053 # Mock UUID generation
10531054 mock_uuid .return_value = "generated-uuid"
@@ -1058,3 +1059,116 @@ def test_convert_a2a_message_to_event_default_author(self, mock_uuid):
10581059 assert result .author == "a2a agent"
10591060 assert result .branch is None
10601061 assert result .invocation_id == "generated-uuid"
1062+
1063+
1064+ class TestRoleMappingRegression :
1065+ """Regression tests for issue #5186: role mapping in A2A→ADK conversion."""
1066+
1067+ def setup_method (self ):
1068+ """Set up test fixtures."""
1069+ self .mock_invocation_context = Mock (spec = InvocationContext )
1070+ self .mock_invocation_context .invocation_id = "test-invocation-id"
1071+ self .mock_invocation_context .branch = "test-branch"
1072+
1073+ def test_user_role_message_maps_to_user_content_role (self ):
1074+ """A2A Role.user must produce content.role='user', not 'model'."""
1075+ message = _compat .make_message (
1076+ message_id = "msg-1" ,
1077+ role = _compat .ROLE_USER ,
1078+ parts = [_compat .make_text_part ("user says hi" )],
1079+ )
1080+
1081+ event = convert_a2a_message_to_event (
1082+ message , "test-author" , self .mock_invocation_context
1083+ )
1084+
1085+ assert event .content .role == "user"
1086+
1087+ def test_agent_role_message_maps_to_model_content_role (self ):
1088+ """A2A Role.agent must produce content.role='model'."""
1089+ message = _compat .make_message (
1090+ message_id = "msg-1" ,
1091+ role = _compat .ROLE_AGENT ,
1092+ parts = [_compat .make_text_part ("agent reply" )],
1093+ )
1094+
1095+ event = convert_a2a_message_to_event (
1096+ message , "test-author" , self .mock_invocation_context
1097+ )
1098+
1099+ assert event .content .role == "model"
1100+
1101+ def test_empty_parts_user_message_preserves_user_role (self ):
1102+ """Even with empty parts, Role.user must map to content.role='user'."""
1103+ message = _compat .make_message (
1104+ message_id = "msg-1" ,
1105+ role = _compat .ROLE_USER ,
1106+ parts = [],
1107+ )
1108+
1109+ event = convert_a2a_message_to_event (
1110+ message , "test-author" , self .mock_invocation_context
1111+ )
1112+
1113+ assert event .content .role == "user"
1114+
1115+ def test_task_history_fallback_skips_trailing_user_message (self ):
1116+ """History fallback must not return a user-role trailing message."""
1117+ agent_msg = _compat .make_message (
1118+ message_id = "m1" ,
1119+ role = _compat .ROLE_AGENT ,
1120+ parts = [_compat .make_text_part ("agent reply" )],
1121+ )
1122+ user_msg = _compat .make_message (
1123+ message_id = "m2" ,
1124+ role = _compat .ROLE_USER ,
1125+ parts = [_compat .make_text_part ("follow-up question" )],
1126+ )
1127+
1128+ status = _compat .make_task_status (_compat .TS_SUBMITTED )
1129+ task = _compat .make_task (
1130+ id = "task-1" ,
1131+ status = status ,
1132+ context_id = "ctx-1" ,
1133+ history = [agent_msg , user_msg ],
1134+ )
1135+
1136+ with patch (
1137+ "google.adk.a2a.converters.event_converter.convert_a2a_message_to_event"
1138+ ) as mock_convert :
1139+ mock_event = Mock (spec = Event )
1140+ mock_convert .return_value = mock_event
1141+
1142+ convert_a2a_task_to_event (
1143+ task , "test-author" , self .mock_invocation_context
1144+ )
1145+
1146+ # Must be called with the agent message, not the trailing user message
1147+ mock_convert .assert_called_once ()
1148+ called_message = mock_convert .call_args [0 ][0 ]
1149+ assert called_message .role == _compat .ROLE_AGENT
1150+ assert called_message .message_id == "m1"
1151+
1152+ def test_task_history_fallback_only_user_messages_creates_minimal_event (self ):
1153+ """History with only user messages must produce a minimal event."""
1154+ user_msg = _compat .make_message (
1155+ message_id = "m1" ,
1156+ role = _compat .ROLE_USER ,
1157+ parts = [_compat .make_text_part ("question" )],
1158+ )
1159+
1160+ status = _compat .make_task_status (_compat .TS_SUBMITTED )
1161+ task = _compat .make_task (
1162+ id = "task-1" ,
1163+ status = status ,
1164+ context_id = "ctx-1" ,
1165+ history = [user_msg ],
1166+ )
1167+
1168+ result = convert_a2a_task_to_event (
1169+ task , "test-author" , self .mock_invocation_context
1170+ )
1171+
1172+ # No agent message to convert → minimal event (no content)
1173+ assert result .author == "test-author"
1174+ assert result .content is None
0 commit comments