From 250fb97fefc4ecaa6a84ee07309741efa66d5da5 Mon Sep 17 00:00:00 2001 From: Aditya Singh <60082699+adityasingh2400@users.noreply.github.com> Date: Fri, 8 May 2026 10:42:05 -0700 Subject: [PATCH] fix(run): preserve last known response_id on conversation resume (#3245) --- src/agents/run_internal/oai_conversation.py | 12 ++++-- tests/test_oaiconv_resume_response_id.py | 44 +++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 tests/test_oaiconv_resume_response_id.py diff --git a/src/agents/run_internal/oai_conversation.py b/src/agents/run_internal/oai_conversation.py index 84d638f7..4a0e0883 100644 --- a/src/agents/run_internal/oai_conversation.py +++ b/src/agents/run_internal/oai_conversation.py @@ -189,8 +189,14 @@ class OpenAIServerConversationTracker: self.sent_initial_input = True self.remaining_initial_input = None - latest_response = model_responses[-1] if model_responses else None + # Pick the most recent response that actually carries an id; live runs preserve the + # last-known id via track_server_items, so resume must mirror that behavior instead of + # blindly using model_responses[-1] (which may have response_id=None for non-Responses + # providers and would silently break the chain). + latest_response_id: str | None = None for response in model_responses: + if response.response_id is not None: + latest_response_id = response.response_id for output_item in response.output: if output_item is None: continue @@ -207,8 +213,8 @@ class OpenAIServerConversationTracker: if isinstance(call_id, str) and has_output_payload: self.server_tool_call_ids.add(call_id) - if self.conversation_id is None and latest_response and latest_response.response_id: - self.previous_response_id = latest_response.response_id + if self.conversation_id is None and latest_response_id is not None: + self.previous_response_id = latest_response_id if session_items: for item in session_items: diff --git a/tests/test_oaiconv_resume_response_id.py b/tests/test_oaiconv_resume_response_id.py new file mode 100644 index 00000000..133ecd10 --- /dev/null +++ b/tests/test_oaiconv_resume_response_id.py @@ -0,0 +1,44 @@ +"""Tests for OpenAIServerConversationTracker.hydrate_from_state response_id seeding.""" + +from typing import Any + +from agents.items import ModelResponse +from agents.run_internal.oai_conversation import OpenAIServerConversationTracker +from agents.usage import Usage + + +def _make_response(response_id: str | None) -> ModelResponse: + response = object.__new__(ModelResponse) + response.output = [] + response.usage = Usage() + response.response_id = response_id + return response + + +def test_hydrate_from_state_uses_latest_non_none_response_id() -> None: + """Resume should chain to the most recent response_id, not None when last response lacks id. + + A run might produce model responses across providers where some have no `response_id` + (e.g., a non-Responses fallback). `track_server_items` skips updates when response_id is + None, so live runs preserve the last known id. Resume hydration should match that + behavior — falling back to the last id-bearing response instead of forgetting the chain. + """ + tracker = OpenAIServerConversationTracker( + conversation_id=None, + previous_response_id=None, + auto_previous_response_id=True, + ) + + responses: list[Any] = [ + _make_response("resp_first"), + _make_response("resp_second"), + _make_response(None), + ] + + tracker.hydrate_from_state( + original_input=[], + generated_items=[], + model_responses=responses, + ) + + assert tracker.previous_response_id == "resp_second"