mirror of
https://github.com/voideditor/void
synced 2026-05-23 17:38:23 +00:00
edit_file -> replace_in_file (SEARCH/REPLACE blocks)
This commit is contained in:
parent
19313c7e5d
commit
1e5f9808b4
8 changed files with 367 additions and 346 deletions
|
|
@ -506,7 +506,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
return {}
|
||||
}
|
||||
// once validated, add checkpoint for edit
|
||||
if (toolName === 'edit_file') { this._addToolEditCheckpoint({ threadId, uri: (toolParams as ToolCallParams['edit_file']).uri }) }
|
||||
if (toolName === 'replace_in_file') { this._addToolEditCheckpoint({ threadId, uri: (toolParams as ToolCallParams['replace_in_file']).uri }) }
|
||||
|
||||
// 2. if tool requires approval, break from the loop, awaiting approval
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import * as dom from '../../../../base/browser/dom.js';
|
|||
import { Widget } from '../../../../base/browser/ui/widget.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { IConsistentEditorItemService, IConsistentItemService } from './helperServices/consistentItemService.js';
|
||||
import { voidPrefixAndSuffix, ctrlKStream_userMessage, ctrlKStream_systemMessage, defaultQuickEditFimTags, rewriteCode_systemMessage, rewriteCode_userMessage, searchReplace_systemMessage, searchReplace_userMessage, } from '../common/prompt/prompts.js';
|
||||
import { voidPrefixAndSuffix, ctrlKStream_userMessage, ctrlKStream_systemMessage, defaultQuickEditFimTags, rewriteCode_systemMessage, rewriteCode_userMessage, searchReplaceGivenDescription_systemMessage, searchReplaceGivenDescription_userMessage, } from '../common/prompt/prompts.js';
|
||||
|
||||
import { mountCtrlK } from './react/out/quick-edit-tsx/index.js'
|
||||
import { QuickEditPropsType } from './quickEditActions.js';
|
||||
|
|
@ -1164,6 +1164,9 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
}
|
||||
|
||||
|
||||
public instantlyApplySearchReplaceBlocks({ uri, searchReplaceBlocks }: { uri: URI, searchReplaceBlocks: string }) {
|
||||
this._instantlyApplySRBlocks(uri, searchReplaceBlocks)
|
||||
}
|
||||
|
||||
|
||||
private _findOverlappingDiffArea({ startLine, endLine, uri, filter }: { startLine: number, endLine: number, uri: URI, filter?: (diffArea: DiffArea) => boolean }): DiffArea | null {
|
||||
|
|
@ -1509,6 +1512,77 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
}
|
||||
|
||||
|
||||
private _errContentOfInvalidStr = (str: 'Not found' | 'Not unique' | 'Has overlap', blockOrig: string) => {
|
||||
|
||||
const descStr = str === `Not found` ?
|
||||
`The most recent ORIGINAL code could not be found in the file, so you were interrupted. The text in ORIGINAL must EXACTLY match lines of code. The problematic ORIGINAL code was:\n${JSON.stringify(blockOrig)}`
|
||||
: str === `Not unique` ?
|
||||
`The most recent ORIGINAL code shows up multiple times in the file, so you were interrupted. You might want to expand the ORIGINAL excerpt so it's unique. The problematic ORIGINAL code was:\n${JSON.stringify(blockOrig)}`
|
||||
: str === 'Has overlap' ?
|
||||
`The most recent ORIGINAL code has overlap with another ORIGINAL code block that you outputted. Do NOT output any overlapping edits. The problematic ORIGINAL code was:\n${JSON.stringify(blockOrig)}`
|
||||
: ``
|
||||
|
||||
// string of <<<<< ORIGINAL >>>>> REPLACE blocks so far so LLM can understand what it currently has
|
||||
// const blocksSoFarStr = blocks.slice(0, blockNum).map(block => `${ORIGINAL}\n${block.orig}\n${DIVIDER}\n${block.final}\n${FINAL}`).join('\n')
|
||||
// const soFarStr = blocksSoFarStr ? `These are the Search/Replace blocks that have been applied so far:${tripleTick[0]}\n${blocksSoFarStr}\n${tripleTick[1]}` : ''
|
||||
// const continueMsg = soFarStr ? `${soFarStr}Please continue outputting SEARCH/REPLACE blocks starting where this leaves off.` : ''
|
||||
// const errMsg = `${descStr}${continueMsg ? `\n${continueMsg}` : ''}`
|
||||
const soFarStr = 'All of your previous outputs have been ignored. Please re-output ALL SEARCH/REPLACE blocks starting from the first one, and avoid the error this time.'
|
||||
const errMsg = `${descStr}\n${soFarStr}`
|
||||
return errMsg
|
||||
|
||||
}
|
||||
|
||||
|
||||
private _instantlyApplySRBlocks(uri: URI, blocksStr: string) {
|
||||
const blocks = extractSearchReplaceBlocks(blocksStr)
|
||||
if (blocks.length === 0) throw new Error(`No Search/Replace blocks were received!`)
|
||||
|
||||
const { model } = this._voidModelService.getModel(uri)
|
||||
if (!model) throw new Error(`Error applying Search/Replace blocks: File does not exist.`)
|
||||
const modelStr = model.getValue(EndOfLinePreference.LF)
|
||||
|
||||
const replacements: { origStart: number; origEnd: number; block: ExtractedSearchReplaceBlock }[] = []
|
||||
for (const b of blocks) {
|
||||
const i = modelStr.indexOf(b.orig)
|
||||
if (i === -1)
|
||||
throw new Error(this._errContentOfInvalidStr('Not found', replacements[i].block.orig))
|
||||
const j = modelStr.lastIndexOf(b.orig)
|
||||
if (i !== j)
|
||||
throw new Error(this._errContentOfInvalidStr('Not unique', replacements[i].block.orig))
|
||||
|
||||
replacements.push({
|
||||
origStart: i,
|
||||
origEnd: i + b.orig.length - 1, // INCLUSIVE
|
||||
block: b,
|
||||
})
|
||||
}
|
||||
|
||||
// sort in increasing order
|
||||
replacements.sort((a, b) => a.origStart - b.origStart)
|
||||
|
||||
// ensure no overlap
|
||||
for (let i = 1; i < replacements.length; i++) {
|
||||
if (replacements[i].origStart < replacements[i - 1].origEnd) {
|
||||
// There's an overlap
|
||||
throw new Error(this._errContentOfInvalidStr('Has overlap', replacements[i].block.orig))
|
||||
}
|
||||
}
|
||||
|
||||
// apply each replacement from right to left (so indexes don't shift)
|
||||
let newCode: string = modelStr
|
||||
for (let i = replacements.length - 1; i >= 0; i--) {
|
||||
const { origStart, origEnd, block } = replacements[i]
|
||||
newCode = newCode.slice(0, origStart) + block.final + newCode.slice(origEnd + 1, Infinity)
|
||||
}
|
||||
|
||||
this._writeURIText(uri, newCode,
|
||||
'wholeFileRange',
|
||||
{ shouldRealignDiffAreas: true }
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
private _initializeSearchAndReplaceStream(opts: StartApplyingOpts & { from: 'ClickApply' }): [DiffZone, Promise<void>] | undefined {
|
||||
const { from, applyStr, } = opts
|
||||
const featureName: FeatureName = 'Apply'
|
||||
|
|
@ -1526,10 +1600,10 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
|
||||
// build messages - ask LLM to generate search/replace block text
|
||||
const originalFileCode = model.getValue(EndOfLinePreference.LF)
|
||||
const userMessageContent = searchReplace_userMessage({ originalCode: originalFileCode, applyStr: applyStr })
|
||||
const userMessageContent = searchReplaceGivenDescription_userMessage({ originalCode: originalFileCode, applyStr: applyStr })
|
||||
|
||||
const { messages, separateSystemMessage: separateSystemMessage } = this._convertToLLMMessageService.prepareLLMSimpleMessages({
|
||||
systemMessage: searchReplace_systemMessage,
|
||||
systemMessage: searchReplaceGivenDescription_systemMessage,
|
||||
simpleMessages: [{ role: 'user', content: userMessageContent, }],
|
||||
featureName,
|
||||
modelSelection,
|
||||
|
|
@ -1577,27 +1651,6 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
}
|
||||
|
||||
|
||||
const errContentOfInvalidStr = (str: 'Not found' | 'Not unique' | 'Has overlap', blockOrig: string) => {
|
||||
|
||||
const descStr = str === `Not found` ?
|
||||
`The most recent ORIGINAL code could not be found in the file, so you were interrupted. The text in ORIGINAL must EXACTLY match lines of code. The problematic ORIGINAL code was:\n${JSON.stringify(blockOrig)}`
|
||||
: str === `Not unique` ?
|
||||
`The most recent ORIGINAL code shows up multiple times in the file, so you were interrupted. You might want to expand the ORIGINAL excerpt so it's unique. The problematic ORIGINAL code was:\n${JSON.stringify(blockOrig)}`
|
||||
: str === 'Has overlap' ?
|
||||
`The most recent ORIGINAL code has overlap with another ORIGINAL code block that you outputted. Do NOT output any overlapping edits. The problematic ORIGINAL code was:\n${JSON.stringify(blockOrig)}`
|
||||
: ``
|
||||
|
||||
// string of <<<<< ORIGINAL >>>>> REPLACE blocks so far so LLM can understand what it currently has
|
||||
// const blocksSoFarStr = blocks.slice(0, blockNum).map(block => `${ORIGINAL}\n${block.orig}\n${DIVIDER}\n${block.final}\n${FINAL}`).join('\n')
|
||||
// const soFarStr = blocksSoFarStr ? `These are the Search/Replace blocks that have been applied so far:${tripleTick[0]}\n${blocksSoFarStr}\n${tripleTick[1]}` : ''
|
||||
// const continueMsg = soFarStr ? `${soFarStr}Please continue outputting SEARCH/REPLACE blocks starting where this leaves off.` : ''
|
||||
// const errMsg = `${descStr}${continueMsg ? `\n${continueMsg}` : ''}`
|
||||
const soFarStr = 'All of your previous outputs have been ignored. Please re-output ALL SEARCH/REPLACE blocks starting from the first one, and avoid the error this time.'
|
||||
const errMsg = `${descStr}\n${soFarStr}`
|
||||
return errMsg
|
||||
|
||||
}
|
||||
|
||||
const onDone = () => {
|
||||
diffZone._streamState = { isStreaming: false, }
|
||||
this._onDidChangeStreamingInDiffZone.fire({ uri, diffareaid: diffZone.diffareaid })
|
||||
|
|
@ -1652,6 +1705,159 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
let resMessageDonePromise: () => void = () => { }
|
||||
const messageDonePromise = new Promise<void>((res, rej) => { resMessageDonePromise = res })
|
||||
|
||||
|
||||
const onText = (params: { fullText: string; fullReasoning: string }) => {
|
||||
const { fullText } = params
|
||||
// blocks are [done done done ... {writingFinal|writingOriginal}]
|
||||
// ^
|
||||
// currStreamingBlockNum
|
||||
|
||||
const blocks = extractSearchReplaceBlocks(fullText)
|
||||
|
||||
for (let blockNum = currStreamingBlockNum; blockNum < blocks.length; blockNum += 1) {
|
||||
const block = blocks[blockNum]
|
||||
|
||||
if (block.state === 'writingOriginal') {
|
||||
// update stream state to the first line of original if some portion of original has been written
|
||||
if (shouldUpdateOrigStreamStyle && block.orig.trim().length >= 20) {
|
||||
const startingAtLine = diffZone._streamState.line ?? 1 // dont go backwards if already have a stream line
|
||||
const originalRange = findTextInCode(block.orig, originalFileCode, false, startingAtLine)
|
||||
if (typeof originalRange !== 'string') {
|
||||
const [startLine, _] = convertOriginalRangeToFinalRange(originalRange)
|
||||
diffZone._streamState.line = startLine
|
||||
shouldUpdateOrigStreamStyle = false
|
||||
}
|
||||
}
|
||||
|
||||
// // starting line is at least the number of lines in the generated code minus 1
|
||||
// const numLinesInOrig = numLinesOfStr(block.orig)
|
||||
// const newLine = Math.max(numLinesInOrig - 1, 1, diffZone._streamState.line ?? 1)
|
||||
// if (newLine !== diffZone._streamState.line) {
|
||||
// diffZone._streamState.line = newLine
|
||||
// this._refreshStylesAndDiffsInURI(uri)
|
||||
// }
|
||||
|
||||
|
||||
// must be done writing original to move on to writing streamed content
|
||||
continue
|
||||
}
|
||||
shouldUpdateOrigStreamStyle = true
|
||||
|
||||
|
||||
// if this is the first time we're seeing this block, add it as a diffarea so we can start streaming in it
|
||||
if (!(blockNum in addedTrackingZoneOfBlockNum)) {
|
||||
|
||||
const originalBounds = findTextInCode(block.orig, originalFileCode, true)
|
||||
// if error
|
||||
// Check for overlap with existing modified ranges
|
||||
const hasOverlap = addedTrackingZoneOfBlockNum.some(trackingZone => {
|
||||
const [existingStart, existingEnd] = trackingZone.metadata.originalBounds;
|
||||
const hasNoOverlap = endLine < existingStart || startLine > existingEnd
|
||||
return !hasNoOverlap
|
||||
});
|
||||
|
||||
if (typeof originalBounds === 'string' || hasOverlap) {
|
||||
const errorMessage = typeof originalBounds === 'string' ? originalBounds : 'Has overlap' as const
|
||||
|
||||
console.log('--------------Error finding text in code:')
|
||||
console.log('originalFileCode', { originalFileCode })
|
||||
console.log('fullText', { fullText })
|
||||
console.log('error:', errorMessage)
|
||||
console.log('block.orig:', block.orig)
|
||||
console.log('---------')
|
||||
const content = this._errContentOfInvalidStr(errorMessage, block.orig)
|
||||
messages.push(
|
||||
{ role: 'assistant', content: fullText }, // latest output
|
||||
{ role: 'user', content: content } // user explanation of what's wrong
|
||||
)
|
||||
|
||||
// REVERT ALL BLOCKS
|
||||
currStreamingBlockNum = 0
|
||||
latestStreamLocationMutable = null
|
||||
shouldUpdateOrigStreamStyle = true
|
||||
oldBlocks = []
|
||||
for (const trackingZone of addedTrackingZoneOfBlockNum)
|
||||
this._deleteTrackingZone(trackingZone)
|
||||
addedTrackingZoneOfBlockNum.splice(0, Infinity)
|
||||
|
||||
this._writeURIText(uri, originalFileCode, 'wholeFileRange', { shouldRealignDiffAreas: true })
|
||||
|
||||
// abort and resolve
|
||||
shouldSendAnotherMessage = true
|
||||
if (streamRequestIdRef.current) {
|
||||
weAreAborting = true
|
||||
this._llmMessageService.abort(streamRequestIdRef.current)
|
||||
weAreAborting = false
|
||||
}
|
||||
diffZone._streamState.line = 1
|
||||
resMessageDonePromise()
|
||||
this._refreshStylesAndDiffsInURI(uri)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
const [startLine, endLine] = convertOriginalRangeToFinalRange(originalBounds)
|
||||
|
||||
// console.log('---------adding-------')
|
||||
// console.log('CURRENT TEXT!!!', { current: model?.getValue() })
|
||||
// console.log('block', deepClone(block))
|
||||
// console.log('origBounds', originalBounds)
|
||||
// console.log('start end', startLine, endLine)
|
||||
|
||||
// otherwise if no error, add the position as a diffarea
|
||||
const adding: Omit<TrackingZone<SearchReplaceDiffAreaMetadata>, 'diffareaid'> = {
|
||||
type: 'TrackingZone',
|
||||
startLine: startLine,
|
||||
endLine: endLine,
|
||||
_URI: uri,
|
||||
metadata: {
|
||||
originalBounds: [...originalBounds],
|
||||
originalCode: block.orig,
|
||||
},
|
||||
}
|
||||
const trackingZone = this._addDiffArea(adding)
|
||||
addedTrackingZoneOfBlockNum.push(trackingZone)
|
||||
latestStreamLocationMutable = { line: startLine, addedSplitYet: false, col: 1, originalCodeStartLine: 1 }
|
||||
} // end adding diffarea
|
||||
|
||||
|
||||
// should always be in streaming state here
|
||||
if (!diffZone._streamState.isStreaming) {
|
||||
console.error('DiffZone was not in streaming state in _initializeSearchAndReplaceStream')
|
||||
continue
|
||||
}
|
||||
|
||||
// if a block is done, finish it by writing all
|
||||
if (block.state === 'done') {
|
||||
const { startLine: finalStartLine, endLine: finalEndLine } = addedTrackingZoneOfBlockNum[blockNum]
|
||||
this._writeURIText(uri, block.final,
|
||||
{ startLineNumber: finalStartLine, startColumn: 1, endLineNumber: finalEndLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed
|
||||
{ shouldRealignDiffAreas: true }
|
||||
)
|
||||
diffZone._streamState.line = finalEndLine + 1
|
||||
currStreamingBlockNum = blockNum + 1
|
||||
continue
|
||||
}
|
||||
|
||||
// write the added text to the file
|
||||
if (!latestStreamLocationMutable) continue
|
||||
const oldBlock = oldBlocks[blockNum]
|
||||
const oldFinalLen = (oldBlock?.final ?? '').length
|
||||
const deltaFinalText = block.final.substring(oldFinalLen, Infinity)
|
||||
|
||||
this._writeStreamedDiffZoneLLMText(uri, block.orig, block.final, deltaFinalText, latestStreamLocationMutable)
|
||||
oldBlocks = blocks // oldblocks is only used if writingFinal
|
||||
|
||||
// const { endLine: currentEndLine } = addedTrackingZoneOfBlockNum[blockNum] // would be bad to do this because a lot of the bottom lines might be the same. more accurate to go with latestStreamLocationMutable
|
||||
// diffZone._streamState.line = currentEndLine
|
||||
diffZone._streamState.line = latestStreamLocationMutable.line
|
||||
|
||||
} // end for
|
||||
|
||||
this._refreshStylesAndDiffsInURI(uri)
|
||||
}
|
||||
|
||||
streamRequestIdRef.current = this._llmMessageService.sendLLMMessage({
|
||||
messagesType: 'chatMessages',
|
||||
logging: { loggingName: `Edit (Search/Replace) - ${from}` },
|
||||
|
|
@ -1661,201 +1867,25 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
separateSystemMessage,
|
||||
chatMode: null, // not chat
|
||||
onText: (params) => {
|
||||
const { fullText } = params
|
||||
// blocks are [done done done ... {writingFinal|writingOriginal}]
|
||||
// ^
|
||||
// currStreamingBlockNum
|
||||
|
||||
const blocks = extractSearchReplaceBlocks(fullText)
|
||||
|
||||
for (let blockNum = currStreamingBlockNum; blockNum < blocks.length; blockNum += 1) {
|
||||
const block = blocks[blockNum]
|
||||
|
||||
if (block.state === 'writingOriginal') {
|
||||
// update stream state to the first line of original if some portion of original has been written
|
||||
if (shouldUpdateOrigStreamStyle && block.orig.trim().length >= 20) {
|
||||
const startingAtLine = diffZone._streamState.line ?? 1 // dont go backwards if already have a stream line
|
||||
const originalRange = findTextInCode(block.orig, originalFileCode, false, startingAtLine)
|
||||
if (typeof originalRange !== 'string') {
|
||||
const [startLine, _] = convertOriginalRangeToFinalRange(originalRange)
|
||||
diffZone._streamState.line = startLine
|
||||
shouldUpdateOrigStreamStyle = false
|
||||
}
|
||||
}
|
||||
|
||||
// // starting line is at least the number of lines in the generated code minus 1
|
||||
// const numLinesInOrig = numLinesOfStr(block.orig)
|
||||
// const newLine = Math.max(numLinesInOrig - 1, 1, diffZone._streamState.line ?? 1)
|
||||
// if (newLine !== diffZone._streamState.line) {
|
||||
// diffZone._streamState.line = newLine
|
||||
// this._refreshStylesAndDiffsInURI(uri)
|
||||
// }
|
||||
|
||||
|
||||
// must be done writing original to move on to writing streamed content
|
||||
continue
|
||||
}
|
||||
shouldUpdateOrigStreamStyle = true
|
||||
|
||||
|
||||
// if this is the first time we're seeing this block, add it as a diffarea so we can start streaming in it
|
||||
if (!(blockNum in addedTrackingZoneOfBlockNum)) {
|
||||
|
||||
const originalBounds = findTextInCode(block.orig, originalFileCode, true)
|
||||
// if error
|
||||
// Check for overlap with existing modified ranges
|
||||
const hasOverlap = addedTrackingZoneOfBlockNum.some(trackingZone => {
|
||||
const [existingStart, existingEnd] = trackingZone.metadata.originalBounds;
|
||||
const hasNoOverlap = endLine < existingStart || startLine > existingEnd
|
||||
return !hasNoOverlap
|
||||
});
|
||||
|
||||
if (typeof originalBounds === 'string' || hasOverlap) {
|
||||
const errorMessage = typeof originalBounds === 'string' ? originalBounds : 'Has overlap' as const
|
||||
|
||||
console.log('--------------Error finding text in code:')
|
||||
console.log('originalFileCode', { originalFileCode })
|
||||
console.log('fullText', { fullText })
|
||||
console.log('error:', errorMessage)
|
||||
console.log('block.orig:', block.orig)
|
||||
console.log('---------')
|
||||
const content = errContentOfInvalidStr(errorMessage, block.orig)
|
||||
messages.push(
|
||||
{ role: 'assistant', content: fullText }, // latest output
|
||||
{ role: 'user', content: content } // user explanation of what's wrong
|
||||
)
|
||||
|
||||
// REVERT ALL BLOCKS
|
||||
currStreamingBlockNum = 0
|
||||
latestStreamLocationMutable = null
|
||||
shouldUpdateOrigStreamStyle = true
|
||||
oldBlocks = []
|
||||
for (const trackingZone of addedTrackingZoneOfBlockNum)
|
||||
this._deleteTrackingZone(trackingZone)
|
||||
addedTrackingZoneOfBlockNum.splice(0, Infinity)
|
||||
|
||||
this._writeURIText(uri, originalFileCode, 'wholeFileRange', { shouldRealignDiffAreas: true })
|
||||
|
||||
// abort and resolve
|
||||
shouldSendAnotherMessage = true
|
||||
if (streamRequestIdRef.current) {
|
||||
weAreAborting = true
|
||||
this._llmMessageService.abort(streamRequestIdRef.current)
|
||||
weAreAborting = false
|
||||
}
|
||||
diffZone._streamState.line = 1
|
||||
resMessageDonePromise()
|
||||
this._refreshStylesAndDiffsInURI(uri)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
const [startLine, endLine] = convertOriginalRangeToFinalRange(originalBounds)
|
||||
|
||||
// console.log('---------adding-------')
|
||||
// console.log('CURRENT TEXT!!!', { current: model?.getValue() })
|
||||
// console.log('block', deepClone(block))
|
||||
// console.log('origBounds', originalBounds)
|
||||
// console.log('start end', startLine, endLine)
|
||||
|
||||
// otherwise if no error, add the position as a diffarea
|
||||
const adding: Omit<TrackingZone<SearchReplaceDiffAreaMetadata>, 'diffareaid'> = {
|
||||
type: 'TrackingZone',
|
||||
startLine: startLine,
|
||||
endLine: endLine,
|
||||
_URI: uri,
|
||||
metadata: {
|
||||
originalBounds: [...originalBounds],
|
||||
originalCode: block.orig,
|
||||
},
|
||||
}
|
||||
const trackingZone = this._addDiffArea(adding)
|
||||
addedTrackingZoneOfBlockNum.push(trackingZone)
|
||||
latestStreamLocationMutable = { line: startLine, addedSplitYet: false, col: 1, originalCodeStartLine: 1 }
|
||||
} // end adding diffarea
|
||||
|
||||
|
||||
// should always be in streaming state here
|
||||
if (!diffZone._streamState.isStreaming) {
|
||||
console.error('DiffZone was not in streaming state in _initializeSearchAndReplaceStream')
|
||||
continue
|
||||
}
|
||||
|
||||
// if a block is done, finish it by writing all
|
||||
if (block.state === 'done') {
|
||||
const { startLine: finalStartLine, endLine: finalEndLine } = addedTrackingZoneOfBlockNum[blockNum]
|
||||
this._writeURIText(uri, block.final,
|
||||
{ startLineNumber: finalStartLine, startColumn: 1, endLineNumber: finalEndLine, endColumn: Number.MAX_SAFE_INTEGER }, // 1-indexed
|
||||
{ shouldRealignDiffAreas: true }
|
||||
)
|
||||
diffZone._streamState.line = finalEndLine + 1
|
||||
currStreamingBlockNum = blockNum + 1
|
||||
continue
|
||||
}
|
||||
|
||||
// write the added text to the file
|
||||
if (!latestStreamLocationMutable) continue
|
||||
const oldBlock = oldBlocks[blockNum]
|
||||
const oldFinalLen = (oldBlock?.final ?? '').length
|
||||
const deltaFinalText = block.final.substring(oldFinalLen, Infinity)
|
||||
|
||||
this._writeStreamedDiffZoneLLMText(uri, block.orig, block.final, deltaFinalText, latestStreamLocationMutable)
|
||||
oldBlocks = blocks // oldblocks is only used if writingFinal
|
||||
|
||||
// const { endLine: currentEndLine } = addedTrackingZoneOfBlockNum[blockNum] // would be bad to do this because a lot of the bottom lines might be the same. more accurate to go with latestStreamLocationMutable
|
||||
// diffZone._streamState.line = currentEndLine
|
||||
diffZone._streamState.line = latestStreamLocationMutable.line
|
||||
|
||||
} // end for
|
||||
|
||||
this._refreshStylesAndDiffsInURI(uri)
|
||||
onText(params)
|
||||
},
|
||||
onFinalMessage: async (params) => {
|
||||
const { fullText } = params
|
||||
onText(params)
|
||||
|
||||
|
||||
// 1. wait 500ms and fix lint errors - call lint error workflow
|
||||
// (update react state to say "Fixing errors")
|
||||
const blocks = extractSearchReplaceBlocks(fullText)
|
||||
|
||||
if (blocks.length === 0) {
|
||||
this._notificationService.info(`Void: We ran Apply, but the LLM didn't output any changes.`)
|
||||
}
|
||||
// writeover the whole file
|
||||
let newCode = originalFileCode
|
||||
|
||||
// IMPORTANT - sort by lineNum
|
||||
addedTrackingZoneOfBlockNum.sort((a, b) => a.metadata.originalBounds[0] - b.metadata.originalBounds[0])
|
||||
|
||||
// const { model } = this._voidModelService.getModel(uri)
|
||||
// console.log('DONE - editCode!', { fullText })
|
||||
// console.log('CURRENT TEXT!!!', { current: model?.getValue() })
|
||||
// console.log('addedTrackingZoneOfBlockNum', addedTrackingZoneOfBlockNum)
|
||||
// console.log('blocks', deepClone(blocks))
|
||||
|
||||
for (let blockNum = addedTrackingZoneOfBlockNum.length - 1; blockNum >= 0; blockNum -= 1) {
|
||||
const { originalBounds } = addedTrackingZoneOfBlockNum[blockNum].metadata
|
||||
const finalCode = blocks[blockNum].final
|
||||
|
||||
if (finalCode === null) continue
|
||||
|
||||
const [originalStart, originalEnd] = originalBounds
|
||||
const lines = newCode.split('\n')
|
||||
newCode = [
|
||||
...lines.slice(0, (originalStart - 1)),
|
||||
...finalCode.split('\n'),
|
||||
...lines.slice((originalEnd - 1) + 1, Infinity)
|
||||
].join('\n')
|
||||
this._notificationService.info(`Void: We ran Fast Apply, but the LLM didn't output any changes.`)
|
||||
}
|
||||
|
||||
this._writeURIText(uri, newCode,
|
||||
'wholeFileRange',
|
||||
{ shouldRealignDiffAreas: true }
|
||||
)
|
||||
|
||||
onDone()
|
||||
resMessageDonePromise()
|
||||
try {
|
||||
this._instantlyApplySRBlocks(uri, fullText)
|
||||
onDone()
|
||||
resMessageDonePromise()
|
||||
}
|
||||
catch (e) {
|
||||
onError(e)
|
||||
}
|
||||
},
|
||||
onError: (e) => {
|
||||
onError(e)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ export interface IEditCodeService {
|
|||
|
||||
callBeforeStartApplying(opts: CallBeforeStartApplyingOpts): Promise<void>;
|
||||
startApplying(opts: StartApplyingOpts): [URI, Promise<void>] | null;
|
||||
instantlyApplySearchReplaceBlocks(opts: { uri: URI; searchReplaceBlocks: string }): void;
|
||||
addCtrlKZone(opts: AddCtrlKOpts): number | undefined;
|
||||
removeCtrlKZone(opts: { diffareaid: number }): void;
|
||||
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ export const StatusIndicatorForApplyButton = ({ applyBoxId, uri }: { applyBoxId:
|
|||
}
|
||||
|
||||
|
||||
export const ApplyButtonsHTML = ({ codeStr, applyBoxId, reapplyIcon, uri }: { codeStr: string, applyBoxId: string, reapplyIcon: boolean, uri: URI | 'current' }) => {
|
||||
export const ApplyButtonsHTML = ({ codeStr, applyBoxId, uri }: { codeStr: string, applyBoxId: string, uri: URI | 'current' }) => {
|
||||
const accessor = useAccessor()
|
||||
const editCodeService = accessor.get('IEditCodeService')
|
||||
const metricsService = accessor.get('IMetricsService')
|
||||
|
|
@ -287,12 +287,6 @@ export const ApplyButtonsHTML = ({ codeStr, applyBoxId, reapplyIcon, uri }: { co
|
|||
if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri, behavior: 'reject', removeCtrlKs: false })
|
||||
}, [applyBoxId, editCodeService])
|
||||
|
||||
// const onReapply = useCallback(() => {
|
||||
// onReject()
|
||||
// onClickSubmit()
|
||||
// }, [onReject, onClickSubmit])
|
||||
|
||||
|
||||
if (currStreamState === 'streaming') {
|
||||
return <IconShell1
|
||||
|
||||
|
|
@ -306,18 +300,14 @@ export const ApplyButtonsHTML = ({ codeStr, applyBoxId, reapplyIcon, uri }: { co
|
|||
if (currStreamState === 'idle-no-changes') {
|
||||
|
||||
return <IconShell1
|
||||
Icon={reapplyIcon ? RotateCw : Play}
|
||||
Icon={Play}
|
||||
onClick={onClickSubmit}
|
||||
{...tooltipPropsForApplyBlock({ tooltipName: reapplyIcon ? 'Reapply' : 'Apply' })}
|
||||
{...tooltipPropsForApplyBlock({ tooltipName: 'Apply' })}
|
||||
/>
|
||||
}
|
||||
|
||||
if (currStreamState === 'idle-has-changes') {
|
||||
return <>
|
||||
{/* <IconShell1
|
||||
Icon={RotateCw}
|
||||
onClick={onReapply}
|
||||
/> */}
|
||||
<IconShell1
|
||||
Icon={X}
|
||||
onClick={onReject}
|
||||
|
|
@ -375,7 +365,7 @@ export const BlockCodeApplyWrapper = ({
|
|||
<div className={`${canApply ? '' : 'hidden'} flex items-center gap-1`}>
|
||||
<JumpToFileButton uri={uri} />
|
||||
{currStreamState === 'idle-no-changes' && <CopyButton codeStr={initValue} toolTipName='Copy' />}
|
||||
<ApplyButtonsHTML uri={uri} applyBoxId={applyBoxId} codeStr={initValue} reapplyIcon={false} />
|
||||
<ApplyButtonsHTML uri={uri} applyBoxId={applyBoxId} codeStr={initValue} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1239,7 +1239,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`) },
|
||||
'edit_file': { done: `Edited file`, proposed: 'Edit file', running: loadingTitleWrapper('Editing file') },
|
||||
'replace_in_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') },
|
||||
|
|
@ -1315,8 +1315,8 @@ const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName
|
|||
desc1Info: getRelative(toolParams.uri, accessor),
|
||||
}
|
||||
},
|
||||
'edit_file': () => {
|
||||
const toolParams = _toolParams as ToolCallParams['edit_file']
|
||||
'replace_in_file': () => {
|
||||
const toolParams = _toolParams as ToolCallParams['replace_in_file']
|
||||
return {
|
||||
desc1: getBasename(toolParams.uri.fsPath),
|
||||
desc1Info: getRelative(toolParams.uri, accessor),
|
||||
|
|
@ -1459,10 +1459,10 @@ export const ListableToolItem = ({ name, onClick, isSmall, className, showDot }:
|
|||
|
||||
|
||||
|
||||
const EditToolChildren = ({ uri, changeDiff }: { uri: URI | undefined, changeDiff: string }) => {
|
||||
const EditToolChildren = ({ uri, searchReplaceBlocks }: { uri: URI | undefined, searchReplaceBlocks: string }) => {
|
||||
return <div className='!select-text cursor-auto'>
|
||||
<SmallProseWrapper>
|
||||
<ChatMarkdownRender string={changeDiff} codeURI={uri} chatMessageLocation={undefined} />
|
||||
<ChatMarkdownRender string={searchReplaceBlocks} codeURI={uri} chatMessageLocation={undefined} />
|
||||
</SmallProseWrapper>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -1513,7 +1513,6 @@ const EditToolHeaderButtons = ({ applyBoxId, uri, codeStr }: { applyBoxId: strin
|
|||
<StatusIndicatorForApplyButton applyBoxId={applyBoxId} uri={uri} />
|
||||
<JumpToFileButton uri={uri} />
|
||||
{currStreamState === 'idle-no-changes' && <CopyButton codeStr={codeStr} toolTipName='Copy' />}
|
||||
<ApplyButtonsHTML applyBoxId={applyBoxId} uri={uri} codeStr={codeStr} reapplyIcon={true} />
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
@ -1974,7 +1973,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
return <ToolHeaderWrapper {...componentParams} />
|
||||
}
|
||||
},
|
||||
'edit_file': {
|
||||
'replace_in_file': {
|
||||
resultWrapper: ({ toolMessage, messageIdx, threadId }) => {
|
||||
const accessor = useAccessor()
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
|
|
@ -1992,7 +1991,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
componentParams.children = <ToolChildrenWrapper className='bg-void-bg-3'>
|
||||
<EditToolChildren
|
||||
uri={params.uri}
|
||||
changeDiff={params.changeDiff}
|
||||
searchReplaceBlocks={params.searchReplaceBlocks}
|
||||
/>
|
||||
</ToolChildrenWrapper>
|
||||
componentParams.desc2 = <JumpToFileButton uri={params.uri} />
|
||||
|
|
@ -2009,7 +2008,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
componentParams.desc2 = <EditToolHeaderButtons
|
||||
applyBoxId={applyBoxId}
|
||||
uri={params.uri}
|
||||
codeStr={params.changeDiff}
|
||||
codeStr={params.searchReplaceBlocks}
|
||||
/>
|
||||
}
|
||||
|
||||
|
|
@ -2022,7 +2021,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
componentParams.children = <ToolChildrenWrapper className='bg-void-bg-3'>
|
||||
<EditToolChildren
|
||||
uri={params.uri}
|
||||
changeDiff={params.changeDiff}
|
||||
searchReplaceBlocks={params.searchReplaceBlocks}
|
||||
/>
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
|
|
@ -2039,7 +2038,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
{/* content */}
|
||||
<EditToolChildren
|
||||
uri={params.uri}
|
||||
changeDiff={params.changeDiff}
|
||||
searchReplaceBlocks={params.searchReplaceBlocks}
|
||||
/>
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
|
|
@ -2632,7 +2631,7 @@ const EditToolSoFar = ({ toolCallSoFar, }: { toolCallSoFar: RawToolCallObj }) =>
|
|||
|
||||
const uri = URI.file(toolCallSoFar.rawParams.uri ?? 'unknown')
|
||||
|
||||
const title = titleOfToolName['edit_file'].proposed
|
||||
const title = titleOfToolName['replace_in_file'].proposed
|
||||
|
||||
const uriDone = toolCallSoFar.doneParams.includes('uri')
|
||||
const desc1 = <span className='flex items-center'>
|
||||
|
|
@ -2650,7 +2649,7 @@ const EditToolSoFar = ({ toolCallSoFar, }: { toolCallSoFar: RawToolCallObj }) =>
|
|||
>
|
||||
<EditToolChildren
|
||||
uri={uri}
|
||||
changeDiff={toolCallSoFar.rawParams.change_diff ?? ''}
|
||||
searchReplaceBlocks={toolCallSoFar.rawParams.search_replace_blocks ?? ''}
|
||||
/>
|
||||
<IconLoading />
|
||||
</ToolHeaderWrapper>
|
||||
|
|
@ -2700,7 +2699,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 === 'edit_file' // show loading for slow tools (right now just edit)
|
||||
const toolIsGenerating = toolCallSoFar && !toolCallSoFar.isDone && toolCallSoFar.name === 'replace_in_file' // show loading for slow tools (right now just edit)
|
||||
|
||||
// ----- SIDEBAR CHAT state (local) -----
|
||||
|
||||
|
|
@ -2791,7 +2790,7 @@ export const SidebarChat = () => {
|
|||
|
||||
// the tool currently being generated
|
||||
const generatingTool = toolIsGenerating ?
|
||||
toolCallSoFar.name === 'edit_file' ? <EditToolSoFar
|
||||
toolCallSoFar.name === 'replace_in_file' ? <EditToolSoFar
|
||||
key={'curr-streaming-tool'}
|
||||
toolCallSoFar={toolCallSoFar}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import { ITerminalToolService } from './terminalToolService.js'
|
|||
import { LintErrorItem, ToolCallParams, ToolResultType } from '../common/toolsServiceTypes.js'
|
||||
import { IVoidModelService } from '../common/voidModelService.js'
|
||||
import { EndOfLinePreference } from '../../../../editor/common/model.js'
|
||||
import { basename } from '../../../../base/common/path.js'
|
||||
import { IVoidCommandBarService } from './voidCommandBarService.js'
|
||||
import { computeDirectoryTree1Deep, IDirectoryStrService, stringifyDirectoryTree1Deep } from './directoryStrService.js'
|
||||
import { IMarkerService, MarkerSeverity } from '../../../../platform/markers/common/markers.js'
|
||||
|
|
@ -37,6 +36,7 @@ const isFalsy = (u: unknown) => {
|
|||
}
|
||||
|
||||
const validateStr = (argName: string, value: unknown) => {
|
||||
if (value === null) return `Invalid LLM output: ${argName} was null.`
|
||||
if (typeof value !== 'string') throw new Error(`Invalid LLM output format: ${argName} must be a string, but it's a ${typeof value}. Value: ${value}.`)
|
||||
return value
|
||||
}
|
||||
|
|
@ -241,11 +241,12 @@ export class ToolsService implements IToolsService {
|
|||
return { uri, isRecursive, isFolder }
|
||||
},
|
||||
|
||||
edit_file: (params: RawToolParamsObj) => {
|
||||
const { uri: uriStr, change_diff: changeDiffUnknown } = params
|
||||
replace_in_file: (params: RawToolParamsObj) => {
|
||||
const { uri: uriStr, search_replace_blocks: searchReplaceBlocksUnknown } = params
|
||||
const uri = validateURI(uriStr)
|
||||
const changeDiff = validateStr('changeDiff', changeDiffUnknown)
|
||||
return { uri, changeDiff }
|
||||
const searchReplaceBlocks = validateStr('searchReplaceBlocks', searchReplaceBlocksUnknown)
|
||||
console.log('params!!!', uri, searchReplaceBlocks, 'nnnnn', searchReplaceBlocksUnknown)
|
||||
return { uri, searchReplaceBlocks }
|
||||
},
|
||||
|
||||
// ---
|
||||
|
|
@ -383,36 +384,22 @@ export class ToolsService implements IToolsService {
|
|||
await fileService.del(uri, { recursive: isRecursive })
|
||||
return { result: {} }
|
||||
},
|
||||
|
||||
edit_file: async ({ uri, changeDiff }) => {
|
||||
replace_in_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.`)
|
||||
}
|
||||
const opts = {
|
||||
uri,
|
||||
applyStr: changeDiff,
|
||||
from: 'ClickApply',
|
||||
startBehavior: 'keep-conflicts',
|
||||
} as const
|
||||
|
||||
await editCodeService.callBeforeStartApplying(opts)
|
||||
const res = editCodeService.startApplying(opts)
|
||||
if (!res) throw new Error(`The Apply model did not start running on ${basename(uri.fsPath)}. Please try again.`)
|
||||
const [diffZoneURI, applyDonePromise] = res
|
||||
|
||||
const interruptTool = () => { // must reject the applyPromiseDone promise
|
||||
editCodeService.interruptURIStreaming({ uri: diffZoneURI })
|
||||
}
|
||||
console.log('aaaa', searchReplaceBlocks)
|
||||
editCodeService.instantlyApplySearchReplaceBlocks({ uri, searchReplaceBlocks })
|
||||
|
||||
// at end, get lint errors
|
||||
const lintErrorsPromise = applyDonePromise.then(async () => {
|
||||
const lintErrorsPromise = Promise.resolve().then(async () => {
|
||||
await timeout(2000)
|
||||
const { lintErrors } = this._getLintErrors(uri)
|
||||
return { lintErrors }
|
||||
})
|
||||
|
||||
return { result: lintErrorsPromise, interruptTool }
|
||||
return { result: lintErrorsPromise }
|
||||
},
|
||||
// ---
|
||||
run_command: async ({ command, bgTerminalId }) => {
|
||||
|
|
@ -484,7 +471,7 @@ export class ToolsService implements IToolsService {
|
|||
delete_file_or_folder: (params, result) => {
|
||||
return `URI ${params.uri.fsPath} successfully deleted.`
|
||||
},
|
||||
edit_file: (params, result) => {
|
||||
replace_in_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.`
|
||||
|
|
|
|||
|
|
@ -33,6 +33,85 @@ export const MAX_TERMINAL_INACTIVE_TIME = 8 // seconds
|
|||
export const MAX_PREFIX_SUFFIX_CHARS = 20_000
|
||||
|
||||
|
||||
|
||||
export const ORIGINAL = `<<<<<<< ORIGINAL`
|
||||
export const DIVIDER = `=======`
|
||||
export const FINAL = `>>>>>>> UPDATED`
|
||||
|
||||
|
||||
|
||||
const searchReplaceBlockTemplate = `\
|
||||
${tripleTick[0]}
|
||||
${ORIGINAL}
|
||||
// ... original code goes here
|
||||
${DIVIDER}
|
||||
// ... final code goes here
|
||||
${FINAL}
|
||||
${tripleTick[1]}`
|
||||
|
||||
|
||||
|
||||
|
||||
const createSearchReplaceBlocks_systemMessage = `\
|
||||
You are a coding assistant that takes in a diff, and outputs SEARCH/REPLACE code blocks to implement the change(s) in the diff.
|
||||
The diff will be labeled \`DIFF\` and the original file will be labeled \`ORIGINAL_FILE\`.
|
||||
|
||||
Format your SEARCH/REPLACE blocks as follows:
|
||||
${searchReplaceBlockTemplate}
|
||||
|
||||
1. Your SEARCH/REPLACE block(s) must implement the diff EXACTLY. Do NOT leave anything out.
|
||||
|
||||
2. You are allowed to output multiple SEARCH/REPLACE blocks to implement the change.
|
||||
|
||||
3. Assume any comments in the diff are PART OF THE CHANGE. Include them in the output.
|
||||
|
||||
4. Your output should consist ONLY of SEARCH/REPLACE blocks. Do NOT output any text or explanations before or after this.
|
||||
|
||||
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.
|
||||
|
||||
7. Each ORIGINAL text must be DISJOINT from all other ORIGINAL text.
|
||||
|
||||
## EXAMPLE 1
|
||||
DIFF
|
||||
${tripleTick[0]}
|
||||
// ... existing code
|
||||
let x = 6.5
|
||||
// ... existing code
|
||||
${tripleTick[1]}
|
||||
|
||||
ORIGINAL_FILE
|
||||
${tripleTick[0]}
|
||||
let w = 5
|
||||
let x = 6
|
||||
let y = 7
|
||||
let z = 8
|
||||
${tripleTick[1]}
|
||||
|
||||
ACCEPTED OUTPUT
|
||||
${tripleTick[0]}
|
||||
${ORIGINAL}
|
||||
let x = 6
|
||||
${DIVIDER}
|
||||
let x = 6.5
|
||||
${FINAL}
|
||||
${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:
|
||||
${searchReplaceBlockTemplate}
|
||||
|
||||
1. You are allowed to output multiple SEARCH/REPLACE blocks to implement your desired change. Just write them sequentially.
|
||||
|
||||
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.
|
||||
|
||||
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.`
|
||||
|
||||
|
||||
// ======================================================== tools ========================================================
|
||||
const changesExampleContent = `\
|
||||
// ... existing code ...
|
||||
|
|
@ -43,10 +122,10 @@ const changesExampleContent = `\
|
|||
// {{change 3}}
|
||||
// ... existing code ...`
|
||||
|
||||
const editToolDescriptionExample = `\
|
||||
${tripleTick[0]}
|
||||
${changesExampleContent}
|
||||
${tripleTick[1]}`
|
||||
// const editToolDescriptionExample = `\
|
||||
// ${tripleTick[0]}
|
||||
// ${changesExampleContent}
|
||||
// ${tripleTick[1]}`
|
||||
|
||||
const fileNameEditExample = `${tripleTick[0]}typescript
|
||||
/Users/username/Dekstop/my_project/app.ts
|
||||
|
|
@ -199,26 +278,18 @@ export const voidTools = {
|
|||
},
|
||||
},
|
||||
|
||||
edit_file: { // APPLY TOOL
|
||||
name: 'edit_file',
|
||||
description: `Edits the contents of a file given the file's URI and a description.`,
|
||||
replace_in_file: { // APPLY TOOL
|
||||
name: 'replace_in_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'),
|
||||
change_diff: {
|
||||
description: `\
|
||||
A code diff describing the change to make to the file. \
|
||||
Your DIFF is the only context that will be given to another LLM to apply the change, so it must be accurate and complete. \
|
||||
Your DIFF MUST be wrapped in triple backticks. \
|
||||
NEVER re-write the whole file. Always bias towards writing as little as possible. \
|
||||
Use comments like "// ... existing code ..." to condense your writing. \
|
||||
Here's an example of a good output:\n${editToolDescriptionExample}`
|
||||
}
|
||||
search_replace_blocks: { description: replaceTool_description }
|
||||
},
|
||||
},
|
||||
|
||||
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 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.`,
|
||||
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.`,
|
||||
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.' },
|
||||
|
|
@ -502,74 +573,17 @@ Please finish writing the new file by applying the change to the original file.
|
|||
|
||||
// ======================================================== apply (fast apply - search/replace) ========================================================
|
||||
|
||||
export const searchReplaceGivenDescription_systemMessage = createSearchReplaceBlocks_systemMessage
|
||||
|
||||
|
||||
export const ORIGINAL = `<<<<<<< ORIGINAL`
|
||||
export const DIVIDER = `=======`
|
||||
export const FINAL = `>>>>>>> UPDATED`
|
||||
|
||||
export const searchReplace_systemMessage = `\
|
||||
You are a coding assistant that takes in a diff, and outputs SEARCH/REPLACE code blocks to implement the change(s) in the diff.
|
||||
The diff will be labeled \`DIFF\` and the original file will be labeled \`ORIGINAL_FILE\`.
|
||||
|
||||
Format your SEARCH/REPLACE blocks as follows:
|
||||
${tripleTick[0]}
|
||||
${ORIGINAL}
|
||||
// ... original code goes here
|
||||
${DIVIDER}
|
||||
// ... final code goes here
|
||||
${FINAL}
|
||||
${tripleTick[1]}
|
||||
|
||||
1. Your SEARCH/REPLACE block(s) must implement the diff EXACTLY. Do NOT leave anything out.
|
||||
|
||||
2. You are allowed to output multiple SEARCH/REPLACE blocks to implement the change.
|
||||
|
||||
3. Assume any comments in the diff are PART OF THE CHANGE. Include them in the output.
|
||||
|
||||
4. Your output should consist ONLY of SEARCH/REPLACE blocks. Do NOT output any text or explanations before or after this.
|
||||
|
||||
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.
|
||||
|
||||
7. Each ORIGINAL text must be DISJOINT from all other ORIGINAL text.
|
||||
|
||||
## EXAMPLE 1
|
||||
DIFF
|
||||
${tripleTick[0]}
|
||||
// ... existing code
|
||||
let x = 6.5
|
||||
// ... existing code
|
||||
${tripleTick[1]}
|
||||
|
||||
ORIGINAL_FILE
|
||||
${tripleTick[0]}
|
||||
let w = 5
|
||||
let x = 6
|
||||
let y = 7
|
||||
let z = 8
|
||||
${tripleTick[1]}
|
||||
|
||||
## ACCEPTED OUTPUT
|
||||
${tripleTick[0]}
|
||||
${ORIGINAL}
|
||||
let x = 6
|
||||
${DIVIDER}
|
||||
let x = 6.5
|
||||
${FINAL}
|
||||
${tripleTick[1]}
|
||||
`
|
||||
|
||||
export const searchReplace_userMessage = ({ originalCode, applyStr }: { originalCode: string, applyStr: string }) => `\
|
||||
export const searchReplaceGivenDescription_userMessage = ({ originalCode, applyStr }: { originalCode: string, applyStr: string }) => `\
|
||||
DIFF
|
||||
${applyStr}
|
||||
|
||||
ORIGINAL_FILE
|
||||
${tripleTick[0]}
|
||||
${originalCode}
|
||||
${tripleTick[1]}
|
||||
`
|
||||
${tripleTick[1]}`
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
'edit_file': 'edits',
|
||||
'replace_in_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 },
|
||||
// ---
|
||||
'edit_file': { uri: URI, changeDiff: string },
|
||||
'replace_in_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 },
|
||||
// ---
|
||||
'edit_file': Promise<{ lintErrors: LintErrorItem[] | null }>,
|
||||
'replace_in_file': Promise<{ lintErrors: LintErrorItem[] | null }>,
|
||||
'create_file_or_folder': {},
|
||||
'delete_file_or_folder': {},
|
||||
// ---
|
||||
|
|
|
|||
Loading…
Reference in a new issue