@@ -432,49 +432,163 @@ export class SubprocessCLITransport {
432432 } ) ;
433433 }
434434
435- const rl = createInterface ( {
436- input : this . process . stdout ,
437- crlfDelay : Infinity
438- } ) ;
439-
440435 try {
441- // Process stream-json format - each line is a JSON object
442- for await ( const line of rl ) {
443- const trimmedLine = line . trim ( ) ;
444- if ( ! trimmedLine ) continue ;
436+ // Handle large JSON responses that may exceed readline buffer limits
437+ // by manually parsing JSON from raw data instead of relying on line-by-line reading
438+ let buffer = '' ;
439+ const messages : CLIOutput [ ] = [ ] ;
440+ let processComplete = false ;
441+ let parseError : Error | null = null ;
442+
443+ // Set up data handler for manual JSON parsing
444+ const onData = ( chunk : Buffer ) => {
445+ buffer += chunk . toString ( ) ;
446+
447+ // Try to parse complete JSON objects from the buffer
448+ let startIndex = 0 ;
449+ while ( startIndex < buffer . length ) {
450+ const openBrace = buffer . indexOf ( '{' , startIndex ) ;
451+ const openBracket = buffer . indexOf ( '[' , startIndex ) ;
452+
453+ // Find the first JSON object/array start
454+ let jsonStart = - 1 ;
455+ if ( openBrace !== - 1 && openBracket !== - 1 ) {
456+ jsonStart = Math . min ( openBrace , openBracket ) ;
457+ } else if ( openBrace !== - 1 ) {
458+ jsonStart = openBrace ;
459+ } else if ( openBracket !== - 1 ) {
460+ jsonStart = openBracket ;
461+ }
445462
446- this . debugLog ( 'DEBUG stdout:' , trimmedLine ) ;
463+ if ( jsonStart === - 1 ) break ;
464+
465+ // Try to parse JSON starting from this position
466+ let depth = 0 ;
467+ let inString = false ;
468+ let escaped = false ;
469+ let jsonEnd = - 1 ;
470+
471+ for ( let i = jsonStart ; i < buffer . length ; i ++ ) {
472+ const char = buffer [ i ] ;
473+
474+ if ( escaped ) {
475+ escaped = false ;
476+ continue ;
477+ }
478+
479+ if ( char === '\\' ) {
480+ escaped = true ;
481+ continue ;
482+ }
483+
484+ if ( char === '"' ) {
485+ inString = ! inString ;
486+ continue ;
487+ }
488+
489+ if ( ! inString ) {
490+ if ( char === '{' || char === '[' ) {
491+ depth ++ ;
492+ } else if ( char === '}' || char === ']' ) {
493+ depth -- ;
494+ if ( depth === 0 ) {
495+ jsonEnd = i + 1 ;
496+ break ;
497+ }
498+ }
499+ }
500+ }
501+
502+ if ( jsonEnd !== - 1 ) {
503+ // Found complete JSON object
504+ const jsonStr = buffer . substring ( jsonStart , jsonEnd ) ;
447505
448- try {
449- const parsed = JSON . parse ( trimmedLine ) as CLIOutput ;
450-
451- // For non-keepAlive mode, close stdin when we receive a result message
452- // This allows the CLI process to exit gracefully after completing the response
453- if (
454- ! this . keepAlive &&
455- ( parsed as any ) . type === 'result' &&
456- this . process ?. stdin &&
457- ! this . process . stdin . destroyed
458- ) {
459506 this . debugLog (
460- 'DEBUG: [Transport] Received result message, closing stdin for non-keepAlive mode'
507+ 'DEBUG stdout:' ,
508+ jsonStr . substring ( 0 , 200 ) + ( jsonStr . length > 200 ? '...' : '' )
461509 ) ;
462- this . process . stdin . end ( ) ;
510+
511+ try {
512+ const parsed = JSON . parse ( jsonStr ) as CLIOutput ;
513+
514+ // For non-keepAlive mode, close stdin when we receive a result message
515+ if (
516+ ! this . keepAlive &&
517+ ( parsed as any ) . type === 'result' &&
518+ this . process ?. stdin &&
519+ ! this . process . stdin . destroyed
520+ ) {
521+ this . debugLog (
522+ 'DEBUG: [Transport] Received result message, closing stdin for non-keepAlive mode'
523+ ) ;
524+ this . process . stdin . end ( ) ;
525+ }
526+
527+ messages . push ( parsed ) ;
528+ } catch ( error ) {
529+ // If JSON parsing fails but it looks like JSON, capture error
530+ if (
531+ jsonStr . trim ( ) . startsWith ( '{' ) ||
532+ jsonStr . trim ( ) . startsWith ( '[' )
533+ ) {
534+ parseError = new CLIJSONDecodeError (
535+ `Failed to parse CLI output: ${ error } ` ,
536+ jsonStr
537+ ) ;
538+ return ; // Stop processing more data
539+ }
540+ this . debugLog (
541+ 'DEBUG: Skipping non-JSON data:' ,
542+ jsonStr . substring ( 0 , 100 )
543+ ) ;
544+ }
545+
546+ // Remove processed JSON from buffer
547+ buffer = buffer . substring ( jsonEnd ) ;
548+ startIndex = 0 ;
549+ } else {
550+ // No complete JSON found, wait for more data
551+ break ;
463552 }
553+ }
554+ } ;
464555
465- yield parsed ;
466- } catch ( error ) {
467- // Skip non-JSON lines (like Python SDK does)
468- if ( trimmedLine . startsWith ( '{' ) || trimmedLine . startsWith ( '[' ) ) {
469- throw new CLIJSONDecodeError (
470- `Failed to parse CLI output: ${ error } ` ,
471- trimmedLine
472- ) ;
556+ const onEnd = ( ) => {
557+ processComplete = true ;
558+ } ;
559+
560+ this . process . stdout . on ( 'data' , onData ) ;
561+ this . process . stdout . on ( 'end' , onEnd ) ;
562+
563+ // Yield messages as they become available
564+ let messageIndex = 0 ;
565+ while ( ! processComplete || messageIndex < messages . length ) {
566+ // Check for parse errors first
567+ if ( parseError ) {
568+ throw parseError ;
569+ }
570+
571+ if ( messageIndex < messages . length ) {
572+ const message = messages [ messageIndex ] ;
573+ if ( message ) {
574+ yield message ;
473575 }
474- continue ;
576+ messageIndex ++ ;
577+ } else {
578+ // Wait a bit for more messages
579+ await new Promise ( ( resolve ) => setTimeout ( resolve , 10 ) ) ;
475580 }
476581 }
477582
583+ // Clean up event listeners
584+ this . process . stdout . removeListener ( 'data' , onData ) ;
585+ this . process . stdout . removeListener ( 'end' , onEnd ) ;
586+
587+ // Final check for parse errors
588+ if ( parseError ) {
589+ throw parseError ;
590+ }
591+
478592 // After all messages are processed, wait for process to exit
479593 try {
480594 await this . process ;
@@ -514,8 +628,6 @@ export class SubprocessCLITransport {
514628 // Ensure cleanup on any error
515629 await this . cleanup ( ) ;
516630 throw error ;
517- } finally {
518- rl . close ( ) ;
519631 }
520632 }
521633
0 commit comments