mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-29 23:12:38 +08:00
feat(tools): add new agent tools for enhanced functionality
- Introduced new tools: EditTool, MultiEditTool, BashOutputTool, NotebookEditTool, and ExitPlanModeTool. - Updated MessageTool to support new tool types. - Enhanced ReadTool to handle output as an array of text outputs. - Improved type definitions to accommodate new tools and their inputs/outputs.
This commit is contained in:
parent
dfc593f2e1
commit
305a454ffd
@ -0,0 +1,190 @@
|
||||
import { AccordionItem, Card, CardBody, Chip, Code } from '@heroui/react'
|
||||
import { CheckCircle, Terminal, XCircle } from 'lucide-react'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { ToolTitle } from './GenericTools'
|
||||
import type { BashOutputToolInput, BashOutputToolOutput } from './types'
|
||||
import { AgentToolsType } from './types'
|
||||
|
||||
interface ParsedBashOutput {
|
||||
status?: string
|
||||
exit_code?: number
|
||||
stdout?: string
|
||||
stderr?: string
|
||||
timestamp?: string
|
||||
tool_use_error?: string
|
||||
}
|
||||
|
||||
export function BashOutputTool({ input, output }: { input: BashOutputToolInput; output?: BashOutputToolOutput }) {
|
||||
// 解析 XML 输出
|
||||
const parsedOutput = useMemo(() => {
|
||||
if (!output) return null
|
||||
|
||||
try {
|
||||
const parser = new DOMParser()
|
||||
// 检查是否包含 tool_use_error 标签
|
||||
const hasToolError = output.includes('<tool_use_error>')
|
||||
// 包装成有效的 XML(如果还没有根元素)
|
||||
const xmlStr = output.includes('<status>') || hasToolError ? `<root>${output}</root>` : output
|
||||
const xmlDoc = parser.parseFromString(xmlStr, 'application/xml')
|
||||
|
||||
// 检查是否有解析错误
|
||||
const parserError = xmlDoc.querySelector('parsererror')
|
||||
if (parserError) {
|
||||
return null
|
||||
}
|
||||
|
||||
const getElementText = (tagName: string): string | undefined => {
|
||||
const element = xmlDoc.getElementsByTagName(tagName)[0]
|
||||
return element?.textContent?.trim()
|
||||
}
|
||||
|
||||
const result: ParsedBashOutput = {
|
||||
status: getElementText('status'),
|
||||
exit_code: getElementText('exit_code') ? parseInt(getElementText('exit_code')!) : undefined,
|
||||
stdout: getElementText('stdout'),
|
||||
stderr: getElementText('stderr'),
|
||||
timestamp: getElementText('timestamp'),
|
||||
tool_use_error: getElementText('tool_use_error')
|
||||
}
|
||||
|
||||
return result
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}, [output])
|
||||
|
||||
// 获取状态配置
|
||||
const statusConfig = useMemo(() => {
|
||||
if (!parsedOutput) return null
|
||||
|
||||
// 如果有 tool_use_error,直接显示错误状态
|
||||
if (parsedOutput.tool_use_error) {
|
||||
return {
|
||||
color: 'danger',
|
||||
icon: <XCircle className="h-3.5 w-3.5" />,
|
||||
text: 'Error'
|
||||
} as const
|
||||
}
|
||||
|
||||
const isCompleted = parsedOutput.status === 'completed'
|
||||
const isSuccess = parsedOutput.exit_code === 0
|
||||
|
||||
return {
|
||||
color: isCompleted && isSuccess ? 'success' : isCompleted && !isSuccess ? 'danger' : 'warning',
|
||||
icon:
|
||||
isCompleted && isSuccess ? (
|
||||
<CheckCircle className="h-3.5 w-3.5" />
|
||||
) : isCompleted && !isSuccess ? (
|
||||
<XCircle className="h-3.5 w-3.5" />
|
||||
) : (
|
||||
<Terminal className="h-3.5 w-3.5" />
|
||||
),
|
||||
text: isCompleted ? (isSuccess ? 'Success' : 'Failed') : 'Running'
|
||||
} as const
|
||||
}, [parsedOutput])
|
||||
|
||||
return (
|
||||
<AccordionItem
|
||||
key={AgentToolsType.BashOutput}
|
||||
aria-label="BashOutput Tool"
|
||||
title={
|
||||
<ToolTitle
|
||||
icon={<Terminal className="h-4 w-4" />}
|
||||
label="Bash Output"
|
||||
params={
|
||||
<div className="flex items-center gap-2">
|
||||
<Code size="sm" className="py-0 text-xs">
|
||||
{input.bash_id}
|
||||
</Code>
|
||||
{statusConfig && (
|
||||
<Chip
|
||||
size="sm"
|
||||
color={statusConfig.color}
|
||||
variant="flat"
|
||||
startContent={statusConfig.icon}
|
||||
className="h-5">
|
||||
{statusConfig.text}
|
||||
</Chip>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
}
|
||||
classNames={{
|
||||
content: 'space-y-3 px-1'
|
||||
}}>
|
||||
{parsedOutput ? (
|
||||
<>
|
||||
{/* Status Info */}
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{parsedOutput.exit_code !== undefined && (
|
||||
<Chip size="sm" color={parsedOutput.exit_code === 0 ? 'success' : 'danger'} variant="flat">
|
||||
Exit Code: {parsedOutput.exit_code}
|
||||
</Chip>
|
||||
)}
|
||||
{parsedOutput.timestamp && (
|
||||
<Code size="sm" className="py-0 text-xs">
|
||||
{new Date(parsedOutput.timestamp).toLocaleString()}
|
||||
</Code>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Standard Output */}
|
||||
{parsedOutput.stdout && (
|
||||
<Card className="bg-default-50 dark:bg-default-900/20" shadow="none">
|
||||
<CardBody className="p-3">
|
||||
<div className="mb-2 font-medium text-default-600 text-xs">stdout:</div>
|
||||
<pre className="whitespace-pre-wrap font-mono text-default-700 text-xs dark:text-default-300">
|
||||
{parsedOutput.stdout}
|
||||
</pre>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Standard Error */}
|
||||
{parsedOutput.stderr && (
|
||||
<Card
|
||||
className="border border-danger-200 bg-danger-50/30 dark:border-danger-800 dark:bg-danger-900/10"
|
||||
shadow="none">
|
||||
<CardBody className="p-3">
|
||||
<div className="mb-2 font-medium text-danger-600 text-xs">stderr:</div>
|
||||
<pre className="whitespace-pre-wrap font-mono text-danger-600 text-xs dark:text-danger-400">
|
||||
{parsedOutput.stderr}
|
||||
</pre>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Tool Use Error */}
|
||||
{parsedOutput.tool_use_error && (
|
||||
<Card
|
||||
className="border border-danger-200 bg-danger-50/30 dark:border-danger-800 dark:bg-danger-900/10"
|
||||
shadow="none">
|
||||
<CardBody className="p-3">
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<XCircle className="h-4 w-4 text-danger" />
|
||||
<span className="font-medium text-danger-600 text-xs">Error:</span>
|
||||
</div>
|
||||
<pre className="whitespace-pre-wrap font-mono text-danger-600 text-xs dark:text-danger-400">
|
||||
{parsedOutput.tool_use_error}
|
||||
</pre>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
// 原始输出(如果解析失败或非 XML 格式)
|
||||
output && (
|
||||
<Card className="bg-default-50 dark:bg-default-900/20" shadow="none">
|
||||
<CardBody className="p-3">
|
||||
<pre className="whitespace-pre-wrap font-mono text-default-700 text-xs dark:text-default-300">
|
||||
{output}
|
||||
</pre>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)
|
||||
)}
|
||||
</AccordionItem>
|
||||
)
|
||||
}
|
||||
@ -21,7 +21,7 @@ export function BashTool({ input, output }: { input: BashToolInputType; output?:
|
||||
/>
|
||||
}
|
||||
subtitle={
|
||||
<Code size="sm" className="line-clamp-1 w-max max-w-full text-ellipsis text-xs">
|
||||
<Code size="sm" className="line-clamp-1 w-max max-w-full text-ellipsis py-0 text-xs">
|
||||
{input.command}
|
||||
</Code>
|
||||
}>
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
import { AccordionItem } from '@heroui/react'
|
||||
import { FileEdit } from 'lucide-react'
|
||||
|
||||
import { ToolTitle } from './GenericTools'
|
||||
import type { EditToolInput, EditToolOutput } from './types'
|
||||
import { AgentToolsType } from './types'
|
||||
|
||||
// 处理多行文本显示
|
||||
export const renderCodeBlock = (content: string, variant: 'old' | 'new') => {
|
||||
const lines = content.split('\n')
|
||||
const textColorClass =
|
||||
variant === 'old' ? 'text-danger-600 dark:text-danger-400' : 'text-success-600 dark:text-success-400'
|
||||
|
||||
return (
|
||||
// 删除线
|
||||
<pre className={`whitespace-pre-wrap font-mono text-xs ${textColorClass}`}>
|
||||
{lines.map((line, idx) => (
|
||||
<div key={idx} className="flex hover:bg-default-100/50 dark:hover:bg-default-900/50">
|
||||
<span className="mr-3 min-w-[2rem] select-none text-right opacity-50">
|
||||
{variant === 'old' && '-'}
|
||||
{variant === 'new' && '+'}
|
||||
{idx + 1}
|
||||
</span>
|
||||
<span className={`flex-1 ${variant === 'old' && 'line-through'}`}>{line || ' '}</span>
|
||||
</div>
|
||||
))}
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
|
||||
export function EditTool({ input, output }: { input: EditToolInput; output?: EditToolOutput }) {
|
||||
return (
|
||||
<AccordionItem
|
||||
key={AgentToolsType.Edit}
|
||||
aria-label="Edit Tool"
|
||||
title={<ToolTitle icon={<FileEdit className="h-4 w-4" />} label="Edit" params={input.file_path} />}>
|
||||
{/* Diff View */}
|
||||
{/* Old Content */}
|
||||
{renderCodeBlock(input.old_string, 'old')}
|
||||
{/* New Content */}
|
||||
{renderCodeBlock(input.new_string, 'new')}
|
||||
{/* Output */}
|
||||
{output}
|
||||
</AccordionItem>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
import { AccordionItem } from '@heroui/react'
|
||||
import { DoorOpen } from 'lucide-react'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
|
||||
import { ToolTitle } from './GenericTools'
|
||||
import type { ExitPlanModeToolInput, ExitPlanModeToolOutput } from './types'
|
||||
import { AgentToolsType } from './types'
|
||||
|
||||
export function ExitPlanModeTool({ input, output }: { input: ExitPlanModeToolInput; output?: ExitPlanModeToolOutput }) {
|
||||
return (
|
||||
<AccordionItem
|
||||
key={AgentToolsType.ExitPlanMode}
|
||||
aria-label="ExitPlanMode Tool"
|
||||
title={
|
||||
<ToolTitle
|
||||
icon={<DoorOpen className="h-4 w-4" />}
|
||||
label="ExitPlanMode"
|
||||
stats={`${input.plan.split('\n\n').length} plans`}
|
||||
/>
|
||||
}>
|
||||
{<ReactMarkdown>{input.plan + '\n\n' + (output ?? '')}</ReactMarkdown>}
|
||||
</AccordionItem>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import { AccordionItem } from '@heroui/react'
|
||||
import { FileText } from 'lucide-react'
|
||||
|
||||
import { renderCodeBlock } from './EditTool'
|
||||
import { ToolTitle } from './GenericTools'
|
||||
import type { MultiEditToolInput, MultiEditToolOutput } from './types'
|
||||
import { AgentToolsType } from './types'
|
||||
|
||||
export function MultiEditTool({ input, output }: { input: MultiEditToolInput; output?: MultiEditToolOutput }) {
|
||||
return (
|
||||
<AccordionItem
|
||||
key={AgentToolsType.MultiEdit}
|
||||
aria-label="MultiEdit Tool"
|
||||
title={<ToolTitle icon={<FileText className="h-4 w-4" />} label="MultiEdit" params={input.file_path} />}>
|
||||
{input.edits.map((edit, index) => (
|
||||
<div key={index}>
|
||||
{renderCodeBlock(edit.old_string, 'old')}
|
||||
{renderCodeBlock(edit.new_string, 'new')}
|
||||
</div>
|
||||
))}
|
||||
</AccordionItem>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import { AccordionItem } from '@heroui/react'
|
||||
import { FileText } from 'lucide-react'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
|
||||
import { ToolTitle } from './GenericTools'
|
||||
import type { NotebookEditToolInput, NotebookEditToolOutput } from './types'
|
||||
import { AgentToolsType } from './types'
|
||||
|
||||
export function NotebookEditTool({ input, output }: { input: NotebookEditToolInput; output?: NotebookEditToolOutput }) {
|
||||
return (
|
||||
<AccordionItem
|
||||
key={AgentToolsType.NotebookEdit}
|
||||
aria-label="NotebookEdit Tool"
|
||||
title={<ToolTitle icon={<FileText className="h-4 w-4" />} label="NotebookEdit" />}
|
||||
subtitle={input.notebook_path}>
|
||||
<ReactMarkdown>{output}</ReactMarkdown>
|
||||
</AccordionItem>
|
||||
)
|
||||
}
|
||||
@ -1,24 +1,46 @@
|
||||
import { AccordionItem } from '@heroui/react'
|
||||
import { FileText } from 'lucide-react'
|
||||
import { useMemo } from 'react'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
|
||||
import { ToolTitle } from './GenericTools'
|
||||
import type { ReadToolInput as ReadToolInputType, ReadToolOutput as ReadToolOutputType } from './types'
|
||||
import type { ReadToolInput as ReadToolInputType, ReadToolOutput as ReadToolOutputType, TextOutput } from './types'
|
||||
import { AgentToolsType } from './types'
|
||||
|
||||
export function ReadTool({ input, output }: { input: ReadToolInputType; output?: ReadToolOutputType }) {
|
||||
// 将 output 统一转换为字符串
|
||||
const outputString = useMemo(() => {
|
||||
if (!output) return null
|
||||
|
||||
// 如果是 TextOutput[] 类型,提取所有 text 内容
|
||||
if (Array.isArray(output)) {
|
||||
return output
|
||||
.filter((item): item is TextOutput => item.type === 'text')
|
||||
.map((item) => item.text)
|
||||
.join('')
|
||||
}
|
||||
|
||||
// 如果是字符串,直接返回
|
||||
return output
|
||||
}, [output])
|
||||
|
||||
// 如果有输出,计算统计信息
|
||||
const stats = output
|
||||
? {
|
||||
lineCount: output.split('\n').length,
|
||||
fileSize: new Blob([output]).size,
|
||||
formatSize: (bytes: number) => {
|
||||
if (bytes < 1024) return `${bytes} B`
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
||||
}
|
||||
}
|
||||
: null
|
||||
const stats = useMemo(() => {
|
||||
if (!outputString) return null
|
||||
|
||||
const bytes = new Blob([outputString]).size
|
||||
const formatSize = (bytes: number) => {
|
||||
if (bytes < 1024) return `${bytes} B`
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
||||
}
|
||||
|
||||
return {
|
||||
lineCount: outputString.split('\n').length,
|
||||
fileSize: bytes,
|
||||
formatSize
|
||||
}
|
||||
}, [outputString])
|
||||
|
||||
return (
|
||||
<AccordionItem
|
||||
@ -29,14 +51,10 @@ export function ReadTool({ input, output }: { input: ReadToolInputType; output?:
|
||||
icon={<FileText className="h-4 w-4" />}
|
||||
label="Read File"
|
||||
params={input.file_path.split('/').pop()}
|
||||
stats={output && stats ? `${stats.lineCount} lines, ${stats.formatSize(stats.fileSize)}` : undefined}
|
||||
stats={stats ? `${stats.lineCount} lines, ${stats.formatSize(stats.fileSize)}` : undefined}
|
||||
/>
|
||||
}>
|
||||
{output ? (
|
||||
// <div className="h-full scroll-auto">
|
||||
<ReactMarkdown>{output}</ReactMarkdown>
|
||||
// </div>
|
||||
) : null}
|
||||
{outputString ? <ReactMarkdown>{outputString}</ReactMarkdown> : null}
|
||||
</AccordionItem>
|
||||
)
|
||||
}
|
||||
|
||||
@ -14,31 +14,28 @@ const getStatusConfig = (status: TodoItem['status']) => {
|
||||
case 'completed':
|
||||
return {
|
||||
color: 'success' as const,
|
||||
icon: <CheckCircle className="h-3 w-3" />,
|
||||
label: '已完成'
|
||||
icon: <CheckCircle className="h-3 w-3" />
|
||||
}
|
||||
case 'in_progress':
|
||||
return {
|
||||
color: 'primary' as const,
|
||||
icon: <Clock className="h-3 w-3" />,
|
||||
label: '进行中'
|
||||
icon: <Clock className="h-3 w-3" />
|
||||
}
|
||||
case 'pending':
|
||||
return {
|
||||
color: 'default' as const,
|
||||
icon: <Circle className="h-3 w-3" />,
|
||||
label: '待处理'
|
||||
icon: <Circle className="h-3 w-3" />
|
||||
}
|
||||
default:
|
||||
return {
|
||||
color: 'default' as const,
|
||||
icon: <Circle className="h-3 w-3" />,
|
||||
label: '待处理'
|
||||
icon: <Circle className="h-3 w-3" />
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function TodoWriteTool({ input, output }: { input: TodoWriteToolInputType; output?: TodoWriteToolOutputType }) {
|
||||
const doneCount = input.todos.filter((todo) => todo.status === 'completed').length
|
||||
return (
|
||||
<AccordionItem
|
||||
key={AgentToolsType.TodoWrite}
|
||||
@ -46,7 +43,8 @@ export function TodoWriteTool({ input, output }: { input: TodoWriteToolInputType
|
||||
title={
|
||||
<ToolTitle
|
||||
icon={<ListTodo className="h-4 w-4" />}
|
||||
label="Todo Update"
|
||||
label="Todo Write"
|
||||
params={`${doneCount} Done`}
|
||||
stats={`${input.todos.length} ${input.todos.length === 1 ? 'item' : 'items'}`}
|
||||
/>
|
||||
}>
|
||||
@ -55,15 +53,10 @@ export function TodoWriteTool({ input, output }: { input: TodoWriteToolInputType
|
||||
const statusConfig = getStatusConfig(todo.status)
|
||||
return (
|
||||
<Card key={index} className="shadow-sm">
|
||||
<CardBody>
|
||||
<CardBody className="p-2">
|
||||
<div className="flex items-start gap-3">
|
||||
<Chip
|
||||
color={statusConfig.color}
|
||||
variant="flat"
|
||||
size="sm"
|
||||
startContent={statusConfig.icon}
|
||||
className="flex-shrink-0">
|
||||
{statusConfig.label}
|
||||
<Chip color={statusConfig.color} variant="flat" size="sm" className="flex-shrink-0">
|
||||
{statusConfig.icon}
|
||||
</Chip>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className={`text-sm ${todo.status === 'completed' ? 'text-default-500 line-through' : ''}`}>
|
||||
|
||||
@ -6,9 +6,14 @@ import { NormalToolResponse } from '@renderer/types'
|
||||
export * from './types'
|
||||
|
||||
// 导入所有渲染器
|
||||
import { BashOutputTool } from './BashOutputTool'
|
||||
import { BashTool } from './BashTool'
|
||||
import { EditTool } from './EditTool'
|
||||
import { ExitPlanModeTool } from './ExitPlanModeTool'
|
||||
import { GlobTool } from './GlobTool'
|
||||
import { GrepTool } from './GrepTool'
|
||||
import { MultiEditTool } from './MultiEditTool'
|
||||
import { NotebookEditTool } from './NotebookEditTool'
|
||||
import { ReadTool } from './ReadTool'
|
||||
import { SearchTool } from './SearchTool'
|
||||
import { TaskTool } from './TaskTool'
|
||||
@ -31,7 +36,12 @@ export const toolRenderers = {
|
||||
[AgentToolsType.WebSearch]: WebSearchTool,
|
||||
[AgentToolsType.Grep]: GrepTool,
|
||||
[AgentToolsType.Write]: WriteTool,
|
||||
[AgentToolsType.WebFetch]: WebFetchTool
|
||||
[AgentToolsType.WebFetch]: WebFetchTool,
|
||||
[AgentToolsType.Edit]: EditTool,
|
||||
[AgentToolsType.MultiEdit]: MultiEditTool,
|
||||
[AgentToolsType.BashOutput]: BashOutputTool,
|
||||
[AgentToolsType.NotebookEdit]: NotebookEditTool,
|
||||
[AgentToolsType.ExitPlanMode]: ExitPlanModeTool
|
||||
} as const
|
||||
|
||||
// 类型守卫函数
|
||||
@ -51,7 +61,8 @@ function renderToolContent(toolName: AgentToolsType, input: ToolInput, output?:
|
||||
'p-0 [&>div:first-child]:!flex-none [&>div:first-child]:flex [&>div:first-child]:flex-col [&>div:first-child]:text-start [&>div:first-child]:max-w-full',
|
||||
indicator: 'flex-shrink-0',
|
||||
subtitle: 'text-xs',
|
||||
content: 'rounded-md bg-foreground-50 p-2 text-foreground-900 dark:bg-foreground-100 h-fit max-h-96 scroll-auto'
|
||||
content:
|
||||
'rounded-md bg-foreground-50 p-2 text-foreground-900 dark:bg-foreground-100 max-h-96 p-2 overflow-scroll'
|
||||
}}
|
||||
defaultExpandedKeys={toolName === AgentToolsType.TodoWrite ? [AgentToolsType.TodoWrite] : []}>
|
||||
{/* <Renderer input={input as any} output={output as any} /> */}
|
||||
|
||||
@ -8,7 +8,17 @@ export enum AgentToolsType {
|
||||
WebSearch = 'WebSearch',
|
||||
Grep = 'Grep',
|
||||
Write = 'Write',
|
||||
WebFetch = 'WebFetch'
|
||||
WebFetch = 'WebFetch',
|
||||
Edit = 'Edit',
|
||||
MultiEdit = 'MultiEdit',
|
||||
BashOutput = 'BashOutput',
|
||||
NotebookEdit = 'NotebookEdit',
|
||||
ExitPlanMode = 'ExitPlanMode'
|
||||
}
|
||||
|
||||
export type TextOutput = {
|
||||
type: 'text'
|
||||
text: string
|
||||
}
|
||||
|
||||
// Read 工具的类型定义
|
||||
@ -16,7 +26,7 @@ export interface ReadToolInput {
|
||||
file_path: string
|
||||
}
|
||||
|
||||
export type ReadToolOutput = string
|
||||
export type ReadToolOutput = string | TextOutput[]
|
||||
|
||||
// Task 工具的类型定义
|
||||
export type TaskToolInput = {
|
||||
@ -25,10 +35,7 @@ export type TaskToolInput = {
|
||||
subagent_type: string
|
||||
}
|
||||
|
||||
export type TaskToolOutput = {
|
||||
type: 'text'
|
||||
text: string
|
||||
}[]
|
||||
export type TaskToolOutput = TextOutput[]
|
||||
|
||||
// Bash 工具的类型定义
|
||||
export type BashToolInput = {
|
||||
@ -84,6 +91,7 @@ export interface GrepToolInput {
|
||||
|
||||
export type GrepToolOutput = string
|
||||
|
||||
// Write 工具的类型定义
|
||||
export type WriteToolInput = {
|
||||
content: string
|
||||
file_path: string
|
||||
@ -91,6 +99,44 @@ export type WriteToolInput = {
|
||||
|
||||
export type WriteToolOutput = string
|
||||
|
||||
// Edit 工具的类型定义
|
||||
export type EditToolInput = {
|
||||
file_path: string
|
||||
old_string: string
|
||||
new_string: string
|
||||
}
|
||||
export type EditToolOutput = string
|
||||
|
||||
// MultiEdit 工具的类型定义
|
||||
export type MultiEditToolInput = {
|
||||
file_path: string
|
||||
edits: {
|
||||
old_string: string
|
||||
new_string: string
|
||||
}[]
|
||||
}
|
||||
export type MultiEditToolOutput = string
|
||||
|
||||
// BashOutput 工具的类型定义
|
||||
export type BashOutputToolInput = {
|
||||
bash_id: string
|
||||
}
|
||||
export type BashOutputToolOutput = string
|
||||
|
||||
// NotebookEdit 工具的类型定义
|
||||
export type NotebookEditToolInput = {
|
||||
notebook_path: string
|
||||
edit_mode: string
|
||||
cell_type: string
|
||||
new_source: string
|
||||
}
|
||||
export type NotebookEditToolOutput = string
|
||||
|
||||
export type ExitPlanModeToolInput = {
|
||||
plan: string
|
||||
}
|
||||
export type ExitPlanModeToolOutput = string
|
||||
|
||||
// 联合类型
|
||||
export type ToolInput =
|
||||
| ReadToolInput
|
||||
@ -103,6 +149,11 @@ export type ToolInput =
|
||||
| WebFetchToolInput
|
||||
| GrepToolInput
|
||||
| WriteToolInput
|
||||
| EditToolInput
|
||||
| MultiEditToolInput
|
||||
| BashOutputToolInput
|
||||
| NotebookEditToolInput
|
||||
| ExitPlanModeToolInput
|
||||
export type ToolOutput =
|
||||
| ReadToolOutput
|
||||
| TaskToolOutput
|
||||
@ -114,6 +165,11 @@ export type ToolOutput =
|
||||
| GrepToolOutput
|
||||
| WebFetchToolOutput
|
||||
| WriteToolOutput
|
||||
| EditToolOutput
|
||||
| MultiEditToolOutput
|
||||
| BashOutputToolOutput
|
||||
| NotebookEditToolOutput
|
||||
| ExitPlanModeToolOutput
|
||||
// 工具渲染器接口
|
||||
export interface ToolRenderer {
|
||||
render: (props: { input: ToolInput; output?: ToolOutput }) => React.ReactElement
|
||||
|
||||
@ -35,6 +35,11 @@ const ChooseTool = (toolResponse: NormalToolResponse): React.ReactNode | null =>
|
||||
case 'Grep':
|
||||
case 'Write':
|
||||
case 'WebFetch':
|
||||
case 'Edit':
|
||||
case 'MultiEdit':
|
||||
case 'BashOutput':
|
||||
case 'NotebookEdit':
|
||||
case 'ExitPlanMode':
|
||||
return <MessageAgentTools toolResponse={toolResponse} />
|
||||
default:
|
||||
return null
|
||||
|
||||
Loading…
Reference in New Issue
Block a user