Compare commits

...

7 Commits

Author SHA1 Message Date
Christian Gunderman c7956866b9 Only give ranges on fuzzy match. 2026-02-26 13:36:50 -08:00
Christian Gunderman 5d4468355d Revert. 2026-02-26 09:41:54 -08:00
Christian Gunderman bc55c2d4ab Fixes. 2026-02-25 18:06:29 -08:00
Christian Gunderman f03b914b0d Refactor. 2026-02-25 13:49:18 -08:00
Christian Gunderman 8963f060f8 PR cleanup. 2026-02-25 11:38:00 -08:00
Christian Gunderman 16543ed5ed Revise. 2026-02-25 11:20:03 -08:00
Christian Gunderman 2406cdd984 Add simplified start_line, end_line, and file_length metadata to replace tool response 2026-02-25 10:50:21 -08:00
2 changed files with 82 additions and 4 deletions
+64
View File
@@ -373,6 +373,70 @@ describe('EditTool', () => {
expect(result.occurrences).toBe(1);
});
it('should correctly calculate matchRanges for exact multi-line replacements', async () => {
const content = 'line1\nmatch\nline\nline3\nmatch\nline\nline5';
const oldString = 'match\nline';
const result = await calculateReplacement(mockConfig, {
params: {
file_path: 'test.ts',
old_string: oldString,
new_string: 'replacement',
allow_multiple: true,
},
currentContent: content,
abortSignal,
});
expect(result.occurrences).toBe(2);
expect(result.matchRanges).toEqual([
{ start: 2, end: 3 },
{ start: 5, end: 6 },
]);
});
it('should correctly calculate matchRanges for flexible multi-line replacements', async () => {
const content =
' line1\n match\n line\n line3\n match\n line\n line5';
const oldString = 'match\nline';
const result = await calculateReplacement(mockConfig, {
params: {
file_path: 'test.ts',
old_string: oldString,
new_string: 'replacement',
allow_multiple: true,
},
currentContent: content,
abortSignal,
});
expect(result.occurrences).toBe(2);
expect(result.strategy).toBe('flexible');
expect(result.matchRanges).toEqual([
{ start: 2, end: 3 },
{ start: 5, end: 6 },
]);
expect(result.newContent).toBe(
' line1\n replacement\n line3\n replacement\n line5',
);
});
it('should correctly calculate matchRanges for regex replacements', async () => {
const content = ' function foo() {\n return 1;\n }';
const oldString = 'function foo() {';
const result = await calculateReplacement(mockConfig, {
params: {
file_path: 'test.js',
old_string: oldString,
new_string: 'function bar() {',
},
currentContent: content,
abortSignal,
});
expect(result.strategy).toBe('regex');
expect(result.matchRanges).toEqual([{ start: 1, end: 1 }]);
});
it('should perform a fuzzy replacement when exact match fails but similarity is high', async () => {
const content =
'const myConfig = {\n enableFeature: true,\n retries: 3\n};';
+18 -4
View File
@@ -547,7 +547,6 @@ class EditToolInvocation
error: undefined,
originalLineEnding,
strategy: secondAttemptResult.strategy,
matchRanges: secondAttemptResult.matchRanges,
};
}
@@ -660,7 +659,6 @@ class EditToolInvocation
error: undefined,
originalLineEnding,
strategy: replacementResult.strategy,
matchRanges: replacementResult.matchRanges,
};
}
@@ -877,10 +875,26 @@ class EditToolInvocation
};
}
const totalLength = finalContent.split('\n').length;
const metadataParts = [];
if (editData.matchRanges && editData.matchRanges.length > 0) {
if (editData.matchRanges.length === 1) {
metadataParts.push(
`start_line: ${editData.matchRanges[0].start}, end_line: ${editData.matchRanges[0].end}`,
);
} else {
const ranges = editData.matchRanges
.map((r) => `${r.start}-${r.end}`)
.join(', ');
metadataParts.push(`ranges: ${ranges}`);
}
}
metadataParts.push(`file_length: ${totalLength}`);
const llmSuccessMessageParts = [
editData.isNewFile
? `Created new file: ${this.params.file_path} with provided content.`
: `Successfully modified file: ${this.params.file_path} (${editData.occurrences} replacements).`,
? `Created new file: ${this.params.file_path} with provided content. [file_length: ${totalLength}]`
: `Successfully modified file: ${this.params.file_path}. [${metadataParts.join(', ')}]`,
];
// Return a diff of the file before and after the write so that the agent