Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ Join us at https://www.reddit.com/r/RooCode to share your custom modes and be pa
## [2.1.14]

- 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)
- If diffs are enabled, automatically reject create_file commands that lead to truncated output
- If diffs are enabled, automatically reject write_to_file commands that lead to truncated output

## [2.1.13]

Expand Down
34 changes: 17 additions & 17 deletions src/core/Cline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,7 @@ export class Cline {
text:
`[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.${
wasRecent
? "\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."
? "\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."
: ""
}` +
(responseText
Expand Down Expand Up @@ -1141,9 +1141,9 @@ export class Cline {
return `[${block.name} for '${block.params.command}']`
case "read_file":
return `[${block.name} for '${block.params.path}']`
case "create_file":
case "write_to_file":
return `[${block.name} for '${block.params.path}']`
case "edit_file":
case "apply_diff":
return `[${block.name} for '${block.params.path}']`
case "search_files":
return `[${block.name} for '${block.params.regex}'${
Expand Down Expand Up @@ -1295,7 +1295,7 @@ export class Cline {
mode ?? defaultModeSlug,
customModes ?? [],
{
edit_file: this.diffEnabled,
apply_diff: this.diffEnabled,
},
block.params,
)
Expand All @@ -1306,7 +1306,7 @@ export class Cline {
}

switch (block.name) {
case "create_file": {
case "write_to_file": {
const relPath: string | undefined = block.params.path
let newContent: string | undefined = block.params.content
let predictedLineCount: number | undefined = parseInt(block.params.line_count ?? "0")
Expand Down Expand Up @@ -1371,20 +1371,20 @@ export class Cline {
} else {
if (!relPath) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("create_file", "path"))
pushToolResult(await this.sayAndCreateMissingParamError("write_to_file", "path"))
await this.diffViewProvider.reset()
break
}
if (!newContent) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("create_file", "content"))
pushToolResult(await this.sayAndCreateMissingParamError("write_to_file", "content"))
await this.diffViewProvider.reset()
break
}
if (!predictedLineCount) {
this.consecutiveMistakeCount++
pushToolResult(
await this.sayAndCreateMissingParamError("create_file", "line_count"),
await this.sayAndCreateMissingParamError("write_to_file", "line_count"),
)
await this.diffViewProvider.reset()
break
Expand Down Expand Up @@ -1421,7 +1421,7 @@ export class Cline {
formatResponse.toolError(
`Content appears to be truncated (file has ${
newContent.split("\n").length
} 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.`,
} 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.`,
),
)
break
Expand Down Expand Up @@ -1497,7 +1497,7 @@ export class Cline {
break
}
}
case "edit_file": {
case "apply_diff": {
const relPath: string | undefined = block.params.path
const diffContent: string | undefined = block.params.diff

Expand All @@ -1515,12 +1515,12 @@ export class Cline {
} else {
if (!relPath) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("edit_file", "path"))
pushToolResult(await this.sayAndCreateMissingParamError("apply_diff", "path"))
break
}
if (!diffContent) {
this.consecutiveMistakeCount++
pushToolResult(await this.sayAndCreateMissingParamError("edit_file", "diff"))
pushToolResult(await this.sayAndCreateMissingParamError("apply_diff", "diff"))
break
}

Expand Down Expand Up @@ -2233,7 +2233,7 @@ export class Cline {
formatResponse.toolResult(
`The browser action has been executed. The console logs and screenshot have been captured for your analysis.\n\nConsole logs:\n${
browserActionResult.logs || "(No new logs)"
}\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.)`,
}\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.)`,
browserActionResult.screenshot ? [browserActionResult.screenshot] : [],
),
)
Expand Down Expand Up @@ -2750,7 +2750,7 @@ export class Cline {

/*
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.
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.
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.
*/
this.presentAssistantMessageLocked = false // this needs to be placed here, if not then calling this.presentAssistantMessage below would fail (sometimes) since it's locked
// 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)
Expand Down Expand Up @@ -3300,10 +3300,10 @@ export class Cline {

// Add warning if not in code mode
if (
!isToolAllowedForMode("create_file", currentMode, customModes ?? [], {
edit_file: this.diffEnabled,
!isToolAllowedForMode("write_to_file", currentMode, customModes ?? [], {
apply_diff: this.diffEnabled,
}) &&
!isToolAllowedForMode("edit_file", currentMode, customModes ?? [], { edit_file: this.diffEnabled })
!isToolAllowedForMode("apply_diff", currentMode, customModes ?? [], { apply_diff: this.diffEnabled })
) {
const currentModeName = getModeBySlug(currentMode, customModes)?.name ?? currentMode
const defaultModeName = getModeBySlug(defaultModeSlug, customModes)?.name ?? defaultModeSlug
Expand Down
40 changes: 20 additions & 20 deletions src/core/__tests__/mode-validator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe("mode-validator", () => {
]
// Should allow tools from read and edit groups
expect(isToolAllowedForMode("read_file", "custom-mode", customModes)).toBe(true)
expect(isToolAllowedForMode("create_file", "custom-mode", customModes)).toBe(true)
expect(isToolAllowedForMode("write_to_file", "custom-mode", customModes)).toBe(true)
// Should not allow tools from other groups
expect(isToolAllowedForMode("execute_command", "custom-mode", customModes)).toBe(false)
})
Expand All @@ -76,7 +76,7 @@ describe("mode-validator", () => {
// Should allow tools from read group
expect(isToolAllowedForMode("read_file", codeMode, customModes)).toBe(true)
// Should not allow tools from other groups
expect(isToolAllowedForMode("create_file", codeMode, customModes)).toBe(false)
expect(isToolAllowedForMode("write_to_file", codeMode, customModes)).toBe(false)
})

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

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

// Should allow other edit tools
expect(isToolAllowedForMode("create_file", "custom-mode", customModes, requirements)).toBe(true)
expect(isToolAllowedForMode("write_to_file", "custom-mode", customModes, requirements)).toBe(true)
})
})

describe("tool requirements", () => {
it("respects tool requirements when provided", () => {
const requirements = { edit_file: false }
expect(isToolAllowedForMode("edit_file", codeMode, [], requirements)).toBe(false)
const requirements = { apply_diff: false }
expect(isToolAllowedForMode("apply_diff", codeMode, [], requirements)).toBe(false)

const enabledRequirements = { edit_file: true }
expect(isToolAllowedForMode("edit_file", codeMode, [], enabledRequirements)).toBe(true)
const enabledRequirements = { apply_diff: true }
expect(isToolAllowedForMode("apply_diff", codeMode, [], enabledRequirements)).toBe(true)
})

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

it("handles undefined and empty requirements", () => {
expect(isToolAllowedForMode("edit_file", codeMode, [], undefined)).toBe(true)
expect(isToolAllowedForMode("edit_file", codeMode, [], {})).toBe(true)
expect(isToolAllowedForMode("apply_diff", codeMode, [], undefined)).toBe(true)
expect(isToolAllowedForMode("apply_diff", codeMode, [], {})).toBe(true)
})

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

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

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

it("handles undefined requirements gracefully", () => {
expect(() => validateToolUse("edit_file", codeMode, [], undefined)).not.toThrow()
expect(() => validateToolUse("apply_diff", codeMode, [], undefined)).not.toThrow()
})
})
})
6 changes: 3 additions & 3 deletions src/core/assistant-message/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export interface TextContent {
export const toolUseNames = [
"execute_command",
"read_file",
"create_file",
"edit_file",
"write_to_file",
"apply_diff",
"insert_content",
"search_and_replace",
"search_files",
Expand Down Expand Up @@ -80,7 +80,7 @@ export interface ReadFileToolUse extends ToolUse {
}

export interface WriteToFileToolUse extends ToolUse {
name: "create_file"
name: "write_to_file"
params: Partial<Pick<Record<ToolParamName, string>, "path" | "content" | "line_count">>
}

Expand Down
4 changes: 2 additions & 2 deletions src/core/assistant-message/parse-assistant-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ export function parseAssistantMessage(assistantMessage: string) {

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

// 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.
// 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.
const contentParamName: ToolParamName = "content"
if (currentToolUse.name === "create_file" && accumulator.endsWith(`</${contentParamName}>`)) {
if (currentToolUse.name === "write_to_file" && accumulator.endsWith(`</${contentParamName}>`)) {
const toolContent = accumulator.slice(currentToolUseStartIndex)
const contentStartTag = `<${contentParamName}>`
const contentEndTag = `</${contentParamName}>`
Expand Down
2 changes: 1 addition & 1 deletion src/core/diff/strategies/__tests__/new-unified.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe("main", () => {
const cwd = "/test/path"
const description = strategy.getToolDescription({ cwd })

expect(description).toContain("edit_file Tool - Generate Precise Code Changes")
expect(description).toContain("apply_diff Tool - Generate Precise Code Changes")
expect(description).toContain(cwd)
expect(description).toContain("Step-by-Step Instructions")
expect(description).toContain("Requirements")
Expand Down
4 changes: 2 additions & 2 deletions src/core/diff/strategies/__tests__/search-replace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1544,8 +1544,8 @@ function two() {
expect(description).toContain("<<<<<<< SEARCH")
expect(description).toContain("=======")
expect(description).toContain(">>>>>>> REPLACE")
expect(description).toContain("<edit_file>")
expect(description).toContain("</edit_file>")
expect(description).toContain("<apply_diff>")
expect(description).toContain("</apply_diff>")
})

it("should document start_line and end_line parameters", async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/core/diff/strategies/__tests__/unified.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe("UnifiedDiffStrategy", () => {
const cwd = "/test/path"
const description = strategy.getToolDescription({ cwd })

expect(description).toContain("edit_file")
expect(description).toContain("apply_diff")
expect(description).toContain(cwd)
expect(description).toContain("Parameters:")
expect(description).toContain("Format Requirements:")
Expand Down
6 changes: 3 additions & 3 deletions src/core/diff/strategies/new-unified/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export class NewUnifiedDiffStrategy implements DiffStrategy {
}

getToolDescription(args: { cwd: string; toolOptions?: { [key: string]: string } }): string {
return `# edit_file Tool - Generate Precise Code Changes
return `# apply_diff Tool - Generate Precise Code Changes

Generate a unified diff that can be cleanly applied to modify code files.

Expand Down Expand Up @@ -168,12 +168,12 @@ Parameters:
- diff: (required) Unified diff content in unified format to apply to the file.

Usage:
<edit_file>
<apply_diff>
<path>path/to/file.ext</path>
<diff>
Your diff here
</diff>
</edit_file>`
</apply_diff>`
}

// Helper function to split a hunk into smaller hunks based on contiguous changes
Expand Down
6 changes: 3 additions & 3 deletions src/core/diff/strategies/search-replace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class SearchReplaceDiffStrategy implements DiffStrategy {
}

getToolDescription(args: { cwd: string; toolOptions?: { [key: string]: string } }): string {
return `## edit_file
return `## apply_diff
Description: Request to replace existing code using a search and replace block.
This tool allows for precise, surgical replaces to files by specifying exactly what content to search for and what to replace it with.
The tool will maintain proper indentation and formatting while making changes.
Expand Down Expand Up @@ -91,14 +91,14 @@ def calculate_total(items):
\`\`\`

Usage:
<edit_file>
<apply_diff>
<path>File path here</path>
<diff>
Your search/replace content here
</diff>
<start_line>1</start_line>
<end_line>5</end_line>
</edit_file>`
</apply_diff>`
}

async applyDiff(
Expand Down
6 changes: 3 additions & 3 deletions src/core/diff/strategies/unified.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DiffStrategy, DiffResult } from "../types"

export class UnifiedDiffStrategy implements DiffStrategy {
getToolDescription(args: { cwd: string; toolOptions?: { [key: string]: string } }): string {
return `## edit_file
return `## apply_diff
Description: Apply a unified diff to a file at the specified path. This tool is useful when you need to make specific modifications to a file based on a set of changes provided in unified diff format (diff -U3).

Parameters:
Expand Down Expand Up @@ -100,12 +100,12 @@ Best Practices:
4. Verify line numbers match the line numbers you have in the file

Usage:
<edit_file>
<apply_diff>
<path>File path here</path>
<diff>
Your diff here
</diff>
</edit_file>`
</apply_diff>`
}

async applyDiff(originalContent: string, diffContent: string): Promise<DiffResult> {
Expand Down
Loading
Loading