@@ -6,6 +6,7 @@ import { AgentBranchItem } from './agent-branch-item'
66import { ElapsedTimer } from './elapsed-timer'
77import { renderToolComponent } from './tools/registry'
88import { ToolCallItem } from './tools/tool-call-item'
9+ import { Thinking } from './thinking'
910import { useTheme } from '../hooks/use-theme'
1011import { getToolDisplayInfo } from '../utils/codebuff-client'
1112import {
@@ -24,6 +25,12 @@ const trimTrailingNewlines = (value: string): string =>
2425const sanitizePreview = ( value : string ) : string =>
2526 value . replace ( / [ # * _ ` ~ \[ \] ( ) ] / g, '' ) . trim ( )
2627
28+ // Delete any complete <cb_plan>...</cb_plan> segment; hide any partial open <cb_plan>... (until closing tag arrives)
29+ const scrubPlanTags = ( value : string ) : string =>
30+ value
31+ . replace ( / < c b _ p l a n > [ \s \S ] * ?< \/ c b _ p l a n > / g, '' )
32+ . replace ( / < c b _ p l a n > [ \s \S ] * $ / g, '' )
33+
2734interface MessageBlockProps {
2835 messageId : string
2936 blocks ?: ContentBlock [ ]
@@ -100,9 +107,8 @@ export const MessageBlock = memo(
100107 . filter ( ( line ) => line . trim ( ) )
101108 const lastThreeLines = outputLines . slice ( - 3 )
102109 const hasMoreLines = outputLines . length > 3
103- return hasMoreLines
104- ? '...\n' + lastThreeLines . join ( '\n' )
105- : lastThreeLines . join ( '\n' )
110+ const preview = lastThreeLines . join ( '\n' )
111+ return hasMoreLines ? `...\n${ preview } ` : preview
106112 }
107113
108114 return sanitizePreview ( lastLine )
@@ -120,6 +126,45 @@ export const MessageBlock = memo(
120126 }
121127 }
122128
129+ const isReasoningTextBlock = ( b : any ) : boolean =>
130+ b ?. type === 'text' &&
131+ ( b . textType === 'reasoning' ||
132+ b . textType === 'reasoning_chunk' ||
133+ ( typeof b . color === 'string' &&
134+ ( b . color . toLowerCase ( ) === 'grey' || b . color . toLowerCase ( ) === 'gray' ) ) )
135+
136+ const renderThinkingBlock = (
137+ blocks : Extract < ContentBlock , { type : 'text' } > [ ] ,
138+ keyPrefix : string ,
139+ startIndex : number ,
140+ indentLevel : number = 0 ,
141+ ) : React . ReactNode => {
142+ const thinkingId = `${ keyPrefix } -thinking-${ startIndex } `
143+ const combinedContent = blocks
144+ . map ( ( b ) => b . content )
145+ . join ( '' )
146+ . trim ( )
147+
148+ if ( ! combinedContent ) {
149+ return null
150+ }
151+
152+ const isCollapsed = collapsedAgents . has ( thinkingId )
153+ const marginLeft = Math . max ( 0 , indentLevel * 2 )
154+ const availWidth = Math . max ( 10 , availableWidth - marginLeft - 4 )
155+
156+ return (
157+ < box key = { thinkingId } style = { { marginLeft } } >
158+ < Thinking
159+ content = { scrubPlanTags ( combinedContent ) }
160+ isCollapsed = { isCollapsed }
161+ onToggle = { ( ) => onToggleCollapsed ( thinkingId ) }
162+ availableWidth = { availWidth }
163+ />
164+ </ box >
165+ )
166+ }
167+
123168 const renderToolBranch = (
124169 toolBlock : Extract < ContentBlock , { type : 'tool' } > ,
125170 indentLevel : number ,
@@ -363,6 +408,29 @@ export const MessageBlock = memo(
363408
364409 for ( let nestedIdx = 0 ; nestedIdx < nestedBlocks . length ; ) {
365410 const nestedBlock = nestedBlocks [ nestedIdx ]
411+ // Handle reasoning text blocks in agents
412+ if ( isReasoningTextBlock ( nestedBlock ) ) {
413+ const start = nestedIdx
414+ const reasoningBlocks : Extract < ContentBlock , { type : 'text' } > [ ] = [ ]
415+ while (
416+ nestedIdx < nestedBlocks . length &&
417+ isReasoningTextBlock ( nestedBlocks [ nestedIdx ] as any )
418+ ) {
419+ reasoningBlocks . push ( nestedBlocks [ nestedIdx ] as any )
420+ nestedIdx ++
421+ }
422+
423+ const thinkingNode = renderThinkingBlock (
424+ reasoningBlocks ,
425+ keyPrefix ,
426+ start ,
427+ indentLevel ,
428+ )
429+ if ( thinkingNode ) {
430+ nodes . push ( thinkingNode )
431+ }
432+ continue
433+ }
366434 switch ( nestedBlock . type ) {
367435 case 'text' : {
368436 const nestedStatus =
@@ -374,10 +442,11 @@ export const MessageBlock = memo(
374442 const rawNestedContent = isNestedStreamingText
375443 ? trimTrailingNewlines ( nestedBlock . content )
376444 : nestedBlock . content . trim ( )
445+ const filteredNestedContent = scrubPlanTags ( rawNestedContent )
377446 const renderKey = `${ keyPrefix } -text-${ nestedIdx } `
378447 const markdownOptionsForLevel = getAgentMarkdownOptions ( indentLevel )
379448 const renderedContent = renderContentWithMarkdown (
380- rawNestedContent ,
449+ filteredNestedContent ,
381450 isNestedStreamingText ,
382451 markdownOptionsForLevel ,
383452 )
@@ -517,8 +586,9 @@ export const MessageBlock = memo(
517586 const normalizedContent = isStreamingMessage
518587 ? trimTrailingNewlines ( content )
519588 : content . trim ( )
589+ const sanitizedContent = scrubPlanTags ( normalizedContent )
520590 const displayContent = renderContentWithMarkdown (
521- normalizedContent ,
591+ sanitizedContent ,
522592 isStreamingMessage ,
523593 markdownOptions ,
524594 )
@@ -536,13 +606,18 @@ export const MessageBlock = memo(
536606 const renderSingleBlock = ( block : ContentBlock , idx : number ) => {
537607 switch ( block . type ) {
538608 case 'text' : {
609+ // Skip raw rendering for reasoning; grouped above into <Thinking>
610+ if ( isReasoningTextBlock ( block as any ) ) {
611+ return null
612+ }
539613 const isStreamingText = isLoading || ! isComplete
540614 const rawContent = isStreamingText
541615 ? trimTrailingNewlines ( block . content )
542616 : block . content . trim ( )
617+ const filteredContent = scrubPlanTags ( rawContent )
543618 const renderKey = `${ messageId } -text-${ idx } `
544619 const renderedContent = renderContentWithMarkdown (
545- rawContent ,
620+ filteredContent ,
546621 isStreamingText ,
547622 markdownOptions ,
548623 )
@@ -622,6 +697,28 @@ export const MessageBlock = memo(
622697 const nodes : React . ReactNode [ ] = [ ]
623698 for ( let i = 0 ; i < sourceBlocks . length ; ) {
624699 const block = sourceBlocks [ i ]
700+ // Handle reasoning text blocks
701+ if ( isReasoningTextBlock ( block as any ) ) {
702+ const start = i
703+ const reasoningBlocks : Extract < ContentBlock , { type : 'text' } > [ ] = [ ]
704+ while (
705+ i < sourceBlocks . length &&
706+ isReasoningTextBlock ( sourceBlocks [ i ] as any )
707+ ) {
708+ reasoningBlocks . push ( sourceBlocks [ i ] as any )
709+ i ++
710+ }
711+
712+ const thinkingNode = renderThinkingBlock (
713+ reasoningBlocks ,
714+ messageId ,
715+ start ,
716+ )
717+ if ( thinkingNode ) {
718+ nodes . push ( thinkingNode )
719+ }
720+ continue
721+ }
625722 if ( block . type === 'tool' ) {
626723 const start = i
627724 const group : Extract < ContentBlock , { type : 'tool' } > [ ] = [ ]
0 commit comments