Merge pull request #391 from voideditor/model-selection

Prepare for 1.0.3 release
This commit is contained in:
Andrew Pareles 2025-04-11 14:26:19 -07:00 committed by GitHub
commit 5d5839736e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 251 additions and 90 deletions

View file

@ -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 <div className='!select-text cursor-auto'>
<SmallProseWrapper>
<ChatMarkdownRender string={changeDescription} codeURI={uri} chatMessageLocation={undefined} />
@ -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 = <span className='flex items-center'>
{uriDone ?
getBasename(toolCallSoFar.rawParams['uri'] ?? 'unknown')
: `Generating`}
<IconLoading />
</span>
// If URI has not been specified
return <ToolHeaderWrapper
title={title}
desc1={desc1}
desc2={<JumpToFileButton uri={uri} />}
>
<EditToolChildren
uri={uri}
changeDescription={toolCallSoFar.rawParams.change_description ?? ''}
/>
<IconLoading />
</ToolHeaderWrapper>
}
export const SidebarChat = () => {
const textAreaRef = useRef<HTMLTextAreaElement | null>(null)
const textAreaFnsRef = useRef<TextAreaFns | null>(null)
@ -2416,6 +2448,15 @@ export const SidebarChat = () => {
/> : null
// the tool currently being generated
const generatingTool = toolIsGenerating ?
toolCallSoFar.name === 'edit_file' ? <EditToolSoFar
key={getChatBubbleId(threadId, streamingChatIdx + 1)}
toolCallSoFar={toolCallSoFar}
/>
: null
: null
const messagesHTML = <ScrollToBottomContainer
key={'messages' + chatThreadsState.currentThreadId} // force rerender on all children if id changes
scrollContainerRef={scrollContainerRef}
@ -2432,17 +2473,11 @@ export const SidebarChat = () => {
{previousMessagesHTML}
{currStreamingMessageHTML}
{toolIsGenerating ?
<ToolHeaderWrapper key={getChatBubbleId(currentThread.id, streamingChatIdx + 1)}
title={toolCallSoFar && toolNames.includes(toolCallSoFar.name as ToolName) ?
titleOfToolName[toolCallSoFar.name as ToolName]?.proposed
: toolCallSoFar?.name}
desc1={<span className='flex items-center'>Generating<IconLoading /></span>}
/>
: null}
{/* Generating tool */}
{generatingTool}
{/* loading indicator */}
{isRunning === 'LLM' && !toolIsGenerating ? <ProseWrapper>
{/* loading indicator */}
{<IconLoading className='opacity-50 text-sm' />}
</ProseWrapper> : null}

View file

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

View file

@ -26,7 +26,7 @@ import { IVoidSettingsService } from '../common/voidSettingsService.js'
type ValidateParams = { [T in ToolName]: (p: RawToolParamsObj) => Promise<ToolCallParams[T]> }
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<ToolResultType[T]>) => 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)

View file

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

View file

@ -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: ['<think>', '</think>'] },
},
'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: ['<think>', '</think>'] },
},
@ -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': {

View file

@ -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<VoidCheckUpdateRespose>;
}

View file

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

View file

@ -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<VoidCheckUpdateRespose> {
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
}
}
}