mirror of
https://github.com/voideditor/void
synced 2026-05-23 09:28:23 +00:00
tool progress
This commit is contained in:
parent
5747904b85
commit
43b6dba7a3
14 changed files with 338 additions and 144 deletions
|
|
@ -11,12 +11,12 @@ 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, ToolName, } from '../common/prompt/prompts.js';
|
||||
import { chat_userMessageContent } from '../common/prompt/prompts.js';
|
||||
import { AnthropicReasoning, getErrorMessage, RawToolCallObj, RawToolParamsObj } from '../common/sendLLMMessageTypes.js';
|
||||
import { generateUuid } from '../../../../base/common/uuid.js';
|
||||
import { FeatureName, ModelSelection, ModelSelectionOptions } from '../common/voidSettingsTypes.js';
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||
import { approvalTypeOfToolName, BuiltinToolCallParams, BuiltinToolResultType } from '../common/toolsServiceTypes.js';
|
||||
import { approvalTypeOfBuiltinToolName, BuiltinToolCallParams, isABuiltinToolName, ToolCallParams, ToolName, ToolResult } from '../common/toolsServiceTypes.js';
|
||||
import { IToolsService } from './toolsService.js';
|
||||
import { CancellationToken } from '../../../../base/common/cancellation.js';
|
||||
import { ILanguageFeaturesService } from '../../../../editor/common/services/languageFeatures.js';
|
||||
|
|
@ -37,6 +37,8 @@ import { deepClone } from '../../../../base/common/objects.js';
|
|||
import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js';
|
||||
import { IDirectoryStrService } from '../common/directoryStrService.js';
|
||||
import { IFileService } from '../../../../platform/files/common/files.js';
|
||||
import { IMCPService } from '../common/mcpService.js';
|
||||
import { RawMCPToolCall } from '../common/mcpServiceTypes.js';
|
||||
|
||||
|
||||
// related to retrying when LLM message has error
|
||||
|
|
@ -181,7 +183,7 @@ export type ThreadStreamState = {
|
|||
llmInfo?: undefined;
|
||||
toolInfo: {
|
||||
toolName: ToolName;
|
||||
toolParams: BuiltinToolCallParams[ToolName];
|
||||
toolParams: ToolCallParams<ToolName>;
|
||||
id: string;
|
||||
content: string;
|
||||
rawParams: RawToolParamsObj;
|
||||
|
|
@ -323,6 +325,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
|
||||
@IDirectoryStrService private readonly _directoryStringService: IDirectoryStrService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@IMCPService private readonly _mcpService: IMCPService,
|
||||
) {
|
||||
super()
|
||||
this.state = { allThreads: {}, currentThreadId: null as unknown as string } // default state
|
||||
|
|
@ -532,7 +535,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
const lastMsg = thread.messages[thread.messages.length - 1]
|
||||
|
||||
let params: BuiltinToolCallParams[ToolName]
|
||||
let params: ToolCallParams<ToolName>
|
||||
if (lastMsg.role === 'tool' && lastMsg.type !== 'invalid_params') {
|
||||
params = lastMsg.params
|
||||
}
|
||||
|
|
@ -597,20 +600,30 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
threadId: string,
|
||||
toolName: ToolName,
|
||||
toolId: string,
|
||||
opts: { preapproved: true, unvalidatedToolParams: RawToolParamsObj, validatedParams: BuiltinToolCallParams[ToolName] } | { preapproved: false, unvalidatedToolParams: RawToolParamsObj },
|
||||
opts: { preapproved: true, unvalidatedToolParams: RawToolParamsObj, validatedParams: ToolCallParams<ToolName> } | { preapproved: false, unvalidatedToolParams: RawToolParamsObj },
|
||||
): Promise<{ awaitingUserApproval?: boolean, interrupted?: boolean }> => {
|
||||
|
||||
// compute these below
|
||||
let toolParams: BuiltinToolCallParams[ToolName]
|
||||
let toolResult: Awaited<BuiltinToolResultType[typeof toolName]>
|
||||
let toolParams: ToolCallParams<ToolName>
|
||||
let toolResult: ToolResult<ToolName>
|
||||
let toolResultStr: string
|
||||
|
||||
// Check if it's a built-in tool
|
||||
const isBuiltInTool = isABuiltinToolName(toolName)
|
||||
|
||||
|
||||
if (!opts.preapproved) { // skip this if pre-approved
|
||||
// 1. validate tool params
|
||||
try {
|
||||
const params = this._toolsService.validateParams[toolName](opts.unvalidatedToolParams)
|
||||
toolParams = params
|
||||
} catch (error) {
|
||||
if (isBuiltInTool) {
|
||||
const params = this._toolsService.validateParams[toolName](opts.unvalidatedToolParams)
|
||||
toolParams = params
|
||||
}
|
||||
else {
|
||||
toolParams = opts.unvalidatedToolParams
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
const errorMessage = getErrorMessage(error)
|
||||
this._addMessageToThread(threadId, { role: 'tool', type: 'invalid_params', rawParams: opts.unvalidatedToolParams, result: null, name: toolName, content: errorMessage, id: toolId, })
|
||||
return {}
|
||||
|
|
@ -621,8 +634,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
// 2. if tool requires approval, break from the loop, awaiting approval
|
||||
|
||||
|
||||
const approvalType = approvalTypeOfToolName[toolName]
|
||||
const approvalType = isBuiltInTool ? approvalTypeOfBuiltinToolName[toolName] : 'mcp-tools'
|
||||
if (approvalType) {
|
||||
const autoApprove = this._settingsService.state.globalSettings.autoApprove[approvalType]
|
||||
// add a tool_request because we use it for UI if a tool is loading (this should be improved in the future)
|
||||
|
|
@ -638,30 +650,6 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
|
||||
|
||||
// TOOL_TODO!!!!!!!!! call the builtin versus the MCP tool here (finish filling in the comment below and replace it out with the tool call and stringify functions further below)
|
||||
// const isBuiltInTool = (toolNames as string[]).includes(toolName)
|
||||
// const callToolFn = (toolName: string, toolParams: BuiltinToolCallParams[ToolName]) => {
|
||||
// if (isBuiltInTool) {
|
||||
|
||||
|
||||
// }
|
||||
// else {
|
||||
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// const stringifyToolFn = (toolName: string, toolResult: Awaited<BuiltinToolResultType[ToolName]>) => {
|
||||
// if (isBuiltInTool) {
|
||||
|
||||
|
||||
// }
|
||||
// else {
|
||||
// if (result.event === 'error' || result.event === 'text') {
|
||||
// return result.text;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
|
@ -679,11 +667,26 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
// set stream state
|
||||
this._setStreamState(threadId, { isRunning: 'tool', interrupt: interruptorPromise, toolInfo: { toolName, toolParams, id: toolId, content: 'interrupted...', rawParams: opts.unvalidatedToolParams } })
|
||||
|
||||
const { result, interruptTool } = await this._toolsService.callTool[toolName](toolParams as any)
|
||||
const interruptor = () => { interrupted = true; interruptTool?.() }
|
||||
resolveInterruptor(interruptor)
|
||||
if (isBuiltInTool) {
|
||||
const { result, interruptTool } = await this._toolsService.callTool[toolName](toolParams as any)
|
||||
const interruptor = () => { interrupted = true; interruptTool?.() }
|
||||
resolveInterruptor(interruptor)
|
||||
|
||||
toolResult = await result
|
||||
toolResult = await result
|
||||
}
|
||||
else {
|
||||
const mcpTools = this._mcpService.getMCPTools()
|
||||
const mcpTool = mcpTools[toolName]
|
||||
if (!mcpTool) { throw new Error(`MCP tool ${toolName} not found`) }
|
||||
|
||||
resolveInterruptor(() => { })
|
||||
|
||||
toolResult = (await this._mcpService.callMCPTool({
|
||||
serverName: mcpTool.mcpServerName ?? 'unknown_mcp_server',
|
||||
toolName: toolName,
|
||||
params: toolParams
|
||||
})).result
|
||||
}
|
||||
|
||||
if (interrupted) { return { interrupted: true } } // the tool result is added where we interrupt, not here
|
||||
}
|
||||
|
|
@ -698,7 +701,26 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
|
||||
// 4. stringify the result to give to the LLM
|
||||
try {
|
||||
toolResultStr = this._toolsService.stringOfResult[toolName](toolParams as any, toolResult as any)
|
||||
if (isBuiltInTool) {
|
||||
toolResultStr = this._toolsService.stringOfResult[toolName](toolParams as any, toolResult as any)
|
||||
}
|
||||
// For MCP tools, handle the result based on its type
|
||||
else {
|
||||
const toolResult_ = toolResult as RawMCPToolCall
|
||||
if (toolResult_.event === 'text') {
|
||||
toolResultStr = toolResult_.text
|
||||
} else if (toolResult_.event === 'error') {
|
||||
toolResultStr = `Error: ${toolResult_.text}`
|
||||
} else if (toolResult_.event === 'image') {
|
||||
toolResultStr = `[Image: ${toolResult_.image.mimeType}]`
|
||||
} else if (toolResult_.event === 'audio') {
|
||||
toolResultStr = `[Audio content]`
|
||||
} else if (toolResult_.event === 'resource') {
|
||||
toolResultStr = `[Resource content]`
|
||||
} else {
|
||||
toolResultStr = JSON.stringify(toolResult)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = this.toolErrMsgs.errWhenStringifying(error)
|
||||
this._updateLatestTool(threadId, { role: 'tool', type: 'tool_error', params: toolParams, result: errorMessage, name: toolName, content: errorMessage, id: toolId, rawParams: opts.unvalidatedToolParams })
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { IWorkspaceContextService } from '../../../../platform/workspace/common/
|
|||
import { IEditorService } from '../../../services/editor/common/editorService.js';
|
||||
import { ChatMessage } from '../common/chatThreadServiceTypes.js';
|
||||
import { getIsReasoningEnabledState, getReservedOutputTokenSpace, getModelCapabilities } from '../common/modelCapabilities.js';
|
||||
import { reParsedToolXMLString, chat_systemMessage, ToolName } from '../common/prompt/prompts.js';
|
||||
import { reParsedToolXMLString, chat_systemMessage } from '../common/prompt/prompts.js';
|
||||
import { AnthropicLLMChatMessage, AnthropicReasoning, GeminiLLMChatMessage, LLMChatMessage, LLMFIMMessage, OpenAILLMChatMessage, RawToolParamsObj } from '../common/sendLLMMessageTypes.js';
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js';
|
||||
import { ChatMode, FeatureName, ModelSelection, ProviderName } from '../common/voidSettingsTypes.js';
|
||||
|
|
@ -16,6 +16,7 @@ import { ITerminalToolService } from './terminalToolService.js';
|
|||
import { IVoidModelService } from '../common/voidModelService.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { EndOfLinePreference } from '../../../../editor/common/model.js';
|
||||
import { ToolName } from '../common/toolsServiceTypes.js';
|
||||
|
||||
export const EMPTY_MESSAGE = '(empty message)'
|
||||
|
||||
|
|
@ -455,8 +456,8 @@ const prepareGeminiMessages = (messages: AnthropicLLMChatMessage[]) => {
|
|||
return { text: c.text }
|
||||
}
|
||||
else if (c.type === 'tool_use') {
|
||||
latestToolName = c.name as ToolName
|
||||
return { functionCall: { id: c.id, name: c.name as ToolName, args: c.input } }
|
||||
latestToolName = c.name
|
||||
return { functionCall: { id: c.id, name: c.name, args: c.input } }
|
||||
}
|
||||
else return null
|
||||
}).filter(m => !!m)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { ChatMarkdownRender, ChatMessageLocation, getApplyBoxId } from '../markd
|
|||
import { URI } from '../../../../../../../base/common/uri.js';
|
||||
import { IDisposable } from '../../../../../../../base/common/lifecycle.js';
|
||||
import { ErrorDisplay } from './ErrorDisplay.js';
|
||||
import { BlockCode, TextAreaFns, VoidCustomDropdownBox, VoidInputBox2, VoidSlider, VoidSwitch } from '../util/inputs.js';
|
||||
import { BlockCode, TextAreaFns, VoidCustomDropdownBox, VoidInputBox2, VoidSlider, VoidSwitch, VoidDiffEditor } from '../util/inputs.js';
|
||||
import { ModelDropdown, } from '../void-settings-tsx/ModelDropdown.js';
|
||||
import { PastThreadsList } from './SidebarThreadSelector.js';
|
||||
import { VOID_CTRL_L_ACTION_ID } from '../../../actionIDs.js';
|
||||
|
|
@ -24,11 +24,11 @@ import { WarningBox } from '../void-settings-tsx/WarningBox.js';
|
|||
import { getModelCapabilities, getIsReasoningEnabledState } from '../../../../common/modelCapabilities.js';
|
||||
import { AlertTriangle, File, Ban, Check, ChevronRight, Dot, FileIcon, Pencil, Undo, Undo2, X, Flag, Copy as CopyIcon, Info, CirclePlus, Ellipsis, CircleEllipsis, Folder, ALargeSmall, TypeOutline, Text } from 'lucide-react';
|
||||
import { ChatMessage, CheckpointEntry, StagingSelectionItem, ToolMessage } from '../../../../common/chatThreadServiceTypes.js';
|
||||
import { approvalTypeOfToolName, BuiltinToolCallParams, LintErrorItem, ToolApprovalType, toolApprovalTypes } from '../../../../common/toolsServiceTypes.js';
|
||||
import { approvalTypeOfBuiltinToolName, BuiltinToolCallParams, BuiltinToolName, builtinToolNames, isABuiltinToolName, ToolName, LintErrorItem, ToolApprovalType, toolApprovalTypes } from '../../../../common/toolsServiceTypes.js';
|
||||
import { CopyButton, EditToolAcceptRejectButtonsHTML, IconShell1, JumpToFileButton, JumpToTerminalButton, StatusIndicator, StatusIndicatorForApplyButton, useApplyStreamState, useEditToolStreamState } from '../markdown/ApplyBlockHoverButtons.js';
|
||||
import { IsRunningType } from '../../../chatThreadService.js';
|
||||
import { acceptAllBg, acceptBorder, buttonFontSize, buttonTextColor, rejectAllBg, rejectBg, rejectBorder } from '../../../../common/helpers/colors.js';
|
||||
import { MAX_FILE_CHARS_PAGE, MAX_TERMINAL_INACTIVE_TIME, ToolName, toolNames } from '../../../../common/prompt/prompts.js';
|
||||
import { MAX_FILE_CHARS_PAGE, MAX_TERMINAL_INACTIVE_TIME } from '../../../../common/prompt/prompts.js';
|
||||
import { RawToolCallObj } from '../../../../common/sendLLMMessageTypes.js';
|
||||
import ErrorBoundary from './ErrorBoundary.js';
|
||||
import { ToolApprovalTypeSwitch } from '../void-settings-tsx/Settings.js';
|
||||
|
|
@ -36,6 +36,7 @@ import { ToolApprovalTypeSwitch } from '../void-settings-tsx/Settings.js';
|
|||
import { persistentTerminalNameOfId } from '../../../terminalToolService.js';
|
||||
|
||||
|
||||
|
||||
export const IconX = ({ size, className = '', ...props }: { size: number, className?: string } & React.SVGProps<SVGSVGElement>) => {
|
||||
return (
|
||||
<svg
|
||||
|
|
@ -907,11 +908,14 @@ const EditTool = ({ toolMessage, threadId, messageIdx, content }: Parameters<Res
|
|||
const desc1OnClick = () => voidOpenFileFn(params.uri, accessor)
|
||||
const componentParams: ToolHeaderParams = { title, desc1, desc1OnClick, desc1Info, isError, icon, isRejected, }
|
||||
|
||||
|
||||
const editToolType = toolMessage.name === 'edit_file' ? 'diff' : 'rewrite'
|
||||
if (toolMessage.type === 'running_now' || toolMessage.type === 'tool_request') {
|
||||
componentParams.children = <ToolChildrenWrapper className='bg-void-bg-3'>
|
||||
<EditToolChildren
|
||||
uri={params.uri}
|
||||
code={content}
|
||||
type={editToolType}
|
||||
/>
|
||||
</ToolChildrenWrapper>
|
||||
// JumpToFileButton removed in favor of FileLinkText
|
||||
|
|
@ -936,6 +940,7 @@ const EditTool = ({ toolMessage, threadId, messageIdx, content }: Parameters<Res
|
|||
<EditToolChildren
|
||||
uri={params.uri}
|
||||
code={content}
|
||||
type={editToolType}
|
||||
/>
|
||||
</ToolChildrenWrapper>
|
||||
|
||||
|
|
@ -1388,7 +1393,7 @@ const loadingTitleWrapper = (item: React.ReactNode): React.ReactNode => {
|
|||
</span>
|
||||
}
|
||||
|
||||
const titleOfToolName = {
|
||||
const titleOfBuiltinToolName = {
|
||||
'read_file': { done: 'Read file', proposed: 'Read file', running: loadingTitleWrapper('Reading file') },
|
||||
'ls_dir': { done: 'Inspected folder', proposed: 'Inspect folder', running: loadingTitleWrapper('Inspecting folder') },
|
||||
'get_dir_tree': { done: 'Inspected folder tree', proposed: 'Inspect folder tree', running: loadingTitleWrapper('Inspecting folder tree') },
|
||||
|
|
@ -1406,21 +1411,21 @@ const titleOfToolName = {
|
|||
|
||||
'read_lint_errors': { done: `Read lint errors`, proposed: 'Read lint errors', running: loadingTitleWrapper('Reading lint errors') },
|
||||
'search_in_file': { done: 'Searched in file', proposed: 'Search in file', running: loadingTitleWrapper('Searching in file') },
|
||||
} as const satisfies Record<ToolName, { done: any, proposed: any, running: any }>
|
||||
} as const satisfies Record<BuiltinToolName, { done: any, proposed: any, running: any }>
|
||||
|
||||
|
||||
const getTitle = (toolMessage: Pick<ChatMessage & { role: 'tool' }, 'name' | 'type'>): React.ReactNode => {
|
||||
const t = toolMessage
|
||||
if (!toolNames.includes(t.name as ToolName)) return t.name // good measure
|
||||
if (!builtinToolNames.includes(t.name as BuiltinToolName)) return t.name // good measure
|
||||
|
||||
const toolName = t.name as ToolName
|
||||
if (t.type === 'success') return titleOfToolName[toolName].done
|
||||
if (t.type === 'running_now') return titleOfToolName[toolName].running
|
||||
return titleOfToolName[toolName].proposed
|
||||
const toolName = t.name as BuiltinToolName
|
||||
if (t.type === 'success') return titleOfBuiltinToolName[toolName].done
|
||||
if (t.type === 'running_now') return titleOfBuiltinToolName[toolName].running
|
||||
return titleOfBuiltinToolName[toolName].proposed
|
||||
}
|
||||
|
||||
|
||||
const toolNameToDesc = (toolName: ToolName, _toolParams: BuiltinToolCallParams[ToolName] | undefined, accessor: ReturnType<typeof useAccessor>): {
|
||||
const toolNameToDesc = (toolName: BuiltinToolName, _toolParams: BuiltinToolCallParams[BuiltinToolName] | undefined, accessor: ReturnType<typeof useAccessor>): {
|
||||
desc1: React.ReactNode,
|
||||
desc1Info?: string,
|
||||
} => {
|
||||
|
|
@ -1590,7 +1595,7 @@ const ToolRequestAcceptRejectButtons = ({ toolName }: { toolName: ToolName }) =>
|
|||
</button>
|
||||
)
|
||||
|
||||
const approvalType = approvalTypeOfToolName[toolName]
|
||||
const approvalType = isABuiltinToolName(toolName) ? approvalTypeOfBuiltinToolName[toolName] : 'mcp-tools'
|
||||
const approvalToggle = approvalType ? <div key={approvalType} className="flex items-center ml-2 gap-x-1">
|
||||
<ToolApprovalTypeSwitch size='xs' approvalType={approvalType} desc='Auto-approve' />
|
||||
</div> : null
|
||||
|
|
@ -1604,7 +1609,7 @@ const ToolRequestAcceptRejectButtons = ({ toolName }: { toolName: ToolName }) =>
|
|||
|
||||
export const ToolChildrenWrapper = ({ children, className }: { children: React.ReactNode, className?: string }) => {
|
||||
return <div className={`${className ? className : ''} cursor-default select-none`}>
|
||||
<div className='px-2 min-w-full'>
|
||||
<div className='px-2 min-w-full overflow-hidden'>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1633,12 +1638,18 @@ export const ListableToolItem = ({ name, onClick, isSmall, className, showDot }:
|
|||
|
||||
|
||||
|
||||
const EditToolChildren = ({ uri, code }: { uri: URI | undefined, code: string }) => {
|
||||
const EditToolChildren = ({ uri, code, type }: { uri: URI | undefined, code: string, type: 'diff' | 'rewrite' }) => {
|
||||
|
||||
const content = type === 'diff' ?
|
||||
<VoidDiffEditor uri={uri} searchReplaceBlocks={code} />
|
||||
: <ChatMarkdownRender string={`\`\`\`\n${code}\n\`\`\``} codeURI={uri} chatMessageLocation={undefined} />
|
||||
|
||||
return <div className='!select-text cursor-auto'>
|
||||
<SmallProseWrapper>
|
||||
<ChatMarkdownRender string={code} codeURI={uri} chatMessageLocation={undefined} />
|
||||
{content}
|
||||
</SmallProseWrapper>
|
||||
</div>
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1819,9 +1830,57 @@ const CommandTool = ({ toolMessage, type, threadId }: { threadId: string } & ({
|
|||
</>
|
||||
}
|
||||
|
||||
type WrapperProps<T extends ToolName> = { toolMessage: Exclude<ToolMessage<T>, { type: 'invalid_params' }>, messageIdx: number, threadId: string }
|
||||
const MCPToolWrapper = ({ toolMessage }: WrapperProps<string>) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
|
||||
type ResultWrapper<T extends ToolName> = (props: { toolMessage: Exclude<ToolMessage<T>, { type: 'invalid_params' }>, messageIdx: number, threadId: string }) => React.ReactNode
|
||||
const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>, } } = {
|
||||
const title = getTitle(toolMessage)
|
||||
const desc1 = toolMessage.name
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.type === 'tool_request') return null // do not show past requests
|
||||
if (toolMessage.type === 'running_now') return null // do not show running
|
||||
|
||||
const isError = false
|
||||
const isRejected = toolMessage.type === 'rejected'
|
||||
const { rawParams, params } = toolMessage
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon, isRejected, }
|
||||
|
||||
if (toolMessage.type === 'success') {
|
||||
const { result } = toolMessage
|
||||
componentParams.children = <ToolChildrenWrapper>
|
||||
<SmallProseWrapper>
|
||||
<ChatMarkdownRender
|
||||
string={`
|
||||
## Parameters
|
||||
\`\`\`\n${JSON.stringify(params, null, 2)}\n\`\`\`
|
||||
## Result
|
||||
\`\`\`\n${JSON.stringify(result, null, 2)}\n\`\`\`
|
||||
`}
|
||||
chatMessageLocation={undefined}
|
||||
isApplyEnabled={false}
|
||||
isLinkDetectionEnabled={true}
|
||||
/>
|
||||
</SmallProseWrapper>
|
||||
</ToolChildrenWrapper>
|
||||
}
|
||||
else if (toolMessage.type === 'tool_error') {
|
||||
const { result } = toolMessage
|
||||
componentParams.bottomChildren = <BottomChildren title='Error'>
|
||||
<CodeChildren>
|
||||
{result}
|
||||
</CodeChildren>
|
||||
</BottomChildren>
|
||||
}
|
||||
|
||||
return <ToolHeaderWrapper {...componentParams} />
|
||||
|
||||
}
|
||||
|
||||
type ResultWrapper<T extends ToolName> = (props: WrapperProps<T>) => React.ReactNode
|
||||
|
||||
const builtinToolNameToComponent: { [T in BuiltinToolName]: { resultWrapper: ResultWrapper<T>, } } = {
|
||||
'read_file': {
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
|
|
@ -2257,12 +2316,12 @@ const toolNameToComponent: { [T in ToolName]: { resultWrapper: ResultWrapper<T>,
|
|||
},
|
||||
'rewrite_file': {
|
||||
resultWrapper: (params) => {
|
||||
return <EditTool {...params} content={`${'```\n'}${params.toolMessage.params.newContent}${'\n```'}`} />
|
||||
return <EditTool {...params} content={params.toolMessage.params.newContent} />
|
||||
}
|
||||
},
|
||||
'edit_file': {
|
||||
resultWrapper: (params) => {
|
||||
return <EditTool {...params} content={`${'```\n'}${params.toolMessage.params.searchReplaceBlocks}${'\n```'}`} />
|
||||
return <EditTool {...params} content={params.toolMessage.params.searchReplaceBlocks} />
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -2446,7 +2505,11 @@ const _ChatBubble = ({ threadId, chatMessage, currCheckpointIdx, isCommitted, me
|
|||
</div>
|
||||
}
|
||||
|
||||
const ToolResultWrapper = toolNameToComponent[chatMessage.name]?.resultWrapper as ResultWrapper<ToolName>
|
||||
const toolName = chatMessage.name
|
||||
const isBuiltInTool = isABuiltinToolName(toolName)
|
||||
const ToolResultWrapper = isBuiltInTool ? builtinToolNameToComponent[toolName]?.resultWrapper as ResultWrapper<ToolName>
|
||||
: MCPToolWrapper as ResultWrapper<ToolName>
|
||||
|
||||
if (ToolResultWrapper)
|
||||
return <>
|
||||
<div className={`${isCheckpointGhost ? 'opacity-50' : ''}`}>
|
||||
|
|
@ -2746,12 +2809,13 @@ const CommandBarInChat = () => {
|
|||
|
||||
const EditToolSoFar = ({ toolCallSoFar, }: { toolCallSoFar: RawToolCallObj }) => {
|
||||
|
||||
if (!isABuiltinToolName( toolCallSoFar.name)) return null
|
||||
|
||||
const accessor = useAccessor()
|
||||
|
||||
const uri = toolCallSoFar.rawParams.uri ? URI.file(toolCallSoFar.rawParams.uri) : undefined
|
||||
|
||||
const title = titleOfToolName[toolCallSoFar.name].proposed
|
||||
const title = titleOfBuiltinToolName[toolCallSoFar.name].proposed
|
||||
|
||||
const uriDone = toolCallSoFar.doneParams.includes('uri')
|
||||
const desc1 = <span className='flex items-center'>
|
||||
|
|
@ -2772,12 +2836,11 @@ const EditToolSoFar = ({ toolCallSoFar, }: { toolCallSoFar: RawToolCallObj }) =>
|
|||
<EditToolChildren
|
||||
uri={uri}
|
||||
code={toolCallSoFar.rawParams.search_replace_blocks ?? toolCallSoFar.rawParams.new_content ?? ''}
|
||||
type={'rewrite'} // as it streams, show in rewrite format, don't make a diff editor
|
||||
/>
|
||||
<IconLoading />
|
||||
</ToolHeaderWrapper>
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ import { URI } from '../../../../../../../base/common/uri.js';
|
|||
import { getBasename, getFolderName } from '../sidebar-tsx/SidebarChat.js';
|
||||
import { ChevronRight, File, Folder, FolderClosed, LucideProps } from 'lucide-react';
|
||||
import { StagingSelectionItem } from '../../../../common/chatThreadServiceTypes.js';
|
||||
import { DiffEditorWidget } from '../../../../../../../editor/browser/widget/diffEditor/diffEditorWidget.js';
|
||||
import { extractSearchReplaceBlocks } from '../../../../common/helpers/extractCodeFromResult.js';
|
||||
import { IAccessibilitySignalService } from '../../../../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js';
|
||||
import { IEditorProgressService } from '../../../../../../../platform/progress/common/progress.js';
|
||||
import { detectLanguage } from '../../../../common/helpers/languageHelpers.js';
|
||||
|
||||
|
||||
// type guard
|
||||
|
|
@ -951,11 +956,11 @@ export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, plac
|
|||
|
||||
const contextViewProvider = accessor.get('IContextViewService')
|
||||
return <WidgetComponent
|
||||
ctor={InputBox}
|
||||
className='
|
||||
bg-void-bg-1
|
||||
@@void-force-child-placeholder-void-fg-1
|
||||
'
|
||||
ctor={InputBox}
|
||||
propsFn={useCallback((container) => [
|
||||
container,
|
||||
contextViewProvider,
|
||||
|
|
@ -991,8 +996,7 @@ export const VoidInputBox = ({ onChangeText, onCreateInstance, inputBoxRef, plac
|
|||
inputBoxRef.current = instance;
|
||||
|
||||
return disposables
|
||||
}, [onChangeText, onCreateInstance, inputBoxRef])
|
||||
}
|
||||
}, [onChangeText, onCreateInstance, inputBoxRef])}
|
||||
/>
|
||||
};
|
||||
|
||||
|
|
@ -1840,4 +1844,93 @@ export const VoidButtonBgDarken = ({ children, disabled, onClick, className }: {
|
|||
// return <div ref={containerRef} className="w-full" />;
|
||||
// };
|
||||
|
||||
/**
|
||||
* ToolDiffEditor mounts a native VSCode DiffEditorWidget to show a diff between original and modified code blocks.
|
||||
* Props:
|
||||
* - uri: URI of the file (for language detection, etc)
|
||||
* - searchReplaceBlocks: string in search/replace format (from LLM)
|
||||
* - language?: string (optional, fallback to 'plaintext')
|
||||
*/
|
||||
export const VoidDiffEditor = ({ uri, searchReplaceBlocks, language }: { uri?: any, searchReplaceBlocks: string, language?: string }) => {
|
||||
const accessor = useAccessor();
|
||||
const modelService = accessor.get('IModelService');
|
||||
const instantiationService = accessor.get('IInstantiationService');
|
||||
const languageService = accessor.get('ILanguageService');
|
||||
const contextKeyService = accessor.get('IContextKeyService');
|
||||
const codeEditorService = accessor.get('ICodeEditorService');
|
||||
|
||||
// Extract the first block (if present)
|
||||
const blocks = extractSearchReplaceBlocks(searchReplaceBlocks);
|
||||
const block = blocks[0] || { orig: '', final: '' };
|
||||
|
||||
// Use detectLanguage for language detection if not provided
|
||||
let lang = language;
|
||||
if (!lang) {
|
||||
lang = detectLanguage(languageService, { uri: uri ?? null, fileContents: block.orig });
|
||||
}
|
||||
|
||||
// Use ILanguageSelection for model creation
|
||||
const languageSelection = useMemo(() => languageService.createById(lang!), [lang, languageService]);
|
||||
|
||||
// Create models for original and modified
|
||||
const originalModel = useMemo(() =>
|
||||
modelService.createModel(block.orig, languageSelection),
|
||||
[block.orig, languageSelection, modelService]
|
||||
);
|
||||
const modifiedModel = useMemo(() =>
|
||||
modelService.createModel(block.final, languageSelection),
|
||||
[block.final, languageSelection, modelService]
|
||||
);
|
||||
|
||||
// Clean up models on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
originalModel.dispose();
|
||||
modifiedModel.dispose();
|
||||
};
|
||||
}, [originalModel, modifiedModel]);
|
||||
|
||||
// Imperatively mount the DiffEditorWidget
|
||||
const divRef = useRef<HTMLDivElement | null>(null);
|
||||
const editorRef = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!divRef.current) return;
|
||||
// Create the diff editor instance
|
||||
const editor = instantiationService.createInstance(
|
||||
DiffEditorWidget,
|
||||
divRef.current,
|
||||
{
|
||||
automaticLayout: true,
|
||||
readOnly: true,
|
||||
renderSideBySide: true,
|
||||
minimap: { enabled: false },
|
||||
lineNumbers: 'off',
|
||||
scrollbar: { vertical: 'auto', horizontal: 'auto', verticalScrollbarSize: 8, horizontalScrollbarSize: 8 },
|
||||
hover: { enabled: false },
|
||||
folding: false,
|
||||
selectionHighlight: false,
|
||||
renderLineHighlight: 'none',
|
||||
overviewRulerLanes: 0,
|
||||
hideCursorInOverviewRuler: true,
|
||||
overviewRulerBorder: false,
|
||||
glyphMargin: false,
|
||||
stickyScroll: { enabled: false },
|
||||
},
|
||||
{ originalEditor: { isSimpleWidget: true }, modifiedEditor: { isSimpleWidget: true } }
|
||||
);
|
||||
editor.setModel({ original: originalModel, modified: modifiedModel });
|
||||
editor.layout();
|
||||
editorRef.current = editor;
|
||||
return () => {
|
||||
editor.dispose();
|
||||
editorRef.current = null;
|
||||
};
|
||||
}, [originalModel, modifiedModel, instantiationService]);
|
||||
|
||||
return (
|
||||
<div className="w-full h-[300px] bg-void-bg-3 rounded" ref={divRef} />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 { LintErrorItem, BuiltinToolCallParams, BuiltinToolResultType } from '../common/toolsServiceTypes.js'
|
||||
import { LintErrorItem, BuiltinToolCallParams, BuiltinToolResultType, BuiltinToolName } from '../common/toolsServiceTypes.js'
|
||||
import { IVoidModelService } from '../common/voidModelService.js'
|
||||
import { EndOfLinePreference } from '../../../../editor/common/model.js'
|
||||
import { IVoidCommandBarService } from './voidCommandBarService.js'
|
||||
|
|
@ -16,15 +16,15 @@ import { computeDirectoryTree1Deep, IDirectoryStrService, stringifyDirectoryTree
|
|||
import { IMarkerService, MarkerSeverity } from '../../../../platform/markers/common/markers.js'
|
||||
import { timeout } from '../../../../base/common/async.js'
|
||||
import { RawToolParamsObj } from '../common/sendLLMMessageTypes.js'
|
||||
import { MAX_CHILDREN_URIs_PAGE, MAX_FILE_CHARS_PAGE, MAX_TERMINAL_BG_COMMAND_TIME, MAX_TERMINAL_INACTIVE_TIME, ToolName } from '../common/prompt/prompts.js'
|
||||
import { MAX_CHILDREN_URIs_PAGE, MAX_FILE_CHARS_PAGE, MAX_TERMINAL_BG_COMMAND_TIME, MAX_TERMINAL_INACTIVE_TIME } from '../common/prompt/prompts.js'
|
||||
import { IVoidSettingsService } from '../common/voidSettingsService.js'
|
||||
import { generateUuid } from '../../../../base/common/uuid.js'
|
||||
|
||||
|
||||
// tool use for AI
|
||||
type ValidateBuiltinParams = { [T in ToolName]: (p: RawToolParamsObj) => BuiltinToolCallParams[T] }
|
||||
type CallBuiltinTool = { [T in ToolName]: (p: BuiltinToolCallParams[T]) => Promise<{ result: BuiltinToolResultType[T] | Promise<BuiltinToolResultType[T]>, interruptTool?: () => void }> }
|
||||
type BuiltinToolResultToString = { [T in ToolName]: (p: BuiltinToolCallParams[T], result: Awaited<BuiltinToolResultType[T]>) => string }
|
||||
type ValidateBuiltinParams = { [T in BuiltinToolName]: (p: RawToolParamsObj) => BuiltinToolCallParams[T] }
|
||||
type CallBuiltinTool = { [T in BuiltinToolName]: (p: BuiltinToolCallParams[T]) => Promise<{ result: BuiltinToolResultType[T] | Promise<BuiltinToolResultType[T]>, interruptTool?: () => void }> }
|
||||
type BuiltinToolResultToString = { [T in BuiltinToolName]: (p: BuiltinToolCallParams[T], result: Awaited<BuiltinToolResultType[T]>) => string }
|
||||
|
||||
|
||||
const isFalsy = (u: unknown) => {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,8 @@
|
|||
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { VoidFileSnapshot } from './editCodeServiceTypes.js';
|
||||
import { ToolName } from './prompt/prompts.js';
|
||||
import { AnthropicReasoning, RawToolParamsObj } from './sendLLMMessageTypes.js';
|
||||
import { BuiltinToolCallParams, BuiltinToolResultType } from './toolsServiceTypes.js';
|
||||
import { ToolCallParams, ToolName, ToolResult } from './toolsServiceTypes.js';
|
||||
|
||||
export type ToolMessage<T extends ToolName> = {
|
||||
role: 'tool';
|
||||
|
|
@ -18,13 +17,13 @@ export type ToolMessage<T extends ToolName> = {
|
|||
// in order of events:
|
||||
| { type: 'invalid_params', result: null, name: T, }
|
||||
|
||||
| { type: 'tool_request', result: null, name: T, params: BuiltinToolCallParams[T], } // params were validated, awaiting user
|
||||
| { type: 'tool_request', result: null, name: T, params: ToolCallParams<T>, } // params were validated, awaiting user
|
||||
|
||||
| { type: 'running_now', result: null, name: T, params: BuiltinToolCallParams[T], }
|
||||
| { type: 'running_now', result: null, name: T, params: ToolCallParams<T>, }
|
||||
|
||||
| { type: 'tool_error', result: string, name: T, params: BuiltinToolCallParams[T], } // error when tool was running
|
||||
| { type: 'success', result: Awaited<BuiltinToolResultType[T]>, name: T, params: BuiltinToolCallParams[T], }
|
||||
| { type: 'rejected', result: null, name: T, params: BuiltinToolCallParams[T] }
|
||||
| { type: 'tool_error', result: string, name: T, params: ToolCallParams<T>, } // error when tool was running
|
||||
| { type: 'success', result: Awaited<ToolResult<T>>, name: T, params: ToolCallParams<T>, }
|
||||
| { type: 'rejected', result: null, name: T, params: ToolCallParams<T> }
|
||||
) // user rejected
|
||||
|
||||
export type DecorativeCanceledTool = {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { IProductService } from '../../../../platform/product/common/productServ
|
|||
import { VSBuffer } from '../../../../base/common/buffer.js';
|
||||
import { IChannel } from '../../../../base/parts/ipc/common/ipc.js';
|
||||
import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js';
|
||||
import { MCPServerOfName, MCPConfigFileJSON, MCPServer, MCPToolCallParams, MCPGenericToolResponse, MCPServerEventResponse } from './mcpServiceTypes.js';
|
||||
import { MCPServerOfName, MCPConfigFileJSON, MCPServer, MCPToolCallParams, RawMCPToolCall, MCPServerEventResponse } from './mcpServiceTypes.js';
|
||||
import { Event, Emitter } from '../../../../base/common/event.js';
|
||||
import { InternalToolInfo } from './prompt/prompts.js';
|
||||
import { IVoidSettingsService } from './voidSettingsService.js';
|
||||
|
|
@ -36,9 +36,7 @@ export interface IMCPService {
|
|||
|
||||
getMCPTools(): Record<string, InternalToolInfo>;
|
||||
|
||||
// TOOL_TODO!!!! implement getMCPTools here, which gets merged with builtins in prompts.ts. Should generally be the same shape as voidTools in prompts.ts.
|
||||
|
||||
callMCPTool(toolData: MCPToolCallParams): Promise<{ result: MCPGenericToolResponse }>;
|
||||
callMCPTool(toolData: MCPToolCallParams): Promise<{ result: RawMCPToolCall }>;
|
||||
|
||||
|
||||
// this is outdated:
|
||||
|
|
@ -304,8 +302,8 @@ class MCPService extends Disposable implements IMCPService {
|
|||
}
|
||||
|
||||
|
||||
public async callMCPTool(toolData: MCPToolCallParams): Promise<{ result: MCPGenericToolResponse }> {
|
||||
const result = await this.channel.call<MCPGenericToolResponse>('callTool', toolData);
|
||||
public async callMCPTool(toolData: MCPToolCallParams): Promise<{ result: RawMCPToolCall }> {
|
||||
const result = await this.channel.call<RawMCPToolCall>('callTool', toolData);
|
||||
return { result };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ export type MCPToolErrorResponse = MCPToolEventResponse<'error'>;
|
|||
export type MCPToolImageResponse = MCPToolEventResponse<'image'>;
|
||||
export type MCPToolAudioResponse = MCPToolEventResponse<'audio'>;
|
||||
export type MCPToolResourceResponse = MCPToolEventResponse<'resource'>;
|
||||
export type MCPGenericToolResponse = MCPToolTextResponse | MCPToolErrorResponse | MCPToolImageResponse | MCPToolAudioResponse | MCPToolResourceResponse;
|
||||
export type RawMCPToolCall = MCPToolTextResponse | MCPToolErrorResponse | MCPToolImageResponse | MCPToolAudioResponse | MCPToolResourceResponse;
|
||||
|
||||
export interface MCPToolCallParams {
|
||||
serverName: string;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { IDirectoryStrService } from '../directoryStrService.js';
|
|||
import { StagingSelectionItem } from '../chatThreadServiceTypes.js';
|
||||
import { os } from '../helpers/systemInfo.js';
|
||||
import { RawToolParamsObj } from '../sendLLMMessageTypes.js';
|
||||
import { approvalTypeOfToolName, BuiltinToolCallParams, BuiltinToolResultType } from '../toolsServiceTypes.js';
|
||||
import { approvalTypeOfBuiltinToolName, BuiltinToolCallParams, BuiltinToolName, BuiltinToolResultType, ToolName } from '../toolsServiceTypes.js';
|
||||
import { ChatMode } from '../voidSettingsTypes.js';
|
||||
|
||||
// Triple backtick wrapper used throughout the prompts for code blocks
|
||||
|
|
@ -184,7 +184,7 @@ export type SnakeCaseKeys<T extends Record<string, any>> = {
|
|||
|
||||
|
||||
// export const voidTools = {
|
||||
export const voidTools
|
||||
export const builtinTools
|
||||
: {
|
||||
[T in keyof BuiltinToolCallParams]: {
|
||||
name: string;
|
||||
|
|
@ -348,32 +348,21 @@ export const voidTools
|
|||
} satisfies { [T in keyof BuiltinToolResultType]: InternalToolInfo }
|
||||
|
||||
|
||||
export type ToolName = keyof BuiltinToolResultType
|
||||
export const toolNames = Object.keys(voidTools) as ToolName[]
|
||||
|
||||
type ToolParamNameOfTool<T extends ToolName> = keyof (typeof voidTools)[T]['params']
|
||||
export type ToolParamName = { [T in ToolName]: ToolParamNameOfTool<T> }[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) => {
|
||||
|
||||
// TOOL_TODO!!!!
|
||||
// merge MCP tools with these builtin tools
|
||||
// Note: This requires refactoring the messaging architecture to pass MCP tools from the renderer process
|
||||
// to the electron-main process through the IPC channel. For now, MCP tools are handled separately
|
||||
// in the chatThreadService where both built-in and MCP tools are called appropriately.
|
||||
|
||||
|
||||
|
||||
const toolNames: ToolName[] | undefined = chatMode === 'normal' ? undefined
|
||||
: chatMode === 'gather' ? (Object.keys(voidTools) as ToolName[]).filter(toolName => !(toolName in approvalTypeOfToolName))
|
||||
: chatMode === 'agent' ? Object.keys(voidTools) as ToolName[]
|
||||
const toolNames: BuiltinToolName[] | undefined = chatMode === 'normal' ? undefined
|
||||
: chatMode === 'gather' ? (Object.keys(builtinTools) as BuiltinToolName[]).filter(toolName => !(toolName in approvalTypeOfBuiltinToolName))
|
||||
: chatMode === 'agent' ? Object.keys(builtinTools) as BuiltinToolName[]
|
||||
: undefined
|
||||
|
||||
const tools: InternalToolInfo[] | undefined = toolNames?.map(toolName => voidTools[toolName])
|
||||
const tools: InternalToolInfo[] | undefined = toolNames?.map(toolName => builtinTools[toolName])
|
||||
return tools
|
||||
}
|
||||
|
||||
|
|
@ -390,7 +379,7 @@ const toolCallDefinitionsXMLString = (tools: InternalToolInfo[]) => {
|
|||
}
|
||||
|
||||
export const reParsedToolXMLString = (toolName: ToolName, toolParams: RawToolParamsObj) => {
|
||||
const params = Object.keys(toolParams).map(paramName => `<${paramName}>${toolParams[paramName as ToolParamName]}</${paramName}>`).join('\n')
|
||||
const params = Object.keys(toolParams).map(paramName => `<${paramName}>${toolParams[paramName]}</${paramName}>`).join('\n')
|
||||
return `\
|
||||
<${toolName}>${!params ? '' : `\n${params}`}
|
||||
</${toolName}>`
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
|
||||
*--------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ToolName, ToolParamName } from './prompt/prompts.js'
|
||||
import { ToolName, ToolParamName } from './toolsServiceTypes.js'
|
||||
import { ChatMode, ModelSelection, ModelSelectionOptions, OverridesOfModel, ProviderName, RefreshableProviderName, SettingsOfProvider } from './voidSettingsTypes.js'
|
||||
|
||||
|
||||
|
|
@ -78,12 +78,12 @@ export type LLMFIMMessage = {
|
|||
|
||||
|
||||
export type RawToolParamsObj = {
|
||||
[paramName in ToolParamName]?: string;
|
||||
[paramName in ToolParamName<ToolName>]?: string;
|
||||
}
|
||||
export type RawToolCallObj = {
|
||||
name: ToolName;
|
||||
rawParams: RawToolParamsObj;
|
||||
doneParams: ToolParamName[];
|
||||
doneParams: ToolParamName<ToolName>[];
|
||||
id: string;
|
||||
isDone: boolean;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { URI } from '../../../../base/common/uri.js'
|
||||
import { ToolName } from './prompt/prompts.js';
|
||||
import { RawMCPToolCall } from './mcpServiceTypes.js';
|
||||
import { builtinTools } from './prompt/prompts.js';
|
||||
import { RawToolParamsObj } from './sendLLMMessageTypes.js';
|
||||
|
||||
|
||||
|
||||
|
|
@ -16,7 +18,7 @@ export type ShallowDirectoryItem = {
|
|||
}
|
||||
|
||||
|
||||
export const approvalTypeOfToolName: Partial<{ [T in ToolName]?: 'edits' | 'terminal' }> = {
|
||||
export const approvalTypeOfBuiltinToolName: Partial<{ [T in BuiltinToolName]?: 'edits' | 'terminal' | 'mcp-tools' }> = {
|
||||
'create_file_or_folder': 'edits',
|
||||
'delete_file_or_folder': 'edits',
|
||||
'rewrite_file': 'edits',
|
||||
|
|
@ -28,13 +30,16 @@ export const approvalTypeOfToolName: Partial<{ [T in ToolName]?: 'edits' | 'term
|
|||
}
|
||||
|
||||
|
||||
export type ToolApprovalType = NonNullable<(typeof approvalTypeOfBuiltinToolName)[keyof typeof approvalTypeOfBuiltinToolName]>;
|
||||
|
||||
|
||||
export const toolApprovalTypes = new Set<ToolApprovalType>([
|
||||
...Object.values(approvalTypeOfBuiltinToolName),
|
||||
'mcp-tools',
|
||||
])
|
||||
|
||||
|
||||
// {{add: define new type for approval types}}
|
||||
export type ToolApprovalType = NonNullable<(typeof approvalTypeOfToolName)[keyof typeof approvalTypeOfToolName]>;
|
||||
|
||||
export const toolApprovalTypes = new Set<ToolApprovalType>(
|
||||
Object.values(approvalTypeOfToolName).filter((v): v is ToolApprovalType => v !== undefined)
|
||||
)
|
||||
|
||||
// PARAMS OF TOOL CALL
|
||||
export type BuiltinToolCallParams = {
|
||||
|
|
@ -78,3 +83,25 @@ export type BuiltinToolResultType = {
|
|||
'kill_persistent_terminal': {},
|
||||
}
|
||||
|
||||
|
||||
export type ToolCallParams<T extends BuiltinToolName | (string & {})> = T extends BuiltinToolName ? BuiltinToolCallParams[T] : RawToolParamsObj
|
||||
export type ToolResult<T extends BuiltinToolName | (string & {})> = T extends BuiltinToolName ? BuiltinToolResultType[T] : RawMCPToolCall
|
||||
|
||||
|
||||
export type BuiltinToolName = keyof BuiltinToolResultType
|
||||
export const builtinToolNames = Object.keys(builtinTools) as BuiltinToolName[]
|
||||
|
||||
type BuiltinToolParamNameOfTool<T extends BuiltinToolName> = keyof (typeof builtinTools)[T]['params']
|
||||
export type BuiltinToolParamName = { [T in BuiltinToolName]: BuiltinToolParamNameOfTool<T> }[BuiltinToolName]
|
||||
|
||||
const toolNamesSet = new Set<string>(builtinToolNames)
|
||||
|
||||
export const isABuiltinToolName = (toolName: string): toolName is BuiltinToolName => {
|
||||
const isAToolName = toolNamesSet.has(toolName)
|
||||
return isAToolName
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type ToolName = BuiltinToolName | (string & {})
|
||||
export type ToolParamName<T extends ToolName> = T extends BuiltinToolName ? BuiltinToolParamNameOfTool<T> : string
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@
|
|||
|
||||
import { generateUuid } from '../../../../../base/common/uuid.js'
|
||||
import { endsWithAnyPrefixOf, SurroundingsRemover } from '../../common/helpers/extractCodeFromResult.js'
|
||||
import { availableTools, InternalToolInfo, ToolName, ToolParamName } from '../../common/prompt/prompts.js'
|
||||
import { availableTools, InternalToolInfo } from '../../common/prompt/prompts.js'
|
||||
import { OnFinalMessage, OnText, RawToolCallObj, RawToolParamsObj } from '../../common/sendLLMMessageTypes.js'
|
||||
import { BuiltinToolName, BuiltinToolParamName } from '../../common/toolsServiceTypes.js'
|
||||
import { ChatMode } from '../../common/voidSettingsTypes.js'
|
||||
|
||||
|
||||
|
|
@ -164,15 +165,15 @@ const findIndexOfAny = (fullText: string, matches: string[]) => {
|
|||
|
||||
|
||||
type ToolOfToolName = { [toolName: string]: InternalToolInfo | undefined }
|
||||
const parseXMLPrefixToToolCall = (toolName: ToolName, toolId: string, str: string, toolOfToolName: ToolOfToolName): RawToolCallObj => {
|
||||
const parseXMLPrefixToToolCall = (toolName: BuiltinToolName, toolId: string, str: string, toolOfToolName: ToolOfToolName): RawToolCallObj => {
|
||||
const paramsObj: RawToolParamsObj = {}
|
||||
const doneParams: ToolParamName[] = []
|
||||
const doneParams: BuiltinToolParamName[] = []
|
||||
let isDone = false
|
||||
|
||||
const getAnswer = (): RawToolCallObj => {
|
||||
// trim off all whitespace at and before first \n and after last \n for each param
|
||||
for (const p in paramsObj) {
|
||||
const paramName = p as ToolParamName
|
||||
const paramName = p as BuiltinToolParamName
|
||||
const orig = paramsObj[paramName]
|
||||
if (orig === undefined) continue
|
||||
paramsObj[paramName] = trimBeforeAndAfterNewLines(orig)
|
||||
|
|
@ -202,16 +203,16 @@ const parseXMLPrefixToToolCall = (toolName: ToolName, toolId: string, str: strin
|
|||
|
||||
const pm = new SurroundingsRemover(str)
|
||||
|
||||
const allowedParams = Object.keys(toolOfToolName[toolName]?.params ?? {}) as ToolParamName[]
|
||||
const allowedParams = Object.keys(toolOfToolName[toolName]?.params ?? {}) as BuiltinToolParamName[]
|
||||
if (allowedParams.length === 0) return getAnswer()
|
||||
let latestMatchedOpenParam: null | ToolParamName = null
|
||||
let latestMatchedOpenParam: null | BuiltinToolParamName = null
|
||||
let n = 0
|
||||
while (true) {
|
||||
n += 1
|
||||
if (n > 10) return getAnswer() // just for good measure as this code is early
|
||||
|
||||
// find the param name opening tag
|
||||
let matchedOpenParam: null | ToolParamName = null
|
||||
let matchedOpenParam: null | BuiltinToolParamName = null
|
||||
for (const paramName of allowedParams) {
|
||||
const removed = pm.removeFromStartUntilFullMatch(`<${paramName}>`, true)
|
||||
if (removed) {
|
||||
|
|
@ -278,7 +279,7 @@ export const extractXMLToolsWrapper = (
|
|||
let trueFullText = ''
|
||||
let latestToolCall: RawToolCallObj | undefined = undefined
|
||||
|
||||
let foundOpenTag: { idx: number, toolName: ToolName } | null = null
|
||||
let foundOpenTag: { idx: number, toolName: BuiltinToolName } | null = null
|
||||
let openToolTagBuffer = '' // the characters we've seen so far that come after a < with no space afterwards, not yet added to fullText
|
||||
|
||||
let prevFullTextLen = 0
|
||||
|
|
@ -308,7 +309,7 @@ export const extractXMLToolsWrapper = (
|
|||
const i = findIndexOfAny(fullText, toolOpenTags)
|
||||
if (i !== null) {
|
||||
const [idx, toolTag] = i
|
||||
const toolName = toolTag.substring(1, toolTag.length - 1) as ToolName
|
||||
const toolName = toolTag.substring(1, toolTag.length - 1) as BuiltinToolName
|
||||
// console.log('found ', toolName)
|
||||
foundOpenTag = { idx, toolName }
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,9 @@ import { AnthropicLLMChatMessage, GeminiLLMChatMessage, LLMChatMessage, LLMFIMMe
|
|||
import { ChatMode, displayInfoOfProviderName, ModelSelectionOptions, OverridesOfModel, ProviderName, SettingsOfProvider } from '../../common/voidSettingsTypes.js';
|
||||
import { getSendableReasoningInfo, getModelCapabilities, getProviderCapabilities, defaultProviderSettings, getReservedOutputTokenSpace } from '../../common/modelCapabilities.js';
|
||||
import { extractReasoningWrapper, extractXMLToolsWrapper } from './extractGrammar.js';
|
||||
import { availableTools, InternalToolInfo, isAToolName, ToolParamName, voidTools } from '../../common/prompt/prompts.js';
|
||||
import { availableTools, InternalToolInfo, builtinTools } from '../../common/prompt/prompts.js';
|
||||
import { generateUuid } from '../../../../../base/common/uuid.js';
|
||||
import { isABuiltinToolName, BuiltinToolParamName } from '../../common/toolsServiceTypes.js';
|
||||
|
||||
const getGoogleApiKey = async () => {
|
||||
// module‑level singleton
|
||||
|
|
@ -220,7 +221,7 @@ const openAITools = (chatMode: ChatMode) => {
|
|||
|
||||
// convert LLM tool call to our tool format
|
||||
const rawToolCallObjOf = (name: string, toolParamsStr: string, id: string): RawToolCallObj | null => {
|
||||
if (!isAToolName(name)) return null
|
||||
if (!isABuiltinToolName(name)) return null
|
||||
const rawParams: RawToolParamsObj = {}
|
||||
let input: unknown
|
||||
try {
|
||||
|
|
@ -231,10 +232,10 @@ const rawToolCallObjOf = (name: string, toolParamsStr: string, id: string): RawT
|
|||
}
|
||||
if (input === null) return null
|
||||
if (typeof input !== 'object') return null
|
||||
for (const paramName in voidTools[name].params) {
|
||||
rawParams[paramName as ToolParamName] = (input as any)[paramName]
|
||||
for (const paramName in builtinTools[name].params) {
|
||||
rawParams[paramName as BuiltinToolParamName] = (input as any)[paramName]
|
||||
}
|
||||
return { id, name, rawParams, doneParams: Object.keys(rawParams) as ToolParamName[], isDone: true }
|
||||
return { id, name, rawParams, doneParams: Object.keys(rawParams) as BuiltinToolParamName[], isDone: true }
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -337,7 +338,7 @@ const _sendOpenAICompatibleChat = async ({ messages, onText, onFinalMessage, onE
|
|||
onText({
|
||||
fullText: fullTextSoFar,
|
||||
fullReasoning: fullReasoningSoFar,
|
||||
toolCall: isAToolName(toolName) ? { name: toolName, rawParams: {}, isDone: false, doneParams: [], id: toolId } : undefined,
|
||||
toolCall: isABuiltinToolName(toolName) ? { name: toolName, rawParams: {}, isDone: false, doneParams: [], id: toolId } : undefined,
|
||||
})
|
||||
|
||||
}
|
||||
|
|
@ -425,14 +426,14 @@ const anthropicTools = (chatMode: ChatMode) => {
|
|||
|
||||
const anthropicToolToRawToolCallObj = (toolBlock: Anthropic.Messages.ToolUseBlock): RawToolCallObj | null => {
|
||||
const { id, name, input } = toolBlock
|
||||
if (!isAToolName(name)) return null
|
||||
if (!isABuiltinToolName(name)) return null
|
||||
const rawParams: RawToolParamsObj = {}
|
||||
if (input === null) return null
|
||||
if (typeof input !== 'object') return null
|
||||
for (const paramName in voidTools[name].params) {
|
||||
rawParams[paramName as ToolParamName] = (input as any)[paramName]
|
||||
for (const paramName in builtinTools[name].params) {
|
||||
rawParams[paramName as BuiltinToolParamName] = (input as any)[paramName]
|
||||
}
|
||||
return { id, name, rawParams, doneParams: Object.keys(rawParams) as ToolParamName[], isDone: true }
|
||||
return { id, name, rawParams, doneParams: Object.keys(rawParams) as BuiltinToolParamName[], isDone: true }
|
||||
}
|
||||
|
||||
// ------------ ANTHROPIC ------------
|
||||
|
|
@ -494,7 +495,7 @@ const sendAnthropicChat = async ({ messages, providerName, onText, onFinalMessag
|
|||
onText({
|
||||
fullText,
|
||||
fullReasoning,
|
||||
toolCall: isAToolName(fullToolName) ? { name: fullToolName, rawParams: {}, isDone: false, doneParams: [], id: 'dummy' } : undefined,
|
||||
toolCall: isABuiltinToolName(fullToolName) ? { name: fullToolName, rawParams: {}, isDone: false, doneParams: [], id: 'dummy' } : undefined,
|
||||
})
|
||||
}
|
||||
// there are no events for tool_use, it comes in at the end
|
||||
|
|
@ -788,7 +789,7 @@ const sendGeminiChat = async ({
|
|||
onText({
|
||||
fullText: fullTextSoFar,
|
||||
fullReasoning: fullReasoningSoFar,
|
||||
toolCall: isAToolName(toolName) ? { name: toolName, rawParams: {}, isDone: false, doneParams: [], id: toolId } : undefined,
|
||||
toolCall: isABuiltinToolName(toolName) ? { name: toolName, rawParams: {}, isDone: false, doneParams: [], id: toolId } : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|||
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
||||
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
||||
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
||||
import { MCPConfigFileJSON, MCPConfigFileEntryJSON, MCPServer, MCPGenericToolResponse, MCPToolErrorResponse, MCPServerEventResponse, MCPToolCallParams } from '../common/mcpServiceTypes.js';
|
||||
import { MCPConfigFileJSON, MCPConfigFileEntryJSON, MCPServer, RawMCPToolCall, MCPToolErrorResponse, MCPServerEventResponse, MCPToolCallParams } from '../common/mcpServiceTypes.js';
|
||||
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
||||
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
||||
import { MCPUserStateOfName } from '../common/voidSettingsTypes.js';
|
||||
|
|
@ -294,7 +294,7 @@ export class MCPChannel implements IServerChannel {
|
|||
|
||||
// tool call functions
|
||||
|
||||
private async _callTool(serverName: string, toolName: string, params: any): Promise<MCPGenericToolResponse> {
|
||||
private async _callTool(serverName: string, toolName: string, params: any): Promise<RawMCPToolCall> {
|
||||
const server = this.infoOfClientId[serverName]
|
||||
if (!server) throw new Error(`Server ${serverName} not found`)
|
||||
const { _client: client } = server
|
||||
|
|
@ -341,7 +341,7 @@ export class MCPChannel implements IServerChannel {
|
|||
}
|
||||
|
||||
// tool call error wrapper
|
||||
private async _safeCallTool(serverName: string, toolName: string, params: any): Promise<MCPGenericToolResponse> {
|
||||
private async _safeCallTool(serverName: string, toolName: string, params: any): Promise<RawMCPToolCall> {
|
||||
try {
|
||||
const response = await this._callTool(serverName, toolName, params)
|
||||
return response
|
||||
|
|
|
|||
Loading…
Reference in a new issue