Fix the generation of globs by using the filesystem instead of a heuristic. (#227)
This commit is contained in:
parent
9f20c5f95e
commit
3ec00d1689
|
@ -4,8 +4,10 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import * as path from 'path';
|
||||||
import { PartListUnion } from '@google/genai';
|
import { PartListUnion } from '@google/genai';
|
||||||
import { Config, getErrorMessage } from '@gemini-code/server';
|
import { Config, getErrorMessage, isNodeError } from '@gemini-code/server';
|
||||||
import {
|
import {
|
||||||
HistoryItem,
|
HistoryItem,
|
||||||
IndividualToolCallDisplay,
|
IndividualToolCallDisplay,
|
||||||
|
@ -39,9 +41,10 @@ interface HandleAtCommandResult {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes user input potentially containing an '@<path>' command.
|
* Processes user input potentially containing an '@<path>' command.
|
||||||
* It finds the first '@<path>', reads the specified path, updates the UI,
|
* It finds the first '@<path>', checks if the path is a file or directory,
|
||||||
* and prepares the query for the LLM, incorporating the file content
|
* prepares the appropriate path specification for the read_many_files tool,
|
||||||
* and surrounding text.
|
* updates the UI, and prepares the query for the LLM, incorporating the
|
||||||
|
* file content and surrounding text.
|
||||||
*
|
*
|
||||||
* @returns An object containing the potentially modified query (or null)
|
* @returns An object containing the potentially modified query (or null)
|
||||||
* and a flag indicating if the main hook should proceed.
|
* and a flag indicating if the main hook should proceed.
|
||||||
|
@ -60,8 +63,6 @@ export async function handleAtCommand({
|
||||||
const match = trimmedQuery.match(atCommandRegex);
|
const match = trimmedQuery.match(atCommandRegex);
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
// This should technically not happen if isPotentiallyAtCommand was true,
|
|
||||||
// but handle defensively.
|
|
||||||
const errorTimestamp = getNextMessageId(userMessageTimestamp);
|
const errorTimestamp = getNextMessageId(userMessageTimestamp);
|
||||||
addHistoryItem(
|
addHistoryItem(
|
||||||
setHistory,
|
setHistory,
|
||||||
|
@ -108,19 +109,41 @@ export async function handleAtCommand({
|
||||||
|
|
||||||
// --- Path Handling for @ command ---
|
// --- Path Handling for @ command ---
|
||||||
let pathSpec = pathPart;
|
let pathSpec = pathPart;
|
||||||
// Basic check: If no extension or ends with '/', assume directory and add globstar.
|
const contentLabel = pathPart;
|
||||||
if (!pathPart.includes('.') || pathPart.endsWith('/')) {
|
|
||||||
|
try {
|
||||||
|
// Resolve the path relative to the target directory
|
||||||
|
const absolutePath = path.resolve(config.getTargetDir(), pathPart);
|
||||||
|
const stats = await fs.stat(absolutePath);
|
||||||
|
|
||||||
|
if (stats.isDirectory()) {
|
||||||
|
// If it's a directory, ensure it ends with a globstar for recursive read
|
||||||
pathSpec = pathPart.endsWith('/') ? `${pathPart}**` : `${pathPart}/**`;
|
pathSpec = pathPart.endsWith('/') ? `${pathPart}**` : `${pathPart}/**`;
|
||||||
|
setDebugMessage(`Path resolved to directory, using glob: ${pathSpec}`);
|
||||||
|
} else {
|
||||||
|
// It's a file, use the original pathPart as pathSpec
|
||||||
|
setDebugMessage(`Path resolved to file: ${pathSpec}`);
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// If stat fails (e.g., file/dir not found), proceed with the original pathPart.
|
||||||
|
// The read_many_files tool will handle the error if it's invalid.
|
||||||
|
if (isNodeError(error) && error.code === 'ENOENT') {
|
||||||
|
setDebugMessage(`Path not found, proceeding with original: ${pathSpec}`);
|
||||||
|
} else {
|
||||||
|
// Log other stat errors but still proceed
|
||||||
|
console.error(`Error stating path ${pathPart}:`, error);
|
||||||
|
setDebugMessage(
|
||||||
|
`Error stating path, proceeding with original: ${pathSpec}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const toolArgs = { paths: [pathSpec] };
|
const toolArgs = { paths: [pathSpec] };
|
||||||
const contentLabel =
|
|
||||||
pathSpec === pathPart ? pathPart : `directory ${pathPart}`;
|
|
||||||
// --- End Path Handling ---
|
// --- End Path Handling ---
|
||||||
|
|
||||||
let toolCallDisplay: IndividualToolCallDisplay;
|
let toolCallDisplay: IndividualToolCallDisplay;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setDebugMessage(`Reading via @ command: ${pathSpec}`);
|
|
||||||
const result = await readManyFilesTool.execute(toolArgs);
|
const result = await readManyFilesTool.execute(toolArgs);
|
||||||
const fileContent = result.llmContent || '';
|
const fileContent = result.llmContent || '';
|
||||||
|
|
||||||
|
@ -133,7 +156,6 @@ export async function handleAtCommand({
|
||||||
confirmationDetails: undefined,
|
confirmationDetails: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Construct the query for Gemini, combining parts
|
|
||||||
const processedQueryParts = [];
|
const processedQueryParts = [];
|
||||||
if (textBefore) {
|
if (textBefore) {
|
||||||
processedQueryParts.push({ text: textBefore });
|
processedQueryParts.push({ text: textBefore });
|
||||||
|
@ -159,7 +181,6 @@ export async function handleAtCommand({
|
||||||
|
|
||||||
return { processedQuery, shouldProceed: true };
|
return { processedQuery, shouldProceed: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Construct error UI
|
|
||||||
toolCallDisplay = {
|
toolCallDisplay = {
|
||||||
callId: `client-read-${userMessageTimestamp}`,
|
callId: `client-read-${userMessageTimestamp}`,
|
||||||
name: readManyFilesTool.displayName,
|
name: readManyFilesTool.displayName,
|
||||||
|
|
Loading…
Reference in New Issue