From 8bfefed9f2c4663e5e4ef85f1dce8d533ce7f352 Mon Sep 17 00:00:00 2001 From: pragnyanramtha Date: Sun, 17 May 2026 04:26:11 +0000 Subject: [PATCH] fix: complete realtime history on audio done --- src/agents/realtime/session.py | 30 ++++++++++++++++++++++++++++++ tests/realtime/test_session.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/agents/realtime/session.py b/src/agents/realtime/session.py index 2b45ccaed6..06c806e419 100644 --- a/src/agents/realtime/session.py +++ b/src/agents/realtime/session.py @@ -303,6 +303,12 @@ async def on_event(self, event: RealtimeModelEvent) -> None: info=self._event_info, item_id=event.item_id, content_index=event.content_index ) ) + new_history = self._mark_assistant_item_completed(self._history, event.item_id) + if new_history is not self._history: + self._history = new_history + await self._put_event( + RealtimeHistoryUpdated(info=self._event_info, history=self._history) + ) elif event.type == "input_audio_transcription_completed": prev_len = len(self._history) self._history = RealtimeSession._get_new_history(self._history, event) @@ -786,6 +792,30 @@ async def _handle_tool_call( ) ) + @staticmethod + def _mark_assistant_item_completed( + old_history: list[RealtimeItem], + item_id: str, + ) -> list[RealtimeItem]: + existing_index = next( + (i for i, item in enumerate(old_history) if item.item_id == item_id), + None, + ) + if existing_index is None: + return old_history + + existing_item = old_history[existing_index] + if ( + existing_item.type != "message" + or existing_item.role != "assistant" + or existing_item.status == "completed" + ): + return old_history + + new_history = old_history.copy() + new_history[existing_index] = existing_item.model_copy(update={"status": "completed"}) + return new_history + @classmethod def _get_new_history( cls, diff --git a/tests/realtime/test_session.py b/tests/realtime/test_session.py index 67cf717aa5..0dabff964d 100644 --- a/tests/realtime/test_session.py +++ b/tests/realtime/test_session.py @@ -426,6 +426,35 @@ async def test_audio_events_transformation(self, mock_model, mock_agent): done_session_event = await session._event_queue.get() assert isinstance(done_session_event, RealtimeAudioEnd) + @pytest.mark.asyncio + async def test_audio_done_marks_assistant_history_item_completed(self, mock_model, mock_agent): + """Test that audio done events complete matching assistant history items.""" + session = RealtimeSession( + mock_model, mock_agent, None, run_config={"async_tool_calls": False} + ) + session._history = [ + AssistantMessageItem( + item_id="item_1", + role="assistant", + status="in_progress", + content=[AssistantAudio(audio=None, transcript="Hello")], + ) + ] + + await session.on_event(RealtimeModelAudioDoneEvent(item_id="item_1", content_index=0)) + + updated_item = cast(AssistantMessageItem, session._history[0]) + assert updated_item.status == "completed" + + assert session._event_queue.qsize() == 3 + await session._event_queue.get() # raw event + audio_done_event = await session._event_queue.get() + assert isinstance(audio_done_event, RealtimeAudioEnd) + history_event = await session._event_queue.get() + assert isinstance(history_event, RealtimeHistoryUpdated) + history_item = cast(AssistantMessageItem, history_event.history[0]) + assert history_item.status == "completed" + @pytest.mark.asyncio async def test_turn_events_transformation(self, mock_model, mock_agent): """Test that turn start/end events are properly transformed"""