11import type { ToolCallContent } from "@agentclientprotocol/sdk" ;
2- import { applyPatch , parsePatch } from "diff" ;
2+ import { applyPatch , parsePatch , reversePatch } from "diff" ;
33import { readFile } from "node:fs/promises" ;
44import path from "node:path" ;
55import type { UpdateSessionEvent } from "./ACPSessionConnection" ;
@@ -293,18 +293,44 @@ async function createAddFileContent(change: FileUpdateChange): Promise<ToolCallC
293293}
294294
295295async function createUpdateFileContent ( change : FileUpdateChange ) : Promise < ToolCallContent | null > {
296- const oldContent = await readFile ( change . path , { encoding : "utf8" } ) . catch ( ( ) => null ) ;
297- if ( oldContent === null ) return null ;
296+ if ( change . kind . type !== "update" ) return null ;
298297
299298 const unifiedDiff = recoverCorruptedDiff ( change . diff ) ;
300- const newContent = applyPatch ( oldContent , unifiedDiff ) ;
301- if ( ! newContent ) return null ;
299+ const movePath = change . kind . move_path ;
302300
301+ const oldContent = await readFileContent ( change . path ) ;
302+ if ( oldContent !== null ) {
303+ const patchedContent = applyPatch ( oldContent , unifiedDiff ) ;
304+ if ( patchedContent === false ) return null ;
305+ return createUpdateDiffContent ( movePath ?? change . path , oldContent , patchedContent ) ;
306+ }
307+
308+ if ( ! movePath ) return null ;
309+ const newContent = await readFileContent ( movePath ) ;
310+ if ( newContent === null ) return null ;
311+
312+ const revertedPatch = revertPatch ( unifiedDiff ) ;
313+ if ( ! revertedPatch ) return null ;
314+
315+ const revertedContent = applyPatch ( newContent , revertedPatch ) ;
316+ if ( revertedContent === false ) return null ;
317+
318+ return createUpdateDiffContent ( movePath , revertedContent , newContent ) ;
319+ }
320+
321+ function revertPatch ( unifiedDiff : string ) {
322+ const [ patch ] = parsePatch ( unifiedDiff ) ;
323+ if ( ! patch ) return null ;
324+
325+ return reversePatch ( patch ) ;
326+ }
327+
328+ function createUpdateDiffContent ( path : string , oldText : string , newText : string ) : ToolCallContent {
303329 return {
304330 type : "diff" ,
305- oldText : oldContent ,
306- newText : newContent ,
307- path : change . path ,
331+ oldText,
332+ newText,
333+ path,
308334 _meta : {
309335 kind : "update" ,
310336 } ,
@@ -313,9 +339,8 @@ async function createUpdateFileContent(change: FileUpdateChange): Promise<ToolCa
313339
314340async function createDeleteFileContent ( change : FileUpdateChange ) : Promise < ToolCallContent > {
315341 // If the patch deletes a file, the old content may be only available from the diff.
316- const oldContent = await readFile ( change . path , { encoding : "utf8" } ) . catch ( ( ) =>
317- isUnifiedDiff ( change . diff ) ? patchToDeletedContent ( change . diff ) : change . diff
318- ) ;
342+ const fileContent = await readFileContent ( change . path ) ;
343+ const oldContent = fileContent ?? ( isUnifiedDiff ( change . diff ) ? patchToDeletedContent ( change . diff ) : change . diff ) ;
319344
320345 return {
321346 type : "diff" ,
@@ -328,6 +353,10 @@ async function createDeleteFileContent(change: FileUpdateChange): Promise<ToolCa
328353 }
329354}
330355
356+ async function readFileContent ( filePath : string ) : Promise < string | null > {
357+ return await readFile ( filePath , { encoding : "utf8" } ) . catch ( ( ) => null ) ;
358+ }
359+
331360/**
332361 * Fix unified diff content corrupted by codex agent.
333362 * Removes synthetic "Moved to" from the end.
0 commit comments