-
Notifications
You must be signed in to change notification settings - Fork 14.4k
Fix EISDIR warnings and Max Stack Size errors for issue #21527 #25444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
657e630
d3481c9
ea51c67
96a80eb
382e95b
f22a6de
a66b75e
d798185
8ca46e3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,6 +70,7 @@ interface HandleAtCommandParams { | |
| messageId: number; | ||
| signal: AbortSignal; | ||
| escapePastedAtSymbols?: boolean; | ||
| depth?: number; | ||
| } | ||
|
|
||
| interface HandleAtCommandResult { | ||
|
|
@@ -504,6 +505,9 @@ async function readLocalFiles( | |
| config: Config, | ||
| signal: AbortSignal, | ||
| userMessageTimestamp: number, | ||
| depth: number, | ||
| addItem: UseHistoryManagerReturn['addItem'], | ||
| onDebugMessage: (message: string) => void, | ||
| ): Promise<{ | ||
| parts: PartUnion[]; | ||
| display?: IndividualToolCallDisplay; | ||
|
|
@@ -580,7 +584,25 @@ async function readLocalFiles( | |
| parts.push({ | ||
| text: `\nContent from @${displayPath}:\n`, | ||
| }); | ||
| parts.push({ text: fileActualContent }); | ||
|
|
||
| if (depth < 2 && typeof fileActualContent === 'string' && fileActualContent.includes('@')) { | ||
| const nestedResult = await handleAtCommand({ | ||
| query: fileActualContent, | ||
| config, | ||
| addItem: () => 0, // Mock addItem to prevent history pollution. | ||
| onDebugMessage: () => {}, | ||
| messageId: userMessageTimestamp, | ||
| signal, | ||
| depth: depth + 1, | ||
| }); | ||
| if (nestedResult.processedQuery) { | ||
| parts.push(...nestedResult.processedQuery); | ||
| } else { | ||
| parts.push({ text: fileActualContent }); | ||
| } | ||
| } else { | ||
| parts.push({ text: fileActualContent }); | ||
| } | ||
| } else { | ||
| parts.push({ text: part }); | ||
| } | ||
|
|
@@ -667,7 +689,12 @@ export async function handleAtCommand({ | |
| messageId: userMessageTimestamp, | ||
| signal, | ||
| escapePastedAtSymbols = false, | ||
| depth = 0, | ||
| }: HandleAtCommandParams): Promise<HandleAtCommandResult> { | ||
| if (depth > 2) { | ||
| return { processedQuery: [{ text: query }] }; | ||
| } | ||
|
Comment on lines
+701
to
+703
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The References
|
||
|
|
||
| const commandParts = parseAllAtCommands(query, escapePastedAtSymbols); | ||
|
|
||
| const { agentParts, resourceParts, fileParts } = categorizeAtCommands( | ||
|
|
@@ -710,7 +737,7 @@ export async function handleAtCommand({ | |
|
|
||
| const [mcpResult, fileResult] = await Promise.all([ | ||
| readMcpResources(resourceParts, config, signal), | ||
| readLocalFiles(resolvedFiles, config, signal, userMessageTimestamp), | ||
| readLocalFiles(resolvedFiles, config, signal, userMessageTimestamp, depth, addItem, onDebugMessage), | ||
| ]); | ||
|
|
||
| const hasContent = mcpResult.parts.length > 0 || fileResult.parts.length > 0; | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -8,7 +8,6 @@ import fs from 'node:fs'; | |||||
| import fsPromises from 'node:fs/promises'; | ||||||
| import path from 'node:path'; | ||||||
| import type { PartUnion } from '@google/genai'; | ||||||
| import { isBinaryFile as isBinaryFileCheck } from 'isbinaryfile'; | ||||||
| import mime from 'mime/lite'; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
|
||||||
| import type { FileSystemService } from '../services/fileSystemService.js'; | ||||||
| import { ToolErrorType } from '../tools/tool-error.js'; | ||||||
|
|
@@ -346,11 +345,74 @@ export async function isEmpty(filePath: string): Promise<boolean> { | |||||
|
|
||||||
| /** | ||||||
| * Heuristic: determine if a file is likely binary. | ||||||
| * Delegates to the `isbinaryfile` package for UTF-8-aware detection. | ||||||
| */ | ||||||
| export async function isBinaryFile(filePath: string): Promise<boolean> { | ||||||
| try { | ||||||
| return await isBinaryFileCheck(filePath); | ||||||
| let fh; | ||||||
| try { | ||||||
| fh = await fs.promises.open(filePath, 'r'); | ||||||
| const stats = await fh.stat(); | ||||||
| if (stats.isDirectory()) return false; | ||||||
| const fileSize = stats.size; | ||||||
| if (fileSize === 0) return false; // empty is not binary | ||||||
|
|
||||||
| // Sample up to 4KB from the head | ||||||
| const sampleSize = Math.min(4096, fileSize); | ||||||
| const buf = Buffer.alloc(sampleSize); | ||||||
| const { bytesRead } = await fh.read(buf, 0, sampleSize, 0); | ||||||
| if (bytesRead === 0) return false; | ||||||
|
|
||||||
| // BOM → text (avoid false positives for UTF‑16/32 with nulls) | ||||||
| const bom = detectBOM(buf.subarray(0, Math.min(4, bytesRead))); | ||||||
| if (bom) return false; | ||||||
|
|
||||||
| let nonPrintableCount = 0; | ||||||
| let i = 0; | ||||||
| while (i < bytesRead) { | ||||||
| const byte = buf[i]; | ||||||
| if (byte === 0) return true; // null byte → strong binary signal | ||||||
| if (byte < 9 || (byte > 13 && byte < 32)) { | ||||||
| nonPrintableCount++; | ||||||
| i++; | ||||||
| } else if (byte >= 0x80) { | ||||||
| // Determine expected UTF-8 sequence length from the lead byte | ||||||
| let seqLen = 0; | ||||||
| if (byte >= 0xf0 && byte <= 0xf7) seqLen = 4; | ||||||
| else if (byte >= 0xe0 && byte <= 0xef) seqLen = 3; | ||||||
| else if (byte >= 0xc2 && byte <= 0xdf) seqLen = 2; | ||||||
| // 0x80–0xBF are continuation bytes without a leading byte → invalid | ||||||
| // 0xC0–0xC1 are overlong encodings → invalid | ||||||
| // 0xF8–0xFF are invalid UTF-8 | ||||||
|
|
||||||
| if (seqLen > 0 && i + seqLen <= bytesRead) { | ||||||
| // Verify continuation bytes (0x80–0xBF) | ||||||
| let valid = true; | ||||||
| for (let j = 1; j < seqLen; j++) { | ||||||
| if ((buf[i + j] & 0xc0) !== 0x80) { | ||||||
| valid = false; | ||||||
| break; | ||||||
| } | ||||||
| } | ||||||
| if (valid) { | ||||||
| i += seqLen; // skip valid multi-byte sequence | ||||||
| } else { | ||||||
| nonPrintableCount++; | ||||||
| i++; | ||||||
| } | ||||||
| } else { | ||||||
| // lone continuation byte, partial sequence, or invalid lead byte | ||||||
| nonPrintableCount++; | ||||||
| i++; | ||||||
| } | ||||||
| } else { | ||||||
| i++; | ||||||
| } | ||||||
| } | ||||||
| // If >30% non-printable characters, consider it binary | ||||||
| return nonPrintableCount / bytesRead > 0.3; | ||||||
| } finally { | ||||||
| if (fh) await fh.close(); | ||||||
| } | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Re-implementing the binary detection heuristic adds significant complexity and risk of regressions. A more maintainable approach is to use the established const stats = await fsPromises.stat(filePath);
if (stats.isDirectory()) return false;
return await isBinaryFileCheck(filePath);References
|
||||||
| } catch (error) { | ||||||
| debugLogger.warn( | ||||||
| `Failed to check if file is binary: ${filePath}`, | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.