diff --git a/src/vs/workbench/contrib/void/browser/editCodeService.ts b/src/vs/workbench/contrib/void/browser/editCodeService.ts index d8bae574..2db9cb90 100644 --- a/src/vs/workbench/contrib/void/browser/editCodeService.ts +++ b/src/vs/workbench/contrib/void/browser/editCodeService.ts @@ -42,6 +42,7 @@ import { ILLMMessageService } from '../common/llmMessageService.js'; import { LLMChatMessage, _InternalLLMChatMessage, errorDetails } from '../common/llmMessageTypes.js'; import { IMetricsService } from '../common/metricsService.js'; import { VSReadFile } from './helpers/readFile.js'; +import { voidTools } from '../common/toolsService.js'; const configOfBG = (color: Color) => { return { dark: color, light: color, hcDark: color, hcLight: color, } @@ -65,7 +66,6 @@ registerColor('void.sweepIdxBG', configOfBG(sweepIdxBG), '', true); - const getLeadingWhitespacePx = (editor: ICodeEditor, startLine: number): number => { const model = editor.getModel(); @@ -1138,6 +1138,44 @@ class EditCodeService extends Disposable implements IEditCodeService { + + + async startAgent(queryStr: string) { + // agent loop + const messages: LLMChatMessage[] = [] + + while (true) { + await new Promise((res, rej) => { + this._llmMessageService.sendLLMMessage({ + messagesType: 'chatMessages', + tools: [voidTools['read_file']], + useProviderFor: 'Apply', + logging: { loggingName: `Agent` }, + messages, + onText: ({ fullText }) => { + + }, + onFinalMessage: async ({ fullText, tools }) => { + res(tools) + }, + onError: (e) => { + }, + }) + }) + } + + + + + } + + + stopAgent() { + + } + + + public startApplying(opts: StartApplyingOpts) { if (opts.type === 'rewrite') { diff --git a/src/vs/workbench/contrib/void/common/llmMessageTypes.ts b/src/vs/workbench/contrib/void/common/llmMessageTypes.ts index 40ab1f2a..dcfd2c67 100644 --- a/src/vs/workbench/contrib/void/common/llmMessageTypes.ts +++ b/src/vs/workbench/contrib/void/common/llmMessageTypes.ts @@ -22,7 +22,7 @@ export const errorDetails = (fullError: Error | null): string | null => { } export type OnText = (p: { newText: string, fullText: string }) => void -export type OnFinalMessage = (p: { fullText: string }) => void +export type OnFinalMessage = (p: { fullText: string, tools: { name: string, args: string }[] }) => void export type OnError = (p: { message: string, fullError: Error | null }) => void export type AbortRef = { current: (() => void) | null } diff --git a/src/vs/workbench/contrib/void/common/toolsService.ts b/src/vs/workbench/contrib/void/common/toolsService.ts index 86cc4356..43b18cb8 100644 --- a/src/vs/workbench/contrib/void/common/toolsService.ts +++ b/src/vs/workbench/contrib/void/common/toolsService.ts @@ -29,7 +29,7 @@ const paginationHelper = { param: { pageNumber: { type: 'number', description: 'The page number (optional, default is 1).' }, } } as const -export const contextTools = { +export const voidTools: { [name: string]: InternalToolInfo } = { read_file: { name: 'read_file', description: 'Returns file contents of a given URI.', @@ -73,12 +73,11 @@ export const contextTools = { // description: 'Searches files semantically for the given string query.', // // RAG // }, - } -export type ContextToolName = keyof typeof contextTools -type ContextToolParamNames = keyof typeof contextTools[T]['params'] -type ContextToolParams = { [paramName in ContextToolParamNames]: unknown } +export type ToolName = keyof typeof voidTools +type ToolParamNames = keyof typeof voidTools[T]['params'] +type ToolParamsObj = { [paramName in ToolParamNames]: unknown } @@ -121,7 +120,7 @@ export class ToolService implements IToolService { readonly _serviceBrand: undefined; - public contextToolCallFns + public toolFns constructor( @IFileService fileService: IFileService, @@ -133,32 +132,32 @@ export class ToolService implements IToolService { const queryBuilder = instantiationService.createInstance(QueryBuilder); - this.contextToolCallFns = { - read_file: async ({ uri: uriStr }: ContextToolParams<'read_file'>) => { + this.toolFns = { + read_file: async ({ uri: uriStr }: ToolParamsObj<'read_file'>) => { const uri = validateURI(uriStr) const fileContents = await VSReadFileRaw(fileService, uri) return fileContents ?? '(could not read file)' }, - list_dir: async ({ uri: uriStr }: ContextToolParams<'list_dir'>) => { + list_dir: async ({ uri: uriStr }: ToolParamsObj<'list_dir'>) => { const uri = validateURI(uriStr) // TODO!!!! check to make sure in workspace // TODO check to make sure is not gitignored const treeStr = await generateDirectoryTreeMd(fileService, uri) return treeStr }, - pathname_search: async ({ query: queryStr }: ContextToolParams<'pathname_search'>) => { + pathname_search: async ({ query: queryStr }: ToolParamsObj<'pathname_search'>) => { if (typeof queryStr !== 'string') return '(Error: query was not a string)' - const query = queryBuilder.file(workspaceContextService.getWorkspace().folders.map(f => f.uri), { filePattern: queryStr, }); + const query = queryBuilder.file(workspaceContextService.getWorkspace().folders.map(f => f.uri), { filePattern: queryStr, }) - const data = await searchService.fileSearch(query, CancellationToken.None); + const data = await searchService.fileSearch(query, CancellationToken.None) const URIs = data.results.map(({ resource, results }) => resource.fsPath) return URIs }, - search: async ({ query: queryStr }: ContextToolParams<'search'>) => { + search: async ({ query: queryStr }: ToolParamsObj<'search'>) => { if (typeof queryStr !== 'string') return '(Error: query was not a string)' - const query = queryBuilder.text({ pattern: queryStr, }, workspaceContextService.getWorkspace().folders.map(f => f.uri)); + const query = queryBuilder.text({ pattern: queryStr, }, workspaceContextService.getWorkspace().folders.map(f => f.uri)) - const data = await searchService.textSearch(query, CancellationToken.None); + const data = await searchService.textSearch(query, CancellationToken.None) const URIs = data.results.map(({ resource, results }) => resource) return URIs }, diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/anthropic.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/anthropic.ts index e957c83a..75ae7d89 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/anthropic.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/anthropic.ts @@ -6,7 +6,7 @@ import Anthropic from '@anthropic-ai/sdk'; import { _InternalSendLLMChatMessageFnType } from '../../common/llmMessageTypes.js'; import { anthropicMaxPossibleTokens } from '../../common/voidSettingsTypes.js'; -import { InternalToolInfo } from '../../common/toolsService.js'; +import { InternalToolInfo, voidTools } from '../../common/toolsService.js'; @@ -45,7 +45,7 @@ export const sendAnthropicChat: _InternalSendLLMChatMessageFnType = ({ messages, messages: messages, model: modelName, max_tokens: maxTokens, - // tools: [toAnthropicTool(contextTools.list_dir)] + tools: [toAnthropicTool(voidTools.list_dir)] }); @@ -55,33 +55,31 @@ export const sendAnthropicChat: _InternalSendLLMChatMessageFnType = ({ messages, }) - // can do tool use streaming - const toolCallOfIndex: { [index: string]: { name: string, args: string } } = {} - stream.on('streamEvent', e => { - if (e.type === 'content_block_start') { - if (e.content_block.type !== 'tool_use') return - const index = e.index - if (!toolCallOfIndex[index]) toolCallOfIndex[index] = { name: '', args: '' } - toolCallOfIndex[index].name += e.content_block.name ?? '' - toolCallOfIndex[index].args += e.content_block.input ?? '' - } - else if (e.type === 'content_block_delta') { - if (e.delta.type !== 'input_json_delta') return - toolCallOfIndex[e.index].args += e.delta.partial_json - } - // TODO!!!!! - // onText({}) - }) + // // can do tool use streaming + // const toolCallOfIndex: { [index: string]: { name: string, args: string } } = {} + // stream.on('streamEvent', e => { + // if (e.type === 'content_block_start') { + // if (e.content_block.type !== 'tool_use') return + // const index = e.index + // if (!toolCallOfIndex[index]) toolCallOfIndex[index] = { name: '', args: '' } + // toolCallOfIndex[index].name += e.content_block.name ?? '' + // toolCallOfIndex[index].args += e.content_block.input ?? '' + // } + // else if (e.type === 'content_block_delta') { + // if (e.delta.type !== 'input_json_delta') return + // toolCallOfIndex[e.index].args += e.delta.partial_json + // } + // // TODO!!!!! + // // onText({}) + // }) // when we get the final message on this stream (or when error/fail) stream.on('finalMessage', (response) => { // stringify the response's content - const content = response.content.map(c => c.type === 'text' ? c.text : '').join('\n') - const tools = response.content.map(c => c.type === 'tool_use' ? { name: c.name, input: c.input } : null).filter(c => !!c) + const content = response.content.map(c => c.type === 'text' ? c.text : '').join('\n\n') + const tools = response.content.map(c => c.type === 'tool_use' ? { name: c.name, args: JSON.stringify(c.input) } : null).filter(c => !!c) - console.log("TOOLS!!!!", typeof tools[0]?.input, JSON.stringify(tools, null, 2)) - - onFinalMessage({ fullText: content, }) + onFinalMessage({ fullText: content, tools }) }) stream.on('error', (error) => { diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/gemini.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/gemini.ts index eef8cc3a..2732fbe4 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/gemini.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/gemini.ts @@ -35,7 +35,7 @@ export const sendGeminiChat: _InternalSendLLMChatMessageFnType = async ({ messag fullText += newText; onText({ newText, fullText }); } - onFinalMessage({ fullText }); + onFinalMessage({ fullText, tools: [] }); }) .catch((error) => { onError({ message: error + '', fullError: error }) diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/groq.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/groq.ts index 8f7efd14..c6fcb290 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/groq.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/groq.ts @@ -32,7 +32,7 @@ export const sendGroqChat: _InternalSendLLMChatMessageFnType = async ({ messages onText({ newText, fullText }); } - onFinalMessage({ fullText }); + onFinalMessage({ fullText, tools: [] }); }) .catch(error => { onError({ message: error + '', fullError: error }); diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/mistral.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/mistral.ts index cfddc2a5..ea3179ed 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/mistral.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/mistral.ts @@ -36,7 +36,7 @@ export const sendMistralChat: _InternalSendLLMChatMessageFnType = async ({ messa onText({ newText, fullText }); } - onFinalMessage({ fullText }); + onFinalMessage({ fullText, tools: [] }); }) .catch(error => { onError({ message: error + '', fullError: error }); diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/ollama.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/ollama.ts index 5a69698b..daff3a29 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/ollama.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/ollama.ts @@ -68,7 +68,7 @@ export const sendOllamaFIM: _InternalSendLLMFIMMessageFnType = ({ messages, onTe fullText += newText; onText({ newText, fullText }); } - onFinalMessage({ fullText }); + onFinalMessage({ fullText, tools: [] }); }) // when error/fail .catch((error) => { @@ -100,14 +100,13 @@ export const sendOllamaChat: _InternalSendLLMChatMessageFnType = ({ messages, on for await (const chunk of stream) { const newText = chunk.message.content; - - // chunk.message.tool_calls[0].function.arguments fullText += newText; onText({ newText, fullText }); } - onFinalMessage({ fullText }); + + onFinalMessage({ fullText, tools: [] }); }) // when error/fail diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/openai.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/openai.ts index ead592d3..4744db62 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/openai.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/openai.ts @@ -111,30 +111,32 @@ export const sendOpenAIFIM: _InternalSendLLMFIMMessageFnType = ({ messages, onTe // openai.completions has a FIM parameter called `suffix`, but it's deprecated and only works for ~GPT 3 era models - onFinalMessage({ fullText: 'TODO' }) + } // OpenAI, OpenRouter, OpenAICompatible -export const sendOpenAIChat: _InternalSendLLMChatMessageFnType = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName }) => { +export const sendOpenAIChat: _InternalSendLLMChatMessageFnType = ({ messages, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName, tools }) => { let fullText = '' const toolCallOfIndex: { [index: string]: { name: string, args: string } } = {} + const openai: OpenAI = newOpenAI({ providerName, settingsOfProvider }) const options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model: modelName, messages: messages, stream: true, - // tools: Object.keys(contextTools).map(name => toOpenAITool(name, contextTools[name as ContextToolName])), + tools: tools?.map(tool => toOpenAITool(tool)), } openai.chat.completions .create(options) .then(async response => { _setAborter(() => response.controller.abort()) + // when receive text for await (const chunk of response) { @@ -153,7 +155,7 @@ export const sendOpenAIChat: _InternalSendLLMChatMessageFnType = ({ messages, on onText({ newText, fullText }); } - onFinalMessage({ fullText }); + onFinalMessage({ fullText, tools: Object.keys(toolCallOfIndex).map(index => toolCallOfIndex[index]) }); }) // when error/fail - this catches errors of both .create() and .then(for await) .catch(error => { diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts index 86a77362..6ebdbdf6 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.ts @@ -8,7 +8,7 @@ import { IMetricsService } from '../../common/metricsService.js'; import { sendAnthropicChat } from './anthropic.js'; import { sendOllamaFIM, sendOllamaChat } from './ollama.js'; -import { sendOpenAIChat, sendOpenAIFIM } from './openai.js'; +import { sendOpenAIChat } from './openai.js'; import { sendGeminiChat } from './gemini.js'; import { sendGroqChat } from './groq.js'; import { sendMistralChat } from './mistral.js'; @@ -107,10 +107,10 @@ export const sendLLMMessage = ({ _fullTextSoFar = fullText } - const onFinalMessage: OnFinalMessage = ({ fullText }) => { + const onFinalMessage: OnFinalMessage = ({ fullText, tools }) => { if (_didAbort) return captureLLMEvent(`${loggingName} - Received Full Message`, { messageLength: fullText.length, duration: new Date().getMilliseconds() - submit_time.getMilliseconds() }) - onFinalMessage_({ fullText }) + onFinalMessage_({ fullText, tools }) } const onError: OnError = ({ message: error, fullError }) => { @@ -141,7 +141,7 @@ export const sendLLMMessage = ({ case 'openRouter': case 'deepseek': case 'openAICompatible': - if (messagesType === 'FIMMessage') sendOpenAIFIM({ messages: messages_, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName }); + if (messagesType === 'FIMMessage') onFinalMessage({ fullText: 'TODO - OpenAI FIM', tools: [] }) else /* */ sendOpenAIChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName, tools }); break; case 'ollama': @@ -149,19 +149,19 @@ export const sendLLMMessage = ({ else /* */ sendOllamaChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName, tools }); break; case 'anthropic': - if (messagesType === 'FIMMessage') onFinalMessage({ fullText: 'TODO - Anthropic FIM' }) + if (messagesType === 'FIMMessage') onFinalMessage({ fullText: 'TODO - Anthropic FIM', tools: [] }) else /* */ sendAnthropicChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName, tools }); break; case 'gemini': - if (messagesType === 'FIMMessage') onFinalMessage({ fullText: 'TODO - Gemini FIM' }) + if (messagesType === 'FIMMessage') onFinalMessage({ fullText: 'TODO - Gemini FIM', tools: [] }) else /* */ sendGeminiChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName, tools }); break; case 'groq': - if (messagesType === 'FIMMessage') onFinalMessage({ fullText: 'TODO - Groq FIM' }) + if (messagesType === 'FIMMessage') onFinalMessage({ fullText: 'TODO - Groq FIM', tools: [] }) else /* */ sendGroqChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName, tools }); break; case 'mistral': - if (messagesType === 'FIMMessage') onFinalMessage({ fullText: 'TODO - Mistral FIM' }) + if (messagesType === 'FIMMessage') onFinalMessage({ fullText: 'TODO - Mistral FIM', tools: [] }) else /* */ sendMistralChat({ messages: messagesArr, onText, onFinalMessage, onError, settingsOfProvider, modelName, _setAborter, providerName, tools }); break; default: diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessageChannel.ts b/src/vs/workbench/contrib/void/electron-main/llmMessageChannel.ts index 98725631..9db9a68f 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessageChannel.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessageChannel.ts @@ -100,7 +100,7 @@ export class LLMMessageChannel implements IServerChannel { const mainThreadParams: SendLLMMessageParams = { ...params, onText: ({ newText, fullText }) => { this._onText_llm.fire({ requestId, newText, fullText }); }, - onFinalMessage: ({ fullText }) => { this._onFinalMessage_llm.fire({ requestId, fullText }); }, + onFinalMessage: ({ fullText, tools }) => { this._onFinalMessage_llm.fire({ requestId, fullText, tools }); }, onError: ({ message: error, fullError }) => { console.log('sendLLM: firing err'); this._onError_llm.fire({ requestId, message: error, fullError }); }, abortRef: this._abortRefOfRequestId_llm[requestId], }