fix: render multi-file tool diffs safely
Prevents chat crashes from multi-file apply_patch diffs Splits aggregate patches before rendering each file diff
This commit is contained in:
@@ -1282,6 +1282,16 @@ type DiffPatchEntry = {
|
|||||||
|
|
||||||
const hasUnifiedDiffHunk = (patch: string): boolean => /^@@\s+-\d+(?:,\d+)?\s+\+\d+(?:,\d+)?\s+@@/m.test(patch);
|
const hasUnifiedDiffHunk = (patch: string): boolean => /^@@\s+-\d+(?:,\d+)?\s+\+\d+(?:,\d+)?\s+@@/m.test(patch);
|
||||||
|
|
||||||
|
const isUnifiedFileHeaderPair = (minusLine: string, plusLine: string): boolean => {
|
||||||
|
if (!minusLine.startsWith('--- ') || !plusLine.startsWith('+++ ')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldPath = minusLine.slice(4).trim();
|
||||||
|
const newPath = plusLine.slice(4).trim();
|
||||||
|
return oldPath.length > 0 && newPath.length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
const getUnifiedDiffPath = (patch: string, fallbackTitle: string): string => {
|
const getUnifiedDiffPath = (patch: string, fallbackTitle: string): string => {
|
||||||
const plusHeader = patch.match(/^\+\+\+\s+(?:[ab]\/(.+)|(.+))$/m);
|
const plusHeader = patch.match(/^\+\+\+\s+(?:[ab]\/(.+)|(.+))$/m);
|
||||||
const rawPath = plusHeader?.[1] ?? plusHeader?.[2];
|
const rawPath = plusHeader?.[1] ?? plusHeader?.[2];
|
||||||
@@ -1303,9 +1313,7 @@ const splitUnifiedDiffPatch = (patch: string): DiffPatchEntry[] => {
|
|||||||
for (let index = 0; index < lines.length; index += 1) {
|
for (let index = 0; index < lines.length; index += 1) {
|
||||||
const line = lines[index] ?? '';
|
const line = lines[index] ?? '';
|
||||||
const nextLine = lines[index + 1] ?? '';
|
const nextLine = lines[index + 1] ?? '';
|
||||||
const isUnifiedFileHeader = /^---\s+(?:[ab]\/|\/dev\/null|\/)/.test(line)
|
if (line.startsWith('diff --git ') || line.startsWith('Index: ') || isUnifiedFileHeaderPair(line, nextLine)) {
|
||||||
&& /^\+\+\+\s+(?:[ab]\/|\/dev\/null|\/)/.test(nextLine);
|
|
||||||
if (line.startsWith('diff --git ') || line.startsWith('Index: ') || isUnifiedFileHeader) {
|
|
||||||
starts.push(index);
|
starts.push(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1430,7 +1438,8 @@ const getDiffPatchEntries = (
|
|||||||
|
|
||||||
const record = file as { relativePath?: unknown; filePath?: unknown; patch?: unknown; diff?: unknown };
|
const record = file as { relativePath?: unknown; filePath?: unknown; patch?: unknown; diff?: unknown };
|
||||||
const patch = getPatchText(record.patch) ?? getPatchText(record.diff) ?? '';
|
const patch = getPatchText(record.patch) ?? getPatchText(record.diff) ?? '';
|
||||||
if (!patch || !hasUnifiedDiffHunk(patch)) {
|
const splitPatchEntries = splitUnifiedDiffPatch(patch);
|
||||||
|
if (!patch || splitPatchEntries.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1444,12 +1453,13 @@ const getDiffPatchEntries = (
|
|||||||
? getRelativePath(rawPath, currentDirectory)
|
? getRelativePath(rawPath, currentDirectory)
|
||||||
: `File ${index + 1}`;
|
: `File ${index + 1}`;
|
||||||
|
|
||||||
return {
|
return splitPatchEntries.map((entry, splitIndex) => ({
|
||||||
id: `${title}-${index}`,
|
id: `${title}-${index}-${splitIndex}`,
|
||||||
title,
|
title: splitPatchEntries.length === 1 ? title : getRelativePath(entry.title, currentDirectory),
|
||||||
patch,
|
patch: entry.patch,
|
||||||
} satisfies DiffPatchEntry;
|
} satisfies DiffPatchEntry));
|
||||||
})
|
})
|
||||||
|
.flat()
|
||||||
.filter((entry): entry is DiffPatchEntry => entry !== null);
|
.filter((entry): entry is DiffPatchEntry => entry !== null);
|
||||||
|
|
||||||
if (entries.length > 0) {
|
if (entries.length > 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user