@@ -84,6 +84,17 @@ def _is_tool_search_item(item: Any) -> bool:
8484 return item_type in {"tool_search_call" , "tool_search_output" }
8585
8686
87+ def _extract_call_id (item : Any ) -> str | None :
88+ """Return a tool call id from mapping or object payloads."""
89+ call_id = item .get ("call_id" ) if isinstance (item , dict ) else getattr (item , "call_id" , None )
90+ return call_id if isinstance (call_id , str ) else None
91+
92+
93+ def _has_output_payload (item : Any ) -> bool :
94+ """Return True when an item carries a local tool output payload."""
95+ return (isinstance (item , dict ) and "output" in item ) or hasattr (item , "output" )
96+
97+
8798@dataclass
8899class OpenAIServerConversationTracker :
89100 """Track server-side conversation state for conversation-aware runs.
@@ -141,6 +152,7 @@ def hydrate_from_state(
141152 generated_items : list [RunItem ],
142153 model_responses : list [ModelResponse ],
143154 session_items : list [TResponseInputItem ] | None = None ,
155+ unsent_tool_call_ids : set [str ] | None = None ,
144156 ) -> None :
145157 """Seed tracking from prior state so resumed runs do not replay already-sent content.
146158
@@ -151,6 +163,7 @@ def hydrate_from_state(
151163 """
152164 if self .sent_initial_input :
153165 return
166+ unsent_tool_call_ids = unsent_tool_call_ids or set ()
154167
155168 normalized_input = original_input
156169 if isinstance (original_input , list ):
@@ -189,13 +202,8 @@ def hydrate_from_state(
189202 )
190203 if item_id is not None :
191204 self .server_item_ids .add (item_id )
192- call_id = (
193- output_item .get ("call_id" )
194- if isinstance (output_item , dict )
195- else getattr (output_item , "call_id" , None )
196- )
197- has_output_payload = isinstance (output_item , dict ) and "output" in output_item
198- has_output_payload = has_output_payload or hasattr (output_item , "output" )
205+ call_id = _extract_call_id (output_item )
206+ has_output_payload = _has_output_payload (output_item )
199207 if isinstance (call_id , str ) and has_output_payload :
200208 self .server_tool_call_ids .add (call_id )
201209
@@ -209,13 +217,8 @@ def hydrate_from_state(
209217 )
210218 if item_id is not None :
211219 self .server_item_ids .add (item_id )
212- call_id = (
213- item .get ("call_id" )
214- if isinstance (item , dict )
215- else getattr (item , "call_id" , None )
216- )
217- has_output = isinstance (item , dict ) and "output" in item
218- has_output = has_output or hasattr (item , "output" )
220+ call_id = _extract_call_id (item )
221+ has_output = _has_output_payload (item )
219222 if isinstance (call_id , str ) and has_output :
220223 self .server_tool_call_ids .add (call_id )
221224 fp = _fingerprint_for_tracker (item )
@@ -237,10 +240,15 @@ def hydrate_from_state(
237240
238241 if isinstance (raw_item , dict ):
239242 item_id = _normalize_server_item_id (raw_item .get ("id" ))
240- call_id = raw_item .get ("call_id" )
241- has_output_payload = "output" in raw_item
242- has_output_payload = has_output_payload or hasattr (raw_item , "output" )
243+ call_id = _extract_call_id (raw_item )
244+ has_output_payload = _has_output_payload (raw_item )
243245 has_call_id = isinstance (call_id , str )
246+ if (
247+ isinstance (call_id , str )
248+ and has_output_payload
249+ and call_id in unsent_tool_call_ids
250+ ):
251+ continue
244252 should_mark = (
245253 item_id is not None
246254 or (has_call_id and (has_output_payload or is_tool_call_item ))
@@ -266,9 +274,15 @@ def hydrate_from_state(
266274 self .server_tool_call_ids .add (call_id )
267275 else :
268276 item_id = _normalize_server_item_id (getattr (raw_item , "id" , None ))
269- call_id = getattr (raw_item , "call_id" , None )
270- has_output_payload = hasattr (raw_item , "output" )
277+ call_id = _extract_call_id (raw_item )
278+ has_output_payload = _has_output_payload (raw_item )
271279 has_call_id = isinstance (call_id , str )
280+ if (
281+ isinstance (call_id , str )
282+ and has_output_payload
283+ and call_id in unsent_tool_call_ids
284+ ):
285+ continue
272286 should_mark = (
273287 item_id is not None
274288 or (has_call_id and (has_output_payload or is_tool_call_item ))
@@ -309,13 +323,8 @@ def track_server_items(self, model_response: ModelResponse | None) -> None:
309323 )
310324 if item_id is not None :
311325 self .server_item_ids .add (item_id )
312- call_id = (
313- output_item .get ("call_id" )
314- if isinstance (output_item , dict )
315- else getattr (output_item , "call_id" , None )
316- )
317- has_output_payload = isinstance (output_item , dict ) and "output" in output_item
318- has_output_payload = has_output_payload or hasattr (output_item , "output" )
326+ call_id = _extract_call_id (output_item )
327+ has_output_payload = _has_output_payload (output_item )
319328 if isinstance (call_id , str ) and has_output_payload :
320329 self .server_tool_call_ids .add (call_id )
321330 fp = _fingerprint_for_tracker (output_item )
@@ -445,13 +454,8 @@ def prepare_input(
445454 if item_id is not None and item_id in self .server_item_ids :
446455 continue
447456
448- call_id = (
449- raw_item .get ("call_id" )
450- if isinstance (raw_item , dict )
451- else getattr (raw_item , "call_id" , None )
452- )
453- has_output_payload = isinstance (raw_item , dict ) and "output" in raw_item
454- has_output_payload = has_output_payload or hasattr (raw_item , "output" )
457+ call_id = _extract_call_id (raw_item )
458+ has_output_payload = _has_output_payload (raw_item )
455459 if (
456460 isinstance (call_id , str )
457461 and has_output_payload
0 commit comments