@@ -11,6 +11,7 @@ use crate::function_tool::FunctionCallError;
1111use crate :: mcp:: auth:: McpAuthStatusEntry ;
1212use crate :: parse_command:: parse_command;
1313use crate :: parse_turn_item;
14+ use crate :: response_processing:: process_items;
1415use crate :: review_format:: format_review_findings_block;
1516use crate :: terminal;
1617use crate :: user_notification:: UserNotifier ;
@@ -893,7 +894,7 @@ impl Session {
893894
894895 /// Records input items: always append to conversation history and
895896 /// persist these response items to rollout.
896- async fn record_conversation_items ( & self , items : & [ ResponseItem ] ) {
897+ pub ( crate ) async fn record_conversation_items ( & self , items : & [ ResponseItem ] ) {
897898 self . record_into_history ( items) . await ;
898899 self . persist_rollout_response_items ( items) . await ;
899900 }
@@ -1786,109 +1787,13 @@ pub(crate) async fn run_task(
17861787 let token_limit_reached = total_usage_tokens
17871788 . map ( |tokens| tokens >= limit)
17881789 . unwrap_or ( false ) ;
1789- let mut items_to_record_in_conversation_history = Vec :: < ResponseItem > :: new ( ) ;
1790- let mut responses = Vec :: < ResponseInputItem > :: new ( ) ;
1791- for processed_response_item in processed_items {
1792- let ProcessedResponseItem { item, response } = processed_response_item;
1793- match ( & item, & response) {
1794- ( ResponseItem :: Message { role, .. } , None ) if role == "assistant" => {
1795- // If the model returned a message, we need to record it.
1796- items_to_record_in_conversation_history. push ( item) ;
1797- }
1798- (
1799- ResponseItem :: LocalShellCall { .. } ,
1800- Some ( ResponseInputItem :: FunctionCallOutput { call_id, output } ) ,
1801- ) => {
1802- items_to_record_in_conversation_history. push ( item) ;
1803- items_to_record_in_conversation_history. push (
1804- ResponseItem :: FunctionCallOutput {
1805- call_id : call_id. clone ( ) ,
1806- output : output. clone ( ) ,
1807- } ,
1808- ) ;
1809- }
1810- (
1811- ResponseItem :: FunctionCall { .. } ,
1812- Some ( ResponseInputItem :: FunctionCallOutput { call_id, output } ) ,
1813- ) => {
1814- items_to_record_in_conversation_history. push ( item) ;
1815- items_to_record_in_conversation_history. push (
1816- ResponseItem :: FunctionCallOutput {
1817- call_id : call_id. clone ( ) ,
1818- output : output. clone ( ) ,
1819- } ,
1820- ) ;
1821- }
1822- (
1823- ResponseItem :: CustomToolCall { .. } ,
1824- Some ( ResponseInputItem :: CustomToolCallOutput { call_id, output } ) ,
1825- ) => {
1826- items_to_record_in_conversation_history. push ( item) ;
1827- items_to_record_in_conversation_history. push (
1828- ResponseItem :: CustomToolCallOutput {
1829- call_id : call_id. clone ( ) ,
1830- output : output. clone ( ) ,
1831- } ,
1832- ) ;
1833- }
1834- (
1835- ResponseItem :: FunctionCall { .. } ,
1836- Some ( ResponseInputItem :: McpToolCallOutput { call_id, result } ) ,
1837- ) => {
1838- items_to_record_in_conversation_history. push ( item) ;
1839- let output = match result {
1840- Ok ( call_tool_result) => {
1841- convert_call_tool_result_to_function_call_output_payload (
1842- call_tool_result,
1843- )
1844- }
1845- Err ( err) => FunctionCallOutputPayload {
1846- content : err. clone ( ) ,
1847- success : Some ( false ) ,
1848- } ,
1849- } ;
1850- items_to_record_in_conversation_history. push (
1851- ResponseItem :: FunctionCallOutput {
1852- call_id : call_id. clone ( ) ,
1853- output,
1854- } ,
1855- ) ;
1856- }
1857- (
1858- ResponseItem :: Reasoning {
1859- id,
1860- summary,
1861- content,
1862- encrypted_content,
1863- } ,
1864- None ,
1865- ) => {
1866- items_to_record_in_conversation_history. push ( ResponseItem :: Reasoning {
1867- id : id. clone ( ) ,
1868- summary : summary. clone ( ) ,
1869- content : content. clone ( ) ,
1870- encrypted_content : encrypted_content. clone ( ) ,
1871- } ) ;
1872- }
1873- _ => {
1874- warn ! ( "Unexpected response item: {item:?} with response: {response:?}" ) ;
1875- }
1876- } ;
1877- if let Some ( response) = response {
1878- responses. push ( response) ;
1879- }
1880- }
1881-
1882- // Only attempt to take the lock if there is something to record.
1883- if !items_to_record_in_conversation_history. is_empty ( ) {
1884- if is_review_mode {
1885- review_thread_history
1886- . record_items ( items_to_record_in_conversation_history. iter ( ) ) ;
1887- } else {
1888- sess. record_conversation_items ( & items_to_record_in_conversation_history)
1889- . await ;
1890- }
1891- }
1790+ let ( responses, items_to_record_in_conversation_history) = process_items (
1791+ processed_items,
1792+ is_review_mode,
1793+ & mut review_thread_history,
1794+ & sess,
1795+ )
1796+ . await ;
18921797
18931798 if token_limit_reached {
18941799 if auto_compact_recently_attempted {
@@ -1927,7 +1832,16 @@ pub(crate) async fn run_task(
19271832 }
19281833 continue ;
19291834 }
1930- Err ( CodexErr :: TurnAborted ) => {
1835+ Err ( CodexErr :: TurnAborted {
1836+ dangling_artifacts : processed_items,
1837+ } ) => {
1838+ let _ = process_items (
1839+ processed_items,
1840+ is_review_mode,
1841+ & mut review_thread_history,
1842+ & sess,
1843+ )
1844+ . await ;
19311845 // Aborted turn is reported via a different event.
19321846 break ;
19331847 }
@@ -2068,7 +1982,13 @@ async fn run_turn(
20681982 . await
20691983 {
20701984 Ok ( output) => return Ok ( output) ,
2071- Err ( CodexErr :: TurnAborted ) => return Err ( CodexErr :: TurnAborted ) ,
1985+ Err ( CodexErr :: TurnAborted {
1986+ dangling_artifacts : processed_items,
1987+ } ) => {
1988+ return Err ( CodexErr :: TurnAborted {
1989+ dangling_artifacts : processed_items,
1990+ } ) ;
1991+ }
20721992 Err ( CodexErr :: Interrupted ) => return Err ( CodexErr :: Interrupted ) ,
20731993 Err ( CodexErr :: EnvVar ( var) ) => return Err ( CodexErr :: EnvVar ( var) ) ,
20741994 Err ( e @ CodexErr :: Fatal ( _) ) => return Err ( e) ,
@@ -2121,9 +2041,9 @@ async fn run_turn(
21212041/// "handled" such that it produces a `ResponseInputItem` that needs to be
21222042/// sent back to the model on the next turn.
21232043#[ derive( Debug ) ]
2124- pub ( crate ) struct ProcessedResponseItem {
2125- pub ( crate ) item : ResponseItem ,
2126- pub ( crate ) response : Option < ResponseInputItem > ,
2044+ pub struct ProcessedResponseItem {
2045+ pub item : ResponseItem ,
2046+ pub response : Option < ResponseInputItem > ,
21272047}
21282048
21292049#[ derive( Debug ) ]
@@ -2172,7 +2092,15 @@ async fn try_run_turn(
21722092 // Poll the next item from the model stream. We must inspect *both* Ok and Err
21732093 // cases so that transient stream failures (e.g., dropped SSE connection before
21742094 // `response.completed`) bubble up and trigger the caller's retry logic.
2175- let event = stream. next ( ) . or_cancel ( & cancellation_token) . await ?;
2095+ let event = match stream. next ( ) . or_cancel ( & cancellation_token) . await {
2096+ Ok ( event) => event,
2097+ Err ( codex_async_utils:: CancelErr :: Cancelled ) => {
2098+ let processed_items = output. try_collect ( ) . await ?;
2099+ return Err ( CodexErr :: TurnAborted {
2100+ dangling_artifacts : processed_items,
2101+ } ) ;
2102+ }
2103+ } ;
21762104
21772105 let event = match event {
21782106 Some ( res) => res?,
@@ -2196,7 +2124,8 @@ async fn try_run_turn(
21962124 let payload_preview = call. payload . log_payload ( ) . into_owned ( ) ;
21972125 tracing:: info!( "ToolCall: {} {}" , call. tool_name, payload_preview) ;
21982126
2199- let response = tool_runtime. handle_tool_call ( call) ;
2127+ let response =
2128+ tool_runtime. handle_tool_call ( call, cancellation_token. child_token ( ) ) ;
22002129
22012130 output. push_back (
22022131 async move {
@@ -2278,12 +2207,7 @@ async fn try_run_turn(
22782207 } => {
22792208 sess. update_token_usage_info ( turn_context. as_ref ( ) , token_usage. as_ref ( ) )
22802209 . await ;
2281-
2282- let processed_items = output
2283- . try_collect ( )
2284- . or_cancel ( & cancellation_token)
2285- . await ??;
2286-
2210+ let processed_items = output. try_collect ( ) . await ?;
22872211 let unified_diff = {
22882212 let mut tracker = turn_diff_tracker. lock ( ) . await ;
22892213 tracker. get_unified_diff ( )
@@ -2387,7 +2311,7 @@ pub(super) fn get_last_assistant_message_from_turn(responses: &[ResponseItem]) -
23872311 }
23882312 } )
23892313}
2390- fn convert_call_tool_result_to_function_call_output_payload (
2314+ pub ( crate ) fn convert_call_tool_result_to_function_call_output_payload (
23912315 call_tool_result : & CallToolResult ,
23922316) -> FunctionCallOutputPayload {
23932317 let CallToolResult {
0 commit comments