1+ import { memo } from "react" ;
12import type { UIMessage } from "@/types" ;
23import { parseUnifiedDiffFromUnknown } from "@/lib/unified-diff" ;
34import { DiffViewer } from "@/components/DiffViewer" ;
45import { UnifiedPatchViewer } from "@/components/UnifiedPatchViewer" ;
56import { firstDefinedString } from "@/components/lib/tool-formatting" ;
7+ import { getStructuredPatches , getPatchPath , filterValidPatches , type StructuredPatchEntry } from "@/lib/patch-utils" ;
68import { GenericContent } from "./GenericContent" ;
79
10+ // ── Multi-file rendering (Codex fileChange with N > 1 changes) ──
11+
12+ /** Render a single patch entry from a structuredPatch array. */
13+ const PatchEntryDiff = memo ( function PatchEntryDiff ( { patch } : { patch : StructuredPatchEntry } ) {
14+ const filePath = getPatchPath ( patch ) ;
15+ const diffText = patch . diff ?? "" ;
16+ const parsedDiff = diffText ? parseUnifiedDiffFromUnknown ( diffText ) : null ;
17+ const oldStr = firstDefinedString ( patch . oldString , parsedDiff ?. oldString ) ;
18+ const newStr = firstDefinedString ( patch . newString , parsedDiff ?. newString ) ;
19+
20+ if ( oldStr || newStr ) {
21+ return (
22+ < DiffViewer
23+ oldString = { oldStr }
24+ newString = { newStr }
25+ filePath = { filePath }
26+ unifiedDiff = { hasUnifiedDiffMarkers ( diffText ) ? diffText : undefined }
27+ />
28+ ) ;
29+ }
30+
31+ if ( diffText ) {
32+ return < UnifiedPatchViewer diffText = { diffText } filePath = { filePath } /> ;
33+ }
34+
35+ return null ;
36+ } ) ;
37+
38+ // ── Main component ──
39+
840export function EditContent ( { message } : { message : UIMessage } ) {
9- const structuredPatch = Array . isArray ( message . toolResult ?. structuredPatch )
10- ? ( message . toolResult . structuredPatch as Array < Record < string , unknown > > )
11- : [ ] ;
41+ const structuredPatch = getStructuredPatches ( message . toolResult ) ;
42+
43+ // Multi-file Codex fileChange: render each file's diff separately
44+ if ( structuredPatch . length > 1 ) {
45+ const validPatches = filterValidPatches ( structuredPatch ) ;
46+ if ( validPatches . length === 0 ) return < GenericContent message = { message } /> ;
47+ return (
48+ < div className = "space-y-2" >
49+ { validPatches . map ( ( patch , i ) => (
50+ < PatchEntryDiff
51+ key = { `${ getPatchPath ( patch ) } -${ i } ` }
52+ patch = { patch }
53+ />
54+ ) ) }
55+ </ div >
56+ ) ;
57+ }
58+
59+ // Single-file: existing logic with full fallback chain
60+ // (Claude engine, ACP engine, single-file Codex edits)
1261 const matchingPatch =
1362 structuredPatch . find ( ( entry ) => {
14- const entryPath = entry . filePath ?? entry . path ;
15- return typeof entryPath === "string"
16- && entryPath
63+ const entryPath = getPatchPath ( entry ) ;
64+ return entryPath
1765 && entryPath === String ( message . toolInput ?. file_path ?? message . toolResult ?. filePath ?? "" ) ;
1866 } ) ?? structuredPatch [ 0 ] ;
1967 const resultContent = typeof message . toolResult ?. content === "string"
@@ -22,12 +70,12 @@ export function EditContent({ message }: { message: UIMessage }) {
2270 const detailedContent = typeof message . toolResult ?. detailedContent === "string"
2371 ? message . toolResult . detailedContent
2472 : "" ;
25- const patchDiffText = typeof matchingPatch ?. diff === "string" ? matchingPatch . diff : "" ;
73+ const patchDiffText = matchingPatch ?. diff ?? "" ;
2674 const candidateDiffText = selectUnifiedDiffText ( patchDiffText , detailedContent , resultContent ) ;
2775 const filePath = String (
2876 message . toolInput ?. file_path
2977 ?? message . toolResult ?. filePath
30- ?? ( typeof matchingPatch ?. filePath === "string" ? matchingPatch . filePath : "" )
78+ ?? matchingPatch ?. filePath
3179 ?? extractFilePathFromDiff ( candidateDiffText )
3280 ?? "" ,
3381 ) ;
@@ -38,15 +86,15 @@ export function EditContent({ message }: { message: UIMessage }) {
3886 const unifiedDiffText = candidateDiffText ;
3987 // Prefer parsed/structured patch text first; toolInput can be a lossy representation.
4088 const oldStr = firstDefinedString (
41- typeof matchingPatch ?. oldString === "string" ? matchingPatch . oldString : undefined ,
89+ matchingPatch ?. oldString ,
4290 parsedStructuredDiff ?. oldString ,
4391 parsedDiff ?. oldString ,
4492 parsedDetailedDiff ?. oldString ,
4593 message . toolResult ?. oldString ,
4694 message . toolInput ?. old_string ,
4795 ) ;
4896 const newStr = firstDefinedString (
49- typeof matchingPatch ?. newString === "string" ? matchingPatch . newString : undefined ,
97+ matchingPatch ?. newString ,
5098 parsedStructuredDiff ?. newString ,
5199 parsedDiff ?. newString ,
52100 parsedDetailedDiff ?. newString ,
0 commit comments