@@ -221,9 +221,6 @@ export const MessageBlock = ({
221221 ? `${ paddedPreviewPrefix } ${ line } `
222222 : blankPreviewPrefix ,
223223 )
224- if ( ! decorated . some ( ( line ) => line . trim ( ) . length === 0 ) ) {
225- decorated . push ( blankPreviewPrefix )
226- }
227224 return decorated . join ( '\n' )
228225 }
229226 const rawStreamingPreview = isStreaming
@@ -398,7 +395,7 @@ export const MessageBlock = ({
398395 key = { `agent-${ idx } ` }
399396 style = { { wrapMode : 'word' , fg : theme . foreground } }
400397 >
401- { ` • ${ identifier } ` }
398+ { `• ${ identifier } ` }
402399 </ text >
403400 )
404401 }
@@ -421,6 +418,7 @@ export const MessageBlock = ({
421418 streamingPreview = ""
422419 finishedPreview = ""
423420 onToggle = { ( ) => onToggleCollapsed ( agentListBlock . id ) }
421+ dense
424422 />
425423 </ box >
426424 )
@@ -436,7 +434,8 @@ export const MessageBlock = ({
436434 const nestedBlocks = agentBlock . blocks ?? [ ]
437435 const nodes : React . ReactNode [ ] = [ ]
438436
439- nestedBlocks . forEach ( ( nestedBlock , nestedIdx ) => {
437+ for ( let nestedIdx = 0 ; nestedIdx < nestedBlocks . length ; ) {
438+ const nestedBlock = nestedBlocks [ nestedIdx ]
440439 switch ( nestedBlock . type ) {
441440 case 'text' : {
442441 const nestedStatus =
@@ -476,6 +475,7 @@ export const MessageBlock = ({
476475 { renderedContent }
477476 </ text > ,
478477 )
478+ nestedIdx ++
479479 break
480480 }
481481
@@ -498,20 +498,77 @@ export const MessageBlock = ({
498498 } ) }
499499 </ box > ,
500500 )
501+ nestedIdx ++
501502 break
502503 }
503504
504505 case 'tool' : {
505- const isLastBranch = ! hasBranchAfter ( nestedBlocks , nestedIdx )
506- nodes . push (
507- renderToolBranch (
508- nestedBlock ,
506+ const start = nestedIdx
507+ const toolGroup : Extract < ContentBlock , { type : 'tool' } > [ ] = [ ]
508+ while (
509+ nestedIdx < nestedBlocks . length &&
510+ nestedBlocks [ nestedIdx ] . type === 'tool'
511+ ) {
512+ toolGroup . push ( nestedBlocks [ nestedIdx ] as any )
513+ nestedIdx ++
514+ }
515+
516+ const groupNodes = toolGroup . map ( ( toolBlock , idxInGroup ) => {
517+ const globalIdx = start + idxInGroup
518+ const isLastBranch = ! hasBranchAfter ( nestedBlocks , globalIdx )
519+ return renderToolBranch (
520+ toolBlock ,
509521 indentLevel ,
510522 isLastBranch ,
511- `${ keyPrefix } -tool-${ nestedBlock . toolCallId } ` ,
523+ `${ keyPrefix } -tool-${ toolBlock . toolCallId } ` ,
512524 ancestorBranchStates ,
513- ) ,
514- )
525+ )
526+ } )
527+
528+ const nonNullGroupNodes = groupNodes . filter (
529+ Boolean ,
530+ ) as React . ReactNode [ ]
531+ if ( nonNullGroupNodes . length > 0 ) {
532+ const isRenderableBlock = ( b : ContentBlock ) : boolean => {
533+ if ( b . type === 'tool' ) {
534+ return ( b as any ) . toolName !== 'end_turn'
535+ }
536+ switch ( b . type ) {
537+ case 'text' :
538+ case 'html' :
539+ case 'agent' :
540+ case 'agent-list' :
541+ return true
542+ default :
543+ return false
544+ }
545+ }
546+
547+ // Check for any subsequent renderable blocks without allocating a slice
548+ let hasRenderableAfter = false
549+ for ( let i = nestedIdx ; i < nestedBlocks . length ; i ++ ) {
550+ if ( isRenderableBlock ( nestedBlocks [ i ] as any ) ) {
551+ hasRenderableAfter = true
552+ break
553+ }
554+ }
555+ nodes . push (
556+ < box
557+ key = { `${ keyPrefix } -tool-group-${ start } ` }
558+ style = { {
559+ flexDirection : 'column' ,
560+ gap : 0 ,
561+ // Avoid double spacing with the agent header, which already
562+ // adds bottom padding. Only add top margin if this group is
563+ // not the first rendered child.
564+ marginTop : nodes . length === 0 ? 0 : 1 ,
565+ marginBottom : hasRenderableAfter ? 1 : 0 ,
566+ } }
567+ >
568+ { nonNullGroupNodes }
569+ </ box > ,
570+ )
571+ }
515572 break
516573 }
517574
@@ -526,10 +583,11 @@ export const MessageBlock = ({
526583 ancestorBranchStates ,
527584 ) ,
528585 )
586+ nestedIdx ++
529587 break
530588 }
531589 }
532- } )
590+ }
533591
534592 return nodes
535593 }
@@ -554,7 +612,7 @@ export const MessageBlock = ({
554612 )
555613 }
556614
557- const renderBlock = ( block : ContentBlock , idx : number ) => {
615+ const renderSingleBlock = ( block : ContentBlock , idx : number ) => {
558616 switch ( block . type ) {
559617 case 'text' : {
560618 const isStreamingText = isLoading || ! isComplete
@@ -613,14 +671,8 @@ export const MessageBlock = ({
613671 }
614672
615673 case 'tool' : {
616- const isLastBranch = ! hasBranchAfter ( blocks , idx )
617- return renderToolBranch (
618- block ,
619- 0 ,
620- isLastBranch ,
621- `${ messageId } -tool-${ block . toolCallId } ` ,
622- [ ] ,
623- )
674+ // Handled in renderBlocks grouping logic
675+ return null
624676 }
625677
626678 case 'agent' : {
@@ -648,6 +700,57 @@ export const MessageBlock = ({
648700 }
649701 }
650702
703+ const renderBlocks = ( sourceBlocks : ContentBlock [ ] ) => {
704+ const nodes : React . ReactNode [ ] = [ ]
705+ for ( let i = 0 ; i < sourceBlocks . length ; ) {
706+ const block = sourceBlocks [ i ]
707+ if ( block . type === 'tool' ) {
708+ const start = i
709+ const group : Extract < ContentBlock , { type : 'tool' } > [ ] = [ ]
710+ while ( i < sourceBlocks . length && sourceBlocks [ i ] . type === 'tool' ) {
711+ group . push ( sourceBlocks [ i ] as any )
712+ i ++
713+ }
714+
715+ const groupNodes = group . map ( ( toolBlock , idxInGroup ) => {
716+ const globalIdx = start + idxInGroup
717+ const isLastBranch = ! hasBranchAfter ( sourceBlocks , globalIdx )
718+ return renderToolBranch (
719+ toolBlock ,
720+ 0 ,
721+ isLastBranch ,
722+ `${ messageId } -tool-${ toolBlock . toolCallId } ` ,
723+ [ ] ,
724+ )
725+ } )
726+
727+ const nonNullGroupNodes = groupNodes . filter (
728+ Boolean ,
729+ ) as React . ReactNode [ ]
730+ if ( nonNullGroupNodes . length > 0 ) {
731+ nodes . push (
732+ < box
733+ key = { `${ messageId } -tool-group-${ start } ` }
734+ style = { {
735+ flexDirection : 'column' ,
736+ gap : 0 ,
737+ marginTop : 1 ,
738+ marginBottom : 1 ,
739+ } }
740+ >
741+ { nonNullGroupNodes }
742+ </ box > ,
743+ )
744+ }
745+ continue
746+ }
747+
748+ nodes . push ( renderSingleBlock ( block , i ) )
749+ i ++
750+ }
751+ return nodes
752+ }
753+
651754 return (
652755 < >
653756 { isUser && (
@@ -666,7 +769,7 @@ export const MessageBlock = ({
666769 ) }
667770 { blocks ? (
668771 < box style = { { flexDirection : 'column' , gap : 0 , width : '100%' } } >
669- { blocks . map ( renderBlock ) }
772+ { renderBlocks ( blocks ) }
670773 </ box >
671774 ) : (
672775 renderSimpleContent ( )
0 commit comments