2727OpenAIResponsesCompactionMode = Literal ["previous_response_id" , "input" , "auto" ]
2828
2929
30+ def _is_user_message_item (item : TResponseInputItem ) -> bool :
31+ if not isinstance (item , dict ):
32+ return False
33+ if item .get ("type" ) == "message" :
34+ return item .get ("role" ) == "user"
35+ return item .get ("role" ) == "user" and "content" in item
36+
37+
3038def select_compaction_candidate_items (
3139 items : list [TResponseInputItem ],
3240) -> list [TResponseInputItem ]:
@@ -35,18 +43,12 @@ def select_compaction_candidate_items(
3543 Excludes user messages and compaction items.
3644 """
3745
38- def _is_user_message (item : TResponseInputItem ) -> bool :
39- if not isinstance (item , dict ):
40- return False
41- if item .get ("type" ) == "message" :
42- return item .get ("role" ) == "user"
43- return item .get ("role" ) == "user" and "content" in item
44-
4546 return [
4647 item
4748 for item in items
4849 if not (
49- _is_user_message (item ) or (isinstance (item , dict ) and item .get ("type" ) == "compaction" )
50+ _is_user_message_item (item )
51+ or (isinstance (item , dict ) and item .get ("type" ) == "compaction" )
5052 )
5153 ]
5254
@@ -272,12 +274,12 @@ async def run_compaction(self, args: OpenAIResponsesCompactionArgs | None = None
272274 )
273275 return
274276
275- unresolved_function_calls = _find_unresolved_function_calls_without_results (session_items )
276- if unresolved_function_calls :
277+ frontier_unresolved_function_calls = _find_frontier_unresolved_function_calls (session_items )
278+ if frontier_unresolved_function_calls :
277279 logger .debug (
278280 "compact: blocked unresolved function calls for %s: %s" ,
279281 self ._response_id ,
280- unresolved_function_calls ,
282+ frontier_unresolved_function_calls ,
281283 )
282284 return
283285
@@ -436,12 +438,19 @@ def _clear_pending_local_history_rewrite(self) -> None:
436438_ResolvedCompactionMode = Literal ["previous_response_id" , "input" ]
437439
438440
439- def _find_unresolved_function_calls_without_results (items : list [TResponseInputItem ]) -> list [str ]:
440- """Return function-call ids that do not yet have matching outputs."""
441- function_calls : dict [str , TResponseInputItem ] = {}
441+ def _find_frontier_unresolved_function_calls (items : list [TResponseInputItem ]) -> list [str ]:
442+ """Return unresolved function-call ids that remain in the active conversation frontier.
443+
444+ Once a later user message appears, earlier unresolved tool calls are considered abandoned and
445+ should no longer block future compaction for the session.
446+ """
447+ function_call_indices : dict [str , int ] = {}
442448 resolved_call_ids : set [str ] = set ()
449+ last_user_message_index = - 1
443450
444- for item in items :
451+ for index , item in enumerate (items ):
452+ if _is_user_message_item (item ):
453+ last_user_message_index = index
445454 if isinstance (item , dict ):
446455 item_type = item .get ("type" )
447456 call_id = item .get ("call_id" )
@@ -452,11 +461,15 @@ def _find_unresolved_function_calls_without_results(items: list[TResponseInputIt
452461 if not isinstance (call_id , str ):
453462 continue
454463 if item_type == "function_call" :
455- function_calls [call_id ] = item
464+ function_call_indices [call_id ] = index
456465 elif item_type == "function_call_output" :
457466 resolved_call_ids .add (call_id )
458467
459- return [call_id for call_id in function_calls if call_id not in resolved_call_ids ]
468+ return [
469+ call_id
470+ for call_id , index in function_call_indices .items ()
471+ if call_id not in resolved_call_ids and index > last_user_message_index
472+ ]
460473
461474
462475def _resolve_compaction_mode (
0 commit comments