feat: pass Edit code diff start line in tool return (#994)
This commit is contained in:
@@ -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+$/,
|
||||
"",
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user