feat: pass Edit code diff start line in tool return (#994)

This commit is contained in:
Christina Tong
2026-02-17 12:01:03 -08:00
committed by GitHub
parent 7cc8729e57
commit 44d4cc87c1
4 changed files with 57 additions and 7 deletions

View File

@@ -233,10 +233,29 @@ export const ToolCallMessage = memo(
const dotShouldAnimate =
line.phase === "running" || (line.phase === "ready" && !isStreaming);
// Extract display text from tool result (handles JSON responses)
const extractMessageFromResult = (text: string): string => {
try {
const parsed = JSON.parse(text);
// If it's a JSON object with a message field, extract that
if (
parsed &&
typeof parsed === "object" &&
typeof parsed.message === "string"
) {
return parsed.message;
}
} catch {
// Not JSON or parsing failed, use as-is
}
return text;
};
// Format result for display
const getResultElement = () => {
if (!line.resultText) return null;
const extractedText = extractMessageFromResult(line.resultText);
const prefix = ``; // Match old format: 2 spaces, glyph, 2 spaces
const prefixWidth = 5; // Total width of prefix
const contentWidth = Math.max(0, columns - prefixWidth);
@@ -270,7 +289,7 @@ export const ToolCallMessage = memo(
// Truncate the result text for display (UI only, API gets full response)
// Strip trailing newlines to avoid extra visual spacing (e.g., from bash echo)
const displayResultText = clipToolReturn(line.resultText).replace(
const displayResultText = clipToolReturn(extractedText).replace(
/\n+$/,
"",
);

View File

@@ -12,6 +12,7 @@ interface EditArgs {
interface EditResult {
message: string;
replacements: number;
startLine?: number;
}
function countOccurrences(content: string, needle: string): number {
@@ -184,13 +185,22 @@ export async function edit(args: EditArgs): Promise<EditResult> {
(expected_replacements !== undefined && expected_replacements > 1);
let newContent: string;
let replacements: number;
let startLine: number | undefined;
if (effectiveReplaceAll) {
newContent = content.split(finalOldString).join(finalNewString);
replacements = occurrences;
// For replace_all, calculate line number of first occurrence
const firstIndex = content.indexOf(finalOldString);
if (firstIndex !== -1) {
startLine = content.substring(0, firstIndex).split("\n").length;
}
} else {
const index = content.indexOf(finalOldString);
if (index === -1)
throw new Error(`String not found in file: ${finalOldString}`);
// Calculate the line number where old_string starts (1-indexed)
startLine = content.substring(0, index).split("\n").length;
newContent =
content.substring(0, index) +
finalNewString +
@@ -198,9 +208,11 @@ export async function edit(args: EditArgs): Promise<EditResult> {
replacements = 1;
}
await fs.writeFile(resolvedPath, newContent, "utf-8");
return {
message: `Successfully replaced ${replacements} occurrence${replacements !== 1 ? "s" : ""} in ${resolvedPath}`,
replacements,
startLine,
};
} catch (error) {
const err = error as NodeJS.ErrnoException;

View File

@@ -11,9 +11,15 @@ export interface MultiEditArgs {
file_path: string;
edits: Edit[];
}
interface EditWithLine {
description: string;
startLine: number;
}
interface MultiEditResult {
message: string;
edits_applied: number;
edits: EditWithLine[];
}
export async function multi_edit(
@@ -45,7 +51,7 @@ export async function multi_edit(
const rawContent = await fs.readFile(resolvedPath, "utf-8");
// Normalize line endings to LF for consistent matching (Windows uses CRLF)
let content = rawContent.replace(/\r\n/g, "\n");
const appliedEdits: string[] = [];
const appliedEdits: EditWithLine[] = [];
for (let i = 0; i < edits.length; i++) {
const edit = edits[i];
if (!edit) continue;
@@ -61,26 +67,33 @@ export async function multi_edit(
`Found ${occurrences} matches of the string to replace, but replace_all is false. To replace all occurrences, set replace_all to true. To replace only one occurrence, please provide more context to uniquely identify the instance.\nString: ${old_string}`,
);
}
// Calculate start line before applying the edit
const index = content.indexOf(old_string);
const startLine = content.substring(0, index).split("\n").length;
if (replace_all) {
content = content.split(old_string).join(new_string);
} else {
const index = content.indexOf(old_string);
content =
content.substring(0, index) +
new_string +
content.substring(index + old_string.length);
}
appliedEdits.push(
`Replaced "${old_string.substring(0, 50)}${old_string.length > 50 ? "..." : ""}" with "${new_string.substring(0, 50)}${new_string.length > 50 ? "..." : ""}"`,
);
appliedEdits.push({
description: `Replaced "${old_string.substring(0, 50)}${old_string.length > 50 ? "..." : ""}" with "${new_string.substring(0, 50)}${new_string.length > 50 ? "..." : ""}"`,
startLine,
});
}
await fs.writeFile(resolvedPath, content, "utf-8");
const editList = appliedEdits
.map((edit, i) => `${i + 1}. ${edit}`)
.map((edit, i) => `${i + 1}. ${edit.description}`)
.join("\n");
return {
message: `Applied ${edits.length} edit${edits.length !== 1 ? "s" : ""} to ${resolvedPath}:\n${editList}`,
edits_applied: edits.length,
edits: appliedEdits,
};
} catch (error) {
const err = error as NodeJS.ErrnoException;

View File

@@ -949,6 +949,12 @@ function flattenToolResponse(result: unknown): ToolReturnContent {
}
if (typeof result.message === "string") {
// If there are other fields besides 'message', return the full object as JSON
const keys = Object.keys(result);
if (keys.length > 1) {
return JSON.stringify(result);
}
return result.message;
}