@@ -70,6 +70,27 @@ export interface ReadManyFilesParams {
7070 } ;
7171}
7272
73+ /**
74+ * Result type for file processing operations
75+ */
76+ type FileProcessingResult =
77+ | {
78+ success : true ;
79+ filePath : string ;
80+ relativePathForDisplay : string ;
81+ fileReadResult : NonNullable <
82+ Awaited < ReturnType < typeof processSingleFileContent > >
83+ > ;
84+ reason ?: undefined ;
85+ }
86+ | {
87+ success : false ;
88+ filePath : string ;
89+ relativePathForDisplay : string ;
90+ fileReadResult ?: undefined ;
91+ reason : string ;
92+ } ;
93+
7394/**
7495 * Default exclusion patterns for commonly ignored directories and binary file types.
7596 * These are compatible with glob ignore patterns.
@@ -413,66 +434,124 @@ Use this tool when the user's query implies needing the content of several files
413434
414435 const sortedFiles = Array . from ( filesToConsider ) . sort ( ) ;
415436
416- for ( const filePath of sortedFiles ) {
417- const relativePathForDisplay = path
418- . relative ( this . config . getTargetDir ( ) , filePath )
419- . replace ( / \\ / g, '/' ) ;
437+ const fileProcessingPromises = sortedFiles . map (
438+ async ( filePath ) : Promise < FileProcessingResult > => {
439+ try {
440+ const relativePathForDisplay = path
441+ . relative ( this . config . getTargetDir ( ) , filePath )
442+ . replace ( / \\ / g, '/' ) ;
443+
444+ const fileType = await detectFileType ( filePath ) ;
445+
446+ if ( fileType === 'image' || fileType === 'pdf' ) {
447+ const fileExtension = path . extname ( filePath ) . toLowerCase ( ) ;
448+ const fileNameWithoutExtension = path . basename (
449+ filePath ,
450+ fileExtension ,
451+ ) ;
452+ const requestedExplicitly = inputPatterns . some (
453+ ( pattern : string ) =>
454+ pattern . toLowerCase ( ) . includes ( fileExtension ) ||
455+ pattern . includes ( fileNameWithoutExtension ) ,
456+ ) ;
457+
458+ if ( ! requestedExplicitly ) {
459+ return {
460+ success : false ,
461+ filePath,
462+ relativePathForDisplay,
463+ reason :
464+ 'asset file (image/pdf) was not explicitly requested by name or extension' ,
465+ } ;
466+ }
467+ }
468+
469+ // Use processSingleFileContent for all file types now
470+ const fileReadResult = await processSingleFileContent (
471+ filePath ,
472+ this . config . getTargetDir ( ) ,
473+ ) ;
474+
475+ if ( fileReadResult . error ) {
476+ return {
477+ success : false ,
478+ filePath,
479+ relativePathForDisplay,
480+ reason : `Read error: ${ fileReadResult . error } ` ,
481+ } ;
482+ }
483+
484+ return {
485+ success : true ,
486+ filePath,
487+ relativePathForDisplay,
488+ fileReadResult,
489+ } ;
490+ } catch ( error ) {
491+ const relativePathForDisplay = path
492+ . relative ( this . config . getTargetDir ( ) , filePath )
493+ . replace ( / \\ / g, '/' ) ;
494+
495+ return {
496+ success : false ,
497+ filePath,
498+ relativePathForDisplay,
499+ reason : `Unexpected error: ${ error instanceof Error ? error . message : String ( error ) } ` ,
500+ } ;
501+ }
502+ } ,
503+ ) ;
420504
421- const fileType = await detectFileType ( filePath ) ;
505+ const results = await Promise . allSettled ( fileProcessingPromises ) ;
422506
423- if ( fileType === 'image' || fileType === 'pdf' ) {
424- const fileExtension = path . extname ( filePath ) . toLowerCase ( ) ;
425- const fileNameWithoutExtension = path . basename ( filePath , fileExtension ) ;
426- const requestedExplicitly = inputPatterns . some (
427- ( pattern : string ) =>
428- pattern . toLowerCase ( ) . includes ( fileExtension ) ||
429- pattern . includes ( fileNameWithoutExtension ) ,
430- ) ;
507+ for ( const result of results ) {
508+ if ( result . status === 'fulfilled' ) {
509+ const fileResult = result . value ;
431510
432- if ( ! requestedExplicitly ) {
511+ if ( ! fileResult . success ) {
512+ // Handle skipped files (images/PDFs not requested or read errors)
433513 skippedFiles . push ( {
434- path : relativePathForDisplay ,
435- reason :
436- 'asset file (image/pdf) was not explicitly requested by name or extension' ,
514+ path : fileResult . relativePathForDisplay ,
515+ reason : fileResult . reason ,
437516 } ) ;
438- continue ;
517+ } else {
518+ // Handle successfully processed files
519+ const { filePath, relativePathForDisplay, fileReadResult } =
520+ fileResult ;
521+
522+ if ( typeof fileReadResult . llmContent === 'string' ) {
523+ const separator = DEFAULT_OUTPUT_SEPARATOR_FORMAT . replace (
524+ '{filePath}' ,
525+ filePath ,
526+ ) ;
527+ contentParts . push (
528+ `${ separator } \n\n${ fileReadResult . llmContent } \n\n` ,
529+ ) ;
530+ } else {
531+ contentParts . push ( fileReadResult . llmContent ) ; // This is a Part for image/pdf
532+ }
533+
534+ processedFilesRelativePaths . push ( relativePathForDisplay ) ;
535+
536+ const lines =
537+ typeof fileReadResult . llmContent === 'string'
538+ ? fileReadResult . llmContent . split ( '\n' ) . length
539+ : undefined ;
540+ const mimetype = getSpecificMimeType ( filePath ) ;
541+ recordFileOperationMetric (
542+ this . config ,
543+ FileOperation . READ ,
544+ lines ,
545+ mimetype ,
546+ path . extname ( filePath ) ,
547+ ) ;
439548 }
440- }
441-
442- // Use processSingleFileContent for all file types now
443- const fileReadResult = await processSingleFileContent (
444- filePath ,
445- this . config . getTargetDir ( ) ,
446- ) ;
447-
448- if ( fileReadResult . error ) {
549+ } else {
550+ // Handle Promise rejection (unexpected errors)
449551 skippedFiles . push ( {
450- path : relativePathForDisplay ,
451- reason : `Read error: ${ fileReadResult . error } ` ,
552+ path : 'unknown' ,
553+ reason : `Unexpected error: ${ result . reason } ` ,
452554 } ) ;
453- } else {
454- if ( typeof fileReadResult . llmContent === 'string' ) {
455- const separator = DEFAULT_OUTPUT_SEPARATOR_FORMAT . replace (
456- '{filePath}' ,
457- filePath ,
458- ) ;
459- contentParts . push ( `${ separator } \n\n${ fileReadResult . llmContent } \n\n` ) ;
460- } else {
461- contentParts . push ( fileReadResult . llmContent ) ; // This is a Part for image/pdf
462- }
463- processedFilesRelativePaths . push ( relativePathForDisplay ) ;
464- const lines =
465- typeof fileReadResult . llmContent === 'string'
466- ? fileReadResult . llmContent . split ( '\n' ) . length
467- : undefined ;
468- const mimetype = getSpecificMimeType ( filePath ) ;
469- recordFileOperationMetric (
470- this . config ,
471- FileOperation . READ ,
472- lines ,
473- mimetype ,
474- path . extname ( filePath ) ,
475- ) ;
476555 }
477556 }
478557
0 commit comments