diff --git a/src/agents/realtime/openai_realtime.py b/src/agents/realtime/openai_realtime.py index 02cb55a1a6..f3c1240f62 100644 --- a/src/agents/realtime/openai_realtime.py +++ b/src/agents/realtime/openai_realtime.py @@ -2011,7 +2011,9 @@ def conversation_item_to_realtime_message_item( "type": item.type, "role": item.role, "content": content, - "status": "in_progress", + # Keep the server's status: hardcoding "in_progress" regressed + # completed items every time one was retrieved (#4597). + "status": item.status or "in_progress", }, ) diff --git a/tests/realtime/test_item_parsing.py b/tests/realtime/test_item_parsing.py index e8484a58f6..a9ddfcda23 100644 --- a/tests/realtime/test_item_parsing.py +++ b/tests/realtime/test_item_parsing.py @@ -78,3 +78,33 @@ def test_system_message_conversion() -> None: ) assert isinstance(converted, SystemMessageItem) + + +def test_status_is_taken_from_the_server_item() -> None: + """A retrieved item reports its real status; overriding it regressed history (#4597).""" + item = RealtimeConversationItemAssistantMessage( + id="123", + type="message", + role="assistant", + status="completed", + content=[AssistantMessageContent(type="output_text", text="hi")], + ) + + converted = _ConversionHelper.conversation_item_to_realtime_message_item(item, None) + + assert isinstance(converted, AssistantMessageItem) + assert converted.status == "completed" + + +def test_status_defaults_to_in_progress_when_the_server_omits_it() -> None: + item = RealtimeConversationItemAssistantMessage( + id="123", + type="message", + role="assistant", + content=[AssistantMessageContent(type="output_text", text="hi")], + ) + + converted = _ConversionHelper.conversation_item_to_realtime_message_item(item, None) + + assert isinstance(converted, AssistantMessageItem) + assert converted.status == "in_progress" diff --git a/tests/realtime/test_openai_realtime.py b/tests/realtime/test_openai_realtime.py index eaf532bee9..fa76f7c2b2 100644 --- a/tests/realtime/test_openai_realtime.py +++ b/tests/realtime/test_openai_realtime.py @@ -553,6 +553,29 @@ async def test_raw_event_preserves_null_previous_item_id(self, model): item_updated_event = mock_listener.on_event.call_args_list[1][0][0] assert item_updated_event.item.previous_item_id == "" + @pytest.mark.asyncio + async def test_retrieved_item_keeps_the_status_the_server_reported(self, model): + """A retrieve of a finished item must not regress it to in_progress (#4597).""" + mock_listener = AsyncMock() + model.add_listener(mock_listener) + server_event = { + "type": "conversation.item.retrieved", + "event_id": "event_1", + "item": { + "id": "item_1", + "type": "message", + "role": "assistant", + "status": "completed", + "content": [{"type": "output_audio", "transcript": "hello there"}], + }, + } + + await model._handle_ws_event(server_event) + + assert mock_listener.on_event.call_count == 2 + item_updated_event = mock_listener.on_event.call_args_list[1][0][0] + assert item_updated_event.item.status == "completed" + @pytest.mark.asyncio async def test_handle_malformed_json_logs_error_continues(self, model): """Test that malformed JSON emits error event but doesn't crash."""