improve edit tool

This commit is contained in:
Andrew Pareles 2025-04-27 20:49:23 -07:00
parent 991e6a9d5f
commit 0bb948ee2d
6 changed files with 56 additions and 27 deletions

View file

@ -506,7 +506,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
return {}
}
// once validated, add checkpoint for edit
if (toolName === 'replace_in_file') { this._addToolEditCheckpoint({ threadId, uri: (toolParams as ToolCallParams['replace_in_file']).uri }) }
if (toolName === 'edit_file') { this._addToolEditCheckpoint({ threadId, uri: (toolParams as ToolCallParams['edit_file']).uri }) }
// 2. if tool requires approval, break from the loop, awaiting approval

View file

@ -1165,7 +1165,30 @@ class EditCodeService extends Disposable implements IEditCodeService {
public instantlyApplySearchReplaceBlocks({ uri, searchReplaceBlocks }: { uri: URI, searchReplaceBlocks: string }) {
// start diffzone
const res = this._startStreamingDiffZone({
uri,
streamRequestIdRef: { current: null },
startBehavior: 'keep-conflicts',
linkedCtrlKZone: null,
onWillUndo: () => { },
})
if (!res) return
const { diffZone, onFinishEdit } = res
const onDone = () => {
diffZone._streamState = { isStreaming: false, }
this._onDidChangeStreamingInDiffZone.fire({ uri, diffareaid: diffZone.diffareaid })
this._refreshStylesAndDiffsInURI(uri)
onFinishEdit()
}
this._instantlyApplySRBlocks(uri, searchReplaceBlocks)
onDone()
}

View file

@ -1243,7 +1243,7 @@ const titleOfToolName = {
'search_for_files': { done: 'Searched', proposed: 'Search', running: loadingTitleWrapper('Searching') },
'create_file_or_folder': { done: `Created`, proposed: `Create`, running: loadingTitleWrapper(`Creating`) },
'delete_file_or_folder': { done: `Deleted`, proposed: `Delete`, running: loadingTitleWrapper(`Deleting`) },
'replace_in_file': { done: `Edited file`, proposed: 'Edit file', running: loadingTitleWrapper('Editing file') },
'edit_file': { done: `Edited file`, proposed: 'Edit file', running: loadingTitleWrapper('Editing file') },
'run_command': { done: `Ran terminal`, proposed: 'Run terminal', running: loadingTitleWrapper('Running terminal') },
'open_persistent_terminal': { done: `Opened terminal`, proposed: 'Open terminal', running: loadingTitleWrapper('Opening terminal') },
'kill_persistent_terminal': { done: `Killed terminal`, proposed: 'Kill terminal', running: loadingTitleWrapper('Killing terminal') },
@ -1319,8 +1319,8 @@ const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName
desc1Info: getRelative(toolParams.uri, accessor),
}
},
'replace_in_file': () => {
const toolParams = _toolParams as ToolCallParams['replace_in_file']
'edit_file': () => {
const toolParams = _toolParams as ToolCallParams['edit_file']
return {
desc1: getBasename(toolParams.uri.fsPath),
desc1Info: getRelative(toolParams.uri, accessor),
@ -1977,7 +1977,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
return <ToolHeaderWrapper {...componentParams} />
}
},
'replace_in_file': {
'edit_file': {
resultWrapper: ({ toolMessage, messageIdx, threadId }) => {
const accessor = useAccessor()
const isError = toolMessage.type === 'tool_error'
@ -2633,9 +2633,9 @@ const CommandBarInChat = () => {
const EditToolSoFar = ({ toolCallSoFar, }: { toolCallSoFar: RawToolCallObj }) => {
const uri = URI.file(toolCallSoFar.rawParams.uri ?? 'unknown')
const uri = toolCallSoFar.rawParams.uri ? URI.file(toolCallSoFar.rawParams.uri) : undefined
const title = titleOfToolName['replace_in_file'].proposed
const title = titleOfToolName['edit_file'].proposed
const uriDone = toolCallSoFar.doneParams.includes('uri')
const desc1 = <span className='flex items-center'>
@ -2649,7 +2649,7 @@ const EditToolSoFar = ({ toolCallSoFar, }: { toolCallSoFar: RawToolCallObj }) =>
return <ToolHeaderWrapper
title={title}
desc1={desc1}
desc2={<JumpToFileButton uri={uri} />}
desc2={uri && <JumpToFileButton uri={uri} />}
>
<EditToolChildren
uri={uri}
@ -2703,7 +2703,7 @@ export const SidebarChat = () => {
const reasoningSoFar = currThreadStreamState?.reasoningSoFar
// this is just if it's currently being generated, NOT if it's currently running
const toolIsGenerating = toolCallSoFar && !toolCallSoFar.isDone && toolCallSoFar.name === 'replace_in_file' // show loading for slow tools (right now just edit)
const toolIsGenerating = toolCallSoFar && !toolCallSoFar.isDone && toolCallSoFar.name === 'edit_file' // show loading for slow tools (right now just edit)
// ----- SIDEBAR CHAT state (local) -----
@ -2794,7 +2794,7 @@ export const SidebarChat = () => {
// the tool currently being generated
const generatingTool = toolIsGenerating ?
toolCallSoFar.name === 'replace_in_file' ? <EditToolSoFar
toolCallSoFar.name === 'edit_file' ? <EditToolSoFar
key={'curr-streaming-tool'}
toolCallSoFar={toolCallSoFar}
/>

View file

@ -241,11 +241,10 @@ export class ToolsService implements IToolsService {
return { uri, isRecursive, isFolder }
},
replace_in_file: (params: RawToolParamsObj) => {
edit_file: (params: RawToolParamsObj) => {
const { uri: uriStr, search_replace_blocks: searchReplaceBlocksUnknown } = params
const uri = validateURI(uriStr)
const searchReplaceBlocks = validateStr('searchReplaceBlocks', searchReplaceBlocksUnknown)
console.log('params!!!', uri, searchReplaceBlocks, 'nnnnn', searchReplaceBlocksUnknown)
return { uri, searchReplaceBlocks }
},
@ -384,7 +383,7 @@ export class ToolsService implements IToolsService {
await fileService.del(uri, { recursive: isRecursive })
return { result: {} }
},
replace_in_file: async ({ uri, searchReplaceBlocks }) => {
edit_file: async ({ uri, searchReplaceBlocks }) => {
await voidModelService.initializeModel(uri)
if (this.commandBarService.getStreamState(uri) === 'streaming') {
throw new Error(`Another LLM is currently making changes to this file. Please stop streaming for now and ask the user to resume later.`)
@ -471,7 +470,7 @@ export class ToolsService implements IToolsService {
delete_file_or_folder: (params, result) => {
return `URI ${params.uri.fsPath} successfully deleted.`
},
replace_in_file: (params, result) => {
edit_file: (params, result) => {
const lintErrsString = (
this.voidSettingsService.state.globalSettings.includeToolLintErrors ?
(result.lintErrors ? ` Lint errors found after change:\n${stringifyLintErrors(result.lintErrors)}.\nIf this is related to a change made while calling this tool, you might want to fix the error.`

View file

@ -43,10 +43,16 @@ export const FINAL = `>>>>>>> UPDATED`
const searchReplaceBlockTemplate = `\
${tripleTick[0]}
${ORIGINAL}
// ... original code goes here
// ... original code 1 goes here
${DIVIDER}
// ... final code goes here
// ... final code 1 goes here
${FINAL}
${ORIGINAL}
// ... original code 2 goes here
${DIVIDER}
// ... final code 2 goes here
${FINAL}
...
${tripleTick[1]}`
@ -69,7 +75,7 @@ ${searchReplaceBlockTemplate}
5. The ORIGINAL code in each SEARCH/REPLACE block must EXACTLY match lines in the original file. Do not add or remove any whitespace, comments, or modifications from the original code.
6. Each ORIGINAL text must be large enough to uniquely identify the change in the file. However; bias towards writing as little as possible.
6. Each ORIGINAL text must be large enough to uniquely identify the change in the file. However, bias towards writing as little as possible.
7. Each ORIGINAL text must be DISJOINT from all other ORIGINAL text.
@ -100,14 +106,15 @@ ${tripleTick[1]}`
const replaceTool_description = `\
Output a single string of SEARCH/REPLACE block(s) here. Your string should be wrapped in triple backticks. Here's how to format your SEARCH/REPLACE blocks:
Output a string of SEARCH/REPLACE block(s) to implement your desired change.
You are encouraged to output multiple changes at once. Here's how to format your blocks:
${searchReplaceBlockTemplate}
1. You are allowed to output multiple SEARCH/REPLACE blocks to implement your desired change. Just write them sequentially.
1. Don't forget to wrap your output in triple backticks.
2. The ORIGINAL code in each SEARCH/REPLACE block must EXACTLY match lines in the original file. Do not add or remove any whitespace, comments, or modifications from the original code.
2. The ORIGINAL code in each SEARCH/REPLACE block must EXACTLY match lines in the original file. Do not add or remove any whitespace or comments from the original code.
3. Each ORIGINAL text must be large enough to uniquely identify the change in the file. However; bias towards writing as little as possible.
3. Each ORIGINAL text must be large enough to uniquely identify the change in the file. However, bias towards writing as little as possible.
4. Each ORIGINAL text must be DISJOINT from all other ORIGINAL text.`
@ -278,8 +285,8 @@ export const voidTools = {
},
},
replace_in_file: { // APPLY TOOL
name: 'replace_in_file',
edit_file: { // APPLY TOOL
name: 'edit_file',
description: `Edit the contents of a file. You must provide the file's URI as well as SEARCH/REPLACE block(s) that will be used to apply the edit.`,
params: {
...uriParam('file'),
@ -289,7 +296,7 @@ export const voidTools = {
run_command: {
name: 'run_command',
description: `Runs a terminal command and waits for the result (times out after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity). You can use this tool to run any command: sed, grep, etc. Do not edit any files with this tool; use replace_in_file instead. When working with git and other tools that open an editor (e.g. git diff), you should pipe to cat to get all results and not get stuck in vim.`,
description: `Runs a terminal command and waits for the result (times out after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity). You can use this tool to run any command: sed, grep, etc. Do not edit any files with this tool; use edit_file instead. When working with git and other tools that open an editor (e.g. git diff), you should pipe to cat to get all results and not get stuck in vim.`,
params: {
command: { description: 'The terminal command to run.' },
bg_terminal_id: { description: 'Optional. This only applies to terminals that have been opened with open_persistent_terminal. Runs the command in the terminal with the specified ID.' },

View file

@ -19,7 +19,7 @@ export type ShallowDirectoryItem = {
export const approvalTypeOfToolName: Partial<{ [T in ToolName]?: 'edits' | 'terminal' }> = {
'create_file_or_folder': 'edits',
'delete_file_or_folder': 'edits',
'replace_in_file': 'edits',
'edit_file': 'edits',
'run_command': 'terminal',
}
@ -42,7 +42,7 @@ export type ToolCallParams = {
'search_in_file': { uri: URI, query: string, isRegex: boolean },
'read_lint_errors': { uri: URI },
// ---
'replace_in_file': { uri: URI, searchReplaceBlocks: string },
'edit_file': { uri: URI, searchReplaceBlocks: string },
'create_file_or_folder': { uri: URI, isFolder: boolean },
'delete_file_or_folder': { uri: URI, isRecursive: boolean, isFolder: boolean },
// ---
@ -61,7 +61,7 @@ export type ToolResultType = {
'search_in_file': { lines: number[]; },
'read_lint_errors': { lintErrors: LintErrorItem[] | null },
// ---
'replace_in_file': Promise<{ lintErrors: LintErrorItem[] | null }>,
'edit_file': Promise<{ lintErrors: LintErrorItem[] | null }>,
'create_file_or_folder': {},
'delete_file_or_folder': {},
// ---