mirror of
https://github.com/cline/cline.git
synced 2026-09-06 20:41:02 +08:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 53de53f49d | |||
| 58605e9309 | |||
| 99a244c2da | |||
| f22f1485ad | |||
| d220f95dca | |||
| b0154750b5 | |||
| 12d952bccf | |||
| 92a25e7159 | |||
| aa2e7b758d | |||
| a38f01beb4 | |||
| be353be6c6 | |||
| b299addc53 | |||
| 06a9f7b4d9 | |||
| 1a53944eb7 | |||
| ce0f5ba08e | |||
| c3d0d06761 | |||
| 3d358f9319 |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add grep tool with new parsing format
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"claude-dev": minor
|
||||
---
|
||||
|
||||
Add edit tool definition
|
||||
@@ -1,4 +1,7 @@
|
||||
import { JSONParser } from "@streamparser/json"
|
||||
import * as fs from "fs"
|
||||
import * as path from "path"
|
||||
import * as os from "os"
|
||||
|
||||
// Fallback type definition based on the error message: "Property 'value' is optional in type 'ParsedElementInfo'"
|
||||
type ParsedElementInfo = {
|
||||
@@ -9,8 +12,8 @@ type ParsedElementInfo = {
|
||||
}
|
||||
|
||||
export interface ReplacementItem {
|
||||
old_str: string
|
||||
new_str: string
|
||||
old_string: string
|
||||
new_string: string
|
||||
}
|
||||
|
||||
export interface ChangeLocation {
|
||||
@@ -27,109 +30,243 @@ export class StreamingJsonReplacer {
|
||||
private onErrorCallback: (error: Error) => void
|
||||
private itemsProcessed: number = 0
|
||||
private successfullyParsedItems: ReplacementItem[] = []
|
||||
private logFilePath: string
|
||||
|
||||
constructor(
|
||||
initialContent: string,
|
||||
onContentUpdatedCallback: (newContent: string, isFinalItem: boolean, changeLocation?: ChangeLocation) => void,
|
||||
onErrorCallback: (error: Error) => void,
|
||||
) {
|
||||
// Initialize log file path
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-")
|
||||
this.logFilePath = path.join(os.homedir(), "Documents", `streaming-json-replacer-debug-${timestamp}.log`)
|
||||
|
||||
// Initialize log file
|
||||
this.log("StreamingJsonReplacer Debug Log Started", "INFO")
|
||||
this.log("Timestamp: " + new Date().toISOString(), "INFO")
|
||||
this.log("Constructor called with initial content length: " + initialContent.length, "INFO")
|
||||
this.log("Initial content preview: " + initialContent.substring(0, 200) + "...", "INFO")
|
||||
|
||||
this.currentFileContent = initialContent
|
||||
this.onContentUpdated = onContentUpdatedCallback
|
||||
this.onErrorCallback = onErrorCallback
|
||||
|
||||
this.parser = new JSONParser({ paths: ["$.replacements.*"] })
|
||||
this.log("Initializing JSONParser with paths: ['$.*']", "INFO")
|
||||
this.parser = new JSONParser({ paths: ["$.*"] })
|
||||
|
||||
this.parser.onValue = (parsedElementInfo: ParsedElementInfo) => {
|
||||
this.log("onValue callback triggered")
|
||||
this.log("parsedElementInfo: " + JSON.stringify(parsedElementInfo, null, 2))
|
||||
|
||||
const { value } = parsedElementInfo // Destructure to get value, which might be undefined
|
||||
this.log("Extracted value: " + JSON.stringify(value))
|
||||
this.log("Value type: " + typeof value)
|
||||
|
||||
// This callback is triggered for each item matched by '$.replacements.*'
|
||||
if (value && typeof value === "object" && "old_str" in value && "new_str" in value) {
|
||||
if (value && typeof value === "object" && "old_string" in value && "new_string" in value) {
|
||||
this.log("Found valid replacement item structure")
|
||||
const item = value as ReplacementItem // Value here is confirmed to be an object
|
||||
this.log("Replacement item: " + JSON.stringify(item, null, 2))
|
||||
|
||||
if (typeof item.old_string === "string" && typeof item.new_string === "string") {
|
||||
this.log("Item has valid string types for old_string and new_string")
|
||||
this.log("old_string length: " + item.old_string.length)
|
||||
this.log("new_string length: " + item.new_string.length)
|
||||
this.log(
|
||||
"old_string preview: " +
|
||||
(item.old_string.substring(0, 100) + (item.old_string.length > 100 ? "..." : "")),
|
||||
)
|
||||
this.log(
|
||||
"new_string preview: " +
|
||||
(item.new_string.substring(0, 100) + (item.new_string.length > 100 ? "..." : "")),
|
||||
)
|
||||
|
||||
if (typeof item.old_str === "string" && typeof item.new_str === "string") {
|
||||
this.successfullyParsedItems.push(item) // Store the structurally valid item
|
||||
this.log("Added item to successfullyParsedItems. Total count: " + this.successfullyParsedItems.length)
|
||||
|
||||
if (this.currentFileContent.includes(item.old_string)) {
|
||||
this.log("old_string found in current file content - proceeding with replacement")
|
||||
|
||||
if (this.currentFileContent.includes(item.old_str)) {
|
||||
// Calculate the change location before making the replacement
|
||||
const changeLocation = this.calculateChangeLocation(item.old_str, item.new_str)
|
||||
const changeLocation = this.calculateChangeLocation(item.old_string, item.new_string)
|
||||
this.log("Calculated change location: " + JSON.stringify(changeLocation))
|
||||
|
||||
const beforeLength = this.currentFileContent.length
|
||||
this.currentFileContent = this.currentFileContent.replace(item.old_string, item.new_string)
|
||||
const afterLength = this.currentFileContent.length
|
||||
this.log("Content length before replacement: " + beforeLength)
|
||||
this.log("Content length after replacement: " + afterLength)
|
||||
this.log("Length difference: " + (afterLength - beforeLength))
|
||||
|
||||
this.currentFileContent = this.currentFileContent.replace(item.old_str, item.new_str)
|
||||
this.itemsProcessed++
|
||||
this.log("Incremented itemsProcessed to: " + this.itemsProcessed)
|
||||
|
||||
// Notify that an item has been processed. The `isFinalItem` argument here is tricky
|
||||
// as we don't know from the parser alone if this is the *absolute* last item
|
||||
// until the stream ends. The caller (Task.ts) will manage the final update.
|
||||
// For now, we'll pass `false` and let Task.ts handle the final diff view update.
|
||||
this.log("Calling onContentUpdated callback")
|
||||
this.onContentUpdated(this.currentFileContent, false, changeLocation)
|
||||
this.log("onContentUpdated callback completed")
|
||||
} else {
|
||||
const snippet = item.old_str.length > 50 ? item.old_str.substring(0, 47) + "..." : item.old_str
|
||||
const error = new Error(`Streaming Replacement failed: 'old_str' not found. Snippet: "${snippet}"`)
|
||||
this.log("old_string NOT found in current file content - generating error", "ERROR")
|
||||
this.log("Current file content length: " + this.currentFileContent.length)
|
||||
this.log("Current file content preview: " + this.currentFileContent.substring(0, 200) + "...")
|
||||
|
||||
const snippet = item.old_string.length > 50 ? item.old_string.substring(0, 47) + "..." : item.old_string
|
||||
const error = new Error(`Streaming Replacement failed: 'old_string' not found. Snippet: "${snippet}"`)
|
||||
this.log("Calling onErrorCallback with error: " + error.message, "ERROR")
|
||||
this.onErrorCallback(error) // Call our own error callback
|
||||
}
|
||||
} else {
|
||||
this.log(
|
||||
"Invalid string types - old_string type: " +
|
||||
typeof item.old_string +
|
||||
", new_string type: " +
|
||||
typeof item.new_string,
|
||||
"ERROR",
|
||||
)
|
||||
const error = new Error(`Invalid item structure in replacements stream: ${JSON.stringify(item)}`)
|
||||
this.log("Calling onErrorCallback with error: " + error.message, "ERROR")
|
||||
this.onErrorCallback(error) // Call our own error callback
|
||||
}
|
||||
} else if (value && (Array.isArray(value) || (typeof value === "object" && "replacements" in value))) {
|
||||
// This might be the 'replacements' array itself or the root object.
|
||||
// The `paths: ['$.replacements.*']` should mean we only get items.
|
||||
// If we get here, it's likely the root object if paths wasn't specific enough or if it's an empty replacements array.
|
||||
console.log("Streaming parser emitted container:", value)
|
||||
this.log("Streaming parser emitted container: " + JSON.stringify(value))
|
||||
this.log(
|
||||
"Container type - isArray: " +
|
||||
Array.isArray(value) +
|
||||
", hasReplacements: " +
|
||||
(typeof value === "object" && "replacements" in value),
|
||||
)
|
||||
} else {
|
||||
// Value is not a ReplacementItem or a known container, could be an issue with the JSON structure or path.
|
||||
// If `paths` is correct, this path should ideally not be hit often for valid streams.
|
||||
console.warn("Streaming parser emitted unexpected value:", value)
|
||||
this.log("Streaming parser emitted unexpected value: " + JSON.stringify(value), "WARN")
|
||||
this.log("Unexpected value type: " + typeof value, "WARN")
|
||||
this.log("Has old_string: " + (value && typeof value === "object" && "old_string" in value), "WARN")
|
||||
this.log("Has new_string: " + (value && typeof value === "object" && "new_string" in value), "WARN")
|
||||
}
|
||||
}
|
||||
|
||||
this.parser.onError = (err: Error) => {
|
||||
this.log("Parser onError callback triggered", "ERROR")
|
||||
this.log("Error details: " + JSON.stringify(err), "ERROR")
|
||||
this.log("Error message: " + err.message, "ERROR")
|
||||
this.log("Error stack: " + err.stack, "ERROR")
|
||||
|
||||
// Propagate the error to the caller via the callback
|
||||
this.log("Calling onErrorCallback with parser error", "ERROR")
|
||||
this.onErrorCallback(err)
|
||||
// Note: The @streamparser/json library might throw synchronously on write if onError is not set,
|
||||
// or if it re-throws. We'll ensure Task.ts wraps write/end in try-catch.
|
||||
}
|
||||
|
||||
this.log("Constructor completed - parser setup finished")
|
||||
|
||||
// Log to console where the debug file is located
|
||||
console.log(`[StreamingJsonReplacer] Debug logging to file: ${this.logFilePath}`)
|
||||
}
|
||||
|
||||
public write(jsonChunk: string): void {
|
||||
// Errors during write will be caught by the parser's onError or thrown.
|
||||
this.parser.write(jsonChunk)
|
||||
this.log("write() called")
|
||||
this.log("JSON chunk length: " + jsonChunk.length)
|
||||
this.log("JSON chunk preview: " + jsonChunk.substring(0, 200) + (jsonChunk.length > 200 ? "..." : ""))
|
||||
|
||||
try {
|
||||
// Errors during write will be caught by the parser's onError or thrown.
|
||||
this.log("Calling parser.write()")
|
||||
this.parser.write(jsonChunk)
|
||||
this.log("parser.write() completed successfully")
|
||||
} catch (error) {
|
||||
this.log("Exception during parser.write(): " + error, "ERROR")
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
public getCurrentContent(): string {
|
||||
this.log("getCurrentContent() called")
|
||||
this.log("Current content length: " + this.currentFileContent.length)
|
||||
return this.currentFileContent
|
||||
}
|
||||
|
||||
public getSuccessfullyParsedItems(): ReplacementItem[] {
|
||||
this.log("getSuccessfullyParsedItems() called")
|
||||
this.log("Returning copy of " + this.successfullyParsedItems.length + " items")
|
||||
return [...this.successfullyParsedItems] // Return a copy
|
||||
}
|
||||
|
||||
private calculateChangeLocation(oldStr: string, newStr: string): ChangeLocation {
|
||||
this.log("calculateChangeLocation() called")
|
||||
this.log("oldStr length: " + oldStr.length)
|
||||
this.log("newStr length: " + newStr.length)
|
||||
this.log("oldStr preview: " + oldStr.substring(0, 50) + (oldStr.length > 50 ? "..." : ""))
|
||||
this.log("newStr preview: " + newStr.substring(0, 50) + (newStr.length > 50 ? "..." : ""))
|
||||
|
||||
// Find the index where the old string starts
|
||||
const startIndex = this.currentFileContent.indexOf(oldStr)
|
||||
this.log("startIndex found: " + startIndex)
|
||||
|
||||
if (startIndex === -1) {
|
||||
this.log("startIndex is -1 - old string not found in content!", "WARN")
|
||||
this.log("This shouldn't happen since we already checked includes()", "WARN")
|
||||
// This shouldn't happen since we already checked includes(), but just in case
|
||||
return { startLine: 0, endLine: 0, startChar: 0, endChar: 0 }
|
||||
}
|
||||
|
||||
// Calculate line numbers by counting newlines before the start index
|
||||
const contentBeforeStart = this.currentFileContent.substring(0, startIndex)
|
||||
this.log("contentBeforeStart length: " + contentBeforeStart.length)
|
||||
|
||||
const startLine = (contentBeforeStart.match(/\n/g) || []).length
|
||||
this.log("calculated startLine: " + startLine)
|
||||
|
||||
// Calculate the end index after replacement
|
||||
const endIndex = startIndex + oldStr.length
|
||||
this.log("calculated endIndex: " + endIndex)
|
||||
|
||||
const contentBeforeEnd = this.currentFileContent.substring(0, endIndex)
|
||||
this.log("contentBeforeEnd length: " + contentBeforeEnd.length)
|
||||
|
||||
const endLine = (contentBeforeEnd.match(/\n/g) || []).length
|
||||
this.log("calculated endLine: " + endLine)
|
||||
|
||||
// Calculate character positions within their respective lines
|
||||
const lastNewlineBeforeStart = contentBeforeStart.lastIndexOf("\n")
|
||||
this.log("lastNewlineBeforeStart: " + lastNewlineBeforeStart)
|
||||
|
||||
const startChar = lastNewlineBeforeStart === -1 ? startIndex : startIndex - lastNewlineBeforeStart - 1
|
||||
this.log("calculated startChar: " + startChar)
|
||||
|
||||
const lastNewlineBeforeEnd = contentBeforeEnd.lastIndexOf("\n")
|
||||
const endChar = lastNewlineBeforeEnd === -1 ? endIndex : endIndex - lastNewlineBeforeEnd - 1
|
||||
this.log("lastNewlineBeforeEnd: " + lastNewlineBeforeEnd)
|
||||
|
||||
return {
|
||||
const endChar = lastNewlineBeforeEnd === -1 ? endIndex : endIndex - lastNewlineBeforeEnd - 1
|
||||
this.log("calculated endChar: " + endChar)
|
||||
|
||||
const result = {
|
||||
startLine,
|
||||
endLine,
|
||||
startChar,
|
||||
endChar,
|
||||
}
|
||||
|
||||
this.log("calculateChangeLocation() returning: " + JSON.stringify(result))
|
||||
return result
|
||||
}
|
||||
|
||||
private log(message: string, level: "INFO" | "WARN" | "ERROR" = "INFO"): void {
|
||||
const timestamp = new Date().toISOString()
|
||||
const logLine = `[${timestamp}] [${level}] ${message}\n`
|
||||
|
||||
try {
|
||||
fs.appendFileSync(this.logFilePath, logLine)
|
||||
} catch (error) {
|
||||
// Fallback to console if file logging fails
|
||||
console.error("Failed to write to log file:", error)
|
||||
console.log(`[${level}] ${message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export type AssistantMessageContent = TextContent | ToolUse
|
||||
|
||||
export { parseAssistantMessageV1, parseAssistantMessageV2 } from "./parse-assistant-message"
|
||||
export { parseAssistantMessageV1, parseAssistantMessageV2, parseAssistantMessageV3 } from "./parse-assistant-message"
|
||||
|
||||
export interface TextContent {
|
||||
type: "text"
|
||||
|
||||
@@ -539,6 +539,7 @@ export function parseAssistantMessageV3(assistantMessage: string): AssistantMess
|
||||
if (
|
||||
inFunctionCalls &&
|
||||
currentInvokeName === "" &&
|
||||
!currentToolUse && // Don't create a new tool if we already have one
|
||||
currentCharIndex >= isInvokeStart.length - 1 &&
|
||||
assistantMessage.startsWith(isInvokeStart, currentCharIndex - isInvokeStart.length + 1)
|
||||
) {
|
||||
@@ -686,6 +687,16 @@ export function parseAssistantMessageV3(assistantMessage: string): AssistantMess
|
||||
}
|
||||
}
|
||||
|
||||
// If this is a MultiEdit invoke, create a replace_in_file tool
|
||||
if (currentInvokeName === "MultiEdit") {
|
||||
currentToolUse = {
|
||||
type: "tool_use",
|
||||
name: "replace_in_file",
|
||||
params: {},
|
||||
partial: true,
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -821,6 +832,16 @@ export function parseAssistantMessageV3(assistantMessage: string): AssistantMess
|
||||
}
|
||||
}
|
||||
|
||||
// Map parameter to tool params for MultiEdit
|
||||
if (currentToolUse && currentInvokeName === "MultiEdit") {
|
||||
if (currentParameterName === "file_path") {
|
||||
currentToolUse.params["path"] = value
|
||||
} else if (currentParameterName === "edits") {
|
||||
// Save the value to the diff parameter for replace_in_file
|
||||
currentToolUse.params["diff"] = value
|
||||
}
|
||||
}
|
||||
|
||||
currentParameterName = ""
|
||||
continue
|
||||
}
|
||||
@@ -849,13 +870,13 @@ export function parseAssistantMessageV3(assistantMessage: string): AssistantMess
|
||||
currentInvokeName === "LoadMcpDocumentation" ||
|
||||
currentInvokeName === "AttemptCompletion" ||
|
||||
currentInvokeName === "BrowserAction" ||
|
||||
currentInvokeName === "NewTask")
|
||||
currentInvokeName === "NewTask" ||
|
||||
currentInvokeName === "MultiEdit")
|
||||
) {
|
||||
currentToolUse.partial = false
|
||||
contentBlocks.push(currentToolUse)
|
||||
currentToolUse = undefined
|
||||
}
|
||||
|
||||
currentInvokeName = ""
|
||||
continue
|
||||
}
|
||||
@@ -868,6 +889,12 @@ export function parseAssistantMessageV3(assistantMessage: string): AssistantMess
|
||||
) {
|
||||
inFunctionCalls = false
|
||||
currentTextContentStart = currentCharIndex + 1
|
||||
// Start a new text content block for any text after function_calls
|
||||
currentTextContent = {
|
||||
type: "text",
|
||||
content: "",
|
||||
partial: true,
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import {loadMcpDocumentationToolDefinition} from "@core/tools/loadMcpDocumentati
|
||||
import { attemptCompletionToolDefinition } from "@core/tools/attemptCompletionTool"
|
||||
import {browserActionToolDefinition} from "@core/tools/browserActionTool"
|
||||
import {newTaskToolDefinition} from "@core/tools/newTaskTool"
|
||||
import { editToolDefinition } from "@/core/tools/editTool"
|
||||
|
||||
export const SYSTEM_PROMPT_CLAUDE4 = async (
|
||||
cwd: string,
|
||||
@@ -42,83 +43,24 @@ TOOL USE
|
||||
|
||||
You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
||||
|
||||
# Tool Use Formatting
|
||||
|
||||
Tool use is formatted using XML-style tags. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure:
|
||||
MultiEdit Tool: Makes multiple changes to a single file in one operation
|
||||
|
||||
<tool_name>
|
||||
<parameter1_name>value1</parameter1_name>
|
||||
<parameter2_name>value2</parameter2_name>
|
||||
...
|
||||
</tool_name>
|
||||
<function_calls>
|
||||
<invoke name="MultiEdit">
|
||||
<parameter name="file_path">/path/to/file</parameter>
|
||||
<parameter name="edits">[
|
||||
{"old_string": "first text to replace", "new_string": "new text 1"},
|
||||
{"old_string": "second text to replace", "new_string": "new text 2"}
|
||||
]</parameter>
|
||||
</invoke>
|
||||
</function_calls>
|
||||
|
||||
Always adhere to this format for the tool use to ensure proper parsing and execution.
|
||||
|
||||
# Tools
|
||||
|
||||
## replace_in_file
|
||||
|
||||
"Description: Return your edits as a JSON object with a "replacements" array. Each replacement should have "old_str" and "new_str" fields. The old_str must match exactly what's in the file (including whitespace, indentation and new lines). You can edit multiple lines, but please keep the replacements as simple as possible.
|
||||
Both old_str and new_str can be multiline strings, but they must be valid JSON strings.
|
||||
|
||||
Usage:
|
||||
<replace_in_file>
|
||||
<path>File path here</path>
|
||||
<diff>
|
||||
{{
|
||||
"replacements": [
|
||||
{{
|
||||
"old_str": "exact string from file",
|
||||
"new_str": "replacement string"
|
||||
}}
|
||||
]
|
||||
}}
|
||||
</diff>
|
||||
</replace_in_file>
|
||||
|
||||
Important: Make sure each old_str matches the exact text in the file, character for character.
|
||||
Parameters:
|
||||
- path: (required) The path of the file to modify (relative to the current working directory ${cwd.toPosix()})
|
||||
- replacements_json: (required) A JSON string containing an object with a "replacements" array. Each object in the array must have "old_str" (the exact string to find in the file) and "new_str" (the string to replace it with). Refer to the example format in the main description.
|
||||
Usage:
|
||||
<replace_in_file>
|
||||
<path>File path here</path>
|
||||
<diff>
|
||||
{{
|
||||
"replacements": [
|
||||
{{
|
||||
"old_str": "exact string from file",
|
||||
"new_str": "replacement string"
|
||||
}}
|
||||
]
|
||||
}}
|
||||
</diff>
|
||||
</replace_in_file>
|
||||
|
||||
## Example 2: Requesting to make targeted edits to a file
|
||||
|
||||
<replace_in_file>
|
||||
|
||||
<path>src/baseApp.py</path>
|
||||
<diff>
|
||||
{
|
||||
"replacements": [
|
||||
{
|
||||
"old_str": "def try_dotdotdots(whole, part, replace):",
|
||||
"new_str": "# Handles search/replace blocks that use ellipsis (...) to represent omitted code sections\n# Validates that ellipsis usage is consistent between search and replace blocks\ndef try_dotdotdots(whole, part, replace):"
|
||||
},
|
||||
{
|
||||
"old_str": "def strip_filename(filename, fence):",
|
||||
"new_str": "# Extracts and cleans filename from various markdown formatting styles\n# Handles filenames with different prefixes, suffixes, and decorations\ndef strip_filename(filename, fence):"
|
||||
},
|
||||
{
|
||||
"old_str": "def main():",
|
||||
"new_str": "# Main entry point for command-line usage\n# Processes chat history and displays diffs for all found edit blocks\ndef main():"
|
||||
}
|
||||
]
|
||||
}
|
||||
</diff>
|
||||
</replace_in_file>
|
||||
Parameters:
|
||||
- file_path (required): Absolute path to the file to modify
|
||||
- edits (required): Array of edit operations, each containing:
|
||||
- old_string (required): Exact text to replace
|
||||
- new_string (required): The replacement text
|
||||
|
||||
# Tool Use Guidelines
|
||||
|
||||
@@ -193,7 +135,7 @@ ${
|
||||
|
||||
EDITING FILES
|
||||
|
||||
You have access to two tools for working with files: **${writeTool.name}** and **replace_in_file**. Understanding their roles and selecting the right one for the job will help ensure efficient and accurate modifications.
|
||||
You have access to two tools for working with files: **${writeTool.name}** and **${editToolDefinition.name}**. Understanding their roles and selecting the right one for the job will help ensure efficient and accurate modifications.
|
||||
|
||||
# ${writeTool.name}
|
||||
|
||||
@@ -205,16 +147,16 @@ You have access to two tools for working with files: **${writeTool.name}** and *
|
||||
|
||||
- Initial file creation, such as when scaffolding a new project.
|
||||
- Overwriting large boilerplate files where you want to replace the entire content at once.
|
||||
- When the complexity or number of changes would make replace_in_file unwieldy or error-prone.
|
||||
- When the complexity or number of changes would make ${editToolDefinition.name} unwieldy or error-prone.
|
||||
- When you need to completely restructure a file's content or change its fundamental organization.
|
||||
|
||||
## Important Considerations
|
||||
|
||||
- Using ${writeTool.name} requires providing the file's complete final content.
|
||||
- If you only need to make small changes to an existing file, consider using replace_in_file instead to avoid unnecessarily rewriting the entire file.
|
||||
- If you only need to make small changes to an existing file, consider using ${editToolDefinition.name} instead to avoid unnecessarily rewriting the entire file.
|
||||
- While ${writeTool.name} should not be your default choice, don't hesitate to use it when the situation truly calls for it.
|
||||
|
||||
# replace_in_file
|
||||
# ${editToolDefinition.name}
|
||||
|
||||
## Purpose
|
||||
|
||||
@@ -233,17 +175,17 @@ You have access to two tools for working with files: **${writeTool.name}** and *
|
||||
|
||||
# Choosing the Appropriate Tool
|
||||
|
||||
- **Default to replace_in_file** for most changes. It's the safer, more precise option that minimizes potential issues.
|
||||
- **Default to ${editToolDefinition.name}** for most changes. It's the safer, more precise option that minimizes potential issues.
|
||||
- **Use ${writeTool.name}** when:
|
||||
- Creating new files
|
||||
- The changes are so extensive that using replace_in_file would be more complex or risky
|
||||
- The changes are so extensive that using ${editToolDefinition.name} would be more complex or risky
|
||||
- You need to completely reorganize or restructure a file
|
||||
- The file is relatively small and the changes affect most of its content
|
||||
- You're generating boilerplate or template files
|
||||
|
||||
# Auto-formatting Considerations
|
||||
|
||||
- After using either ${writeTool.name} or replace_in_file, the user's editor may automatically format the file
|
||||
- After using either ${writeTool.name} or ${editToolDefinition.name}, the user's editor may automatically format the file
|
||||
- This auto-formatting may modify the file contents, for example:
|
||||
- Breaking single lines into multiple lines
|
||||
- Adjusting indentation to match project style (e.g. 2 spaces vs 4 spaces vs tabs)
|
||||
@@ -252,20 +194,20 @@ You have access to two tools for working with files: **${writeTool.name}** and *
|
||||
- Adding/removing trailing commas in objects and arrays
|
||||
- Enforcing consistent brace style (e.g. same-line vs new-line)
|
||||
- Standardizing semicolon usage (adding or removing based on style)
|
||||
- The ${writeTool.name} and replace_in_file tool responses will include the final state of the file after any auto-formatting
|
||||
- Use this final state as your reference point for any subsequent edits. This is ESPECIALLY important when crafting SEARCH blocks for replace_in_file which require the content to match what's in the file exactly.
|
||||
- The ${writeTool.name} and ${editToolDefinition.name} tool responses will include the final state of the file after any auto-formatting
|
||||
- Use this final state as your reference point for any subsequent edits. This is ESPECIALLY important when crafting SEARCH blocks for ${editToolDefinition.name} which require the content to match what's in the file exactly.
|
||||
|
||||
# Workflow Tips
|
||||
|
||||
1. Before editing, assess the scope of your changes and decide which tool to use.
|
||||
2. For major overhauls or initial file creation, rely on ${writeTool.name}.
|
||||
3. Once the file has been edited with either ${writeTool.name} or replace_in_file, the system will provide you with the final state of the modified file. Use this updated content as the reference point for any subsequent SEARCH/REPLACE operations, since it reflects any auto-formatting or user-applied changes.
|
||||
3. Once the file has been edited with either ${writeTool.name} or ${editToolDefinition.name}, the system will provide you with the final state of the modified file. Use this updated content as the reference point for any subsequent SEARCH/REPLACE operations, since it reflects any auto-formatting or user-applied changes.
|
||||
4. All edits are applied in sequence, in the order they are provided
|
||||
5. All edits must be valid for the operation to succeed - if any edit fails, none will be applied
|
||||
6. Do not make more than 4 replacements in a single replace_in_file call, as this can lead to errors and make it difficult to track changes. If you need to make more than 4 changes, consider breaking them into multiple replace_in_file calls.
|
||||
7. Make sure a single old_str in a replace_in_file call is no more than 4 lines, as too many lines can lead to errors. If you need to replace a larger section, break it into smaller blocks.
|
||||
6. Do not make more than 4 replacements in a single ${editToolDefinition.name} call, as this can lead to errors and make it difficult to track changes. If you need to make more than 4 changes, consider breaking them into multiple ${editToolDefinition.name} calls.
|
||||
7. Make sure a single old_str in a ${editToolDefinition.name} call is no more than 4 lines, as too many lines can lead to errors. If you need to replace a larger section, break it into smaller blocks.
|
||||
|
||||
By thoughtfully selecting between ${writeTool.name} and replace_in_file, you can make your file editing process smoother, safer, and more efficient.
|
||||
By thoughtfully selecting between ${writeTool.name} and ${editToolDefinition.name}, you can make your file editing process smoother, safer, and more efficient.
|
||||
|
||||
====
|
||||
|
||||
@@ -298,7 +240,7 @@ CAPABILITIES
|
||||
- When the user initially gives you a task, a recursive list of all filepaths in the current working directory ('${cwd.toPosix()}') will be included in environment_details. This provides an overview of the project's file structure, offering key insights into the project from directory/file names (how developers conceptualize and organize their code) and file extensions (the language used). This can also guide decision-making on which files to explore further. If you pass 'true' for the recursive parameter, it will list files recursively. Otherwise, it will list files at the top level, which is better suited for generic directories where you don't necessarily need the nested structure, like the Desktop.
|
||||
- You can use ${grepToolDefinition.name} to perform regex searches across files in a specified directory, outputting context-rich results that include surrounding lines. This is particularly useful for understanding code patterns, finding specific implementations, or identifying areas that need refactoring.
|
||||
- You can use the ${listCodeDefinitionNamesTool.name} tool to get an overview of source code definitions for all files at the top level of a specified directory. This can be particularly useful when you need to understand the broader context and relationships between certain parts of the code. You may need to call this tool multiple times to understand various parts of the codebase related to the task.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use ${listCodeDefinitionNamesTool.name} to get further insight using source code definitions for files located in relevant directories, then ${readTool.name} to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the replace_in_file tool to implement changes. If you refactored code that could affect other parts of the codebase, you could use ${grepToolDefinition.name} to ensure you update other files as needed.
|
||||
- For example, when asked to make edits or improvements you might analyze the file structure in the initial environment_details to get an overview of the project, then use ${listCodeDefinitionNamesTool.name} to get further insight using source code definitions for files located in relevant directories, then ${readTool.name} to examine the contents of relevant files, analyze the code and suggest improvements or make necessary edits, then use the ${editToolDefinition.name} tool to implement changes. If you refactored code that could affect other parts of the codebase, you could use ${grepToolDefinition.name} to ensure you update other files as needed.
|
||||
- You can use the ${bashTool.name} tool to run commands on the user's computer whenever you feel it can help accomplish the user's task. When you need to execute a CLI command, you must provide a clear explanation of what the command does. Prefer to execute complex CLI commands over creating executable scripts, since they are more flexible and easier to run. Interactive and long-running commands are allowed, since the commands are run in the user's VSCode terminal. The user may keep commands running in the background and you will be kept updated on their status along the way. Each command you execute is run in a new terminal instance.${
|
||||
supportsBrowserUse
|
||||
? `\n- You can use the ${browserActionTool.name} tool to interact with websites (including html files and locally running development servers) through a Puppeteer-controlled browser when you feel it is necessary in accomplishing the user's task. This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. This tool may be useful at key stages of web development tasks-such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. You can analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues.\n - For example, if asked to add a component to a react website, you might create the necessary files, use the ${bashTool.name} tool to run the site locally, then use ${browserActionTool.name} to launch the browser, navigate to the local server, and verify the component renders & functions correctly before closing the browser.`
|
||||
@@ -315,11 +257,11 @@ RULES
|
||||
- You cannot \`cd\` into a different directory to complete a task. You are stuck operating from '${cwd.toPosix()}', so be sure to pass in the correct 'path' parameter when using tools that require a path.
|
||||
- Do not use the ~ character or $HOME to refer to the home directory.
|
||||
- Before using the ${bashTool.name} tool, you must first think about the SYSTEM INFORMATION context provided to understand the user's environment and tailor your commands to ensure they are compatible with their system. You must also consider if the command you need to run should be executed in a specific directory outside of the current working directory '${cwd.toPosix()}', and if so prepend with \`cd\`'ing into that directory && then executing the command (as one command since you are stuck operating from '${cwd.toPosix()}'). For example, if you needed to run \`npm install\` in a project outside of '${cwd.toPosix()}', you would need to prepend with a \`cd\` i.e. pseudocode for this would be \`cd (path to project) && (command, in this case npm install)\`.
|
||||
- When using the ${grepToolDefinition.name} tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the ${grepToolDefinition.name} tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use ${readTool.name} to examine the full context of interesting matches before using replace_in_file to make informed changes.
|
||||
- When using the ${grepToolDefinition.name} tool, craft your regex patterns carefully to balance specificity and flexibility. Based on the user's task you may use it to find code patterns, TODO comments, function definitions, or any text-based information across the project. The results include context, so analyze the surrounding code to better understand the matches. Leverage the ${grepToolDefinition.name} tool in combination with other tools for more comprehensive analysis. For example, use it to find specific code patterns, then use ${readTool.name} to examine the full context of interesting matches before using ${editToolDefinition.name} to make informed changes.
|
||||
- When creating a new project (such as an app, website, or any software project), organize all new files within a dedicated project directory unless the user specifies otherwise. Use appropriate file paths when creating files, as the ${writeTool.name} tool will automatically create any necessary directories. Structure the project logically, adhering to best practices for the specific type of project being created. Unless otherwise specified, new projects should be easily run without additional setup, for example most projects can be built in HTML, CSS, and JavaScript - which you can open in a browser.
|
||||
- Be sure to consider the type of project (e.g. Python, JavaScript, web application) when determining the appropriate structure and files to include. Also consider what files may be most relevant to accomplishing the task, for example looking at a project's manifest file would help you understand the project's dependencies, which you could incorporate into any code you write.
|
||||
- When making changes to code, always consider the context in which the code is being used. Ensure that your changes are compatible with the existing codebase and that they follow the project's coding standards and best practices.
|
||||
- When you want to modify a file, use the replace_in_file or ${writeTool.name} tool directly with the desired changes. You do not need to display the changes before using the tool.
|
||||
- When you want to modify a file, use the ${editToolDefinition.name} or ${writeTool.name} tool directly with the desired changes. You do not need to display the changes before using the tool.
|
||||
- Do not ask for more information than necessary. Use the tools provided to accomplish the user's request efficiently and effectively. When you've completed your task, you must use the ${attemptCompletionToolDefinition.name} tool to present the result to the user. The user may provide feedback, which you can use to make improvements and try again.
|
||||
- You are only allowed to ask the user questions using the ${askQuestionToolDefinition.name} tool. Use this tool only when you need additional details to complete a task, and be sure to use a clear and concise question that will help you move forward with the task. However if you can use the available tools to avoid having to ask the user questions, you should do so. For example, if the user mentions a file that may be in an outside directory like the Desktop, you should use the ${lsToolDefinition.name} tool to list the files in the Desktop and check if the file they are talking about is there, rather than asking the user to provide the file path themselves.
|
||||
- When executing commands, if you don't see the expected output, assume the terminal executed the command successfully and proceed with the task. The user's terminal may be unable to stream the output back properly. If you absolutely need to see the actual terminal output, use the ${askQuestionToolDefinition.name} tool to request the user to copy and paste it back to you.
|
||||
@@ -334,7 +276,7 @@ RULES
|
||||
- When presented with images, utilize your vision capabilities to thoroughly examine them and extract meaningful information. Incorporate these insights into your thought process as you accomplish the user's task.
|
||||
- At the end of each user message, you will automatically receive environment_details. This information is not written by the user themselves, but is auto-generated to provide potentially relevant context about the project structure and environment. While this information can be valuable for understanding the project context, do not treat it as a direct part of the user's request or response. Use it to inform your actions and decisions, but don't assume the user is explicitly asking about or referring to this information unless they clearly do so in their message. When using environment_details, explain your actions clearly to ensure the user understands, as they may not be aware of these details.
|
||||
- Before executing commands, check the "Actively Running Terminals" section in environment_details. If present, consider how these active processes might impact your task. For example, if a local development server is already running, you wouldn't need to start it again. If no active terminals are listed, proceed with command execution as normal.
|
||||
- When using the replace_in_file tool, you must include complete lines
|
||||
- When using the ${editToolDefinition.name} tool, you must include complete lines
|
||||
- It is critical you wait for the user's response after each tool use, in order to confirm the success of the tool use. For example, if asked to make a todo app, you would create a file, wait for the user's response it was created successfully, then create another file if needed, wait for the user's response it was created successfully, etc.${
|
||||
supportsBrowserUse
|
||||
? ` Then if you want to test your work, you might use ${browserActionTool.name} to launch the site, wait for the user's response confirming the site was launched along with a screenshot, then perhaps e.g., click a button to test functionality if needed, wait for the user's response confirming the button was clicked along with a screenshot of the new state, before finally closing the browser.`
|
||||
@@ -372,7 +314,7 @@ You accomplish a given task iteratively, breaking it down into clear steps and w
|
||||
4. Once you've completed the user's task, you must use the ${attemptCompletionToolDefinition.name} tool to present the result of the task to the user. You may also provide a CLI command to showcase the result of your task; this can be particularly useful for web development tasks, where you can run e.g. \`open index.html\` to show the website you've built.
|
||||
5. The user may provide feedback, which you can use to make improvements and try again. But DO NOT continue in pointless back and forth conversations, i.e. don't end your responses with questions or offers for further assistance.`
|
||||
|
||||
const tools = [readTool, writeTool, askQuestionToolDefinition, planModeRespondToolDefinition, bashTool, lsToolDefinition, grepToolDefinition, webFetchToolDefinition, listCodeDefinitionNamesTool, useMCPToolDefinition, accessMcpResourceToolDefinition, loadMcpDocumentationTool, newTaskToolDefinition];
|
||||
const tools = [readTool, writeTool, editToolDefinition, askQuestionToolDefinition, planModeRespondToolDefinition, bashTool, lsToolDefinition, grepToolDefinition, webFetchToolDefinition, listCodeDefinitionNamesTool, useMCPToolDefinition, accessMcpResourceToolDefinition, loadMcpDocumentationTool, newTaskToolDefinition, editToolDefinition];
|
||||
if (supportsBrowserUse) {
|
||||
tools.push(browserActionTool);
|
||||
}
|
||||
|
||||
+48
-6
@@ -60,7 +60,13 @@ import { fileExistsAtPath } from "@utils/fs"
|
||||
import { createAndOpenGitHubIssue } from "@utils/github-url-utils"
|
||||
import { arePathsEqual, getReadablePath, isLocatedInWorkspace } from "@utils/path"
|
||||
import { fixModelHtmlEscaping, removeInvalidChars } from "@utils/string"
|
||||
import { AssistantMessageContent, parseAssistantMessageV2, ToolParamName, ToolUseName } from "@core/assistant-message"
|
||||
import {
|
||||
AssistantMessageContent,
|
||||
parseAssistantMessageV2,
|
||||
parseAssistantMessageV3,
|
||||
ToolParamName,
|
||||
ToolUseName,
|
||||
} from "@core/assistant-message"
|
||||
import { constructNewFileContent } from "@core/assistant-message/diff"
|
||||
import { ClineIgnoreController } from "@core/ignore/ClineIgnoreController"
|
||||
import { parseMentions } from "@core/mentions"
|
||||
@@ -104,7 +110,6 @@ import { isInTestMode } from "../../services/test/TestMode"
|
||||
import { processFilesIntoText } from "@integrations/misc/extract-text"
|
||||
import { featureFlagsService } from "@services/posthog/feature-flags/FeatureFlagsService"
|
||||
import { StreamingJsonReplacer, ChangeLocation } from "@core/assistant-message/diff-json"
|
||||
import { parseAssistantMessageV3 } from "../assistant-message/parse-assistant-message"
|
||||
|
||||
export const cwd =
|
||||
vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) ?? path.join(os.homedir(), "Desktop") // may or may not exist but fs checking existence would immediately ask for permission which would be bad UX, need to come up with a better solution
|
||||
@@ -1784,7 +1789,6 @@ export class Task {
|
||||
): Promise<{ shouldBreak: boolean; newContent?: string; error?: string }> {
|
||||
// Calculate the delta - what's new since last time
|
||||
const newJsonChunk = currentFullJson.substring(this.lastProcessedJsonLength)
|
||||
|
||||
if (block.partial) {
|
||||
// Initialize on first chunk
|
||||
if (!this.streamingJsonReplacer) {
|
||||
@@ -1800,6 +1804,7 @@ export class Task {
|
||||
|
||||
const onError = (error: Error) => {
|
||||
console.error("StreamingJsonReplacer error:", error)
|
||||
console.log("Failed StreamingJsonReplacer update:")
|
||||
// Handle error: push tool result, cleanup
|
||||
this.userMessageContent.push({
|
||||
type: "text",
|
||||
@@ -1841,9 +1846,45 @@ export class Task {
|
||||
if (!this.diffViewProvider.isEditing) {
|
||||
await this.diffViewProvider.open(relPath)
|
||||
}
|
||||
// Would need to initialize StreamingJsonReplacer here for non-streaming case
|
||||
|
||||
// Initialize StreamingJsonReplacer for non-streaming case
|
||||
const onContentUpdated = (newContent: string, _isFinalItem: boolean, changeLocation?: ChangeLocation) => {
|
||||
// Update diff view incrementally
|
||||
this.diffViewProvider.update(newContent, false, changeLocation)
|
||||
}
|
||||
|
||||
const onError = (error: Error) => {
|
||||
console.error("StreamingJsonReplacer error:", error)
|
||||
// Handle error
|
||||
this.userMessageContent.push({
|
||||
type: "text",
|
||||
text: formatResponse.toolError(`JSON replacement error: ${error.message}`),
|
||||
})
|
||||
this.didAlreadyUseTool = true
|
||||
this.userMessageContentReady = true
|
||||
throw error
|
||||
}
|
||||
|
||||
this.streamingJsonReplacer = new StreamingJsonReplacer(
|
||||
this.diffViewProvider.originalContent || "",
|
||||
onContentUpdated,
|
||||
onError,
|
||||
)
|
||||
|
||||
// Write the entire JSON at once
|
||||
this.streamingJsonReplacer.write(currentFullJson)
|
||||
|
||||
// Get the final content
|
||||
const newContent = this.streamingJsonReplacer.getCurrentContent()
|
||||
|
||||
// Cleanup
|
||||
this.streamingJsonReplacer = undefined
|
||||
this.lastProcessedJsonLength = 0
|
||||
return { shouldBreak: true }
|
||||
|
||||
// Update diff view with final content
|
||||
await this.diffViewProvider.update(newContent, true)
|
||||
|
||||
return { shouldBreak: false, newContent }
|
||||
}
|
||||
|
||||
// Feed final delta
|
||||
@@ -2201,9 +2242,10 @@ export class Task {
|
||||
const currentFullJson = block.params.diff
|
||||
// Check if we should use streaming (e.g., for specific models)
|
||||
const isClaude4ModelFamily = await this.isClaude4ModelFamily()
|
||||
|
||||
console.log("[EDIT] currentFullJson " + currentFullJson)
|
||||
// Going through claude family of models
|
||||
if (isClaude4ModelFamily && currentFullJson) {
|
||||
console.log("[EDIT] Streaming JSON replacement")
|
||||
const streamingResult = await this.handleStreamingJsonReplacement(
|
||||
block,
|
||||
relPath,
|
||||
|
||||
Reference in New Issue
Block a user