mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
tool UI progress + terminal fix + misc fixes
This commit is contained in:
parent
1ef8011bb0
commit
11d376325e
11 changed files with 388 additions and 266 deletions
|
|
@ -189,10 +189,10 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
private readonly _onDidChangeCurrentThread = new Emitter<void>();
|
||||
readonly onDidChangeCurrentThread: Event<void> = this._onDidChangeCurrentThread.event;
|
||||
|
||||
readonly streamState: ThreadStreamState = {}
|
||||
private readonly _onDidChangeStreamState = new Emitter<{ threadId: string }>();
|
||||
readonly onDidChangeStreamState: Event<{ threadId: string }> = this._onDidChangeStreamState.event;
|
||||
|
||||
readonly streamState: ThreadStreamState = {}
|
||||
state: ThreadsState // allThreads is persisted, currentThread is not
|
||||
|
||||
constructor(
|
||||
|
|
@ -445,7 +445,7 @@ class ChatThreadService extends Disposable implements IChatThreadService {
|
|||
// TODO!!! test rejection
|
||||
// if (Math.random() > 0) throw new Error('TESTING')
|
||||
const errorMessage = 'Tool call was rejected by the user.'
|
||||
this._addMessageToThread(threadId, { role: 'tool', name: toolName, paramsStr: tool.paramsStr, id: tool.id, content: errorMessage, result: { type: 'error', params: toolParams, value: errorMessage }, })
|
||||
this._addMessageToThread(threadId, { role: 'tool', name: toolName, paramsStr: tool.paramsStr, id: tool.id, content: errorMessage, result: { type: 'rejected', params: toolParams, value: errorMessage }, })
|
||||
shouldSendAnotherMessage = false // interrupt flow by rejecting
|
||||
res_()
|
||||
return
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ const configOfBG = (color: Color) => {
|
|||
const greenBG = new Color(new RGBA(155, 185, 85, .1)); // default is RGBA(155, 185, 85, .2)
|
||||
registerColor('void.greenBG', configOfBG(greenBG), '', true);
|
||||
|
||||
const redBG = new Color(new RGBA(255, 0, 0, .2)); // default is RGBA(255, 0, 0, .2)
|
||||
const redBG = new Color(new RGBA(255, 0, 0, .05)); // default is RGBA(255, 0, 0, .2)
|
||||
registerColor('void.redBG', configOfBG(redBG), '', true);
|
||||
|
||||
const sweepBG = new Color(new RGBA(100, 100, 100, .2));
|
||||
|
|
|
|||
|
|
@ -656,52 +656,69 @@ export const SelectedFiles = (
|
|||
|
||||
const ReasoningComponent = ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
return children
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
const ToolComponent = ({
|
||||
type ToolHeaderParams = {
|
||||
icon?: React.ReactNode;
|
||||
title: string;
|
||||
desc1: string;
|
||||
desc2?: React.ReactNode;
|
||||
isError?: boolean;
|
||||
requestToolId?: string;
|
||||
numResults?: number;
|
||||
children?: React.ReactNode;
|
||||
isLastMessage?: boolean;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const ToolHeaderComponent = ({
|
||||
icon,
|
||||
title,
|
||||
desc1,
|
||||
desc2,
|
||||
numResults,
|
||||
children,
|
||||
isError,
|
||||
requestToolId,
|
||||
isLastMessage,
|
||||
onClick,
|
||||
}: {
|
||||
title: string;
|
||||
desc1: string;
|
||||
desc2?: React.ReactNode;
|
||||
numResults?: number;
|
||||
children?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
}) => {
|
||||
}: ToolHeaderParams) => {
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
const isDropdown = !!children
|
||||
const isClickable = !!isDropdown || !!onClick
|
||||
|
||||
return (
|
||||
<div className="select-none">
|
||||
<div className="border border-void-border-3 rounded px-2 py-1 bg-void-bg-2-alt overflow-hidden">
|
||||
<div
|
||||
className={`flex items-center min-h-[24px] ${isClickable ? 'cursor-pointer hover:brightness-125 transition-all duration-150' : ''} ${!isDropdown ? 'mx-1' : ''}`}
|
||||
onClick={() => {
|
||||
if (children) { setIsExpanded(v => !v); }
|
||||
if (onClick) { onClick(); }
|
||||
}}
|
||||
>
|
||||
{isDropdown && (
|
||||
<ChevronRight
|
||||
className={`text-void-fg-3 mr-0.5 h-4 w-4 flex-shrink-0 transition-transform duration-100 ease-[cubic-bezier(0.4,0,0.2,1)] ${isExpanded ? 'rotate-90' : ''}`}
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-center justify-between w-full flex-nowrap whitespace-nowrap gap-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<span className="text-void-fg-3">{title}</span>
|
||||
<span className="text-void-fg-4 text-xs italic">{desc1}</span>
|
||||
</div>
|
||||
return (<div className=''>
|
||||
<div className="border border-void-border-3 rounded px-2 py-1 bg-void-bg-2-alt overflow-hidden">
|
||||
|
||||
{/* header */}
|
||||
<div
|
||||
className={`select-none flex items-center min-h-[24px] ${isClickable ? 'cursor-pointer hover:brightness-125 transition-all duration-150' : ''} ${!isDropdown ? 'mx-1' : ''}`}
|
||||
onClick={() => {
|
||||
if (children) { setIsExpanded(v => !v); }
|
||||
if (onClick) { onClick(); }
|
||||
}}
|
||||
>
|
||||
{isDropdown && (
|
||||
<ChevronRight
|
||||
className={`text-void-fg-3 mr-0.5 h-4 w-4 flex-shrink-0 transition-transform duration-100 ease-[cubic-bezier(0.4,0,0.2,1)] ${isExpanded ? 'rotate-90' : ''}`}
|
||||
/>
|
||||
)}
|
||||
<div className="flex items-center justify-between w-full flex-nowrap whitespace-nowrap gap-x-2">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<span className="text-void-fg-3">{title}</span>
|
||||
<span className="text-void-fg-4 text-xs italic">{desc1}</span>
|
||||
</div>
|
||||
<div
|
||||
// the py-1 here makes sure all elements in the container have py-2 total. this makes a nice animation effect during transition.
|
||||
className={`overflow-hidden transition-all duration-200 ease-in-out ${isExpanded ? 'opacity-100 py-1' : 'max-h-0 opacity-0'}`}
|
||||
>
|
||||
<div className="flex items-center gap-x-2">
|
||||
{desc2 && <span className="text-void-fg-4 text-xs">
|
||||
{desc2}
|
||||
|
|
@ -711,19 +728,23 @@ const ToolComponent = ({
|
|||
{`(`}{numResults}{` result`}{numResults !== 1 ? 's' : ''}{`)`}
|
||||
</span>
|
||||
)}
|
||||
{isError && <AlertTriangle className='text-void-warning opacity-90 flex-shrink-0' size={12} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
// the py-1 here makes sure all elements in the container have py-2 total. this makes a nice animation effect during transition.
|
||||
className={`overflow-hidden transition-all duration-200 ease-in-out ${isExpanded ? 'opacity-100 py-1' : 'max-h-0 opacity-0'}`}
|
||||
>
|
||||
<div className="text-xs text-void-fg-4 px-2 py-1 bg-black bg-opacity-20 border border-void-border-4 border-opacity-50 rounded-sm">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
{/* children */}
|
||||
<div
|
||||
// the py-1 here makes sure all elements in the container have py-2 total. this makes a nice animation effect during transition.
|
||||
className={`overflow-hidden transition-all duration-200 ease-in-out ${isExpanded ? 'opacity-100 py-1' : 'max-h-0 opacity-0'}`}
|
||||
>
|
||||
<div className="text-void-fg-4 px-2 py-1 bg-black bg-opacity-20 border border-void-border-4 border-opacity-50 rounded-sm">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!requestToolId ? null : <ToolRequestAcceptRejectButtons voidToolId={requestToolId} isLastMessage={!!isLastMessage} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -994,42 +1015,15 @@ const AssistantMessageComponent = ({ chatMessage, isLoading, messageIdx, isLast
|
|||
|
||||
|
||||
|
||||
const ToolError = ({ title, desc1, errorMessage }: { title: string, desc1: string, errorMessage: string }) => {
|
||||
return (
|
||||
// px-2 py-1
|
||||
// <div className='flex gap-2 p-3 border border-void-border-3 text-void-fg-3 rounded bg-void-bg-2-alt'>
|
||||
// <AlertTriangle className='text-void-warning opacity-90 flex-shrink-0' size={20} />
|
||||
// <div className='flex flex-col'>
|
||||
// <span className='mb-1'>{title + ' error'}</span>
|
||||
// <div className='text-sm opacity-90'>{errorMessage}</div>
|
||||
// </div>
|
||||
// </div>
|
||||
<ToolComponent
|
||||
title={title}
|
||||
desc1={desc1}
|
||||
desc2={
|
||||
<span className="flex items-center flex-nowrap gap-1">
|
||||
<AlertTriangle className='text-void-warning opacity-90 flex-shrink-0' size={12} />
|
||||
Error
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<div className='text-xs text-wrap whitespace-pre-wrap break-all break-words'>{errorMessage}</div>
|
||||
</ToolComponent>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const toolNameToTitle: Record<ToolName, string> = {
|
||||
'read_file': 'Read file',
|
||||
'list_dir': 'Inspected folder',
|
||||
'pathname_search': 'Searched by file name',
|
||||
'search': 'Searched files',
|
||||
'create_uri': 'Created file',
|
||||
'delete_uri': 'Deleted file',
|
||||
'edit': 'Edited file',
|
||||
'terminal_command': 'Ran terminal command'
|
||||
'list_dir': 'Inspect folder',
|
||||
'pathname_search': 'Search by file name',
|
||||
'search': 'Search',
|
||||
'create_uri': 'Create file',
|
||||
'delete_uri': 'Delete file',
|
||||
'edit': 'Edit file',
|
||||
'terminal_command': 'Run terminal command'
|
||||
}
|
||||
const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName] | undefined): string => {
|
||||
|
||||
|
|
@ -1067,7 +1061,7 @@ const toolNameToDesc = (toolName: ToolName, _toolParams: ToolCallParams[ToolName
|
|||
}
|
||||
|
||||
|
||||
const ToolRequestAcceptRejectButtons = ({ toolRequest, messageIdx, isLast }: { toolRequest: ToolRequestApproval<ToolName> } & Omit<ChatBubbleProps, 'chatMessage'>) => {
|
||||
const ToolRequestAcceptRejectButtons = ({ voidToolId, isLastMessage: isLast }: { voidToolId: string, isLastMessage: boolean }) => {
|
||||
const accessor = useAccessor()
|
||||
const chatThreadsService = accessor.get('IChatThreadService')
|
||||
const metricsService = accessor.get('IMetricsService')
|
||||
|
|
@ -1076,16 +1070,16 @@ const ToolRequestAcceptRejectButtons = ({ toolRequest, messageIdx, isLast }: { t
|
|||
const [requestState, setRequestState] = useState<'accepted' | 'rejected' | 'awaiting_response'>(initRequestState)
|
||||
|
||||
const onAccept = useCallback(() => {
|
||||
chatThreadsService.approveTool(toolRequest.voidToolId)
|
||||
chatThreadsService.approveTool(voidToolId)
|
||||
setRequestState('accepted')
|
||||
metricsService.capture('Tool Request Accepted', {})
|
||||
}, [chatThreadsService, toolRequest.voidToolId, metricsService])
|
||||
}, [chatThreadsService, voidToolId, metricsService])
|
||||
|
||||
const onReject = useCallback(() => {
|
||||
chatThreadsService.rejectTool(toolRequest.voidToolId)
|
||||
chatThreadsService.rejectTool(voidToolId)
|
||||
setRequestState('rejected')
|
||||
metricsService.capture('Tool Request Rejected', {})
|
||||
}, [chatThreadsService, toolRequest.voidToolId, metricsService])
|
||||
}, [chatThreadsService, voidToolId, metricsService])
|
||||
|
||||
const approveButton = (
|
||||
<button
|
||||
|
|
@ -1130,7 +1124,7 @@ const ToolRequestAcceptRejectButtons = ({ toolRequest, messageIdx, isLast }: { t
|
|||
}
|
||||
|
||||
const toolNameToComponent: { [T in ToolName]: {
|
||||
requestWrapper: T extends ToolNameWithApproval ? ((props: { toolRequest: ToolRequestApproval<T> }) => React.ReactNode) : null,
|
||||
requestWrapper: T extends ToolNameWithApproval ? ((props: { toolRequest: ToolRequestApproval<T>, isLastMessage: boolean }) => React.ReactNode) : null,
|
||||
resultWrapper: (props: { toolMessage: ToolMessage<T> }) => React.ReactNode,
|
||||
} } = {
|
||||
'read_file': {
|
||||
|
|
@ -1139,25 +1133,27 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolNameToTitle[toolMessage.name]
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const { uri } = toolMessage.result.params ?? {}
|
||||
const desc1 = uri ? getBasename(uri.fsPath) : '';
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.result.type === 'error') {
|
||||
return <ToolError title={title} desc1={desc1} errorMessage={toolMessage.result.value} />
|
||||
if (toolMessage.result.type === 'rejected') return null
|
||||
|
||||
const isError = toolMessage.result.type === 'error'
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
|
||||
|
||||
if (toolMessage.result.type !== 'error') {
|
||||
const { value, params } = toolMessage.result
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
if (toolMessage.result.value.hasNextPage) componentParams.desc2 = `(AI can scroll for more)`
|
||||
}
|
||||
else {
|
||||
componentParams.children = <>
|
||||
{toolMessage.result.value}
|
||||
</>
|
||||
}
|
||||
|
||||
const { value, params } = toolMessage.result
|
||||
|
||||
return <ToolComponent title={title} desc1={desc1}>
|
||||
<div
|
||||
className="hover:brightness-125 hover:cursor-pointer transition-all duration-200 flex items-center flex-nowrap"
|
||||
onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }}
|
||||
>
|
||||
<div className="flex-shrink-0"><svg className="w-1 h-1 opacity-60 mr-1.5 fill-current" viewBox="0 0 100 40"><rect x="0" y="15" width="100" height="10" /></svg></div>
|
||||
{params.uri.fsPath}
|
||||
</div>
|
||||
{toolMessage.result.value.hasNextPage && (<div className="italic">AI can scroll for more content...</div>)}
|
||||
|
||||
</ToolComponent>
|
||||
return <ToolHeaderComponent {...componentParams} />
|
||||
},
|
||||
},
|
||||
'list_dir': {
|
||||
|
|
@ -1168,37 +1164,44 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
const explorerService = accessor.get('IExplorerService')
|
||||
const title = toolNameToTitle[toolMessage.name]
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.result.type === 'error') {
|
||||
return <ToolError title={title} desc1={desc1} errorMessage={toolMessage.result.value} />
|
||||
if (toolMessage.result.type === 'rejected') return null
|
||||
|
||||
const isError = toolMessage.result.type === 'error'
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
|
||||
|
||||
if (toolMessage.result.type !== 'error') {
|
||||
const { value, params } = toolMessage.result
|
||||
componentParams.numResults = value.children?.length
|
||||
componentParams.children = <>
|
||||
{value.children?.map((child, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="hover:brightness-125 hover:cursor-pointer transition-all duration-200 flex items-center flex-nowrap"
|
||||
onClick={() => {
|
||||
commandService.executeCommand('workbench.view.explorer');
|
||||
explorerService.select(child.uri, true);
|
||||
}}
|
||||
>
|
||||
<div className="flex-shrink-0"><svg className="w-1 h-1 opacity-60 mr-1.5 fill-current" viewBox="0 0 100 40"><rect x="0" y="15" width="100" height="10" /></svg></div>
|
||||
{`${child.name}${child.isDirectory ? '/' : ''}`}
|
||||
</div>
|
||||
))}
|
||||
{value.hasNextPage && (
|
||||
<div className="italic">
|
||||
{value.itemsRemaining} more items...
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
else {
|
||||
componentParams.children = <>
|
||||
{toolMessage.result.value}
|
||||
</>
|
||||
}
|
||||
|
||||
const { value, params } = toolMessage.result
|
||||
|
||||
return <ToolComponent
|
||||
title={title}
|
||||
desc1={desc1}
|
||||
numResults={value.children?.length}
|
||||
>
|
||||
{value.children?.map((child, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="hover:brightness-125 hover:cursor-pointer transition-all duration-200 flex items-center flex-nowrap"
|
||||
onClick={() => {
|
||||
commandService.executeCommand('workbench.view.explorer');
|
||||
explorerService.select(child.uri, true);
|
||||
}}
|
||||
>
|
||||
<div className="flex-shrink-0"><svg className="w-1 h-1 opacity-60 mr-1.5 fill-current" viewBox="0 0 100 40"><rect x="0" y="15" width="100" height="10" /></svg></div>
|
||||
{`${child.name}${child.isDirectory ? '/' : ''}`}
|
||||
</div>
|
||||
))}
|
||||
{value.hasNextPage && (
|
||||
<div className="italic">
|
||||
{value.itemsRemaining} more items...
|
||||
</div>
|
||||
)}
|
||||
</ToolComponent>
|
||||
return <ToolHeaderComponent {...componentParams} />
|
||||
}
|
||||
},
|
||||
'pathname_search': {
|
||||
|
|
@ -1208,19 +1211,16 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolNameToTitle[toolMessage.name]
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.result.type === 'error') {
|
||||
return <ToolError title={title} desc1={desc1} errorMessage={toolMessage.result.value} />
|
||||
}
|
||||
if (toolMessage.result.type === 'rejected') return null
|
||||
|
||||
const { value, params } = toolMessage.result
|
||||
const isError = toolMessage.result.type === 'error'
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
|
||||
|
||||
return (
|
||||
<ToolComponent
|
||||
title={title}
|
||||
desc1={desc1}
|
||||
numResults={value.uris.length}
|
||||
>
|
||||
if (toolMessage.result.type !== 'error') {
|
||||
const { value, params } = toolMessage.result
|
||||
componentParams.children = <>
|
||||
{value.uris.map((uri, i) => (
|
||||
<div
|
||||
key={i}
|
||||
|
|
@ -1238,8 +1238,15 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
More results available...
|
||||
</div>
|
||||
)}
|
||||
</ToolComponent>
|
||||
)
|
||||
</>
|
||||
}
|
||||
else {
|
||||
componentParams.children = <>
|
||||
{toolMessage.result.value}
|
||||
</>
|
||||
}
|
||||
|
||||
return <ToolHeaderComponent {...componentParams} />
|
||||
}
|
||||
},
|
||||
'search': {
|
||||
|
|
@ -1249,19 +1256,17 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolNameToTitle[toolMessage.name]
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.result.type === 'error') {
|
||||
return <ToolError title={title} desc1={desc1} errorMessage={toolMessage.result.value} />
|
||||
}
|
||||
if (toolMessage.result.type === 'rejected') return null
|
||||
|
||||
const { value, params } = toolMessage.result
|
||||
const isError = toolMessage.result.type === 'error'
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
|
||||
|
||||
return (
|
||||
<ToolComponent
|
||||
title={title}
|
||||
desc1={desc1}
|
||||
numResults={value.uris.length}
|
||||
>
|
||||
if (toolMessage.result.type !== 'error') {
|
||||
const { value, params } = toolMessage.result
|
||||
|
||||
componentParams.children = <>
|
||||
{value.uris.map((uri, i) => (
|
||||
<div key={i}
|
||||
className="hover:brightness-125 hover:cursor-pointer transition-all duration-200 flex items-center flex-nowrap"
|
||||
|
|
@ -1272,153 +1277,214 @@ const toolNameToComponent: { [T in ToolName]: {
|
|||
</div>
|
||||
))}
|
||||
{value.hasNextPage && (<div className="italic">More results available...</div>)}
|
||||
</ToolComponent>
|
||||
)
|
||||
</>
|
||||
}
|
||||
else {
|
||||
componentParams.children = <>
|
||||
{toolMessage.result.value}
|
||||
</>
|
||||
}
|
||||
return <ToolHeaderComponent {...componentParams} />
|
||||
}
|
||||
},
|
||||
|
||||
// ---
|
||||
|
||||
'create_uri': {
|
||||
requestWrapper: ({ toolRequest }) => {
|
||||
requestWrapper: ({ toolRequest, isLastMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolNameToTitle[toolRequest.name]
|
||||
const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params)
|
||||
return <ToolComponent title={title} desc1={desc1} />
|
||||
const icon = null
|
||||
|
||||
const isError = false
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon, isLastMessage, requestToolId: toolRequest.voidToolId }
|
||||
|
||||
const { params } = toolRequest
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
|
||||
return <ToolHeaderComponent title={title} desc1={desc1} />
|
||||
},
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolNameToTitle[toolMessage.name]
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.result.type === 'error') {
|
||||
return <ToolError title={title} desc1={desc1} errorMessage={toolMessage.result.value} />
|
||||
if (toolMessage.result.type === 'rejected') return null
|
||||
|
||||
const isError = toolMessage.result.type === 'error'
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
|
||||
|
||||
if (toolMessage.result.type !== 'error') {
|
||||
const { params } = toolMessage.result
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
}
|
||||
else {
|
||||
componentParams.children = <>
|
||||
{toolMessage.result.value}
|
||||
</>
|
||||
}
|
||||
|
||||
const { params } = toolMessage.result
|
||||
|
||||
return (
|
||||
<ToolComponent
|
||||
title={title}
|
||||
desc1={desc1}
|
||||
onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }}
|
||||
/>
|
||||
)
|
||||
return <ToolHeaderComponent {...componentParams} />
|
||||
}
|
||||
},
|
||||
'delete_uri': {
|
||||
requestWrapper: ({ toolRequest }) => {
|
||||
requestWrapper: ({ toolRequest, isLastMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolNameToTitle[toolRequest.name]
|
||||
const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params)
|
||||
return <ToolComponent title={title} desc1={desc1}
|
||||
onClick={() => { commandService.executeCommand('vscode.open', toolRequest.params.uri, { preview: true }) }}
|
||||
/>
|
||||
const icon = null
|
||||
|
||||
const isError = false
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon, isLastMessage, requestToolId: toolRequest.voidToolId }
|
||||
|
||||
const { params } = toolRequest
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
|
||||
return <ToolHeaderComponent {...componentParams} />
|
||||
},
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolNameToTitle[toolMessage.name]
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.result.type === 'error') {
|
||||
return <ToolError title={title} desc1={desc1} errorMessage={toolMessage.result.value} />
|
||||
if (toolMessage.result.type === 'rejected') return null
|
||||
|
||||
const isError = toolMessage.result.type === 'error'
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
|
||||
|
||||
if (toolMessage.result.type !== 'error') {
|
||||
const { params } = toolMessage.result
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
}
|
||||
else {
|
||||
componentParams.children = <>
|
||||
{toolMessage.result.value}
|
||||
</>
|
||||
}
|
||||
|
||||
const { params } = toolMessage.result
|
||||
|
||||
return (
|
||||
<ToolComponent
|
||||
title={title}
|
||||
desc1={desc1}
|
||||
onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }}
|
||||
/>
|
||||
)
|
||||
return <ToolHeaderComponent {...componentParams} />
|
||||
}
|
||||
},
|
||||
'edit': {
|
||||
requestWrapper: ({ toolRequest }) => {
|
||||
requestWrapper: ({ toolRequest, isLastMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolNameToTitle[toolRequest.name]
|
||||
const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params)
|
||||
return <ToolComponent title={title} desc1={desc1}
|
||||
onClick={() => { commandService.executeCommand('vscode.open', toolRequest.params.uri, { preview: true }) }}
|
||||
>
|
||||
<ChatMarkdownRender string={toolRequest.params.changeDescription} chatMessageLocation={undefined} />
|
||||
</ToolComponent>
|
||||
const icon = null
|
||||
|
||||
const isError = false
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon, isLastMessage, requestToolId: toolRequest.voidToolId }
|
||||
|
||||
const { params } = toolRequest
|
||||
componentParams.children = <ChatMarkdownRender string={params.changeDescription} chatMessageLocation={undefined} />
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
|
||||
return <ToolHeaderComponent {...componentParams} />
|
||||
},
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const title = toolNameToTitle[toolMessage.name]
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.result.type === 'error') {
|
||||
return <ToolError title={title} desc1={desc1} errorMessage={toolMessage.result.value} />
|
||||
if (toolMessage.result.type === 'rejected') return null
|
||||
|
||||
const isError = toolMessage.result.type === 'error'
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
|
||||
|
||||
if (toolMessage.result.type !== 'error') {
|
||||
const { params } = toolMessage.result
|
||||
componentParams.children = <ChatMarkdownRender string={params.changeDescription} chatMessageLocation={undefined} />
|
||||
componentParams.onClick = () => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }
|
||||
}
|
||||
else {
|
||||
componentParams.children = <>
|
||||
{toolMessage.result.value}
|
||||
</>
|
||||
}
|
||||
|
||||
const { params } = toolMessage.result
|
||||
|
||||
return (
|
||||
<ToolComponent
|
||||
title={title}
|
||||
desc1={desc1}
|
||||
onClick={() => { commandService.executeCommand('vscode.open', params.uri, { preview: true }) }}
|
||||
/>
|
||||
)
|
||||
return <ToolHeaderComponent {...componentParams} />
|
||||
}
|
||||
},
|
||||
'terminal_command': {
|
||||
requestWrapper: ({ toolRequest }) => {
|
||||
requestWrapper: ({ toolRequest, isLastMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const terminalToolsService = accessor.get('ITerminalToolService')
|
||||
const title = toolNameToTitle[toolRequest.name]
|
||||
const desc1 = toolNameToDesc(toolRequest.name, toolRequest.params)
|
||||
const icon = null
|
||||
|
||||
const isError = false
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon, isLastMessage, requestToolId: toolRequest.voidToolId }
|
||||
|
||||
const { proposedTerminalId } = toolRequest.params
|
||||
if (terminalToolsService.terminalExists(proposedTerminalId))
|
||||
componentParams.onClick = () => terminalToolsService.openTerminal(proposedTerminalId)
|
||||
|
||||
if (!toolRequest.params.waitForCompletion)
|
||||
componentParams.desc2 = '(background task)'
|
||||
|
||||
const { waitForCompletion, command, proposedTerminalId } = toolRequest.params
|
||||
return <ToolComponent title={title} desc1={desc1} desc2={waitForCompletion ? null : '(background task)'}
|
||||
// TODO!!! open terminal
|
||||
/>
|
||||
return <ToolHeaderComponent {...componentParams} />
|
||||
},
|
||||
resultWrapper: ({ toolMessage }) => {
|
||||
const accessor = useAccessor()
|
||||
const commandService = accessor.get('ICommandService')
|
||||
const terminalToolsService = accessor.get('ITerminalToolService')
|
||||
const title = toolNameToTitle[toolMessage.name]
|
||||
const desc1 = toolNameToDesc(toolMessage.name, toolMessage.result.params)
|
||||
const icon = null
|
||||
|
||||
if (toolMessage.result.type === 'error') {
|
||||
return <ToolError title={title} desc1={desc1} errorMessage={toolMessage.result.value} />
|
||||
if (toolMessage.result.type === 'rejected') return null
|
||||
|
||||
const isError = toolMessage.result.type === 'error'
|
||||
const componentParams: ToolHeaderParams = { title, desc1, isError, icon }
|
||||
|
||||
if (toolMessage.result.type !== 'error') {
|
||||
const { command } = toolMessage.result.params
|
||||
const { terminalId, resolveReason, result } = toolMessage.result.value
|
||||
|
||||
componentParams.children = <div className='font-mono whitespace-pre text-nowrap text-xs overflow-auto bg-void-bg-1'>
|
||||
|
||||
<div
|
||||
className='cursor-pointer'
|
||||
onClick={() => terminalToolsService.openTerminal(terminalId)}
|
||||
>$ {command}</div>
|
||||
|
||||
<hr className='border-void-border-1' />
|
||||
|
||||
{resolveReason.type === 'bgtask' ? 'Result so far:\n' : null}
|
||||
{result}
|
||||
{resolveReason.type === 'done' ? (resolveReason.exitCode !== 0 ? `\nError: exit code ${resolveReason.exitCode}` : null)
|
||||
: resolveReason.type === 'bgtask' ? null :
|
||||
resolveReason.type === 'timeout' ? `\n(partial results; request timed out)` :
|
||||
resolveReason.type === 'toofull' ? `\n(truncated)`
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
|
||||
if (resolveReason.type === 'bgtask')
|
||||
componentParams.desc2 = '(background task)'
|
||||
}
|
||||
else {
|
||||
componentParams.children = <>
|
||||
{toolMessage.result.value}
|
||||
</>
|
||||
}
|
||||
|
||||
// TODO!!! open terminal
|
||||
|
||||
const { command } = toolMessage.result.params
|
||||
const { terminalId, resolveReason, result } = toolMessage.result.value
|
||||
|
||||
return (
|
||||
<ToolComponent
|
||||
title={title}
|
||||
desc1={desc1}
|
||||
desc2={resolveReason.type === 'bgtask' ? '(background task)' : null}
|
||||
>
|
||||
|
||||
<div className='font-mono whitespace-pre text-nowrap text-xs overflow-auto bg-void-bg-1'>
|
||||
{resolveReason.type === 'bgtask' ? 'Result so far:' : null}
|
||||
{result}
|
||||
{
|
||||
resolveReason.type === 'done' ? (resolveReason.exitCode !== 0 ? `
|
||||
Error: exit code ${resolveReason.exitCode}` : null)
|
||||
: resolveReason.type === 'bgtask' ? null :
|
||||
resolveReason.type === 'timeout' ? `
|
||||
(partial results; request timed out)` :
|
||||
resolveReason.type === 'toofull' ? `
|
||||
(truncated)`
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
</ToolComponent>
|
||||
)
|
||||
return <ToolHeaderComponent {...componentParams} />
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -1448,10 +1514,8 @@ const ChatBubble = ({ chatMessage, isLoading, messageIdx, isLast }: ChatBubblePr
|
|||
/>
|
||||
}
|
||||
else if (role === 'tool_request') {
|
||||
const isLastMessage = true // TODO!!! fix this
|
||||
if (!isLastMessage) return null
|
||||
const ToolRequestWrapper = toolNameToComponent[chatMessage.name].requestWrapper as React.FC<{ toolRequest: any }> // ts isnt smart enough...
|
||||
return <ToolRequestWrapper toolRequest={chatMessage} />
|
||||
const ToolRequestWrapper = toolNameToComponent[chatMessage.name].requestWrapper as React.FC<{ toolRequest: any, isLastMessage: boolean }> // ts isnt smart enough...
|
||||
return <ToolRequestWrapper toolRequest={chatMessage} isLastMessage={isLast} />
|
||||
|
||||
}
|
||||
else if (role === 'tool') {
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import { IPathService } from '../../../../../../../workbench/services/path/commo
|
|||
import { IMetricsService } from '../../../../../../../workbench/contrib/void/common/metricsService.js'
|
||||
import { URI } from '../../../../../../../base/common/uri.js'
|
||||
import { IChatThreadService, ThreadsState, ThreadStreamState } from '../../../chatThreadService.js'
|
||||
import { ITerminalToolService } from '../../../terminalToolService.js'
|
||||
|
||||
|
||||
|
||||
|
|
@ -232,6 +233,7 @@ const getReactAccessor = (accessor: ServicesAccessor) => {
|
|||
IConfigurationService: accessor.get(IConfigurationService),
|
||||
IPathService: accessor.get(IPathService),
|
||||
IMetricsService: accessor.get(IMetricsService),
|
||||
ITerminalToolService: accessor.get(ITerminalToolService)
|
||||
|
||||
} as const
|
||||
return reactAccessor
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { removeAnsiEscapeCodes } from '../../../../base/common/strings.js';
|
|||
import { registerSingleton, InstantiationType } from '../../../../platform/instantiation/common/extensions.js';
|
||||
import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';
|
||||
import { TerminalLocation } from '../../../../platform/terminal/common/terminal.js';
|
||||
import { ITerminalService, ITerminalInstance, ITerminalGroupService } from '../../../../workbench/contrib/terminal/browser/terminal.js';
|
||||
import { ITerminalService, ITerminalInstance } from '../../../../workbench/contrib/terminal/browser/terminal.js';
|
||||
import { ResolveReason } from '../common/toolsServiceTypes.js';
|
||||
import { MAX_TERMINAL_CHARS_PAGE, TERMINAL_BG_WAIT_TIME, TERMINAL_TIMEOUT_TIME } from './toolsService.js';
|
||||
|
||||
|
|
@ -17,8 +17,10 @@ import { MAX_TERMINAL_CHARS_PAGE, TERMINAL_BG_WAIT_TIME, TERMINAL_TIMEOUT_TIME }
|
|||
export interface ITerminalToolService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
runCommand(command: string, proposedTerminalId: string, waitForCompletion: boolean): Promise<{ terminalId: string, didCreateTerminal: boolean, result: string, resolveReason: ResolveReason }>;
|
||||
listTerminalIds(): string[];
|
||||
runCommand(command: string, proposedTerminalId: string, waitForCompletion: boolean): Promise<{ terminalId: string, didCreateTerminal: boolean, result: string, resolveReason: ResolveReason }>;
|
||||
openTerminal(terminalId: string): Promise<void>
|
||||
terminalExists(terminalId: string): boolean
|
||||
}
|
||||
|
||||
export const ITerminalToolService = createDecorator<ITerminalToolService>('TerminalToolService');
|
||||
|
|
@ -54,16 +56,31 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
|
||||
constructor(
|
||||
@ITerminalService private readonly terminalService: ITerminalService,
|
||||
@ITerminalGroupService private readonly terminalGroupService: ITerminalGroupService,
|
||||
) {
|
||||
super();
|
||||
|
||||
// runs on ALL terminals for simplicity
|
||||
const initializeTerminal = (terminal: ITerminalInstance) => {
|
||||
// when exit, remove
|
||||
const d = terminal.onExit(() => {
|
||||
const terminalId = idOfName(terminal.title)
|
||||
if (terminalId !== null && (terminalId in this.terminalInstanceOfId)) delete this.terminalInstanceOfId[terminalId]
|
||||
d.dispose()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// initialize any terminals that are already open
|
||||
for (const terminal of terminalService.instances) {
|
||||
const proposedTerminalId = idOfName(terminal.title)
|
||||
if (proposedTerminalId) this.terminalInstanceOfId[proposedTerminalId] = terminal
|
||||
|
||||
initializeTerminal(terminal)
|
||||
}
|
||||
console.log('Initialized terminal instances:', this.terminalInstanceOfId)
|
||||
|
||||
this._register(
|
||||
terminalService.onDidCreateInstance(terminal => { initializeTerminal(terminal) })
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -91,16 +108,47 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
private async _getOrCreateTerminal(proposedTerminalId: string) {
|
||||
// if terminal ID exists, return it
|
||||
if (proposedTerminalId in this.terminalInstanceOfId) return { terminalId: proposedTerminalId, didCreateTerminal: false }
|
||||
|
||||
// create new terminal and return its ID
|
||||
const terminalId = this.getValidNewTerminalId();
|
||||
const terminal = await this.terminalService.createTerminal({
|
||||
location: TerminalLocation.Panel,
|
||||
config: { name: nameOfId(terminalId), title: nameOfId(terminalId) }
|
||||
config: { name: nameOfId(terminalId), title: nameOfId(terminalId) },
|
||||
})
|
||||
|
||||
|
||||
// when a new terminal is created, there is an initial command that gets run which is empty, wait for it to end before returning
|
||||
const disposables: IDisposable[] = []
|
||||
const waitForMount = new Promise<void>(res => {
|
||||
let data = ''
|
||||
const d = terminal.onData(newData => {
|
||||
data += newData
|
||||
if (isCommandComplete(data)) { res() }
|
||||
})
|
||||
disposables.push(d)
|
||||
})
|
||||
const waitForTimeout = new Promise<void>(res => { setTimeout(() => { res() }, 1000) })
|
||||
|
||||
await Promise.any([waitForMount, waitForTimeout,])
|
||||
disposables.forEach(d => d.dispose())
|
||||
|
||||
this.terminalInstanceOfId[terminalId] = terminal
|
||||
return { terminalId, didCreateTerminal: true }
|
||||
}
|
||||
|
||||
terminalExists(terminalId: string): boolean {
|
||||
return terminalId in this.terminalInstanceOfId
|
||||
}
|
||||
|
||||
|
||||
openTerminal: ITerminalToolService['openTerminal'] = async (terminalId) => {
|
||||
if (!terminalId) return
|
||||
|
||||
const terminal = this.terminalInstanceOfId[terminalId]
|
||||
this.terminalService.setActiveInstance(terminal)
|
||||
await this.terminalService.focusActiveInstance()
|
||||
}
|
||||
|
||||
|
||||
|
||||
runCommand: ITerminalToolService['runCommand'] = async (command, proposedTerminalId, waitForCompletion) => {
|
||||
|
|
@ -109,18 +157,22 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
const terminal = this.terminalInstanceOfId[terminalId];
|
||||
if (!terminal) throw new Error(`Unexpected internal error: Terminal with ID ${terminalId} did not exist.`);
|
||||
|
||||
this.terminalGroupService.focusInstance(terminal)
|
||||
// focus the terminal about to run
|
||||
this.terminalService.setActiveInstance(terminal)
|
||||
await this.terminalService.focusActiveInstance()
|
||||
|
||||
let result: string = ''
|
||||
let resolveReason: ResolveReason | undefined = undefined
|
||||
|
||||
const disposables: IDisposable[] = []
|
||||
|
||||
// onFullPage
|
||||
const waitUntilFullPage = new Promise<void>((res, rej) => {
|
||||
const d1 = terminal.onData(async newData => {
|
||||
const waitUntilDone = new Promise<void>((res, rej) => {
|
||||
const d2 = terminal.onData(async newData => {
|
||||
if (resolveReason) return
|
||||
|
||||
result += newData
|
||||
|
||||
// onPageFull
|
||||
if (result.length > MAX_TERMINAL_CHARS_PAGE) {
|
||||
result = result.substring(0, MAX_TERMINAL_CHARS_PAGE)
|
||||
await terminal.sendText('\x03', true) // interrupt the terminal with Ctrl+C
|
||||
|
|
@ -128,14 +180,8 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
res()
|
||||
return
|
||||
}
|
||||
})
|
||||
disposables.push(d1)
|
||||
})
|
||||
|
||||
// onDone
|
||||
const waitUntilDone = new Promise<void>((res, rej) => {
|
||||
const d2 = terminal.onData(newData => {
|
||||
if (resolveReason) return
|
||||
// onDone
|
||||
const isDone = isCommandComplete(result)
|
||||
if (isDone) {
|
||||
resolveReason = { type: 'done', exitCode: isDone.exitCode }
|
||||
|
|
@ -157,12 +203,12 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
await terminal.sendText('\x03', true) // interrupt the terminal with Ctrl+C
|
||||
resolveReason = { type: waitForCompletion ? 'timeout' : 'bgtask' }
|
||||
res()
|
||||
return
|
||||
}, (waitForCompletion ? TERMINAL_TIMEOUT_TIME : TERMINAL_BG_WAIT_TIME) * 1000)
|
||||
})
|
||||
|
||||
await Promise.any([
|
||||
waitUntilDone,
|
||||
waitUntilFullPage,
|
||||
waitUntilTimeout,
|
||||
])
|
||||
|
||||
|
|
@ -170,14 +216,11 @@ export class TerminalToolService extends Disposable implements ITerminalToolServ
|
|||
|
||||
if (!resolveReason) throw new Error('Unexpected internal error: Promise.any should have resolved with a reason.')
|
||||
|
||||
console.log('res', { terminalId, didCreateTerminal, result, resolveReason })
|
||||
|
||||
result = removeAnsiEscapeCodes(result)
|
||||
.split('\n').slice(1, -1) // remove first and last line (first = command, last = andrewpareles/void %)
|
||||
.join('\n')
|
||||
|
||||
console.log('TerminalToolService: Command completed:', JSON.stringify(result))
|
||||
|
||||
return { terminalId, didCreateTerminal, result, resolveReason }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ type ToolResultToString = { [T in ToolName]: (p: ToolCallParams[T], result: Tool
|
|||
// pagination info
|
||||
const MAX_FILE_CHARS_PAGE = 50_000
|
||||
const MAX_CHILDREN_URIs_PAGE = 500
|
||||
export const MAX_TERMINAL_CHARS_PAGE = 50_000
|
||||
export const MAX_TERMINAL_CHARS_PAGE = 20_000
|
||||
export const TERMINAL_TIMEOUT_TIME = 15
|
||||
export const TERMINAL_BG_WAIT_TIME = 1
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ const validateStr = (argName: string, value: unknown) => {
|
|||
}
|
||||
|
||||
|
||||
// TODO!!!! check to make sure in workspace
|
||||
// We are NOT checking to make sure in workspace
|
||||
const validateURI = (uriStr: unknown) => {
|
||||
if (typeof uriStr !== 'string') throw new Error('Error: provided uri must be a string.')
|
||||
|
||||
|
|
@ -152,6 +152,14 @@ const validateWaitForCompletion = (b: unknown) => {
|
|||
}
|
||||
return true // default is true
|
||||
}
|
||||
|
||||
|
||||
const checkIfIsFolder = (uriStr: string) => {
|
||||
uriStr = uriStr.trim()
|
||||
if (uriStr.endsWith('/') || uriStr.endsWith('\\')) return true
|
||||
return false
|
||||
}
|
||||
|
||||
export interface IToolsService {
|
||||
readonly _serviceBrand: undefined;
|
||||
validateParams: ValidateParams;
|
||||
|
|
@ -224,17 +232,21 @@ export class ToolsService implements IToolsService {
|
|||
|
||||
create_uri: async (params: string) => {
|
||||
const o = validateJSON(params)
|
||||
const { uri: uriStr } = o
|
||||
const uri = validateURI(uriStr)
|
||||
return { uri }
|
||||
const { uri: uriUnknown } = o
|
||||
const uri = validateURI(uriUnknown)
|
||||
const uriStr = validateStr('uri', uriUnknown)
|
||||
const isFolder = checkIfIsFolder(uriStr)
|
||||
return { uri, isFolder }
|
||||
},
|
||||
|
||||
delete_uri: async (params: string) => {
|
||||
const o = validateJSON(params)
|
||||
const { uri: uriStr, params: paramsStr } = o
|
||||
const uri = validateURI(uriStr)
|
||||
const { uri: uriUnknown, params: paramsStr } = o
|
||||
const uri = validateURI(uriUnknown)
|
||||
const isRecursive = validateRecursiveParamStr(paramsStr)
|
||||
return { uri, isRecursive }
|
||||
const uriStr = validateStr('uri', uriUnknown)
|
||||
const isFolder = checkIfIsFolder(uriStr)
|
||||
return { uri, isRecursive, isFolder }
|
||||
},
|
||||
|
||||
edit: async (params: string) => {
|
||||
|
|
@ -242,7 +254,6 @@ export class ToolsService implements IToolsService {
|
|||
const { uri: uriStr, changeDescription: changeDescriptionUnknown } = o
|
||||
const uri = validateURI(uriStr)
|
||||
const changeDescription = validateStr('changeDescription', changeDescriptionUnknown)
|
||||
|
||||
return { uri, changeDescription }
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,12 @@ export type ToolMessage<T extends ToolName> = {
|
|||
paramsStr: string; // internal use
|
||||
id: string; // apis require this tool use id
|
||||
content: string; // give this result to LLM
|
||||
result: { type: 'success'; params: ToolCallParams[T]; value: ToolResultType[T], } | { type: 'error'; params: ToolCallParams[T] | undefined; value: string }; // give this result to user
|
||||
|
||||
// if rejected, don't show in chat
|
||||
result:
|
||||
| { type: 'success'; params: ToolCallParams[T]; value: ToolResultType[T], }
|
||||
| { type: 'error'; params: ToolCallParams[T] | undefined; value: string }
|
||||
| { type: 'rejected'; params: ToolCallParams[T]; value: string }
|
||||
}
|
||||
export type ToolRequestApproval<T extends ToolName> = {
|
||||
role: 'tool_request';
|
||||
|
|
|
|||
|
|
@ -17,9 +17,8 @@ export const tripleTick = ['```', '```']
|
|||
|
||||
export const editToolDesc_toolDescription = `\
|
||||
A high level description of the change you'd like to make in the file. This description will be handed to a dumber, faster model that will quickly apply the change. \
|
||||
Typically the best description you can give here is a high level view of the final code you'd like to see. For example, you can write code excerpt(s) with "// ... existing code ..." comments to help you write less. \
|
||||
However, you are allowed to describe the change using whatever text/language you like, especially if the change is better described without code. \
|
||||
Do NOT output the whole file if possible, and try to write as LITTLE as needed to describe the change.`
|
||||
Typically the best description you can give here is a single code block of the form:\n${tripleTick[0]}\n// ... existing code ...\n{{change 1}}\n// ... existing code ...\n{{change2}}\n// ... existing code ...\n{{change 3}}\n...\n${tripleTick[1]}.\
|
||||
Do NOT output the whole file here if possible, and try to write as LITTLE as needed to describe the change.`
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -148,8 +148,8 @@ export type ToolCallParams = {
|
|||
'search': { queryStr: string, pageNumber: number },
|
||||
// ---
|
||||
'edit': { uri: URI, changeDescription: string },
|
||||
'create_uri': { uri: URI },
|
||||
'delete_uri': { uri: URI, isRecursive: boolean },
|
||||
'create_uri': { uri: URI, isFolder: boolean },
|
||||
'delete_uri': { uri: URI, isRecursive: boolean, isFolder: boolean },
|
||||
'terminal_command': { command: string, proposedTerminalId: string, waitForCompletion: boolean },
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -385,14 +385,13 @@ export type GlobalSettings = {
|
|||
autoRefreshModels: boolean;
|
||||
aiInstructions: string;
|
||||
enableAutocomplete: boolean;
|
||||
chatMode: ChatMode;
|
||||
}
|
||||
|
||||
export const defaultGlobalSettings: GlobalSettings = {
|
||||
autoRefreshModels: true,
|
||||
aiInstructions: '',
|
||||
enableAutocomplete: false,
|
||||
chatMode: 'agent',
|
||||
|
||||
}
|
||||
|
||||
export type GlobalSettingName = keyof GlobalSettings
|
||||
|
|
|
|||
|
|
@ -285,7 +285,6 @@ const prepareMessages_tools_anthropic = ({ messages }: { messages: InternalLLMCh
|
|||
role: 'user',
|
||||
content: [
|
||||
...[{ type: 'tool_result', tool_use_id: currMsg.id, content: currMsg.content || EMPTY_TOOL_CONTENT }] as const,
|
||||
...currMsg.content ? [{ type: 'text', text: currMsg.content }] as const : [],
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue