diff --git a/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts b/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts index 3d64033e..5518d810 100644 --- a/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts +++ b/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts @@ -7,7 +7,7 @@ import { IWorkspaceContextService } from '../../../../platform/workspace/common/ import { IEditorService } from '../../../services/editor/common/editorService.js'; import { ChatMessage } from '../common/chatThreadServiceTypes.js'; import { getIsReasoningEnabledState, getMaxOutputTokens, getModelCapabilities } from '../common/modelCapabilities.js'; -import { toolCallXMLStr, chat_systemMessage, ToolName } from '../common/prompt/prompts.js'; +import { reParsedToolXMLString, chat_systemMessage, ToolName } from '../common/prompt/prompts.js'; import { AnthropicLLMChatMessage, AnthropicReasoning, LLMChatMessage, LLMFIMMessage, OpenAILLMChatMessage, RawToolParamsObj } from '../common/sendLLMMessageTypes.js'; import { IVoidSettingsService } from '../common/voidSettingsService.js'; import { ChatMode, FeatureName, ModelSelection } from '../common/voidSettingsTypes.js'; @@ -199,7 +199,7 @@ const prepareMessages_XML_tools = (messages: SimpleLLMMessage[], supportsAnthrop // alternatively, could just hold onto the original output, but this way requires less piping raw strings everywhere let content: LLMChatMessage['content'] = c.content if (next?.role === 'tool') { - content = `${content}\n\n${toolCallXMLStr(next.name, next.rawParams)}` + content = `${content}\n\n${reParsedToolXMLString(next.name, next.rawParams)}` } // anthropic reasoning @@ -442,7 +442,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess `...Directories string cut off, use tools to read more...` : `...Directories string cut off, ask user for more if necessary...` }) - const includeXMLToolDefinitions = specialToolFormat === undefined + const includeXMLToolDefinitions = !specialToolFormat const runningTerminalIds = this.terminalToolService.listTerminalIds() const systemMessage = chat_systemMessage({ workspaceFolders, openedURIs, directoryStr, activeURI, runningTerminalIds, chatMode, includeXMLToolDefinitions }) diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index 653cf3af..78fafa7c 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -1562,20 +1562,22 @@ class EditCodeService extends Disposable implements IEditCodeService { } - const errContentOfInvalidStr = (str: string & ReturnType, blockOrig: string, blockNum: number, blocks: ExtractedSearchReplaceBlock[]) => { + 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.' + 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 @@ -1610,7 +1612,7 @@ class EditCodeService extends Disposable implements IEditCodeService { const addedTrackingZoneOfBlockNum: TrackingZone[] = [] diffZone._streamState.line = 1 - const N_RETRIES = 2 + const N_RETRIES = 4 // allowed to throw errors - this is called inside a promise that handles everything const runSearchReplace = async () => { @@ -1684,17 +1686,25 @@ class EditCodeService extends Disposable implements IEditCodeService { // 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 - if (typeof originalBounds === 'string') { + // 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:', originalBounds) + console.log('error:', errorMessage) console.log('block.orig:', block.orig) console.log('---------') - const content = errContentOfInvalidStr(originalBounds, block.orig, blockNum, blocks) + const content = errContentOfInvalidStr(errorMessage, block.orig) messages.push( { role: 'assistant', content: fullText }, // latest output { role: 'user', content: content } // user explanation of what's wrong diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-editor-widgets-tsx/VoidCommandBar.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-editor-widgets-tsx/VoidCommandBar.tsx index dcf97f8e..40b34d14 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-editor-widgets-tsx/VoidCommandBar.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-editor-widgets-tsx/VoidCommandBar.tsx @@ -111,7 +111,7 @@ const VoidCommandBar = ({ uri, editor }: VoidCommandBarProps) => { const { model } = await voidModelService.getModelSafe(nextURI) if (model) { // switch to the URI - editorService.openCodeEditor({ resource: nextURI, options: { revealIfVisible: true } }, editor) + editorService.openCodeEditor({ resource: model.uri, options: { revealIfVisible: true } }, editor) } } diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx index 91ec82b3..3a885c6c 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx @@ -392,8 +392,32 @@ const VoidOnboardingContent = () => { // page 1 state const [wantToUseOption, setWantToUseOption] = useState('smart') - // page 2 state - const [selectedProviderName, setSelectedProviderName] = useState(null) + // Replace the single selectedProviderName with four separate states + // page 2 state - each tab gets its own state + const [selectedIntelligentProvider, setSelectedIntelligentProvider] = useState('anthropic'); + const [selectedPrivateProvider, setSelectedPrivateProvider] = useState('ollama'); + const [selectedAffordableProvider, setSelectedAffordableProvider] = useState('gemini'); + const [selectedAllProvider, setSelectedAllProvider] = useState('anthropic'); + + // Helper function to get the current selected provider based on active tab + const getSelectedProvider = (): ProviderName => { + switch (wantToUseOption) { + case 'smart': return selectedIntelligentProvider; + case 'private': return selectedPrivateProvider; + case 'cheap': return selectedAffordableProvider; + case 'all': return selectedAllProvider; + } + } + + // Helper function to set the selected provider for the current tab + const setSelectedProvider = (provider: ProviderName) => { + switch (wantToUseOption) { + case 'smart': setSelectedIntelligentProvider(provider); break; + case 'private': setSelectedPrivateProvider(provider); break; + case 'cheap': setSelectedAffordableProvider(provider); break; + case 'all': setSelectedAllProvider(provider); break; + } + } const providerNamesOfWantToUseOption: { [wantToUseOption in WantToUseOption]: ProviderName[] } = { smart: ['anthropic', 'openAI', 'gemini', 'openRouter'], @@ -403,6 +427,7 @@ const VoidOnboardingContent = () => { } + const selectedProviderName = getSelectedProvider(); const didFillInProviderSettings = selectedProviderName && voidSettingsState.settingsOfProvider[selectedProviderName]._didFillInProviderSettings const isApiKeyLongEnoughIfApiKeyExists = selectedProviderName && voidSettingsState.settingsOfProvider[selectedProviderName].apiKey ? voidSettingsState.settingsOfProvider[selectedProviderName].apiKey.length > 15 : true const isAtLeastOneModel = selectedProviderName && voidSettingsState.settingsOfProvider[selectedProviderName].models.length >= 1 @@ -438,10 +463,21 @@ const VoidOnboardingContent = () => { all: "", } - // set the selected provider name to be the zeroth option when the user changes the page + // Modified: initialize separate provider states on initial render instead of watching wantToUseOption changes useEffect(() => { - setSelectedProviderName(providerNamesOfWantToUseOption[wantToUseOption][0]); - }, [wantToUseOption]); + if (selectedIntelligentProvider === undefined) { + setSelectedIntelligentProvider(providerNamesOfWantToUseOption['smart'][0]); + } + if (selectedPrivateProvider === undefined) { + setSelectedPrivateProvider(providerNamesOfWantToUseOption['private'][0]); + } + if (selectedAffordableProvider === undefined) { + setSelectedAffordableProvider(providerNamesOfWantToUseOption['cheap'][0]); + } + if (selectedAllProvider === undefined) { + setSelectedAllProvider(providerNamesOfWantToUseOption['all'][0]); + } + }, []); // reset the page to page 0 if the user redos onboarding useEffect(() => { @@ -551,33 +587,75 @@ const VoidOnboardingContent = () => { - {/* Provider Buttons */} -
- {/* Provider options - mapped for each wantToUseOption type */} - {(['smart', 'private', 'cheap', 'all'] as WantToUseOption[]).map((option) => ( -
- {(option === 'all' ? providerNames : providerNamesOfWantToUseOption[option]).map((providerName) => { - const isSelected = selectedProviderName === providerName - return ( - - ) - })} -
- ))} + {/* Provider Buttons - Modified to use separate components for each tab */} +
+ {/* Intelligent tab */} +
+ {providerNamesOfWantToUseOption['smart'].map((providerName) => { + const isSelected = selectedIntelligentProvider === providerName; + return ( + + ); + })} +
+ + {/* Private tab */} +
+ {providerNamesOfWantToUseOption['private'].map((providerName) => { + const isSelected = selectedPrivateProvider === providerName; + return ( + + ); + })} +
+ + {/* Affordable tab */} +
+ {providerNamesOfWantToUseOption['cheap'].map((providerName) => { + const isSelected = selectedAffordableProvider === providerName; + return ( + + ); + })} +
+ + {/* All tab */} +
+ {providerNames.map((providerName) => { + const isSelected = selectedAllProvider === providerName; + return ( + + ); + })} +
{/* Description */} diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index ab92f45f..c87a6c98 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -624,7 +624,7 @@ export const FeaturesTab = () => {

{displayInfoOfFeatureName('Autocomplete')}

- Experimental. Only works with FIM models. + Experimental.{' '} { data-tooltip-content='We recommend using qwen2.5-coder:1.5b with Ollama.' data-tooltip-class-name='void-max-w-[20px]' > - * + Only works with FIM models.*
diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index 0c33c7f7..19dcf545 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -205,7 +205,7 @@ export const availableTools = (chatMode: ChatMode) => { return tools } -const availableXMLToolsStr = (tools: InternalToolInfo[]) => { +const toolCallDefinitionsXMLString = (tools: InternalToolInfo[]) => { return `${tools.map((t, i) => { const params = Object.keys(t.params).map(paramName => `<${paramName}>${t.params[paramName].description}`).join('\n') return `\ @@ -217,7 +217,7 @@ Format: }).join('\n\n')}` } -export const toolCallXMLStr = (toolName: ToolName, toolParams: RawToolParamsObj) => { +export const reParsedToolXMLString = (toolName: ToolName, toolParams: RawToolParamsObj) => { const params = Object.keys(toolParams).map(paramName => `<${paramName}>${toolParams[paramName as ToolParamName]}`).join('\n') return `\ <${toolName}>${!params ? '' : `\n${params}`} @@ -234,12 +234,12 @@ const systemToolsXMLPrompt = (chatMode: ChatMode) => { const toolXMLDefinitions = (`\ Available tools: -${availableXMLToolsStr(tools)}`) +${toolCallDefinitionsXMLString(tools)}`) const toolCallXMLGuidelines = (`\ Tool calling details: -- Once you write a tool call, you must STOP and WAIT for the result. -- To call a tool, write its name and parameters in one of the XML formats specified above at the BOTTOM of your response. +- To call a tool, write its name and parameters in one of the XML formats specified above. +- After you write the tool call, you must STOP and WAIT for the result. - All parameters are REQUIRED unless noted otherwise. - You are only allowed to output ONE tool call, and it must be at the END of your response. - Your tool call will be executed immediately, and the results will appear in the following user message.`) @@ -341,9 +341,9 @@ ${details.map((d, i) => `${i + 1}. ${d}`).join('\n\n')}`) const ansStrs: string[] = [] ansStrs.push(header) ansStrs.push(sysInfo) - ansStrs.push(fsInfo) if (toolDefinitions) ansStrs.push(toolDefinitions) ansStrs.push(importantDetails) + ansStrs.push(fsInfo) const fullSystemMsgStr = ansStrs .join('\n\n\n')