@@ -20,13 +20,13 @@ import EventEmitter from 'events';
2020import * as fsSync from 'fs' ;
2121import fs from 'fs/promises' ;
2222import path from 'path' ;
23+ import { GenkitError } from '../types' ;
2324import {
2425 RunActionResponseSchema ,
2526 type Action ,
2627 type RunActionResponse ,
2728} from '../types/action' ;
2829import * as apis from '../types/apis' ;
29- import type { GenkitError } from '../types/error' ;
3030import type { TraceData } from '../types/trace' ;
3131import { logger } from '../utils/logger' ;
3232import {
@@ -228,7 +228,12 @@ export class RuntimeManager {
228228 responseType : 'stream' ,
229229 }
230230 )
231- . catch ( this . httpErrorHandler ) ;
231+ . catch ( ( err ) =>
232+ this . handleStreamError (
233+ err ,
234+ `Error running action key='${ input . key } '.`
235+ )
236+ ) ;
232237 let genkitVersion : string ;
233238 if ( response . headers [ 'x-genkit-version' ] ) {
234239 genkitVersion = response . headers [ 'x-genkit-version' ] ;
@@ -302,7 +307,10 @@ export class RuntimeManager {
302307 responseType : 'stream' , // Use stream to get early headers
303308 } )
304309 . catch ( ( err ) =>
305- this . httpErrorHandler ( err , `Error running action key='${ input . key } '.` )
310+ this . handleStreamError (
311+ err ,
312+ `Error running action key='${ input . key } '.`
313+ )
306314 ) ;
307315
308316 const traceId = response . headers [ 'x-genkit-trace-id' ] ;
@@ -735,15 +743,23 @@ export class RuntimeManager {
735743 /**
736744 * Handles an HTTP error.
737745 */
738- private httpErrorHandler ( error : AxiosError , message ?: string ) : any {
746+ private httpErrorHandler ( error : AxiosError , message ?: string ) : never {
739747 const newError = new GenkitToolsError ( message || 'Internal Error' ) ;
740748
741749 if ( error . response ) {
742- if ( ( error . response ?. data as any ) . message ) {
743- newError . message = ( error . response ?. data as any ) . message ;
744- }
745750 // we got a non-200 response; copy the payload and rethrow
746751 newError . data = error . response . data as GenkitError ;
752+ newError . stack = ( error . response ?. data as any ) . message ;
753+ if ( ( error . response ?. data as any ) . message ) {
754+ newError . data . data = {
755+ ...newError . data . data ,
756+ genkitErrorMessage : message ,
757+ genkitErrorDetails : {
758+ stack : ( error . response ?. data as any ) . message ,
759+ traceId : ( error . response ?. data as any ) . traceId ,
760+ } ,
761+ } ;
762+ }
747763 throw newError ;
748764 }
749765
@@ -753,6 +769,57 @@ export class RuntimeManager {
753769 } ) ;
754770 }
755771
772+ /**
773+ * Handles a stream error by reading the stream and then calling httpErrorHandler.
774+ */
775+ private async handleStreamError (
776+ error : AxiosError ,
777+ message : string
778+ ) : Promise < never > {
779+ if (
780+ error . response &&
781+ error . config ?. responseType === 'stream' &&
782+ ( error . response . data as any ) . on
783+ ) {
784+ try {
785+ const body = await this . streamToString ( error . response . data ) ;
786+ try {
787+ error . response . data = JSON . parse ( body ) ;
788+ } catch ( e ) {
789+ error . response . data = {
790+ message : body || 'Unknown error' ,
791+ } ;
792+ }
793+ } catch ( e ) {
794+ // If stream reading fails, we must replace the stream object with a safe error object
795+ // to prevent circular structure errors during JSON serialization.
796+ error . response . data = {
797+ message : 'Failed to read error response stream' ,
798+ details : String ( e ) ,
799+ } ;
800+ }
801+ }
802+ this . httpErrorHandler ( error , message ) ;
803+ }
804+
805+ /**
806+ * Helper to convert a stream to string.
807+ */
808+ private streamToString ( stream : any ) : Promise < string > {
809+ return new Promise ( ( resolve , reject ) => {
810+ let buffer = '' ;
811+ stream . on ( 'data' , ( chunk : Buffer ) => {
812+ buffer += chunk . toString ( ) ;
813+ } ) ;
814+ stream . on ( 'end' , ( ) => {
815+ resolve ( buffer ) ;
816+ } ) ;
817+ stream . on ( 'error' , ( err : Error ) => {
818+ reject ( err ) ;
819+ } ) ;
820+ } ) ;
821+ }
822+
756823 /**
757824 * Performs health checks on all runtimes.
758825 */
0 commit comments