mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 21:03:05 -07:00
Fixes
This commit is contained in:
@@ -410,6 +410,57 @@ interface CalculatedEdit {
|
|||||||
matchRanges?: Array<{ start: number; end: number }>;
|
matchRanges?: Array<{ start: number; end: number }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDiffContextSnippet(
|
||||||
|
originalContent: string,
|
||||||
|
newContent: string,
|
||||||
|
contextLines = 5,
|
||||||
|
): string {
|
||||||
|
if (!originalContent) return newContent;
|
||||||
|
const changes = Diff.diffLines(originalContent, newContent);
|
||||||
|
const newLines = newContent.split(/\r?\n/);
|
||||||
|
const ranges: Array<{ start: number; end: number }> = [];
|
||||||
|
let newLineIdx = 0;
|
||||||
|
for (const change of changes) {
|
||||||
|
if (change.added) {
|
||||||
|
ranges.push({ start: newLineIdx, end: newLineIdx + (change.count ?? 0) });
|
||||||
|
newLineIdx += change.count ?? 0;
|
||||||
|
} else if (change.removed) {
|
||||||
|
ranges.push({ start: newLineIdx, end: newLineIdx });
|
||||||
|
} else {
|
||||||
|
newLineIdx += change.count ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ranges.length === 0) return newContent;
|
||||||
|
const expandedRanges = ranges.map((r) => ({
|
||||||
|
start: Math.max(0, r.start - contextLines),
|
||||||
|
end: Math.min(newLines.length, r.end + contextLines),
|
||||||
|
}));
|
||||||
|
expandedRanges.sort((a, b) => a.start - b.start);
|
||||||
|
const mergedRanges: Array<{ start: number; end: number }> = [];
|
||||||
|
if (expandedRanges.length > 0) {
|
||||||
|
let current = expandedRanges[0];
|
||||||
|
for (let i = 1; i < expandedRanges.length; i++) {
|
||||||
|
const next = expandedRanges[i];
|
||||||
|
if (next.start <= current.end) {
|
||||||
|
current.end = Math.max(current.end, next.end);
|
||||||
|
} else {
|
||||||
|
mergedRanges.push(current);
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mergedRanges.push(current);
|
||||||
|
}
|
||||||
|
const outputParts: string[] = [];
|
||||||
|
let lastEnd = 0;
|
||||||
|
for (const range of mergedRanges) {
|
||||||
|
if (range.start > lastEnd) outputParts.push('...');
|
||||||
|
outputParts.push(newLines.slice(range.start, range.end).join('\n'));
|
||||||
|
lastEnd = range.end;
|
||||||
|
}
|
||||||
|
if (lastEnd < newLines.length) outputParts.push('...');
|
||||||
|
return outputParts.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
class EditToolInvocation
|
class EditToolInvocation
|
||||||
extends BaseToolInvocation<EditToolParams, ToolResult>
|
extends BaseToolInvocation<EditToolParams, ToolResult>
|
||||||
implements ToolInvocation<EditToolParams, ToolResult>
|
implements ToolInvocation<EditToolParams, ToolResult>
|
||||||
@@ -876,6 +927,13 @@ class EditToolInvocation
|
|||||||
? `Created new file: ${this.params.file_path} with provided content.`
|
? `Created new file: ${this.params.file_path} with provided content.`
|
||||||
: `Successfully modified file: ${this.params.file_path} (${editData.occurrences} replacements).`,
|
: `Successfully modified file: ${this.params.file_path} (${editData.occurrences} replacements).`,
|
||||||
];
|
];
|
||||||
|
const snippet = getDiffContextSnippet(
|
||||||
|
originalContent,
|
||||||
|
finalContent,
|
||||||
|
5,
|
||||||
|
);
|
||||||
|
llmSuccessMessageParts.push(`Here is the updated code:
|
||||||
|
${snippet}`);
|
||||||
const fuzzyFeedback = getFuzzyMatchFeedback(editData);
|
const fuzzyFeedback = getFuzzyMatchFeedback(editData);
|
||||||
if (fuzzyFeedback) {
|
if (fuzzyFeedback) {
|
||||||
llmSuccessMessageParts.push(fuzzyFeedback);
|
llmSuccessMessageParts.push(fuzzyFeedback);
|
||||||
|
|||||||
@@ -160,6 +160,8 @@ ${result.llmContent}`;
|
|||||||
/**
|
/**
|
||||||
* Implementation of the ReadFile tool logic
|
* Implementation of the ReadFile tool logic
|
||||||
*/
|
*/
|
||||||
|
const readCache = new Map<string, ToolResult>();
|
||||||
|
|
||||||
export class ReadFileTool extends BaseDeclarativeTool<
|
export class ReadFileTool extends BaseDeclarativeTool<
|
||||||
ReadFileToolParams,
|
ReadFileToolParams,
|
||||||
ToolResult
|
ToolResult
|
||||||
@@ -185,6 +187,12 @@ export class ReadFileTool extends BaseDeclarativeTool<
|
|||||||
config.getTargetDir(),
|
config.getTargetDir(),
|
||||||
config.getFileFilteringOptions(),
|
config.getFileFilteringOptions(),
|
||||||
);
|
);
|
||||||
|
// Clear cache on any potential file modification
|
||||||
|
messageBus.on('tool_call_confirm', (details) => {
|
||||||
|
if (details.type === 'edit' || details.title.toLowerCase().includes('write')) {
|
||||||
|
readCache.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override validateToolParamValues(
|
protected override validateToolParamValues(
|
||||||
@@ -233,13 +241,34 @@ export class ReadFileTool extends BaseDeclarativeTool<
|
|||||||
_toolName?: string,
|
_toolName?: string,
|
||||||
_toolDisplayName?: string,
|
_toolDisplayName?: string,
|
||||||
): ToolInvocation<ReadFileToolParams, ToolResult> {
|
): ToolInvocation<ReadFileToolParams, ToolResult> {
|
||||||
return new ReadFileToolInvocation(
|
const key = JSON.stringify({
|
||||||
this.config,
|
path: path.resolve(this.config.getTargetDir(), params.file_path),
|
||||||
|
offset: params.offset,
|
||||||
|
limit: params.limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
toolLocations: () => [{ path: path.resolve(this.config.getTargetDir(), params.file_path), line: params.offset }],
|
||||||
|
getDescription: () => shortenPath(makeRelative(path.resolve(this.config.getTargetDir(), params.file_path), this.config.getTargetDir())),
|
||||||
params,
|
params,
|
||||||
messageBus,
|
execute: async () => {
|
||||||
_toolName,
|
if (readCache.has(key)) {
|
||||||
_toolDisplayName,
|
return readCache.get(key)!;
|
||||||
);
|
}
|
||||||
|
const invocation = new ReadFileToolInvocation(
|
||||||
|
this.config,
|
||||||
|
params,
|
||||||
|
messageBus,
|
||||||
|
_toolName,
|
||||||
|
_toolDisplayName,
|
||||||
|
);
|
||||||
|
const result = await invocation.execute();
|
||||||
|
if (!result.error) {
|
||||||
|
readCache.set(key, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
} as unknown as ToolInvocation<ReadFileToolParams, ToolResult>;
|
||||||
}
|
}
|
||||||
|
|
||||||
override getSchema(modelId?: string) {
|
override getSchema(modelId?: string) {
|
||||||
|
|||||||
@@ -160,6 +160,8 @@ export interface RipGrepToolParams {
|
|||||||
*/
|
*/
|
||||||
interface GrepMatch {
|
interface GrepMatch {
|
||||||
filePath: string;
|
filePath: string;
|
||||||
|
absolutePath: string;
|
||||||
|
absolutePath: string;
|
||||||
lineNumber: number;
|
lineNumber: number;
|
||||||
line: string;
|
line: string;
|
||||||
isContext?: boolean;
|
isContext?: boolean;
|
||||||
@@ -310,6 +312,66 @@ class GrepToolInvocation extends BaseToolInvocation<
|
|||||||
const matchCount = matchesOnly.length;
|
const matchCount = matchesOnly.length;
|
||||||
const matchTerm = matchCount === 1 ? 'match' : 'matches';
|
const matchTerm = matchCount === 1 ? 'match' : 'matches';
|
||||||
|
|
||||||
|
// Greedy Grep: If match count is low and no context was requested, automatically return context.
|
||||||
|
if (
|
||||||
|
matchCount <= 3 &&
|
||||||
|
matchCount > 0 &&
|
||||||
|
!this.params.names_only &&
|
||||||
|
!this.params.context &&
|
||||||
|
!this.params.before &&
|
||||||
|
!this.params.after
|
||||||
|
) {
|
||||||
|
for (const filePath in matchesByFile) {
|
||||||
|
const fileMatches = matchesByFile[filePath];
|
||||||
|
let fileLines: string[] | null = null;
|
||||||
|
try {
|
||||||
|
const content = await fsPromises.readFile(
|
||||||
|
fileMatches[0].absolutePath,
|
||||||
|
'utf8',
|
||||||
|
);
|
||||||
|
fileLines = content.split(/
?
|
||||||
|
/);
|
||||||
|
} catch (err) {
|
||||||
|
debugLogger.warn(
|
||||||
|
\`Failed to read file for context: \${fileMatches[0].absolutePath}\`,
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileLines) {
|
||||||
|
const newFileMatches: GrepMatch[] = [];
|
||||||
|
const seenLines = new Set<number>();
|
||||||
|
for (const match of fileMatches) {
|
||||||
|
const startLine = Math.max(0, match.lineNumber - 1 - 50);
|
||||||
|
const endLine = Math.min(
|
||||||
|
fileLines.length,
|
||||||
|
match.lineNumber - 1 + 50 + 1,
|
||||||
|
);
|
||||||
|
for (let i = startLine; i < endLine; i++) {
|
||||||
|
if (!seenLines.has(i + 1)) {
|
||||||
|
newFileMatches.push({
|
||||||
|
absolutePath: match.absolutePath,
|
||||||
|
filePath: match.filePath,
|
||||||
|
lineNumber: i + 1,
|
||||||
|
line: fileLines[i],
|
||||||
|
isContext: i + 1 !== match.lineNumber,
|
||||||
|
});
|
||||||
|
seenLines.add(i + 1);
|
||||||
|
} else if (i + 1 === match.lineNumber) {
|
||||||
|
const index = newFileMatches.findIndex(m => m.lineNumber === i + 1);
|
||||||
|
if (index !== -1) {
|
||||||
|
newFileMatches[index].isContext = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matchesByFile[filePath] = newFileMatches.sort(
|
||||||
|
(a, b) => a.lineNumber - b.lineNumber,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const wasTruncated = matchCount >= totalMaxMatches;
|
const wasTruncated = matchCount >= totalMaxMatches;
|
||||||
|
|
||||||
if (this.params.names_only) {
|
if (this.params.names_only) {
|
||||||
@@ -500,6 +562,7 @@ class GrepToolInvocation extends BaseToolInvocation<
|
|||||||
const relativeFilePath = path.relative(basePath, absoluteFilePath);
|
const relativeFilePath = path.relative(basePath, absoluteFilePath);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
absolutePath: absoluteFilePath,
|
||||||
filePath: relativeFilePath || path.basename(absoluteFilePath),
|
filePath: relativeFilePath || path.basename(absoluteFilePath),
|
||||||
lineNumber: data.line_number,
|
lineNumber: data.line_number,
|
||||||
line: data.lines.text.trimEnd(),
|
line: data.lines.text.trimEnd(),
|
||||||
|
|||||||
Reference in New Issue
Block a user