@@ -137,6 +137,9 @@ def __init__(self):
137137 # Track streaming message state
138138 self ._streaming_message_id : Optional [str ] = None # Current streaming message ID
139139 self ._is_streaming : bool = False # Whether we're currently streaming a message
140+ self ._current_stream_text : str = "" # Accumulates text for the active stream
141+ self ._last_streamed_text : Optional [str ] = None # Snapshot of most recently streamed text
142+ self ._last_streamed_run_id : Optional [str ] = None # Run identifier for the last streamed text
140143 self .long_running_tool_ids : List [str ] = [] # Track the long running tool IDs
141144
142145 async def translate (
@@ -261,8 +264,9 @@ async def _translate_text_content(
261264
262265 if not text_parts :
263266 return
264-
265-
267+
268+ combined_text = "" .join (text_parts )
269+
266270 # Use proper ADK streaming detection (handle None values)
267271 is_partial = getattr (adk_event , 'partial' , False )
268272 turn_complete = getattr (adk_event , 'turn_complete' , False )
@@ -288,57 +292,100 @@ async def _translate_text_content(
288292
289293 if is_final_response :
290294
291- # If a final text response wasn't streamed (not generated by an LLM) then deliver it in 3 events
292295 if not self ._is_streaming and not adk_event .usage_metadata and should_send_end :
293- logger .info (f"⏭️ Deliver non-llm response via message events "
294- f"event_id={ adk_event .id } " )
296+ logger .info (
297+ f"⏭️ Deliver non-llm response via message events event_id={ adk_event .id } "
298+ )
295299
296- combined_text = "" .join (text_parts )
297300 message_events = [
298301 TextMessageStartEvent (
299302 type = EventType .TEXT_MESSAGE_START ,
300303 message_id = adk_event .id ,
301- role = "assistant"
304+ role = "assistant" ,
302305 ),
303306 TextMessageContentEvent (
304307 type = EventType .TEXT_MESSAGE_CONTENT ,
305308 message_id = adk_event .id ,
306- delta = combined_text
309+ delta = combined_text ,
307310 ),
308311 TextMessageEndEvent (
309312 type = EventType .TEXT_MESSAGE_END ,
310- message_id = adk_event .id
311- )
313+ message_id = adk_event .id ,
314+ ),
312315 ]
313316 for msg in message_events :
314317 yield msg
315318
319+ self ._current_stream_text = ""
320+ self ._last_streamed_text = None
321+ self ._last_streamed_run_id = None
322+ return
323+
324+ if not self ._is_streaming and adk_event .usage_metadata :
325+ if (
326+ self ._last_streamed_text is None
327+ or self ._last_streamed_run_id != run_id
328+ or combined_text != self ._last_streamed_text
329+ ):
330+ logger .info (
331+ f"⏩ Deliver final response after stream due to new content event_id={ adk_event .id } "
332+ )
333+ message_events = [
334+ TextMessageStartEvent (
335+ type = EventType .TEXT_MESSAGE_START ,
336+ message_id = adk_event .id ,
337+ role = "assistant" ,
338+ ),
339+ TextMessageContentEvent (
340+ type = EventType .TEXT_MESSAGE_CONTENT ,
341+ message_id = adk_event .id ,
342+ delta = combined_text ,
343+ ),
344+ TextMessageEndEvent (
345+ type = EventType .TEXT_MESSAGE_END ,
346+ message_id = adk_event .id ,
347+ ),
348+ ]
349+ for msg in message_events :
350+ yield msg
351+ else :
352+ logger .info (
353+ "⏭️ Skipping final response event (duplicate content detected)"
354+ )
355+
356+ self ._current_stream_text = ""
357+ self ._last_streamed_text = None
358+ self ._last_streamed_run_id = None
359+ return
360+
316361 logger .info ("⏭️ Skipping final response event (content already streamed)" )
317-
318- # If we're currently streaming, this final response means we should end the stream
362+
319363 if self ._is_streaming and self ._streaming_message_id :
364+ if self ._current_stream_text :
365+ self ._last_streamed_text = self ._current_stream_text
366+ self ._last_streamed_run_id = run_id
367+ self ._current_stream_text = ""
368+
320369 end_event = TextMessageEndEvent (
321370 type = EventType .TEXT_MESSAGE_END ,
322371 message_id = self ._streaming_message_id
323372 )
324373 logger .info (f"📤 TEXT_MESSAGE_END (from final response): { end_event .model_dump_json ()} " )
325374 yield end_event
326-
327- # Reset streaming state
375+
328376 self ._streaming_message_id = None
329377 self ._is_streaming = False
330378 logger .info ("🏁 Streaming completed via final response" )
331-
379+
332380 return
333381
334- combined_text = "" .join (text_parts ) # Don't add newlines for streaming
335-
336382 # Handle streaming logic
337383 if not self ._is_streaming :
338384 # Start of new message - emit START event
339385 self ._streaming_message_id = str (uuid .uuid4 ())
340386 self ._is_streaming = True
341-
387+ self ._current_stream_text = ""
388+
342389 start_event = TextMessageStartEvent (
343390 type = EventType .TEXT_MESSAGE_START ,
344391 message_id = self ._streaming_message_id ,
@@ -349,6 +396,7 @@ async def _translate_text_content(
349396
350397 # Always emit content (unless empty)
351398 if combined_text :
399+ self ._current_stream_text += combined_text
352400 content_event = TextMessageContentEvent (
353401 type = EventType .TEXT_MESSAGE_CONTENT ,
354402 message_id = self ._streaming_message_id ,
@@ -365,8 +413,12 @@ async def _translate_text_content(
365413 )
366414 logger .info (f"📤 TEXT_MESSAGE_END: { end_event .model_dump_json ()} " )
367415 yield end_event
368-
416+
369417 # Reset streaming state
418+ if self ._current_stream_text :
419+ self ._last_streamed_text = self ._current_stream_text
420+ self ._last_streamed_run_id = run_id
421+ self ._current_stream_text = ""
370422 self ._streaming_message_id = None
371423 self ._is_streaming = False
372424 logger .info ("🏁 Streaming completed, state reset" )
@@ -556,15 +608,16 @@ async def force_close_streaming_message(self) -> AsyncGenerator[BaseEvent, None]
556608 """
557609 if self ._is_streaming and self ._streaming_message_id :
558610 logger .warning (f"🚨 Force-closing unterminated streaming message: { self ._streaming_message_id } " )
559-
611+
560612 end_event = TextMessageEndEvent (
561613 type = EventType .TEXT_MESSAGE_END ,
562614 message_id = self ._streaming_message_id
563615 )
564616 logger .info (f"📤 TEXT_MESSAGE_END (forced): { end_event .model_dump_json ()} " )
565617 yield end_event
566-
618+
567619 # Reset streaming state
620+ self ._current_stream_text = ""
568621 self ._streaming_message_id = None
569622 self ._is_streaming = False
570623 logger .info ("🔄 Streaming state reset after force-close" )
@@ -578,5 +631,8 @@ def reset(self):
578631 self ._active_tool_calls .clear ()
579632 self ._streaming_message_id = None
580633 self ._is_streaming = False
634+ self ._current_stream_text = ""
635+ self ._last_streamed_text = None
636+ self ._last_streamed_run_id = None
581637 self .long_running_tool_ids .clear ()
582638 logger .debug ("Reset EventTranslator state (including streaming state)" )
0 commit comments