Skip to content

Commit fcd44ca

Browse files
committed
fix(acp): old-style diffs keep their own file next to Git patches
The client diff parser splits only at `diff --git` headers once the patch contains one. An oldText/newText section after a Git patch had its hunk attached to that file, and one before a Git patch was dropped. Give those sections a Git header when another section has one. Output with no Git patch is unchanged.
1 parent 03df3ca commit fcd44ca

2 files changed

Lines changed: 43 additions & 5 deletions

File tree

‎apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.ts‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ describe("acpToolCallDiffPatch", () => {
303303
"-b",
304304
"+B",
305305
"",
306+
'diff --git "a//repo/c.ts" "b//repo/c.ts"',
306307
"--- /repo/c.ts",
307308
"+++ /repo/c.ts",
308309
"@@ -1,1 +1,1 @@",
@@ -311,6 +312,31 @@ describe("acpToolCallDiffPatch", () => {
311312
"",
312313
].join("\n"),
313314
);
315+
assert.equal(
316+
acpToolCallDiffPatch([
317+
{ type: "diff", path: "/repo/new.txt", oldText: null, newText: "hello\n" },
318+
{
319+
type: "diff",
320+
changes: [{ operation: "modify", path: "/repo/a.ts" }],
321+
patch: { format: "git_patch", text: "@@ -1 +1 @@\n-a\n+A\n" },
322+
},
323+
]),
324+
[
325+
'diff --git "a//repo/new.txt" "b//repo/new.txt"',
326+
"--- /dev/null",
327+
"+++ /repo/new.txt",
328+
"@@ -0,0 +1,1 @@",
329+
"+hello",
330+
"",
331+
'diff --git "a//repo/a.ts" "b//repo/a.ts"',
332+
'--- "a//repo/a.ts"',
333+
'+++ "b//repo/a.ts"',
334+
"@@ -1 +1 @@",
335+
"-a",
336+
"+A",
337+
"",
338+
].join("\n"),
339+
);
314340
});
315341
});
316342

‎packages/provider-acp/src/server/adapter.ts‎

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -908,7 +908,7 @@ export function acpToolCallDiffPatch(content: unknown): string | undefined {
908908
const diff = unknownRecord(entry);
909909
return diff?.type === "diff" ? [diff] : [];
910910
});
911-
const patches = diffs.flatMap((diff) => {
911+
const patches = diffs.flatMap((diff): ReadonlyArray<{ text: string; path?: string }> => {
912912
const patch = unknownRecord(diff.patch);
913913
if (typeof patch?.text === "string") {
914914
const change =
@@ -925,10 +925,12 @@ export function acpToolCallDiffPatch(content: unknown): string | undefined {
925925
const after =
926926
change.operation === "delete" ? "/dev/null" : JSON.stringify(`b/${change.path}`);
927927
return [
928-
`diff --git ${JSON.stringify(`a/${oldPath}`)} ${JSON.stringify(`b/${change.path}`)}\n--- ${before}\n+++ ${after}\n${patch.text.trimStart()}`,
928+
{
929+
text: `diff --git ${JSON.stringify(`a/${oldPath}`)} ${JSON.stringify(`b/${change.path}`)}\n--- ${before}\n+++ ${after}\n${patch.text.trimStart()}`,
930+
},
929931
];
930932
}
931-
return [patch.text];
933+
return [{ text: patch.text }];
932934
}
933935
if (typeof diff.path !== "string" || typeof diff.newText !== "string") return [];
934936
const oldText = typeof diff.oldText === "string" ? diff.oldText : undefined;
@@ -946,9 +948,19 @@ export function acpToolCallDiffPatch(content: unknown): string | undefined {
946948
);
947949
return structured === undefined || structured.hunks.length === 0
948950
? []
949-
: [formatPatch(structured, FILE_HEADERS_ONLY)];
951+
: [{ text: formatPatch(structured, FILE_HEADERS_ONLY), path: diff.path }];
950952
});
951-
return patches.length === 0 ? undefined : patches.join("\n");
953+
// Diff parsers split at Git headers once one exists, so every section then needs its own.
954+
const hasGitHeader = patches.some(({ text }) => /^diff --git /m.test(text));
955+
return patches.length === 0
956+
? undefined
957+
: patches
958+
.map(({ text, path }) =>
959+
hasGitHeader && path !== undefined
960+
? `diff --git ${JSON.stringify(`a/${path}`)} ${JSON.stringify(`b/${path}`)}\n${text}`
961+
: text,
962+
)
963+
.join("\n");
952964
}
953965

954966
function pathFromToolCall(toolCall: AcpToolCallState): string | undefined {

0 commit comments

Comments
 (0)