@@ -21,6 +21,7 @@ import type {
2121 ExecutionContext ,
2222 NormalizedBlockOutput ,
2323} from '@/executor/types'
24+ import { streamingResponseFormatProcessor } from '@/executor/utils'
2425import { buildBlockExecutionError , normalizeError } from '@/executor/utils/errors'
2526import type { VariableResolver } from '@/executor/variables/resolver'
2627import type { SerializedBlock } from '@/serializer/types'
@@ -100,11 +101,14 @@ export class BlockExecutor {
100101 const streamingExec = output as { stream : ReadableStream ; execution : any }
101102
102103 if ( ctx . onStream ) {
103- try {
104- await ctx . onStream ( streamingExec )
105- } catch ( error ) {
106- logger . error ( 'Error in onStream callback' , { blockId : node . id , error } )
107- }
104+ await this . handleStreamingExecution (
105+ ctx ,
106+ node ,
107+ block ,
108+ streamingExec ,
109+ resolvedInputs ,
110+ ctx . selectedOutputs ?? [ ]
111+ )
108112 }
109113
110114 normalizedOutput = this . normalizeOutput (
@@ -446,4 +450,128 @@ export class BlockExecutor {
446450 }
447451 }
448452 }
453+
454+ private async handleStreamingExecution (
455+ ctx : ExecutionContext ,
456+ node : DAGNode ,
457+ block : SerializedBlock ,
458+ streamingExec : { stream : ReadableStream ; execution : any } ,
459+ resolvedInputs : Record < string , any > ,
460+ selectedOutputs : string [ ]
461+ ) : Promise < void > {
462+ const blockId = node . id
463+
464+ const responseFormat =
465+ resolvedInputs ?. responseFormat ??
466+ ( block . config ?. params as Record < string , any > | undefined ) ?. responseFormat ??
467+ ( block . config as Record < string , any > | undefined ) ?. responseFormat
468+
469+ const stream = streamingExec . stream
470+ if ( typeof stream . tee !== 'function' ) {
471+ await this . forwardStream ( ctx , blockId , streamingExec , stream , responseFormat , selectedOutputs )
472+ return
473+ }
474+
475+ const [ clientStream , executorStream ] = stream . tee ( )
476+
477+ const processedClientStream = streamingResponseFormatProcessor . processStream (
478+ clientStream ,
479+ blockId ,
480+ selectedOutputs ,
481+ responseFormat
482+ )
483+
484+ const clientStreamingExec = {
485+ ...streamingExec ,
486+ stream : processedClientStream ,
487+ }
488+
489+ const executorConsumption = this . consumeExecutorStream (
490+ executorStream ,
491+ streamingExec ,
492+ blockId ,
493+ responseFormat
494+ )
495+
496+ const clientConsumption = ( async ( ) => {
497+ try {
498+ await ctx . onStream ?.( clientStreamingExec )
499+ } catch ( error ) {
500+ logger . error ( 'Error in onStream callback' , { blockId, error } )
501+ }
502+ } ) ( )
503+
504+ await Promise . all ( [ clientConsumption , executorConsumption ] )
505+ }
506+
507+ private async forwardStream (
508+ ctx : ExecutionContext ,
509+ blockId : string ,
510+ streamingExec : { stream : ReadableStream ; execution : any } ,
511+ stream : ReadableStream ,
512+ responseFormat : any ,
513+ selectedOutputs : string [ ]
514+ ) : Promise < void > {
515+ const processedStream = streamingResponseFormatProcessor . processStream (
516+ stream ,
517+ blockId ,
518+ selectedOutputs ,
519+ responseFormat
520+ )
521+
522+ try {
523+ await ctx . onStream ?.( {
524+ ...streamingExec ,
525+ stream : processedStream ,
526+ } )
527+ } catch ( error ) {
528+ logger . error ( 'Error in onStream callback' , { blockId, error } )
529+ }
530+ }
531+
532+ private async consumeExecutorStream (
533+ stream : ReadableStream ,
534+ streamingExec : { execution : any } ,
535+ blockId : string ,
536+ responseFormat : any
537+ ) : Promise < void > {
538+ const reader = stream . getReader ( )
539+ const decoder = new TextDecoder ( )
540+ let fullContent = ''
541+
542+ try {
543+ while ( true ) {
544+ const { done, value } = await reader . read ( )
545+ if ( done ) break
546+ fullContent += decoder . decode ( value , { stream : true } )
547+ }
548+ } catch ( error ) {
549+ logger . error ( 'Error reading executor stream for block' , { blockId, error } )
550+ } finally {
551+ try {
552+ reader . releaseLock ( )
553+ } catch { }
554+ }
555+
556+ if ( ! fullContent ) {
557+ return
558+ }
559+
560+ const executionOutput = streamingExec . execution ?. output
561+ if ( ! executionOutput || typeof executionOutput !== 'object' ) {
562+ return
563+ }
564+
565+ if ( responseFormat ) {
566+ try {
567+ const parsed = JSON . parse ( fullContent . trim ( ) )
568+ Object . assign ( executionOutput , parsed )
569+ return
570+ } catch ( error ) {
571+ logger . warn ( 'Failed to parse streamed content for response format' , { blockId, error } )
572+ }
573+ }
574+
575+ executionOutput . content = fullContent
576+ }
449577}
0 commit comments