@@ -200,6 +200,47 @@ function resolveAssistantTextChunk(params: {
200200 return "" ;
201201}
202202
203+ const REASONING_TAG_RE = / < \s * \/ ? \s * (?: (?: a n t m l : ) ? (?: t h i n k (?: i n g ) ? | t h o u g h t ) | a n t t h i n k i n g ) \b / i;
204+
205+ function resolveStreamVisibleText ( params : {
206+ previousRawText : string ;
207+ visibleDelta : string ;
208+ finalText ?: string ;
209+ } ) : { rawText : string ; visibleText : string } {
210+ if ( params . finalText !== undefined ) {
211+ const rawText = params . finalText ;
212+ return { rawText, visibleText : rawText . trim ( ) } ;
213+ }
214+ const rawText = `${ params . previousRawText } ${ params . visibleDelta } ` ;
215+ return { rawText, visibleText : rawText . trim ( ) } ;
216+ }
217+
218+ function copyPartialBlockState (
219+ target : EmbeddedAgentSubscribeState [ "partialBlockState" ] ,
220+ source : EmbeddedAgentSubscribeState [ "partialBlockState" ] ,
221+ ) {
222+ const copyFenceState = ( fence ?: typeof source . fence ) =>
223+ fence
224+ ? {
225+ atLineStart : fence . atLineStart ,
226+ ...( fence . open ? { open : { ...fence . open } } : { } ) ,
227+ }
228+ : undefined ;
229+ target . thinking = source . thinking ;
230+ target . final = source . final ;
231+ target . inlineCode = { ...source . inlineCode } ;
232+ target . fence = copyFenceState ( source . fence ) ;
233+ target . reasoningInlineCode = source . reasoningInlineCode
234+ ? { ...source . reasoningInlineCode }
235+ : undefined ;
236+ target . reasoningFence = copyFenceState ( source . reasoningFence ) ;
237+ target . reasoningPendingFenceFragment = source . reasoningPendingFenceFragment ;
238+ target . finalInlineCode = source . finalInlineCode ? { ...source . finalInlineCode } : undefined ;
239+ target . finalFence = copyFenceState ( source . finalFence ) ;
240+ target . pendingFenceFragment = source . pendingFenceFragment ;
241+ target . pendingTagFragment = source . pendingTagFragment ;
242+ }
243+
203244export function resolveSilentReplyFallbackText ( params : {
204245 text : unknown ;
205246 messagingToolSentTexts : string [ ] ;
@@ -551,9 +592,6 @@ export function handleMessageUpdate(
551592 if ( isPhasePendingOpenAiResponsesTextItem ) {
552593 return ;
553594 }
554- const phaseAwareVisibleText = coerceChatContentText (
555- extractAssistantVisibleText ( partialAssistant ) ,
556- ) . trim ( ) ;
557595 const shouldUsePhaseAwareBlockReply = Boolean ( deliveryPhase ) ;
558596
559597 if ( chunk ) {
@@ -567,27 +605,58 @@ export function handleMessageUpdate(
567605 // Handle partial <think> tags: stream whatever reasoning is visible so far.
568606 ctx . emitReasoningStream ( extractThinkingFromTaggedStream ( ctx . state . deltaBuffer ) ) ;
569607 }
570- const next =
571- phaseAwareVisibleText ||
572- ( deliveryPhase === "final_answer"
573- ? ""
574- : ctx
575- . stripBlockTags (
576- ctx . state . deltaBuffer ,
577- {
578- thinking : false ,
579- final : false ,
580- inlineCode : createInlineCodeState ( ) ,
581- } ,
582- { final : evtType === "text_end" } ,
583- )
584- . trim ( ) ) ;
608+ const wasThinking = ctx . state . partialBlockState . thinking ;
609+ let visibleDelta = "" ;
610+ let next = shouldUsePhaseAwareBlockReply
611+ ? coerceChatContentText ( extractAssistantVisibleText ( partialAssistant ) ) . trim ( )
612+ : "" ;
613+ let nextRawStreamText = next ;
614+ if ( ! next && deliveryPhase !== "final_answer" ) {
615+ const pendingTagFragment = ctx . state . partialBlockState . pendingTagFragment ;
616+ const shouldRecomputeFullStream = Boolean ( pendingTagFragment ) || REASONING_TAG_RE . test ( chunk ) ;
617+ if ( shouldRecomputeFullStream ) {
618+ const recomputeState : EmbeddedAgentSubscribeState [ "partialBlockState" ] = {
619+ thinking : false ,
620+ final : false ,
621+ inlineCode : createInlineCodeState ( ) ,
622+ } ;
623+ const recomputedRawText = ctx . stripBlockTags ( ctx . state . deltaBuffer , recomputeState , {
624+ final : evtType === "text_end" ,
625+ } ) ;
626+ const previousRawText = ctx . state . lastStreamedAssistant ?? "" ;
627+ const isFullStreamReplacement = ! recomputedRawText . startsWith ( previousRawText ) ;
628+ next = recomputedRawText . trim ( ) ;
629+ visibleDelta = isFullStreamReplacement
630+ ? recomputedRawText
631+ : recomputedRawText . slice ( previousRawText . length ) ;
632+ nextRawStreamText = recomputedRawText ;
633+ copyPartialBlockState ( ctx . state . partialBlockState , recomputeState ) ;
634+ } else {
635+ visibleDelta =
636+ chunk || evtType === "text_end"
637+ ? ctx . stripBlockTags ( chunk , ctx . state . partialBlockState , {
638+ final : evtType === "text_end" ,
639+ } )
640+ : "" ;
641+ if ( ctx . state . partialBlockState . pendingTagFragment ) {
642+ visibleDelta = "" ;
643+ next = ctx . state . lastStreamedAssistantCleaned ?? "" ;
644+ nextRawStreamText = ctx . state . lastStreamedAssistant ?? "" ;
645+ } else {
646+ const streamVisibleText = resolveStreamVisibleText ( {
647+ previousRawText : ctx . state . lastStreamedAssistant ?? "" ,
648+ visibleDelta,
649+ } ) ;
650+ next = streamVisibleText . visibleText ;
651+ nextRawStreamText = streamVisibleText . rawText ;
652+ }
653+ }
654+ } else if ( next && ( chunk || evtType === "text_end" ) ) {
655+ visibleDelta = ctx . stripBlockTags ( chunk , ctx . state . partialBlockState , {
656+ final : evtType === "text_end" ,
657+ } ) ;
658+ }
585659 if ( next ) {
586- const wasThinking = ctx . state . partialBlockState . thinking ;
587- const visibleDelta =
588- chunk || evtType === "text_end"
589- ? ctx . stripBlockTags ( chunk , ctx . state . partialBlockState , { final : evtType === "text_end" } )
590- : "" ;
591660 if ( ! wasThinking && ctx . state . partialBlockState . thinking ) {
592661 openReasoningStream ( ctx ) ;
593662 }
@@ -636,7 +705,7 @@ export function handleMessageUpdate(
636705 }
637706 }
638707
639- ctx . state . lastStreamedAssistant = next ;
708+ ctx . state . lastStreamedAssistant = nextRawStreamText ;
640709 ctx . state . lastStreamedAssistantCleaned = cleanedText ;
641710
642711 if ( ctx . params . silentExpected || suppressDeterministicApprovalOutput ) {
@@ -755,7 +824,21 @@ export function handleMessageEnd(
755824 ctx . state . blockState . thinking = false ;
756825 ctx . state . blockState . final = false ;
757826 ctx . state . blockState . inlineCode = createInlineCodeState ( ) ;
827+ ctx . state . blockState . fence = undefined ;
828+ ctx . state . blockState . reasoningInlineCode = undefined ;
829+ ctx . state . blockState . reasoningFence = undefined ;
830+ ctx . state . blockState . reasoningPendingFenceFragment = undefined ;
831+ ctx . state . blockState . finalInlineCode = undefined ;
832+ ctx . state . blockState . finalFence = undefined ;
833+ ctx . state . blockState . pendingFenceFragment = undefined ;
758834 ctx . state . blockState . pendingTagFragment = undefined ;
835+ ctx . state . partialBlockState . fence = undefined ;
836+ ctx . state . partialBlockState . reasoningInlineCode = undefined ;
837+ ctx . state . partialBlockState . reasoningFence = undefined ;
838+ ctx . state . partialBlockState . reasoningPendingFenceFragment = undefined ;
839+ ctx . state . partialBlockState . finalInlineCode = undefined ;
840+ ctx . state . partialBlockState . finalFence = undefined ;
841+ ctx . state . partialBlockState . pendingFenceFragment = undefined ;
759842 ctx . state . partialBlockState . pendingTagFragment = undefined ;
760843 ctx . state . lastStreamedAssistant = undefined ;
761844 ctx . state . lastStreamedAssistantCleaned = undefined ;
0 commit comments