file and folder dump!

This commit is contained in:
Andrew Pareles 2025-05-06 05:38:57 -07:00
parent fa83dfc529
commit 097d89366c
9 changed files with 266 additions and 112 deletions

View file

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

View file

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

View file

@ -249,7 +249,6 @@ const getOptionsAtPath = async (accessor: ReturnType<typeof useAccessor>, 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<HTMLTextAreaElement, InputBox2Props>(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<HTMLInputElement>) => {
// Create a ref for the input element to maintain the same DOM node between renders
const inputRef = useRef<HTMLInputElement>(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<HTMLInputElement>) => {
onChangeValue(e.target.value);
}, [onChangeValue]);
return (
<input
value={value}
onChange={(e) => 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

View file

@ -130,6 +130,24 @@ const AddProvidersPage = ({ pageIndex, setPageIndex }: { pageIndex: number, setP
const settingsState = useSettingsState();
const [errorMessage, setErrorMessage] = useState<string | null>(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 (
<div className="flex flex-col md:flex-row w-full h-[80vh] gap-6 max-w-[900px] mx-auto relative">
{/* Left Column - Fixed */}
@ -192,13 +210,13 @@ const AddProvidersPage = ({ pageIndex, setPageIndex }: { pageIndex: number, setP
<div className="text-xl font-medium text-[#0e70c0]">Models</div>
<div className="h-px flex-grow bg-void-border-2/30"></div>
</div>
{currentTab === 'Local' && (
<div className="text-sm text-void-fg-3 mb-4 bg-void-bg-3/30 p-3 rounded border-l-2 border-[#0e70c0]/70">
Local models should be detected automatically. You can add custom models below.
</div>
)}
{currentTab === 'Local' && <ModelDump filteredProviders={localProviderNames} />}
{currentTab === 'Cloud/Other' && <ModelDump filteredProviders={cloudProviders} />}
</div>
@ -222,7 +240,7 @@ Only applies to models labeled \`:free\`. More information [here](https://openro
{/* Navigation buttons in right column */}
<div className="flex flex-col items-end w-full mt-auto pt-8">
{errorMessage && (
<div className="text-amber-400 mb-2 text-sm opacity-80">{errorMessage}</div>
<div className="text-amber-400 mb-2 text-sm opacity-80 transition-opacity duration-300">{errorMessage}</div>
)}
<div className="flex items-center gap-2">
<PreviousButton onClick={() => 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.");
}
}}
/>

View file

@ -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 <ErrorBoundary>
<div className='my-1'>
<VoidSimpleInputBox
value={settingValue}
onChangeValue={useCallback((newVal) => {
voidSettingsService.setSettingOfProvider(providerName, settingName, newVal)
}, [voidSettingsService, providerName, settingName])}
// placeholder={`${providerTitle} ${settingTitle} (${placeholder})`}
onChangeValue={handleChangeValue}
placeholder={`${settingTitle} (${placeholder})`}
passwordBlur={isPasswordField}
compact={true}

View file

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

View file

@ -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<string>
getAllDirectoriesStr(opts: { cutOffMessage: string, maxItemsPerDir?: number }): Promise<string>
getDirectoryStrTool(uri: URI): Promise<string>
getAllDirectoriesStr(opts: { cutOffMessage: string }): Promise<string>
getAllURIsInDirectory(uri: URI, opts: { maxResults: number }): Promise<URI[]>
}
export const IDirectoryStrService = createDecorator<IDirectoryStrService>('voidDirectoryStrService');
@ -38,35 +37,35 @@ export const IDirectoryStrService = createDecorator<IDirectoryStrService>('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<IFileStat[]> => {
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<URI[]> {
const result: URI[] = [];
// Helper function to recursively collect URIs
async function visitAll(folderStat: IFileStat): Promise<boolean> {
// 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<URI[]> {
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
}
}

View file

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

View file

@ -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', }