11import type { ToolResultBlockParam } from '@anthropic-ai/sdk/resources/index.mjs'
2- import type { StructuredPatchHunk } from 'diff'
32import * as React from 'react'
4- import { Suspense , use , useState } from 'react'
53import { FileEditToolUseRejectedMessage } from 'src/components/FileEditToolUseRejectedMessage.js'
64import { MessageResponse } from 'src/components/MessageResponse.js'
75import { extractTag } from 'src/utils/messages.js'
@@ -12,19 +10,10 @@ import { Text } from '@anthropic/ink'
1210import { FilePathLink } from 'src/components/FilePathLink.js'
1311import type { Tools } from 'src/Tool.js'
1412import type { Message , ProgressMessage } from 'src/types/message.js'
15- import { adjustHunkLineNumbers , CONTEXT_LINES } from 'src/utils/diff.js'
1613import { FILE_NOT_FOUND_CWD_NOTE , getDisplayPath } from 'src/utils/file.js'
17- import { logError } from 'src/utils/log.js'
1814import { getPlansDirectory } from 'src/utils/plans.js'
19- import { readEditContext } from 'src/utils/readEditContext.js'
20- import { firstLineOf } from 'src/utils/stringUtils.js'
2115import type { ThemeName } from 'src/utils/theme.js'
2216import type { FileEditOutput } from './types.js'
23- import {
24- findActualString ,
25- getPatchForEdit ,
26- preserveQuoteStyle ,
27- } from './utils.js'
2817
2918export function userFacingName (
3019 input :
@@ -99,8 +88,6 @@ export function renderToolResultMessage(
9988 < FileEditToolUpdatedMessage
10089 filePath = { filePath }
10190 structuredPatch = { structuredPatch }
102- firstLine = { originalFile . split ( '\n' ) [ 0 ] ?? null }
103- fileContent = { originalFile }
10491 style = { style }
10592 verbose = { verbose }
10693 previewHint = { isPlanFile ? '/plan to preview' : undefined }
@@ -116,7 +103,7 @@ export function renderToolUseRejectedMessage(
116103 replace_all ?: boolean
117104 edits ?: unknown [ ]
118105 } ,
119- options : {
106+ _options : {
120107 columns : number
121108 messages : Message [ ]
122109 progressMessagesForMessage : ProgressMessage [ ]
@@ -126,45 +113,14 @@ export function renderToolUseRejectedMessage(
126113 verbose : boolean
127114 } ,
128115) : React . ReactElement {
129- const { style, verbose } = options
116+ const { style, verbose } = _options
130117 const filePath = input . file_path
131- const oldString = input . old_string ?? ''
132- const newString = input . new_string ?? ''
133- const replaceAll = input . replace_all ?? false
134-
135- // Defensive: if input has an unexpected shape, show a simple rejection message
136- if ( 'edits' in input && input . edits != null ) {
137- return (
138- < FileEditToolUseRejectedMessage
139- file_path = { filePath }
140- operation = "update"
141- firstLine = { null }
142- verbose = { verbose }
143- />
144- )
145- }
146-
147- const isNewFile = oldString === ''
148-
149- // For new file creation, show content preview instead of diff
150- if ( isNewFile ) {
151- return (
152- < FileEditToolUseRejectedMessage
153- file_path = { filePath }
154- operation = "write"
155- content = { newString }
156- firstLine = { firstLineOf ( newString ) }
157- verbose = { verbose }
158- />
159- )
160- }
118+ const isNewFile = input . old_string === ''
161119
162120 return (
163- < EditRejectionDiff
164- filePath = { filePath }
165- oldString = { oldString }
166- newString = { newString }
167- replaceAll = { replaceAll }
121+ < FileEditToolUseRejectedMessage
122+ file_path = { filePath }
123+ operation = { isNewFile ? 'write' : 'update' }
168124 style = { style }
169125 verbose = { verbose }
170126 />
@@ -201,115 +157,3 @@ export function renderToolUseErrorMessage(
201157 }
202158 return < FallbackToolUseErrorMessage result = { result } verbose = { verbose } />
203159}
204-
205- type RejectionDiffData = {
206- patch : StructuredPatchHunk [ ]
207- firstLine : string | null
208- fileContent : string | undefined
209- }
210-
211- function EditRejectionDiff ( {
212- filePath,
213- oldString,
214- newString,
215- replaceAll,
216- style,
217- verbose,
218- } : {
219- filePath : string
220- oldString : string
221- newString : string
222- replaceAll : boolean
223- style ?: 'condensed'
224- verbose : boolean
225- } ) : React . ReactNode {
226- const [ dataPromise ] = useState ( ( ) =>
227- loadRejectionDiff ( filePath , oldString , newString , replaceAll ) ,
228- )
229- return (
230- < Suspense
231- fallback = {
232- < FileEditToolUseRejectedMessage
233- file_path = { filePath }
234- operation = "update"
235- firstLine = { null }
236- verbose = { verbose }
237- />
238- }
239- >
240- < EditRejectionBody
241- promise = { dataPromise }
242- filePath = { filePath }
243- style = { style }
244- verbose = { verbose }
245- />
246- </ Suspense >
247- )
248- }
249-
250- function EditRejectionBody ( {
251- promise,
252- filePath,
253- style,
254- verbose,
255- } : {
256- promise : Promise < RejectionDiffData >
257- filePath : string
258- style ?: 'condensed'
259- verbose : boolean
260- } ) : React . ReactNode {
261- const { patch, firstLine, fileContent } = use ( promise )
262- return (
263- < FileEditToolUseRejectedMessage
264- file_path = { filePath }
265- operation = "update"
266- patch = { patch }
267- firstLine = { firstLine }
268- fileContent = { fileContent }
269- style = { style }
270- verbose = { verbose }
271- />
272- )
273- }
274-
275- async function loadRejectionDiff (
276- filePath : string ,
277- oldString : string ,
278- newString : string ,
279- replaceAll : boolean ,
280- ) : Promise < RejectionDiffData > {
281- try {
282- // Chunked read — context window around the first occurrence. replaceAll
283- // still shows matches *within* the window via getPatchForEdit; we accept
284- // losing the all-occurrences view to keep the read bounded.
285- const ctx = await readEditContext ( filePath , oldString , CONTEXT_LINES )
286- if ( ctx === null || ctx . truncated || ctx . content === '' ) {
287- // ENOENT / not found / truncated — diff just the tool inputs.
288- const { patch } = getPatchForEdit ( {
289- filePath,
290- fileContents : oldString ,
291- oldString,
292- newString,
293- } )
294- return { patch, firstLine : null , fileContent : undefined }
295- }
296- const actualOld = findActualString ( ctx . content , oldString ) || oldString
297- const actualNew = preserveQuoteStyle ( oldString , actualOld , newString )
298- const { patch } = getPatchForEdit ( {
299- filePath,
300- fileContents : ctx . content ,
301- oldString : actualOld ,
302- newString : actualNew ,
303- replaceAll,
304- } )
305- return {
306- patch : adjustHunkLineNumbers ( patch , ctx . lineOffset - 1 ) ,
307- firstLine : ctx . lineOffset === 1 ? firstLineOf ( ctx . content ) : null ,
308- fileContent : ctx . content ,
309- }
310- } catch ( e ) {
311- // User may have manually applied the change while the diff was shown.
312- logError ( e as Error )
313- return { patch : [ ] , firstLine : null , fileContent : undefined }
314- }
315- }
0 commit comments