better terminal context and search/replace fix

This commit is contained in:
Andrew Pareles 2025-04-28 15:56:03 -07:00
parent 7605931abe
commit 6b4142e9b1
7 changed files with 43 additions and 35 deletions

View file

@ -483,8 +483,8 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
})
const includeXMLToolDefinitions = !specialToolFormat
const runningTerminalIds = this.terminalToolService.listTerminalIds()
const systemMessage = chat_systemMessage({ workspaceFolders, openedURIs, directoryStr, activeURI, runningTerminalIds, chatMode, includeXMLToolDefinitions })
const persistentTerminalIDs = this.terminalToolService.listTerminalIds()
const systemMessage = chat_systemMessage({ workspaceFolders, openedURIs, directoryStr, activeURI, persistentTerminalIDs, chatMode, includeXMLToolDefinitions })
return systemMessage
}

View file

@ -1594,10 +1594,10 @@ class EditCodeService extends Disposable implements IEditCodeService {
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))
throw new Error(this._errContentOfInvalidStr('Not found', b.orig))
const j = modelStr.lastIndexOf(b.orig)
if (i !== j)
throw new Error(this._errContentOfInvalidStr('Not unique', replacements[i].block.orig))
throw new Error(this._errContentOfInvalidStr('Not unique', b.orig))
replacements.push({
origStart: i,
@ -1611,9 +1611,8 @@ class EditCodeService extends Disposable implements IEditCodeService {
// 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))
if (replacements[i].origStart <= replacements[i - 1].origEnd) {
throw new Error(this._errContentOfInvalidStr('Has overlap', replacements[i]?.block?.orig))
}
}

View file

@ -1321,8 +1321,8 @@ 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`) },
'rewrite_file': { done: `Rewrote file`, proposed: 'Rewrite file', running: loadingTitleWrapper('Rewriting file') },
'edit_file': { done: `Edited file`, proposed: 'Edit file', running: loadingTitleWrapper('Editing file') },
'rewrite_file': { done: `Wrote file`, proposed: 'Write file', running: loadingTitleWrapper('Writing 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') },
@ -2065,12 +2065,12 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
},
'rewrite_file': {
resultWrapper: (params) => {
return <EditTool {...params} content={params.toolMessage.params.newContent} />
return <EditTool {...params} content={`${'```\n'}${params.toolMessage.params.newContent}${'\n```'}`} />
}
},
'edit_file': {
resultWrapper: (params) => {
return <EditTool {...params} content={params.toolMessage.params.searchReplaceBlocks} />
return <EditTool {...params} content={`${'```\n'}${params.toolMessage.params.searchReplaceBlocks}${'\n```'}`} />
}
},

View file

@ -136,7 +136,7 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
async killTerminal(terminalId: string) {
const terminal = this.terminalInstanceOfId[terminalId]
if (!terminal) throw new Error(`Kill Terminal: Terminal with ID ${terminalId} did not exist.`);
if (!terminal) throw new Error(`Kill Terminal: Terminal with ID ${terminalId} does not exist.`);
terminal.dispose(TerminalExitReason.Extension)
delete this.terminalInstanceOfId[terminalId]
return
@ -168,7 +168,7 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
let terminalId: string
if (isBG) { // BG process
terminal = this.terminalInstanceOfId[bgTerminalId];
if (!terminal) throw new Error(`Unexpected internal error: Terminal with ID ${bgTerminalId} did not exist.`);
if (!terminal) throw new Error(`Unexpected internal error: Terminal with ID ${bgTerminalId} does not exist.`);
terminalId = bgTerminalId
}
else {

View file

@ -37,7 +37,7 @@ const isFalsy = (u: unknown) => {
const validateStr = (argName: string, value: unknown) => {
if (value === null) throw new Error(`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(n) ${typeof value}. Value: ${JSON.stringify(value)}.`)
if (typeof value !== 'string') throw new Error(`Invalid LLM output format: ${argName} must be a string, but its type is "${typeof value}". Full value: ${JSON.stringify(value)}.`)
return value
}
@ -46,7 +46,7 @@ const validateStr = (argName: string, value: unknown) => {
// TODO!!!! check to make sure folder/file exists
const validateURI = (uriStr: unknown) => {
if (uriStr === null) throw new Error(`Invalid LLM output: uri was null.`)
if (typeof uriStr !== 'string') throw new Error(`Invalid LLM output format: Provided uri must be a string, but it's a(n) ${typeof uriStr}. Value: ${uriStr}.`)
if (typeof uriStr !== 'string') throw new Error(`Invalid LLM output format: Provided uri must be a string, but it's a(n) ${typeof uriStr}. Full value: ${JSON.stringify(uriStr)}.`)
const uri = URI.file(uriStr)
return uri
}

View file

@ -41,19 +41,17 @@ export const FINAL = `>>>>>>> UPDATED`
const searchReplaceBlockTemplate = `\
${tripleTick[0]}
${ORIGINAL}
// ... original code 1 goes here
// ... original code goes here
${DIVIDER}
// ... final code 1 goes here
// ... final code goes here
${FINAL}
${ORIGINAL}
// ... original code 2 goes here
// ... original code goes here
${DIVIDER}
// ... final code 2 goes here
${FINAL}
...
${tripleTick[1]}`
// ... final code goes here
${FINAL}`
@ -63,7 +61,9 @@ You are a coding assistant that takes in a diff, and outputs SEARCH/REPLACE code
The diff will be labeled \`DIFF\` and the original file will be labeled \`ORIGINAL_FILE\`.
Format your SEARCH/REPLACE blocks as follows:
${tripleTick[0]}
${searchReplaceBlockTemplate}
${tripleTick[1]}
1. Your SEARCH/REPLACE block(s) must implement the diff EXACTLY. Do NOT leave anything out.
@ -106,18 +106,21 @@ ${tripleTick[1]}`
const replaceTool_description = `\
A string of SEARCH/REPLACE block(s) to apply to the given file.
You are encouraged to output multiple changes in this string when possible. For example:
A string of SEARCH/REPLACE block(s) which will be applied to the given file.
Your SEARCH/REPLACE blocks string must be formatted as follows:
${searchReplaceBlockTemplate}
Guidelines:
1. 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.
## Guidelines:
2. Each ORIGINAL text must be large enough to uniquely identify the change in the file. However, bias towards writing as little as possible.
1. You are encouraged to output multiple changes whenever possible.
3. Each ORIGINAL text must be DISJOINT from all other ORIGINAL text.
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.
4. This field is a STRING (not an array). You should wrap the string in triple backticks.`
3. Each ORIGINAL text must be large enough to uniquely identify the change. However, bias towards writing as little as possible.
4. Each ORIGINAL text must be DISJOINT from all other ORIGINAL text.
5. This field is a STRING (not an array).`
// ======================================================== tools ========================================================
@ -300,7 +303,7 @@ export const voidTools = {
description: `Edits a file, deleting all the old contents and replacing them with your new contents. Use this tool if you want to edit a file you just created.`,
params: {
...uriParam('file'),
new_content: { description: `The new contents of the file.` }
new_content: { description: `The new contents of the file. Must be a string.` }
},
},
@ -403,7 +406,7 @@ ${toolCallXMLGuidelines}`
// ======================================================== chat (normal, gather, agent) ========================================================
export const chat_systemMessage = ({ workspaceFolders, openedURIs, activeURI, runningTerminalIds, directoryStr, chatMode: mode, includeXMLToolDefinitions }: { workspaceFolders: string[], directoryStr: string, openedURIs: string[], activeURI: string | undefined, runningTerminalIds: string[], chatMode: ChatMode, includeXMLToolDefinitions: boolean }) => {
export const chat_systemMessage = ({ workspaceFolders, openedURIs, activeURI, persistentTerminalIDs, directoryStr, chatMode: mode, includeXMLToolDefinitions }: { workspaceFolders: string[], directoryStr: string, openedURIs: string[], activeURI: string | undefined, persistentTerminalIDs: string[], chatMode: ChatMode, includeXMLToolDefinitions: boolean }) => {
const header = (`You are an expert coding ${mode === 'agent' ? 'agent' : 'assistant'} whose job is \
${mode === 'agent' ? `to help the user develop, run, and make changes to their codebase.`
: mode === 'gather' ? `to search, understand, and reference files in the user's codebase.`
@ -425,9 +428,9 @@ ${workspaceFolders.join('\n') || 'NO WORKSPACE OPEN'}
${activeURI}
- Open files:
${openedURIs.join('\n') || 'NO OPENED EDITORS'}${''/* separator */}${mode === 'agent' && runningTerminalIds.length !== 0 ? `
${openedURIs.join('\n') || 'NO OPENED EDITORS'}${''/* separator */}${mode === 'agent' && persistentTerminalIDs.length !== 0 ? `
- Existing terminal IDs: ${runningTerminalIds.join(', ')}` : ''}
- Persistent terminal IDs available for you to run commands in: ${persistentTerminalIDs.join(', ')}` : ''}
</system_info>`)
@ -510,7 +513,7 @@ ${details.map((d, i) => `${i + 1}. ${d}`).join('\n\n')}`)
// // log all prompts
// for (const chatMode of ['agent', 'gather', 'normal'] satisfies ChatMode[]) {
// console.log(`========================================= SYSTEM MESSAGE FOR ${chatMode} ===================================\n`,
// chat_systemMessage({ chatMode, workspaceFolders: [], openedURIs: [], activeURI: 'pee', runningTerminalIds: [], directoryStr: 'lol', }))
// chat_systemMessage({ chatMode, workspaceFolders: [], openedURIs: [], activeURI: 'pee', persistentTerminalIDs: [], directoryStr: 'lol', }))
// }

View file

@ -162,6 +162,10 @@ const _sendOpenAICompatibleFIM = async ({ messages: { prefix, suffix, stopTokens
const toOpenAICompatibleTool = (toolInfo: InternalToolInfo) => {
const { name, description, params } = toolInfo
const paramsWithType: { [s: string]: { description: string; type: 'string' } } = {}
for (const key in params) { paramsWithType[key] = { ...params[key], type: 'string' } }
return {
type: 'function',
function: {
@ -358,12 +362,14 @@ const _openaiCompatibleList = async ({ onSuccess: onSuccess_, onError: onError_,
// ------------ ANTHROPIC (HELPERS) ------------
const toAnthropicTool = (toolInfo: InternalToolInfo) => {
const { name, description, params } = toolInfo
const paramsWithType: { [s: string]: { description: string; type: 'string' } } = {}
for (const key in params) { paramsWithType[key] = { ...params[key], type: 'string' } }
return {
name: name,
description: description,
input_schema: {
type: 'object',
properties: params,
properties: paramsWithType,
// required: Object.keys(params),
},
} satisfies Anthropic.Messages.Tool