diff --git a/src/vs/platform/void/common/toolsService.ts b/src/vs/platform/void/common/toolsService.ts new file mode 100644 index 00000000..6e87e974 --- /dev/null +++ b/src/vs/platform/void/common/toolsService.ts @@ -0,0 +1,193 @@ +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' + + +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 + + + +// we do this using Anthropic's style and convert to OpenAI style later +export type InternalToolInfo = { + description: string, + params: { + [paramName: string]: { type: string, description: string | undefined } // name -> type + }, + required: string[], // required paramNames +} +const contextTools = { + read_file: { + description: 'Returns file contents of a given URI.', + params: { + uri: { type: 'string', description: undefined }, + }, + required: ['uri'], + }, + + list_dir: { + description: `Returns all file names and folder names in a given URI. ${pagination.desc}`, + params: { + uri: { type: 'string', description: undefined }, + ...pagination.param + }, + required: ['uri'], + }, + + pathname_search: { + description: `Returns all pathnames that match a given grep query. You should use this when looking for a file with a specific name or path. This does NOT search file content. ${pagination.desc}`, + params: { + query: { type: 'string', description: undefined }, + ...pagination.param, + }, + required: ['query'] + }, + + grep_search: { + description: `Returns all code excerpts containing the given string or grep query. Does not search filename. 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 }, + ...pagination.param, + }, + required: ['query'], + }, + + // semantic_search: { + // description: 'Searches files semantically for the given string query.', + // // RAG + // }, + +} as const satisfies { [name: string]: InternalToolInfo } + +type ContextToolName = keyof typeof contextTools +type ContextParamNames = keyof typeof contextTools[T]['params'] +type ContextParams = { [paramName in ContextParamNames]: string } + +type ContextToolCallFns = { + [ToolName in ContextToolName]: ((p: (ContextParams)) => Promise) +} + + + + + + + + +/* +Generates something that looks like this: + ++ folder1 +│ ├── file1.py +│ ├── subfolder1 +│ │ ├── file1.json +│ │ └── file2.py +│ └── another_file.txt +└── folder2 + ├── script.js + └── styles.css +*/ + + +/** + * Generates a Markdown tree starting at the given URI. + * The root folder is printed as a header (without a bullet). + */ +export async function generateMarkdownTree(fileService: IFileService, uri: 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); + } + } + const stat = await fileService.resolve(uri, { resolveMetadata: false }); + + // kickstart recursion + output += `${stat.name}\n`; + traverseChildren(stat.children ?? [], 1); + + 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) + return uri +} + + + + + + + + + + + + + + + + +export interface IToolService { + readonly _serviceBrand: undefined; + callContextTool: (toolName: T, params: ContextParams) => Promise +} + +export const IToolService = createDecorator('ToolService'); + + +// implemented by calling channel +export class ToolService implements IToolService { + + readonly _serviceBrand: undefined; + + contextToolCallFns: ContextToolCallFns + + constructor( + @IFileService fileService: IFileService, + @IModelService modelService: IModelService, + ) { + this.contextToolCallFns = { + read_file: async ({ uri: uriStr }) => { + const uri = validateURI(uriStr) + const fileContents = await VSReadFile(modelService, uri) + return fileContents ?? '(could not read file)' + }, + list_dir: async ({ uri: uriStr }) => { + const uri = validateURI(uriStr) + const treeStr = await generateMarkdownTree(fileService, uri) + return treeStr + }, + pathname_search: async ({ query }) => { + return '' + }, + grep_search: async ({ query }) => { + return '' + }, + + } + } + + callContextTool: IToolService['callContextTool'] = (toolName, params) => { + return this.contextToolCallFns[toolName](params) + } + + +} + +registerSingleton(IToolService, ToolService, InstantiationType.Eager); + diff --git a/src/vs/platform/void/electron-main/llmMessage/anthropic.ts b/src/vs/platform/void/electron-main/llmMessage/anthropic.ts index ea220eed..91461b16 100644 --- a/src/vs/platform/void/electron-main/llmMessage/anthropic.ts +++ b/src/vs/platform/void/electron-main/llmMessage/anthropic.ts @@ -6,6 +6,27 @@ import Anthropic from '@anthropic-ai/sdk'; import { _InternalSendLLMChatMessageFnType } from '../../common/llmMessageTypes.js'; import { anthropicMaxPossibleTokens } from '../../common/voidSettingsTypes.js'; +import { InternalToolInfo } from '../../common/toolsService.js'; + + + + +export const toAnthropicTool = (toolName: string, toolInfo: InternalToolInfo) => { + const { description, params, required } = toolInfo + return { + name: toolName, + description: description, + input_schema: { + type: 'object', + properties: params, + required: required, + } + } satisfies Anthropic.Messages.Tool +} + + + + export const sendAnthropicChat: _InternalSendLLMChatMessageFnType = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter }) => { diff --git a/src/vs/platform/void/electron-main/llmMessage/openai.ts b/src/vs/platform/void/electron-main/llmMessage/openai.ts index 8fd49684..3a3c4818 100644 --- a/src/vs/platform/void/electron-main/llmMessage/openai.ts +++ b/src/vs/platform/void/electron-main/llmMessage/openai.ts @@ -6,11 +6,33 @@ import OpenAI from 'openai'; import { _InternalModelListFnType, _InternalSendLLMFIMMessageFnType, _InternalSendLLMChatMessageFnType } from '../../common/llmMessageTypes.js'; import { Model } from 'openai/resources/models.js'; +import { InternalToolInfo } from '../../common/toolsService.js'; // import { parseMaxTokensStr } from './util.js'; -// https://cdn.openai.com/spec/model-spec-2024-05-08.html#follow-the-chain-of-command -// https://platform.openai.com/docs/guides/reasoning#advice-on-prompting +// developer command - https://cdn.openai.com/spec/model-spec-2024-05-08.html#follow-the-chain-of-command +// prompting - https://platform.openai.com/docs/guides/reasoning#advice-on-prompting + + +export const toOpenAITool = (toolName: string, toolInfo: InternalToolInfo) => { + const { description, params, required } = toolInfo + return { + type: 'function', + function: { + name: toolName, + description: description, + parameters: { + type: 'object', + properties: params, + required: required, + } + } + } satisfies OpenAI.Chat.Completions.ChatCompletionTool +} + + + + // might not currently be used in the code diff --git a/src/vs/platform/void/electron-main/templates/tools.ts b/src/vs/platform/void/electron-main/templates/tools.ts deleted file mode 100644 index 70c250e6..00000000 --- a/src/vs/platform/void/electron-main/templates/tools.ts +++ /dev/null @@ -1,111 +0,0 @@ - -import Anthropic from '@anthropic-ai/sdk' -import OpenAI from 'openai' - -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 - -// we do this using Anthropic's style and convert to OpenAI style later -type InternalToolInfo = { - description: string, - params: { - [paramName: string]: { type: string, description: string | undefined } // name -> type - }, - required: string[], // required paramNames -} -const contextTools = { - read_file: { - description: 'Returns file contents of a given URI.', - params: { - uri: { type: 'string', description: undefined }, - }, - required: ['uri'], - }, - - list_dir: { - description: `Returns all file names and folder names in a given URI. ${pagination.desc}`, - params: { - uri: { type: 'string', description: undefined }, - ...pagination.param - }, - required: ['uri'], - }, - - pathname_search: { - description: `Returns all pathnames that match a given grep query. You should use this when looking for a file with a specific name or path. This does NOT search file content. ${pagination.desc}`, - params: { - query: { type: 'string', description: undefined }, - ...pagination.param, - }, - required: ['query'] - }, - - grep_search: { - description: `Returns all code excerpts containing the given string or grep query. Does not search filename. 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 }, - ...pagination.param, - }, - required: ['query'], - }, - - // semantic_search: { - // description: 'Searches files semantically for the given string query.', - // // RAG - // }, - -} as const satisfies { [name: string]: InternalToolInfo } - -type ContextToolName = keyof typeof contextTools -type ContextParams = keyof typeof contextTools[T]['params'] - -const contextFunctions: { [ToolName in ContextToolName]: (p: ({ [paramName in ContextParams]: string })) => string } = { - read_file: ({ uri }) => { - return '' - }, - list_dir: ({ }) => { - return '' - }, - pathname_search: ({ }) => { - return '' - }, - grep_search: ({ }) => { - return '' - }, - -} - - - -const toOpenAITool = (toolName: string, toolInfo: InternalToolInfo) => { - const { description, params, required } = toolInfo - return { - type: 'function', - function: { - name: toolName, - description: description, - parameters: { - type: 'object', - properties: params, - required: required, - } - } - } satisfies OpenAI.Chat.Completions.ChatCompletionTool -} - - - -const toAnthropicTool = (toolName: string, toolInfo: InternalToolInfo) => { - const { description, params, required } = toolInfo - return { - name: toolName, - description: description, - input_schema: { - type: 'object', - properties: params, - required: required, - } - } satisfies Anthropic.Messages.Tool -} diff --git a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts index 7dc23d53..468de855 100644 --- a/src/vs/workbench/contrib/void/browser/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/browser/prompt/prompts.ts @@ -133,8 +133,8 @@ Store Result: After computing fib(n), the result is stored in memo for future re ` -type FileSelnLocal = FileSelection & { content: string } -const stringifyFileSelection = ({ fileURI, selectionStr, range, content }: FileSelnLocal) => { +type FileSelnLocal = { fileURI: URI, content: string } +const stringifyFileSelection = ({ fileURI, content }: FileSelnLocal) => { return `\ ${fileURI.fsPath} \`\`\`${filenameToVscodeLanguage(fileURI.fsPath) ?? ''}