diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx
index 4ef01353..87983026 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx
@@ -29,6 +29,7 @@ import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg
import { PlacesType } from 'react-tooltip';
import { ToolName, toolNames } from '../../../../common/prompt/prompts.js';
import { error } from 'console';
+import { RawToolCallObj } from '../../../../common/sendLLMMessageTypes.js';
@@ -1333,7 +1334,7 @@ export const ListableToolItem = ({ name, onClick, isSmall, className, showDot }:
-const EditToolChildren = ({ uri, changeDescription }: { uri: URI, changeDescription: string }) => {
+const EditToolChildren = ({ uri, changeDescription }: { uri: URI | undefined, changeDescription: string }) => {
return
@@ -2285,6 +2286,37 @@ const CommandBarInChat = () => {
}
+const EditToolSoFar = ({ toolCallSoFar, }: { toolCallSoFar: RawToolCallObj }) => {
+
+ const uri = URI.file(toolCallSoFar.rawParams.uri ?? 'unknown')
+
+ const title = titleOfToolName['edit_file'].proposed
+
+ const uriDone = toolCallSoFar.doneParams.includes('uri')
+ const desc1 =
+ {uriDone ?
+ getBasename(toolCallSoFar.rawParams['uri'] ?? 'unknown')
+ : `Generating`}
+
+
+
+ // If URI has not been specified
+ return }
+ >
+
+
+
+
+
+
+}
+
export const SidebarChat = () => {
const textAreaRef = useRef(null)
const textAreaFnsRef = useRef(null)
@@ -2416,6 +2448,15 @@ export const SidebarChat = () => {
/> : null
+ // the tool currently being generated
+ const generatingTool = toolIsGenerating ?
+ toolCallSoFar.name === 'edit_file' ?
+ : null
+ : null
+
const messagesHTML = {
{previousMessagesHTML}
{currStreamingMessageHTML}
- {toolIsGenerating ?
- Generating}
- />
- : null}
+ {/* Generating tool */}
+ {generatingTool}
+ {/* loading indicator */}
{isRunning === 'LLM' && !toolIsGenerating ?
- {/* loading indicator */}
{}
: null}
diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx
index 6574c5e1..ab0e61ee 100644
--- a/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx
+++ b/src/vs/workbench/contrib/void/browser/react/src/util/services.tsx
@@ -47,6 +47,7 @@ import { IWorkspaceContextService } from '../../../../../../../platform/workspac
import { IVoidCommandBarService } from '../../../voidCommandBarService.js'
import { INativeHostService } from '../../../../../../../platform/native/common/native.js';
import { IEditCodeService } from '../../../editCodeServiceInterface.js'
+import { IToolsService } from '../../../toolsService.js'
// normally to do this you'd use a useEffect that calls .onDidChangeState(), but useEffect mounts too late and misses initial state changes
@@ -215,6 +216,7 @@ const getReactAccessor = (accessor: ServicesAccessor) => {
IVoidCommandBarService: accessor.get(IVoidCommandBarService),
INativeHostService: accessor.get(INativeHostService),
+ IToolsService: accessor.get(IToolsService),
} as const
return reactAccessor
diff --git a/src/vs/workbench/contrib/void/browser/toolsService.ts b/src/vs/workbench/contrib/void/browser/toolsService.ts
index 8dec053d..ab3ca373 100644
--- a/src/vs/workbench/contrib/void/browser/toolsService.ts
+++ b/src/vs/workbench/contrib/void/browser/toolsService.ts
@@ -26,7 +26,7 @@ import { IVoidSettingsService } from '../common/voidSettingsService.js'
-type ValidateParams = { [T in ToolName]: (p: RawToolParamsObj) => Promise }
+type ValidateParams = { [T in ToolName]: (p: RawToolParamsObj) => ToolCallParams[T] }
type CallTool = { [T in ToolName]: (p: ToolCallParams[T]) => Promise<{ result: ToolResultType[T], interruptTool?: () => void }> }
type ToolResultToString = { [T in ToolName]: (p: ToolCallParams[T], result: Awaited) => string }
@@ -158,7 +158,7 @@ export class ToolsService implements IToolsService {
const queryBuilder = instantiationService.createInstance(QueryBuilder);
this.validateParams = {
- read_file: async (params: RawToolParamsObj) => {
+ read_file: (params: RawToolParamsObj) => {
const { uri: uriStr, start_line: startLineUnknown, end_line: endLineUnknown, page_number: pageNumberUnknown } = params
const uri = validateURI(uriStr)
const pageNumber = validatePageNum(pageNumberUnknown)
@@ -168,19 +168,19 @@ export class ToolsService implements IToolsService {
return { uri, startLine, endLine, pageNumber }
},
- ls_dir: async (params: RawToolParamsObj) => {
+ ls_dir: (params: RawToolParamsObj) => {
const { uri: uriStr, page_number: pageNumberUnknown } = params
const uri = validateURI(uriStr)
const pageNumber = validatePageNum(pageNumberUnknown)
return { rootURI: uri, pageNumber }
},
- get_dir_structure: async (params: RawToolParamsObj) => {
+ get_dir_structure: (params: RawToolParamsObj) => {
const { uri: uriStr, } = params
const uri = validateURI(uriStr)
return { rootURI: uri }
},
- search_pathnames_only: async (params: RawToolParamsObj) => {
+ search_pathnames_only: (params: RawToolParamsObj) => {
const {
query: queryUnknown,
search_in_folder: includeUnknown,
@@ -194,7 +194,7 @@ export class ToolsService implements IToolsService {
return { queryStr, searchInFolder, pageNumber }
},
- search_files: async (params: RawToolParamsObj) => {
+ search_files: (params: RawToolParamsObj) => {
const {
query: queryUnknown,
search_in_folder: searchInFolderUnknown,
@@ -213,7 +213,7 @@ export class ToolsService implements IToolsService {
// ---
- create_file_or_folder: async (params: RawToolParamsObj) => {
+ create_file_or_folder: (params: RawToolParamsObj) => {
const { uri: uriUnknown } = params
const uri = validateURI(uriUnknown)
const uriStr = validateStr('uri', uriUnknown)
@@ -221,7 +221,7 @@ export class ToolsService implements IToolsService {
return { uri, isFolder }
},
- delete_file_or_folder: async (params: RawToolParamsObj) => {
+ delete_file_or_folder: (params: RawToolParamsObj) => {
const { uri: uriUnknown, params: paramsStr } = params
const uri = validateURI(uriUnknown)
const isRecursive = validateRecursiveParamStr(paramsStr)
@@ -230,14 +230,14 @@ export class ToolsService implements IToolsService {
return { uri, isRecursive, isFolder }
},
- edit_file: async (params: RawToolParamsObj) => {
+ edit_file: (params: RawToolParamsObj) => {
const { uri: uriStr, change_description: changeDescriptionUnknown } = params
const uri = validateURI(uriStr)
const changeDescription = validateStr('changeDescription', changeDescriptionUnknown)
return { uri, changeDescription }
},
- run_terminal_command: async (params: RawToolParamsObj) => {
+ run_terminal_command: (params: RawToolParamsObj) => {
const { command: commandUnknown, terminal_id: terminalIdUnknown, wait_for_completion: waitForCompletionUnknown } = params
const command = validateStr('command', commandUnknown)
const proposedTerminalId = validateProposedTerminalId(terminalIdUnknown)
diff --git a/src/vs/workbench/contrib/void/browser/voidUpdateActions.ts b/src/vs/workbench/contrib/void/browser/voidUpdateActions.ts
index 905b9b80..378171d1 100644
--- a/src/vs/workbench/contrib/void/browser/voidUpdateActions.ts
+++ b/src/vs/workbench/contrib/void/browser/voidUpdateActions.ts
@@ -8,45 +8,94 @@ import Severity from '../../../../base/common/severity.js';
import { ServicesAccessor } from '../../../../editor/browser/editorExtensions.js';
import { localize2 } from '../../../../nls.js';
import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js';
-import { INotificationService } from '../../../../platform/notification/common/notification.js';
+import { INotificationActions, INotificationService } from '../../../../platform/notification/common/notification.js';
import { IMetricsService } from '../common/metricsService.js';
import { IVoidUpdateService } from '../common/voidUpdateService.js';
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js';
import * as dom from '../../../../base/browser/dom.js';
+import { IUpdateService } from '../../../../platform/update/common/update.js';
+import { VoidCheckUpdateRespose } from '../common/voidUpdateServiceTypes.js';
+import { IAction } from '../../../../base/common/actions.js';
-const notifyYesUpdate = (notifService: INotificationService, res: { message?: string } = {}) => {
+const notifyUpdate = (res: VoidCheckUpdateRespose & { message: string }, notifService: INotificationService, updateService: IUpdateService) => {
const message = res?.message || 'This is a very old version of Void, please download the latest version! [Void Editor](https://voideditor.com/download-beta)!'
- const notifController = notifService.notify({
- severity: Severity.Info,
- message: message,
- sticky: true,
- progress: { worked: 0, total: 100 },
- actions: {
- primary: [{
- id: 'void.updater.update',
- enabled: true,
+
+ let actions: INotificationActions | undefined
+
+ if (res?.action) {
+ const primary: IAction[] = []
+
+ if (res.action === 'reinstall') {
+ primary.push({
label: `Reinstall`,
+ id: 'void.updater.reinstall',
+ enabled: true,
tooltip: '',
class: undefined,
run: () => {
const { window } = dom.getActiveWindow()
window.open('https://voideditor.com/download-beta')
}
- },
- {
- id: 'void.updater.site',
+ })
+ }
+
+ if (res.action === 'download') {
+ primary.push({
+ label: `Download`,
+ id: 'void.updater.download',
enabled: true,
- label: `Void Site`,
tooltip: '',
class: undefined,
run: () => {
- const { window } = dom.getActiveWindow()
- window.open('https://voideditor.com/')
+ updateService.downloadUpdate()
}
- }],
+ })
+ }
+
+
+ if (res.action === 'apply') {
+ primary.push({
+ label: `Apply`,
+ id: 'void.updater.apply',
+ enabled: true,
+ tooltip: '',
+ class: undefined,
+ run: () => {
+ updateService.applyUpdate()
+ }
+ })
+ }
+
+ if (res.action === 'restart') {
+ primary.push({
+ label: `Restart`,
+ id: 'void.updater.restart',
+ enabled: true,
+ tooltip: '',
+ class: undefined,
+ run: () => {
+ updateService.quitAndInstall()
+ }
+ })
+ }
+
+ primary.push({
+ id: 'void.updater.site',
+ enabled: true,
+ label: `Void Site`,
+ tooltip: '',
+ class: undefined,
+ run: () => {
+ const { window } = dom.getActiveWindow()
+ window.open('https://voideditor.com/')
+ }
+ })
+
+ actions = {
+ primary: primary,
secondary: [{
id: 'void.updater.close',
enabled: true,
@@ -57,19 +106,24 @@ const notifyYesUpdate = (notifService: INotificationService, res: { message?: st
notifController.close()
}
}]
- },
+ }
+ }
+ else {
+ actions = undefined
+ }
+
+ const notifController = notifService.notify({
+ severity: Severity.Info,
+ message: message,
+ sticky: true,
+ progress: actions ? { worked: 0, total: 100 } : undefined,
+ actions: actions,
})
// const d = notifController.onDidClose(() => {
// notifyYesUpdate(notifService, res)
// d.dispose()
// })
}
-const notifyNoUpdate = (notifService: INotificationService) => {
- notifService.notify({
- severity: Severity.Info,
- message: 'Void is up-to-date!',
- })
-}
const notifyErrChecking = (notifService: INotificationService) => {
const message = `Void Error: There was an error checking for updates. If this persists, please get in touch or reinstall Void [here](https://voideditor.com/download-beta)!`
notifService.notify({
@@ -80,6 +134,34 @@ const notifyErrChecking = (notifService: INotificationService) => {
}
+const performVoidCheck = async (
+ explicit: boolean,
+ notifService: INotificationService,
+ voidUpdateService: IVoidUpdateService,
+ metricsService: IMetricsService,
+ updateService: IUpdateService,
+) => {
+
+ const metricsTag = explicit ? 'Manual' : 'Auto'
+
+ metricsService.capture(`Void Update ${metricsTag}: Checking...`, {})
+ const res = await voidUpdateService.check(explicit)
+ if (!res) {
+ notifyErrChecking(notifService);
+ metricsService.capture(`Void Update ${metricsTag}: Error`, { res })
+ }
+ else {
+ if (res.message) {
+ notifyUpdate(res, notifService, updateService)
+ metricsService.capture(`Void Update ${metricsTag}: Yes`, { res })
+ }
+ else {
+ metricsService.capture(`Void Update ${metricsTag}: No`, { res })
+ return
+ }
+ }
+}
+
// Action
registerAction2(class extends Action2 {
@@ -94,12 +176,8 @@ registerAction2(class extends Action2 {
const voidUpdateService = accessor.get(IVoidUpdateService)
const notifService = accessor.get(INotificationService)
const metricsService = accessor.get(IMetricsService)
-
- metricsService.capture('Void Update Manual: Checking...', {})
- const res = await voidUpdateService.check(true)
- if (!res) { notifyErrChecking(notifService); metricsService.capture('Void Update Manual: Error', { res }) }
- else if (res.hasUpdate) { notifyYesUpdate(notifService, res); metricsService.capture('Void Update Manual: Yes', { res }) }
- else if (!res.hasUpdate) { notifyNoUpdate(notifService); metricsService.capture('Void Update Manual: No', { res }) }
+ const updateService = accessor.get(IUpdateService)
+ performVoidCheck(true, notifService, voidUpdateService, metricsService, updateService)
}
})
@@ -107,17 +185,15 @@ registerAction2(class extends Action2 {
class VoidUpdateWorkbenchContribution extends Disposable implements IWorkbenchContribution {
static readonly ID = 'workbench.contrib.void.voidUpdate'
constructor(
- @IVoidUpdateService private readonly voidUpdateService: IVoidUpdateService,
- @IMetricsService private readonly metricsService: IMetricsService,
- @INotificationService private readonly notifService: INotificationService,
+ @IVoidUpdateService voidUpdateService: IVoidUpdateService,
+ @IMetricsService metricsService: IMetricsService,
+ @INotificationService notifService: INotificationService,
+ @IUpdateService updateService: IUpdateService,
) {
super()
- const autoCheck = async () => {
- this.metricsService.capture('Void Update Startup: Checking...', {})
- const res = await this.voidUpdateService.check(false)
- if (!res) { notifyErrChecking(this.notifService); this.metricsService.capture('Void Update Startup: Error', { res }) }
- else if (res.hasUpdate) { notifyYesUpdate(this.notifService, res); this.metricsService.capture('Void Update Startup: Yes', { res }) }
- else if (!res.hasUpdate) { this.metricsService.capture('Void Update Startup: No', { res }) } // display nothing if up to date
+
+ const autoCheck = () => {
+ performVoidCheck(false, notifService, voidUpdateService, metricsService, updateService)
}
// check once 5 seconds after mount
diff --git a/src/vs/workbench/contrib/void/common/modelCapabilities.ts b/src/vs/workbench/contrib/void/common/modelCapabilities.ts
index 3268f1dc..daef05ab 100644
--- a/src/vs/workbench/contrib/void/common/modelCapabilities.ts
+++ b/src/vs/workbench/contrib/void/common/modelCapabilities.ts
@@ -122,8 +122,7 @@ export type VoidStaticModelInfo = { // not stateful
sizeGb: number | 'not-known'
}
- supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated'; // separated = anthropic where "system" is a special parameter
- supportsTools?: false | 'TODO-yes-but-we-handle-it-manually' | 'anthropic-style' | 'openai-style';
+ supportsSystemMessage: false | 'system-role' | 'developer-role' | 'separated'; // separated = anthropic where "system" is a special paramete
supportsFIM: boolean;
reasoningCapabilities: false | {
@@ -678,7 +677,6 @@ const ollamaModelOptions = {
downloadable: { sizeGb: 1.9 },
supportsFIM: true,
supportsSystemMessage: 'system-role',
- supportsTools: false,
reasoningCapabilities: false,
},
'qwen2.5-coder': {
@@ -688,7 +686,6 @@ const ollamaModelOptions = {
downloadable: { sizeGb: 4.7 },
supportsFIM: false,
supportsSystemMessage: 'system-role',
- supportsTools: false,
reasoningCapabilities: false,
},
'qwq': {
@@ -698,7 +695,6 @@ const ollamaModelOptions = {
downloadable: { sizeGb: 20 },
supportsFIM: false,
supportsSystemMessage: 'system-role',
- supportsTools: 'TODO-yes-but-we-handle-it-manually',
reasoningCapabilities: { supportsReasoning: true, canIOReasoning: false, canTurnOffReasoning: false, openSourceThinkTags: ['', ''] },
},
'deepseek-r1': {
@@ -708,7 +704,6 @@ const ollamaModelOptions = {
downloadable: { sizeGb: 4.7 },
supportsFIM: false,
supportsSystemMessage: 'system-role',
- supportsTools: 'TODO-yes-but-we-handle-it-manually',
reasoningCapabilities: { supportsReasoning: true, canIOReasoning: false, canTurnOffReasoning: false, openSourceThinkTags: ['', ''] },
},
@@ -825,7 +820,6 @@ const openRouterModelOptions_assumingOpenAICompat = {
maxOutputTokens: null,
cost: { input: 0.3, output: 0.9 },
downloadable: false,
- supportsTools: 'openai-style',
reasoningCapabilities: false,
},
'qwen/qwen-2.5-coder-32b-instruct': {
diff --git a/src/vs/workbench/contrib/void/common/voidUpdateService.ts b/src/vs/workbench/contrib/void/common/voidUpdateService.ts
index d6fe4140..fbf72b54 100644
--- a/src/vs/workbench/contrib/void/common/voidUpdateService.ts
+++ b/src/vs/workbench/contrib/void/common/voidUpdateService.ts
@@ -7,12 +7,13 @@ import { ProxyChannel } from '../../../../base/parts/ipc/common/ipc.js';
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js';
+import { VoidCheckUpdateRespose } from './voidUpdateServiceTypes.js';
export interface IVoidUpdateService {
readonly _serviceBrand: undefined;
- check: (explicit: boolean) => Promise<{ hasUpdate: true, message: string } | { hasUpdate: false } | null>;
+ check: (explicit: boolean) => Promise;
}
diff --git a/src/vs/workbench/contrib/void/common/voidUpdateServiceTypes.ts b/src/vs/workbench/contrib/void/common/voidUpdateServiceTypes.ts
new file mode 100644
index 00000000..eaf90dd1
--- /dev/null
+++ b/src/vs/workbench/contrib/void/common/voidUpdateServiceTypes.ts
@@ -0,0 +1,14 @@
+/*--------------------------------------------------------------------------------------
+ * Copyright 2025 Glass Devtools, Inc. All rights reserved.
+ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
+ *--------------------------------------------------------------------------------------*/
+
+export type VoidCheckUpdateRespose = {
+ message: string,
+ action?: 'reinstall' | 'restart' | 'download' | 'apply'
+} | {
+ message: null,
+ actions?: undefined,
+} | null
+
+
diff --git a/src/vs/workbench/contrib/void/electron-main/voidUpdateMainService.ts b/src/vs/workbench/contrib/void/electron-main/voidUpdateMainService.ts
index 7570d965..1f38a74c 100644
--- a/src/vs/workbench/contrib/void/electron-main/voidUpdateMainService.ts
+++ b/src/vs/workbench/contrib/void/electron-main/voidUpdateMainService.ts
@@ -8,6 +8,7 @@ import { IEnvironmentMainService } from '../../../../platform/environment/electr
import { IProductService } from '../../../../platform/product/common/productService.js';
import { IUpdateService, StateType } from '../../../../platform/update/common/update.js';
import { IVoidUpdateService } from '../common/voidUpdateService.js';
+import { VoidCheckUpdateRespose } from '../common/voidUpdateServiceTypes.js';
@@ -17,48 +18,86 @@ export class VoidMainUpdateService extends Disposable implements IVoidUpdateServ
constructor(
@IProductService private readonly _productService: IProductService,
@IEnvironmentMainService private readonly _envMainService: IEnvironmentMainService,
- @IUpdateService private readonly _updateService: IUpdateService
+ @IUpdateService private readonly _updateService: IUpdateService,
) {
super()
}
- nIgnores = 0
- async check(explicit: boolean) {
+ async check(explicit: boolean): Promise {
const isDevMode = !this._envMainService.isBuilt // found in abstractUpdateService.ts
if (isDevMode) {
- return { hasUpdate: false } as const
+ return { message: null } as const
}
this._updateService.checkForUpdates(false) // implicity check, then handle result ourselves
+ console.log('updateState', this._updateService.state)
+
+ if (this._updateService.state.type === StateType.Uninitialized) {
+ // The update service hasn't been initialized yet
+ return { message: explicit ? 'Not yet checking for updates...' : null, action: explicit ? 'reinstall' : undefined } as const
+ }
+
+ if (this._updateService.state.type === StateType.Idle) {
+ // No updates currently available
+ return { message: explicit ? 'No update found!' : null, action: explicit ? 'reinstall' : undefined } as const
+ }
+
+ if (this._updateService.state.type === StateType.CheckingForUpdates) {
+ // Currently checking for updates
+ return { message: explicit ? 'Currently checking for updates...' : null } as const
+ }
+
+ if (this._updateService.state.type === StateType.AvailableForDownload) {
+ // Update available but requires manual download (mainly for Linux)
+ return { message: 'A new update is available!', action: 'download', } as const
+ }
+
+ if (this._updateService.state.type === StateType.Downloading) {
+ // Update is currently being downloaded
+ return { message: explicit ? 'Currently downloading update...' : null } as const
+ }
+
+ if (this._updateService.state.type === StateType.Downloaded) {
+ // Update has been downloaded but not yet ready
+ return { message: explicit ? 'Got download, need to apply...' : null, action: 'apply' } as const
+ }
+
+ if (this._updateService.state.type === StateType.Updating) {
+ // Update is being applied
+ return { message: explicit ? 'Applying update...' : null } as const
+ }
+
if (this._updateService.state.type === StateType.Ready) {
- return { hasUpdate: true, message: 'Restart Void to update!' }
+ // Update is ready
+ return { message: 'Restart Void to update!', action: 'restart' } as const
}
- const wasAutomaticCheck = !explicit // ignore the first auto check, just use it to call updateService.check()
- if (wasAutomaticCheck && this.nIgnores < 1) {
- this.nIgnores += 1
- return { hasUpdate: false } as const
- }
+ if (this._updateService.state.type === StateType.Disabled) {
+ try {
+ const res = await fetch(`https://updates.voideditor.dev/api/v0/${this._productService.commit}`)
+ const resJSON = await res.json()
- try {
- const res = await fetch(`https://updates.voideditor.dev/api/v0/${this._productService.commit}`)
- const resJSON = await res.json()
+ if (!resJSON) return null // null means error
- if (!resJSON) return null // null means error
+ const { hasUpdate, downloadMessage } = resJSON ?? {}
+ if (hasUpdate === undefined)
+ return null
- const { hasUpdate, downloadMessage } = resJSON ?? {}
- if (hasUpdate === undefined)
+ const after = (downloadMessage || '') + ''
+ if (hasUpdate)
+ return { message: after, action: 'reinstall' } as const
+ return { message: 'Void is up-to-date!' } as const
+ }
+ catch (e) {
return null
+ }
+ }
+
+ return null
- const after = (downloadMessage || '') + ''
- return { hasUpdate: !!hasUpdate, message: after }
- }
- catch (e) {
- return null
- }
}
}