Skip to content

Commit dddac90

Browse files
authored
Merge pull request RooCodeInc#1125 from hannesrudolph/change_order_apply_diff
Improved diff editing use consistency
2 parents 1c9dcad + 6225994 commit dddac90

File tree

25 files changed

+285
-280
lines changed

25 files changed

+285
-280
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ Join us at https://www.reddit.com/r/RooCode to share your custom modes and be pa
479479
## [2.1.14]
480480

481481
- Fix bug where diffs were not being applied correctly and try Aider's [unified diff prompt](https://github.com/Aider-AI/aider/blob/3995accd0ca71cea90ef76d516837f8c2731b9fe/aider/coders/udiff_prompts.py#L75-L105)
482-
- If diffs are enabled, automatically reject write_to_file commands that lead to truncated output
482+
- If diffs are enabled, automatically reject create_file commands that lead to truncated output
483483

484484
## [2.1.13]
485485

src/api/transform/o1-format.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,13 @@ Description: Perform a regex search across files in a specified directory, provi
6767
</read_file>
6868
Description: Read the contents of a file at the specified path. Use this when you need to examine the contents of an existing file, for example to analyze code, review text files, or extract information from configuration files. Automatically extracts raw text from PDF and DOCX files. May not be suitable for other types of binary files, as it returns the raw content as a string.
6969
70-
6. write_to_file:
71-
<write_to_file>
70+
6. create_file:
71+
<create_file>
7272
<path>File path here</path>
7373
<content>
7474
Your file content here
7575
</content>
76-
</write_to_file>
76+
</create_file>
7777
Description: Write content to a file at the specified path. If the file exists, it will be overwritten with the provided content. If the file doesn't exist, it will be created. Always provide the full intended content of the file, without any truncation. This tool will automatically create any directories needed to write the file.
7878
7979
7. ask_followup_question:
@@ -107,7 +107,7 @@ Example 2: Using multiple tools
107107
108108
Let's create two new configuration files for the web application: one for the frontend and one for the backend.
109109
110-
<write_to_file>
110+
<create_file>
111111
<path>./frontend-config.json</path>
112112
<content>
113113
{
@@ -125,9 +125,9 @@ Let's create two new configuration files for the web application: one for the fr
125125
"version": "1.0.0"
126126
}
127127
</content>
128-
</write_to_file>
128+
</create_file>
129129
130-
<write_to_file>
130+
<create_file>
131131
<path>./backend-config.yaml</path>
132132
<content>
133133
database:
@@ -154,7 +154,7 @@ externalServices:
154154
emailProvider: sendgrid
155155
storageProvider: aws-s3
156156
</content>
157-
</write_to_file>
157+
</create_file>
158158
159159
Example 3: Asking a follow-up question
160160
@@ -239,7 +239,7 @@ const toolNames = [
239239
"list_code_definition_names",
240240
"search_files",
241241
"read_file",
242-
"write_to_file",
242+
"create_file",
243243
"ask_followup_question",
244244
"attempt_completion",
245245
]
@@ -331,7 +331,7 @@ function validateToolInput(toolName: string, tool_input: Record<string, string>)
331331
return "path" in tool_input
332332
case "search_files":
333333
return "path" in tool_input && "regex" in tool_input
334-
case "write_to_file":
334+
case "create_file":
335335
return "path" in tool_input && "content" in tool_input
336336
case "ask_followup_question":
337337
return "question" in tool_input
@@ -349,10 +349,10 @@ function validateToolInput(toolName: string, tool_input: Record<string, string>)
349349
// <command>ls -la</command>
350350
// </execute_command>
351351

352-
// <write_to_file>
352+
// <create_file>
353353
// <path>./example.txt</path>
354354
// <content>Hello, World!</content>
355-
// </write_to_file>`;
355+
// </create_file>`;
356356
//
357357
// const { normalText, toolCalls } = parseAIResponse(aiResponse);
358358
// console.log(normalText);

