@@ -297,85 +297,128 @@ def _create_turn_from_db_metadata(
297297 )
298298
299299
300- def build_conversation_turns_from_items (
300+ def _group_items_into_turns (
301301 items : list [ItemListResponse ],
302- turns_metadata : list [UserTurn ],
303- conversation_start_time : datetime ,
304- ) -> list [ConversationTurn ]:
305- """Build conversation turns from Conversations API items and turns metadata.
302+ ) -> list [list [ItemListResponse ]]:
303+ """Group conversation items into turns.
304+
305+ Each turn starts with a user message. All subsequent messages and tool items
306+ belong to that turn until the next user message.
306307
307308 Args:
308309 items: Conversation items list from Conversations API, oldest first
309- turns_metadata: List of UserTurn database objects ordered by turn_number.
310- Can be empty for legacy conversations without stored metadata.
311- conversation_start_time: Timestamp to use for dummy metadata in legacy conversations.
312- Typically the conversation's created_at timestamp.
313310
314311 Returns:
315- List of ConversationTurn objects, oldest first
312+ List of turns, where each turn is a list of items belonging to that turn
316313 """
317- chat_history : list [ConversationTurn ] = []
318- current_messages : list [Message ] = []
319- current_tool_calls : list [ToolCallSummary ] = []
320- current_tool_results : list [ToolResultSummary ] = []
321- current_turn_index = 0
314+ turns : list [list [ItemListResponse ]] = []
315+ current_turn_items : list [ItemListResponse ] = []
322316
323317 for item in items :
324318 item_type = getattr (item , "type" , None )
325319
326- # Parse message items
320+ # User message marks the beginning of a new turn
327321 if item_type == "message" :
328322 message_item = cast (MessageOutput , item )
329- message = _parse_message_item (message_item )
330-
331- # User message marks the beginning of a new turn
332- if message .type == "user" :
323+ if message_item .role == "user" :
333324 # If we have accumulated items, finish the previous turn
334- if current_messages or current_tool_calls or current_tool_results :
335- turn_metadata = (
336- turns_metadata [current_turn_index ]
337- if current_turn_index < len (turns_metadata )
338- else _create_dummy_turn_metadata (conversation_start_time )
339- )
340- chat_history .append (
341- _create_turn_from_db_metadata (
342- turn_metadata ,
343- current_messages ,
344- current_tool_calls ,
345- current_tool_results ,
346- )
347- )
348- current_turn_index += 1
325+ if current_turn_items :
326+ turns .append (current_turn_items )
327+ current_turn_items = []
349328
350329 # Start new turn with this user message
351- current_messages = [message ]
352- current_tool_calls = []
353- current_tool_results = []
330+ current_turn_items = [item ]
354331 else :
355332 # Add non-user message to current turn
356- current_messages .append (message )
333+ current_turn_items .append (item )
334+ else :
335+ # Add tool-related items to current turn
336+ current_turn_items .append (item )
357337
358- # Parse tool-related items
338+ # Add final turn if there are items
339+ if current_turn_items :
340+ turns .append (current_turn_items )
341+
342+ return turns
343+
344+
345+ def _process_turn_items (
346+ turn_items : list [ItemListResponse ],
347+ ) -> tuple [list [Message ], list [ToolCallSummary ], list [ToolResultSummary ]]:
348+ """Process items from a single turn into messages, tool calls, and tool results.
349+
350+ Args:
351+ turn_items: List of items belonging to a single turn
352+
353+ Returns:
354+ Tuple of (messages, tool_calls, tool_results)
355+ """
356+ messages : list [Message ] = []
357+ tool_calls : list [ToolCallSummary ] = []
358+ tool_results : list [ToolResultSummary ] = []
359+
360+ for item in turn_items :
361+ item_type = getattr (item , "type" , None )
362+
363+ if item_type == "message" :
364+ message_item = cast (MessageOutput , item )
365+ message = _parse_message_item (message_item )
366+ messages .append (message )
359367 else :
360368 tool_call , tool_result = _build_tool_call_summary_from_item (item )
361369 if tool_call is not None :
362- current_tool_calls .append (tool_call )
370+ tool_calls .append (tool_call )
363371 if tool_result is not None :
364- current_tool_results .append (tool_result )
372+ tool_results .append (tool_result )
365373
366- # Add final turn if there are items
367- if current_messages or current_tool_calls or current_tool_results :
368- turn_metadata = (
369- turns_metadata [current_turn_index ]
370- if current_turn_index < len (turns_metadata )
371- else _create_dummy_turn_metadata (conversation_start_time )
372- )
374+ return messages , tool_calls , tool_results
375+
376+
377+ def build_conversation_turns_from_items (
378+ items : list [ItemListResponse ],
379+ turns_metadata : list [UserTurn ],
380+ conversation_start_time : datetime ,
381+ ) -> list [ConversationTurn ]:
382+ """Build conversation turns from Conversations API items and turns metadata.
383+
384+ Args:
385+ items: Conversation items list from Conversations API, oldest first
386+ turns_metadata: List of UserTurn database objects ordered by turn_number.
387+ Can be empty for legacy conversations without stored metadata.
388+ For extended legacy conversations, only the newer turns have metadata.
389+ conversation_start_time: Timestamp to use for dummy metadata in legacy conversations.
390+ Typically the conversation's created_at timestamp.
391+
392+ Returns:
393+ List of ConversationTurn objects, oldest first
394+ """
395+ # Group items into turns first
396+ turn_items_list = _group_items_into_turns (items )
397+
398+ # Calculate how many legacy turns don't have metadata
399+ total_turns = len (turn_items_list )
400+ legacy_turns_count = total_turns - len (turns_metadata )
401+
402+ # Process each turn with its corresponding metadata
403+ chat_history : list [ConversationTurn ] = []
404+ for turn_index , turn_items in enumerate (turn_items_list ):
405+ # Process items into messages, tool calls, and tool results
406+ messages , tool_calls , tool_results = _process_turn_items (turn_items )
407+
408+ # Select appropriate metadata for this turn
409+ if turn_index < legacy_turns_count :
410+ turn_metadata = _create_dummy_turn_metadata (conversation_start_time )
411+ else :
412+ metadata_index = turn_index - legacy_turns_count
413+ turn_metadata = turns_metadata [metadata_index ]
414+
415+ # Create ConversationTurn from metadata and processed items
373416 chat_history .append (
374417 _create_turn_from_db_metadata (
375418 turn_metadata ,
376- current_messages ,
377- current_tool_calls ,
378- current_tool_results ,
419+ messages ,
420+ tool_calls ,
421+ tool_results ,
379422 )
380423 )
381424
0 commit comments