mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
progress
This commit is contained in:
parent
1c5adb96d3
commit
6f693c4d0a
10 changed files with 65 additions and 49 deletions
|
|
@ -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<ParsedToolCallObj | undefined>((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<RawToolCallObj | undefined>((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 })
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = <ScrollToBottomContainer
|
||||
key={'messages' + chatThreadsState.currentThreadId} // force rerender on all children if id changes
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ export const SidebarThreadSelector = () => {
|
|||
// 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 (
|
||||
<li key={pastThread.id}>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<T extends ToolName> = {
|
||||
role: 'tool';
|
||||
|
|
|
|||
|
|
@ -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<string>(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))
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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<string>(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]
|
||||
|
|
|
|||
|
|
@ -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 <availableTools[0]></availableTools[0]>, 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 <tagName>
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 = ''
|
||||
|
|
|
|||
Loading…
Reference in a new issue