@@ -10,6 +10,7 @@ use crate::function_tool::FunctionCallError;
1010use crate :: mcp:: auth:: McpAuthStatusEntry ;
1111use crate :: parse_command:: parse_command;
1212use crate :: parse_turn_item;
13+ use crate :: response_processing:: process_items;
1314use crate :: review_format:: format_review_findings_block;
1415use crate :: terminal;
1516use crate :: user_notification:: UserNotifier ;
@@ -855,7 +856,7 @@ impl Session {
855856
856857 /// Records input items: always append to conversation history and
857858 /// persist these response items to rollout.
858- async fn record_conversation_items ( & self , items : & [ ResponseItem ] ) {
859+ pub ( crate ) async fn record_conversation_items ( & self , items : & [ ResponseItem ] ) {
859860 self . record_into_history ( items) . await ;
860861 self . persist_rollout_response_items ( items) . await ;
861862 }
@@ -1608,109 +1609,13 @@ pub(crate) async fn run_task(
16081609 let token_limit_reached = total_usage_tokens
16091610 . map ( |tokens| tokens >= limit)
16101611 . unwrap_or ( false ) ;
1611- let mut items_to_record_in_conversation_history = Vec :: < ResponseItem > :: new ( ) ;
1612- let mut responses = Vec :: < ResponseInputItem > :: new ( ) ;
1613- for processed_response_item in processed_items {
1614- let ProcessedResponseItem { item, response } = processed_response_item;
1615- match ( & item, & response) {
1616- ( ResponseItem :: Message { role, .. } , None ) if role == "assistant" => {
1617- // If the model returned a message, we need to record it.
1618- items_to_record_in_conversation_history. push ( item) ;
1619- }
1620- (
1621- ResponseItem :: LocalShellCall { .. } ,
1622- Some ( ResponseInputItem :: FunctionCallOutput { call_id, output } ) ,
1623- ) => {
1624- items_to_record_in_conversation_history. push ( item) ;
1625- items_to_record_in_conversation_history. push (
1626- ResponseItem :: FunctionCallOutput {
1627- call_id : call_id. clone ( ) ,
1628- output : output. clone ( ) ,
1629- } ,
1630- ) ;
1631- }
1632- (
1633- ResponseItem :: FunctionCall { .. } ,
1634- Some ( ResponseInputItem :: FunctionCallOutput { call_id, output } ) ,
1635- ) => {
1636- items_to_record_in_conversation_history. push ( item) ;
1637- items_to_record_in_conversation_history. push (
1638- ResponseItem :: FunctionCallOutput {
1639- call_id : call_id. clone ( ) ,
1640- output : output. clone ( ) ,
1641- } ,
1642- ) ;
1643- }
1644- (
1645- ResponseItem :: CustomToolCall { .. } ,
1646- Some ( ResponseInputItem :: CustomToolCallOutput { call_id, output } ) ,
1647- ) => {
1648- items_to_record_in_conversation_history. push ( item) ;
1649- items_to_record_in_conversation_history. push (
1650- ResponseItem :: CustomToolCallOutput {
1651- call_id : call_id. clone ( ) ,
1652- output : output. clone ( ) ,
1653- } ,
1654- ) ;
1655- }
1656- (
1657- ResponseItem :: FunctionCall { .. } ,
1658- Some ( ResponseInputItem :: McpToolCallOutput { call_id, result } ) ,
1659- ) => {
1660- items_to_record_in_conversation_history. push ( item) ;
1661- let output = match result {
1662- Ok ( call_tool_result) => {
1663- convert_call_tool_result_to_function_call_output_payload (
1664- call_tool_result,
1665- )
1666- }
1667- Err ( err) => FunctionCallOutputPayload {
1668- content : err. clone ( ) ,
1669- success : Some ( false ) ,
1670- } ,
1671- } ;
1672- items_to_record_in_conversation_history. push (
1673- ResponseItem :: FunctionCallOutput {
1674- call_id : call_id. clone ( ) ,
1675- output,
1676- } ,
1677- ) ;
1678- }
1679- (
1680- ResponseItem :: Reasoning {
1681- id,
1682- summary,
1683- content,
1684- encrypted_content,
1685- } ,
1686- None ,
1687- ) => {
1688- items_to_record_in_conversation_history. push ( ResponseItem :: Reasoning {
1689- id : id. clone ( ) ,
1690- summary : summary. clone ( ) ,
1691- content : content. clone ( ) ,
1692- encrypted_content : encrypted_content. clone ( ) ,
1693- } ) ;
1694- }
1695- _ => {
1696- warn ! ( "Unexpected response item: {item:?} with response: {response:?}" ) ;
1697- }
1698- } ;
1699- if let Some ( response) = response {
1700- responses. push ( response) ;
1701- }
1702- }
1703-
1704- // Only attempt to take the lock if there is something to record.
1705- if !items_to_record_in_conversation_history. is_empty ( ) {
1706- if is_review_mode {
1707- review_thread_history
1708- . record_items ( items_to_record_in_conversation_history. iter ( ) ) ;
1709- } else {
1710- sess. record_conversation_items ( & items_to_record_in_conversation_history)
1711- . await ;
1712- }
1713- }
1612+ let ( responses, items_to_record_in_conversation_history) = process_items (
1613+ processed_items,
1614+ is_review_mode,
1615+ & mut review_thread_history,
1616+ & sess,
1617+ )
1618+ . await ;
17141619
17151620 if token_limit_reached {
17161621 if auto_compact_recently_attempted {
@@ -1749,7 +1654,16 @@ pub(crate) async fn run_task(
17491654 }
17501655 continue ;
17511656 }
1752- Err ( CodexErr :: TurnAborted ) => {
1657+ Err ( CodexErr :: TurnAborted {
1658+ dangling_artifacts : processed_items,
1659+ } ) => {
1660+ let _ = process_items (
1661+ processed_items,
1662+ is_review_mode,
1663+ & mut review_thread_history,
1664+ & sess,
1665+ )
1666+ . await ;
17531667 // Aborted turn is reported via a different event.
17541668 break ;
17551669 }
@@ -1850,7 +1764,13 @@ async fn run_turn(
18501764 . await
18511765 {
18521766 Ok ( output) => return Ok ( output) ,
1853- Err ( CodexErr :: TurnAborted ) => return Err ( CodexErr :: TurnAborted ) ,
1767+ Err ( CodexErr :: TurnAborted {
1768+ dangling_artifacts : processed_items,
1769+ } ) => {
1770+ return Err ( CodexErr :: TurnAborted {
1771+ dangling_artifacts : processed_items,
1772+ } ) ;
1773+ }
18541774 Err ( CodexErr :: Interrupted ) => return Err ( CodexErr :: Interrupted ) ,
18551775 Err ( CodexErr :: EnvVar ( var) ) => return Err ( CodexErr :: EnvVar ( var) ) ,
18561776 Err ( e @ CodexErr :: Fatal ( _) ) => return Err ( e) ,
@@ -1903,9 +1823,9 @@ async fn run_turn(
19031823/// "handled" such that it produces a `ResponseInputItem` that needs to be
19041824/// sent back to the model on the next turn.
19051825#[ derive( Debug ) ]
1906- pub ( crate ) struct ProcessedResponseItem {
1907- pub ( crate ) item : ResponseItem ,
1908- pub ( crate ) response : Option < ResponseInputItem > ,
1826+ pub struct ProcessedResponseItem {
1827+ pub item : ResponseItem ,
1828+ pub response : Option < ResponseInputItem > ,
19091829}
19101830
19111831#[ derive( Debug ) ]
@@ -1954,7 +1874,15 @@ async fn try_run_turn(
19541874 // Poll the next item from the model stream. We must inspect *both* Ok and Err
19551875 // cases so that transient stream failures (e.g., dropped SSE connection before
19561876 // `response.completed`) bubble up and trigger the caller's retry logic.
1957- let event = stream. next ( ) . or_cancel ( & cancellation_token) . await ?;
1877+ let event = match stream. next ( ) . or_cancel ( & cancellation_token) . await {
1878+ Ok ( event) => event,
1879+ Err ( codex_async_utils:: CancelErr :: Cancelled ) => {
1880+ let processed_items = output. try_collect ( ) . await ?;
1881+ return Err ( CodexErr :: TurnAborted {
1882+ dangling_artifacts : processed_items,
1883+ } ) ;
1884+ }
1885+ } ;
19581886
19591887 let event = match event {
19601888 Some ( res) => res?,
@@ -1978,7 +1906,8 @@ async fn try_run_turn(
19781906 let payload_preview = call. payload . log_payload ( ) . into_owned ( ) ;
19791907 tracing:: info!( "ToolCall: {} {}" , call. tool_name, payload_preview) ;
19801908
1981- let response = tool_runtime. handle_tool_call ( call) ;
1909+ let response =
1910+ tool_runtime. handle_tool_call ( call, cancellation_token. child_token ( ) ) ;
19821911
19831912 output. push_back (
19841913 async move {
@@ -2060,12 +1989,7 @@ async fn try_run_turn(
20601989 } => {
20611990 sess. update_token_usage_info ( turn_context. as_ref ( ) , token_usage. as_ref ( ) )
20621991 . await ;
2063-
2064- let processed_items = output
2065- . try_collect ( )
2066- . or_cancel ( & cancellation_token)
2067- . await ??;
2068-
1992+ let processed_items = output. try_collect ( ) . await ?;
20691993 let unified_diff = {
20701994 let mut tracker = turn_diff_tracker. lock ( ) . await ;
20711995 tracker. get_unified_diff ( )
@@ -2169,7 +2093,7 @@ pub(super) fn get_last_assistant_message_from_turn(responses: &[ResponseItem]) -
21692093 }
21702094 } )
21712095}
2172- fn convert_call_tool_result_to_function_call_output_payload (
2096+ pub ( crate ) fn convert_call_tool_result_to_function_call_output_payload (
21732097 call_tool_result : & CallToolResult ,
21742098) -> FunctionCallOutputPayload {
21752099 let CallToolResult {
0 commit comments