mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
feat: add optional Morph Fast Apply tool for improved diff accuracy
- Add morph_fast_apply tool that uses Morph API for 98% diff accuracy - Tool is conditionally available when morphApiKey is configured - Add morphApiKey to provider settings for API authentication - Integrate tool into presentAssistantMessage and system prompt - Tool provides fallback to standard apply_diff when API key not set Fixes #7206
This commit is contained in:
parent
87c42c1f26
commit
46e979c289
7 changed files with 273 additions and 1 deletions
|
|
@ -91,6 +91,9 @@ const baseProviderSettingsSchema = z.object({
|
|||
|
||||
// Model verbosity.
|
||||
verbosity: verbosityLevelsSchema.optional(),
|
||||
|
||||
// Morph Fast Apply API key
|
||||
morphApiKey: z.string().optional(),
|
||||
})
|
||||
|
||||
// Several of the providers share common model config properties.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export const toolNames = [
|
|||
"read_file",
|
||||
"write_to_file",
|
||||
"apply_diff",
|
||||
"morph_fast_apply",
|
||||
"insert_content",
|
||||
"search_and_replace",
|
||||
"search_files",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { listFilesTool } from "../tools/listFilesTool"
|
|||
import { getReadFileToolDescription, readFileTool } from "../tools/readFileTool"
|
||||
import { writeToFileTool } from "../tools/writeToFileTool"
|
||||
import { applyDiffTool } from "../tools/multiApplyDiffTool"
|
||||
import { morphFastApplyTool } from "../tools/morphFastApplyTool"
|
||||
import { insertContentTool } from "../tools/insertContentTool"
|
||||
import { searchAndReplaceTool } from "../tools/searchAndReplaceTool"
|
||||
import { listCodeDefinitionNamesTool } from "../tools/listCodeDefinitionNamesTool"
|
||||
|
|
@ -179,6 +180,8 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
}
|
||||
}
|
||||
return `[${block.name}]`
|
||||
case "morph_fast_apply":
|
||||
return `[${block.name} for '${block.params.path}']`
|
||||
case "search_files":
|
||||
return `[${block.name} for '${block.params.regex}'${
|
||||
block.params.file_pattern ? ` in '${block.params.file_pattern}'` : ""
|
||||
|
|
@ -445,6 +448,10 @@ export async function presentAssistantMessage(cline: Task) {
|
|||
}
|
||||
break
|
||||
}
|
||||
case "morph_fast_apply":
|
||||
await checkpointSaveAndMark(cline)
|
||||
await morphFastApplyTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
|
||||
break
|
||||
case "insert_content":
|
||||
await checkpointSaveAndMark(cline)
|
||||
await insertContentTool(cline, block, askApproval, handleError, pushToolResult, removeClosingTag)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { getSwitchModeDescription } from "./switch-mode"
|
|||
import { getNewTaskDescription } from "./new-task"
|
||||
import { getCodebaseSearchDescription } from "./codebase-search"
|
||||
import { getUpdateTodoListDescription } from "./update-todo-list"
|
||||
import { getMorphFastApplyDescription } from "./morph-fast-apply"
|
||||
import { CodeIndexManager } from "../../../services/code-index/manager"
|
||||
|
||||
// Map of tool names to their description functions
|
||||
|
|
@ -46,6 +47,7 @@ const toolDescriptionMap: Record<string, (args: ToolArgs) => string | undefined>
|
|||
search_and_replace: (args) => getSearchAndReplaceDescription(args),
|
||||
apply_diff: (args) =>
|
||||
args.diffStrategy ? args.diffStrategy.getToolDescription({ cwd: args.cwd, toolOptions: args.toolOptions }) : "",
|
||||
morph_fast_apply: (args) => getMorphFastApplyDescription(args),
|
||||
update_todo_list: (args) => getUpdateTodoListDescription(args),
|
||||
}
|
||||
|
||||
|
|
|
|||
51
src/core/prompts/tools/morph-fast-apply.ts
Normal file
51
src/core/prompts/tools/morph-fast-apply.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { ToolArgs } from "./types"
|
||||
|
||||
export function getMorphFastApplyDescription(args: ToolArgs): string | undefined {
|
||||
// Only show this tool if Morph API key is configured
|
||||
if (!args.settings?.morphApiKey) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return `## morph_fast_apply
|
||||
Description: Use Morph Fast Apply to apply diffs with 98% accuracy. This tool uses Morph's advanced AI model to intelligently apply changes to files, handling complex edits that might fail with standard diff application. Only available when Morph API key is configured.
|
||||
|
||||
Parameters:
|
||||
- path: (required) The path of the file to modify (relative to the current workspace directory ${args.cwd})
|
||||
- diff: (required) The diff content in SEARCH/REPLACE format
|
||||
|
||||
Diff format:
|
||||
\`\`\`
|
||||
<<<<<<< SEARCH
|
||||
[exact content to find including whitespace]
|
||||
=======
|
||||
[new content to replace with]
|
||||
>>>>>>> REPLACE
|
||||
\`\`\`
|
||||
|
||||
Usage:
|
||||
<morph_fast_apply>
|
||||
<path>File path here</path>
|
||||
<diff>
|
||||
Your search/replace content here
|
||||
</diff>
|
||||
</morph_fast_apply>
|
||||
|
||||
Example:
|
||||
<morph_fast_apply>
|
||||
<path>src/utils.ts</path>
|
||||
<diff>
|
||||
<<<<<<< SEARCH
|
||||
function calculateTotal(items) {
|
||||
total = 0
|
||||
for item in items:
|
||||
total += item
|
||||
return total
|
||||
=======
|
||||
function calculateTotal(items) {
|
||||
return items.reduce((sum, item) => sum + item, 0)
|
||||
>>>>>>> REPLACE
|
||||
</diff>
|
||||
</morph_fast_apply>
|
||||
|
||||
Note: This tool provides higher accuracy than standard apply_diff, especially for complex code transformations and when dealing with formatting variations.`
|
||||
}
|
||||
207
src/core/tools/morphFastApplyTool.ts
Normal file
207
src/core/tools/morphFastApplyTool.ts
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
||||
import { ClineSayTool } from "../../shared/ExtensionMessage"
|
||||
import { getReadablePath } from "../../utils/path"
|
||||
import { Task } from "../task/Task"
|
||||
import { ToolUse, RemoveClosingTag, AskApproval, HandleError, PushToolResult } from "../../shared/tools"
|
||||
import { formatResponse } from "../prompts/responses"
|
||||
import { fileExistsAtPath } from "../../utils/fs"
|
||||
import { RecordSource } from "../context-tracking/FileContextTrackerTypes"
|
||||
|
||||
interface MorphApiResponse {
|
||||
success: boolean
|
||||
content?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the Morph Fast Apply API to apply diffs with high accuracy
|
||||
* @param apiKey The Morph API key
|
||||
* @param originalContent The original file content
|
||||
* @param diffContent The diff content to apply
|
||||
* @returns The result from the Morph API
|
||||
*/
|
||||
async function callMorphApi(apiKey: string, originalContent: string, diffContent: string): Promise<MorphApiResponse> {
|
||||
try {
|
||||
// Using native fetch API (available in Node.js 18+)
|
||||
const response = await fetch("https://api.morph.so/v1/fast-apply", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
original: originalContent,
|
||||
diff: diffContent,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
return {
|
||||
success: false,
|
||||
error: `Morph API error (${response.status}): ${errorText}`,
|
||||
}
|
||||
}
|
||||
|
||||
const data = (await response.json()) as any
|
||||
return {
|
||||
success: true,
|
||||
content: data.result || data.content,
|
||||
}
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to call Morph API: ${error.message}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function morphFastApplyTool(
|
||||
cline: Task,
|
||||
block: ToolUse,
|
||||
askApproval: AskApproval,
|
||||
handleError: HandleError,
|
||||
pushToolResult: PushToolResult,
|
||||
removeClosingTag: RemoveClosingTag,
|
||||
) {
|
||||
const relPath: string | undefined = block.params.path
|
||||
let diffContent: string | undefined = block.params.diff
|
||||
|
||||
const sharedMessageProps: ClineSayTool = {
|
||||
tool: "appliedDiff",
|
||||
path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)),
|
||||
diff: diffContent,
|
||||
}
|
||||
|
||||
try {
|
||||
if (block.partial) {
|
||||
// Update GUI message for partial blocks
|
||||
await cline.ask("tool", JSON.stringify(sharedMessageProps), block.partial).catch(() => {})
|
||||
return
|
||||
}
|
||||
|
||||
if (!relPath) {
|
||||
cline.consecutiveMistakeCount++
|
||||
cline.recordToolError("morph_fast_apply")
|
||||
pushToolResult(await cline.sayAndCreateMissingParamError("morph_fast_apply", "path"))
|
||||
return
|
||||
}
|
||||
|
||||
if (!diffContent) {
|
||||
cline.consecutiveMistakeCount++
|
||||
cline.recordToolError("morph_fast_apply")
|
||||
pushToolResult(await cline.sayAndCreateMissingParamError("morph_fast_apply", "diff"))
|
||||
return
|
||||
}
|
||||
|
||||
// Check if file access is allowed
|
||||
const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath)
|
||||
if (!accessAllowed) {
|
||||
await cline.say("rooignore_error", relPath)
|
||||
pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath)))
|
||||
return
|
||||
}
|
||||
|
||||
const absolutePath = path.resolve(cline.cwd, relPath)
|
||||
const fileExists = await fileExistsAtPath(absolutePath)
|
||||
|
||||
if (!fileExists) {
|
||||
cline.consecutiveMistakeCount++
|
||||
cline.recordToolError("morph_fast_apply")
|
||||
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>`
|
||||
await cline.say("error", formattedError)
|
||||
pushToolResult(formattedError)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the Morph API key from provider state
|
||||
const provider = cline.providerRef.deref()
|
||||
const state = await provider?.getState()
|
||||
// Check if Morph API key is configured (we'll add this to provider settings later)
|
||||
const morphApiKey = (state as any)?.morphApiKey
|
||||
|
||||
if (!morphApiKey) {
|
||||
// Morph API key not configured, fall back to regular apply_diff
|
||||
await cline.say("text", "Morph API key not configured. Falling back to standard diff application.")
|
||||
pushToolResult("Morph API key not configured. Please configure it in settings to use Morph Fast Apply.")
|
||||
return
|
||||
}
|
||||
|
||||
const originalContent = await fs.readFile(absolutePath, "utf-8")
|
||||
|
||||
// Call Morph API to apply the diff
|
||||
const morphResult = await callMorphApi(morphApiKey, originalContent, diffContent)
|
||||
|
||||
if (!morphResult.success) {
|
||||
cline.consecutiveMistakeCount++
|
||||
const currentCount = (cline.consecutiveMistakeCountForApplyDiff.get(relPath) || 0) + 1
|
||||
cline.consecutiveMistakeCountForApplyDiff.set(relPath, currentCount)
|
||||
|
||||
TelemetryService.instance.captureDiffApplicationError(cline.taskId, currentCount)
|
||||
|
||||
const formattedError = `Unable to apply diff using Morph Fast Apply: ${absolutePath}\n\n<error_details>\n${morphResult.error}\n</error_details>`
|
||||
|
||||
if (currentCount >= 2) {
|
||||
await cline.say("diff_error", formattedError)
|
||||
}
|
||||
|
||||
cline.recordToolError("morph_fast_apply", formattedError)
|
||||
pushToolResult(formattedError)
|
||||
return
|
||||
}
|
||||
|
||||
cline.consecutiveMistakeCount = 0
|
||||
cline.consecutiveMistakeCountForApplyDiff.delete(relPath)
|
||||
|
||||
// Check if file is write-protected
|
||||
const isWriteProtected = cline.rooProtectedController?.isWriteProtected(relPath) || false
|
||||
|
||||
// Show diff view and ask for approval
|
||||
cline.diffViewProvider.editType = "modify"
|
||||
await cline.diffViewProvider.open(relPath)
|
||||
await cline.diffViewProvider.update(morphResult.content!, true)
|
||||
cline.diffViewProvider.scrollToFirstDiff()
|
||||
|
||||
const completeMessage = JSON.stringify({
|
||||
...sharedMessageProps,
|
||||
diff: diffContent,
|
||||
isProtected: isWriteProtected,
|
||||
} satisfies ClineSayTool)
|
||||
|
||||
const didApprove = await askApproval("tool", completeMessage, undefined, isWriteProtected)
|
||||
|
||||
if (!didApprove) {
|
||||
await cline.diffViewProvider.revertChanges()
|
||||
return
|
||||
}
|
||||
|
||||
// Save the changes
|
||||
const diagnosticsEnabled = state?.diagnosticsEnabled ?? true
|
||||
const writeDelayMs = state?.writeDelayMs ?? 0
|
||||
await cline.diffViewProvider.saveChanges(diagnosticsEnabled, writeDelayMs)
|
||||
|
||||
// Track file edit operation
|
||||
await cline.fileContextTracker.trackFileContext(relPath, "roo_edited" as RecordSource)
|
||||
|
||||
cline.didEditFile = true
|
||||
|
||||
// Get the formatted response message
|
||||
const message = await cline.diffViewProvider.pushToolWriteResult(cline, cline.cwd, !fileExists)
|
||||
|
||||
// Add success notice about using Morph
|
||||
const morphNotice = "\n<notice>Successfully applied diff using Morph Fast Apply (98% accuracy)</notice>"
|
||||
pushToolResult(message + morphNotice)
|
||||
|
||||
await cline.diffViewProvider.reset()
|
||||
|
||||
// Track successful Morph usage
|
||||
TelemetryService.instance.captureToolUsage(cline.taskId, "morph_fast_apply")
|
||||
} catch (error) {
|
||||
await handleError("applying diff with Morph Fast Apply", error)
|
||||
await cline.diffViewProvider.reset()
|
||||
}
|
||||
}
|
||||
|
|
@ -176,6 +176,7 @@ export const TOOL_DISPLAY_NAMES: Record<ToolName, string> = {
|
|||
fetch_instructions: "fetch instructions",
|
||||
write_to_file: "write files",
|
||||
apply_diff: "apply changes",
|
||||
morph_fast_apply: "apply changes (Morph)",
|
||||
search_files: "search files",
|
||||
list_files: "list files",
|
||||
list_code_definition_names: "list definitions",
|
||||
|
|
@ -205,7 +206,7 @@ export const TOOL_GROUPS: Record<ToolGroup, ToolGroupConfig> = {
|
|||
],
|
||||
},
|
||||
edit: {
|
||||
tools: ["apply_diff", "write_to_file", "insert_content", "search_and_replace"],
|
||||
tools: ["apply_diff", "morph_fast_apply", "write_to_file", "insert_content", "search_and_replace"],
|
||||
},
|
||||
browser: {
|
||||
tools: ["browser_action"],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue