Skip to content

Commit 1bb1113

Browse files
committed
Render edit diffs from tool input
1 parent 28b9daa commit 1bb1113

2 files changed

Lines changed: 48 additions & 54 deletions

File tree

cli/src/components/tools/str-replace.tsx

Lines changed: 10 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,14 @@ import { TextAttributes } from '@opentui/core'
33
import { DiffViewer } from './diff-viewer'
44
import { defineToolComponent } from './types'
55
import { useTheme } from '../../hooks/use-theme'
6+
import {
7+
extractDiff,
8+
extractFilePath,
9+
isCreateFile,
10+
} from '../../utils/implementor-helpers'
611

712
import type { ToolRenderConfig } from './types'
813

9-
function extractValueForKey(output: string, key: string): string | null {
10-
if (!output) return null
11-
const lines = output.split('\n')
12-
for (let i = 0; i < lines.length; i++) {
13-
const line = lines[i]
14-
const match = line.match(/^\s*([A-Za-z0-9_]+):\s*(.*)$/)
15-
if (match && match[1] === key) {
16-
const rest = match[2]
17-
if (rest.trim().startsWith('|')) {
18-
const baseIndent = lines[i + 1]?.match(/^\s*/)?.[0].length ?? 0
19-
const acc: string[] = []
20-
for (let j = i + 1; j < lines.length; j++) {
21-
const l = lines[j]
22-
const indent = l.match(/^\s*/)?.[0].length ?? 0
23-
if (l.trim().length === 0) {
24-
acc.push('')
25-
continue
26-
}
27-
if (indent < baseIndent) break
28-
acc.push(l.slice(baseIndent))
29-
}
30-
return acc.join('\n')
31-
} else {
32-
let val = rest.trim()
33-
if (val.startsWith('"') && val.endsWith('"')) {
34-
val = val.slice(1, -1)
35-
}
36-
return val
37-
}
38-
}
39-
}
40-
return null
41-
}
42-
43-
function isCreatedFileMessage(message: string | null): boolean {
44-
return message === 'Created file successfully.' || message === 'Created new file'
45-
}
46-
4714
interface EditHeaderProps {
4815
name: string
4916
filePath: string | null
@@ -70,14 +37,13 @@ interface EditBodyProps {
7037
name: string
7138
filePath: string | null
7239
diffText: string
73-
isCreate: boolean
7440
}
7541

76-
const EditBody = ({ name, filePath, diffText, isCreate }: EditBodyProps) => {
42+
const EditBody = ({ name, filePath, diffText }: EditBodyProps) => {
7743
return (
7844
<box style={{ flexDirection: 'column', gap: 0, width: '100%' }}>
7945
<EditHeader name={name} filePath={filePath} />
80-
{!isCreate && diffText.length > 0 && (
46+
{diffText.length > 0 && (
8147
<box style={{ paddingLeft: 2, width: '100%' }}>
8248
<DiffViewer diffText={diffText} />
8349
</box>
@@ -90,26 +56,16 @@ export const StrReplaceComponent = defineToolComponent({
9056
toolName: 'str_replace',
9157

9258
render(toolBlock): ToolRenderConfig {
93-
const outputStr =
94-
typeof toolBlock.output === 'string' ? toolBlock.output : ''
95-
const diff =
96-
extractValueForKey(outputStr, 'unifiedDiff') ||
97-
extractValueForKey(outputStr, 'patch')
98-
const filePath =
99-
extractValueForKey(outputStr, 'file') ||
100-
(typeof (toolBlock.input as any)?.path === 'string'
101-
? (toolBlock.input as any).path
102-
: null)
103-
const message = extractValueForKey(outputStr, 'message')
104-
const isCreate = isCreatedFileMessage(message)
59+
const diff = extractDiff(toolBlock)
60+
const filePath = extractFilePath(toolBlock)
61+
const isCreate = isCreateFile(toolBlock)
10562

10663
return {
10764
content: (
10865
<EditBody
10966
name={isCreate ? 'Create' : 'Edit'}
11067
filePath={filePath}
11168
diffText={diff ?? ''}
112-
isCreate={isCreate}
11369
/>
11470
),
11571
}

cli/src/utils/__tests__/implementor-helpers.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,32 @@ describe('extractDiff', () => {
117117
expect(diff).toContain('+ const x = 2')
118118
})
119119

120+
test('constructs diff from successful str_replace input when output omits diff', () => {
121+
const block: ToolContentBlock = {
122+
type: 'tool',
123+
toolCallId: 'test-1',
124+
toolName: 'str_replace',
125+
input: {
126+
replacements: [{ oldString: 'const x = 1', newString: 'const x = 2' }],
127+
},
128+
output: 'message: String replace applied successfully.',
129+
}
130+
const diff = extractDiff(block)
131+
expect(diff).toContain('- const x = 1')
132+
expect(diff).toContain('+ const x = 2')
133+
})
134+
135+
test('uses patch content from successful str_replace input when output omits diff', () => {
136+
const block: ToolContentBlock = {
137+
type: 'tool',
138+
toolCallId: 'test-1',
139+
toolName: 'str_replace',
140+
input: { type: 'patch', content: '- const x = 1\n+ const x = 2' },
141+
output: 'message: String replace applied successfully.',
142+
}
143+
expect(extractDiff(block)).toBe('- const x = 1\n+ const x = 2')
144+
})
145+
120146
test('constructs diff from write_file input', () => {
121147
const block: ToolContentBlock = {
122148
type: 'tool',
@@ -128,6 +154,18 @@ describe('extractDiff', () => {
128154
expect(diff).toBe('+ line1\n+ line2')
129155
})
130156

157+
test('constructs diff from successful write_file input when output omits diff', () => {
158+
const block: ToolContentBlock = {
159+
type: 'tool',
160+
toolCallId: 'test-1',
161+
toolName: 'write_file',
162+
input: { content: 'line1\nline2' },
163+
output: 'message: Overwrote file successfully.',
164+
}
165+
const diff = extractDiff(block)
166+
expect(diff).toBe('+ line1\n+ line2')
167+
})
168+
131169
test('constructs diff from propose_str_replace input', () => {
132170
const block: ToolContentBlock = {
133171
type: 'tool',

0 commit comments

Comments
 (0)