@@ -467,30 +467,10 @@ fn extract_tool_session_messages(
467467 }
468468}
469469
470- fn count_tool_use_blocks ( blocks : & [ ContentBlock ] ) -> usize {
470+ fn has_tool_result_blocks ( blocks : & [ ContentBlock ] ) -> bool {
471471 blocks
472472 . iter ( )
473- . filter ( |block| matches ! ( block, ContentBlock :: ToolUse ( _) ) )
474- . count ( )
475- }
476-
477- fn count_tool_result_blocks ( blocks : & [ ContentBlock ] ) -> usize {
478- blocks
479- . iter ( )
480- . filter ( |block| matches ! ( block, ContentBlock :: ToolResult ( _) ) )
481- . count ( )
482- }
483-
484- fn count_user_tool_result_messages ( user_msg : & claude_agent_sdk:: UserMessage ) -> usize {
485- let block_count = match user_msg. content {
486- claude_agent_sdk:: UserContent :: Blocks ( ref blocks) => count_tool_result_blocks ( blocks) ,
487- claude_agent_sdk:: UserContent :: Text ( _) => 0 ,
488- } ;
489- if block_count > 0 {
490- block_count
491- } else {
492- usize:: from ( user_msg. tool_use_result . is_some ( ) )
493- }
473+ . any ( |b| matches ! ( b, ContentBlock :: ToolResult ( _) ) )
494474}
495475
496476fn metadata_string_map (
@@ -663,13 +643,9 @@ pub struct ClaudeCliProvider {
663643 run_session_map : Mutex < HashMap < String , String > > ,
664644 /// User inputs accepted but not yet completed per live run.
665645 run_pending_inputs : Mutex < HashMap < String , VecDeque < PendingAckMarker > > > ,
666- /// User inputs accepted while Claude is between tool_use and tool_result.
667- run_input_states : Mutex < HashMap < String , RunInputState > > ,
668646 /// Last user message per thread, used for auto-recovery replay.
669647 last_messages : Mutex < HashMap < String , String > > ,
670648 #[ cfg( test) ]
671- test_sent_streaming_inputs : Mutex < Vec < ( String , OutboundUserMessage ) > > ,
672- #[ cfg( test) ]
673649 test_run_attempts : Mutex < VecDeque < Result < Option < SdkRunOutcome > , BridgeError > > > ,
674650 #[ cfg( test) ]
675651 test_recorded_session_attempts : Mutex < Vec < Option < String > > > ,
@@ -682,18 +658,6 @@ enum PendingAckMarker {
682658 QueuedInput ( String ) ,
683659}
684660
685- #[ derive( Debug , Clone ) ]
686- struct PendingClaudeInput {
687- pending_input_id : String ,
688- outbound : OutboundUserMessage ,
689- }
690-
691- #[ derive( Debug , Default ) ]
692- struct RunInputState {
693- outstanding_tool_uses : usize ,
694- queued_inputs : VecDeque < PendingClaudeInput > ,
695- }
696-
697661impl ClaudeCliProvider {
698662 /// Create a new provider with the given config.
699663 pub fn new ( config : ClaudeCodeConfig ) -> Self {
@@ -704,11 +668,8 @@ impl ClaudeCliProvider {
704668 active_runs : Mutex :: new ( HashMap :: new ( ) ) ,
705669 run_session_map : Mutex :: new ( HashMap :: new ( ) ) ,
706670 run_pending_inputs : Mutex :: new ( HashMap :: new ( ) ) ,
707- run_input_states : Mutex :: new ( HashMap :: new ( ) ) ,
708671 last_messages : Mutex :: new ( HashMap :: new ( ) ) ,
709672 #[ cfg( test) ]
710- test_sent_streaming_inputs : Mutex :: new ( Vec :: new ( ) ) ,
711- #[ cfg( test) ]
712673 test_run_attempts : Mutex :: new ( VecDeque :: new ( ) ) ,
713674 #[ cfg( test) ]
714675 test_recorded_session_attempts : Mutex :: new ( Vec :: new ( ) ) ,
@@ -920,18 +881,13 @@ impl ClaudeCliProvider {
920881 . lock ( )
921882 . await
922883 . insert ( run_id. to_owned ( ) , thread_id. to_owned ( ) ) ;
923- self . run_input_states
924- . lock ( )
925- . await
926- . insert ( run_id. to_owned ( ) , RunInputState :: default ( ) ) ;
927884 }
928885
929886 /// Unregister a run after completion and return handle for cleanup.
930887 async fn unregister_run ( & self , run_id : & str ) -> ( Option < ClaudeRunControl > , Option < String > ) {
931888 let run = self . active_runs . lock ( ) . await . remove ( run_id) ;
932889 let thread_id = self . run_session_map . lock ( ) . await . remove ( run_id) ;
933890 self . run_pending_inputs . lock ( ) . await . remove ( run_id) ;
934- self . run_input_states . lock ( ) . await . remove ( run_id) ;
935891 ( run, thread_id)
936892 }
937893
@@ -942,124 +898,6 @@ impl ClaudeCliProvider {
942898 }
943899 }
944900
945- async fn note_tool_uses_started ( & self , run_id : & str , count : usize ) {
946- if count == 0 {
947- return ;
948- }
949- if let Some ( state) = self . run_input_states . lock ( ) . await . get_mut ( run_id) {
950- state. outstanding_tool_uses = state. outstanding_tool_uses . saturating_add ( count) ;
951- }
952- }
953-
954- async fn note_tool_results_finished ( & self , run_id : & str , count : usize ) {
955- if count == 0 {
956- return ;
957- }
958- if let Some ( state) = self . run_input_states . lock ( ) . await . get_mut ( run_id) {
959- state. outstanding_tool_uses = state. outstanding_tool_uses . saturating_sub ( count) ;
960- }
961- }
962-
963- async fn send_streaming_input_now (
964- & self ,
965- run_id : & str ,
966- pending_input_id : & str ,
967- outbound : OutboundUserMessage ,
968- ) -> bool {
969- let run = { self . active_runs . lock ( ) . await . get ( run_id) . cloned ( ) } ;
970- if let Some ( run) = run {
971- match run. send_user_message ( outbound) . await {
972- Ok ( ( ) ) => return true ,
973- Err ( e) => {
974- self . rollback_pending_input ( run_id, pending_input_id) . await ;
975- tracing:: warn!(
976- run_id = %run_id,
977- pending_input_id = %pending_input_id,
978- error = %e,
979- "failed to send queued Claude streaming input"
980- ) ;
981- return false ;
982- }
983- }
984- }
985-
986- #[ cfg( test) ]
987- {
988- self . test_sent_streaming_inputs
989- . lock ( )
990- . await
991- . push ( ( run_id. to_owned ( ) , outbound) ) ;
992- return true ;
993- }
994-
995- #[ cfg( not( test) ) ]
996- {
997- self . rollback_pending_input ( run_id, pending_input_id) . await ;
998- false
999- }
1000- }
1001-
1002- async fn send_or_queue_streaming_input (
1003- & self ,
1004- run_id : & str ,
1005- pending_input_id : String ,
1006- outbound : OutboundUserMessage ,
1007- ) -> bool {
1008- let pending = PendingClaudeInput {
1009- pending_input_id,
1010- outbound,
1011- } ;
1012-
1013- {
1014- let mut states = self . run_input_states . lock ( ) . await ;
1015- if let Some ( state) = states. get_mut ( run_id)
1016- && state. outstanding_tool_uses > 0
1017- {
1018- state. queued_inputs . push_back ( pending) ;
1019- tracing:: debug!(
1020- run_id = %run_id,
1021- outstanding_tool_uses = state. outstanding_tool_uses,
1022- queued_inputs = state. queued_inputs. len( ) ,
1023- "deferring Claude streaming input until tool results are complete"
1024- ) ;
1025- return true ;
1026- }
1027- }
1028-
1029- let PendingClaudeInput {
1030- pending_input_id,
1031- outbound,
1032- } = pending;
1033- self . send_streaming_input_now ( run_id, & pending_input_id, outbound)
1034- . await
1035- }
1036-
1037- async fn flush_queued_streaming_inputs_if_safe ( & self , run_id : & str ) {
1038- loop {
1039- let pending = {
1040- let mut states = self . run_input_states . lock ( ) . await ;
1041- let Some ( state) = states. get_mut ( run_id) else {
1042- return ;
1043- } ;
1044- if state. outstanding_tool_uses > 0 {
1045- return ;
1046- }
1047- state. queued_inputs . pop_front ( )
1048- } ;
1049-
1050- let Some ( pending) = pending else {
1051- return ;
1052- } ;
1053-
1054- let sent = self
1055- . send_streaming_input_now ( run_id, & pending. pending_input_id , pending. outbound )
1056- . await ;
1057- if !sent {
1058- return ;
1059- }
1060- }
1061- }
1062-
1063901 async fn initialize_pending_inputs ( & self , run_id : & str ) {
1064902 self . run_pending_inputs . lock ( ) . await . insert (
1065903 run_id. to_owned ( ) ,
@@ -1349,7 +1187,6 @@ impl ClaudeCliProvider {
13491187 Ok ( None ) => break , // stream closed normally
13501188 Ok ( Some ( msg_result) ) => match msg_result {
13511189 Ok ( Message :: User ( user_msg) ) => {
1352- let tool_result_count = count_user_tool_result_messages ( & user_msg) ;
13531190 if let claude_agent_sdk:: UserContent :: Blocks ( ref blocks) = user_msg. content
13541191 {
13551192 extract_tool_session_messages (
@@ -1358,12 +1195,10 @@ impl ClaudeCliProvider {
13581195 Some ( on_chunk) ,
13591196 user_msg. parent_tool_use_id . as_deref ( ) ,
13601197 ) ;
1361- }
1362- if tool_result_count > 0 {
1363- self . note_tool_results_finished ( run_id, tool_result_count)
1364- . await ;
1365- self . flush_queued_streaming_inputs_if_safe ( run_id) . await ;
1366- continue ;
1198+ if has_tool_result_blocks ( blocks) || user_msg. tool_use_result . is_some ( )
1199+ {
1200+ continue ;
1201+ }
13671202 }
13681203 // Keep Python parity: normal UserMessage echoes are ACKs that
13691204 // indicate one queued user input has been consumed.
@@ -1380,18 +1215,13 @@ impl ClaudeCliProvider {
13801215 Ok ( Message :: Assistant ( assistant_msg) ) => {
13811216 if is_synthetic_no_response_message ( & assistant_msg) {
13821217 tracing:: debug!(
1383- run_id = %run_id,
1384- thread_id = %thread_id,
1218+ run_id = %run_id,
1219+ thread_id = %thread_id,
13851220 "suppressing claude synthetic no-response placeholder"
13861221 ) ;
13871222 continue ;
13881223 }
13891224 assistant_or_tool_activity_seen = true ;
1390- self . note_tool_uses_started (
1391- run_id,
1392- count_tool_use_blocks ( & assistant_msg. content ) ,
1393- )
1394- . await ;
13951225 if actual_model. is_none ( ) {
13961226 actual_model = Some ( assistant_msg. model . trim ( ) . to_owned ( ) )
13971227 . filter ( |value| !value. is_empty ( ) ) ;
@@ -1435,7 +1265,6 @@ impl ClaudeCliProvider {
14351265 thread_title : thread_title. clone ( ) ,
14361266 session_messages : session_messages. clone ( ) ,
14371267 } ) ;
1438- self . flush_queued_streaming_inputs_if_safe ( run_id) . await ;
14391268 // Atomically check-and-close: if no queued inputs remain,
14401269 // remove the queue entry so that concurrent
14411270 // add_streaming_input callers see the run as closed and
@@ -1617,7 +1446,6 @@ impl AgentLoopProvider for ClaudeCliProvider {
16171446 } ;
16181447 self . run_session_map . lock ( ) . await . clear ( ) ;
16191448 self . run_pending_inputs . lock ( ) . await . clear ( ) ;
1620- self . run_input_states . lock ( ) . await . clear ( ) ;
16211449
16221450 for ( run_id, run) in runs {
16231451 if let Err ( e) = run. interrupt ( ) . await {
@@ -1793,8 +1621,8 @@ impl AgentLoopProvider for ClaudeCliProvider {
17931621
17941622 async fn add_streaming_input ( & self , thread_id : & str , input : QueuedUserInput ) -> bool {
17951623 if let Some ( run_id) = self . resolve_active_run_id_for_session ( thread_id) . await {
1796- let has_run = { self . active_runs . lock ( ) . await . contains_key ( & run_id) } ;
1797- if has_run {
1624+ let run = { self . active_runs . lock ( ) . await . get ( & run_id) . cloned ( ) } ;
1625+ if let Some ( run ) = run {
17981626 let pending_input_id = input. pending_input_id . unwrap_or_default ( ) ;
17991627 if pending_input_id. trim ( ) . is_empty ( ) {
18001628 return false ;
@@ -1813,14 +1641,21 @@ impl AgentLoopProvider for ClaudeCliProvider {
18131641 UserInput :: Text ( text) => OutboundUserMessage :: text ( text, "" ) ,
18141642 UserInput :: Blocks ( blocks) => OutboundUserMessage :: blocks ( blocks, "" ) ,
18151643 } ;
1816- if self
1817- . send_or_queue_streaming_input ( & run_id, pending_input_id. clone ( ) , outbound)
1818- . await
1819- {
1820- tracing:: debug!( thread_id = %thread_id, "queued streaming input" ) ;
1821- true
1822- } else {
1823- false
1644+ match run. send_user_message ( outbound) . await {
1645+ Ok ( ( ) ) => {
1646+ tracing:: debug!( thread_id = %thread_id, "queued streaming input" ) ;
1647+ true
1648+ }
1649+ Err ( e) => {
1650+ self . rollback_pending_input ( & run_id, & pending_input_id)
1651+ . await ;
1652+ tracing:: warn!(
1653+ thread_id = %thread_id,
1654+ error = %e,
1655+ "failed to queue streaming input"
1656+ ) ;
1657+ false
1658+ }
18241659 }
18251660 } else {
18261661 let _ = self . unregister_run ( & run_id) . await ;
0 commit comments