summaryrefslogtreecommitdiff
path: root/packages/core/src/utils/fileUtils.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/core/src/utils/fileUtils.ts')
-rw-r--r--packages/core/src/utils/fileUtils.ts51
1 files changed, 36 insertions, 15 deletions
diff --git a/packages/core/src/utils/fileUtils.ts b/packages/core/src/utils/fileUtils.ts
index 33eda6ab..1489c4ee 100644
--- a/packages/core/src/utils/fileUtils.ts
+++ b/packages/core/src/utils/fileUtils.ts
@@ -4,8 +4,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import fs from 'fs';
-import path from 'path';
+import fs from 'node:fs';
+import path from 'node:path';
import { PartUnion } from '@google/genai';
import mime from 'mime-types';
@@ -56,22 +56,24 @@ export function isWithinRoot(
/**
* Determines if a file is likely binary based on content sampling.
* @param filePath Path to the file.
- * @returns True if the file appears to be binary.
+ * @returns Promise that resolves to true if the file appears to be binary.
*/
-export function isBinaryFile(filePath: string): boolean {
+export async function isBinaryFile(filePath: string): Promise<boolean> {
+ let fileHandle: fs.promises.FileHandle | undefined;
try {
- const fd = fs.openSync(filePath, 'r');
+ fileHandle = await fs.promises.open(filePath, 'r');
+
// Read up to 4KB or file size, whichever is smaller
- const fileSize = fs.fstatSync(fd).size;
+ const stats = await fileHandle.stat();
+ const fileSize = stats.size;
if (fileSize === 0) {
// Empty file is not considered binary for content checking
- fs.closeSync(fd);
return false;
}
const bufferSize = Math.min(4096, fileSize);
const buffer = Buffer.alloc(bufferSize);
- const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
- fs.closeSync(fd);
+ const result = await fileHandle.read(buffer, 0, buffer.length, 0);
+ const bytesRead = result.bytesRead;
if (bytesRead === 0) return false;
@@ -84,21 +86,40 @@ export function isBinaryFile(filePath: string): boolean {
}
// If >30% non-printable characters, consider it binary
return nonPrintableCount / bytesRead > 0.3;
- } catch {
+ } catch (error) {
+ // Log error for debugging while maintaining existing behavior
+ console.warn(
+ `Failed to check if file is binary: ${filePath}`,
+ error instanceof Error ? error.message : String(error),
+ );
// If any error occurs (e.g. file not found, permissions),
// treat as not binary here; let higher-level functions handle existence/access errors.
return false;
+ } finally {
+ // Safely close the file handle if it was successfully opened
+ if (fileHandle) {
+ try {
+ await fileHandle.close();
+ } catch (closeError) {
+ // Log close errors for debugging while continuing with cleanup
+ console.warn(
+ `Failed to close file handle for: ${filePath}`,
+ closeError instanceof Error ? closeError.message : String(closeError),
+ );
+ // The important thing is that we attempted to clean up
+ }
+ }
}
}
/**
* Detects the type of file based on extension and content.
* @param filePath Path to the file.
- * @returns 'text', 'image', 'pdf', 'audio', 'video', or 'binary'.
+ * @returns Promise that resolves to 'text', 'image', 'pdf', 'audio', 'video', 'binary' or 'svg'.
*/
-export function detectFileType(
+export async function detectFileType(
filePath: string,
-): 'text' | 'image' | 'pdf' | 'audio' | 'video' | 'binary' | 'svg' {
+): Promise<'text' | 'image' | 'pdf' | 'audio' | 'video' | 'binary' | 'svg'> {
const ext = path.extname(filePath).toLowerCase();
// The mimetype for "ts" is MPEG transport stream (a video format) but we want
@@ -166,7 +187,7 @@ export function detectFileType(
// Fallback to content-based check if mime type wasn't conclusive for image/pdf
// and it's not a known binary extension.
- if (isBinaryFile(filePath)) {
+ if (await isBinaryFile(filePath)) {
return 'binary';
}
@@ -227,7 +248,7 @@ export async function processSingleFileContent(
);
}
- const fileType = detectFileType(filePath);
+ const fileType = await detectFileType(filePath);
const relativePathForDisplay = path
.relative(rootDirectory, filePath)
.replace(/\\/g, '/');