diff --git a/src/agents/run_internal/oai_conversation.py b/src/agents/run_internal/oai_conversation.py index 44d0d1465b..0f6a9b1a6a 100644 --- a/src/agents/run_internal/oai_conversation.py +++ b/src/agents/run_internal/oai_conversation.py @@ -156,10 +156,11 @@ def hydrate_from_state( if isinstance(original_input, list): normalized_input = prepare_model_input_items(original_input) + # Hydrated initial input is reconstructed during resume, so object identity is not a + # stable dedupe key and can later collide with unrelated freshly allocated items. for item in ItemHelpers.input_to_new_input_list(normalized_input): if item is None: continue - self.sent_items.add(id(item)) item_id = _normalize_server_item_id( item.get("id") if isinstance(item, dict) else getattr(item, "id", None) ) diff --git a/tests/test_server_conversation_tracker.py b/tests/test_server_conversation_tracker.py index baafac6fda..408f0446c0 100644 --- a/tests/test_server_conversation_tracker.py +++ b/tests/test_server_conversation_tracker.py @@ -84,6 +84,41 @@ def test_prepare_input_filters_items_seen_by_server_and_tool_calls() -> None: assert tracker.remaining_initial_input is None +def test_hydrate_from_state_does_not_track_string_initial_input_by_object_identity() -> None: + tracker = OpenAIServerConversationTracker( + conversation_id="conv-init-string", previous_response_id=None + ) + + tracker.hydrate_from_state( + original_input="hello", + generated_items=[], + model_responses=[], + ) + + assert tracker.sent_items == set() + assert tracker.sent_initial_input is True + assert tracker.remaining_initial_input is None + assert len(tracker.sent_item_fingerprints) == 1 + + +def test_hydrate_from_state_does_not_track_list_initial_input_by_object_identity() -> None: + tracker = OpenAIServerConversationTracker( + conversation_id="conv-init-list", previous_response_id=None + ) + original_input = [cast(TResponseInputItem, {"role": "user", "content": "hello"})] + + tracker.hydrate_from_state( + original_input=original_input, + generated_items=[], + model_responses=[], + ) + + assert tracker.sent_items == set() + assert tracker.sent_initial_input is True + assert tracker.remaining_initial_input is None + assert len(tracker.sent_item_fingerprints) == 1 + + def test_mark_input_as_sent_and_rewind_input_respects_remaining_initial_input() -> None: tracker = OpenAIServerConversationTracker(conversation_id="conv2", previous_response_id=None) pending_1: TResponseInputItem = cast(TResponseInputItem, {"id": "p-1", "type": "message"})