Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions packages/core/src/tools/edit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,94 @@ function doIt() {
});
});

describe('getDescription', () => {
it('should return "Create <path>" for new file (empty old_string)', () => {
const filePath = path.join(rootDir, 'new.ts');
const params: EditToolParams = {
file_path: filePath,
instruction: 'Create new file',
old_string: '',
new_string: 'content',
};
const invocation = tool.build(params);
expect(invocation.getDescription()).toMatch(/^Create /);
});

it('should NOT append "..." to a short single-line old_string and new_string', () => {
const filePath = path.join(rootDir, 'test.ts');
const params: EditToolParams = {
file_path: filePath,
instruction: 'Replace short old with short new',
old_string: 'short old',
new_string: 'short new',
};
const invocation = tool.build(params);
const description = invocation.getDescription();
expect(description).toContain('short old');
expect(description).toContain('short new');
expect(description).not.toMatch(/short old\.\.\./);
expect(description).not.toMatch(/short new\.\.\./);
});

it('should append "..." when old_string or new_string is multi-line, even if first line is short', () => {
const filePath = path.join(rootDir, 'test.ts');
// old_string first line is only 17 chars (< 30), but the full string is multi-line
const params: EditToolParams = {
file_path: filePath,
instruction: 'Replace function foo with bar',
old_string: 'function foo() {\n return 1;\n}',
new_string: 'function bar() {\n return 2;\n}',
};
const invocation = tool.build(params);
const description = invocation.getDescription();
// First lines are short, but since strings are multi-line, '...' should appear
// The full first line 'function foo() {' is kept (17 chars < 30) and '...' is appended
expect(description).toContain('function foo() {...');
expect(description).toContain('function bar() {...');
});

it('should append "..." when the first line of old_string exceeds 30 characters', () => {
const filePath = path.join(rootDir, 'test.ts');
const longFirstLine = 'a'.repeat(35); // 35 chars, exceeds 30
const params: EditToolParams = {
file_path: filePath,
instruction: 'Replace long line',
old_string: longFirstLine,
new_string: 'short',
};
const invocation = tool.build(params);
const description = invocation.getDescription();
expect(description).toContain('...');
});

it('should return "No file changes" when old_string equals new_string', () => {
const filePath = path.join(rootDir, 'test.ts');
const params: EditToolParams = {
file_path: filePath,
instruction: 'No-op edit',
old_string: 'same content',
new_string: 'same content',
};
const invocation = tool.build(params);
expect(invocation.getDescription()).toMatch(/No file changes/);
});

it('should normalize Windows-style line endings (\\r\\n) and not include \\r in the output snippet', () => {
const filePath = path.join(rootDir, 'test.ts');
const params: EditToolParams = {
file_path: filePath,
instruction: 'Handle CRLF',
old_string: 'hello\r\nworld',
new_string: 'short',
};
const invocation = tool.build(params);
const description = invocation.getDescription();
// Ensure the '\r' is stripped from the 'hello' portion and '...' is appended due to multi-line
expect(description).toContain('hello...');
expect(description).not.toContain('hello\r...');
});
});

describe('execute', () => {
const testFile = 'execute_me.txt';
let filePath: string;
Expand Down
31 changes: 25 additions & 6 deletions packages/core/src/tools/edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -837,12 +837,31 @@ class EditToolInvocation
return `Create ${shortenPath(relativePath)}`;
}

const oldStringSnippet =
this.params.old_string.split('\n')[0].substring(0, 30) +
(this.params.old_string.length > 30 ? '...' : '');
const newStringSnippet =
this.params.new_string.split('\n')[0].substring(0, 30) +
(this.params.new_string.length > 30 ? '...' : '');
const getSnippet = (str: string): string => {
const safeStr = str ?? '';
const newlineIdx = safeStr.indexOf('\n');
let firstLine =
newlineIdx !== -1 ? safeStr.substring(0, newlineIdx) : safeStr;
if (firstLine.endsWith('\r')) {
firstLine = firstLine.slice(0, -1);
}

const segments: string[] = [];
for (const s of new Intl.Segmenter().segment(firstLine)) {
segments.push(s.segment);
if (segments.length > 30) {
break;
}
}

if (segments.length > 30) {
return segments.slice(0, 30).join('') + '...';
}
return firstLine + (newlineIdx !== -1 ? '...' : '');
};
Comment thread
Megashubham marked this conversation as resolved.
Comment thread
Megashubham marked this conversation as resolved.
Comment thread
Megashubham marked this conversation as resolved.
Comment thread
Megashubham marked this conversation as resolved.

const oldStringSnippet = getSnippet(this.params.old_string);
const newStringSnippet = getSnippet(this.params.new_string);

if (this.params.old_string === this.params.new_string) {
return `No file changes to ${shortenPath(relativePath)}`;
Expand Down
Loading