mirror of
https://github.com/voideditor/void
synced 2026-05-23 17:38:23 +00:00
bgTerminalId !== null
This commit is contained in:
parent
cf5ff98081
commit
e94d2de641
12 changed files with 631 additions and 297 deletions
|
|
@ -16,7 +16,7 @@ import { getErrorMessage, RawToolCallObj, RawToolParamsObj } from '../common/sen
|
|||
import { generateUuid } from '../../../../base/common/uuid.js';
|
||||
import { FeatureName, ModelSelection, ModelSelectionOptions } from '../common/voidSettingsTypes.js';
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||
import { ToolCallParams, ToolResultType, toolNamesThatRequireApproval } from '../common/toolsServiceTypes.js';
|
||||
import { approvalTypeOfToolName, ToolCallParams, ToolResultType } from '../common/toolsServiceTypes.js';
|
||||
import { IToolsService } from './toolsService.js';
|
||||
import { CancellationToken } from '../../../../base/common/cancellation.js';
|
||||
import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';
|
||||
|
|
@ -486,9 +486,11 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
if (toolName === 'edit_file') { this._addToolEditCheckpoint({ threadId, uri: (toolParams as ToolCallParams['edit_file']).uri }) }
|
||||
|
||||
// 2. if tool requires approval, break from the loop, awaiting approval
|
||||
const toolRequiresApproval = toolNamesThatRequireApproval.has(toolName)
|
||||
if (toolRequiresApproval) {
|
||||
const autoApprove = this._settingsService.state.globalSettings.autoApprove
|
||||
|
||||
|
||||
const approvalType = approvalTypeOfToolName[toolName]
|
||||
if (approvalType) {
|
||||
const autoApprove = this._settingsService.state.globalSettings.autoApprove[approvalType]
|
||||
// add a tool_request because we use it for UI if a tool is loading (this should be improved in the future)
|
||||
this._addMessageToThread(threadId, { role: 'tool', type: 'tool_request', content: '(never)', result: null, name: toolName, params: toolParams, id: toolId, rawParams: opts.unvalidatedToolParams })
|
||||
if (!autoApprove) {
|
||||
|
|
@ -1199,7 +1201,7 @@ We only need to do it for files that were edited since `from`, ie files between
|
|||
// else search codebase for `target`
|
||||
let uris: URI[] = []
|
||||
try {
|
||||
const { result } = await this._toolsService.callTool['search_pathnames_only']({ queryStr: target, searchInFolder: null, pageNumber: 0 })
|
||||
const { result } = await this._toolsService.callTool['search_pathnames_only']({ query: target, includePattern: null, pageNumber: 0 })
|
||||
uris = result.uris
|
||||
} catch (e) {
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -452,7 +452,6 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess
|
|||
return voidRules.trim();
|
||||
}
|
||||
catch (e) {
|
||||
console.log('Could not read .voidrules, continuing...')
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,10 @@ import { createDecorator } from '../../../../platform/instantiation/common/insta
|
|||
import { IFileService } from '../../../../platform/files/common/files.js';
|
||||
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
|
||||
import { ShallowDirectoryItem, ToolCallParams, ToolResultType } from '../common/toolsServiceTypes.js';
|
||||
import { MAX_CHILDREN_URIs_PAGE } from './toolsService.js';
|
||||
import { IExplorerService } from '../../files/browser/files.js';
|
||||
import { SortOrder } from '../../files/common/files.js';
|
||||
import { ExplorerItem } from '../../files/common/explorerModel.js';
|
||||
import { MAX_DIRSTR_CHARS_TOTAL_BEGINNING, MAX_DIRSTR_CHARS_TOTAL_TOOL } from '../common/prompt/prompts.js';
|
||||
import { MAX_CHILDREN_URIs_PAGE, MAX_DIRSTR_CHARS_TOTAL_BEGINNING, MAX_DIRSTR_CHARS_TOTAL_TOOL } from '../common/prompt/prompts.js';
|
||||
|
||||
|
||||
const MAX_FILES_TOTAL = 300;
|
||||
|
|
@ -111,14 +110,14 @@ export const computeDirectoryTree1Deep = async (
|
|||
|
||||
export const stringifyDirectoryTree1Deep = (params: ToolCallParams['ls_dir'], result: ToolResultType['ls_dir']): string => {
|
||||
if (!result.children) {
|
||||
return `Error: ${params.rootURI} is not a directory`;
|
||||
return `Error: ${params.uri} is not a directory`;
|
||||
}
|
||||
|
||||
let output = '';
|
||||
const entries = result.children;
|
||||
|
||||
if (!result.hasPrevPage) { // is first page
|
||||
output += `${params.rootURI.fsPath}\n`;
|
||||
output += `${params.uri.fsPath}\n`;
|
||||
}
|
||||
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
|
|
@ -419,7 +418,7 @@ class DirectoryStrService extends Disposable implements IDirectoryStrService {
|
|||
}
|
||||
|
||||
if (cutOff) {
|
||||
return `${str}\n${cutOffMessage}`
|
||||
return `${str.trimEnd()}\n${cutOffMessage}`
|
||||
}
|
||||
|
||||
return str
|
||||
|
|
|
|||
|
|
@ -31,14 +31,11 @@ import { mountCtrlK } from './react/out/quick-edit-tsx/index.js'
|
|||
import { QuickEditPropsType } from './quickEditActions.js';
|
||||
import { IModelContentChangedEvent } from '../../../../editor/common/textModelEvents.js';
|
||||
import { extractCodeFromFIM, extractCodeFromRegular, ExtractedSearchReplaceBlock, extractSearchReplaceBlocks } from '../common/helpers/extractCodeFromResult.js';
|
||||
import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';
|
||||
import { isMacintosh } from '../../../../base/common/platform.js';
|
||||
import { INotificationService, } from '../../../../platform/notification/common/notification.js';
|
||||
import { EditorOption } from '../../../../editor/common/config/editorOptions.js';
|
||||
import { Emitter } from '../../../../base/common/event.js';
|
||||
import { VOID_OPEN_SETTINGS_ACTION_ID } from './voidSettingsPane.js';
|
||||
import { ICommandService } from '../../../../platform/commands/common/commands.js';
|
||||
import { ILLMMessageService } from '../common/sendLLMMessageService.js';
|
||||
import { LLMChatMessage, OnError, errorDetails } from '../common/sendLLMMessageTypes.js';
|
||||
import { LLMChatMessage } from '../common/sendLLMMessageTypes.js';
|
||||
import { IMetricsService } from '../common/metricsService.js';
|
||||
import { IEditCodeService, AddCtrlKOpts, StartApplyingOpts, CallBeforeStartApplyingOpts, } from './editCodeServiceInterface.js';
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||
|
|
@ -48,6 +45,8 @@ import { deepClone } from '../../../../base/common/objects.js';
|
|||
import { acceptBg, acceptBorder, buttonFontSize, buttonTextColor, rejectBg, rejectBorder } from '../common/helpers/colors.js';
|
||||
import { DiffArea, Diff, CtrlKZone, VoidFileSnapshot, DiffAreaSnapshotEntry, diffAreaSnapshotKeys, DiffZone, TrackingZone, ComputedDiff } from '../common/editCodeServiceTypes.js';
|
||||
import { IConvertToLLMMessageService } from './convertToLLMMessageService.js';
|
||||
// import { isMacintosh } from '../../../../base/common/platform.js';
|
||||
// import { VOID_OPEN_SETTINGS_ACTION_ID } from './voidSettingsPane.js';
|
||||
|
||||
const configOfBG = (color: Color) => {
|
||||
return { dark: color, light: color, hcDark: color, hcLight: color, }
|
||||
|
|
@ -199,7 +198,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
@IConsistentEditorItemService private readonly _consistentEditorItemService: IConsistentEditorItemService,
|
||||
@IMetricsService private readonly _metricsService: IMetricsService,
|
||||
@INotificationService private readonly _notificationService: INotificationService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
// @ICommandService private readonly _commandService: ICommandService,
|
||||
@IVoidSettingsService private readonly _settingsService: IVoidSettingsService,
|
||||
// @IFileService private readonly _fileService: IFileService,
|
||||
@IVoidModelService private readonly _voidModelService: IVoidModelService,
|
||||
|
|
@ -279,24 +278,24 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
|
||||
|
||||
|
||||
private _notifyError = (e: Parameters<OnError>[0]) => {
|
||||
const details = errorDetails(e.fullError)
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Warning,
|
||||
message: `Void Error: ${e.message}`,
|
||||
actions: {
|
||||
secondary: [{
|
||||
id: 'void.onerror.opensettings',
|
||||
enabled: true,
|
||||
label: `Open Void's settings`,
|
||||
tooltip: '',
|
||||
class: undefined,
|
||||
run: () => { this._commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID) }
|
||||
}]
|
||||
},
|
||||
source: details ? `(Hold ${isMacintosh ? 'Option' : 'Alt'} to hover) - ${details}\n\nIf this persists, feel free to [report](https://github.com/voideditor/void/issues/new) it.` : undefined
|
||||
})
|
||||
}
|
||||
// private _notifyError = (e: Parameters<OnError>[0]) => {
|
||||
// const details = errorDetails(e.fullError)
|
||||
// this._notificationService.notify({
|
||||
// severity: Severity.Warning,
|
||||
// message: `Void Error: ${e.message}`,
|
||||
// actions: {
|
||||
// secondary: [{
|
||||
// id: 'void.onerror.opensettings',
|
||||
// enabled: true,
|
||||
// label: `Open Void's settings`,
|
||||
// tooltip: '',
|
||||
// class: undefined,
|
||||
// run: () => { this._commandService.executeCommand(VOID_OPEN_SETTINGS_ACTION_ID) }
|
||||
// }]
|
||||
// },
|
||||
// source: details ? `(Hold ${isMacintosh ? 'Option' : 'Alt'} to hover) - ${details}\n\nIf this persists, feel free to [report](https://github.com/voideditor/void/issues/new) it.` : undefined
|
||||
// })
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
|
@ -1393,7 +1392,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
|
||||
// throws
|
||||
const onError = (e: { message: string; fullError: Error | null; }) => {
|
||||
this._notifyError(e)
|
||||
// this._notifyError(e)
|
||||
onDone()
|
||||
this._undoHistory(uri)
|
||||
throw e.fullError
|
||||
|
|
@ -1612,7 +1611,7 @@ class EditCodeService extends Disposable implements IEditCodeService {
|
|||
}
|
||||
|
||||
const onError = (e: { message: string; fullError: Error | null; }) => {
|
||||
this._notifyError(e)
|
||||
// this._notifyError(e)
|
||||
onDone()
|
||||
this._undoHistory(uri)
|
||||
throw e.fullError || new Error(e.message) // throw error h
|
||||
|
|
|
|||
|
|
@ -20,17 +20,17 @@ import { VOID_OPEN_SETTINGS_ACTION_ID } from '../../../voidSettingsPane.js';
|
|||
import { ChatMode, displayInfoOfProviderName, FeatureName, isFeatureNameDisabled } from '../../../../../../../workbench/contrib/void/common/voidSettingsTypes.js';
|
||||
import { WarningBox } from '../void-settings-tsx/WarningBox.js';
|
||||
import { getModelCapabilities, getIsReasoningEnabledState } from '../../../../common/modelCapabilities.js';
|
||||
import { AlertTriangle, Ban, Check, ChevronRight, Dot, FileIcon, Pencil, Undo, Undo2, X, Flag, Copy as CopyIcon } from 'lucide-react';
|
||||
import { AlertTriangle, Ban, Check, ChevronRight, Dot, FileIcon, Pencil, Undo, Undo2, X, Flag, Copy as CopyIcon, Info } from 'lucide-react';
|
||||
import { ChatMessage, CheckpointEntry, StagingSelectionItem, ToolMessage } from '../../../../common/chatThreadServiceTypes.js';
|
||||
import { LintErrorItem, ToolCallParams, ToolNameWithApproval } from '../../../../common/toolsServiceTypes.js';
|
||||
import { approvalTypeOfToolName, LintErrorItem, ToolApprovalType, toolApprovalTypes, ToolCallParams } from '../../../../common/toolsServiceTypes.js';
|
||||
import { ApplyButtonsHTML, CopyButton, IconShell1, JumpToFileButton, JumpToTerminalButton, StatusIndicator, StatusIndicatorForApplyButton, useApplyButtonState } from '../markdown/ApplyBlockHoverButtons.js';
|
||||
import { IsRunningType } from '../../../chatThreadService.js';
|
||||
import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js';
|
||||
import { ToolName, toolNames } from '../../../../common/prompt/prompts.js';
|
||||
import { MAX_FILE_CHARS_PAGE, MAX_TERMINAL_INACTIVE_TIME, ToolName, toolNames } from '../../../../common/prompt/prompts.js';
|
||||
import { RawToolCallObj } from '../../../../common/sendLLMMessageTypes.js';
|
||||
import { MAX_FILE_CHARS_PAGE } from '../../../toolsService.js';
|
||||
import jsonStringify from 'fast-json-stable-stringify'
|
||||
import ErrorBoundary from './ErrorBoundary.js';
|
||||
import { ToolApprovalTypeSwitch } from '../void-settings-tsx/Settings.js';
|
||||
|
||||
|
||||
|
||||
|
|
@ -464,6 +464,22 @@ const ScrollToBottomContainer = ({ children, className, style, scrollContainerRe
|
|||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getRelative = (uri: URI, accessor: ReturnType<typeof useAccessor>) => {
|
||||
const workspaceContextService = accessor.get('IWorkspaceContextService')
|
||||
let path: string
|
||||
const isInside = workspaceContextService.isInsideWorkspace(uri)
|
||||
if (isInside) {
|
||||
const f = workspaceContextService.getWorkspace().folders.find(f => uri.fsPath.startsWith(f.uri.fsPath))
|
||||
if (f) { path = uri.fsPath.replace(f.uri.fsPath, '') }
|
||||
else { path = uri.fsPath }
|
||||
}
|
||||
else {
|
||||
path = uri.fsPath
|
||||
}
|
||||
return path || undefined
|
||||
}
|
||||
|
||||
export const getFolderName = (pathStr: string) => {
|
||||
// 'unixify' path
|
||||
pathStr = pathStr.replace(/[/\\]+/g, '/') // replace any / or \ or \\ with /
|
||||
|
|
@ -495,6 +511,9 @@ export const SelectedFiles = (
|
|||
const commandService = accessor.get('ICommandService')
|
||||
const modelReferenceService = accessor.get('IVoidModelService')
|
||||
|
||||
|
||||
|
||||
|
||||
// state for tracking prospective files
|
||||
const { uri: currentURI } = useActiveURI()
|
||||
const [recentUris, setRecentUris] = useState<URI[]>([])
|
||||
|
|
@ -653,6 +672,8 @@ type ToolHeaderParams = {
|
|||
desc1: React.ReactNode;
|
||||
desc2?: React.ReactNode;
|
||||
isError?: boolean;
|
||||
info?: string;
|
||||
desc1Info?: string;
|
||||
isRejected?: boolean;
|
||||
numResults?: number;
|
||||
hasNextPage?: boolean;
|
||||
|
|
@ -667,10 +688,12 @@ const ToolHeaderWrapper = ({
|
|||
icon,
|
||||
title,
|
||||
desc1,
|
||||
desc1Info,
|
||||
desc2,
|
||||
numResults,
|
||||
hasNextPage,
|
||||
children,
|
||||
info,
|
||||
bottomChildren,
|
||||
isError,
|
||||
onClick,
|
||||
|
|
@ -707,13 +730,33 @@ const ToolHeaderWrapper = ({
|
|||
`}
|
||||
/>)}
|
||||
<span className="text-void-fg-3 flex-shrink-0">{title}</span>
|
||||
<span className="text-void-fg-4 text-xs italic truncate ml-2">{desc1}</span>
|
||||
<span className="text-void-fg-4 text-xs italic truncate ml-2"
|
||||
{...desc1Info ? {
|
||||
'data-tooltip-id': 'void-tooltip',
|
||||
'data-tooltip-content': desc1Info,
|
||||
'data-tooltip-place': 'top-end',
|
||||
} : {}}
|
||||
>{desc1}</span>
|
||||
</div>
|
||||
|
||||
{/* right */}
|
||||
<div className="flex items-center gap-x-2 flex-shrink-0">
|
||||
{isError && <AlertTriangle className='text-void-warning opacity-90 flex-shrink-0' size={14} />}
|
||||
{isRejected && <Ban className='text-void-fg-4 opacity-90 flex-shrink-0' size={14} />}
|
||||
|
||||
|
||||
{isError && <AlertTriangle
|
||||
className='text-void-warning opacity-90 flex-shrink-0'
|
||||
size={14}
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-content={'Error applying'}
|
||||
data-tooltip-place='top'
|
||||
/>}
|
||||
{isRejected && <Ban
|
||||
className='text-void-fg-4 opacity-90 flex-shrink-0'
|
||||
size={14}
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-content={'Canceled'}
|
||||
data-tooltip-place='top'
|
||||
/>}
|
||||
{desc2 && <span className="text-void-fg-4 text-xs">
|
||||
{desc2}
|
||||
</span>}
|
||||
|
|
@ -722,6 +765,13 @@ const ToolHeaderWrapper = ({
|
|||
{`${numResults}${hasNextPage ? '+' : ''} result${numResults !== 1 ? 's' : ''}`}
|
||||
</span>
|
||||
)}
|
||||
{info && <Info
|
||||
className='text-void-bg-1 flex-shrink-0'
|
||||
size={14}
|
||||
data-tooltip-id='void-tooltip'
|
||||
data-tooltip-content={info}
|
||||
data-tooltip-place='top-end'
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1170,13 +1220,15 @@ const loadingTitleWrapper = (item: React.ReactNode): React.ReactNode => {
|
|||
const titleOfToolName = {
|
||||
'read_file': { done: 'Read file', proposed: 'Read file', running: loadingTitleWrapper('Reading file') },
|
||||
'ls_dir': { done: 'Inspected folder', proposed: 'Inspect folder', running: loadingTitleWrapper('Inspecting folder') },
|
||||
'get_dir_structure': { done: 'Inspected folder', proposed: 'Inspect folder', running: loadingTitleWrapper('Inspecting folder') },
|
||||
'get_dir_tree': { done: 'Inspected folder tree', proposed: 'Inspect folder tree', running: loadingTitleWrapper('Inspecting folder tree') },
|
||||
'search_pathnames_only': { done: 'Searched by file name', proposed: 'Search by file name', running: loadingTitleWrapper('Searching by file name') },
|
||||
'search_for_files': { done: 'Searched', proposed: 'Search', running: loadingTitleWrapper('Searching') },
|
||||
'create_file_or_folder': { done: `Created`, proposed: `Create`, running: loadingTitleWrapper(`Creating`) },
|
||||
'delete_file_or_folder': { done: `Deleted`, proposed: `Delete`, running: loadingTitleWrapper(`Deleting`) },
|
||||
'edit_file': { done: `Edited file`, proposed: 'Edit file', running: loadingTitleWrapper('Editing file') },
|
||||
'command_tool': { done: `Ran terminal`, proposed: 'Run terminal', running: loadingTitleWrapper('Running terminal') },
|
||||
'run_terminal': { done: `Ran terminal`, proposed: 'Run terminal', running: loadingTitleWrapper('Running terminal') },
|
||||
'open_bg_terminal': { done: `Opened terminal`, proposed: 'Open terminal', running: loadingTitleWrapper('Opening terminal') },
|
||||
'kill_bg_terminal': { done: `Killed terminal`, proposed: 'Kill terminal', running: loadingTitleWrapper('Killing terminal') },
|
||||
'read_lint_errors': { done: `Read lint errors`, proposed: 'Read lint errors', running: loadingTitleWrapper('Reading lint errors') },
|
||||
} as const satisfies Record<ToolName, { done: any, proposed: any, running: any }>
|
||||
|
||||
|
|
@ -1191,43 +1243,103 @@ const getTitle = (toolMessage: Pick<ChatMessage & { role: 'tool' }, 'name' | 'ty
|
|||
}
|
||||
|
||||
|
||||
const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName] | undefined): string => {
|
||||
const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName] | undefined, accessor: ReturnType<typeof useAccessor>): {
|
||||
desc1: string,
|
||||
desc1Info?: string,
|
||||
} => {
|
||||
|
||||
if (!_toolParams) {
|
||||
return '';
|
||||
return { desc1: '' };
|
||||
}
|
||||
|
||||
if (toolName === 'read_file') {
|
||||
const toolParams = _toolParams as ToolCallParams['read_file']
|
||||
return getBasename(toolParams.uri.fsPath);
|
||||
} else if (toolName === 'ls_dir') {
|
||||
const toolParams = _toolParams as ToolCallParams['ls_dir']
|
||||
return `${getFolderName(toolParams.rootURI.fsPath)}`;
|
||||
} else if (toolName === 'search_pathnames_only') {
|
||||
const toolParams = _toolParams as ToolCallParams['search_pathnames_only']
|
||||
return `"${toolParams.queryStr}"`;
|
||||
} else if (toolName === 'search_for_files') {
|
||||
const toolParams = _toolParams as ToolCallParams['search_for_files']
|
||||
return `"${toolParams.queryStr}"`;
|
||||
} else if (toolName === 'create_file_or_folder') {
|
||||
const toolParams = _toolParams as ToolCallParams['create_file_or_folder']
|
||||
return toolParams.isFolder ? getFolderName(toolParams.uri.fsPath) : getBasename(toolParams.uri.fsPath);
|
||||
} else if (toolName === 'delete_file_or_folder') {
|
||||
const toolParams = _toolParams as ToolCallParams['delete_file_or_folder']
|
||||
return toolParams.isFolder ? getFolderName(toolParams.uri.fsPath) : getBasename(toolParams.uri.fsPath);
|
||||
} else if (toolName === 'edit_file') {
|
||||
const toolParams = _toolParams as ToolCallParams['edit_file']
|
||||
return getBasename(toolParams.uri.fsPath);
|
||||
} else if (toolName === 'command_tool') {
|
||||
const toolParams = _toolParams as ToolCallParams['command_tool']
|
||||
return `"${toolParams.command}"`;
|
||||
} else {
|
||||
return ''
|
||||
const x = {
|
||||
'read_file': () => {
|
||||
const toolParams = _toolParams as ToolCallParams['read_file']
|
||||
return {
|
||||
desc1: getBasename(toolParams.uri.fsPath),
|
||||
desc1Info: getRelative(toolParams.uri, accessor),
|
||||
};
|
||||
},
|
||||
'ls_dir': () => {
|
||||
const toolParams = _toolParams as ToolCallParams['ls_dir']
|
||||
return {
|
||||
desc1: getFolderName(toolParams.uri.fsPath),
|
||||
desc1Info: getRelative(toolParams.uri, accessor),
|
||||
};
|
||||
},
|
||||
'search_pathnames_only': () => {
|
||||
const toolParams = _toolParams as ToolCallParams['search_pathnames_only']
|
||||
return {
|
||||
desc1: `"${toolParams.query}"`,
|
||||
}
|
||||
},
|
||||
'search_for_files': () => {
|
||||
const toolParams = _toolParams as ToolCallParams['search_for_files']
|
||||
return {
|
||||
desc1: `"${toolParams.query}"`,
|
||||
}
|
||||
},
|
||||
'create_file_or_folder': () => {
|
||||
const toolParams = _toolParams as ToolCallParams['create_file_or_folder']
|
||||
return {
|
||||
desc1: toolParams.isFolder ? getFolderName(toolParams.uri.fsPath) ?? '/' : getBasename(toolParams.uri.fsPath),
|
||||
desc1Info: getRelative(toolParams.uri, accessor),
|
||||
}
|
||||
},
|
||||
'delete_file_or_folder': () => {
|
||||
const toolParams = _toolParams as ToolCallParams['delete_file_or_folder']
|
||||
return {
|
||||
desc1: toolParams.isFolder ? getFolderName(toolParams.uri.fsPath) ?? '/' : getBasename(toolParams.uri.fsPath),
|
||||
desc1Info: getRelative(toolParams.uri, accessor),
|
||||
}
|
||||
},
|
||||
'edit_file': () => {
|
||||
const toolParams = _toolParams as ToolCallParams['edit_file']
|
||||
return {
|
||||
desc1: getBasename(toolParams.uri.fsPath),
|
||||
desc1Info: getRelative(toolParams.uri, accessor),
|
||||
}
|
||||
},
|
||||
'run_terminal': () => {
|
||||
const toolParams = _toolParams as ToolCallParams['run_terminal']
|
||||
return {
|
||||
desc1: `"${toolParams.command}"`,
|
||||
desc1Info: toolParams.bgTerminalId
|
||||
}
|
||||
},
|
||||
'open_bg_terminal': () => {
|
||||
const toolParams = _toolParams as ToolCallParams['open_bg_terminal']
|
||||
return { desc1: '' }
|
||||
},
|
||||
'kill_bg_terminal': () => {
|
||||
const toolParams = _toolParams as ToolCallParams['kill_bg_terminal']
|
||||
return { desc1: toolParams.terminalId }
|
||||
},
|
||||
'get_dir_tree': () => {
|
||||
const toolParams = _toolParams as ToolCallParams['get_dir_tree']
|
||||
return {
|
||||
desc1: getFolderName(toolParams.uri.fsPath) ?? '/',
|
||||
desc1Info: getRelative(toolParams.uri, accessor),
|
||||
}
|
||||
},
|
||||
'read_lint_errors': () => {
|
||||
const toolParams = _toolParams as ToolCallParams['read_lint_errors']
|
||||
return {
|
||||
desc1: getBasename(toolParams.uri.fsPath),
|
||||
desc1Info: getRelative(toolParams.uri, accessor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return x[toolName]?.() || { desc1: '' }
|
||||
}
|
||||
catch {
|
||||
return { desc1: '' }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const ToolRequestAcceptRejectButtons = () => {
|
||||
const ToolRequestAcceptRejectButtons = ({ toolName }: { toolName: ToolName }) => {
|
||||
const accessor = useAccessor()
|
||||
const chatThreadsService = accessor.get('IChatThreadService')
|
||||
const metricsService = accessor.get('IMetricsService')
|
||||
|
|
@ -1250,11 +1362,6 @@ const ToolRequestAcceptRejectButtons = () => {
|
|||
metricsService.capture('Tool Request Rejected', {})
|
||||
}, [chatThreadsService, metricsService])
|
||||
|
||||
const onToggleAutoApprove = useCallback((newValue: boolean) => {
|
||||
voidSettingsService.setGlobalSetting('autoApprove', newValue)
|
||||
metricsService.capture('Tool Auto-Accept Toggle', { enabled: newValue })
|
||||
}, [voidSettingsService, metricsService])
|
||||
|
||||
const approveButton = (
|
||||
<button
|
||||
onClick={onAccept}
|
||||
|
|
@ -1287,21 +1394,15 @@ const ToolRequestAcceptRejectButtons = () => {
|
|||
</button>
|
||||
)
|
||||
|
||||
const autoApproveToggle = (
|
||||
<div className="flex items-center ml-2 gap-x-1">
|
||||
<VoidSwitch
|
||||
size="xxs"
|
||||
value={voidSettingsState.globalSettings.autoApprove}
|
||||
onChange={onToggleAutoApprove}
|
||||
/>
|
||||
<span className="text-void-fg-3 text-xs">Auto-approve</span>
|
||||
</div>
|
||||
)
|
||||
const approvalType = approvalTypeOfToolName[toolName]
|
||||
const approvalToggle = approvalType ? <div key={approvalType} className="flex items-center ml-2 gap-x-1">
|
||||
<ToolApprovalTypeSwitch size='xs' approvalType={approvalType} desc='Auto-approve' />
|
||||
</div> : null
|
||||
|
||||
return <div className="flex gap-2 mx-4 items-center">
|
||||
{approveButton}
|
||||
{cancelButton}
|
||||
{autoApproveToggle}
|
||||
{approvalToggle}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
@ -1432,8 +1533,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
|
||||
const title = getTitle(toolMessage)
|
||||
|
||||
const { uri } = toolMessage.params ?? {}
|
||||
const desc1 = uri ? getBasename(uri.fsPath) : '';
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor);
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null
|
||||
|
|
@ -1441,7 +1541,8 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
|
||||
|
||||
if (toolMessage.params.startLine !== null || toolMessage.params.endLine !== null) {
|
||||
const start = toolMessage.params.startLine === null ? `1` : `${toolMessage.params.startLine}`
|
||||
|
|
@ -1451,7 +1552,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
}
|
||||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
if (result.hasNextPage && params.pageNumber === 1) // first page
|
||||
componentParams.desc2 = `(truncated after ${Math.round(MAX_FILE_CHARS_PAGE) / 1000}k)`
|
||||
|
|
@ -1459,8 +1560,8 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
componentParams.desc2 = `(part ${params.pageNumber})`
|
||||
}
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { params, result } = toolMessage
|
||||
if (params) componentParams.desc2 = <JumpToFileButton uri={params.uri} />
|
||||
const { result } = toolMessage
|
||||
componentParams.desc2 = <JumpToFileButton uri={params.uri} />
|
||||
componentParams.children = <ToolChildrenWrapper>
|
||||
<CodeChildren>
|
||||
{result}
|
||||
|
|
@ -1471,13 +1572,13 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
return <ToolHeaderWrapper {...componentParams} />
|
||||
},
|
||||
},
|
||||
'get_dir_structure': {
|
||||
'get_dir_tree': {
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
|
||||
const title = getTitle(toolMessage)
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.params)
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null
|
||||
|
|
@ -1485,10 +1586,16 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
|
||||
|
||||
if (params.uri) {
|
||||
const rel = getRelative(params.uri, accessor)
|
||||
if (rel) componentParams.info = `Only in ${rel}`
|
||||
}
|
||||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
componentParams.children = <ToolChildrenWrapper>
|
||||
<SmallProseWrapper>
|
||||
<ChatMarkdownRender
|
||||
|
|
@ -1501,7 +1608,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
</ToolChildrenWrapper>
|
||||
}
|
||||
else {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
componentParams.children = <ToolChildrenWrapper>
|
||||
<CodeChildren>
|
||||
{result}
|
||||
|
|
@ -1519,7 +1626,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
const commandService = accessor.get('ICommandService')
|
||||
const explorerService = accessor.get('IExplorerService')
|
||||
const title = getTitle(toolMessage)
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.params)
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null
|
||||
|
|
@ -1527,10 +1634,16 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
|
||||
|
||||
if (params.uri) {
|
||||
const rel = getRelative(params.uri, accessor)
|
||||
if (rel) componentParams.info = `Only in ${rel}`
|
||||
}
|
||||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
componentParams.numResults = result.children?.length
|
||||
componentParams.hasNextPage = result.hasNextPage
|
||||
componentParams.children = !result.children || (result.children.length ?? 0) === 0 ? undefined
|
||||
|
|
@ -1550,7 +1663,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
</ToolChildrenWrapper>
|
||||
}
|
||||
else {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
componentParams.children = <ToolChildrenWrapper>
|
||||
<CodeChildren>
|
||||
{result}
|
||||
|
|
@ -1567,22 +1680,25 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
const commandService = accessor.get('ICommandService')
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const title = getTitle(toolMessage)
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.params)
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null
|
||||
if (toolMessage.type === 'rejected') return null // will never happen, not rejectable
|
||||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
|
||||
|
||||
if (params.includePattern)
|
||||
componentParams.info = `Only in ${params.includePattern}`
|
||||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { params, result, rawParams } = toolMessage
|
||||
const { result, rawParams } = toolMessage
|
||||
componentParams.numResults = result.uris.length
|
||||
componentParams.hasNextPage = result.hasNextPage
|
||||
componentParams.children = result.uris.length === 0 ? undefined
|
||||
: <ToolChildrenWrapper>
|
||||
{rawParams.search_in_folder ? `Search in ${rawParams.search_in_folder}` : null}
|
||||
{result.uris.map((uri, i) => (<ListableToolItem key={i}
|
||||
name={getBasename(uri.fsPath)}
|
||||
className='w-full overflow-auto'
|
||||
|
|
@ -1595,7 +1711,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
</ToolChildrenWrapper>
|
||||
}
|
||||
else {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
componentParams.children = <ToolChildrenWrapper>
|
||||
<CodeChildren>
|
||||
{result}
|
||||
|
|
@ -1612,22 +1728,27 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
const commandService = accessor.get('ICommandService')
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const title = getTitle(toolMessage)
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.params)
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null
|
||||
if (toolMessage.type === 'rejected') return null // will never happen, not rejectable
|
||||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
|
||||
|
||||
if (params.searchInFolder) {
|
||||
const rel = getRelative(params.searchInFolder, accessor)
|
||||
if (rel) componentParams.desc1Info = `Only in ${rel}`
|
||||
}
|
||||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { params, result, rawParams } = toolMessage
|
||||
const { result, rawParams } = toolMessage
|
||||
componentParams.numResults = result.uris.length
|
||||
componentParams.hasNextPage = result.hasNextPage
|
||||
componentParams.children = result.uris.length === 0 ? undefined
|
||||
: <ToolChildrenWrapper>
|
||||
{rawParams.search_in_folder ? `Search in ${rawParams.search_in_folder}` : null}
|
||||
{result.uris.map((uri, i) => (<ListableToolItem key={i}
|
||||
name={getBasename(uri.fsPath)}
|
||||
className='w-full overflow-auto'
|
||||
|
|
@ -1640,7 +1761,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
</ToolChildrenWrapper>
|
||||
}
|
||||
else {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
componentParams.children = <ToolChildrenWrapper>
|
||||
<CodeChildren>
|
||||
{result}
|
||||
|
|
@ -1659,7 +1780,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
const title = getTitle(toolMessage)
|
||||
|
||||
const { uri } = toolMessage.params ?? {}
|
||||
const desc1 = uri ? getBasename(uri.fsPath) : '';
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null
|
||||
|
|
@ -1667,10 +1788,13 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
|
||||
|
||||
componentParams.info = getRelative(uri, accessor) // full path
|
||||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
if (result.lintErrors)
|
||||
componentParams.children = <LintErrorChildren lintErrors={result.lintErrors} />
|
||||
|
|
@ -1679,7 +1803,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
|
||||
}
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
if (params) componentParams.desc2 = <JumpToFileButton uri={params.uri} />
|
||||
componentParams.children = <ToolChildrenWrapper>
|
||||
<CodeChildren>
|
||||
|
|
@ -1701,21 +1825,24 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
const isError = toolMessage.type === 'tool_error'
|
||||
const isRejected = toolMessage.type === 'rejected'
|
||||
const title = getTitle(toolMessage)
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.params)
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon, isRejected }
|
||||
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
componentParams.info = getRelative(params.uri, accessor) // full path
|
||||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
}
|
||||
else if (toolMessage.type === 'rejected') {
|
||||
const { params } = toolMessage
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
}
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
if (params) { componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } }
|
||||
componentParams.children = componentParams.children = <ToolChildrenWrapper>
|
||||
<CodeChildren>
|
||||
|
|
@ -1741,21 +1868,23 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
const isError = toolMessage.type === 'tool_error'
|
||||
const isRejected = toolMessage.type === 'rejected'
|
||||
const title = getTitle(toolMessage)
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.params)
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon, isRejected }
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
componentParams.info = getRelative(params.uri, accessor) // full path
|
||||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
}
|
||||
else if (toolMessage.type === 'rejected') {
|
||||
const { params } = toolMessage
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
}
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
if (params) { componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) } }
|
||||
componentParams.children = componentParams.children = <ToolChildrenWrapper>
|
||||
<CodeChildren>
|
||||
|
|
@ -1764,11 +1893,11 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
</ToolChildrenWrapper>
|
||||
}
|
||||
else if (toolMessage.type === 'running_now') {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
}
|
||||
else if (toolMessage.type === 'tool_request') {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
}
|
||||
|
||||
|
|
@ -1783,13 +1912,15 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
|
||||
const title = getTitle(toolMessage)
|
||||
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.params)
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon, isRejected }
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
componentParams.info = getRelative(params.uri, accessor) // full path
|
||||
|
||||
if (toolMessage.type === 'running_now' || toolMessage.type === 'tool_request') {
|
||||
const { params } = toolMessage
|
||||
componentParams.children = <ToolChildrenWrapper className='bg-void-bg-3'>
|
||||
<EditToolChildren
|
||||
uri={params.uri}
|
||||
|
|
@ -1799,8 +1930,6 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
componentParams.desc2 = <JumpToFileButton uri={params.uri} />
|
||||
}
|
||||
else if (toolMessage.type === 'success' || toolMessage.type === 'rejected' || toolMessage.type === 'tool_error') {
|
||||
const { params } = toolMessage
|
||||
|
||||
// add apply box
|
||||
if (params) {
|
||||
const applyBoxId = getApplyBoxId({
|
||||
|
|
@ -1818,7 +1947,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
|
||||
// add children
|
||||
if (toolMessage.type !== 'tool_error') {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
|
||||
componentParams.bottomChildren = <EditToolLintErrors lintErrors={result?.lintErrors || []} />
|
||||
|
||||
|
|
@ -1831,7 +1960,7 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
}
|
||||
else {
|
||||
// error
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
if (params) {
|
||||
componentParams.children = <ToolChildrenWrapper className='bg-void-bg-3'>
|
||||
{/* error */}
|
||||
|
|
@ -1857,23 +1986,27 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
return <ToolHeaderWrapper {...componentParams} />
|
||||
}
|
||||
},
|
||||
'command_tool': {
|
||||
|
||||
// ---
|
||||
|
||||
'run_terminal': {
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const terminalToolsService = accessor.get('ITerminalToolService')
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const title = getTitle(toolMessage)
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.params)
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const icon = null
|
||||
|
||||
const isRejected = toolMessage.type === 'rejected'
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon, isRejected }
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { params, result } = toolMessage
|
||||
const { result } = toolMessage
|
||||
const { command } = params
|
||||
const { terminalId, resolveReason, result: terminalResult } = result
|
||||
const { resolveReason, result: terminalResult } = result
|
||||
|
||||
// it's unclear that this is a button and not an icon.
|
||||
// componentParams.desc2 = <JumpToTerminalButton
|
||||
|
|
@ -1881,20 +2014,16 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
// />
|
||||
|
||||
const additionalDetailsStr = resolveReason.type === 'done' ? (resolveReason.exitCode !== 0 ? `\nError: exit code ${resolveReason.exitCode}` : null)
|
||||
: resolveReason.type === 'bgtask' ? null :
|
||||
resolveReason.type === 'timeout' ? `\n(partial results; request timed out)` :
|
||||
resolveReason.type === 'toofull' ? `\n(truncated)`
|
||||
: null
|
||||
: resolveReason.type === 'timeout' ? `\n(timed out)`
|
||||
: null
|
||||
|
||||
componentParams.children = <ToolChildrenWrapper className='whitespace-pre text-nowrap overflow-auto text-sm'>
|
||||
|
||||
<div className='!select-text cursor-auto'>
|
||||
<div>
|
||||
<span className="text-void-fg-1 font-sans">{`Ran command: `}</span>
|
||||
<span className="font-mono">{command}</span>
|
||||
</div>
|
||||
{(terminalResult + additionalDetailsStr).length && <div>
|
||||
<span>{resolveReason.type === 'bgtask' ? 'Result so far:\n' : null}</span>
|
||||
<span className='text-void-fg-1'>{`Result: `}</span>
|
||||
<span className="font-mono">{terminalResult}</span>
|
||||
<span className="font-mono">{additionalDetailsStr}</span>
|
||||
|
|
@ -1902,18 +2031,18 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
</div>
|
||||
</ToolChildrenWrapper>
|
||||
|
||||
if (params.bgTerminalId)
|
||||
componentParams.desc2 = `(terminal ${params.bgTerminalId})`
|
||||
|
||||
if (resolveReason.type === 'bgtask')
|
||||
componentParams.desc2 = '(background task)'
|
||||
}
|
||||
else if (toolMessage.type === 'rejected' || toolMessage.type === 'tool_error') {
|
||||
const { params } = toolMessage
|
||||
if (params) {
|
||||
const { proposedTerminalId, waitForCompletion } = params
|
||||
if (terminalToolsService.terminalExists(proposedTerminalId))
|
||||
componentParams.onClick = () => terminalToolsService.openTerminal(proposedTerminalId)
|
||||
if (!waitForCompletion)
|
||||
componentParams.desc2 = '(background task)'
|
||||
const { bgTerminalId, command } = params
|
||||
if (bgTerminalId) {
|
||||
componentParams.desc2 = '(persistent terminal)'
|
||||
if (terminalToolsService.terminalExists(bgTerminalId))
|
||||
componentParams.onClick = () => terminalToolsService.focusTerminal(bgTerminalId)
|
||||
}
|
||||
}
|
||||
if (toolMessage.type === 'tool_error') {
|
||||
const { result } = toolMessage
|
||||
|
|
@ -1921,16 +2050,81 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
}
|
||||
}
|
||||
else if (toolMessage.type === 'running_now' || toolMessage.type === 'tool_request') {
|
||||
const { proposedTerminalId, waitForCompletion } = toolMessage.params
|
||||
if (terminalToolsService.terminalExists(proposedTerminalId))
|
||||
componentParams.onClick = () => terminalToolsService.openTerminal(proposedTerminalId)
|
||||
if (!waitForCompletion)
|
||||
componentParams.desc2 = '(background task)'
|
||||
const { bgTerminalId } = toolMessage.params
|
||||
if (bgTerminalId) {
|
||||
componentParams.desc2 = '(persistent terminal)'
|
||||
if (terminalToolsService.terminalExists(bgTerminalId))
|
||||
componentParams.onClick = () => terminalToolsService.focusTerminal(bgTerminalId)
|
||||
}
|
||||
}
|
||||
|
||||
return <ToolHeaderWrapper {...componentParams} />
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'open_bg_terminal': {
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const title = getTitle(toolMessage)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null
|
||||
if (toolMessage.type === 'rejected') return null // will never happen, not rejectable
|
||||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
|
||||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { result } = toolMessage
|
||||
}
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { result } = toolMessage
|
||||
componentParams.children = <ToolChildrenWrapper>
|
||||
<CodeChildren>
|
||||
{result}
|
||||
</CodeChildren>
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
|
||||
return <ToolHeaderWrapper {...componentParams} />
|
||||
},
|
||||
},
|
||||
'kill_bg_terminal': {
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
|
||||
const { desc1, desc1Info } = toolNameToDesc(toolMessage.name, toolMessage.params, accessor)
|
||||
const title = getTitle(toolMessage)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null
|
||||
if (toolMessage.type === 'rejected') return null // will never happen, not rejectable
|
||||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const isError = toolMessage.type === 'tool_error'
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1Info, isError, icon, }
|
||||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { result } = toolMessage
|
||||
}
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { result } = toolMessage
|
||||
componentParams.children = <ToolChildrenWrapper>
|
||||
<CodeChildren>
|
||||
{result}
|
||||
</CodeChildren>
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
|
||||
return <ToolHeaderWrapper {...componentParams} />
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -2024,7 +2218,7 @@ const _ChatBubble = ({ threadId, chatMessage, currCheckpointIdx, isCommitted, me
|
|||
</div>
|
||||
{chatMessage.type === 'tool_request' ?
|
||||
<div className={`${isCheckpointGhost ? 'opacity-50 pointer-events-none' : ''}`}>
|
||||
<ToolRequestAcceptRejectButtons />
|
||||
<ToolRequestAcceptRejectButtons toolName={chatMessage.name} />
|
||||
</div> : null}
|
||||
</>
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { ChatMarkdownRender } from '../markdown/ChatMarkdownRender.js'
|
|||
import { WarningBox } from './WarningBox.js'
|
||||
import { os } from '../../../../common/helpers/systemInfo.js'
|
||||
import { IconLoading } from '../sidebar-tsx/SidebarChat.js'
|
||||
import { ToolApprovalType, toolApprovalTypes } from '../../../../common/toolsServiceTypes.js'
|
||||
|
||||
|
||||
const ButtonLeftTextRightOption = ({ text, leftButton }: { text: string, leftButton?: React.ReactNode }) => {
|
||||
|
|
@ -711,6 +712,34 @@ const transferTheseFilesOfOS = (os: 'mac' | 'windows' | 'linux' | null, fromEdit
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export const ToolApprovalTypeSwitch = ({ approvalType, size, desc }: { approvalType: ToolApprovalType, size: "xxs" | "xs" | "sm" | "sm+" | "md", desc: string }) => {
|
||||
const accessor = useAccessor()
|
||||
const voidSettingsService = accessor.get('IVoidSettingsService')
|
||||
const voidSettingsState = useSettingsState()
|
||||
const metricsService = accessor.get('IMetricsService')
|
||||
|
||||
const onToggleAutoApprove = useCallback((approvalType: ToolApprovalType, newValue: boolean) => {
|
||||
voidSettingsService.setGlobalSetting('autoApprove', {
|
||||
...voidSettingsService.state.globalSettings.autoApprove,
|
||||
[approvalType]: newValue
|
||||
})
|
||||
metricsService.capture('Tool Auto-Accept Toggle', { enabled: newValue })
|
||||
}, [voidSettingsService, metricsService])
|
||||
|
||||
return <>
|
||||
<VoidSwitch
|
||||
size={size}
|
||||
value={voidSettingsState.globalSettings.autoApprove[approvalType] ?? false}
|
||||
onChange={(newVal) => onToggleAutoApprove(approvalType, newVal)}
|
||||
/>
|
||||
<span className="text-void-fg-3 text-xs">{desc}</span>
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const OneClickSwitchButton = ({ fromEditor = 'VS Code', className = '' }: { fromEditor?: TransferEditorType, className?: string }) => {
|
||||
const accessor = useAccessor()
|
||||
const fileService = accessor.get('IFileService')
|
||||
|
|
@ -933,15 +962,12 @@ export const Settings = () => {
|
|||
<div className='my-2'>
|
||||
{/* Auto Accept Switch */}
|
||||
<ErrorBoundary>
|
||||
{[...toolApprovalTypes].map((approvalType) => {
|
||||
return <div key={approvalType} className="flex items-center gap-x-2 my-2">
|
||||
<ToolApprovalTypeSwitch size='xs' approvalType={approvalType} desc={`Auto-approve ${approvalType}`} />
|
||||
</div>
|
||||
})}
|
||||
|
||||
<div className='flex items-center gap-x-2 my-2'>
|
||||
<VoidSwitch
|
||||
size='xs'
|
||||
value={settingsState.globalSettings.autoApprove}
|
||||
onChange={(newVal) => voidSettingsService.setGlobalSetting('autoApprove', newVal)}
|
||||
/>
|
||||
<span className='text-void-fg-3 text-xs pointer-events-none'>{settingsState.globalSettings.autoApprove ? 'Auto-approve' : 'Auto-approve'}</span>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
|
||||
{/* Tool Lint Errors Switch */}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@
|
|||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { Disposable, IDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { removeAnsiEscapeCodes } from '../../../../base/common/strings.js';
|
||||
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { TerminalLocation } from '../../../../platform/terminal/common/terminal.js';
|
||||
import { TerminalExitReason, TerminalLocation } from '../../../../platform/terminal/common/terminal.js';
|
||||
import { ITerminalService, ITerminalInstance } from '../../../../workbench/contrib/terminal/browser/terminal.js';
|
||||
import { MAX_TERMINAL_CHARS, MAX_TERMINAL_INACTIVE_TIME } from '../common/prompt/prompts.js';
|
||||
import { TerminalResolveReason } from '../common/toolsServiceTypes.js';
|
||||
import { MAX_TERMINAL_CHARS_PAGE, TERMINAL_BG_WAIT_TIME, TERMINAL_TIMEOUT_TIME } from './toolsService.js';
|
||||
|
||||
|
||||
|
||||
|
|
@ -18,9 +18,12 @@ export interface ITerminalToolService {
|
|||
readonly _serviceBrand: undefined;
|
||||
|
||||
listTerminalIds(): string[];
|
||||
runCommand(command: string, proposedTerminalId: string, waitForCompletion: boolean): Promise<{ terminalId: string, didCreateTerminal: boolean, result: string, resolveReason: TerminalResolveReason }>;
|
||||
openTerminal(terminalId: string): Promise<void>
|
||||
runCommand(command: string, bgTerminalId: string | null): Promise<{ result: string, resolveReason: TerminalResolveReason }>;
|
||||
focusTerminal(terminalId: string): Promise<void>
|
||||
terminalExists(terminalId: string): boolean
|
||||
|
||||
createTerminal(): Promise<string>
|
||||
killTerminal(terminalId: string): Promise<void>
|
||||
}
|
||||
|
||||
export const ITerminalToolService = createDecorator<ITerminalToolService>('TerminalToolService');
|
||||
|
|
@ -103,12 +106,7 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
throw new Error('This should never be reached by pigeonhole principle');
|
||||
}
|
||||
|
||||
|
||||
|
||||
private async _getOrCreateTerminal(proposedTerminalId: string) {
|
||||
// if terminal ID exists, return it
|
||||
if (proposedTerminalId in this.terminalInstanceOfId) return { terminalId: proposedTerminalId, didCreateTerminal: false }
|
||||
|
||||
async createTerminal() {
|
||||
// create new terminal and return its ID
|
||||
const terminalId = this.getValidNewTerminalId();
|
||||
const terminal = await this.terminalService.createTerminal({
|
||||
|
|
@ -127,13 +125,21 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
})
|
||||
disposables.push(d)
|
||||
})
|
||||
const waitForTimeout = new Promise<void>(res => { setTimeout(() => { res() }, 1000) })
|
||||
const waitForTimeout = new Promise<void>(res => { setTimeout(() => { res() }, 5000) })
|
||||
|
||||
await Promise.any([waitForMount, waitForTimeout,])
|
||||
disposables.forEach(d => d.dispose())
|
||||
|
||||
this.terminalInstanceOfId[terminalId] = terminal
|
||||
return { terminalId, didCreateTerminal: true }
|
||||
return terminalId
|
||||
}
|
||||
|
||||
async killTerminal(terminalId: string) {
|
||||
const terminal = this.terminalInstanceOfId[terminalId]
|
||||
if (!terminal) throw new Error(`Kill Terminal: Terminal with ID ${terminalId} did not exist.`);
|
||||
terminal.dispose(TerminalExitReason.Extension)
|
||||
delete this.terminalInstanceOfId[terminalId]
|
||||
return
|
||||
}
|
||||
|
||||
terminalExists(terminalId: string): boolean {
|
||||
|
|
@ -141,7 +147,7 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
}
|
||||
|
||||
|
||||
openTerminal: ITerminalToolService['openTerminal'] = async (terminalId) => {
|
||||
focusTerminal: ITerminalToolService['focusTerminal'] = async (terminalId) => {
|
||||
if (!terminalId) return
|
||||
const terminal = this.terminalInstanceOfId[terminalId]
|
||||
if (!terminal) return // should never happen
|
||||
|
|
@ -151,11 +157,26 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
|
||||
|
||||
|
||||
runCommand: ITerminalToolService['runCommand'] = async (command, proposedTerminalId, waitForCompletion) => {
|
||||
|
||||
runCommand: ITerminalToolService['runCommand'] = async (command, bgTerminalId) => {
|
||||
await this.terminalService.whenConnected;
|
||||
const { terminalId, didCreateTerminal } = await this._getOrCreateTerminal(proposedTerminalId)
|
||||
const terminal = this.terminalInstanceOfId[terminalId];
|
||||
if (!terminal) throw new Error(`Unexpected internal error: Terminal with ID ${terminalId} did not exist.`);
|
||||
|
||||
let terminal: ITerminalInstance
|
||||
const disposables: IDisposable[] = []
|
||||
|
||||
const isBG = bgTerminalId !== null
|
||||
let terminalId: string
|
||||
if (isBG) { // BG process
|
||||
terminal = this.terminalInstanceOfId[bgTerminalId];
|
||||
if (!terminal) throw new Error(`Unexpected internal error: Terminal with ID ${bgTerminalId} did not exist.`);
|
||||
terminalId = bgTerminalId
|
||||
}
|
||||
else {
|
||||
terminalId = await this.createTerminal()
|
||||
terminal = this.terminalInstanceOfId[terminalId]
|
||||
if (!terminal) throw new Error(`Unexpected error: Terminal could not be created.`)
|
||||
}
|
||||
|
||||
|
||||
// focus the terminal about to run
|
||||
this.terminalService.setActiveInstance(terminal)
|
||||
|
|
@ -164,23 +185,12 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
let result: string = ''
|
||||
let resolveReason: TerminalResolveReason | undefined = undefined
|
||||
|
||||
const disposables: IDisposable[] = []
|
||||
|
||||
// create this before we send so that we don't miss events on terminal
|
||||
const waitUntilDone = new Promise<void>((res, rej) => {
|
||||
const d2 = terminal.onData(async newData => {
|
||||
if (resolveReason) return
|
||||
|
||||
result += newData
|
||||
|
||||
// onPageFull
|
||||
if (result.length > MAX_TERMINAL_CHARS_PAGE) {
|
||||
result = result.substring(0, MAX_TERMINAL_CHARS_PAGE)
|
||||
await terminal.sendText('\x03', true) // interrupt the terminal with Ctrl+C
|
||||
resolveReason = { type: 'toofull' }
|
||||
res()
|
||||
return
|
||||
}
|
||||
|
||||
// onDone
|
||||
const isDone = isCommandComplete(result)
|
||||
if (isDone) {
|
||||
|
|
@ -196,36 +206,49 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
// send the command here
|
||||
await terminal.sendText(command, true)
|
||||
|
||||
// timeout promise
|
||||
const waitUntilTimeout = new Promise<void>((res, rej) => {
|
||||
setTimeout(async () => {
|
||||
if (resolveReason) return
|
||||
await terminal.sendText('\x03', true) // interrupt the terminal with Ctrl+C
|
||||
resolveReason = { type: waitForCompletion ? 'timeout' : 'bgtask' }
|
||||
res()
|
||||
return
|
||||
}, (waitForCompletion ? TERMINAL_TIMEOUT_TIME : TERMINAL_BG_WAIT_TIME) * 1000)
|
||||
})
|
||||
|
||||
await Promise.any([
|
||||
waitUntilDone,
|
||||
waitUntilTimeout,
|
||||
])
|
||||
// inactivity-based timeout
|
||||
const waitUntilInactive = new Promise<void>(res => {
|
||||
let globalTimeoutId: ReturnType<typeof setTimeout>;
|
||||
const resetTimer = () => {
|
||||
clearTimeout(globalTimeoutId);
|
||||
globalTimeoutId = setTimeout(() => {
|
||||
if (resolveReason) return
|
||||
|
||||
resolveReason = { type: 'timeout' };
|
||||
res();
|
||||
}, MAX_TERMINAL_INACTIVE_TIME * 1000);
|
||||
};
|
||||
|
||||
const dTimeout = terminal.onData(() => { resetTimer(); });
|
||||
disposables.push(dTimeout, toDisposable(() => clearTimeout(globalTimeoutId)));
|
||||
resetTimer();
|
||||
});
|
||||
|
||||
// wait for result
|
||||
await Promise.any([waitUntilDone, waitUntilInactive,])
|
||||
|
||||
disposables.forEach(d => d.dispose())
|
||||
if (!isBG) {
|
||||
await this.killTerminal(terminalId)
|
||||
}
|
||||
|
||||
if (!resolveReason) throw new Error('Unexpected internal error: Promise.any should have resolved with a reason.')
|
||||
|
||||
|
||||
result = removeAnsiEscapeCodes(result)
|
||||
.split('\n').slice(1, -1) // remove first and last line (first = command, last = andrewpareles/void %)
|
||||
.join('\n')
|
||||
|
||||
return { terminalId, didCreateTerminal, result, resolveReason }
|
||||
if (result.length > MAX_TERMINAL_CHARS) {
|
||||
const half = MAX_TERMINAL_CHARS / 2
|
||||
result = result.slice(0, half)
|
||||
+ '\n...\n'
|
||||
+ result.slice(result.length - half, Infinity)
|
||||
}
|
||||
|
||||
return { result, resolveReason }
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(ITerminalToolService, TerminalToolService, InstantiationType.Delayed);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import { computeDirectoryTree1Deep, IDirectoryStrService, stringifyDirectoryTree
|
|||
import { IMarkerService, MarkerSeverity } from '../../../../platform/markers/common/markers.js'
|
||||
import { timeout } from '../../../../base/common/async.js'
|
||||
import { RawToolParamsObj } from '../common/sendLLMMessageTypes.js'
|
||||
import { ToolName } from '../common/prompt/prompts.js'
|
||||
import { MAX_CHILDREN_URIs_PAGE, MAX_FILE_CHARS_PAGE, MAX_TERMINAL_INACTIVE_TIME, ToolName } from '../common/prompt/prompts.js'
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js'
|
||||
|
||||
|
||||
|
|
@ -32,15 +32,6 @@ type ToolResultToString = { [T in ToolName]: (p: ToolCallParams[T], result: Awai
|
|||
|
||||
|
||||
|
||||
|
||||
// pagination info
|
||||
export const MAX_FILE_CHARS_PAGE = 500_000
|
||||
export const MAX_CHILDREN_URIs_PAGE = 500
|
||||
export const MAX_TERMINAL_CHARS_PAGE = 20_000
|
||||
export const TERMINAL_TIMEOUT_TIME = 5 // seconds
|
||||
export const TERMINAL_BG_WAIT_TIME = 1
|
||||
|
||||
|
||||
const isFalsy = (u: unknown) => {
|
||||
return !u || u === 'null' || u === 'undefined'
|
||||
}
|
||||
|
|
@ -176,12 +167,12 @@ export class ToolsService implements IToolsService {
|
|||
|
||||
const uri = validateURI(uriStr)
|
||||
const pageNumber = validatePageNum(pageNumberUnknown)
|
||||
return { rootURI: uri, pageNumber }
|
||||
return { uri, pageNumber }
|
||||
},
|
||||
get_dir_structure: (params: RawToolParamsObj) => {
|
||||
get_dir_tree: (params: RawToolParamsObj) => {
|
||||
const { uri: uriStr, } = params
|
||||
const uri = validateURI(uriStr)
|
||||
return { rootURI: uri }
|
||||
return { uri }
|
||||
},
|
||||
search_pathnames_only: (params: RawToolParamsObj) => {
|
||||
const {
|
||||
|
|
@ -192,9 +183,9 @@ export class ToolsService implements IToolsService {
|
|||
|
||||
const queryStr = validateStr('query', queryUnknown)
|
||||
const pageNumber = validatePageNum(pageNumberUnknown)
|
||||
const searchInFolder = validateOptionalStr('search_in_folder', includeUnknown)
|
||||
const includePattern = validateOptionalStr('include_pattern', includeUnknown)
|
||||
|
||||
return { queryStr, searchInFolder, pageNumber }
|
||||
return { query: queryStr, includePattern, pageNumber }
|
||||
|
||||
},
|
||||
search_for_files: (params: RawToolParamsObj) => {
|
||||
|
|
@ -211,7 +202,7 @@ export class ToolsService implements IToolsService {
|
|||
const searchInFolder = validateOptionalURI(searchInFolderUnknown)
|
||||
const isRegex = validateBoolean(isRegexUnknown, { default: false })
|
||||
|
||||
return { queryStr, searchInFolder, isRegex, pageNumber }
|
||||
return { query: queryStr, searchInFolder, isRegex, pageNumber }
|
||||
},
|
||||
|
||||
read_lint_errors: (params: RawToolParamsObj) => {
|
||||
|
|
@ -248,12 +239,22 @@ export class ToolsService implements IToolsService {
|
|||
return { uri, changeDescription }
|
||||
},
|
||||
|
||||
command_tool: (params: RawToolParamsObj) => {
|
||||
const { command: commandUnknown, terminal_id: terminalIdUnknown, wait_for_completion: waitForCompletionUnknown } = params
|
||||
const command = validateStr('command', commandUnknown)
|
||||
const proposedTerminalId = validateProposedTerminalId(terminalIdUnknown)
|
||||
const waitForCompletion = validateBoolean(waitForCompletionUnknown, { default: true })
|
||||
return { command, proposedTerminalId, waitForCompletion }
|
||||
// ---
|
||||
|
||||
run_terminal: (params: RawToolParamsObj) => {
|
||||
const { command: commandUnknown, terminal_id: terminalIdUnknown } = params;
|
||||
const command = validateStr('command', commandUnknown);
|
||||
const proposedTerminalId = terminalIdUnknown ? validateProposedTerminalId(terminalIdUnknown) : null;
|
||||
return { command, bgTerminalId: proposedTerminalId };
|
||||
},
|
||||
open_bg_terminal: (_params: RawToolParamsObj) => {
|
||||
// No parameters needed; will open a new background terminal
|
||||
return {};
|
||||
},
|
||||
kill_bg_terminal: (params: RawToolParamsObj) => {
|
||||
const { terminal_id: terminalIdUnknown } = params;
|
||||
const terminalId = validateProposedTerminalId(terminalIdUnknown);
|
||||
return { terminalId };
|
||||
},
|
||||
|
||||
}
|
||||
|
|
@ -283,21 +284,21 @@ export class ToolsService implements IToolsService {
|
|||
return { result: { fileContents, totalFileLen, hasNextPage } }
|
||||
},
|
||||
|
||||
ls_dir: async ({ rootURI, pageNumber }) => {
|
||||
const dirResult = await computeDirectoryTree1Deep(fileService, rootURI, pageNumber)
|
||||
ls_dir: async ({ uri, pageNumber }) => {
|
||||
const dirResult = await computeDirectoryTree1Deep(fileService, uri, pageNumber)
|
||||
return { result: dirResult }
|
||||
},
|
||||
|
||||
get_dir_structure: async ({ rootURI }) => {
|
||||
const str = await this.directoryStrService.getDirectoryStrTool(rootURI)
|
||||
get_dir_tree: async ({ uri }) => {
|
||||
const str = await this.directoryStrService.getDirectoryStrTool(uri)
|
||||
return { result: { str } }
|
||||
},
|
||||
|
||||
search_pathnames_only: async ({ queryStr, searchInFolder, pageNumber }) => {
|
||||
search_pathnames_only: async ({ query: queryStr, includePattern, pageNumber }) => {
|
||||
|
||||
const query = queryBuilder.file(workspaceContextService.getWorkspace().folders.map(f => f.uri), {
|
||||
filePattern: queryStr,
|
||||
includePattern: searchInFolder ?? undefined,
|
||||
includePattern: includePattern ?? undefined,
|
||||
})
|
||||
const data = await searchService.fileSearch(query, CancellationToken.None)
|
||||
|
||||
|
|
@ -311,7 +312,7 @@ export class ToolsService implements IToolsService {
|
|||
return { result: { uris, hasNextPage } }
|
||||
},
|
||||
|
||||
search_for_files: async ({ queryStr, isRegex, searchInFolder, pageNumber }) => {
|
||||
search_for_files: async ({ query: queryStr, isRegex, searchInFolder, pageNumber }) => {
|
||||
const searchFolders = searchInFolder === null ?
|
||||
workspaceContextService.getWorkspace().folders.map(f => f.uri)
|
||||
: [searchInFolder]
|
||||
|
|
@ -385,10 +386,22 @@ export class ToolsService implements IToolsService {
|
|||
|
||||
return { result: lintErrorsPromise, interruptTool }
|
||||
},
|
||||
command_tool: async ({ command, proposedTerminalId, waitForCompletion }) => {
|
||||
const { terminalId, didCreateTerminal, result, resolveReason } = await this.terminalToolService.runCommand(command, proposedTerminalId, waitForCompletion)
|
||||
return { result: { terminalId, didCreateTerminal, result, resolveReason } }
|
||||
// ---
|
||||
run_terminal: async ({ command, bgTerminalId }) => {
|
||||
const { result, resolveReason } = await this.terminalToolService.runCommand(command, bgTerminalId)
|
||||
return { result: { result, resolveReason } }
|
||||
},
|
||||
open_bg_terminal: async () => {
|
||||
// Open a new background terminal without waiting for completion
|
||||
const terminalId = await this.terminalToolService.createTerminal()
|
||||
return { result: { terminalId } }
|
||||
},
|
||||
kill_bg_terminal: async ({ terminalId }) => {
|
||||
// Close the background terminal by sending exit
|
||||
await this.terminalToolService.killTerminal(terminalId)
|
||||
return { result: {} }
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -401,7 +414,7 @@ export class ToolsService implements IToolsService {
|
|||
.substring(0, MAX_FILE_CHARS_PAGE)
|
||||
}
|
||||
|
||||
// given to the LLM after the call
|
||||
// given to the LLM after the call for successful tool calls
|
||||
this.stringOfResult = {
|
||||
read_file: (params, result) => {
|
||||
return `${params.uri.fsPath}\n\`\`\`\n${result.fileContents}\n\`\`\`${nextPageStr(result.hasNextPage)}`
|
||||
|
|
@ -410,7 +423,7 @@ export class ToolsService implements IToolsService {
|
|||
const dirTreeStr = stringifyDirectoryTree1Deep(params, result)
|
||||
return dirTreeStr // + nextPageStr(result.hasNextPage) // already handles num results remaining
|
||||
},
|
||||
get_dir_structure: (params, result) => {
|
||||
get_dir_tree: (params, result) => {
|
||||
return result.str
|
||||
},
|
||||
search_pathnames_only: (params, result) => {
|
||||
|
|
@ -440,30 +453,41 @@ export class ToolsService implements IToolsService {
|
|||
|
||||
return `Change successfully made to ${params.uri.fsPath}.${lintErrsString}`
|
||||
},
|
||||
command_tool: (params, result) => {
|
||||
run_terminal: (params, result) => {
|
||||
const {
|
||||
terminalId,
|
||||
didCreateTerminal,
|
||||
resolveReason,
|
||||
result: result_,
|
||||
} = result
|
||||
const { bgTerminalId } = params
|
||||
|
||||
const terminalDesc = `terminal ${terminalId}${didCreateTerminal ? ` (a newly-created terminal)` : ''}`
|
||||
// success
|
||||
if (resolveReason.type === 'done') {
|
||||
const desc = bgTerminalId ? ` in terminal ${bgTerminalId}` : ''
|
||||
return `Terminal command executed and finished${desc}. Result (exit code ${resolveReason.exitCode}):\n${result_}`
|
||||
}
|
||||
|
||||
if (resolveReason.type === 'timeout') {
|
||||
return `Terminal command ran in ${terminalDesc}, but did not complete after ${TERMINAL_TIMEOUT_TIME} seconds. Result:\n${result_}`
|
||||
// bg command
|
||||
if (bgTerminalId !== null) {
|
||||
if (resolveReason.type === 'timeout') {
|
||||
return `Terminal command is running in the background in terminal ${bgTerminalId}. Here were the outputs after ${MAX_TERMINAL_INACTIVE_TIME} seconds:\n${result_}`
|
||||
}
|
||||
}
|
||||
else if (resolveReason.type === 'bgtask') {
|
||||
return `Terminal command is running in the background in ${terminalDesc}. Here were the outputs after ${TERMINAL_BG_WAIT_TIME} seconds:\n${result_}`
|
||||
}
|
||||
else if (resolveReason.type === 'toofull') {
|
||||
return `Terminal command executed in terminal ${terminalDesc}. Command was interrupted because output was too long. Result:\n${result_}`
|
||||
}
|
||||
else if (resolveReason.type === 'done') {
|
||||
return `Terminal command executed in terminal ${terminalDesc}. Result (exit code ${resolveReason.exitCode}):\n${result_}`
|
||||
// normal command
|
||||
else {
|
||||
if (resolveReason.type === 'timeout') {
|
||||
return `Terminal command ran, but was interrupted after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity and did not necessarily finish successfully. Full output:\n${result_}`
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected internal error: Terminal command did not resolve with a valid reason.`)
|
||||
},
|
||||
open_bg_terminal: (_params, result) => {
|
||||
const { terminalId } = result;
|
||||
return `Successfully created background terminal with ID ${terminalId}`;
|
||||
},
|
||||
kill_bg_terminal: (params, _result) => {
|
||||
return `Successfully closed terminal ${params.terminalId}.`;
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { EndOfLinePreference } from '../../../../../editor/common/model.js';
|
|||
import { StagingSelectionItem } from '../chatThreadServiceTypes.js';
|
||||
import { os } from '../helpers/systemInfo.js';
|
||||
import { RawToolParamsObj } from '../sendLLMMessageTypes.js';
|
||||
import { toolNamesThatRequireApproval } from '../toolsServiceTypes.js';
|
||||
import { approvalTypeOfToolName, ToolResultType } from '../toolsServiceTypes.js';
|
||||
import { IVoidModelService } from '../voidModelService.js';
|
||||
import { ChatMode } from '../voidSettingsTypes.js';
|
||||
|
||||
|
|
@ -20,6 +20,14 @@ export const MAX_DIRSTR_CHARS_TOTAL_TOOL = 20_000
|
|||
export const MAX_DIRSTR_RESULTS_TOTAL_BEGINNING = 100
|
||||
export const MAX_DIRSTR_RESULTS_TOTAL_TOOL = 100
|
||||
|
||||
// tool info
|
||||
export const MAX_FILE_CHARS_PAGE = 500_000
|
||||
export const MAX_CHILDREN_URIs_PAGE = 500
|
||||
|
||||
// terminal tool info
|
||||
export const MAX_TERMINAL_CHARS = 100_000
|
||||
export const MAX_TERMINAL_INACTIVE_TIME = 8 // seconds
|
||||
|
||||
|
||||
// Maximum character limits for prefix and suffix context
|
||||
export const MAX_PREFIX_SUFFIX_CHARS = 20_000
|
||||
|
|
@ -66,7 +74,36 @@ const paginationParam = {
|
|||
} as const
|
||||
|
||||
|
||||
|
||||
|
||||
// export type SnakeCase<S extends string> =
|
||||
// // exact acronym URI
|
||||
// S extends 'URI' ? 'uri'
|
||||
// // suffix URI: e.g. 'rootURI' -> snakeCase('root') + '_uri'
|
||||
// : S extends `${infer Prefix}URI` ? `${SnakeCase<Prefix>}_uri`
|
||||
// // default: for each char, prefix '_' on uppercase letters
|
||||
// : S extends `${infer C}${infer Rest}`
|
||||
// ? `${C extends Lowercase<C> ? C : `_${Lowercase<C>}`}${SnakeCase<Rest>}`
|
||||
// : S;
|
||||
|
||||
// export type SnakeCaseKeys<T extends Record<string, any>> = {
|
||||
// [K in keyof T as SnakeCase<Extract<K, string>>]: T[K]
|
||||
// };
|
||||
|
||||
|
||||
|
||||
export const voidTools = {
|
||||
// export const voidTools
|
||||
// : {
|
||||
// [T in keyof ToolCallParams]: {
|
||||
// name: string;
|
||||
// description: string;
|
||||
// params: {
|
||||
// [paramName in keyof SnakeCaseKeys<ToolCallParams[T]>]: { description: string }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// = {
|
||||
// --- context-gathering (read/search/list) ---
|
||||
|
||||
read_file: {
|
||||
|
|
@ -89,8 +126,8 @@ export const voidTools = {
|
|||
},
|
||||
},
|
||||
|
||||
get_dir_structure: {
|
||||
name: 'get_dir_structure',
|
||||
get_dir_tree: {
|
||||
name: 'get_dir_tree',
|
||||
description: `This is a very effective way to learn about the user's codebase. Returns a tree diagram of all the files and folders in the given folder. `,
|
||||
params: {
|
||||
...uriParam('folder')
|
||||
|
|
@ -106,7 +143,7 @@ export const voidTools = {
|
|||
description: `Returns all pathnames that match a given query (searches ONLY file names). You should use this when looking for a file with a specific name or path.`,
|
||||
params: {
|
||||
query: { description: `Your query for the search.` },
|
||||
search_in_folder: { description: 'Optional. Only fill this in if you need to limit your search because there were too many results.' },
|
||||
include_pattern: { description: 'Optional. Only fill this in if you need to limit your search because there were too many results.' },
|
||||
...paginationParam,
|
||||
},
|
||||
},
|
||||
|
|
@ -118,7 +155,7 @@ export const voidTools = {
|
|||
description: `Returns a list of file names whose content matches the given query. The query can be any substring or regex.`,
|
||||
params: {
|
||||
query: { description: `Your query for the search.` },
|
||||
search_in_folder: { description: 'Optional. Only fill this in if you need to limit your search because there were too many results.' },
|
||||
search_in_folder: { description: 'Optional. Leave as blank by default. ONLY fill this in if your previous search with the same query was truncated. Searches descendants of this folder only.' },
|
||||
is_regex: { description: 'Optional. Default is false. Whether query is a regex.' },
|
||||
...paginationParam,
|
||||
},
|
||||
|
|
@ -166,23 +203,34 @@ Here's an example of a good description:\n${editToolDescriptionExample}`
|
|||
},
|
||||
},
|
||||
|
||||
command_tool: {
|
||||
name: 'command_tool',
|
||||
description: `Runs a terminal command. You can use this tool to run any command: sed, grep, etc. We just prefer you edit with the edit tool, not this tool if possible.`,
|
||||
run_terminal: {
|
||||
name: 'run_terminal',
|
||||
description: `Runs a terminal command and waits for the result (times out after ${MAX_TERMINAL_INACTIVE_TIME}s of inactivity). You can use this tool to run any command: sed, grep, etc. Do not edit any files with this tool; use edit_file instead. When working with git and other tools that open an editor like vim, you might need to pipe to cat to get all results.`,
|
||||
params: {
|
||||
command: { description: 'The terminal command to run.' },
|
||||
wait_for_completion: { description: `Optional. Default is true. Make this value false when you want a command to run without waiting for it to complete.` },
|
||||
terminal_id: { description: 'Optional. The ID of the terminal instance that should execute the command (if not provided, defaults to the preferred terminal ID). The primary purpose of this is to let you open a new terminal for testing or background processes (e.g. running a dev server for the user in a separate terminal). Must be an integer >= 1.' },
|
||||
bg_terminal_id: { description: 'Optional. This only applies to terminals that have been opened with open_bg_terminal. Runs the command in the terminal with the specified ID.' },
|
||||
},
|
||||
},
|
||||
|
||||
open_bg_terminal: {
|
||||
name: 'open_bg_terminal',
|
||||
description: `Use this tool when you want to run a terminal command indefinitely, like a dev server (eg \`npm run dev\`), a background listener, etc. Opens a new terminal in the user's environment which will not awaited for or killed.`,
|
||||
params: {}
|
||||
},
|
||||
kill_bg_terminal: {
|
||||
name: 'kill_bg_terminal',
|
||||
description: `Closes a BG terminal with the given ID.`,
|
||||
params: { terminal_id: { description: `The terminal ID to interrupt and close.` } }
|
||||
}
|
||||
|
||||
|
||||
// go_to_definition
|
||||
// go_to_usages
|
||||
|
||||
} satisfies { [name: string]: InternalToolInfo }
|
||||
} satisfies { [T in keyof ToolResultType]: InternalToolInfo }
|
||||
|
||||
|
||||
export type ToolName = keyof typeof voidTools
|
||||
export type ToolName = keyof ToolResultType
|
||||
export const toolNames = Object.keys(voidTools) as ToolName[]
|
||||
|
||||
type ToolParamNameOfTool<T extends ToolName> = keyof (typeof voidTools)[T]['params']
|
||||
|
|
@ -197,7 +245,7 @@ export const isAToolName = (toolName: string): toolName is ToolName => {
|
|||
|
||||
export const availableTools = (chatMode: ChatMode) => {
|
||||
const toolNames: ToolName[] | undefined = chatMode === 'normal' ? undefined
|
||||
: chatMode === 'gather' ? (Object.keys(voidTools) as ToolName[]).filter(toolName => !toolNamesThatRequireApproval.has(toolName))
|
||||
: chatMode === 'gather' ? (Object.keys(voidTools) as ToolName[]).filter(toolName => !(toolName in approvalTypeOfToolName))
|
||||
: chatMode === 'agent' ? Object.keys(voidTools) as ToolName[]
|
||||
: undefined
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { ToolName } from './prompt/prompts.js';
|
|||
|
||||
|
||||
|
||||
export type TerminalResolveReason = { type: 'toofull' | 'timeout' | 'bgtask' } | { type: 'done', exitCode: number }
|
||||
export type TerminalResolveReason = { type: 'timeout' } | { type: 'done', exitCode: number }
|
||||
|
||||
export type LintErrorItem = { code: string, message: string, startLineNumber: number, endLineNumber: number }
|
||||
|
||||
|
|
@ -16,32 +16,45 @@ export type ShallowDirectoryItem = {
|
|||
}
|
||||
|
||||
|
||||
export const approvalTypeOfToolName: Partial<{ [T in ToolName]?: 'edits' | 'terminal' }> = {
|
||||
'create_file_or_folder': 'edits',
|
||||
'delete_file_or_folder': 'edits',
|
||||
'edit_file': 'edits',
|
||||
'run_terminal': 'terminal',
|
||||
}
|
||||
|
||||
const toolNamesWithApproval = ['create_file_or_folder', 'delete_file_or_folder', 'edit_file', 'command_tool'] as const satisfies readonly ToolName[]
|
||||
export type ToolNameWithApproval = typeof toolNamesWithApproval[number]
|
||||
export const toolNamesThatRequireApproval = new Set<ToolName>(toolNamesWithApproval)
|
||||
|
||||
|
||||
// {{add: define new type for approval types}}
|
||||
export type ToolApprovalType = NonNullable<(typeof approvalTypeOfToolName)[keyof typeof approvalTypeOfToolName]>;
|
||||
|
||||
export const toolApprovalTypes = new Set<ToolApprovalType>(
|
||||
Object.values(approvalTypeOfToolName).filter((v): v is ToolApprovalType => v !== undefined)
|
||||
)
|
||||
|
||||
// PARAMS OF TOOL CALL
|
||||
export type ToolCallParams = {
|
||||
'read_file': { uri: URI, startLine: number | null, endLine: number | null, pageNumber: number },
|
||||
'ls_dir': { rootURI: URI, pageNumber: number },
|
||||
'get_dir_structure': { rootURI: URI },
|
||||
'search_pathnames_only': { queryStr: string, searchInFolder: string | null, pageNumber: number },
|
||||
'search_for_files': { queryStr: string, isRegex: boolean, searchInFolder: URI | null, pageNumber: number },
|
||||
'ls_dir': { uri: URI, pageNumber: number },
|
||||
'get_dir_tree': { uri: URI },
|
||||
'search_pathnames_only': { query: string, includePattern: string | null, pageNumber: number },
|
||||
'search_for_files': { query: string, isRegex: boolean, searchInFolder: URI | null, pageNumber: number },
|
||||
'read_lint_errors': { uri: URI },
|
||||
// ---
|
||||
'edit_file': { uri: URI, changeDescription: string },
|
||||
'create_file_or_folder': { uri: URI, isFolder: boolean },
|
||||
'delete_file_or_folder': { uri: URI, isRecursive: boolean, isFolder: boolean },
|
||||
'command_tool': { command: string, proposedTerminalId: string, waitForCompletion: boolean },
|
||||
// ---
|
||||
'run_terminal': { command: string; bgTerminalId: string | null },
|
||||
'open_bg_terminal': {},
|
||||
'kill_bg_terminal': { terminalId: string },
|
||||
}
|
||||
|
||||
|
||||
// RESULT OF TOOL CALL
|
||||
export type ToolResultType = {
|
||||
'read_file': { fileContents: string, totalFileLen: number, hasNextPage: boolean },
|
||||
'ls_dir': { children: ShallowDirectoryItem[] | null, hasNextPage: boolean, hasPrevPage: boolean, itemsRemaining: number },
|
||||
'get_dir_structure': { str: string, },
|
||||
'get_dir_tree': { str: string, },
|
||||
'search_pathnames_only': { uris: URI[], hasNextPage: boolean },
|
||||
'search_for_files': { uris: URI[], hasNextPage: boolean },
|
||||
'read_lint_errors': { lintErrors: LintErrorItem[] | null },
|
||||
|
|
@ -49,6 +62,9 @@ export type ToolResultType = {
|
|||
'edit_file': Promise<{ lintErrors: LintErrorItem[] | null }>,
|
||||
'create_file_or_folder': {},
|
||||
'delete_file_or_folder': {},
|
||||
'command_tool': { terminalId: string, didCreateTerminal: boolean, result: string; resolveReason: TerminalResolveReason; },
|
||||
// ---
|
||||
'run_terminal': { result: string; resolveReason: TerminalResolveReason; },
|
||||
'open_bg_terminal': { terminalId: string },
|
||||
'kill_bg_terminal': {},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -237,6 +237,9 @@ class VoidSettingsService extends Disposable implements IVoidSettingsService {
|
|||
readS = await this._readState();
|
||||
// 1.0.3 addition, remove when enough users have had this code run
|
||||
if (readS.globalSettings.includeToolLintErrors === undefined) readS.globalSettings.includeToolLintErrors = true
|
||||
|
||||
// autoapprove is now an obj not a boolean (1.2.5)
|
||||
if (typeof readS.globalSettings.autoApprove === 'boolean') readS.globalSettings.autoApprove = {}
|
||||
}
|
||||
catch (e) {
|
||||
readS = defaultState()
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { defaultModelsOfProvider, defaultProviderSettings } from './modelCapabilities.js';
|
||||
import { ToolApprovalType } from './toolsServiceTypes.js';
|
||||
import { VoidSettingsState } from './voidSettingsService.js'
|
||||
|
||||
|
||||
|
|
@ -425,7 +426,7 @@ export type GlobalSettings = {
|
|||
syncApplyToChat: boolean;
|
||||
enableFastApply: boolean;
|
||||
chatMode: ChatMode;
|
||||
autoApprove: boolean;
|
||||
autoApprove: { [approvalType in ToolApprovalType]?: boolean };
|
||||
showInlineSuggestions: boolean;
|
||||
includeToolLintErrors: boolean;
|
||||
isOnboardingComplete: boolean;
|
||||
|
|
@ -438,7 +439,7 @@ export const defaultGlobalSettings: GlobalSettings = {
|
|||
syncApplyToChat: true,
|
||||
enableFastApply: true,
|
||||
chatMode: 'agent',
|
||||
autoApprove: false,
|
||||
autoApprove: {},
|
||||
showInlineSuggestions: true,
|
||||
includeToolLintErrors: true,
|
||||
isOnboardingComplete: false,
|
||||
|
|
|
|||
Loading…
Reference in a new issue