diff --git a/src/vs/platform/void/browser/void.contribution.ts b/src/vs/platform/void/browser/void.contribution.ts index 276d6e72..25d7b529 100644 --- a/src/vs/platform/void/browser/void.contribution.ts +++ b/src/vs/platform/void/browser/void.contribution.ts @@ -19,3 +19,6 @@ import '../common/metricsService.js' // updates import '../common/voidUpdateService.js' + +// tools +import '../common/toolsService.js' diff --git a/src/vs/platform/void/common/toolsService.ts b/src/vs/platform/void/common/toolsService.ts index 77666b84..4a97508d 100644 --- a/src/vs/platform/void/common/toolsService.ts +++ b/src/vs/platform/void/common/toolsService.ts @@ -1,15 +1,18 @@ -import { URI } from '../../../base/common/uri' -import { IModelService } from '../../../editor/common/services/model' -import { VSReadFile } from '../../../workbench/contrib/void/browser/helpers/readFile' -import { IFileService, IFileStat } from '../../files/common/files' -import { registerSingleton, InstantiationType } from '../../instantiation/common/extensions' -import { createDecorator } from '../../instantiation/common/instantiation' +import { CancellationToken } from '../../../base/common/cancellation.js' +import { URI } from '../../../base/common/uri.js' +import { IModelService } from '../../../editor/common/services/model.js' +import { VSReadFileRaw } from '../../../workbench/contrib/void/browser/helpers/readFile.js' +import { QueryBuilder } from '../../../workbench/services/search/common/queryBuilder.js' +import { ISearchService } from '../../../workbench/services/search/common/search.js' +import { IFileService, IFileStat } from '../../files/common/files.js' +import { registerSingleton, InstantiationType } from '../../instantiation/common/extensions.js' +import { createDecorator, IInstantiationService } from '../../instantiation/common/instantiation.js' +import { IWorkspaceContextService } from '../../workspace/common/workspace.js' +// import { IWorkspacesService } from '../../workspaces/common/workspaces.js' -const pagination = { - desc: `Very large results may be paginated (indicated in the result). Pagination fails gracefully if out of bounds or invalid page number.`, - param: { pageNumber: { type: 'number', description: 'The page number (optional, defaults to 1).' }, } -} as const +// tool use for AI + @@ -21,6 +24,13 @@ export type InternalToolInfo = { }, required: string[], // required paramNames } + +// helper +const pagination = { + desc: `Very large results may be paginated (indicated in the result). Pagination fails gracefully if out of bounds or invalid page number.`, + param: { pageNumber: { type: 'number', description: 'The page number (optional, defaults to 1).' }, } +} as const + const contextTools = { read_file: { description: 'Returns file contents of a given URI.', @@ -48,7 +58,7 @@ const contextTools = { required: ['query'] }, - grep_search: { + search: { description: `Returns all code excerpts containing the given string or grep query. This does NOT search pathname. As a follow-up, you may want to use read_file to view the full file contents of the results. ${pagination.desc}`, params: { query: { type: 'string', description: undefined }, @@ -66,7 +76,7 @@ const contextTools = { type ContextToolName = keyof typeof contextTools type ContextParamNames = keyof typeof contextTools[T]['params'] -type ContextParams = { [paramName in ContextParamNames]: string } +type ContextParams = { [paramName in ContextParamNames]: unknown } type ContextToolCallFns = { [ToolName in ContextToolName]: ((p: (ContextParams)) => Promise) @@ -78,20 +88,22 @@ type ContextToolCallFns = { +// TODO check to make sure in workspace +// TODO check to make sure is not gitignored -export async function generateDirectoryTreeMd(fileService: IFileService, uri: URI): Promise { +async function generateDirectoryTreeMd(fileService: IFileService, rootURI: URI): Promise { let output = '' - function traverseChildren(children: IFileStat[], depth: number) { const indentation = ' '.repeat(depth); for (const child of children) { output += `${indentation}- ${child.name}\n`; - output += traverseChildren(child.children ?? [], depth + 1); + traverseChildren(child.children ?? [], depth + 1); } } - const stat = await fileService.resolve(uri, { resolveMetadata: false }); + const stat = await fileService.resolve(rootURI, { resolveMetadata: false }); + console.log('statttttttttt', JSON.stringify(stat, null, 2)) // kickstart recursion output += `${stat.name}\n`; traverseChildren(stat.children ?? [], 1); @@ -99,30 +111,42 @@ export async function generateDirectoryTreeMd(fileService: IFileService, uri: UR return output; } +// async function searchPathnameRegex(fileService: IFileService, pathnameRegex: string, workspaceURI: URI) { +// let output: string[] = [] +// let regex: RegExp +// try { +// regex = new RegExp(pathnameRegex) +// } catch (e) { +// return [`(Error: invalid regex: ${e})`] +// } + +// function traverseChildren(children: IFileStat[]) { +// for (const child of children) { +// // if it's a file, match its name +// if (child.isFile) { +// if (regex.test(child.resource.fsPath)) { output.push(child.resource.fsPath) } +// } +// // otherwise traverse children +// else { +// traverseChildren(child.children ?? []) +// } +// } +// } +// const stat = await fileService.resolve(workspaceURI, { resolveMetadata: false }); +// traverseChildren(stat.children ?? []); +// return output; +// } + + const validateURI = (uriStr: unknown) => { if (typeof uriStr !== 'string') throw new Error('(uri was not a string)') console.log('uriStr!!!!', uriStr) const uri = URI.file(uriStr) - console.log('uri!!!!', uri) + console.log('uri!!!!', uri.fsPath) return uri } - - - - - - - - - - - - - - - export interface IToolService { readonly _serviceBrand: undefined; callContextTool: (toolName: T, params: ContextParams) => Promise @@ -139,26 +163,47 @@ export class ToolService implements IToolService { constructor( @IFileService fileService: IFileService, @IModelService modelService: IModelService, + @IWorkspaceContextService w: IWorkspaceContextService, + @ISearchService s: ISearchService, + @IInstantiationService instantiationService: IInstantiationService, ) { + + + const queryBuilder = instantiationService.createInstance(QueryBuilder); + this.contextToolCallFns = { read_file: async ({ uri: uriStr }) => { const uri = validateURI(uriStr) - const fileContents = await VSReadFile(modelService, uri) + const fileContents = await VSReadFileRaw(fileService, uri) return fileContents ?? '(could not read file)' }, list_dir: async ({ uri: uriStr }) => { const uri = validateURI(uriStr) const treeStr = await generateDirectoryTreeMd(fileService, uri) + console.log('!!PIZA', treeStr) return treeStr }, - pathname_search: async ({ query }) => { - return '' + pathname_search: async ({ query: queryStr }) => { + if (typeof queryStr !== 'string') return '(Error: query was not a string)' + const query = queryBuilder.file(w.getWorkspace().folders.map(f => f.uri), { filePattern: queryStr, }); + + const data = await s.fileSearch(query, CancellationToken.None); + const str = data.results.map(({ resource, results }) => resource.fsPath).join('\n') + return str }, - grep_search: async ({ query }) => { - return '' + search: async ({ query: queryStr }) => { + if (typeof queryStr !== 'string') return '(Error: query was not a string)' + const query = queryBuilder.text({ pattern: queryStr, }, w.getWorkspace().folders.map(f => f.uri)); + + const data = await s.textSearch(query, CancellationToken.None); + const str = data.results.map(({ resource, results }) => resource.fsPath).join('\n') + return str }, } + + + } callContextTool: IToolService['callContextTool'] = (toolName, params) => { diff --git a/src/vs/platform/void/electron-main/templates/templates.ts b/src/vs/platform/void/electron-main/templates/templates.ts index 0f74197e..9b90dfee 100644 --- a/src/vs/platform/void/electron-main/templates/templates.ts +++ b/src/vs/platform/void/electron-main/templates/templates.ts @@ -4,7 +4,9 @@ modelName -> { system_message_type: 'system' | 'developer' (openai) | null // if null, we will just do a string of system message supports_tools: boolean // we will just do a string of tool use if it doesn't support supports_autocomplete_FIM (suffix) // we will just do a description of FIM if it doens't support <|fim_hole|> - max_tokens + + supports_streaming: boolean (o1 does NOT) + max_tokens: number } diff --git a/src/vs/workbench/contrib/void/browser/helpers/readFile.ts b/src/vs/workbench/contrib/void/browser/helpers/readFile.ts index 60e5dc5c..d511239b 100644 --- a/src/vs/workbench/contrib/void/browser/helpers/readFile.ts +++ b/src/vs/workbench/contrib/void/browser/helpers/readFile.ts @@ -1,6 +1,7 @@ import { URI } from '../../../../../base/common/uri' import { EndOfLinePreference } from '../../../../../editor/common/model' import { IModelService } from '../../../../../editor/common/services/model.js' +import { IFileService } from '../../../../../platform/files/common/files' // read files from VSCode export const VSReadFile = async (modelService: IModelService, uri: URI): Promise => { @@ -8,3 +9,9 @@ export const VSReadFile = async (modelService: IModelService, uri: URI): Promise if (!model) return null return model.getValue(EndOfLinePreference.LF) } + +export const VSReadFileRaw = async (fileService: IFileService, uri: URI) => { + const res = await fileService.readFile(uri) + const str = res.value.toString() + return str +}