@@ -759,3 +759,76 @@ def test_full_conversation(self):
759759 assert len (result ) == 2
760760 assert result [0 ].parts [0 ].text == 'Great'
761761 assert result [1 ].parts [0 ].text == 'Tell me more'
762+
763+
764+ class TestResponseInteractionDeduplication :
765+ """Tests for response interaction api deduplication."""
766+
767+ def test_is_final_response_requires_turn_complete_true (self ):
768+ """Verify is_final_response() returns False when turn_complete=False."""
769+ # Case 1: partial=False but turn_complete=False -> should be False
770+ event = Event (
771+ author = 'agent' ,
772+ content = types .Content (role = 'model' , parts = [types .Part (text = 'Hello' )]),
773+ partial = False ,
774+ turn_complete = False , # Turn not complete
775+ )
776+ assert event .is_final_response () is False
777+
778+ # Case 2: partial=False and turn_complete=True -> should be True
779+ event_final = Event (
780+ author = 'agent' ,
781+ content = types .Content (role = 'model' , parts = [types .Part (text = 'Hello' )]),
782+ partial = False ,
783+ turn_complete = True , # Turn complete
784+ )
785+ assert event_final .is_final_response () is True
786+
787+ def test_no_duplicate_final_response_in_streaming (self ):
788+ """Verify streaming events don't duplicate the final response."""
789+ from unittest .mock import MagicMock
790+
791+ # Simulate the streaming flow
792+ aggregated_parts = []
793+
794+ # Event 1: content.delta (streaming text)
795+ delta_event = MagicMock ()
796+ delta_event .event_type = 'content.delta'
797+ delta_event .delta = MagicMock ()
798+ delta_event .delta .type = 'text'
799+ delta_event .delta .text = 'Hello'
800+
801+ response1 = interactions_utils .convert_interaction_event_to_llm_response (
802+ delta_event , aggregated_parts , 'interaction_123'
803+ )
804+ assert response1 .partial is True
805+ assert response1 .turn_complete is False
806+
807+ # Event 2: content.stop (content complete but turn not complete)
808+ stop_event = MagicMock ()
809+ stop_event .event_type = 'content.stop'
810+
811+ response2 = interactions_utils .convert_interaction_event_to_llm_response (
812+ stop_event , aggregated_parts , 'interaction_123'
813+ )
814+ assert response2 .partial is False
815+ assert response2 .turn_complete is False # Not final yet
816+
817+ # Event 3: interaction.status_update with completed (final response)
818+ status_event = MagicMock ()
819+ status_event .event_type = 'interaction.status_update'
820+ status_event .status = 'completed'
821+
822+ response3 = interactions_utils .convert_interaction_event_to_llm_response (
823+ status_event , aggregated_parts , 'interaction_123'
824+ )
825+ assert response3 .partial is False
826+ assert response3 .turn_complete is True # This is the final response
827+
828+ # Verify: Only response3 should have turn_complete=True
829+ # This proves no duplication - there's only ONE final response
830+ final_responses = [
831+ r for r in [response1 , response2 , response3 ] if r .turn_complete
832+ ]
833+ assert len (final_responses ) == 1
834+ assert final_responses [0 ] == response3
0 commit comments