@@ -517,20 +517,31 @@ def accumulate_content(event: Event) -> None:
517517 if event .is_final_response ():
518518 self ._save_output_to_state (ctx , event )
519519
520- # Process code execution responses if code executor is configured
520+ # Process code execution responses if code executor is configured.
521+ # We collect code execution events first (this mutates event.content in place,
522+ # stripping executable_code parts but keeping text/function_call), then yield
523+ # the main event BEFORE the code execution events so the causal order in
524+ # session is preserved: assistant declaration → code execution → result.
525+ pending_code_events : list [Event ] = []
521526 if self .code_executor and event .content :
522527 async for code_event in CodeExecutionResponseProcessor .run_async (ctx , event ):
523- # Check if this is a code execution result event
524528 if code_event .content and code_event .content .parts :
525529 for part in code_event .content .parts :
526530 if part .code_execution_result or part .executable_code :
527531 code_was_executed = True
528532 break
529- yield code_event
530-
531- # Yield LLM response events directly
532- yield event
533- accumulate_content (event )
533+ pending_code_events .append (code_event )
534+
535+ # Yield the main LLM response event first (now stripped of executable_code
536+ # but still carrying text and function_call parts).
537+ # Skip empty events (content became None after all parts were consumed).
538+ if event .content is not None :
539+ yield event
540+ accumulate_content (event )
541+
542+ # Then yield code execution events in order.
543+ for code_event in pending_code_events :
544+ yield code_event
534545 else :
535546 # Yield other events directly
536547 yield event
0 commit comments