@@ -91,52 +91,84 @@ impl AgentRunner {
9191 return Ok ( ( ) ) ;
9292 }
9393
94- // Create an internal channel to collect events from this LLM call
95- let ( inner_tx, mut inner_rx) = mpsc:: channel :: < AgentEvent > ( 32 ) ;
96-
97- provider
98- . call_streaming ( & current_messages, tools, system_prompt, inner_tx)
99- . await ?;
100-
101- // Collect events, forwarding text/usage/error to client,
102- // collecting tool_use calls for dispatch
103- let mut tool_calls: Vec < ( String , String , serde_json:: Value ) > = Vec :: new ( ) ;
94+ let tool_calls: Vec < ( String , String , serde_json:: Value ) > = {
95+ // Create an internal channel to collect events from this LLM call.
96+ // We must drain this channel while provider streaming is in-flight;
97+ // otherwise long responses can fill the buffer and deadlock.
98+ let ( inner_tx, mut inner_rx) = mpsc:: channel :: < AgentEvent > ( 32 ) ;
99+ let mut tool_calls: Vec < ( String , String , serde_json:: Value ) > = Vec :: new ( ) ;
100+ let mut provider_done = false ;
101+ let mut provider_error: Option < anyhow:: Error > = None ;
102+
103+ let provider_call =
104+ provider. call_streaming ( & current_messages, tools, system_prompt, inner_tx) ;
105+ tokio:: pin!( provider_call) ;
106+
107+ loop {
108+ tokio:: select! {
109+ result = & mut provider_call, if !provider_done => {
110+ provider_done = true ;
111+ if let Err ( e) = result {
112+ provider_error = Some ( e) ;
113+ }
114+ }
115+ maybe_event = inner_rx. recv( ) => {
116+ let Some ( event) = maybe_event else {
117+ if !provider_done {
118+ if let Err ( e) = ( & mut provider_call) . await {
119+ provider_error = Some ( e) ;
120+ }
121+ }
122+ break ;
123+ } ;
104124
105- while let Some ( event) = inner_rx. recv ( ) . await {
106- match event {
107- AgentEvent :: Text ( ref _t) => {
108- let _ = tx. send ( event) . await ;
109- }
110- AgentEvent :: ToolUse {
111- ref id,
112- ref name,
113- ref input,
114- } => {
115- // Forward to client so they can observe
116- let _ = tx
117- . send ( AgentEvent :: ToolUse {
118- id : id. clone ( ) ,
119- name : name. clone ( ) ,
120- input : input. clone ( ) ,
121- } )
122- . await ;
123- tool_calls. push ( ( id. clone ( ) , name. clone ( ) , input. clone ( ) ) ) ;
124- }
125- AgentEvent :: Usage { .. } => {
126- let _ = tx. send ( event) . await ;
127- }
128- AgentEvent :: Error ( ref _e) => {
129- let _ = tx. send ( event) . await ;
130- }
131- AgentEvent :: Done => {
132- // Don't forward Done yet — we may need to continue the loop
125+ match event {
126+ AgentEvent :: Text ( ref _t) => {
127+ let _ = tx. send( event) . await ;
128+ }
129+ AgentEvent :: ToolUse {
130+ ref id,
131+ ref name,
132+ ref input,
133+ } => {
134+ // Forward to client so they can observe
135+ let _ = tx
136+ . send( AgentEvent :: ToolUse {
137+ id: id. clone( ) ,
138+ name: name. clone( ) ,
139+ input: input. clone( ) ,
140+ } )
141+ . await ;
142+ tool_calls. push( ( id. clone( ) , name. clone( ) , input. clone( ) ) ) ;
143+ }
144+ AgentEvent :: Usage { .. } => {
145+ let _ = tx. send( event) . await ;
146+ }
147+ AgentEvent :: Error ( ref _e) => {
148+ let _ = tx. send( event) . await ;
149+ }
150+ AgentEvent :: Done => {
151+ // Don't forward Done yet — we may need to continue the loop
152+ }
153+ AgentEvent :: ToolResult { .. } => {
154+ // Shouldn't come from provider, but forward if it does
155+ let _ = tx. send( event) . await ;
156+ }
157+ }
158+ }
133159 }
134- AgentEvent :: ToolResult { .. } => {
135- // Shouldn't come from provider, but forward if it does
136- let _ = tx . send ( event ) . await ;
160+
161+ if provider_done && inner_rx . is_closed ( ) && inner_rx . is_empty ( ) {
162+ break ;
137163 }
138164 }
139- }
165+
166+ if let Some ( e) = provider_error {
167+ return Err ( e) ;
168+ }
169+
170+ tool_calls
171+ } ;
140172
141173 // If no tool calls, we're done
142174 if tool_calls. is_empty ( ) {
@@ -366,3 +398,73 @@ impl Default for AgentRunner {
366398 Self :: new ( )
367399 }
368400}
401+
402+ #[ cfg( test) ]
403+ mod tests {
404+ use super :: { AgentEvent , AgentRunner , providers} ;
405+ use crate :: sandbox:: PluginHost ;
406+ use async_trait:: async_trait;
407+ use std:: sync:: Arc ;
408+ use tokio:: sync:: { RwLock , mpsc} ;
409+ use tokio:: time:: { Duration , timeout} ;
410+
411+ struct BurstProvider {
412+ chunks : usize ,
413+ }
414+
415+ #[ async_trait]
416+ impl providers:: LlmProvider for BurstProvider {
417+ async fn call_streaming (
418+ & self ,
419+ _messages : & [ serde_json:: Value ] ,
420+ _tools : & [ serde_json:: Value ] ,
421+ _system_prompt : Option < & str > ,
422+ tx : mpsc:: Sender < AgentEvent > ,
423+ ) -> anyhow:: Result < ( ) > {
424+ for i in 0 ..self . chunks {
425+ tx. send ( AgentEvent :: Text ( format ! ( "chunk-{i}" ) ) ) . await ?;
426+ }
427+ tx. send ( AgentEvent :: Done ) . await ?;
428+ Ok ( ( ) )
429+ }
430+ }
431+
432+ #[ tokio:: test]
433+ async fn run_with_tools_drains_stream_while_provider_is_running ( ) {
434+ let runner = AgentRunner :: new ( ) ;
435+ let provider = BurstProvider { chunks : 64 } ;
436+ let plugins = Arc :: new ( RwLock :: new ( PluginHost :: new ( ) ) ) ;
437+ let ( tx, mut rx) = mpsc:: channel :: < AgentEvent > ( 256 ) ;
438+
439+ timeout (
440+ Duration :: from_secs ( 5 ) ,
441+ runner. run_with_tools (
442+ & provider,
443+ vec ! [ serde_json:: json!( {
444+ "role" : "user" ,
445+ "content" : "hello" ,
446+ } ) ] ,
447+ & [ ] ,
448+ None ,
449+ & plugins,
450+ tx,
451+ ) ,
452+ )
453+ . await
454+ . expect ( "runner should not deadlock" )
455+ . expect ( "runner should succeed" ) ;
456+
457+ let mut text_count = 0usize ;
458+ let mut saw_done = false ;
459+ while let Ok ( event) = rx. try_recv ( ) {
460+ match event {
461+ AgentEvent :: Text ( _) => text_count += 1 ,
462+ AgentEvent :: Done => saw_done = true ,
463+ _ => { }
464+ }
465+ }
466+
467+ assert_eq ! ( text_count, 64 ) ;
468+ assert ! ( saw_done) ;
469+ }
470+ }
0 commit comments