src/core/Cline.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,7 @@ export class Cline {
698698
text:
699699
`[TASK RESUMPTION] This task was interrupted ${agoText}. It may or may not be complete, so please reassess the task context. Be aware that the project state may have changed since then. The current working directory is now '${cwd.toPosix()}'. If the task has not been completed, retry the last step before interruption and proceed with completing the task.\n\nNote: If you previously attempted a tool use that the user did not provide a result for, you should assume the tool use was not successful and assess whether you should retry. If the last tool was a browser_action, the browser has been closed and you must launch a new browser if needed.${
700700
wasRecent
701-
? "\n\nIMPORTANT: If the last tool use was a write_to_file that was interrupted, the file was reverted back to its original state before the interrupted edit, and you do NOT need to re-read the file as you already have its up-to-date contents."
701+
? "\n\nIMPORTANT: If the last tool use was a create_file that was interrupted, the file was reverted back to its original state before the interrupted edit, and you do NOT need to re-read the file as you already have its up-to-date contents."
702702
: ""
703703
}` +
704704
(responseText
@@ -1102,9 +1102,9 @@ export class Cline {
11021102
return `[${block.name} for '${block.params.command}']`
11031103
case "read_file":
11041104
return `[${block.name} for '${block.params.path}']`
1105-
case "write_to_file":
1105+
case "create_file":
11061106
return `[${block.name} for '${block.params.path}']`
1107-
case "apply_diff":
1107+
case "edit_file":
11081108
return `[${block.name} for '${block.params.path}']`
11091109
case "search_files":
11101110
return `[${block.name} for '${block.params.regex}'${
@@ -1256,7 +1256,7 @@ export class Cline {
12561256
mode ?? defaultModeSlug,
12571257
customModes ?? [],
12581258
{
1259-
apply_diff: this.diffEnabled,
1259+
edit_file: this.diffEnabled,
12601260
},
12611261
block.params,
12621262
)
@@ -1267,7 +1267,7 @@ export class Cline {
12671267
}
12681268

12691269
switch (block.name) {
1270-
case "write_to_file": {
1270+
case "create_file": {
12711271
const relPath: string | undefined = block.params.path
12721272
let newContent: string | undefined = block.params.content
12731273
let predictedLineCount: number | undefined = parseInt(block.params.line_count ?? "0")
@@ -1332,20 +1332,20 @@ export class Cline {
13321332
} else {
13331333
if (!relPath) {
13341334
this.consecutiveMistakeCount++
1335-
pushToolResult(await this.sayAndCreateMissingParamError("write_to_file", "path"))
1335+
pushToolResult(await this.sayAndCreateMissingParamError("create_file", "path"))
13361336
await this.diffViewProvider.reset()
13371337
break
13381338
}
13391339
if (!newContent) {
13401340
this.consecutiveMistakeCount++
1341-
pushToolResult(await this.sayAndCreateMissingParamError("write_to_file", "content"))
1341+
pushToolResult(await this.sayAndCreateMissingParamError("create_file", "content"))
13421342
await this.diffViewProvider.reset()
13431343
break
13441344
}
13451345
if (!predictedLineCount) {
13461346
this.consecutiveMistakeCount++
13471347
pushToolResult(
1348-
await this.sayAndCreateMissingParamError("write_to_file", "line_count"),
1348+
await this.sayAndCreateMissingParamError("create_file", "line_count"),
13491349
)
13501350
await this.diffViewProvider.reset()
13511351
break
@@ -1382,7 +1382,7 @@ export class Cline {
13821382
formatResponse.toolError(
13831383
`Content appears to be truncated (file has ${
13841384
newContent.split("\n").length
1385-
} lines but was predicted to have ${predictedLineCount} lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`,
1385+
} lines but was predicted to have ${predictedLineCount} lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'edit_file' tool to apply the diff to the original file.`,
13861386
),
13871387
)
13881388
break
@@ -1458,7 +1458,7 @@ export class Cline {
14581458
break
14591459
}
14601460
}
1461-
case "apply_diff": {
1461+
case "edit_file": {
14621462
const relPath: string | undefined = block.params.path
14631463
const diffContent: string | undefined = block.params.diff
14641464

@@ -1476,12 +1476,12 @@ export class Cline {
14761476
} else {
14771477
if (!relPath) {
14781478
this.consecutiveMistakeCount++
1479-
pushToolResult(await this.sayAndCreateMissingParamError("apply_diff", "path"))
1479+
pushToolResult(await this.sayAndCreateMissingParamError("edit_file", "path"))
14801480
break
14811481
}
14821482
if (!diffContent) {
14831483
this.consecutiveMistakeCount++
1484-
pushToolResult(await this.sayAndCreateMissingParamError("apply_diff", "diff"))
1484+
pushToolResult(await this.sayAndCreateMissingParamError("edit_file", "diff"))
14851485
break
14861486
}
14871487

@@ -2194,7 +2194,7 @@ export class Cline {
21942194
formatResponse.toolResult(
21952195
`The browser action has been executed. The console logs and screenshot have been captured for your analysis.\n\nConsole logs:\n${
21962196
browserActionResult.logs || "(No new logs)"
2197-
}\n\n(REMEMBER: if you need to proceed to using non-\`browser_action\` tools or launch a new browser, you MUST first close this browser. For example, if after analyzing the logs and screenshot you need to edit a file, you must first close the browser before you can use the write_to_file tool.)`,
2197+
}\n\n(REMEMBER: if you need to proceed to using non-\`browser_action\` tools or launch a new browser, you MUST first close this browser. For example, if after analyzing the logs and screenshot you need to edit a file, you must first close the browser before you can use the create_file tool.)`,
21982198
browserActionResult.screenshot ? [browserActionResult.screenshot] : [],
21992199
),
22002200
)
@@ -2711,7 +2711,7 @@ export class Cline {
27112711

27122712
/*
27132713
Seeing out of bounds is fine, it means that the next too call is being built up and ready to add to assistantMessageContent to present.
2714-
When you see the UI inactive during this, it means that a tool is breaking without presenting any UI. For example the write_to_file tool was breaking when relpath was undefined, and for invalid relpath it never presented UI.
2714+
When you see the UI inactive during this, it means that a tool is breaking without presenting any UI. For example the create_file tool was breaking when relpath was undefined, and for invalid relpath it never presented UI.
27152715
*/
27162716
this.presentAssistantMessageLocked = false // this needs to be placed here, if not then calling this.presentAssistantMessage below would fail (sometimes) since it's locked
27172717
// NOTE: when tool is rejected, iterator stream is interrupted and it waits for userMessageContentReady to be true. Future calls to present will skip execution since didRejectTool and iterate until contentIndex is set to message length and it sets userMessageContentReady to true itself (instead of preemptively doing it in iterator)
@@ -3261,10 +3261,10 @@ export class Cline {
32613261

32623262
// Add warning if not in code mode
32633263
if (
3264-
!isToolAllowedForMode("write_to_file", currentMode, customModes ?? [], {
3265-
apply_diff: this.diffEnabled,
3264+
!isToolAllowedForMode("create_file", currentMode, customModes ?? [], {
3265+
edit_file: this.diffEnabled,
32663266
}) &&
3267-
!isToolAllowedForMode("apply_diff", currentMode, customModes ?? [], { apply_diff: this.diffEnabled })
3267+
!isToolAllowedForMode("edit_file", currentMode, customModes ?? [], { edit_file: this.diffEnabled })
32683268
) {
32693269
const currentModeName = getModeBySlug(currentMode, customModes)?.name ?? currentMode
32703270
const defaultModeName = getModeBySlug(defaultModeSlug, customModes)?.name ?? defaultModeSlug

src/core/__tests__/mode-validator.test.ts

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe("mode-validator", () => {
5959
]
6060
// Should allow tools from read and edit groups
6161
expect(isToolAllowedForMode("read_file", "custom-mode", customModes)).toBe(true)
62-
expect(isToolAllowedForMode("write_to_file", "custom-mode", customModes)).toBe(true)
62+
expect(isToolAllowedForMode("create_file", "custom-mode", customModes)).toBe(true)
6363
// Should not allow tools from other groups
6464
expect(isToolAllowedForMode("execute_command", "custom-mode", customModes)).toBe(false)
6565
})
@@ -76,7 +76,7 @@ describe("mode-validator", () => {
7676
// Should allow tools from read group
7777
expect(isToolAllowedForMode("read_file", codeMode, customModes)).toBe(true)
7878
// Should not allow tools from other groups
79-
expect(isToolAllowedForMode("write_to_file", codeMode, customModes)).toBe(false)
79+
expect(isToolAllowedForMode("create_file", codeMode, customModes)).toBe(false)
8080
})
8181

8282
it("respects tool requirements in custom modes", () => {
@@ -88,39 +88,39 @@ describe("mode-validator", () => {
8888
groups: ["edit"] as const,
8989
},
9090
]
91-
const requirements = { apply_diff: false }
91+
const requirements = { edit_file: false }
9292

9393
// Should respect disabled requirement even if tool group is allowed
94-
expect(isToolAllowedForMode("apply_diff", "custom-mode", customModes, requirements)).toBe(false)
94+
expect(isToolAllowedForMode("edit_file", "custom-mode", customModes, requirements)).toBe(false)
9595

9696
// Should allow other edit tools
97-
expect(isToolAllowedForMode("write_to_file", "custom-mode", customModes, requirements)).toBe(true)
97+
expect(isToolAllowedForMode("create_file", "custom-mode", customModes, requirements)).toBe(true)
9898
})
9999
})
100100

101101
describe("tool requirements", () => {
102102
it("respects tool requirements when provided", () => {
103-
const requirements = { apply_diff: false }
104-
expect(isToolAllowedForMode("apply_diff", codeMode, [], requirements)).toBe(false)
103+
const requirements = { edit_file: false }
104+
expect(isToolAllowedForMode("edit_file", codeMode, [], requirements)).toBe(false)
105105

106-
const enabledRequirements = { apply_diff: true }
107-
expect(isToolAllowedForMode("apply_diff", codeMode, [], enabledRequirements)).toBe(true)
106+
const enabledRequirements = { edit_file: true }
107+
expect(isToolAllowedForMode("edit_file", codeMode, [], enabledRequirements)).toBe(true)
108108
})
109109

110110
it("allows tools when their requirements are not specified", () => {
111111
const requirements = { some_other_tool: true }
112-
expect(isToolAllowedForMode("apply_diff", codeMode, [], requirements)).toBe(true)
112+
expect(isToolAllowedForMode("edit_file", codeMode, [], requirements)).toBe(true)
113113
})
114114

115115
it("handles undefined and empty requirements", () => {
116-
expect(isToolAllowedForMode("apply_diff", codeMode, [], undefined)).toBe(true)
117-
expect(isToolAllowedForMode("apply_diff", codeMode, [], {})).toBe(true)
116+
expect(isToolAllowedForMode("edit_file", codeMode, [], undefined)).toBe(true)
117+
expect(isToolAllowedForMode("edit_file", codeMode, [], {})).toBe(true)
118118
})
119119

120120
it("prioritizes requirements over mode configuration", () => {
121-
const requirements = { apply_diff: false }
121+
const requirements = { edit_file: false }
122122
// Even in code mode which allows all tools, disabled requirement should take precedence
123-
expect(isToolAllowedForMode("apply_diff", codeMode, [], requirements)).toBe(false)
123+
expect(isToolAllowedForMode("edit_file", codeMode, [], requirements)).toBe(false)
124124
})
125125
})
126126
})
@@ -137,19 +137,19 @@ describe("mode-validator", () => {
137137
})
138138

139139
it("throws error when tool requirement is not met", () => {
140-
const requirements = { apply_diff: false }
141-
expect(() => validateToolUse("apply_diff", codeMode, [], requirements)).toThrow(
142-
'Tool "apply_diff" is not allowed in code mode.',
140+
const requirements = { edit_file: false }
141+
expect(() => validateToolUse("edit_file", codeMode, [], requirements)).toThrow(
142+
'Tool "edit_file" is not allowed in code mode.',
143143
)
144144
})
145145

146146
it("does not throw when tool requirement is met", () => {
147-
const requirements = { apply_diff: true }
148-
expect(() => validateToolUse("apply_diff", codeMode, [], requirements)).not.toThrow()
147+
const requirements = { edit_file: true }
148+
expect(() => validateToolUse("edit_file", codeMode, [], requirements)).not.toThrow()
149149
})
150150

151151
it("handles undefined requirements gracefully", () => {
152-
expect(() => validateToolUse("apply_diff", codeMode, [], undefined)).not.toThrow()
152+
expect(() => validateToolUse("edit_file", codeMode, [], undefined)).not.toThrow()
153153
})
154154
})
155155
})

src/core/assistant-message/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export interface TextContent {
1111
export const toolUseNames = [
1212
"execute_command",
1313
"read_file",
14-
"write_to_file",
15-
"apply_diff",
14+
"create_file",
15+
"edit_file",
1616
"insert_content",
1717
"search_and_replace",
1818
"search_files",
@@ -80,7 +80,7 @@ export interface ReadFileToolUse extends ToolUse {
8080
}
8181

8282
export interface WriteToFileToolUse extends ToolUse {
83-
name: "write_to_file"
83+
name: "create_file"
8484
params: Partial<Pick<Record<ToolParamName, string>, "path" | "content" | "line_count">>
8585
}
8686

src/core/assistant-message/parse-assistant-message.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ export function parseAssistantMessage(assistantMessage: string) {
6161

6262
// there's no current param, and not starting a new param
6363

64-
// special case for write_to_file where file contents could contain the closing tag, in which case the param would have closed and we end up with the rest of the file contents here. To work around this, we get the string between the starting content tag and the LAST content tag.
64+
// special case for create_file where file contents could contain the closing tag, in which case the param would have closed and we end up with the rest of the file contents here. To work around this, we get the string between the starting content tag and the LAST content tag.
6565
const contentParamName: ToolParamName = "content"
66-
if (currentToolUse.name === "write_to_file" && accumulator.endsWith(`</${contentParamName}>`)) {
66+
if (currentToolUse.name === "create_file" && accumulator.endsWith(`</${contentParamName}>`)) {
6767
const toolContent = accumulator.slice(currentToolUseStartIndex)
6868
const contentStartTag = `<${contentParamName}>`
6969
const contentEndTag = `</${contentParamName}>`

src/core/diff/strategies/__tests__/new-unified.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe("main", () => {
2929
const cwd = "/test/path"
3030
const description = strategy.getToolDescription({ cwd })
3131

32-
expect(description).toContain("apply_diff Tool - Generate Precise Code Changes")
32+
expect(description).toContain("edit_file Tool - Generate Precise Code Changes")
3333
expect(description).toContain(cwd)
3434
expect(description).toContain("Step-by-Step Instructions")
3535
expect(description).toContain("Requirements")

src/core/diff/strategies/__tests__/search-replace.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1544,8 +1544,8 @@ function two() {
15441544
expect(description).toContain("<<<<<<< SEARCH")
15451545
expect(description).toContain("=======")
15461546
expect(description).toContain(">>>>>>> REPLACE")
1547-
expect(description).toContain("<apply_diff>")
1548-
expect(description).toContain("</apply_diff>")
1547+
expect(description).toContain("<edit_file>")
1548+
expect(description).toContain("</edit_file>")
15491549
})
15501550

15511551
it("should document start_line and end_line parameters", async () => {

src/core/diff/strategies/__tests__/unified.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe("UnifiedDiffStrategy", () => {
1212
const cwd = "/test/path"
1313
const description = strategy.getToolDescription({ cwd })
1414

15-
expect(description).toContain("apply_diff")
15+
expect(description).toContain("edit_file")
1616
expect(description).toContain(cwd)
1717
expect(description).toContain("Parameters:")
1818
expect(description).toContain("Format Requirements:")

src/core/diff/strategies/new-unified/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export class NewUnifiedDiffStrategy implements DiffStrategy {
108108
}
109109

110110
getToolDescription(args: { cwd: string; toolOptions?: { [key: string]: string } }): string {
111-
return `# apply_diff Tool - Generate Precise Code Changes
111+
return `# edit_file Tool - Generate Precise Code Changes
112112
113113
Generate a unified diff that can be cleanly applied to modify code files.
114114
@@ -168,12 +168,12 @@ Parameters:
168168
- diff: (required) Unified diff content in unified format to apply to the file.
169169
170170
Usage:
171-
<apply_diff>
171+
<edit_file>
172172
<path>path/to/file.ext</path>
173173
<diff>
174174
Your diff here
175175
</diff>
176-
</apply_diff>`
176+
</edit_file>`
177177
}
178178

179179
// Helper function to split a hunk into smaller hunks based on contiguous changes

0 commit comments

Comments
 (0)