fix(native-chat): render structured patches as diffs

This commit is contained in:
Brennan Benson
2026-08-14 23:35:13 -07:00
parent b36a3a2022
commit 11ab673ed0
3 changed files with 90 additions and 0 deletions
@@ -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(<NativeChatToolRun blocks={blocks} expandSignal />)
expect(screen.getByText('+after')).toBeInTheDocument()
expect(screen.getByText('-before')).toBeInTheDocument()
expect(container.querySelector('pre')).toBeNull()
})
})
@@ -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()
})
+33
View File
@@ -80,6 +80,35 @@ function toLines(value: unknown, maxLines: number): { lines: string[]; truncated
return { lines: bounded, truncated }
}
function patchTextFromToolInput(value: Record<string, unknown>): 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<string, unknown>
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<string, unknown>)
: 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<string, unknown>
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,