@@ -13,6 +13,9 @@ import {
1313import type { ContentBlock } from '../chat'
1414import type { ChatTheme } from '../utils/theme-system'
1515
16+ const trimTrailingNewlines = ( value : string ) : string =>
17+ value . replace ( / [ \r \n ] + $ / g, '' )
18+
1619interface MessageBlockProps {
1720 messageId : string
1821 blocks ?: ContentBlock [ ]
@@ -33,6 +36,7 @@ interface MessageBlockProps {
3336 collapsedAgents : Set < string >
3437 streamingAgents : Set < string >
3538 onToggleCollapsed : ( id : string ) => void
39+ registerAgentRef : ( id : string , element : any ) => void
3640}
3741
3842export const MessageBlock = ( {
@@ -55,6 +59,7 @@ export const MessageBlock = ({
5559 collapsedAgents,
5660 streamingAgents,
5761 onToggleCollapsed,
62+ registerAgentRef,
5863} : MessageBlockProps ) : ReactNode => {
5964 return (
6065 < >
@@ -76,10 +81,16 @@ export const MessageBlock = ({
7681 < box style = { { flexDirection : 'column' , gap : 0 , width : '100%' } } >
7782 { blocks . map ( ( block , idx ) => {
7883 if ( block . type === 'text' ) {
79- const trimmedContent = block . content . trim ( )
80- const renderedContent = hasMarkdown ( trimmedContent )
81- ? renderStreamingMarkdown ( trimmedContent , markdownOptions )
82- : trimmedContent
84+ const isStreamingText = isLoading || ! isComplete
85+ const rawContent = isStreamingText
86+ ? trimTrailingNewlines ( block . content )
87+ : block . content . trim ( )
88+ const renderKey = `${ messageId } -text-${ idx } -${ rawContent . length } -${ isStreamingText ? 'stream' : 'final' } `
89+ const renderedContent = hasMarkdown ( rawContent )
90+ ? isStreamingText
91+ ? renderStreamingMarkdown ( rawContent , markdownOptions )
92+ : renderMarkdown ( rawContent , markdownOptions )
93+ : rawContent
8394 const prevBlock = idx > 0 ? blocks [ idx - 1 ] : null
8495 const marginTop =
8596 prevBlock &&
@@ -88,7 +99,7 @@ export const MessageBlock = ({
8899 : 0
89100 return (
90101 < text
91- key = { ` ${ messageId } -text- ${ idx } ` }
102+ key = { renderKey }
92103 wrap
93104 style = { { fg : textColor , marginTop } }
94105 >
@@ -155,7 +166,10 @@ export const MessageBlock = ({
155166 const branchChar = isLastBranch ? '└─ ' : '├─ '
156167
157168 return (
158- < box key = { `${ messageId } -tool-${ block . toolCallId } ` } >
169+ < box
170+ key = { `${ messageId } -tool-${ block . toolCallId } ` }
171+ ref = { ( el : any ) => registerAgentRef ( block . toolCallId , el ) }
172+ >
159173 < BranchItem
160174 name = { displayInfo . name }
161175 content = { displayContent }
@@ -171,7 +185,8 @@ export const MessageBlock = ({
171185 )
172186 } else if ( block . type === 'agent' ) {
173187 const isCollapsed = collapsedAgents . has ( block . agentId )
174- const isStreaming = streamingAgents . has ( block . agentId )
188+ const isStreaming =
189+ block . status === 'running' || streamingAgents . has ( block . agentId )
175190
176191 const allTextContent =
177192 block . blocks
@@ -208,15 +223,27 @@ export const MessageBlock = ({
208223 < box style = { { flexDirection : 'column' , gap : 0 } } >
209224 { block . blocks ?. map ( ( nestedBlock , nestedIdx ) => {
210225 if ( nestedBlock . type === 'text' ) {
211- const renderedContent = hasMarkdown ( nestedBlock . content )
212- ? renderStreamingMarkdown (
213- nestedBlock . content ,
214- agentMarkdownOptions ,
215- )
216- : nestedBlock . content
226+ const nestedStatus =
227+ typeof ( nestedBlock as any ) . status === 'string'
228+ ? ( nestedBlock as any ) . status
229+ : undefined
230+ const isNestedStreamingText =
231+ isStreaming || nestedStatus === 'running'
232+ const rawNestedContent = isNestedStreamingText
233+ ? trimTrailingNewlines ( nestedBlock . content )
234+ : nestedBlock . content . trim ( )
235+ const renderKey = `${ messageId } -agent-${ block . agentId } -text-${ nestedIdx } -${ rawNestedContent . length } -${ isNestedStreamingText ? 'stream' : 'final' } `
236+ const renderedContent = hasMarkdown ( rawNestedContent )
237+ ? isNestedStreamingText
238+ ? renderStreamingMarkdown (
239+ rawNestedContent ,
240+ agentMarkdownOptions ,
241+ )
242+ : renderMarkdown ( rawNestedContent , agentMarkdownOptions )
243+ : rawNestedContent
217244 return (
218245 < text
219- key = { ` ${ messageId } -agent- ${ block . agentId } -text- ${ nestedIdx } ` }
246+ key = { renderKey }
220247 wrap
221248 style = { { fg : theme . agentText , marginLeft : 2 } }
222249 >
@@ -305,6 +332,9 @@ export const MessageBlock = ({
305332 return (
306333 < box
307334 key = { `${ messageId } -agent-${ block . agentId } -tool-${ nestedBlock . toolCallId } ` }
335+ ref = { ( el : any ) =>
336+ registerAgentRef ( nestedBlock . toolCallId , el )
337+ }
308338 >
309339 < BranchItem
310340 name = { displayInfo . name }
@@ -333,6 +363,7 @@ export const MessageBlock = ({
333363 return (
334364 < box
335365 key = { `${ messageId } -agent-${ block . agentId } ` }
366+ ref = { ( el : any ) => registerAgentRef ( block . agentId , el ) }
336367 style = { { flexDirection : 'column' , gap : 0 } }
337368 >
338369 < BranchItem
@@ -354,17 +385,26 @@ export const MessageBlock = ({
354385 } ) }
355386 </ box >
356387 ) : (
357- < text
358- key = { `message-content-${ messageId } ` }
359- wrap
360- style = { { fg : textColor } }
361- >
362- { isLoading
363- ? ''
364- : hasMarkdown ( content )
365- ? renderStreamingMarkdown ( content , markdownOptions )
366- : content }
367- </ text >
388+ ( ( ) => {
389+ const isStreamingMessage = isLoading || ! isComplete
390+ const normalizedContent = isStreamingMessage
391+ ? trimTrailingNewlines ( content )
392+ : content . trim ( )
393+ const displayContent = hasMarkdown ( normalizedContent )
394+ ? isStreamingMessage
395+ ? renderStreamingMarkdown ( normalizedContent , markdownOptions )
396+ : renderMarkdown ( normalizedContent , markdownOptions )
397+ : normalizedContent
398+ return (
399+ < text
400+ key = { `message-content-${ messageId } -${ normalizedContent . length } -${ isStreamingMessage ? 'stream' : 'final' } ` }
401+ wrap
402+ style = { { fg : textColor } }
403+ >
404+ { displayContent }
405+ </ text >
406+ )
407+ } ) ( )
368408 ) }
369409 { isAi && isComplete && ( completionTime || credits ) && (
370410 < text
0 commit comments