@@ -457,8 +457,6 @@ class LLM_Agentflow implements INode {
457457 */
458458 await addImageArtifactsToMessages ( messages , options )
459459
460- console . log ( 'messages' , messages [ 0 ] )
461-
462460 // Configure structured output if specified
463461 const isStructuredOutput = _llmStructuredOutput && Array . isArray ( _llmStructuredOutput ) && _llmStructuredOutput . length > 0
464462 if ( isStructuredOutput ) {
@@ -484,7 +482,15 @@ class LLM_Agentflow implements INode {
484482 * Invoke LLM
485483 */
486484 if ( isStreamable ) {
487- response = await this . handleStreamingResponse ( sseStreamer , llmNodeInstance , messages , chatId , abortController )
485+ response = await this . handleStreamingResponse (
486+ sseStreamer ,
487+ llmNodeInstance ,
488+ messages ,
489+ chatId ,
490+ abortController ,
491+ isStructuredOutput ,
492+ isLastNode
493+ )
488494 } else {
489495 response = await llmNodeInstance . invoke ( messages , { signal : abortController ?. signal } )
490496
@@ -502,7 +508,6 @@ class LLM_Agentflow implements INode {
502508 sseStreamer . streamTokenEvent ( chatId , finalResponse )
503509 }
504510 }
505- console . log ( 'response.contentBlocks' , response . contentBlocks )
506511
507512 // Calculate execution time
508513 const endTime = Date . now ( )
@@ -555,6 +560,31 @@ class LLM_Agentflow implements INode {
555560 }
556561 }
557562
563+ // Extract reason content from response (reasoning_content/reasoning_duration or contentBlocks)
564+ let reasonContent = ( response . additional_kwargs ?. reasoning_content as string ) || ''
565+ let thinkingDuration : number | undefined =
566+ typeof response . additional_kwargs ?. reasoning_duration === 'number'
567+ ? response . additional_kwargs . reasoning_duration
568+ : undefined
569+ if ( ! reasonContent && response . contentBlocks ?. length && isLastNode && sseStreamer && ! isStructuredOutput ) {
570+ for ( const block of response . contentBlocks ) {
571+ if ( block . type === 'reasoning' && ( block as { reasoning ?: string } ) . reasoning ) {
572+ reasonContent += ( block as { reasoning : string } ) . reasoning
573+ }
574+ if ( ( block as any ) . type === 'thinking' && ( block as any ) . thinking ) {
575+ reasonContent += ( block as any ) . thinking
576+ }
577+ }
578+ if ( reasonContent ) {
579+ sseStreamer . streamThinkingEvent ( chatId , reasonContent )
580+ const reasoningTokens = response . usage_metadata ?. output_token_details ?. reasoning || 0
581+ thinkingDuration = reasoningTokens > 0 ? Math . round ( reasoningTokens / 50 ) : 2
582+ sseStreamer . streamThinkingEvent ( chatId , '' , thinkingDuration )
583+ }
584+ }
585+ const reasonContentObj =
586+ reasonContent !== undefined && reasonContent !== '' ? { thinking : reasonContent , thinkingDuration } : undefined
587+
558588 // Prepare final response and output object
559589 let finalResponse = ''
560590 if ( response . content && Array . isArray ( response . content ) ) {
@@ -575,7 +605,8 @@ class LLM_Agentflow implements INode {
575605 timeDelta ,
576606 isStructuredOutput ,
577607 artifacts ,
578- fileAnnotations
608+ fileAnnotations ,
609+ reasonContentObj
579610 )
580611
581612 // End analytics tracking
@@ -839,16 +870,43 @@ class LLM_Agentflow implements INode {
839870 llmNodeInstance : BaseChatModel ,
840871 messages : BaseMessageLike [ ] ,
841872 chatId : string ,
842- abortController : AbortController
873+ abortController : AbortController ,
874+ isStructuredOutput : boolean = false ,
875+ isLastNode : boolean = false
843876 ) : Promise < AIMessageChunk > {
844877 let response = new AIMessageChunk ( '' )
878+ let reasonContent = ''
879+ let thinkingDuration : number | undefined
880+ let thinkingStartTime : number | null = null
881+ let wasThinking = false
882+ let sentLastThinkingEvent = false
845883
846884 try {
847885 for await ( const chunk of await llmNodeInstance . stream ( messages , { signal : abortController ?. signal } ) ) {
848- if ( sseStreamer ) {
886+ if ( sseStreamer && ! isStructuredOutput ) {
849887 let content = ''
850888
851- console . log ( 'chunk.contentBlocks' , chunk . contentBlocks )
889+ if ( chunk . contentBlocks ?. length ) {
890+ for ( const block of chunk . contentBlocks ) {
891+ if ( isLastNode ) {
892+ // As soon as we see the first non-reasoning block, send last thinking event with duration (only when isLastNode)
893+ if ( block . type !== 'reasoning' && wasThinking && ! sentLastThinkingEvent && thinkingStartTime != null ) {
894+ thinkingDuration = Math . round ( ( Date . now ( ) - thinkingStartTime ) / 1000 )
895+ sseStreamer . streamThinkingEvent ( chatId , '' , thinkingDuration )
896+ sentLastThinkingEvent = true
897+ }
898+ if ( block . type === 'reasoning' && ( block as { reasoning ?: string } ) . reasoning ) {
899+ if ( ! thinkingStartTime ) {
900+ thinkingStartTime = Date . now ( )
901+ }
902+ wasThinking = true
903+ const reasoningContent = ( block as { reasoning : string } ) . reasoning
904+ sseStreamer . streamThinkingEvent ( chatId , reasoningContent )
905+ reasonContent += reasoningContent
906+ }
907+ }
908+ }
909+ }
852910
853911 if ( typeof chunk === 'string' ) {
854912 content = chunk
@@ -868,10 +926,26 @@ class LLM_Agentflow implements INode {
868926 console . error ( 'Error during streaming:' , error )
869927 throw error
870928 }
929+
930+ // Only convert to string if all content items are text (no inlineData or other special types)
871931 if ( Array . isArray ( response . content ) && response . content . length > 0 ) {
872- const responseContents = response . content as ContentBlock . Text [ ]
873- response . content = responseContents . map ( ( item ) => item . text ) . join ( '' )
932+ const hasNonTextContent = response . content . some (
933+ ( item : any ) => item . type === 'inlineData' || item . type === 'executableCode' || item . type === 'codeExecutionResult'
934+ )
935+ if ( ! hasNonTextContent ) {
936+ const responseContents = response . content as ContentBlock . Text [ ]
937+ response . content = responseContents . map ( ( item ) => item . text ) . join ( '' )
938+ }
874939 }
940+
941+ if ( reasonContent . length > 0 ) {
942+ response . additional_kwargs = {
943+ ...response . additional_kwargs ,
944+ reasoning_content : reasonContent ,
945+ reasoning_duration : thinkingDuration
946+ }
947+ }
948+
875949 return response
876950 }
877951
@@ -886,7 +960,8 @@ class LLM_Agentflow implements INode {
886960 timeDelta : number ,
887961 isStructuredOutput : boolean ,
888962 artifacts : any [ ] = [ ] ,
889- fileAnnotations : any [ ] = [ ]
963+ fileAnnotations : any [ ] = [ ] ,
964+ reasonContent ?: { thinking : string ; thinkingDuration ?: number }
890965 ) : any {
891966 const output : any = {
892967 content : finalResponse ,
@@ -926,6 +1001,10 @@ class LLM_Agentflow implements INode {
9261001 output . fileAnnotations = fileAnnotations
9271002 }
9281003
1004+ if ( reasonContent ) {
1005+ output . reasonContent = reasonContent
1006+ }
1007+
9291008 return output
9301009 }
9311010
0 commit comments