From 11ab673ed0fd8db74501ea4cb84932a595171d97 Mon Sep 17 00:00:00 2001
From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Date: Fri, 14 Aug 2026 23:02:45 -0700
Subject: [PATCH] fix(native-chat): render structured patches as diffs
---
.../native-chat/NativeChatToolRun.test.tsx | 24 ++++++++++++++
.../native-chat/native-chat-diff.test.ts | 33 +++++++++++++++++++
src/shared/native-chat-diff.ts | 33 +++++++++++++++++++
3 files changed, 90 insertions(+)
diff --git a/src/renderer/src/components/native-chat/NativeChatToolRun.test.tsx b/src/renderer/src/components/native-chat/NativeChatToolRun.test.tsx
index fa5358a3fad..ac77ca5f3e4 100644
--- a/src/renderer/src/components/native-chat/NativeChatToolRun.test.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatToolRun.test.tsx
@@ -24,4 +24,28 @@ describe('NativeChatToolRun', () => {
expect(screen.getByTitle('src/index.ts')).toHaveTextContent('src/index.ts')
expect(screen.queryByTitle('{"file_path":"src/index.ts","offset":10}')).toBeNull()
})
+
+ it('renders structured apply_patch changes as a reviewable diff instead of JSON', () => {
+ const blocks: NativeChatBlock[] = [
+ {
+ type: 'tool-call',
+ name: 'apply_patch',
+ input: {
+ changes: [
+ {
+ path: '/repo/src/app.ts',
+ kind: { type: 'update', move_path: null },
+ diff: '@@ -1 +1 @@\n-before\n+after'
+ }
+ ]
+ }
+ }
+ ]
+
+ const { container } = render()
+
+ expect(screen.getByText('+after')).toBeInTheDocument()
+ expect(screen.getByText('-before')).toBeInTheDocument()
+ expect(container.querySelector('pre')).toBeNull()
+ })
})
diff --git a/src/renderer/src/components/native-chat/native-chat-diff.test.ts b/src/renderer/src/components/native-chat/native-chat-diff.test.ts
index 229edc1e4bd..7f8fa7af575 100644
--- a/src/renderer/src/components/native-chat/native-chat-diff.test.ts
+++ b/src/renderer/src/components/native-chat/native-chat-diff.test.ts
@@ -30,6 +30,39 @@ describe('diffFromToolCall', () => {
])
})
+ it('reads structured apply_patch file changes as a diff', () => {
+ const diff = diffFromToolCall('apply_patch', {
+ changes: [
+ {
+ path: '/repo/src/app.ts',
+ kind: { type: 'update', move_path: null },
+ diff: '@@ -1,2 +1,2 @@\n const value = 1\n-old()\n+newValue()'
+ }
+ ]
+ })
+
+ expect(diff).toEqual([
+ { kind: 'meta', text: '--- /repo/src/app.ts' },
+ { kind: 'meta', text: '+++ /repo/src/app.ts' },
+ { kind: 'meta', text: '@@ -1,2 +1,2 @@' },
+ { kind: 'context', text: ' const value = 1' },
+ { kind: 'del', text: 'old()' },
+ { kind: 'add', text: 'newValue()' }
+ ])
+ })
+
+ it('reads direct apply_patch text as a diff', () => {
+ expect(
+ diffFromToolCall('apply_patch', {
+ patch: '@@ -1 +1 @@\n-before\n+after'
+ })
+ ).toEqual([
+ { kind: 'meta', text: '@@ -1 +1 @@' },
+ { kind: 'del', text: 'before' },
+ { kind: 'add', text: 'after' }
+ ])
+ })
+
it('returns null when there is no old/new payload', () => {
expect(diffFromToolCall('Edit', { file_path: '/x' })).toBeNull()
})
diff --git a/src/shared/native-chat-diff.ts b/src/shared/native-chat-diff.ts
index 0e9e242af6e..1df82db4800 100644
--- a/src/shared/native-chat-diff.ts
+++ b/src/shared/native-chat-diff.ts
@@ -80,6 +80,35 @@ function toLines(value: unknown, maxLines: number): { lines: string[]; truncated
return { lines: bounded, truncated }
}
+function patchTextFromToolInput(value: Record): string | null {
+ if (typeof value.patch === 'string') {
+ return value.patch
+ }
+ if (typeof value.diff === 'string') {
+ return value.diff
+ }
+ if (!Array.isArray(value.changes)) {
+ return null
+ }
+ const sections = value.changes.flatMap((entry) => {
+ if (typeof entry !== 'object' || entry === null) {
+ return []
+ }
+ const change = entry as Record
+ if (typeof change.diff !== 'string') {
+ return []
+ }
+ const path = typeof change.path === 'string' ? change.path : 'file'
+ const kind =
+ typeof change.kind === 'object' && change.kind !== null
+ ? (change.kind as Record)
+ : null
+ const nextPath = kind && typeof kind.move_path === 'string' ? kind.move_path : path
+ return [`--- ${path}\n+++ ${nextPath}\n${change.diff}`]
+ })
+ return sections.length > 0 ? sections.join('\n') : null
+}
+
export function diffFromToolCall(
name: string,
input: unknown,
@@ -89,6 +118,10 @@ export function diffFromToolCall(
return null
}
const value = input as Record
+ const patchText = patchTextFromToolInput(value)
+ if (patchText !== null) {
+ return diffFromText(patchText, maxLines)
+ }
const oldLines = toLines(value.old_string ?? value.oldString ?? value.old, maxLines)
const newLines = toLines(
value.new_string ?? value.newString ?? value.new ?? value.content ?? value.file_text,