From 6f693c4d0a6f86c32965cec10ace19921d81ee96 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 7 Apr 2025 23:05:03 -0700 Subject: [PATCH] progress --- .../contrib/void/browser/chatThreadService.ts | 14 ++++---- .../react/src/sidebar-tsx/SidebarChat.tsx | 10 +++--- .../src/sidebar-tsx/SidebarThreadSelector.tsx | 2 +- .../contrib/void/browser/toolsService.ts | 3 +- .../void/common/chatThreadServiceTypes.ts | 3 +- .../contrib/void/common/prompt/prompts.ts | 14 ++++++-- .../void/common/sendLLMMessageTypes.ts | 8 ++--- .../contrib/void/common/toolsServiceTypes.ts | 11 +----- .../llmMessage/extractGrammar.ts | 36 ++++++++++++------- .../llmMessage/sendLLMMessage.impl.ts | 13 +++---- 10 files changed, 65 insertions(+), 49 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/chatThreadService.ts b/src/vs/workbench/contrib/void/browser/chatThreadService.ts index 6a78483b..5d79d68a 100644 --- a/src/vs/workbench/contrib/void/browser/chatThreadService.ts +++ b/src/vs/workbench/contrib/void/browser/chatThreadService.ts @@ -11,13 +11,13 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo import { URI } from '../../../../base/common/uri.js'; import { Emitter, Event } from '../../../../base/common/event.js'; import { ILLMMessageService } from '../common/sendLLMMessageService.js'; -import { chat_userMessageContent, chat_systemMessage, } from '../common/prompt/prompts.js'; -import { getErrorMessage, LLMChatMessage, ParsedToolCallObj, ParsedToolParamsObj } from '../common/sendLLMMessageTypes.js'; +import { chat_userMessageContent, chat_systemMessage, ToolName, } from '../common/prompt/prompts.js'; +import { getErrorMessage, LLMChatMessage, RawToolCallObj, ParsedToolParamsObj } from '../common/sendLLMMessageTypes.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; import { generateUuid } from '../../../../base/common/uuid.js'; import { FeatureName, ModelSelection, ModelSelectionOptions } from '../common/voidSettingsTypes.js'; import { IVoidSettingsService } from '../common/voidSettingsService.js'; -import { ToolName, ToolCallParams, ToolResultType, toolNamesThatRequireApproval } from '../common/toolsServiceTypes.js'; +import { ToolCallParams, ToolResultType, toolNamesThatRequireApproval } from '../common/toolsServiceTypes.js'; import { IToolsService } from './toolsService.js'; import { CancellationToken } from '../../../../base/common/cancellation.js'; import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js'; @@ -148,7 +148,7 @@ export type ThreadStreamState = { streamingToken?: string; messageSoFar?: string; reasoningSoFar?: string; - toolCallSoFar?: ParsedToolCallObj; + toolCallSoFar?: RawToolCallObj; } } @@ -700,8 +700,8 @@ class ChatThreadService extends Disposable implements IChatThreadService { isRunningWhenEnd = undefined nMessagesSent += 1 - let resMessageIsDonePromise: (toolCall?: ParsedToolCallObj | undefined) => void // resolves when user approves this tool use (or if tool doesn't require approval) - const messageIsDonePromise = new Promise((res, rej) => { resMessageIsDonePromise = res }) + let resMessageIsDonePromise: (toolCall?: RawToolCallObj | undefined) => void // resolves when user approves this tool use (or if tool doesn't require approval) + const messageIsDonePromise = new Promise((res, rej) => { resMessageIsDonePromise = res }) // send llm message this._setStreamState(threadId, { isRunning: 'LLM' }, 'merge') @@ -752,7 +752,7 @@ class ChatThreadService extends Disposable implements IChatThreadService { this._setStreamState(threadId, { streamingToken: undefined }, 'merge') // streaming message is done // call tool if there is one - const tool: ParsedToolCallObj | undefined = toolCall + const tool: RawToolCallObj | undefined = toolCall if (tool) { const { awaitingUserApproval, interrupted } = await handleToolCall(tool.name, { preapproved: false, unvalidatedToolParams: tool.rawParams }) diff --git a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx index f36435d7..28c59250 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/sidebar-tsx/SidebarChat.tsx @@ -23,9 +23,10 @@ import { WarningBox } from '../void-settings-tsx/WarningBox.js'; import { getModelCapabilities, getIsReasoningEnabledState } from '../../../../common/modelCapabilities.js'; import { AlertTriangle, Ban, ChevronRight, Dot, Pencil, Undo, Undo2, X } from 'lucide-react'; import { ChatMessage, CheckpointEntry, StagingSelectionItem, ToolMessage } from '../../../../common/chatThreadServiceTypes.js'; -import { ToolCallParams, ToolName, toolNames, ToolNameWithApproval } from '../../../../common/toolsServiceTypes.js'; +import { ToolCallParams } from '../../../../common/toolsServiceTypes.js'; import { ApplyButtonsHTML, CopyButton, JumpToFileButton, JumpToTerminalButton, StatusIndicatorHTML, useApplyButtonState } from '../markdown/ApplyBlockHoverButtons.js'; import { IsRunningType } from '../../../chatThreadService.js'; +import { ToolName, toolNames } from '../../../../common/prompt/prompts.js'; @@ -2007,9 +2008,8 @@ export const SidebarChat = () => { const messageSoFar = currThreadStreamState?.messageSoFar const reasoningSoFar = currThreadStreamState?.reasoningSoFar - const toolNameSoFar = currThreadStreamState?.toolNameSoFar - const toolParamsSoFar = currThreadStreamState?.toolParamsSoFar - const toolIsGenerating = !!toolNameSoFar && toolNameSoFar === 'edit_file' // show loading for slow tools (right now just edit) + const toolCallSoFar = currThreadStreamState?.toolCallSoFar + const toolIsGenerating = !!toolCallSoFar && toolCallSoFar.name === 'edit_file' // show loading for slow tools (right now just edit) // ----- SIDEBAR CHAT state (local) ----- @@ -2101,7 +2101,7 @@ export const SidebarChat = () => { /> : null - const generatingToolTitle = toolNameSoFar && toolNames.includes(toolNameSoFar as ToolName) ? titleOfToolName[toolNameSoFar as ToolName]?.proposed : toolNameSoFar + const generatingToolTitle = toolCallSoFar && toolNames.includes(toolCallSoFar.name as ToolName) ? titleOfToolName[toolCallSoFar.name as ToolName]?.proposed : toolCallSoFar?.name const messagesHTML = { // secondMsg = truncate(pastThread.messages[secondMsgIdx].displayContent ?? ''); // } - const numMessages = pastThread.messages.filter((msg) => msg.role !== 'tool_request').length; + const numMessages = pastThread.messages.filter((msg) => msg.role === 'assistant' || msg.role === 'user').length; return (
  • diff --git a/src/vs/workbench/contrib/void/browser/toolsService.ts b/src/vs/workbench/contrib/void/browser/toolsService.ts index 2a50aeca..d7823531 100644 --- a/src/vs/workbench/contrib/void/browser/toolsService.ts +++ b/src/vs/workbench/contrib/void/browser/toolsService.ts @@ -8,7 +8,7 @@ import { QueryBuilder } from '../../../services/search/common/queryBuilder.js' import { ISearchService } from '../../../services/search/common/search.js' import { IEditCodeService } from './editCodeServiceInterface.js' import { ITerminalToolService } from './terminalToolService.js' -import { ToolCallParams, ToolName, ToolResultType } from '../common/toolsServiceTypes.js' +import { ToolCallParams, ToolResultType } from '../common/toolsServiceTypes.js' import { IVoidModelService } from '../common/voidModelService.js' import { EndOfLinePreference } from '../../../../editor/common/model.js' import { basename } from '../../../../base/common/path.js' @@ -17,6 +17,7 @@ import { computeDirectoryTree1Deep, IDirectoryStrService, stringifyDirectoryTree import { IMarkerService } from '../../../../platform/markers/common/markers.js' import { timeout } from '../../../../base/common/async.js' import { ParsedToolParamsObj } from '../common/sendLLMMessageTypes.js' +import { ToolName } from '../common/prompt/prompts.js' // tool use for AI diff --git a/src/vs/workbench/contrib/void/common/chatThreadServiceTypes.ts b/src/vs/workbench/contrib/void/common/chatThreadServiceTypes.ts index 7cf2ec5d..9a358c55 100644 --- a/src/vs/workbench/contrib/void/common/chatThreadServiceTypes.ts +++ b/src/vs/workbench/contrib/void/common/chatThreadServiceTypes.ts @@ -5,8 +5,9 @@ import { URI } from '../../../../base/common/uri.js'; import { VoidFileSnapshot } from './editCodeServiceTypes.js'; +import { ToolName } from './prompt/prompts.js'; import { AnthropicReasoning } from './sendLLMMessageTypes.js'; -import { ToolName, ToolCallParams, ToolResultType } from './toolsServiceTypes.js'; +import { ToolCallParams, ToolResultType } from './toolsServiceTypes.js'; export type ToolMessage = { role: 'tool'; diff --git a/src/vs/workbench/contrib/void/common/prompt/prompts.ts b/src/vs/workbench/contrib/void/common/prompt/prompts.ts index 218aee01..61ee6524 100644 --- a/src/vs/workbench/contrib/void/common/prompt/prompts.ts +++ b/src/vs/workbench/contrib/void/common/prompt/prompts.ts @@ -6,7 +6,7 @@ import { os } from '../helpers/systemInfo.js'; import { StagingSelectionItem } from '../chatThreadServiceTypes.js'; import { ChatMode } from '../voidSettingsTypes.js'; -import { ToolName, toolNamesThatRequireApproval } from '../toolsServiceTypes.js'; +import { toolNamesThatRequireApproval } from '../toolsServiceTypes.js'; import { IVoidModelService } from '../voidModelService.js'; import { EndOfLinePreference } from '../../../../../editor/common/model.js'; @@ -153,7 +153,7 @@ Here's an example of a good description:\n${editToolDescriptionExample}.` name: 'run_terminal_command', description: `Executes a terminal command.`, params: { - command: { description: 'The terminal command to execute. Typically you should pipe to cat to avoid pagination.' }, + command: { description: 'The terminal command to execute.' }, waitForCompletion: { description: `Whether or not to await the command to complete and get the final result. Default is true. Make this value false when you want a command to run indefinitely without waiting for it.` }, terminalId: { description: 'Optional (value must be an integer >= 1, or empty which will go with the default). This is the ID of the terminal instance to execute the command in. The primary purpose of this is to start a new terminal for background processes or tasks that run indefinitely (e.g. if you want to run a server locally). Fails gracefully if a terminal ID does not exist, by creating a new terminal instance. Defaults to the preferred terminal ID.' }, }, @@ -166,6 +166,16 @@ Here's an example of a good description:\n${editToolDescriptionExample}.` } satisfies { [name: string]: InternalToolInfo } +export type ToolName = keyof typeof voidTools +export const toolNames = Object.keys(voidTools) as ToolName[] + +const toolNamesSet = new Set(toolNames) + +export const isAToolName = (toolName: string): toolName is ToolName => { + const isAToolName = toolNamesSet.has(toolName) + return isAToolName +} + export const availableTools = (chatMode: ChatMode) => { const toolNames: ToolName[] | undefined = chatMode === 'normal' ? undefined : chatMode === 'gather' ? (Object.keys(voidTools) as ToolName[]).filter(toolName => !toolNamesThatRequireApproval.has(toolName)) diff --git a/src/vs/workbench/contrib/void/common/sendLLMMessageTypes.ts b/src/vs/workbench/contrib/void/common/sendLLMMessageTypes.ts index 881e6201..49fe38fc 100644 --- a/src/vs/workbench/contrib/void/common/sendLLMMessageTypes.ts +++ b/src/vs/workbench/contrib/void/common/sendLLMMessageTypes.ts @@ -3,7 +3,7 @@ * Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information. *--------------------------------------------------------------------------------------*/ -import { ToolName } from './toolsServiceTypes.js' +import { ToolName } from './prompt/prompts.js' import { ChatMode, ModelSelection, ModelSelectionOptions, ProviderName, SettingsOfProvider } from './voidSettingsTypes.js' @@ -43,7 +43,7 @@ export type LLMChatMessage = { export type ParsedToolParamsObj = { [paramName: string]: string; } -export type ParsedToolCallObj = { +export type RawToolCallObj = { name: ToolName; rawParams: ParsedToolParamsObj; doneParams: string[]; @@ -53,8 +53,8 @@ export type ParsedToolCallObj = { export type AnthropicReasoning = ({ type: 'thinking'; thinking: any; signature: string; } | { type: 'redacted_thinking', data: any }) -export type OnText = (p: { fullText: string; fullReasoning: string; toolCall?: ParsedToolCallObj }) => void -export type OnFinalMessage = (p: { fullText: string; fullReasoning: string; toolCall?: ParsedToolCallObj; anthropicReasoning: AnthropicReasoning[] | null }) => void // id is tool_use_id +export type OnText = (p: { fullText: string; fullReasoning: string; toolCall?: RawToolCallObj }) => void +export type OnFinalMessage = (p: { fullText: string; fullReasoning: string; toolCall?: RawToolCallObj; anthropicReasoning: AnthropicReasoning[] | null }) => void // id is tool_use_id export type OnError = (p: { message: string; fullError: Error | null }) => void export type OnAbort = () => void export type AbortRef = { current: (() => void) | null } diff --git a/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts b/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts index ce0c4b68..383dab54 100644 --- a/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts +++ b/src/vs/workbench/contrib/void/common/toolsServiceTypes.ts @@ -1,5 +1,5 @@ import { URI } from '../../../../base/common/uri.js' -import { voidTools } from './prompt/prompts.js'; +import { ToolName } from './prompt/prompts.js'; @@ -14,15 +14,6 @@ export type ShallowDirectoryItem = { } -export type ToolName = keyof typeof voidTools -export const toolNames = Object.keys(voidTools) as ToolName[] - -const toolNamesSet = new Set(toolNames) -export const isAToolName = (toolName: string): toolName is ToolName => { - const isAToolName = toolNamesSet.has(toolName) - return isAToolName -} - const toolNamesWithApproval = ['create_file_or_folder', 'delete_file_or_folder', 'edit_file', 'run_terminal_command'] as const satisfies readonly ToolName[] export type ToolNameWithApproval = typeof toolNamesWithApproval[number] diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/extractGrammar.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/extractGrammar.ts index 35a1ed2f..33ecc5b2 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/extractGrammar.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/extractGrammar.ts @@ -1,8 +1,8 @@ import { endsWithAnyPrefixOf } from '../../common/helpers/extractCodeFromResult.js' -import { InternalToolInfo } from '../../common/prompt/prompts.js' -import { OnText, ParsedToolCallObj } from '../../common/sendLLMMessageTypes.js' +import { availableTools, InternalToolInfo, ToolName } from '../../common/prompt/prompts.js' +import { OnText, RawToolCallObj } from '../../common/sendLLMMessageTypes.js' +import { ChatMode } from '../../common/voidSettingsTypes.js' import sax from 'sax' -import { ToolName } from '../../common/toolsServiceTypes.js' // =============== reasoning =============== @@ -123,22 +123,25 @@ type ToolsState = { } | { level: 'tool', toolName: string, - currentToolCall: ParsedToolCallObj, + currentToolCall: RawToolCallObj, } | { level: 'param', toolName: string, paramName: string, - currentToolCall: ParsedToolCallObj, + currentToolCall: RawToolCallObj, } -export const extractToolsOnTextWrapper = (onText: OnText, availableTools: InternalToolInfo[]) => { +export const extractToolsOnTextWrapper = (onText: OnText, chatMode: ChatMode) => { + const tools = availableTools(chatMode) + if (!tools) return onText + const toolOfToolName: { [toolName: string]: InternalToolInfo | undefined } = {} - for (const t of availableTools) { toolOfToolName[t.name] = t } + for (const t of tools) { toolOfToolName[t.name] = t } // detect , etc let fullText = ''; let trueFullText = '' - const currentToolCalls: ParsedToolCallObj[] = []; // the answer + const currentToolCalls: RawToolCallObj[] = []; // the answer let state: ToolsState = { level: 'normal' } @@ -146,7 +149,9 @@ export const extractToolsOnTextWrapper = (onText: OnText, availableTools: Intern const getRawNewText = () => { return trueFullText.substring(parser.startTagPosition, parser.position + 1) } - const parser = sax.parser(false); + const parser = sax.parser(false, { + lowercase: true, + }); // when see open tag @@ -186,7 +191,7 @@ export const extractToolsOnTextWrapper = (onText: OnText, availableTools: Intern }; parser.ontext = (text) => { - console.log('TEXT!', text) + console.log('TEXT!', JSON.stringify(text)) if (state.level === 'normal') { fullText += text } @@ -227,16 +232,23 @@ export const extractToolsOnTextWrapper = (onText: OnText, availableTools: Intern currentToolCall: state.currentToolCall, } } + else { + fullText += rawNewText + } } }; + let prevFullTextLen = 0 const newOnText: OnText = (params) => { - const newText = params.fullText.substring(fullText.length); - console.log('newText', state.level, newText) + const newText = params.fullText.substring(prevFullTextLen) + prevFullTextLen = params.fullText.length trueFullText = params.fullText + + console.log('newText', newText.length) parser.write(newText) + console.log('calling ontext...') onText({ ...params, fullText, diff --git a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts index b0d5544c..c8af7ff4 100644 --- a/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts +++ b/src/vs/workbench/contrib/void/electron-main/llmMessage/sendLLMMessage.impl.ts @@ -12,7 +12,6 @@ import { ChatMode, defaultProviderSettings, displayInfoOfProviderName, ModelSele import { prepareFIMMessage, prepareMessages } from './preprocessLLMMessages.js'; import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities } from '../../common/modelCapabilities.js'; import { extractReasoningOnFinalMessage, extractReasoningOnTextWrapper, extractToolsOnTextWrapper } from './extractGrammar.js'; -import { availableTools } from '../../common/prompt/prompts.js'; type InternalCommonMessageParams = { @@ -162,10 +161,7 @@ const _sendOpenAICompatibleChat = ({ messages: messages_, onText, onFinalMessage // manually parse out tool results if (chatMode) { - const tools = availableTools(chatMode) - if (tools) { - onText = extractToolsOnTextWrapper(onText, tools) - } + onText = extractToolsOnTextWrapper(onText, chatMode) } let fullReasoningSoFar = '' @@ -250,7 +246,7 @@ const _openaiCompatibleList = async ({ onSuccess: onSuccess_, onError: onError_, // ------------ ANTHROPIC ------------ -const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, aiInstructions }: SendChatParams_Internal) => { +const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalMessage, onError, settingsOfProvider, modelSelectionOptions, modelName: modelName_, _setAborter, aiInstructions, chatMode }: SendChatParams_Internal) => { const { modelName, supportsSystemMessage, @@ -284,6 +280,11 @@ const sendAnthropicChat = ({ messages: messages_, providerName, onText, onFinalM ...includeInPayload, }) + // manually parse out tool results + if (chatMode) { + onText = extractToolsOnTextWrapper(onText, chatMode) + } + // when receive text let fullText = '' let fullReasoning = ''