@@ -86,6 +86,7 @@ import { validateToolUse, isToolAllowedForMode, ToolName } from "./mode-validato
8686import { parseXml } from "../utils/xml"
8787import { getWorkspacePath } from "../utils/path"
8888import { writeToFileTool } from "./tools/writeToFileTool"
89+ import { applyDiffTool } from "./tools/applyDiffTool"
8990
9091export type ToolResponse = string | Array < Anthropic . TextBlockParam | Anthropic . ImageBlockParam >
9192type UserContent = Array < Anthropic . Messages . ContentBlockParam >
@@ -151,7 +152,7 @@ export class Cline extends EventEmitter<ClineEvents> {
151152 private lastMessageTs ?: number
152153 // Not private since it needs to be accessible by tools
153154 consecutiveMistakeCount : number = 0
154- private consecutiveMistakeCountForApplyDiff : Map < string , number > = new Map ( )
155+ consecutiveMistakeCountForApplyDiff : Map < string , number > = new Map ( )
155156 // Not private since it needs to be accessible by tools
156157 providerRef : WeakRef < ClineProvider >
157158 private abort : boolean = false
@@ -1568,178 +1569,9 @@ export class Cline extends EventEmitter<ClineEvents> {
15681569 case "write_to_file" :
15691570 await writeToFileTool ( this , block , askApproval , handleError , pushToolResult , removeClosingTag )
15701571 break
1571- case "apply_diff" : {
1572- const relPath : string | undefined = block . params . path
1573- const diffContent : string | undefined = block . params . diff
1574-
1575- const sharedMessageProps : ClineSayTool = {
1576- tool : "appliedDiff" ,
1577- path : getReadablePath ( this . cwd , removeClosingTag ( "path" , relPath ) ) ,
1578- }
1579-
1580- try {
1581- if ( block . partial ) {
1582- // update gui message
1583- let toolProgressStatus
1584- if ( this . diffStrategy && this . diffStrategy . getProgressStatus ) {
1585- toolProgressStatus = this . diffStrategy . getProgressStatus ( block )
1586- }
1587-
1588- const partialMessage = JSON . stringify ( sharedMessageProps )
1589-
1590- await this . ask ( "tool" , partialMessage , block . partial , toolProgressStatus ) . catch (
1591- ( ) => { } ,
1592- )
1593- break
1594- } else {
1595- if ( ! relPath ) {
1596- this . consecutiveMistakeCount ++
1597- pushToolResult ( await this . sayAndCreateMissingParamError ( "apply_diff" , "path" ) )
1598- break
1599- }
1600- if ( ! diffContent ) {
1601- this . consecutiveMistakeCount ++
1602- pushToolResult ( await this . sayAndCreateMissingParamError ( "apply_diff" , "diff" ) )
1603- break
1604- }
1605-
1606- const accessAllowed = this . rooIgnoreController ?. validateAccess ( relPath )
1607- if ( ! accessAllowed ) {
1608- await this . say ( "rooignore_error" , relPath )
1609- pushToolResult ( formatResponse . toolError ( formatResponse . rooIgnoreError ( relPath ) ) )
1610-
1611- break
1612- }
1613-
1614- const absolutePath = path . resolve ( this . cwd , relPath )
1615- const fileExists = await fileExistsAtPath ( absolutePath )
1616-
1617- if ( ! fileExists ) {
1618- this . consecutiveMistakeCount ++
1619- const formattedError = `File does not exist at path: ${ absolutePath } \n\n<error_details>\nThe specified file could not be found. Please verify the file path and try again.\n</error_details>`
1620- await this . say ( "error" , formattedError )
1621- pushToolResult ( formattedError )
1622- break
1623- }
1624-
1625- const originalContent = await fs . readFile ( absolutePath , "utf-8" )
1626-
1627- // Apply the diff to the original content
1628- const diffResult = ( await this . diffStrategy ?. applyDiff (
1629- originalContent ,
1630- diffContent ,
1631- parseInt ( block . params . start_line ?? "" ) ,
1632- parseInt ( block . params . end_line ?? "" ) ,
1633- ) ) ?? {
1634- success : false ,
1635- error : "No diff strategy available" ,
1636- }
1637- let partResults = ""
1638-
1639- if ( ! diffResult . success ) {
1640- this . consecutiveMistakeCount ++
1641- const currentCount =
1642- ( this . consecutiveMistakeCountForApplyDiff . get ( relPath ) || 0 ) + 1
1643- this . consecutiveMistakeCountForApplyDiff . set ( relPath , currentCount )
1644- let formattedError = ""
1645- if ( diffResult . failParts && diffResult . failParts . length > 0 ) {
1646- for ( const failPart of diffResult . failParts ) {
1647- if ( failPart . success ) {
1648- continue
1649- }
1650- const errorDetails = failPart . details
1651- ? JSON . stringify ( failPart . details , null , 2 )
1652- : ""
1653- formattedError = `<error_details>\n${
1654- failPart . error
1655- } ${ errorDetails ? `\n\nDetails:\n${ errorDetails } ` : "" } \n</error_details>`
1656- partResults += formattedError
1657- }
1658- } else {
1659- const errorDetails = diffResult . details
1660- ? JSON . stringify ( diffResult . details , null , 2 )
1661- : ""
1662- formattedError = `Unable to apply diff to file: ${ absolutePath } \n\n<error_details>\n${
1663- diffResult . error
1664- } ${ errorDetails ? `\n\nDetails:\n${ errorDetails } ` : "" } \n</error_details>`
1665- }
1666-
1667- if ( currentCount >= 2 ) {
1668- await this . say ( "error" , formattedError )
1669- }
1670- pushToolResult ( formattedError )
1671- break
1672- }
1673-
1674- this . consecutiveMistakeCount = 0
1675- this . consecutiveMistakeCountForApplyDiff . delete ( relPath )
1676- // Show diff view before asking for approval
1677- this . diffViewProvider . editType = "modify"
1678- await this . diffViewProvider . open ( relPath )
1679- await this . diffViewProvider . update ( diffResult . content , true )
1680- await this . diffViewProvider . scrollToFirstDiff ( )
1681-
1682- const completeMessage = JSON . stringify ( {
1683- ...sharedMessageProps ,
1684- diff : diffContent ,
1685- } satisfies ClineSayTool )
1686-
1687- let toolProgressStatus
1688- if ( this . diffStrategy && this . diffStrategy . getProgressStatus ) {
1689- toolProgressStatus = this . diffStrategy . getProgressStatus ( block , diffResult )
1690- }
1691-
1692- const didApprove = await askApproval ( "tool" , completeMessage , toolProgressStatus )
1693- if ( ! didApprove ) {
1694- await this . diffViewProvider . revertChanges ( ) // This likely handles closing the diff view
1695- break
1696- }
1697-
1698- const { newProblemsMessage, userEdits, finalContent } =
1699- await this . diffViewProvider . saveChanges ( )
1700- this . didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request
1701- let partFailHint = ""
1702- if ( diffResult . failParts && diffResult . failParts . length > 0 ) {
1703- partFailHint = `But unable to apply all diff parts to file: ${ absolutePath } , silently use <read_file> tool to check newest file version and re-apply diffs\n`
1704- }
1705- if ( userEdits ) {
1706- await this . say (
1707- "user_feedback_diff" ,
1708- JSON . stringify ( {
1709- tool : fileExists ? "editedExistingFile" : "newFileCreated" ,
1710- path : getReadablePath ( this . cwd , relPath ) ,
1711- diff : userEdits ,
1712- } satisfies ClineSayTool ) ,
1713- )
1714- pushToolResult (
1715- `The user made the following updates to your content:\n\n${ userEdits } \n\n` +
1716- partFailHint +
1717- `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${ relPath . toPosix ( ) } . Here is the full, updated content of the file, including line numbers:\n\n` +
1718- `<final_file_content path="${ relPath . toPosix ( ) } ">\n${ addLineNumbers (
1719- finalContent || "" ,
1720- ) } \n</final_file_content>\n\n` +
1721- `Please note:\n` +
1722- `1. You do not need to re-write the file with these changes, as they have already been applied.\n` +
1723- `2. Proceed with the task using this updated file content as the new baseline.\n` +
1724- `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` +
1725- `${ newProblemsMessage } ` ,
1726- )
1727- } else {
1728- pushToolResult (
1729- `Changes successfully applied to ${ relPath . toPosix ( ) } :\n\n${ newProblemsMessage } \n` +
1730- partFailHint ,
1731- )
1732- }
1733- await this . diffViewProvider . reset ( )
1734- break
1735- }
1736- } catch ( error ) {
1737- await handleError ( "applying diff" , error )
1738- await this . diffViewProvider . reset ( )
1739- break
1740- }
1741- }
1742-
1572+ case "apply_diff" :
1573+ await applyDiffTool ( this , block , askApproval , handleError , pushToolResult , removeClosingTag )
1574+ break
17431575 case "insert_content" : {
17441576 const relPath : string | undefined = block . params . path
17451577 const operations : string | undefined = block . params . operations
0 commit comments