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 - } } }