@@ -1068,48 +1068,16 @@ async fn prepare_prompt<S: ModelSession + Send + 'static>(
10681068 wire:: UserMessage :: new ( user_message_id) . content ( request. prompt ) ,
10691069 ) ,
10701070 ) ) ?;
1071- send_state (
1072- sink,
1073- session_id,
1074- wire:: StateUpdate :: Running ( wire:: RunningStateUpdate :: new ( ) ) ,
1075- ) ?;
1076- let stop_reason = match drive_prompt (
1071+ run_active_turn (
10771072 session_id,
1078- driver ,
1073+ integration ,
10791074 handle,
1075+ driver,
1076+ sink,
10801077 cancellation_generation,
10811078 structured_completion. then_some ( ( tasks, background_jobs) ) ,
10821079 )
10831080 . await
1084- {
1085- Ok ( stop_reason) => stop_reason,
1086- Err ( _)
1087- if handle
1088- . cancellation_handle ( )
1089- . is_cancelled_since ( cancellation_generation) =>
1090- {
1091- wire:: StopReason :: Cancelled
1092- }
1093- Err ( error) => {
1094- if structured_completion {
1095- super :: cancel_background_jobs ( tasks, background_jobs) . await ;
1096- let _ = super :: settle_background_jobs ( tasks, background_jobs) . await ;
1097- }
1098- terminalize_running_error ( session_id, integration, handle, sink, & error) . await ?;
1099- return Err ( error) ;
1100- }
1101- } ;
1102- if structured_completion && stop_reason == wire:: StopReason :: Cancelled {
1103- super :: cancel_background_jobs ( tasks, background_jobs) . await ;
1104- let _ = super :: settle_background_jobs ( tasks, background_jobs) . await ?;
1105- }
1106- let _ = integration. flush_session_updates ( session_id) . await ;
1107- integration. finish_prompt ( session_id) ;
1108- send_state (
1109- sink,
1110- session_id,
1111- wire:: StateUpdate :: Idle ( wire:: IdleStateUpdate :: new ( ) . stop_reason ( stop_reason) ) ,
1112- )
11131081 }
11141082 . await ;
11151083 if result. is_err ( ) && structured_completion {
@@ -1265,80 +1233,110 @@ where
12651233 }
12661234}
12671235
1268- async fn drive_autonomous < S : ModelSession + Send + ' static > (
1236+ async fn run_active_turn < S : ModelSession + Send + ' static > (
12691237 session_id : & wire:: SessionId ,
12701238 integration : & AcpIntegration ,
12711239 handle : & AcpSessionHandle ,
1272- busy : & AtomicBool ,
12731240 driver : & mut LoopDriver < S > ,
12741241 sink : & impl AcpSessionUpdateSink ,
1242+ cancellation_generation : u64 ,
1243+ structured : Option < ( & TaskManagerHandle , & BackgroundJobs ) > ,
12751244) -> Result < ( ) , AcpRuntimeError > {
1276- if claim_prompt ( busy) . is_err ( ) {
1277- return Ok ( ( ) ) ;
1278- }
1279- handle. prepare_injection_turn ( ) ;
1280- integration. finish_prompt ( session_id) ;
1281- let cancellation_generation = handle. cancellation_handle ( ) . generation ( ) ;
1282- handle. start_injection_turn ( ) ;
1283- let result = async {
1284- match drive_prompt ( session_id, driver, handle, cancellation_generation, None ) . await {
1285- Ok ( _) => { }
1286- Err ( _)
1287- if handle
1288- . cancellation_handle ( )
1289- . is_cancelled_since ( cancellation_generation) => { }
1290- Err ( error) => {
1291- terminalize_error ( session_id, integration, handle, sink, & error, None ) . await ?;
1292- return Err ( error) ;
1245+ send_state (
1246+ sink,
1247+ session_id,
1248+ wire:: StateUpdate :: Running ( wire:: RunningStateUpdate :: new ( ) ) ,
1249+ ) ?;
1250+ let stop_reason = match drive_prompt (
1251+ session_id,
1252+ driver,
1253+ handle,
1254+ cancellation_generation,
1255+ structured,
1256+ )
1257+ . await
1258+ {
1259+ Ok ( stop_reason) => stop_reason,
1260+ Err ( _)
1261+ if handle
1262+ . cancellation_handle ( )
1263+ . is_cancelled_since ( cancellation_generation) =>
1264+ {
1265+ wire:: StopReason :: Cancelled
1266+ }
1267+ Err ( error) => {
1268+ if let Some ( ( tasks, background_jobs) ) = structured {
1269+ super :: cancel_background_jobs ( tasks, background_jobs) . await ;
1270+ let _ = super :: settle_background_jobs ( tasks, background_jobs) . await ;
12931271 }
1272+ terminalize_running_error ( session_id, integration, handle, sink, & error) . await ?;
1273+ return Err ( error) ;
12941274 }
1295- let _ = integration. flush_session_updates ( session_id) . await ;
1296- Ok ( ( ) )
1275+ } ;
1276+ if stop_reason == wire:: StopReason :: Cancelled
1277+ && let Some ( ( tasks, background_jobs) ) = structured
1278+ {
1279+ super :: cancel_background_jobs ( tasks, background_jobs) . await ;
1280+ let _ = super :: settle_background_jobs ( tasks, background_jobs) . await ?;
12971281 }
1298- . await ;
1282+ let _ = integration . flush_session_updates ( session_id ) . await ;
12991283 integration. finish_prompt ( session_id) ;
1300- handle. stop_injection_turn ( ) ;
1301- busy. store ( false , Ordering :: Release ) ;
1302- result
1284+ send_state (
1285+ sink,
1286+ session_id,
1287+ wire:: StateUpdate :: Idle ( wire:: IdleStateUpdate :: new ( ) . stop_reason ( stop_reason) ) ,
1288+ )
13031289}
13041290
1305- async fn terminalize_running_error (
1291+ async fn drive_autonomous < S : ModelSession + Send + ' static > (
13061292 session_id : & wire:: SessionId ,
13071293 integration : & AcpIntegration ,
13081294 handle : & AcpSessionHandle ,
1295+ busy : & AtomicBool ,
1296+ driver : & mut LoopDriver < S > ,
13091297 sink : & impl AcpSessionUpdateSink ,
1310- error : & AcpRuntimeError ,
13111298) -> Result < ( ) , AcpRuntimeError > {
1312- terminalize_error (
1299+ if claim_prompt ( busy) . is_err ( ) {
1300+ return Ok ( ( ) ) ;
1301+ }
1302+ handle. prepare_injection_turn ( ) ;
1303+ integration. finish_prompt ( session_id) ;
1304+ let cancellation_generation = handle. cancellation_handle ( ) . generation ( ) ;
1305+ handle. start_injection_turn ( ) ;
1306+ let result = run_active_turn (
13131307 session_id,
13141308 integration,
13151309 handle,
1310+ driver,
13161311 sink,
1317- error ,
1318- Some ( wire :: IdleStateUpdate :: new ( ) . stop_reason ( error_stop_reason ( ) ) ) ,
1312+ cancellation_generation ,
1313+ None ,
13191314 )
1320- . await
1315+ . await ;
1316+ integration. finish_prompt ( session_id) ;
1317+ handle. stop_injection_turn ( ) ;
1318+ busy. store ( false , Ordering :: Release ) ;
1319+ result
13211320}
13221321
1323- async fn terminalize_error (
1322+ async fn terminalize_running_error (
13241323 session_id : & wire:: SessionId ,
13251324 integration : & AcpIntegration ,
13261325 handle : & AcpSessionHandle ,
13271326 sink : & impl AcpSessionUpdateSink ,
13281327 error : & AcpRuntimeError ,
1329- idle : Option < wire:: IdleStateUpdate > ,
13301328) -> Result < ( ) , AcpRuntimeError > {
13311329 handle. stop_injection_turn ( ) ;
13321330 let _ = integration. flush_session_updates ( session_id) . await ;
13331331 integration. finish_prompt ( session_id) ;
13341332
13351333 let diagnostic_result = sink. update ( error_diagnostic_notification ( session_id, error) ) ;
1336- if let Some ( idle ) = idle {
1337- let idle_result = send_state ( sink, session_id , wire :: StateUpdate :: Idle ( idle ) ) ;
1338- diagnostic_result . and ( idle_result )
1339- } else {
1340- diagnostic_result
1341- }
1334+ let idle_result = send_state (
1335+ sink,
1336+ session_id ,
1337+ wire :: StateUpdate :: Idle ( wire :: IdleStateUpdate :: new ( ) . stop_reason ( error_stop_reason ( ) ) ) ,
1338+ ) ;
1339+ diagnostic_result . and ( idle_result )
13421340}
13431341
13441342fn error_diagnostic_notification (
@@ -1912,6 +1910,31 @@ mod tests {
19121910 }
19131911 }
19141912
1913+ fn assert_running_then_idle (
1914+ updates : & [ wire:: UpdateSessionNotification ] ,
1915+ stop_reason : wire:: StopReason ,
1916+ ) {
1917+ assert ! ( matches!(
1918+ updates. first( ) . map( |update| & update. update) ,
1919+ Some ( wire:: SessionUpdate :: StateUpdate (
1920+ wire:: StateUpdate :: Running ( _)
1921+ ) )
1922+ ) ) ;
1923+ assert ! ( matches!(
1924+ updates. last( ) . map( |update| & update. update) ,
1925+ Some ( wire:: SessionUpdate :: StateUpdate ( wire:: StateUpdate :: Idle (
1926+ idle
1927+ ) ) ) if idle. stop_reason. as_ref( ) == Some ( & stop_reason)
1928+ ) ) ;
1929+ assert_eq ! (
1930+ updates
1931+ . iter( )
1932+ . filter( |update| matches!( update. update, wire:: SessionUpdate :: StateUpdate ( _) ) )
1933+ . count( ) ,
1934+ 2
1935+ ) ;
1936+ }
1937+
19151938 #[ test]
19161939 fn observer_reports_usage_with_a_known_context_window ( ) {
19171940 let recording = RecordingSink :: default ( ) ;
@@ -2783,7 +2806,7 @@ mod tests {
27832806 }
27842807
27852808 #[ tokio:: test]
2786- async fn autonomous_no_work_emits_no_state_update ( ) {
2809+ async fn autonomous_no_work_emits_running_then_idle ( ) {
27872810 let integration = AcpIntegration :: default ( ) ;
27882811 let sink = RecordingSink :: default ( ) ;
27892812 let session_id = wire:: SessionId :: new ( "autonomous-no-work" ) ;
@@ -2812,11 +2835,11 @@ mod tests {
28122835 assert_eq ! ( turns. load( Ordering :: Relaxed ) , 0 ) ;
28132836 assert ! ( !busy. load( Ordering :: Relaxed ) ) ;
28142837 assert_eq ! ( sink. flushes. load( Ordering :: Relaxed ) , 1 ) ;
2815- assert ! ( sink. updates. lock( ) . unwrap( ) . is_empty ( ) ) ;
2838+ assert_running_then_idle ( & sink. updates . lock ( ) . unwrap ( ) , wire :: StopReason :: EndTurn ) ;
28162839 }
28172840
28182841 #[ tokio:: test]
2819- async fn autonomous_content_emits_no_state_update ( ) {
2842+ async fn autonomous_content_emits_running_then_idle ( ) {
28202843 let integration = AcpIntegration :: default ( ) ;
28212844 let recording = RecordingSink :: default ( ) ;
28222845 let sink = ResponseReplacementSink :: new ( recording. clone ( ) ) ;
@@ -2869,15 +2892,11 @@ mod tests {
28692892 . unwrap( )
28702893 . contains( "autonomous content" )
28712894 } ) ) ;
2872- assert ! (
2873- !updates
2874- . iter( )
2875- . any( |update| matches!( update. update, wire:: SessionUpdate :: StateUpdate ( _) ) )
2876- ) ;
2895+ assert_running_then_idle ( & updates, wire:: StopReason :: EndTurn ) ;
28772896 }
28782897
28792898 #[ tokio:: test]
2880- async fn autonomous_provider_error_emits_diagnostic_without_state_update ( ) {
2899+ async fn autonomous_provider_error_emits_running_error_idle ( ) {
28812900 let integration = AcpIntegration :: default ( ) ;
28822901 let sink = RecordingSink :: default ( ) ;
28832902 let session_id = wire:: SessionId :: new ( "autonomous-provider-error" ) ;
@@ -2910,21 +2929,17 @@ mod tests {
29102929 assert ! ( !busy. load( Ordering :: Relaxed ) ) ;
29112930 assert_eq ! ( sink. flushes. load( Ordering :: Relaxed ) , 1 ) ;
29122931 let updates = sink. updates . lock ( ) . unwrap ( ) ;
2913- assert_eq ! ( updates. len( ) , 1 ) ;
2932+ assert_eq ! ( updates. len( ) , 3 ) ;
29142933 assert ! (
2915- serde_json:: to_string( & updates[ 0 ] . update)
2934+ serde_json:: to_string( & updates[ 1 ] . update)
29162935 . unwrap( )
29172936 . contains( "provider failed" )
29182937 ) ;
29192938 assert ! ( matches!(
2920- updates[ 0 ] . update,
2939+ updates[ 1 ] . update,
29212940 wire:: SessionUpdate :: AgentMessage ( _)
29222941 ) ) ;
2923- assert ! (
2924- !updates
2925- . iter( )
2926- . any( |update| matches!( update. update, wire:: SessionUpdate :: StateUpdate ( _) ) )
2927- ) ;
2942+ assert_running_then_idle ( & updates, error_stop_reason ( ) ) ;
29282943 }
29292944
29302945 #[ tokio:: test]
@@ -2964,11 +2979,11 @@ mod tests {
29642979 assert_eq ! ( turns. load( Ordering :: Relaxed ) , 1 ) ;
29652980 assert ! ( !busy. load( Ordering :: Relaxed ) ) ;
29662981 assert_eq ! ( sink. flushes. load( Ordering :: Relaxed ) , 1 ) ;
2967- assert ! ( sink. updates. lock( ) . unwrap( ) . is_empty ( ) ) ;
2982+ assert_running_then_idle ( & sink. updates . lock ( ) . unwrap ( ) , wire :: StopReason :: Cancelled ) ;
29682983 }
29692984
29702985 #[ tokio:: test]
2971- async fn autonomous_finish_error_emits_diagnostic_without_state_update ( ) {
2986+ async fn autonomous_finish_error_emits_running_error_idle ( ) {
29722987 let integration = AcpIntegration :: default ( ) ;
29732988 let sink = RecordingSink :: default ( ) ;
29742989 let session_id = wire:: SessionId :: new ( "autonomous-error" ) ;
@@ -3001,21 +3016,17 @@ mod tests {
30013016 assert ! ( !busy. load( Ordering :: Relaxed ) ) ;
30023017 assert_eq ! ( sink. flushes. load( Ordering :: Relaxed ) , 1 ) ;
30033018 let updates = sink. updates . lock ( ) . unwrap ( ) ;
3004- assert_eq ! ( updates. len( ) , 1 ) ;
3019+ assert_eq ! ( updates. len( ) , 3 ) ;
30053020 assert ! ( matches!(
3006- updates[ 0 ] . update,
3021+ updates[ 1 ] . update,
30073022 wire:: SessionUpdate :: AgentMessage ( _)
30083023 ) ) ;
30093024 assert ! (
3010- serde_json:: to_string( & updates[ 0 ] . update)
3025+ serde_json:: to_string( & updates[ 1 ] . update)
30113026 . unwrap( )
30123027 . contains( "loop error: model turn failed" )
30133028 ) ;
3014- assert ! (
3015- !updates
3016- . iter( )
3017- . any( |update| matches!( update. update, wire:: SessionUpdate :: StateUpdate ( _) ) )
3018- ) ;
3029+ assert_running_then_idle ( & updates, error_stop_reason ( ) ) ;
30193030 }
30203031
30213032 #[ test]
0 commit comments