From a9aa9a26ea697fd550f0fd2214f7a2113dda2a6d Mon Sep 17 00:00:00 2001 From: Ismail Pelaseyed Date: Fri, 9 May 2025 14:59:27 +0200 Subject: [PATCH 01/20] feat: add support for applying shellscripts and bash directly to terminal --- .../src/markdown/ApplyBlockHoverButtons.tsx | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 41b5a649..7c994e08 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -243,26 +243,51 @@ export const ApplyButtonsHTML = ({ codeStr, applyBoxId, uri, + language, }: { codeStr: string, applyBoxId: string, -} & ({ + language?: string, +} & { uri: URI | 'current'; -}) -) => { +}) => { const accessor = useAccessor() const editCodeService = accessor.get('IEditCodeService') const metricsService = accessor.get('IMetricsService') + const terminalToolService = accessor.get('ITerminalToolService') const settingsState = useSettingsState() const isDisabled = !!isFeatureNameDisabled('Apply', settingsState) || !applyBoxId const { currStreamStateRef, setApplying } = useApplyStreamState({ applyBoxId }) + const isShellLanguage = language === 'bash' || language === 'shellscript' const onClickSubmit = useCallback(async () => { if (currStreamStateRef.current === 'streaming') return + // For shell scripts, run in terminal instead of applying to file + if (isShellLanguage) { + try { + // Create a terminal if none exists or use terminal 1 + const terminalIds = terminalToolService.listPersistentTerminalIds() + let terminalId = '1'; + if (!terminalIds.includes(terminalId)) { + terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }) + } + await terminalToolService.runCommand( + codeStr, + { type: 'persistent', persistentTerminalId: terminalId } + ); + await terminalToolService.focusPersistentTerminal(terminalId) + metricsService.capture('Execute Shell', { length: codeStr.length }) + } catch (e) { + console.error('Failed to execute in terminal:', e) + } + return; + } + + // Normal file apply logic for non-shell languages await editCodeService.callBeforeApplyOrEdit(uri) const [newApplyingUri, applyDonePromise] = editCodeService.startApplying({ @@ -281,7 +306,7 @@ export const ApplyButtonsHTML = ({ }) metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only - }, [setApplying, currStreamStateRef, editCodeService, codeStr, uri, applyBoxId, metricsService]) + }, [setApplying, currStreamStateRef, editCodeService, codeStr, uri, applyBoxId, metricsService, isShellLanguage, terminalToolService]) const onClickStop = useCallback(() => { @@ -451,7 +476,7 @@ export const BlockCodeApplyWrapper = ({
{currStreamState === 'idle-no-changes' && } - +
From 168b92f6c4816fe00ec79b211e8d69945a6d01c7 Mon Sep 17 00:00:00 2001 From: Steven Wexler Date: Fri, 9 May 2025 15:38:32 -0600 Subject: [PATCH 02/20] Add Void: Copy Prompt as menu item to the ExplorerContext window --- .../contrib/void/browser/fileService.ts | 89 +++++++++++++++++++ .../contrib/void/browser/void.contribution.ts | 3 + .../contrib/void/common/prompt/prompts.ts | 6 +- 3 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 src/vs/workbench/contrib/void/browser/fileService.ts diff --git a/src/vs/workbench/contrib/void/browser/fileService.ts b/src/vs/workbench/contrib/void/browser/fileService.ts new file mode 100644 index 00000000..8857feb4 --- /dev/null +++ b/src/vs/workbench/contrib/void/browser/fileService.ts @@ -0,0 +1,89 @@ +import { localize2 } from '../../../../nls.js'; +import { URI } from '../../../../base/common/uri.js'; +import { Action2, registerAction2, MenuId } from '../../../../platform/actions/common/actions.js'; +import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js'; +import { INotificationService } from '../../../../platform/notification/common/notification.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; +import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; +import { IDirectoryStrService } from '../common/directoryStrService.js'; +import { readFile, DEFAULT_FILE_SIZE_LIMIT } from '../common/prompt/prompts.js'; + + + +async function filePrompt(fileService: IFileService, uri: URI, clipboardService: IClipboardService): Promise { + const { val } = await readFile(fileService, uri, DEFAULT_FILE_SIZE_LIMIT) + const fileName = uri.fsPath.split('/').pop() ?? '' + if (!val) { + throw new Error('Failed to copy prompt') + } + const prompt = ` +${fileName}: +\`\`\` +${val} +\`\`\``.trim() + return prompt +} + +/** + * Add a menu item to the explorer context menu that copies a prompt for the selected file or directory. + * + * Example file prompt: + * + * ``` + * index.js: + * \`\`\` + * console.log('Hello World!'); + * \`\`\` + * + * Example directory prompt: + * ``` + * Directory of /path/to/src: + * src/ + * ├── index.ts + * ├── src.ts + * ├── latest/ + * │ ├── index.ts + * │ └── src.ts + * ├── types.ts + * └── util.ts + * ``` + */ +class FilePromptActionService extends Action2 { + private static readonly VOID_COPY_FILE_PROMPT_ID = 'void.copyfileprompt' + + constructor() { + super({ + id: FilePromptActionService.VOID_COPY_FILE_PROMPT_ID, + title: localize2('voidCopyPrompt', "Void: Copy Prompt"), + menu: [{ + id: MenuId.ExplorerContext, + group: '8_void', + order: 1, + }] + }); + } + + async run(accessor: ServicesAccessor, uri: URI): Promise { + try { + const fileService = accessor.get(IFileService); + const clipboardService = accessor.get(IClipboardService) + const directoryStrService = accessor.get(IDirectoryStrService) + const stat = await fileService.resolve(uri) + const prompt = stat.isFile + ? await filePrompt(fileService, uri, clipboardService) + : await directoryStrService.getDirectoryStrTool(uri) + await clipboardService.writeText(prompt) + } catch (error) { + const notificationService = accessor.get(INotificationService) + FilePromptActionService._onError(notificationService, error) + } + } + + private static _onError(notificationService: INotificationService, error: Error) { + const errorMessage = localize2('voidCopyPromptError', 'Failed to copy prompt') + notificationService.error(errorMessage.value) + throw error + } +} + +registerAction2(FilePromptActionService) diff --git a/src/vs/workbench/contrib/void/browser/void.contribution.ts b/src/vs/workbench/contrib/void/browser/void.contribution.ts index c0e84606..3ab89abf 100644 --- a/src/vs/workbench/contrib/void/browser/void.contribution.ts +++ b/src/vs/workbench/contrib/void/browser/void.contribution.ts @@ -58,6 +58,9 @@ import './voidOnboardingService.js' // register misc service import './miscWokrbenchContrib.js' +// register file service (for explorer context menu) +import './fileService.js' + // ---------- common (unclear if these actually need to be imported, because they're already imported wherever they're used) ---------- // llmMessage diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index 613142b8..5f53cd07 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -525,7 +525,9 @@ ${details.map((d, i) => `${i + 1}. ${d}`).join('\n\n')}`) // chat_systemMessage({ chatMode, workspaceFolders: [], openedURIs: [], activeURI: 'pee', persistentTerminalIDs: [], directoryStr: 'lol', })) // } -const readFile = async (fileService: IFileService, uri: URI, fileSizeLimit: number): Promise<{ +export const DEFAULT_FILE_SIZE_LIMIT = 2_000_000 + +export const readFile = async (fileService: IFileService, uri: URI, fileSizeLimit: number): Promise<{ val: string, truncated: boolean, fullFileLen: number, @@ -561,7 +563,7 @@ export const chat_userMessageContent = async (instructions: string, currSelns: S selnsStrs = await Promise.all(currSelns?.map(async (s) => { if (s.type === 'File' || s.type === 'CodeSelection') { - const { val } = await readFile(opts.fileService, s.uri, 2_000_000) + const { val } = await readFile(opts.fileService, s.uri, DEFAULT_FILE_SIZE_LIMIT) const lineNumAdd = s.type === 'CodeSelection' ? lineNumAddition(s.range) : '' const content = val === null ? 'null' : `${tripleTick[0]}${s.language}\n${val}\n${tripleTick[1]}` const str = `${s.uri.fsPath}${lineNumAdd}:\n${content}` From 08274637e15ffc70ddca913f302c3cf5f28f3fd9 Mon Sep 17 00:00:00 2001 From: Steven Wexler Date: Fri, 9 May 2025 15:40:34 -0600 Subject: [PATCH 03/20] Small touchups --- src/vs/workbench/contrib/void/browser/fileService.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/fileService.ts b/src/vs/workbench/contrib/void/browser/fileService.ts index 8857feb4..518803ed 100644 --- a/src/vs/workbench/contrib/void/browser/fileService.ts +++ b/src/vs/workbench/contrib/void/browser/fileService.ts @@ -9,7 +9,6 @@ import { IDirectoryStrService } from '../common/directoryStrService.js'; import { readFile, DEFAULT_FILE_SIZE_LIMIT } from '../common/prompt/prompts.js'; - async function filePrompt(fileService: IFileService, uri: URI, clipboardService: IClipboardService): Promise { const { val } = await readFile(fileService, uri, DEFAULT_FILE_SIZE_LIMIT) const fileName = uri.fsPath.split('/').pop() ?? '' @@ -54,7 +53,7 @@ class FilePromptActionService extends Action2 { constructor() { super({ id: FilePromptActionService.VOID_COPY_FILE_PROMPT_ID, - title: localize2('voidCopyPrompt', "Void: Copy Prompt"), + title: localize2('voidCopyPrompt', 'Void: Copy Prompt'), menu: [{ id: MenuId.ExplorerContext, group: '8_void', From 5232b4c30c67546d42748a3749ee215381e2424f Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Sat, 10 May 2025 02:28:40 -0700 Subject: [PATCH 04/20] update prompt to work better in agent --- src/vs/workbench/contrib/void/common/prompt/prompts.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index 613142b8..c558dba1 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -479,12 +479,13 @@ ${directoryStr} details.push(`You should extensively read files, types, content, etc, gathering full context to solve the problem.`) } - - if (mode === 'gather' || mode === 'normal') { - details.push(`If you write any code blocks, please use this format: + details.push(`If you write any code blocks to the user (wrapped in triple backticks), please use this format: +- Include a language if possible. Terminal should have the language 'shell'. - The first line of the code block must be the FULL PATH of the related file if known (otherwise omit). - The remaining contents of the file should proceed as usual.`) + if (mode === 'gather' || mode === 'normal') { + details.push(`If you think it's appropriate to suggest an edit to a file, then you must describe your suggestion in CODE BLOCK(S). - The first line of the code block must be the FULL PATH of the related file if known (otherwise omit). - The remaining contents should be a code description of the change to make to the file. \ From d6295e5d659532b2d6dba671a1607317d786d397 Mon Sep 17 00:00:00 2001 From: Hunter Evangelista Date: Sun, 11 May 2025 16:02:17 -0700 Subject: [PATCH 05/20] added optional json config for VoidStaticModelInfo --- src/vs/workbench/contrib/void/common/modelCapabilities.ts | 5 ++++- .../void/electron-main/llmMessage/sendLLMMessage.impl.ts | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/void/common/modelCapabilities.ts b/src/vs/workbench/contrib/void/common/modelCapabilities.ts index e9eec31b..c254535f 100644 --- a/src/vs/workbench/contrib/void/common/modelCapabilities.ts +++ b/src/vs/workbench/contrib/void/common/modelCapabilities.ts @@ -153,6 +153,9 @@ export type VoidStaticModelInfo = { // not stateful specialToolFormat?: 'openai-style' | 'anthropic-style' | 'gemini-style', // typically you should use 'openai-style'. null means "can't call tools by default", and asks the LLM to output XML in agent mode supportsFIM: boolean; // whether the model was specifically designed for autocomplete or "FIM" ("fill-in-middle" format) + // do we need any additional information? + additionalConfig?: { [key: string]: string } // additional info for openAI requests + // reasoning options reasoningCapabilities: false | { readonly supportsReasoning: true; // for clarity, this must be true if anything below is specified @@ -186,7 +189,7 @@ export type VoidStaticModelInfo = { // not stateful export type ModelOverrides = Pick diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts index 4eec4850..66aa67b4 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts @@ -236,6 +236,7 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE modelName, specialToolFormat, reasoningCapabilities, + additionalConfig } = getModelCapabilities(providerName, modelName_, overridesOfModel) const { providerReasoningIOSettings } = getProviderCapabilities(providerName) @@ -245,8 +246,11 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions, overridesOfModel) // user's modelName_ here const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {} + const additionalOpenAIPayload = additionalConfig || {} + console.log('include', includeInPayload) console.log('reasoningInfo', reasoningInfo) + console.log('additionalOpenAIPayload', additionalOpenAIPayload) // tools const potentialTools = chatMode !== null ? openAITools(chatMode) : null const nativeToolsObj = potentialTools && specialToolFormat === 'openai-style' ? @@ -260,6 +264,7 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE messages: messages as any, stream: true, ...nativeToolsObj, + ...additionalOpenAIPayload // max_completion_tokens: maxTokens, } From 970fd7ecbb59fc0dcac2a162121cfbf1e382966c Mon Sep 17 00:00:00 2001 From: Hunter Evangelista Date: Sun, 11 May 2025 17:09:47 -0700 Subject: [PATCH 06/20] moved payload to sdk initialization --- .../void/electron-main/llmMessage/sendLLMMessage.impl.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts index 7dfd63ff..007b4413 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts @@ -68,10 +68,11 @@ const parseHeadersJSON = (s: string | undefined): Record { +const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includeInPayload, additionalOpenAIPayload }: { settingsOfProvider: SettingsOfProvider, providerName: ProviderName, includeInPayload?: { [s: string]: any }, additionalOpenAIPayload?: { [s: string]: string } }) => { const commonPayloadOpts: ClientOptions = { dangerouslyAllowBrowser: true, ...includeInPayload, + ...additionalOpenAIPayload } if (providerName === 'openAI') { const thisConfig = settingsOfProvider[providerName] @@ -256,7 +257,7 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE : {} // instance - const openai: OpenAI = await newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload }) + const openai: OpenAI = await newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload, additionalOpenAIPayload }) const options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model: modelName, messages: messages as any, From dd42e40a8d38408b3487221e6ec28ceccd4c2be4 Mon Sep 17 00:00:00 2001 From: Ujjwal Jain Date: Sun, 11 May 2025 22:01:04 -0400 Subject: [PATCH 07/20] fix: preserve selection when adding to chat via CTRL+L with closed panel --- .../contrib/void/browser/sidebarActions.ts | 41 ++++++++----------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/sidebarActions.ts b/src/vs/workbench/contrib/void/browser/sidebarActions.ts index 0e962e09..a2db29ea 100644 --- a/src/vs/workbench/contrib/void/browser/sidebarActions.ts +++ b/src/vs/workbench/contrib/void/browser/sidebarActions.ts @@ -89,10 +89,7 @@ registerAction2(class extends Action2 { }); } async run(accessor: ServicesAccessor): Promise { - - - // Get the views service to check if the sidebar is open - // const viewsService = accessor.get(IViewsService) + // Get services const commandService = accessor.get(ICommandService) const viewsService = accessor.get(IViewsService) const metricsService = accessor.get(IMetricsService) @@ -101,31 +98,28 @@ registerAction2(class extends Action2 { metricsService.capture('Ctrl+L', {}) + // Capture selection and model BEFORE opening the chat panel + const editor = editorService.getActiveCodeEditor() + const model = editor?.getModel() + if (!model) return + + const selectionRange = roundRangeToLines(editor?.getSelection(), { emptySelectionBehavior: 'null' }) + + // Now check if panel is open and open it if needed const wasAlreadyOpen = viewsService.isViewContainerVisible(VOID_VIEW_CONTAINER_ID) if (!wasAlreadyOpen) { await commandService.executeCommand(VOID_OPEN_SIDEBAR_ACTION_ID) - return } - - // if was already open - - const model = accessor.get(ICodeEditorService).getActiveCodeEditor()?.getModel() - if (!model) return - - const editor = editorService.getActiveCodeEditor() - const selectionRange = roundRangeToLines(editor?.getSelection(), { emptySelectionBehavior: 'null' }) - - // if has no selection, close + return - // if (!selectionRange) { - // viewsService.closeViewContainer(VOID_VIEW_CONTAINER_ID); - // return; - // } - - + // Add selection to chat (whether it was already open or we just opened it) // add line selection if (selectionRange) { - editor?.setSelection({ startLineNumber: selectionRange.startLineNumber, endLineNumber: selectionRange.endLineNumber, startColumn: 1, endColumn: Number.MAX_SAFE_INTEGER }) + editor?.setSelection({ + startLineNumber: selectionRange.startLineNumber, + endLineNumber: selectionRange.endLineNumber, + startColumn: 1, + endColumn: Number.MAX_SAFE_INTEGER + }) chatThreadService.addNewStagingSelection({ type: 'CodeSelection', uri: model.uri, @@ -142,12 +136,9 @@ registerAction2(class extends Action2 { language: model.getLanguageId(), state: { wasAddedAsCurrentFile: false }, }) - } await chatThreadService.focusCurrentChat() - - } }) From 9a905f6cb2cc1937b3bb42ed109d0c2410941cc5 Mon Sep 17 00:00:00 2001 From: Ismail Pelaseyed Date: Mon, 12 May 2025 09:06:22 +0200 Subject: [PATCH 08/20] add support for stopping the terminal command --- .../src/markdown/ApplyBlockHoverButtons.tsx | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 89137cbc..b158acc2 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -264,25 +264,30 @@ export const ApplyButtonsHTML = ({ const isShellLanguage = language === 'bash' || language === 'shellscript' + const [isShellRunning, setIsShellRunning] = useState(false) + const interruptToolRef = useRef<(() => void) | null>(null) + const onClickSubmit = useCallback(async () => { - if (currStreamStateRef.current === 'streaming') return + if (currStreamStateRef.current === 'streaming' || isShellRunning) return // For shell scripts, run in terminal instead of applying to file if (isShellLanguage) { try { + setIsShellRunning(true) // Create a terminal if none exists or use terminal 1 const terminalIds = terminalToolService.listPersistentTerminalIds() let terminalId = '1'; if (!terminalIds.includes(terminalId)) { terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }) } - await terminalToolService.runCommand( + const { interrupt } = await terminalToolService.runCommand( codeStr, { type: 'persistent', persistentTerminalId: terminalId } ); - await terminalToolService.focusPersistentTerminal(terminalId) + interruptToolRef.current = interrupt metricsService.capture('Execute Shell', { length: codeStr.length }) } catch (e) { + setIsShellRunning(false) console.error('Failed to execute in terminal:', e) } return; @@ -338,6 +343,19 @@ export const ApplyButtonsHTML = ({ const currStreamState = currStreamStateRef.current console.log('currStreamState...', currStreamState) + if (isShellRunning) { + return ( + { + interruptToolRef.current?.(); + setIsShellRunning(false); + }} + {...tooltipPropsForApplyBlock({ tooltipName: 'Stop' })} + /> + ); + } + if (currStreamState === 'streaming') { return Date: Mon, 12 May 2025 11:50:25 -0700 Subject: [PATCH 09/20] use 1st terminal id --- .../react/src/markdown/ApplyBlockHoverButtons.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index b158acc2..99847fe0 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -248,7 +248,6 @@ export const ApplyButtonsHTML = ({ codeStr: string, applyBoxId: string, language?: string, -} & { uri: URI | 'current'; }) => { const accessor = useAccessor() @@ -270,16 +269,19 @@ export const ApplyButtonsHTML = ({ const onClickSubmit = useCallback(async () => { if (currStreamStateRef.current === 'streaming' || isShellRunning) return - // For shell scripts, run in terminal instead of applying to file + // for shell scripts, run in terminal instead of applying to file if (isShellLanguage) { try { setIsShellRunning(true) - // Create a terminal if none exists or use terminal 1 + // create a terminal if none exists or use terminal 1 const terminalIds = terminalToolService.listPersistentTerminalIds() - let terminalId = '1'; - if (!terminalIds.includes(terminalId)) { + + let terminalId: string + if (terminalIds.length !== 0) + terminalId = terminalIds[0] // use 1st terminal id + else terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }) - } + const { interrupt } = await terminalToolService.runCommand( codeStr, { type: 'persistent', persistentTerminalId: terminalId } From e8b7c9c470957b3734e7827e804061c721418ecb Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 12 May 2025 11:56:08 -0700 Subject: [PATCH 10/20] always create terminal when apply --- .../react/src/markdown/ApplyBlockHoverButtons.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 786fb737..94fae35b 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -273,14 +273,7 @@ export const ApplyButtonsHTML = ({ if (isShellLanguage) { try { setIsShellRunning(true) - // create a terminal if none exists or use terminal 1 - const terminalIds = terminalToolService.listPersistentTerminalIds() - - let terminalId: string - if (terminalIds.length !== 0) - terminalId = terminalIds[0] // use 1st terminal id - else - terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }) + const terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }) const { interrupt } = await terminalToolService.runCommand( codeStr, From 3fa680e8bab5c64afa1fd0703d96450392d3f5ae Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 12 May 2025 12:09:55 -0700 Subject: [PATCH 11/20] identify more shells --- .../src/markdown/ApplyBlockHoverButtons.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 94fae35b..834909b9 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -239,6 +239,21 @@ export const StatusIndicatorForApplyButton = ({ applyBoxId, uri }: { applyBoxId: } +const terminalLanguages = new Set([ + 'bash', + 'shellscript', + 'shell', + 'powershell', + 'bat', + 'zsh', + 'sh', + 'fish', + 'nushell', + 'ksh', + 'xonsh', + 'elvish', +]) + export const ApplyButtonsHTML = ({ codeStr, applyBoxId, @@ -261,7 +276,7 @@ export const ApplyButtonsHTML = ({ const { currStreamStateRef, setApplying } = useApplyStreamState({ applyBoxId }) - const isShellLanguage = language === 'bash' || language === 'shellscript' + const isShellLanguage = !!language && terminalLanguages.has(language) const [isShellRunning, setIsShellRunning] = useState(false) const interruptToolRef = useRef<(() => void) | null>(null) From d6683482441a4afbb46649c745eefdd1d8d2d347 Mon Sep 17 00:00:00 2001 From: vrtnis <123119434+vrtnis@users.noreply.github.com> Date: Mon, 12 May 2025 20:58:55 +0000 Subject: [PATCH 12/20] feat(models): update modelCapabilities with mistral-medium entry --- .../workbench/contrib/void/common/modelCapabilities.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/vs/workbench/contrib/void/common/modelCapabilities.ts b/src/vs/workbench/contrib/void/common/modelCapabilities.ts index 6048f143..ee536981 100644 --- a/src/vs/workbench/contrib/void/common/modelCapabilities.ts +++ b/src/vs/workbench/contrib/void/common/modelCapabilities.ts @@ -129,6 +129,7 @@ export const defaultModelsOfProvider = { mistral: [ // https://docs.mistral.ai/getting-started/models/models_overview/ 'codestral-latest', 'mistral-large-latest', + 'mistral-medium-latest', 'ministral-3b-latest', 'ministral-8b-latest', ], @@ -881,6 +882,15 @@ const mistralModelOptions = { // https://mistral.ai/products/la-plateforme#prici supportsSystemMessage: 'system-role', reasoningCapabilities: false, }, + 'mistral-medium-latest': { // https://openrouter.ai/mistralai/mistral-medium-3 + contextWindow: 131_000, + reservedOutputTokenSpace: 8_192, + cost: { input: 0.40, output: 2.00 }, + supportsFIM: false, + downloadable: { sizeGb: 'not-known' }, + supportsSystemMessage: 'system-role', + reasoningCapabilities: false, + }, 'codestral-latest': { contextWindow: 256_000, reservedOutputTokenSpace: 8_192, From 45f7e4b157dc815dcd2e91c0cd88ad0869ab883d Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 12 May 2025 16:48:27 -0700 Subject: [PATCH 13/20] minor updates --- src/vs/workbench/contrib/void/browser/sidebarActions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/sidebarActions.ts b/src/vs/workbench/contrib/void/browser/sidebarActions.ts index a2db29ea..4112745c 100644 --- a/src/vs/workbench/contrib/void/browser/sidebarActions.ts +++ b/src/vs/workbench/contrib/void/browser/sidebarActions.ts @@ -98,20 +98,20 @@ registerAction2(class extends Action2 { metricsService.capture('Ctrl+L', {}) - // Capture selection and model BEFORE opening the chat panel + // capture selection and model before opening the chat panel const editor = editorService.getActiveCodeEditor() const model = editor?.getModel() if (!model) return const selectionRange = roundRangeToLines(editor?.getSelection(), { emptySelectionBehavior: 'null' }) - // Now check if panel is open and open it if needed + // open panel const wasAlreadyOpen = viewsService.isViewContainerVisible(VOID_VIEW_CONTAINER_ID) if (!wasAlreadyOpen) { await commandService.executeCommand(VOID_OPEN_SIDEBAR_ACTION_ID) } - // Add selection to chat (whether it was already open or we just opened it) + // Add selection to chat // add line selection if (selectionRange) { editor?.setSelection({ From 7066b4285b281345c3d3ef80342c07aba21fff65 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 12 May 2025 18:20:44 -0700 Subject: [PATCH 14/20] file and folder copy uniform --- .../contrib/void/browser/fileService.ts | 88 ++++++++----------- .../contrib/void/common/prompt/prompts.ts | 86 +++++++++++------- 2 files changed, 93 insertions(+), 81 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/fileService.ts b/src/vs/workbench/contrib/void/browser/fileService.ts index 518803ed..93da1b1e 100644 --- a/src/vs/workbench/contrib/void/browser/fileService.ts +++ b/src/vs/workbench/contrib/void/browser/fileService.ts @@ -6,47 +6,11 @@ import { INotificationService } from '../../../../platform/notification/common/n import { IFileService } from '../../../../platform/files/common/files.js'; import { IClipboardService } from '../../../../platform/clipboard/common/clipboardService.js'; import { IDirectoryStrService } from '../common/directoryStrService.js'; -import { readFile, DEFAULT_FILE_SIZE_LIMIT } from '../common/prompt/prompts.js'; +import { messageOfSelection } from '../common/prompt/prompts.js'; +import { IVoidModelService } from '../common/voidModelService.js'; -async function filePrompt(fileService: IFileService, uri: URI, clipboardService: IClipboardService): Promise { - const { val } = await readFile(fileService, uri, DEFAULT_FILE_SIZE_LIMIT) - const fileName = uri.fsPath.split('/').pop() ?? '' - if (!val) { - throw new Error('Failed to copy prompt') - } - const prompt = ` -${fileName}: -\`\`\` -${val} -\`\`\``.trim() - return prompt -} -/** - * Add a menu item to the explorer context menu that copies a prompt for the selected file or directory. - * - * Example file prompt: - * - * ``` - * index.js: - * \`\`\` - * console.log('Hello World!'); - * \`\`\` - * - * Example directory prompt: - * ``` - * Directory of /path/to/src: - * src/ - * ├── index.ts - * ├── src.ts - * ├── latest/ - * │ ├── index.ts - * │ └── src.ts - * ├── types.ts - * └── util.ts - * ``` - */ class FilePromptActionService extends Action2 { private static readonly VOID_COPY_FILE_PROMPT_ID = 'void.copyfileprompt' @@ -67,22 +31,48 @@ class FilePromptActionService extends Action2 { const fileService = accessor.get(IFileService); const clipboardService = accessor.get(IClipboardService) const directoryStrService = accessor.get(IDirectoryStrService) - const stat = await fileService.resolve(uri) - const prompt = stat.isFile - ? await filePrompt(fileService, uri, clipboardService) - : await directoryStrService.getDirectoryStrTool(uri) - await clipboardService.writeText(prompt) + const voidModelService = accessor.get(IVoidModelService) + + const stat = await fileService.stat(uri) + + const folderOpts = { + maxChildren: 1000, + maxCharsPerFile: 2_000_000, + } as const + + let m: string = 'No contents detected' + if (stat.isFile) { + m = await messageOfSelection({ + type: 'File', + uri, + language: (await voidModelService.getModelSafe(uri)).model?.getLanguageId() || '', + state: { wasAddedAsCurrentFile: false, }, + }, { + folderOpts, + directoryStrService, + fileService, + }) + } + + if (stat.isDirectory) { + m = await messageOfSelection({ + type: 'Folder', + uri, + }, { + folderOpts, + fileService, + directoryStrService, + }) + } + + await clipboardService.writeText(m) + } catch (error) { const notificationService = accessor.get(INotificationService) - FilePromptActionService._onError(notificationService, error) + notificationService.error(error + '') } } - private static _onError(notificationService: INotificationService, error: Error) { - const errorMessage = localize2('voidCopyPromptError', 'Failed to copy prompt') - notificationService.error(errorMessage.value) - throw error - } } registerAction2(FilePromptActionService) diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index 5f53cd07..a4c0daa8 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -551,46 +551,68 @@ export const readFile = async (fileService: IFileService, uri: URI, fileSizeLimi +export const messageOfSelection = async ( + s: StagingSelectionItem, + opts: { + directoryStrService: IDirectoryStrService, + fileService: IFileService, + folderOpts: { + maxChildren: number, + maxCharsPerFile: number, + } + } +) => { + const lineNumAddition = (range: [number, number]) => ` (lines ${range[0]}:${range[1]})` + + if (s.type === 'File' || s.type === 'CodeSelection') { + const { val } = await readFile(opts.fileService, s.uri, DEFAULT_FILE_SIZE_LIMIT) + const lineNumAdd = s.type === 'CodeSelection' ? lineNumAddition(s.range) : '' + const content = val === null ? 'null' : `${tripleTick[0]}${s.language}\n${val}\n${tripleTick[1]}` + const str = `${s.uri.fsPath}${lineNumAdd}:\n${content}` + return str + } + else if (s.type === 'Folder') { + const dirStr: string = await opts.directoryStrService.getDirectoryStrTool(s.uri) + const folderStructure = `${s.uri.fsPath} folder structure:${tripleTick[0]}\n${dirStr}\n${tripleTick[1]}` + + const uris = await opts.directoryStrService.getAllURIsInDirectory(s.uri, { maxResults: opts.folderOpts.maxChildren }) + const strOfFiles = await Promise.all(uris.map(async uri => { + const { val, truncated } = await readFile(opts.fileService, uri, opts.folderOpts.maxCharsPerFile) + const truncationStr = truncated ? `\n... file truncated ...` : '' + const content = val === null ? 'null' : `${tripleTick[0]}\n${val}${truncationStr}\n${tripleTick[1]}` + const str = `${uri.fsPath}:\n${content}` + return str + })) + const contentStr = [folderStructure, ...strOfFiles].join('\n\n') + return contentStr + } + else + return '' + +} - -export const chat_userMessageContent = async (instructions: string, currSelns: StagingSelectionItem[] | null, - opts: { directoryStrService: IDirectoryStrService, fileService: IFileService } +export const chat_userMessageContent = async ( + instructions: string, + currSelns: StagingSelectionItem[] | null, + opts: { + directoryStrService: IDirectoryStrService, + fileService: IFileService + }, ) => { - const lineNumAddition = (range: [number, number]) => ` (lines ${range[0]}:${range[1]})` - let selnsStrs: string[] = [] - selnsStrs = await Promise.all(currSelns?.map(async (s) => { + const selnsStrs = await Promise.all((currSelns ?? []).map(async (s) => + messageOfSelection(s, { + ...opts, + folderOpts: { maxChildren: 100, maxCharsPerFile: 100_000, } + }) + )) - if (s.type === 'File' || s.type === 'CodeSelection') { - const { val } = await readFile(opts.fileService, s.uri, DEFAULT_FILE_SIZE_LIMIT) - const lineNumAdd = s.type === 'CodeSelection' ? lineNumAddition(s.range) : '' - const content = val === null ? 'null' : `${tripleTick[0]}${s.language}\n${val}\n${tripleTick[1]}` - const str = `${s.uri.fsPath}${lineNumAdd}:\n${content}` - return str - } - else if (s.type === 'Folder') { - const dirStr: string = await opts.directoryStrService.getDirectoryStrTool(s.uri) - const folderStructure = `${s.uri.fsPath} folder structure:${tripleTick[0]}\n${dirStr}\n${tripleTick[1]}` - const uris = await opts.directoryStrService.getAllURIsInDirectory(s.uri, { maxResults: 100 }) - const strOfFiles = await Promise.all(uris.map(async uri => { - const { val, truncated } = await readFile(opts.fileService, uri, 100_000) - const truncationStr = truncated ? `\n... file truncated ...` : '' - const content = val === null ? 'null' : `${tripleTick[0]}\n${val}${truncationStr}\n${tripleTick[1]}` - const str = `${uri.fsPath}:\n${content}` - return str - })) - const contentStr = [folderStructure, ...strOfFiles].join('\n\n') - return contentStr - } - else - return '' - }) ?? []) - - const selnsStr = selnsStrs.join('\n') ?? '' let str = '' str += `${instructions}` + + const selnsStr = selnsStrs.join('\n\n') ?? '' if (selnsStr) str += `\n---\nSELECTIONS\n${selnsStr}` return str; } From f98894ad77c36423a07ba29c54196827b6b04064 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 12 May 2025 18:22:59 -0700 Subject: [PATCH 15/20] format --- .../contrib/void/common/prompt/prompts.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index b5cae7cb..5746067e 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -605,12 +605,14 @@ export const chat_userMessageContent = async ( }, ) => { - const selnsStrs = await Promise.all((currSelns ?? []).map(async (s) => - messageOfSelection(s, { - ...opts, - folderOpts: { maxChildren: 100, maxCharsPerFile: 100_000, } - }) - )) + const selnsStrs = await Promise.all( + (currSelns ?? []).map(async (s) => + messageOfSelection(s, { + ...opts, + folderOpts: { maxChildren: 100, maxCharsPerFile: 100_000, } + }) + ) + ) let str = '' From 1c6edf70234e812327218eea17e99f6fe362ecda Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 12 May 2025 18:45:33 -0700 Subject: [PATCH 16/20] generalize --- .../src/markdown/ApplyBlockHoverButtons.tsx | 134 +++++++++++------- 1 file changed, 86 insertions(+), 48 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 834909b9..4674f9a0 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -254,7 +254,69 @@ const terminalLanguages = new Set([ 'elvish', ]) -export const ApplyButtonsHTML = ({ +const ApplyButtonsForTerminal = ({ + codeStr, + applyBoxId, + uri, + language, +}: { + codeStr: string, + applyBoxId: string, + language?: string, + uri: URI | 'current'; +}) => { + const accessor = useAccessor() + const metricsService = accessor.get('IMetricsService') + const terminalToolService = accessor.get('ITerminalToolService') + + const settingsState = useSettingsState() + const isDisabled = !!isFeatureNameDisabled('Apply', settingsState) || !applyBoxId + + const [isShellRunning, setIsShellRunning] = useState(false) + const interruptToolRef = useRef<(() => void) | null>(null) + + const onClickSubmit = useCallback(async () => { + if (isShellRunning) return + try { + setIsShellRunning(true) + const terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }) + const { interrupt } = await terminalToolService.runCommand( + codeStr, + { type: 'persistent', persistentTerminalId: terminalId } + ); + interruptToolRef.current = interrupt + metricsService.capture('Execute Shell', { length: codeStr.length }) + } catch (e) { + setIsShellRunning(false) + console.error('Failed to execute in terminal:', e) + } + }, [codeStr, uri, applyBoxId, metricsService, terminalToolService, isShellRunning]) + + if (isShellRunning) { + return ( + { + interruptToolRef.current?.(); + setIsShellRunning(false); + }} + {...tooltipPropsForApplyBlock({ tooltipName: 'Stop' })} + /> + ); + } + if (isDisabled) { + return null + } + return +} + + + +const ApplyButtonsForEdit = ({ codeStr, applyBoxId, uri, @@ -268,7 +330,6 @@ export const ApplyButtonsHTML = ({ const accessor = useAccessor() const editCodeService = accessor.get('IEditCodeService') const metricsService = accessor.get('IMetricsService') - const terminalToolService = accessor.get('ITerminalToolService') const notificationService = accessor.get('INotificationService') const settingsState = useSettingsState() @@ -276,34 +337,9 @@ export const ApplyButtonsHTML = ({ const { currStreamStateRef, setApplying } = useApplyStreamState({ applyBoxId }) - const isShellLanguage = !!language && terminalLanguages.has(language) - - const [isShellRunning, setIsShellRunning] = useState(false) - const interruptToolRef = useRef<(() => void) | null>(null) - const onClickSubmit = useCallback(async () => { - if (currStreamStateRef.current === 'streaming' || isShellRunning) return + if (currStreamStateRef.current === 'streaming') return - // for shell scripts, run in terminal instead of applying to file - if (isShellLanguage) { - try { - setIsShellRunning(true) - const terminalId = await terminalToolService.createPersistentTerminal({ cwd: null }) - - const { interrupt } = await terminalToolService.runCommand( - codeStr, - { type: 'persistent', persistentTerminalId: terminalId } - ); - interruptToolRef.current = interrupt - metricsService.capture('Execute Shell', { length: codeStr.length }) - } catch (e) { - setIsShellRunning(false) - console.error('Failed to execute in terminal:', e) - } - return; - } - - // Normal file apply logic for non-shell languages await editCodeService.callBeforeApplyOrEdit(uri) const [newApplyingUri, applyDonePromise] = editCodeService.startApplying({ @@ -327,7 +363,7 @@ export const ApplyButtonsHTML = ({ }) metricsService.capture('Apply Code', { length: codeStr.length }) // capture the length only - }, [setApplying, currStreamStateRef, editCodeService, codeStr, uri, applyBoxId, metricsService, isShellLanguage, terminalToolService]) + }, [setApplying, currStreamStateRef, editCodeService, codeStr, uri, applyBoxId, metricsService, notificationService]) const onClickStop = useCallback(() => { @@ -349,22 +385,7 @@ export const ApplyButtonsHTML = ({ if (uri) editCodeService.acceptOrRejectAllDiffAreas({ uri: uri, behavior: 'reject', removeCtrlKs: false }) }, [uri, applyBoxId, editCodeService]) - const currStreamState = currStreamStateRef.current - - if (isShellRunning) { - return ( - { - interruptToolRef.current?.(); - setIsShellRunning(false); - }} - {...tooltipPropsForApplyBlock({ tooltipName: 'Stop' })} - /> - ); - } - if (currStreamState === 'streaming') { return } - if (isDisabled) { return null } - - if (currStreamState === 'idle-no-changes') { return } - if (currStreamState === 'idle-has-changes') { return { + const { language } = params + const isShellLanguage = !!language && terminalLanguages.has(language) + + if (isShellLanguage) { + return + } + else { + return + } +} + + + + + export const EditToolAcceptRejectButtonsHTML = ({ codeStr, applyBoxId, From 3772cf98eabd17518c3ef50ca44170c4a1801662 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 12 May 2025 18:47:40 -0700 Subject: [PATCH 17/20] delete --- src/vs/workbench/contrib/void/browser/terminalToolService.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/vs/workbench/contrib/void/browser/terminalToolService.ts b/src/vs/workbench/contrib/void/browser/terminalToolService.ts index b079eb5a..d5bb5228 100644 --- a/src/vs/workbench/contrib/void/browser/terminalToolService.ts +++ b/src/vs/workbench/contrib/void/browser/terminalToolService.ts @@ -277,6 +277,8 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ terminal.dispose() if (!isPersistent) delete this.temporaryTerminalInstanceOfId[params.terminalId] + else + delete this.persistentTerminalInstanceOfId[params.persistentTerminalId] } const waitForResult = async () => { From 67ed52415e5ad88bbe0dbcc6fd6716f31d4939bf Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 12 May 2025 19:29:45 -0700 Subject: [PATCH 18/20] apply terminal? --- .../workbench/contrib/void/browser/terminalToolService.ts | 7 ++++++- src/vs/workbench/contrib/void/browser/toolsService.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/terminalToolService.ts b/src/vs/workbench/contrib/void/browser/terminalToolService.ts index d5bb5228..2e18511b 100644 --- a/src/vs/workbench/contrib/void/browser/terminalToolService.ts +++ b/src/vs/workbench/contrib/void/browser/terminalToolService.ts @@ -22,7 +22,12 @@ export interface ITerminalToolService { readonly _serviceBrand: undefined; listPersistentTerminalIds(): string[]; - runCommand(command: string, opts: { type: 'persistent', persistentTerminalId: string } | { type: 'ephemeral', cwd: string | null, terminalId: string }): Promise<{ interrupt: () => void; resPromise: Promise<{ result: string, resolveReason: TerminalResolveReason }> }>; + runCommand(command: string, opts: + | { type: 'persistent', persistentTerminalId: string } + | { type: 'temporary', cwd: string | null, terminalId: string } + // | { type: 'apply', terminalId: string } + ): Promise<{ interrupt: () => void; resPromise: Promise<{ result: string, resolveReason: TerminalResolveReason }> }>; + focusPersistentTerminal(terminalId: string): Promise persistentTerminalExists(terminalId: string): boolean diff --git a/src/vs/workbench/contrib/void/browser/toolsService.ts b/src/vs/workbench/contrib/void/browser/toolsService.ts index bba085d8..02edf047 100644 --- a/src/vs/workbench/contrib/void/browser/toolsService.ts +++ b/src/vs/workbench/contrib/void/browser/toolsService.ts @@ -430,7 +430,7 @@ export class ToolsService implements IToolsService { }, // --- run_command: async ({ command, cwd, terminalId }) => { - const { resPromise, interrupt } = await this.terminalToolService.runCommand(command, { type: 'ephemeral', cwd, terminalId }) + const { resPromise, interrupt } = await this.terminalToolService.runCommand(command, { type: 'temporary', cwd, terminalId }) return { result: resPromise, interruptTool: interrupt } }, run_persistent_command: async ({ command, persistentTerminalId }) => { From 28dcdff189fba6c0e868f6e67ff56cf40a6a1975 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 12 May 2025 19:31:05 -0700 Subject: [PATCH 19/20] isdisabled fix --- .../void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx index 4674f9a0..93e26b0d 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/markdown/ApplyBlockHoverButtons.tsx @@ -270,10 +270,10 @@ const ApplyButtonsForTerminal = ({ const terminalToolService = accessor.get('ITerminalToolService') const settingsState = useSettingsState() - const isDisabled = !!isFeatureNameDisabled('Apply', settingsState) || !applyBoxId const [isShellRunning, setIsShellRunning] = useState(false) const interruptToolRef = useRef<(() => void) | null>(null) + const isDisabled = isShellRunning const onClickSubmit = useCallback(async () => { if (isShellRunning) return From f65e4f51a24ba9e27fe85ed6eafc423d72ff777f Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 12 May 2025 19:40:39 -0700 Subject: [PATCH 20/20] misc --- .../contrib/void/common/modelCapabilities.ts | 11 ++++++--- .../llmMessage/sendLLMMessage.impl.ts | 23 +++++++++++-------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/void/common/modelCapabilities.ts b/src/vs/workbench/contrib/void/common/modelCapabilities.ts index 4a1750e3..52cc3947 100644 --- a/src/vs/workbench/contrib/void/common/modelCapabilities.ts +++ b/src/vs/workbench/contrib/void/common/modelCapabilities.ts @@ -153,8 +153,7 @@ export type VoidStaticModelInfo = { // not stateful specialToolFormat?: 'openai-style' | 'anthropic-style' | 'gemini-style', // typically you should use 'openai-style'. null means "can't call tools by default", and asks the LLM to output XML in agent mode supportsFIM: boolean; // whether the model was specifically designed for autocomplete or "FIM" ("fill-in-middle" format) - // do we need any additional information? - additionalConfig?: { [key: string]: string } // additional info for openAI requests + additionalOpenAIPayload?: { [key: string]: string } // additional info for openAI requests // reasoning options reasoningCapabilities: false | { @@ -189,7 +188,13 @@ export type VoidStaticModelInfo = { // not stateful export type ModelOverrides = Pick diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts index 007b4413..c58daeb7 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts @@ -68,11 +68,10 @@ const parseHeadersJSON = (s: string | undefined): Record { +const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includeInPayload }: { settingsOfProvider: SettingsOfProvider, providerName: ProviderName, includeInPayload?: { [s: string]: any } }) => { const commonPayloadOpts: ClientOptions = { dangerouslyAllowBrowser: true, ...includeInPayload, - ...additionalOpenAIPayload } if (providerName === 'openAI') { const thisConfig = settingsOfProvider[providerName] @@ -148,7 +147,12 @@ const newOpenAICompatibleSDK = async ({ settingsOfProvider, providerName, includ const _sendOpenAICompatibleFIM = async ({ messages: { prefix, suffix, stopTokens }, onFinalMessage, onError, settingsOfProvider, modelName: modelName_, _setAborter, providerName, overridesOfModel }: SendFIMParams_Internal) => { - const { modelName, supportsFIM } = getModelCapabilities(providerName, modelName_, overridesOfModel) + const { + modelName, + supportsFIM, + additionalOpenAIPayload, + } = getModelCapabilities(providerName, modelName_, overridesOfModel) + if (!supportsFIM) { if (modelName === modelName_) onError({ message: `Model ${modelName} does not support FIM.`, fullError: null }) @@ -157,7 +161,7 @@ const _sendOpenAICompatibleFIM = async ({ messages: { prefix, suffix, stopTokens return } - const openai = await newOpenAICompatibleSDK({ providerName, settingsOfProvider }) + const openai = await newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload: additionalOpenAIPayload }) openai.completions .create({ model: modelName, @@ -237,7 +241,7 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE modelName, specialToolFormat, reasoningCapabilities, - additionalConfig + additionalOpenAIPayload, } = getModelCapabilities(providerName, modelName_, overridesOfModel) const { providerReasoningIOSettings } = getProviderCapabilities(providerName) @@ -245,10 +249,11 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE // reasoning const { canIOReasoning, openSourceThinkTags } = reasoningCapabilities || {} const reasoningInfo = getSendableReasoningInfo('Chat', providerName, modelName_, modelSelectionOptions, overridesOfModel) // user's modelName_ here - const includeInPayload = providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo) || {} - - const additionalOpenAIPayload = additionalConfig || {} + const includeInPayload = { + ...providerReasoningIOSettings?.input?.includeInPayload?.(reasoningInfo), + ...additionalOpenAIPayload + } // tools const potentialTools = chatMode !== null ? openAITools(chatMode) : null @@ -257,7 +262,7 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE : {} // instance - const openai: OpenAI = await newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload, additionalOpenAIPayload }) + const openai: OpenAI = await newOpenAICompatibleSDK({ providerName, settingsOfProvider, includeInPayload }) const options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model: modelName, messages: messages as any,