Skip to content

Commit 7f3eff8

Browse files
committed
Avoid leaking realtime history list
1 parent afc59f0 commit 7f3eff8

File tree

2 files changed

+26
-5
lines changed

2 files changed

+26
-5
lines changed

src/agents/realtime/session.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ async def __aenter__(self) -> RealtimeSession:
186186
# Emit initial history update
187187
await self._put_event(
188188
RealtimeHistoryUpdated(
189-
history=self._history,
189+
history=list(self._history),
190190
info=self._event_info,
191191
)
192192
)
@@ -290,7 +290,7 @@ async def on_event(self, event: RealtimeModelEvent) -> None:
290290
await self._put_event(RealtimeHistoryAdded(info=self._event_info, item=new_item))
291291
else:
292292
await self._put_event(
293-
RealtimeHistoryUpdated(info=self._event_info, history=self._history)
293+
RealtimeHistoryUpdated(info=self._event_info, history=list(self._history))
294294
)
295295
elif event.type == "input_audio_timeout_triggered":
296296
await self._put_event(
@@ -314,7 +314,7 @@ async def on_event(self, event: RealtimeModelEvent) -> None:
314314
),
315315
)
316316
await self._put_event(
317-
RealtimeHistoryUpdated(info=self._event_info, history=self._history)
317+
RealtimeHistoryUpdated(info=self._event_info, history=list(self._history))
318318
)
319319

320320
# Check if we should run guardrails based on debounce threshold
@@ -387,13 +387,13 @@ async def on_event(self, event: RealtimeModelEvent) -> None:
387387
await self._put_event(RealtimeHistoryAdded(info=self._event_info, item=new_item))
388388
else:
389389
await self._put_event(
390-
RealtimeHistoryUpdated(info=self._event_info, history=self._history)
390+
RealtimeHistoryUpdated(info=self._event_info, history=list(self._history))
391391
)
392392
elif event.type == "item_deleted":
393393
deleted_id = event.item_id
394394
self._history = [item for item in self._history if item.item_id != deleted_id]
395395
await self._put_event(
396-
RealtimeHistoryUpdated(info=self._event_info, history=self._history)
396+
RealtimeHistoryUpdated(info=self._event_info, history=list(self._history))
397397
)
398398
elif event.type == "connection_status":
399399
pass

tests/realtime/test_session.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,27 @@ async def test_transcript_delta_updates_history_and_emits_history_updated(
590590
updated_history_item = cast(AssistantMessageItem, history_event.history[0])
591591
assert cast(AssistantAudio, updated_history_item.content[0]).transcript == "hello"
592592

593+
@pytest.mark.asyncio
594+
async def test_transcript_delta_history_updated_uses_list_snapshot(
595+
self, mock_model, mock_agent
596+
):
597+
session = RealtimeSession(mock_model, mock_agent, None)
598+
599+
await session.on_event(
600+
RealtimeModelTranscriptDeltaEvent(
601+
item_id="item_1", delta="hello", response_id="resp_1"
602+
)
603+
)
604+
605+
await session._event_queue.get() # raw event
606+
history_event = await session._event_queue.get()
607+
assert isinstance(history_event, RealtimeHistoryUpdated)
608+
609+
history_event.history.clear()
610+
611+
assert len(session._history) == 1
612+
assert session._history[0].item_id == "item_1"
613+
593614
@pytest.mark.asyncio
594615
async def test_ignored_events_only_generate_raw_events(self, mock_model, mock_agent):
595616
"""Test that ignored events (connection_status, other) only generate raw events"""

0 commit comments

Comments
 (0)