@@ -495,6 +495,33 @@ func (t *ToolTracker) FlushStreamingText(threshold int, width int, renderMd func
495495 return FlushStreamingTextResult {}
496496 }
497497
498+ // First: flush any completed tool segments before the text segment.
499+ // Tool calls are always processed before text continues, so any tool segments
500+ // that appear before the current text segment are already complete.
501+ var contentBuilder strings.Builder
502+ var toolsToFlush []* Segment
503+ for i := 0 ; i < segIdx ; i ++ {
504+ s := & t .Segments [i ]
505+ if s .Type == SegmentTool && ! s .Flushed && s .ToolStatus != ToolPending {
506+ toolsToFlush = append (toolsToFlush , s )
507+ s .Flushed = true
508+ }
509+ }
510+
511+ if len (toolsToFlush ) > 0 {
512+ toolContent := RenderSegments (toolsToFlush , width , - 1 , nil , true )
513+ if t .HasFlushed {
514+ toolContent = stripLeadingBlankLine (toolContent )
515+ prefix := t .LeadingSeparator (toolsToFlush [0 ].Type )
516+ if prefix != "" {
517+ toolContent = prefix + toolContent
518+ }
519+ }
520+ contentBuilder .WriteString (toolContent )
521+ t .HasFlushed = true
522+ t .LastFlushedType = toolsToFlush [len (toolsToFlush )- 1 ].Type
523+ }
524+
498525 // Get current text length
499526 textLen := 0
500527 if seg .TextBuilder != nil {
@@ -507,6 +534,10 @@ func (t *ToolTracker) FlushStreamingText(threshold int, width int, renderMd func
507534 unflushedLen := textLen - seg .FlushedPos
508535 if unflushedLen < threshold {
509536 debugFlushf ("stream skip seg=%d reason=below-threshold textLen=%d flushedPos=%d unflushedLen=%d threshold=%d" , segIdx , textLen , seg .FlushedPos , unflushedLen , threshold )
537+ // Return any tool content we already flushed, even if text doesn't need flushing
538+ if contentBuilder .Len () > 0 {
539+ return FlushStreamingTextResult {ToPrint : contentBuilder .String ()}
540+ }
510541 return FlushStreamingTextResult {}
511542 }
512543
@@ -532,25 +563,32 @@ func (t *ToolTracker) FlushStreamingText(threshold int, width int, renderMd func
532563 if safeBoundary <= seg .FlushedPos {
533564 // No new committed blocks to flush yet
534565 debugFlushf ("stream skip seg=%d reason=no-committed committed=%d flushedPos=%d" , segIdx , safeBoundary , seg .FlushedPos )
566+ // Return any tool content we already flushed
567+ if contentBuilder .Len () > 0 {
568+ return FlushStreamingTextResult {ToPrint : contentBuilder .String ()}
569+ }
535570 return FlushStreamingTextResult {}
536571 }
537572 rendered := seg .StreamRenderer .RenderedUnflushed ()
538573 if rendered == "" {
539574 debugFlushf ("stream skip seg=%d reason=empty-rendered committed=%d flushedPos=%d" , segIdx , safeBoundary , seg .FlushedPos )
575+ // Return any tool content we already flushed
576+ if contentBuilder .Len () > 0 {
577+ return FlushStreamingTextResult {ToPrint : contentBuilder .String ()}
578+ }
540579 return FlushStreamingTextResult {}
541580 }
542581
543- var b strings.Builder
544582 if t .HasFlushed && seg .FlushedPos == 0 {
545- b .WriteString (t .LeadingSeparator (SegmentText ))
583+ contentBuilder .WriteString (t .LeadingSeparator (SegmentText ))
546584 }
547585
548586 // Strip leading blank line if we've already flushed content,
549587 // since tea.Printf adds a newline after each flush
550588 if t .HasFlushed {
551589 rendered = stripLeadingBlankLine (rendered )
552590 }
553- b .WriteString (rendered )
591+ contentBuilder .WriteString (rendered )
554592
555593 // Update tracker state
556594 seg .FlushedPos = safeBoundary
@@ -562,7 +600,7 @@ func (t *ToolTracker) FlushStreamingText(threshold int, width int, renderMd func
562600 debugFlushf ("stream flush seg=%d committed=%d flushedPos=%d renderedUnflushedLen=%d flushedRenderedPos=%d" , segIdx , safeBoundary , seg .FlushedPos , len (rendered ), seg .FlushedRenderedPos )
563601
564602 return FlushStreamingTextResult {
565- ToPrint : b .String (),
603+ ToPrint : contentBuilder .String (),
566604 }
567605 }
568606
@@ -571,51 +609,74 @@ func (t *ToolTracker) FlushStreamingText(threshold int, width int, renderMd func
571609 if safeBoundary <= seg .FlushedPos {
572610 // No safe boundary found, can't flush yet
573611 debugFlushf ("stream skip seg=%d reason=no-safe-boundary flushedPos=%d textLen=%d" , segIdx , seg .FlushedPos , len (fullText ))
612+ // Return any tool content we already flushed
613+ if contentBuilder .Len () > 0 {
614+ return FlushStreamingTextResult {ToPrint : contentBuilder .String ()}
615+ }
574616 return FlushStreamingTextResult {}
575617 }
576618
577619 // Fallback for when no streaming renderer is available (should be rare)
578620 toFlush := fullText [seg .FlushedPos :safeBoundary ]
579621 if toFlush == "" {
580622 debugFlushf ("stream skip seg=%d reason=empty-toFlush safeBoundary=%d flushedPos=%d" , segIdx , safeBoundary , seg .FlushedPos )
623+ // Return any tool content we already flushed
624+ if contentBuilder .Len () > 0 {
625+ return FlushStreamingTextResult {ToPrint : contentBuilder .String ()}
626+ }
581627 return FlushStreamingTextResult {}
582628 }
583629
584630 if renderMd == nil {
585631 debugFlushf ("stream skip seg=%d reason=no-renderer safeBoundary=%d flushedPos=%d" , segIdx , safeBoundary , seg .FlushedPos )
632+ // Return any tool content we already flushed
633+ if contentBuilder .Len () > 0 {
634+ return FlushStreamingTextResult {ToPrint : contentBuilder .String ()}
635+ }
586636 return FlushStreamingTextResult {}
587637 }
588638
589639 // Render the full committed markdown so inter-block spacing is preserved.
590640 renderedAll := renderMd (fullText [:safeBoundary ], width )
591641 if renderedAll == "" {
592642 debugFlushf ("stream skip seg=%d reason=empty-rendered-fallback safeBoundary=%d flushedPos=%d" , segIdx , safeBoundary , seg .FlushedPos )
643+ // Return any tool content we already flushed
644+ if contentBuilder .Len () > 0 {
645+ return FlushStreamingTextResult {ToPrint : contentBuilder .String ()}
646+ }
593647 return FlushStreamingTextResult {}
594648 }
595649
596650 rendered := renderedAll
597651 if seg .FlushedRenderedPos > 0 {
598652 if seg .FlushedRenderedPos >= len (renderedAll ) {
599653 debugFlushf ("stream skip seg=%d reason=rendered-pos>=len safeBoundary=%d flushedRenderedPos=%d renderedLen=%d" , segIdx , safeBoundary , seg .FlushedRenderedPos , len (renderedAll ))
654+ // Return any tool content we already flushed
655+ if contentBuilder .Len () > 0 {
656+ return FlushStreamingTextResult {ToPrint : contentBuilder .String ()}
657+ }
600658 return FlushStreamingTextResult {}
601659 }
602660 rendered = renderedAll [seg .FlushedRenderedPos :]
603661 }
604662 if rendered == "" {
605663 debugFlushf ("stream skip seg=%d reason=empty-rendered-slice safeBoundary=%d flushedPos=%d" , segIdx , safeBoundary , seg .FlushedPos )
664+ // Return any tool content we already flushed
665+ if contentBuilder .Len () > 0 {
666+ return FlushStreamingTextResult {ToPrint : contentBuilder .String ()}
667+ }
606668 return FlushStreamingTextResult {}
607669 }
608670
609- var b strings.Builder
610671 // Only add leading separator if this is the very FIRST flush for this segment.
611672 if t .HasFlushed && seg .FlushedPos == 0 {
612- b .WriteString (t .LeadingSeparator (SegmentText ))
673+ contentBuilder .WriteString (t .LeadingSeparator (SegmentText ))
613674 }
614675 // Strip leading blank line since tea.Printf adds a newline after each flush
615676 if t .HasFlushed {
616677 rendered = stripLeadingBlankLine (rendered )
617678 }
618- b .WriteString (rendered )
679+ contentBuilder .WriteString (rendered )
619680
620681 // Update flushed position/state
621682 seg .FlushedPos = safeBoundary
@@ -625,7 +686,7 @@ func (t *ToolTracker) FlushStreamingText(threshold int, width int, renderMd func
625686 debugFlushf ("stream flush seg=%d safeBoundary=%d flushedPos=%d toFlushLen=%d renderedLen=%d" , segIdx , safeBoundary , seg .FlushedPos , len (toFlush ), len (rendered ))
626687
627688 return FlushStreamingTextResult {
628- ToPrint : b .String (),
689+ ToPrint : contentBuilder .String (),
629690 }
630691}
631692
0 commit comments