all tools seem to work!

This commit is contained in:
Andrew Pareles 2025-02-09 00:26:42 -08:00
parent d3e73b92e0
commit 812014d6d3
4 changed files with 95 additions and 38 deletions

View file

@ -19,3 +19,6 @@ import '../common/metricsService.js'
// updates
import '../common/voidUpdateService.js'
// tools
import '../common/toolsService.js'

View file

@ -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<T extends ContextToolName> = keyof typeof contextTools[T]['params']
type ContextParams<T extends ContextToolName> = { [paramName in ContextParamNames<T>]: string }
type ContextParams<T extends ContextToolName> = { [paramName in ContextParamNames<T>]: unknown }
type ContextToolCallFns = {
[ToolName in ContextToolName]: ((p: (ContextParams<ToolName>)) => Promise<string>)
@ -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<string> {
async function generateDirectoryTreeMd(fileService: IFileService, rootURI: URI): Promise<string> {
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: <T extends ContextToolName>(toolName: T, params: ContextParams<T>) => Promise<string>
@ -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) => {

View file

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

View file

@ -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<string | null> => {
@ -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
}