From 097d89366cb70b44de164762400586a119b7349a Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Tue, 6 May 2025 05:38:57 -0700 Subject: [PATCH] file and folder dump! --- .../contrib/void/browser/chatThreadService.ts | 8 +- .../browser/convertToLLMMessageService.ts | 13 +- .../void/browser/react/src/util/inputs.tsx | 50 ++++- .../src/void-onboarding/VoidOnboarding.tsx | 26 ++- .../react/src/void-settings-tsx/Settings.tsx | 10 +- .../contrib/void/browser/toolsService.ts | 2 +- .../directoryStrService.ts | 186 ++++++++++++------ .../contrib/void/common/prompt/prompts.ts | 81 +++++--- .../contrib/void/common/voidSettingsTypes.ts | 2 +- 9 files changed, 266 insertions(+), 112 deletions(-) rename src/vs/workbench/contrib/void/{browser => common}/directoryStrService.ts (74%) diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 49a6dd09..db482108 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -35,6 +35,8 @@ import { IConvertToLLMMessageService } from './convertToLLMMessageService.js'; import { timeout } from '../../../../base/common/async.js'; import { deepClone } from '../../../../base/common/objects.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; +import { IDirectoryStrService } from '../common/directoryStrService.js'; +import { IFileService } from '../../../../platform/files/common/files.js'; // related to retrying when LLM message has error @@ -319,6 +321,8 @@ class ChatThreadService extends Disposable implements IChatThreadService { @INotificationService private readonly _notificationService: INotificationService, @IConvertToLLMMessageService private readonly _convertToLLMMessagesService: IConvertToLLMMessageService, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, + @IDirectoryStrService private readonly _directoryStringService: IDirectoryStrService, + @IFileService private readonly _fileService: IFileService, ) { super() this.state = { allThreads: {}, currentThreadId: null as unknown as string } // default state @@ -1189,14 +1193,12 @@ We only need to do it for files that were edited since `from`, ie files between this._addUserCheckpoint({ threadId }) } - const { chatMode } = this._settingsService.state.globalSettings // add user's message to chat history const instructions = userMessage const currSelns: StagingSelectionItem[] = _chatSelections ?? thread.state.stagingSelections - const opts = chatMode !== 'normal' ? { type: 'references' } as const : { type: 'fullCode', voidModelService: this._voidModelService } as const - const userMessageContent = await chat_userMessageContent(instructions, currSelns, opts) // user message + names of files (NOT content) + const userMessageContent = await chat_userMessageContent(instructions, currSelns, { directoryStrService: this._directoryStringService, fileService: this._fileService }) // user message + names of files (NOT content) const userHistoryElt: ChatMessage = { role: 'user', content: userMessageContent, displayContent: instructions, selections: currSelns, state: defaultMessageState } this._addMessageToThread(threadId, userHistoryElt) diff --git a/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts b/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts index c4ffa286..48b21b77 100644 --- a/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts +++ b/src/vs/workbench/contrib/void/browser/convertToLLMMessageService.ts @@ -11,7 +11,7 @@ import { reParsedToolXMLString, chat_systemMessage, ToolName } from '../common/p import { AnthropicLLMChatMessage, AnthropicReasoning, GeminiLLMChatMessage, LLMChatMessage, LLMFIMMessage, OpenAILLMChatMessage, RawToolParamsObj } from '../common/sendLLMMessageTypes.js'; import { IVoidSettingsService } from '../common/voidSettingsService.js'; import { ChatMode, FeatureName, ModelSelection, ProviderName } from '../common/voidSettingsTypes.js'; -import { IDirectoryStrService } from './directoryStrService.js'; +import { IDirectoryStrService } from '../common/directoryStrService.js'; import { ITerminalToolService } from './terminalToolService.js'; import { IVoidModelService } from '../common/voidModelService.js'; import { URI } from '../../../../base/common/uri.js'; @@ -38,7 +38,7 @@ type SimpleLLMMessage = { const EMPTY_MESSAGE = '(empty message)' -const CHARS_PER_TOKEN = 4 +const CHARS_PER_TOKEN = 4 // assume abysmal chars per token const TRIM_TO_LEN = 120 @@ -271,7 +271,10 @@ const prepareOpenAIOrAnthropicMessages = ({ reservedOutputTokenSpace: number | null | undefined, }): { messages: AnthropicOrOpenAILLMMessage[], separateSystemMessage: string | undefined } => { - reservedOutputTokenSpace = reservedOutputTokenSpace ?? 4_096 // default to 4096 + reservedOutputTokenSpace = Math.max( + contextWindow * 1 / 2, // reserve at least 1/4 of the token window length + reservedOutputTokenSpace ?? 4_096 // defaults to 4096 + ) let messages: (SimpleLLMMessage | { role: 'system', content: string })[] = deepClone(messages_) // ================ system message ================ @@ -337,7 +340,7 @@ const prepareOpenAIOrAnthropicMessages = ({ for (const m of messages) { totalLen += m.content.length } const charsNeedToTrim = totalLen - Math.max( (contextWindow - reservedOutputTokenSpace) * CHARS_PER_TOKEN, // can be 0, in which case charsNeedToTrim=everything, bad - 4_096 // ensure we don't trim at least 4096 chars (just a random small value) + 5_000 // ensure we don't trim at least 5k chars (just a random small value) ) @@ -359,6 +362,7 @@ const prepareOpenAIOrAnthropicMessages = ({ // if can finish here, do const numCharsWillTrim = m.content.length - TRIM_TO_LEN if (numCharsWillTrim > remainingCharsToTrim) { + // trim remainingCharsToTrim + '...'.length chars m.content = m.content.slice(0, m.content.length - remainingCharsToTrim - '...'.length).trim() + '...' break } @@ -581,6 +585,7 @@ class ConvertToLLMMessageService extends Disposable implements IConvertToLLMMess `...Directories string cut off, use tools to read more...` : `...Directories string cut off, ask user for more if necessary...` }) + const includeXMLToolDefinitions = !specialToolFormat const persistentTerminalIDs = this.terminalToolService.listPersistentTerminalIds() diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx index 8f98bdac..3788e62f 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx @@ -249,7 +249,6 @@ const getOptionsAtPath = async (accessor: ReturnType, path: for (let i = 0; i < pathParts.length - 1; i++) { currentPath = i === 0 ? `/${pathParts[i]}` : `${currentPath}/${pathParts[i]}`; - console.log('filepath', currentPath); // Create a proper directory URI const directoryUri = URI.joinPath( @@ -420,17 +419,21 @@ export const VoidInputBox2 = forwardRef(fun setIsMenuOpen(false) insertTextAtCursor(option.abbreviatedName) - const newSelection: StagingSelectionItem = option.leafNodeType === 'File' ? { + let newSelection: StagingSelectionItem + if (option.leafNodeType === 'File') newSelection = { type: 'File', uri: option.uri, language: languageService.guessLanguageIdByFilepathOrFirstLine(option.uri) || '', - state: { wasAddedAsCurrentFile: false } - } : option.leafNodeType === 'Folder' ? { + state: { wasAddedAsCurrentFile: false }, + } + else if (option.leafNodeType === 'Folder') newSelection = { type: 'Folder', uri: option.uri, language: undefined, state: undefined, - } : (undefined as never) + } + else throw new Error(`Unexpected leafNodeType ${option.leafNodeType}`) + chatThreadService.addNewStagingSelection(newSelection) console.log('selected', option.uri?.fsPath) } @@ -868,15 +871,44 @@ export const VoidSimpleInputBox = ({ value, onChangeValue, placeholder, classNam compact?: boolean; passwordBlur?: boolean; } & React.InputHTMLAttributes) => { + // Create a ref for the input element to maintain the same DOM node between renders + const inputRef = useRef(null); + + // Track if we need to restore selection + const selectionRef = useRef<{ start: number | null, end: number | null }>({ + start: null, + end: null + }); + + // Handle value changes without recreating the input + useEffect(() => { + const input = inputRef.current; + if (input && input.value !== value) { + // Store current selection positions + selectionRef.current.start = input.selectionStart; + selectionRef.current.end = input.selectionEnd; + + // Update the value + input.value = value; + + // Restore selection if we had it before + if (selectionRef.current.start !== null && selectionRef.current.end !== null) { + input.setSelectionRange(selectionRef.current.start, selectionRef.current.end); + } + } + }, [value]); + + const handleChange = useCallback((e: React.ChangeEvent) => { + onChangeValue(e.target.value); + }, [onChangeValue]); return ( onChangeValue(e.target.value)} + ref={inputRef} + defaultValue={value} // Use defaultValue instead of value to avoid recreation + onChange={handleChange} placeholder={placeholder} disabled={disabled} - // className='max-w-44 w-full border border-void-border-2 bg-void-bg-1 text-void-fg-3 text-root' - // className={`w-full resize-none text-void-fg-1 placeholder:text-void-fg-3 px-2 py-1 rounded-sm className={`w-full resize-none bg-void-bg-1 text-void-fg-1 placeholder:text-void-fg-3 border border-void-border-2 focus:border-void-border-1 ${compact ? 'py-1 px-2' : 'py-2 px-4 '} rounded diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx index d03556a1..1cbb5081 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-onboarding/VoidOnboarding.tsx @@ -130,6 +130,24 @@ const AddProvidersPage = ({ pageIndex, setPageIndex }: { pageIndex: number, setP const settingsState = useSettingsState(); const [errorMessage, setErrorMessage] = useState(null); + // Clear error message after 5 seconds + useEffect(() => { + let timeoutId: NodeJS.Timeout | null = null; + + if (errorMessage) { + timeoutId = setTimeout(() => { + setErrorMessage(null); + }, 5000); + } + + // Cleanup function to clear the timeout if component unmounts or error changes + return () => { + if (timeoutId) { + clearTimeout(timeoutId); + } + }; + }, [errorMessage]); + return (
{/* Left Column - Fixed */} @@ -192,13 +210,13 @@ const AddProvidersPage = ({ pageIndex, setPageIndex }: { pageIndex: number, setP
Models
- + {currentTab === 'Local' && (
Local models should be detected automatically. You can add custom models below.
)} - + {currentTab === 'Local' && } {currentTab === 'Cloud/Other' && } @@ -222,7 +240,7 @@ Only applies to models labeled \`:free\`. More information [here](https://openro {/* Navigation buttons in right column */}
{errorMessage && ( -
{errorMessage}
+
{errorMessage}
)}
setPageIndex(pageIndex - 1)} /> @@ -235,7 +253,7 @@ Only applies to models labeled \`:free\`. More information [here](https://openro setErrorMessage(null); } else { // Show error message - setErrorMessage("Please set up at least one Chat model first."); + setErrorMessage("Please set up at least one Chat model before moving on."); } }} /> diff --git a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx index 641d7370..2771cae1 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/void-settings-tsx/Settings.tsx @@ -607,15 +607,17 @@ const ProviderSetting = ({ providerName, settingName, subTextMd }: { providerNam console.log('Error: Provider setting had a non-string value.') return } + + // Create a stable callback reference using useCallback with proper dependencies + const handleChangeValue = useCallback((newVal: string) => { + voidSettingsService.setSettingOfProvider(providerName, settingName, newVal) + }, [voidSettingsService, providerName, settingName]); return
{ - voidSettingsService.setSettingOfProvider(providerName, settingName, newVal) - }, [voidSettingsService, providerName, settingName])} - // placeholder={`${providerTitle} ${settingTitle} (${placeholder})`} + onChangeValue={handleChangeValue} placeholder={`${settingTitle} (${placeholder})`} passwordBlur={isPasswordField} compact={true} diff --git a/src/vs/workbench/contrib/void/browser/toolsService.ts b/src/vs/workbench/contrib/void/browser/toolsService.ts index a9d25dd1..ad6944d9 100644 --- a/src/vs/workbench/contrib/void/browser/toolsService.ts +++ b/src/vs/workbench/contrib/void/browser/toolsService.ts @@ -12,7 +12,7 @@ import { LintErrorItem, ToolCallParams, ToolResultType } from '../common/toolsSe import { IVoidModelService } from '../common/voidModelService.js' import { EndOfLinePreference } from '../../../../editor/common/model.js' import { IVoidCommandBarService } from './voidCommandBarService.js' -import { computeDirectoryTree1Deep, IDirectoryStrService, stringifyDirectoryTree1Deep } from './directoryStrService.js' +import { computeDirectoryTree1Deep, IDirectoryStrService, stringifyDirectoryTree1Deep } from '../common/directoryStrService.js' import { IMarkerService, MarkerSeverity } from '../../../../platform/markers/common/markers.js' import { timeout } from '../../../../base/common/async.js' import { RawToolParamsObj } from '../common/sendLLMMessageTypes.js' diff --git a/src/vs/workbench/contrib/void/browser/directoryStrService.ts b/src/vs/workbench/contrib/void/common/directoryStrService.ts similarity index 74% rename from src/vs/workbench/contrib/void/browser/directoryStrService.ts rename to src/vs/workbench/contrib/void/common/directoryStrService.ts index 80f70373..f661eca8 100644 --- a/src/vs/workbench/contrib/void/browser/directoryStrService.ts +++ b/src/vs/workbench/contrib/void/common/directoryStrService.ts @@ -7,13 +7,10 @@ import { URI } from '../../../../base/common/uri.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js'; import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js'; -import { IFileService } from '../../../../platform/files/common/files.js'; +import { IFileService, IFileStat } from '../../../../platform/files/common/files.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; -import { ShallowDirectoryItem, ToolCallParams, ToolResultType } from '../common/toolsServiceTypes.js'; -import { IExplorerService } from '../../files/browser/files.js'; -import { SortOrder } from '../../files/common/files.js'; -import { ExplorerItem } from '../../files/common/explorerModel.js'; -import { MAX_CHILDREN_URIs_PAGE, MAX_DIRSTR_CHARS_TOTAL_BEGINNING, MAX_DIRSTR_CHARS_TOTAL_TOOL } from '../common/prompt/prompts.js'; +import { ShallowDirectoryItem, ToolCallParams, ToolResultType } from './toolsServiceTypes.js'; +import { MAX_CHILDREN_URIs_PAGE, MAX_DIRSTR_CHARS_TOTAL_BEGINNING, MAX_DIRSTR_CHARS_TOTAL_TOOL } from './prompt/prompts.js'; const MAX_FILES_TOTAL = 1000; @@ -28,8 +25,10 @@ const DEFAULT_MAX_ITEMS_PER_DIR = 3; export interface IDirectoryStrService { readonly _serviceBrand: undefined; - getDirectoryStrTool(uri: URI, options?: { maxItemsPerDir?: number }): Promise - getAllDirectoriesStr(opts: { cutOffMessage: string, maxItemsPerDir?: number }): Promise + getDirectoryStrTool(uri: URI): Promise + getAllDirectoriesStr(opts: { cutOffMessage: string }): Promise + + getAllURIsInDirectory(uri: URI, opts: { maxResults: number }): Promise } export const IDirectoryStrService = createDecorator('voidDirectoryStrService'); @@ -38,35 +37,35 @@ export const IDirectoryStrService = createDecorator('voidD // Check if it's a known filtered type like .git -const shouldExcludeDirectory = (item: ExplorerItem) => { - if (item.name === '.git' || - item.name === 'node_modules' || - item.name.startsWith('.') || - item.name === 'dist' || - item.name === 'build' || - item.name === 'out' || - item.name === 'bin' || - item.name === 'coverage' || - item.name === '__pycache__' || - item.name === 'env' || - item.name === 'venv' || - item.name === 'tmp' || - item.name === 'temp' || - item.name === 'artifacts' || - item.name === 'target' || - item.name === 'obj' || - item.name === 'vendor' || - item.name === 'logs' || - item.name === 'cache' || - item.name === 'resource' || - item.name === 'resources' +const shouldExcludeDirectory = (name: string) => { + if (name === '.git' || + name === 'node_modules' || + name.startsWith('.') || + name === 'dist' || + name === 'build' || + name === 'out' || + name === 'bin' || + name === 'coverage' || + name === '__pycache__' || + name === 'env' || + name === 'venv' || + name === 'tmp' || + name === 'temp' || + name === 'artifacts' || + name === 'target' || + name === 'obj' || + name === 'vendor' || + name === 'logs' || + name === 'cache' || + name === 'resource' || + name === 'resources' ) { return true; } - if (item.name.match(/\bout\b/)) return true - if (item.name.match(/\bbuild\b/)) return true + if (name.match(/\bout\b/)) return true + if (name.match(/\bbuild\b/)) return true return false; } @@ -138,10 +137,16 @@ export const stringifyDirectoryTree1Deep = (params: ToolCallParams['ls_dir'], re // ---------- IN GENERAL ---------- +const resolveChildren = async (children: undefined | IFileStat[], fileService: IFileService): Promise => { + const res = await fileService.resolveAll(children ?? []) + const stats = res.map(s => s.success ? s.stat : null).filter(s => !!s) + return stats +} + // Remove the old computeDirectoryTree function and replace with a combined version that handles both computation and rendering const computeAndStringifyDirectoryTree = async ( - eItem: ExplorerItem, - explorerService: IExplorerService, + eItem: IFileStat, + fileService: IFileService, MAX_CHARS: number, fileCount: { count: number } = { count: 0 }, options: { maxDepth?: number, currentDepth?: number, maxItemsPerDir?: number } = {} @@ -181,12 +186,13 @@ const computeAndStringifyDirectoryTree = async ( let remainingChars = MAX_CHARS - nodeLine.length; // Check if it's a directory we should skip - const isGitIgnoredDirectory = eItem.isDirectory && shouldExcludeDirectory(eItem); + const isGitIgnoredDirectory = eItem.isDirectory && shouldExcludeDirectory(eItem.name); + // Fetch and process children if not a filtered directory if (eItem.isDirectory && !isGitIgnoredDirectory) { // Fetch children with Modified sort order to show recently modified first - const eChildren = await eItem.fetchChildren(SortOrder.Modified); + const eChildren = await resolveChildren(eItem.children, fileService) // Then recursively add all children with proper tree formatting if (eChildren && eChildren.length > 0) { @@ -194,7 +200,7 @@ const computeAndStringifyDirectoryTree = async ( eChildren, remainingChars, '', - explorerService, + fileService, fileCount, { maxDepth, currentDepth, maxItemsPerDir } // Pass maxItemsPerDir to the render function ); @@ -208,10 +214,10 @@ const computeAndStringifyDirectoryTree = async ( // Helper function to render children with proper tree formatting const renderChildrenCombined = async ( - children: ExplorerItem[], + children: IFileStat[], maxChars: number, parentPrefix: string, - explorerService: IExplorerService, + fileService: IFileService, fileCount: { count: number }, options: { maxDepth: number, currentDepth: number, maxItemsPerDir?: number } ): Promise<{ childrenContent: string, childrenCutOff: boolean }> => { @@ -263,12 +269,12 @@ const renderChildrenCombined = async ( const nextLevelPrefix = parentPrefix + (isLast ? ' ' : '│ '); // Skip processing children for git ignored directories - const isGitIgnoredDirectory = child.isDirectory && shouldExcludeDirectory(child); + const isGitIgnoredDirectory = child.isDirectory && shouldExcludeDirectory(child.name); // Create the prefix for the next level (continuation line or space) if (child.isDirectory && !isGitIgnoredDirectory) { // Fetch children with Modified sort order to show recently modified first - const eChildren = await child.fetchChildren(SortOrder.Modified); + const eChildren = await resolveChildren(child.children, fileService) if (eChildren && eChildren.length > 0) { const { @@ -278,7 +284,7 @@ const renderChildrenCombined = async ( eChildren, remainingChars, nextLevelPrefix, - explorerService, + fileService, fileCount, { maxDepth, currentDepth: nextDepth, maxItemsPerDir } ); @@ -311,7 +317,68 @@ const renderChildrenCombined = async ( }; -// --------------------------------------------------- +// ------------------------- FOLDERS ------------------------- + +export async function getAllUrisInDirectory( + directoryUri: URI, + maxResults: number, + fileService: IFileService, +): Promise { + const result: URI[] = []; + + // Helper function to recursively collect URIs + async function visitAll(folderStat: IFileStat): Promise { + // Stop if we've reached the limit + if (result.length >= maxResults) { + return false; + } + + try { + + if (!folderStat.isDirectory || !folderStat.children) { + return true; + } + + const eChildren = await resolveChildren(folderStat.children, fileService) + + // Process files first (common convention to list files before directories) + for (const child of eChildren) { + if (!child.isDirectory) { + result.push(child.resource); + + // Check if we've hit the limit + if (result.length >= maxResults) { + return false; + } + } + } + + // Then process directories recursively + for (const child of eChildren) { + const isGitIgnored = shouldExcludeDirectory(child.name) + if (child.isDirectory && !isGitIgnored) { + const shouldContinue = await visitAll(child); + if (!shouldContinue) { + return false; + } + } + } + + return true; + } catch (error) { + console.error(`Error processing directory ${folderStat.resource.fsPath}: ${error}`); + return true; // Continue despite errors in a specific directory + } + } + + const rootStat = await fileService.resolve(directoryUri) + await visitAll(rootStat); + return result; +} + + + +// -------------------------------------------------- class DirectoryStrService extends Disposable implements IDirectoryStrService { @@ -319,21 +386,25 @@ class DirectoryStrService extends Disposable implements IDirectoryStrService { constructor( @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IExplorerService private readonly explorerService: IExplorerService, + @IFileService private readonly fileService: IFileService, ) { super(); } - async getDirectoryStrTool(uri: URI, options?: { maxItemsPerDir?: number }) { - const eRoot = this.explorerService.findClosest(uri) + async getAllURIsInDirectory(uri: URI, opts: { maxResults: number }): Promise { + return getAllUrisInDirectory(uri, opts.maxResults, this.fileService) + } + + async getDirectoryStrTool(uri: URI) { + const eRoot = await this.fileService.resolve(uri) if (!eRoot) throw new Error(`The folder ${uri.fsPath} does not exist.`) - const maxItemsPerDir = options?.maxItemsPerDir ?? START_MAX_ITEMS_PER_DIR; // Use START_MAX_ITEMS_PER_DIR + const maxItemsPerDir = START_MAX_ITEMS_PER_DIR; // Use START_MAX_ITEMS_PER_DIR // First try with START_MAX_DEPTH const { content: initialContent, wasCutOff: initialCutOff } = await computeAndStringifyDirectoryTree( eRoot, - this.explorerService, + this.fileService, MAX_DIRSTR_CHARS_TOTAL_TOOL, { count: 0 }, { maxDepth: START_MAX_DEPTH, currentDepth: 0, maxItemsPerDir } @@ -344,7 +415,7 @@ class DirectoryStrService extends Disposable implements IDirectoryStrService { if (initialCutOff) { const result = await computeAndStringifyDirectoryTree( eRoot, - this.explorerService, + this.fileService, MAX_DIRSTR_CHARS_TOTAL_TOOL, { count: 0 }, { maxDepth: DEFAULT_MAX_DEPTH, currentDepth: 0, maxItemsPerDir: DEFAULT_MAX_ITEMS_PER_DIR } @@ -363,7 +434,7 @@ class DirectoryStrService extends Disposable implements IDirectoryStrService { return c } - async getAllDirectoriesStr({ cutOffMessage, maxItemsPerDir }: { cutOffMessage: string, maxItemsPerDir?: number }) { + async getAllDirectoriesStr({ cutOffMessage, }: { cutOffMessage: string, }) { let str: string = ''; let cutOff = false; const folders = this.workspaceContextService.getWorkspace().folders; @@ -371,7 +442,7 @@ class DirectoryStrService extends Disposable implements IDirectoryStrService { return '(NO WORKSPACE OPEN)'; // Use START_MAX_ITEMS_PER_DIR if not specified - const startMaxItemsPerDir = maxItemsPerDir ?? START_MAX_ITEMS_PER_DIR; + const startMaxItemsPerDir = START_MAX_ITEMS_PER_DIR; for (let i = 0; i < folders.length; i += 1) { if (i > 0) str += '\n'; @@ -381,13 +452,13 @@ class DirectoryStrService extends Disposable implements IDirectoryStrService { str += `Directory of ${f.uri.fsPath}:\n`; const rootURI = f.uri; - const eRoot = this.explorerService.findClosestRoot(rootURI); + const eRoot = await this.fileService.resolve(rootURI) if (!eRoot) continue; // First try with START_MAX_DEPTH and startMaxItemsPerDir const { content: initialContent, wasCutOff: initialCutOff } = await computeAndStringifyDirectoryTree( eRoot, - this.explorerService, + this.fileService, MAX_DIRSTR_CHARS_TOTAL_BEGINNING - str.length, { count: 0 }, { maxDepth: START_MAX_DEPTH, currentDepth: 0, maxItemsPerDir: startMaxItemsPerDir } @@ -398,7 +469,7 @@ class DirectoryStrService extends Disposable implements IDirectoryStrService { if (initialCutOff) { const result = await computeAndStringifyDirectoryTree( eRoot, - this.explorerService, + this.fileService, MAX_DIRSTR_CHARS_TOTAL_BEGINNING - str.length, { count: 0 }, { maxDepth: DEFAULT_MAX_DEPTH, currentDepth: 0, maxItemsPerDir: DEFAULT_MAX_ITEMS_PER_DIR } @@ -417,11 +488,8 @@ class DirectoryStrService extends Disposable implements IDirectoryStrService { } } - if (cutOff) { - return `${str.trimEnd()}\n${cutOffMessage}` - } - - return str + const ans = cutOff ? `${str.trimEnd()}\n${cutOffMessage}` : str + return ans } } diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index 14544216..bbc4e2c3 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -3,12 +3,13 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import { EndOfLinePreference } from '../../../../../editor/common/model.js'; +import { URI } from '../../../../../base/common/uri.js'; +import { IFileService } from '../../../../../platform/files/common/files.js'; +import { IDirectoryStrService } from '../directoryStrService.js'; import { StagingSelectionItem } from '../chatThreadServiceTypes.js'; import { os } from '../helpers/systemInfo.js'; import { RawToolParamsObj } from '../sendLLMMessageTypes.js'; import { approvalTypeOfToolName, ToolCallParams, ToolResultType } from '../toolsServiceTypes.js'; -import { IVoidModelService } from '../voidModelService.js'; import { ChatMode } from '../voidSettingsTypes.js'; // Triple backtick wrapper used throughout the prompts for code blocks @@ -524,40 +525,66 @@ ${details.map((d, i) => `${i + 1}. ${d}`).join('\n\n')}`) // chat_systemMessage({ chatMode, workspaceFolders: [], openedURIs: [], activeURI: 'pee', persistentTerminalIDs: [], directoryStr: 'lol', })) // } +const readFile = async (fileService: IFileService, uri: URI, fileSizeLimit: number): Promise<{ + val: string, + truncated: boolean, + fullFileLen: number, +} | { + val: null, + truncated?: undefined + fullFileLen?: undefined, +}> => { + try { + const fileContent = await fileService.readFile(uri) + const val = fileContent.value.toString() + if (val.length > fileSizeLimit) return { val: val.substring(0, fileSizeLimit), truncated: true, fullFileLen: val.length } + return { val, truncated: false, fullFileLen: val.length } + } + catch (e) { + return { val: null } + } +} + + + + + + + export const chat_userMessageContent = async (instructions: string, currSelns: StagingSelectionItem[] | null, - opts: { type: 'references' } | { type: 'fullCode', voidModelService: IVoidModelService } + opts: { directoryStrService: IDirectoryStrService, fileService: IFileService } ) => { const lineNumAddition = (range: [number, number]) => ` (lines ${range[0]}:${range[1]})` let selnsStrs: string[] = [] - if (opts.type === 'references') { - selnsStrs = currSelns?.map((s) => { - if (s.type === 'File') return `${s.uri.fsPath}` - if (s.type === 'CodeSelection') return `${s.uri.fsPath}${lineNumAddition(s.range)}` - if (s.type === 'Folder') return `${s.uri.fsPath}/` - return '' - }) ?? [] - } - if (opts.type === 'fullCode') { - selnsStrs = await Promise.all(currSelns?.map(async (s) => { - if (s.type === 'File' || s.type === 'CodeSelection') { - const voidModelService = opts.voidModelService - const { model } = await voidModelService.getModelSafe(s.uri) - if (!model) return '' - const val = model.getValue(EndOfLinePreference.LF) + selnsStrs = await Promise.all(currSelns?.map(async (s) => { - const lineNumAdd = s.type === 'CodeSelection' ? lineNumAddition(s.range) : '' - const str = `${s.uri.fsPath}${lineNumAdd}\n${tripleTick[0]}${s.language}\n${val}\n${tripleTick[1]}` + if (s.type === 'File' || s.type === 'CodeSelection') { + const { val } = await readFile(opts.fileService, s.uri, 2_000_000) + const lineNumAdd = s.type === 'CodeSelection' ? lineNumAddition(s.range) : '' + const content = val === null ? 'null' : `${tripleTick[0]}${s.language}\n${val}\n${tripleTick[1]}` + const str = `${s.uri.fsPath}${lineNumAdd}:\n${content}` + return str + } + else if (s.type === 'Folder') { + const dirStr: string = await opts.directoryStrService.getDirectoryStrTool(s.uri) + const folderStructure = `${s.uri.fsPath} folder structure:${tripleTick[0]}\n${dirStr}\n${tripleTick[1]}` + + const uris = await opts.directoryStrService.getAllURIsInDirectory(s.uri, { maxResults: 1_000 }) + const strOfFiles = await Promise.all(uris.map(async uri => { + const { val, truncated } = await readFile(opts.fileService, uri, 100_000) + const truncationStr = truncated ? `\n... file truncated ...` : '' + const content = val === null ? 'null' : `${tripleTick[0]}\n${val}${truncationStr}\n${tripleTick[1]}` + const str = `${uri.fsPath}:\n${content}` return str - } - if (s.type === 'Folder') { - // TODO - return '' - } + })) + const contentStr = [folderStructure, ...strOfFiles].join('\n\n') + return contentStr + } + else return '' - }) ?? []) - } + }) ?? []) const selnsStr = selnsStrs.join('\n') ?? '' let str = '' diff --git a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts index d7ba0024..dc60602d 100644 --- a/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts +++ b/src/vs/workbench/contrib/void/common/voidSettingsTypes.ts @@ -92,7 +92,7 @@ export const displayInfoOfProviderName = (providerName: ProviderName): DisplayIn return { title: 'Groq', } } else if (providerName === 'xAI') { - return { title: 'Grok', } + return { title: 'Grok (xAI)', } } else if (providerName === 'mistral') { return { title: 'Mistral', }