2828OpenAIResponsesCompactionMode = Literal ["previous_response_id" , "input" , "auto" ]
2929
3030
31+ def _is_user_message_item (item : TResponseInputItem ) -> bool :
32+ if not isinstance (item , dict ):
33+ return False
34+ if item .get ("type" ) == "message" :
35+ return item .get ("role" ) == "user"
36+ return item .get ("role" ) == "user" and "content" in item
37+
38+
3139def select_compaction_candidate_items (
3240 items : list [TResponseInputItem ],
3341) -> list [TResponseInputItem ]:
@@ -36,18 +44,12 @@ def select_compaction_candidate_items(
3644 Excludes user messages and compaction items.
3745 """
3846
39- def _is_user_message (item : TResponseInputItem ) -> bool :
40- if not isinstance (item , dict ):
41- return False
42- if item .get ("type" ) == "message" :
43- return item .get ("role" ) == "user"
44- return item .get ("role" ) == "user" and "content" in item
45-
4647 return [
4748 item
4849 for item in items
4950 if not (
50- _is_user_message (item ) or (isinstance (item , dict ) and item .get ("type" ) == "compaction" )
51+ _is_user_message_item (item )
52+ or (isinstance (item , dict ) and item .get ("type" ) == "compaction" )
5153 )
5254 ]
5355
@@ -273,12 +275,12 @@ async def run_compaction(self, args: OpenAIResponsesCompactionArgs | None = None
273275 )
274276 return
275277
276- unresolved_function_calls = _find_unresolved_function_calls_without_results (session_items )
277- if unresolved_function_calls :
278+ frontier_unresolved_function_calls = _find_frontier_unresolved_function_calls (session_items )
279+ if frontier_unresolved_function_calls :
278280 logger .debug (
279281 "compact: blocked unresolved function calls for %s: %s" ,
280282 self ._response_id ,
281- unresolved_function_calls ,
283+ frontier_unresolved_function_calls ,
282284 )
283285 return
284286
@@ -476,12 +478,19 @@ def _normalize_compaction_session_items(
476478_ResolvedCompactionMode = Literal ["previous_response_id" , "input" ]
477479
478480
479- def _find_unresolved_function_calls_without_results (items : list [TResponseInputItem ]) -> list [str ]:
480- """Return function-call ids that do not yet have matching outputs."""
481- function_calls : dict [str , TResponseInputItem ] = {}
481+ def _find_frontier_unresolved_function_calls (items : list [TResponseInputItem ]) -> list [str ]:
482+ """Return unresolved function-call ids that remain in the active conversation frontier.
483+
484+ Once a later user message appears, earlier unresolved tool calls are considered abandoned and
485+ should no longer block future compaction for the session.
486+ """
487+ function_call_indices : dict [str , int ] = {}
482488 resolved_call_ids : set [str ] = set ()
489+ last_user_message_index = - 1
483490
484- for item in items :
491+ for index , item in enumerate (items ):
492+ if _is_user_message_item (item ):
493+ last_user_message_index = index
485494 if isinstance (item , dict ):
486495 item_type = item .get ("type" )
487496 call_id = item .get ("call_id" )
@@ -492,11 +501,15 @@ def _find_unresolved_function_calls_without_results(items: list[TResponseInputIt
492501 if not isinstance (call_id , str ):
493502 continue
494503 if item_type == "function_call" :
495- function_calls [call_id ] = item
504+ function_call_indices [call_id ] = index
496505 elif item_type == "function_call_output" :
497506 resolved_call_ids .add (call_id )
498507
499- return [call_id for call_id in function_calls if call_id not in resolved_call_ids ]
508+ return [
509+ call_id
510+ for call_id , index in function_call_indices .items ()
511+ if call_id not in resolved_call_ids and index > last_user_message_index
512+ ]
500513
501514
502515def _resolve_compaction_mode (
0 commit comments