mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 06:30:10 +08:00
fix: add null checks and type guards to all MessageAgentTools to prevent rendering errors (#11512)
* Initial plan * fix: add null checks to BashTool to prevent rendering errors Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com> * fix: add null checks to all MessageAgentTools to prevent rendering errors Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com> * fix: add Array.isArray checks to prevent map errors on non-array values Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com> * fix: add typeof checks for string operations to prevent type errors Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com> * refactor: remove redundant typeof string checks for typed outputs Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: DeJeune <67425183+DeJeune@users.noreply.github.com>
This commit is contained in:
parent
77a9504f74
commit
7ce1590eaf
@ -76,7 +76,7 @@ export function BashOutputTool({
|
|||||||
input,
|
input,
|
||||||
output
|
output
|
||||||
}: {
|
}: {
|
||||||
input: BashOutputToolInput
|
input?: BashOutputToolInput
|
||||||
output?: BashOutputToolOutput
|
output?: BashOutputToolOutput
|
||||||
}): NonNullable<CollapseProps['items']>[number] {
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
const parsedOutput = parseBashOutput(output)
|
const parsedOutput = parseBashOutput(output)
|
||||||
@ -144,7 +144,7 @@ export function BashOutputTool({
|
|||||||
label="Bash Output"
|
label="Bash Output"
|
||||||
params={
|
params={
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Tag className="py-0 font-mono text-xs">{input.bash_id}</Tag>
|
<Tag className="py-0 font-mono text-xs">{input?.bash_id}</Tag>
|
||||||
{statusConfig && (
|
{statusConfig && (
|
||||||
<Tag
|
<Tag
|
||||||
color={statusConfig.color}
|
color={statusConfig.color}
|
||||||
|
|||||||
@ -11,14 +11,14 @@ export function BashTool({
|
|||||||
input,
|
input,
|
||||||
output
|
output
|
||||||
}: {
|
}: {
|
||||||
input: BashToolInputType
|
input?: BashToolInputType
|
||||||
output?: BashToolOutputType
|
output?: BashToolOutputType
|
||||||
}): NonNullable<CollapseProps['items']>[number] {
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
// 如果有输出,计算输出行数
|
// 如果有输出,计算输出行数
|
||||||
const outputLines = output ? output.split('\n').length : 0
|
const outputLines = output ? output.split('\n').length : 0
|
||||||
|
|
||||||
// 处理命令字符串的截断
|
// 处理命令字符串的截断,添加空值检查
|
||||||
const command = input.command
|
const command = input?.command ?? ''
|
||||||
const needsTruncate = command.length > MAX_TAG_LENGTH
|
const needsTruncate = command.length > MAX_TAG_LENGTH
|
||||||
const displayCommand = needsTruncate ? `${command.slice(0, MAX_TAG_LENGTH)}...` : command
|
const displayCommand = needsTruncate ? `${command.slice(0, MAX_TAG_LENGTH)}...` : command
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ export function BashTool({
|
|||||||
<ToolTitle
|
<ToolTitle
|
||||||
icon={<Terminal className="h-4 w-4" />}
|
icon={<Terminal className="h-4 w-4" />}
|
||||||
label="Bash"
|
label="Bash"
|
||||||
params={input.description}
|
params={input?.description}
|
||||||
stats={output ? `${outputLines} ${outputLines === 1 ? 'line' : 'lines'}` : undefined}
|
stats={output ? `${outputLines} ${outputLines === 1 ? 'line' : 'lines'}` : undefined}
|
||||||
/>
|
/>
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
|
|||||||
@ -32,19 +32,19 @@ export function EditTool({
|
|||||||
input,
|
input,
|
||||||
output
|
output
|
||||||
}: {
|
}: {
|
||||||
input: EditToolInput
|
input?: EditToolInput
|
||||||
output?: EditToolOutput
|
output?: EditToolOutput
|
||||||
}): NonNullable<CollapseProps['items']>[number] {
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
return {
|
return {
|
||||||
key: AgentToolsType.Edit,
|
key: AgentToolsType.Edit,
|
||||||
label: <ToolTitle icon={<FileEdit className="h-4 w-4" />} label="Edit" params={input.file_path} />,
|
label: <ToolTitle icon={<FileEdit className="h-4 w-4" />} label="Edit" params={input?.file_path} />,
|
||||||
children: (
|
children: (
|
||||||
<>
|
<>
|
||||||
{/* Diff View */}
|
{/* Diff View */}
|
||||||
{/* Old Content */}
|
{/* Old Content */}
|
||||||
{renderCodeBlock(input.old_string, 'old')}
|
{renderCodeBlock(input?.old_string ?? '', 'old')}
|
||||||
{/* New Content */}
|
{/* New Content */}
|
||||||
{renderCodeBlock(input.new_string, 'new')}
|
{renderCodeBlock(input?.new_string ?? '', 'new')}
|
||||||
{/* Output */}
|
{/* Output */}
|
||||||
{output}
|
{output}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -10,18 +10,19 @@ export function ExitPlanModeTool({
|
|||||||
input,
|
input,
|
||||||
output
|
output
|
||||||
}: {
|
}: {
|
||||||
input: ExitPlanModeToolInput
|
input?: ExitPlanModeToolInput
|
||||||
output?: ExitPlanModeToolOutput
|
output?: ExitPlanModeToolOutput
|
||||||
}): NonNullable<CollapseProps['items']>[number] {
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
|
const plan = input?.plan ?? ''
|
||||||
return {
|
return {
|
||||||
key: AgentToolsType.ExitPlanMode,
|
key: AgentToolsType.ExitPlanMode,
|
||||||
label: (
|
label: (
|
||||||
<ToolTitle
|
<ToolTitle
|
||||||
icon={<DoorOpen className="h-4 w-4" />}
|
icon={<DoorOpen className="h-4 w-4" />}
|
||||||
label="ExitPlanMode"
|
label="ExitPlanMode"
|
||||||
stats={`${input.plan.split('\n\n').length} plans`}
|
stats={`${plan.split('\n\n').length} plans`}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
children: <ReactMarkdown>{input.plan + '\n\n' + (output ?? '')}</ReactMarkdown>
|
children: <ReactMarkdown>{plan + '\n\n' + (output ?? '')}</ReactMarkdown>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export function GlobTool({
|
|||||||
input,
|
input,
|
||||||
output
|
output
|
||||||
}: {
|
}: {
|
||||||
input: GlobToolInputType
|
input?: GlobToolInputType
|
||||||
output?: GlobToolOutputType
|
output?: GlobToolOutputType
|
||||||
}): NonNullable<CollapseProps['items']>[number] {
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
// 如果有输出,计算文件数量
|
// 如果有输出,计算文件数量
|
||||||
@ -20,7 +20,7 @@ export function GlobTool({
|
|||||||
<ToolTitle
|
<ToolTitle
|
||||||
icon={<FolderSearch className="h-4 w-4" />}
|
icon={<FolderSearch className="h-4 w-4" />}
|
||||||
label="Glob"
|
label="Glob"
|
||||||
params={input.pattern}
|
params={input?.pattern}
|
||||||
stats={output ? `${lineCount} ${lineCount === 1 ? 'file' : 'files'}` : undefined}
|
stats={output ? `${lineCount} ${lineCount === 1 ? 'file' : 'files'}` : undefined}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export function GrepTool({
|
|||||||
input,
|
input,
|
||||||
output
|
output
|
||||||
}: {
|
}: {
|
||||||
input: GrepToolInput
|
input?: GrepToolInput
|
||||||
output?: GrepToolOutput
|
output?: GrepToolOutput
|
||||||
}): NonNullable<CollapseProps['items']>[number] {
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
// 如果有输出,计算结果行数
|
// 如果有输出,计算结果行数
|
||||||
@ -22,8 +22,8 @@ export function GrepTool({
|
|||||||
label="Grep"
|
label="Grep"
|
||||||
params={
|
params={
|
||||||
<>
|
<>
|
||||||
{input.pattern}
|
{input?.pattern}
|
||||||
{input.output_mode && <span className="ml-1">({input.output_mode})</span>}
|
{input?.output_mode && <span className="ml-1">({input.output_mode})</span>}
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
stats={output ? `${resultLines} ${resultLines === 1 ? 'line' : 'lines'}` : undefined}
|
stats={output ? `${resultLines} ${resultLines === 1 ? 'line' : 'lines'}` : undefined}
|
||||||
|
|||||||
@ -9,18 +9,19 @@ import { AgentToolsType } from './types'
|
|||||||
export function MultiEditTool({
|
export function MultiEditTool({
|
||||||
input
|
input
|
||||||
}: {
|
}: {
|
||||||
input: MultiEditToolInput
|
input?: MultiEditToolInput
|
||||||
output?: MultiEditToolOutput
|
output?: MultiEditToolOutput
|
||||||
}): NonNullable<CollapseProps['items']>[number] {
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
|
const edits = Array.isArray(input?.edits) ? input.edits : []
|
||||||
return {
|
return {
|
||||||
key: AgentToolsType.MultiEdit,
|
key: AgentToolsType.MultiEdit,
|
||||||
label: <ToolTitle icon={<FileText className="h-4 w-4" />} label="MultiEdit" params={input.file_path} />,
|
label: <ToolTitle icon={<FileText className="h-4 w-4" />} label="MultiEdit" params={input?.file_path} />,
|
||||||
children: (
|
children: (
|
||||||
<div>
|
<div>
|
||||||
{input.edits.map((edit, index) => (
|
{edits.map((edit, index) => (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
{renderCodeBlock(edit.old_string, 'old')}
|
{renderCodeBlock(edit.old_string ?? '', 'old')}
|
||||||
{renderCodeBlock(edit.new_string, 'new')}
|
{renderCodeBlock(edit.new_string ?? '', 'new')}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export function NotebookEditTool({
|
|||||||
input,
|
input,
|
||||||
output
|
output
|
||||||
}: {
|
}: {
|
||||||
input: NotebookEditToolInput
|
input?: NotebookEditToolInput
|
||||||
output?: NotebookEditToolOutput
|
output?: NotebookEditToolOutput
|
||||||
}): NonNullable<CollapseProps['items']>[number] {
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
return {
|
return {
|
||||||
@ -20,10 +20,10 @@ export function NotebookEditTool({
|
|||||||
<>
|
<>
|
||||||
<ToolTitle icon={<FileText className="h-4 w-4" />} label="NotebookEdit" />
|
<ToolTitle icon={<FileText className="h-4 w-4" />} label="NotebookEdit" />
|
||||||
<Tag className="mt-1" color="blue">
|
<Tag className="mt-1" color="blue">
|
||||||
{input.notebook_path}{' '}
|
{input?.notebook_path}{' '}
|
||||||
</Tag>
|
</Tag>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
children: <ReactMarkdown>{output}</ReactMarkdown>
|
children: <ReactMarkdown>{output ?? ''}</ReactMarkdown>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,7 +46,7 @@ export function ReadTool({
|
|||||||
input,
|
input,
|
||||||
output
|
output
|
||||||
}: {
|
}: {
|
||||||
input: ReadToolInputType
|
input?: ReadToolInputType
|
||||||
output?: ReadToolOutputType
|
output?: ReadToolOutputType
|
||||||
}): NonNullable<CollapseProps['items']>[number] {
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
const outputString = normalizeOutputString(output)
|
const outputString = normalizeOutputString(output)
|
||||||
@ -58,7 +58,7 @@ export function ReadTool({
|
|||||||
<ToolTitle
|
<ToolTitle
|
||||||
icon={<FileText className="h-4 w-4" />}
|
icon={<FileText className="h-4 w-4" />}
|
||||||
label="Read File"
|
label="Read File"
|
||||||
params={input.file_path.split('/').pop()}
|
params={input?.file_path?.split('/').pop()}
|
||||||
stats={stats ? `${stats.lineCount} lines, ${stats.formatSize(stats.fileSize)}` : undefined}
|
stats={stats ? `${stats.lineCount} lines, ${stats.formatSize(stats.fileSize)}` : undefined}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export function SearchTool({
|
|||||||
input,
|
input,
|
||||||
output
|
output
|
||||||
}: {
|
}: {
|
||||||
input: SearchToolInputType
|
input?: SearchToolInputType
|
||||||
output?: SearchToolOutputType
|
output?: SearchToolOutputType
|
||||||
}): NonNullable<CollapseProps['items']>[number] {
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
// 如果有输出,计算结果数量
|
// 如果有输出,计算结果数量
|
||||||
@ -20,13 +20,13 @@ export function SearchTool({
|
|||||||
<ToolTitle
|
<ToolTitle
|
||||||
icon={<Search className="h-4 w-4" />}
|
icon={<Search className="h-4 w-4" />}
|
||||||
label="Search"
|
label="Search"
|
||||||
params={`"${input}"`}
|
params={input ? `"${input}"` : undefined}
|
||||||
stats={output ? `${resultCount} ${resultCount === 1 ? 'result' : 'results'}` : undefined}
|
stats={output ? `${resultCount} ${resultCount === 1 ? 'result' : 'results'}` : undefined}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
children: (
|
children: (
|
||||||
<div>
|
<div>
|
||||||
<StringInputTool input={input} label="Search Query" />
|
{input && <StringInputTool input={input} label="Search Query" />}
|
||||||
{output && (
|
{output && (
|
||||||
<div>
|
<div>
|
||||||
<StringOutputTool output={output} label="Search Results" textColor="text-yellow-600 dark:text-yellow-400" />
|
<StringOutputTool output={output} label="Search Results" textColor="text-yellow-600 dark:text-yellow-400" />
|
||||||
|
|||||||
@ -8,12 +8,12 @@ export function SkillTool({
|
|||||||
input,
|
input,
|
||||||
output
|
output
|
||||||
}: {
|
}: {
|
||||||
input: SkillToolInput
|
input?: SkillToolInput
|
||||||
output?: SkillToolOutput
|
output?: SkillToolOutput
|
||||||
}): NonNullable<CollapseProps['items']>[number] {
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
return {
|
return {
|
||||||
key: 'tool',
|
key: 'tool',
|
||||||
label: <ToolTitle icon={<PencilRuler className="h-4 w-4" />} label="Skill" params={input.command} />,
|
label: <ToolTitle icon={<PencilRuler className="h-4 w-4" />} label="Skill" params={input?.command} />,
|
||||||
children: <div>{output}</div>
|
children: <div>{output}</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,19 +9,20 @@ export function TaskTool({
|
|||||||
input,
|
input,
|
||||||
output
|
output
|
||||||
}: {
|
}: {
|
||||||
input: TaskToolInputType
|
input?: TaskToolInputType
|
||||||
output?: TaskToolOutputType
|
output?: TaskToolOutputType
|
||||||
}): NonNullable<CollapseProps['items']>[number] {
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
return {
|
return {
|
||||||
key: 'tool',
|
key: 'tool',
|
||||||
label: <ToolTitle icon={<Bot className="h-4 w-4" />} label="Task" params={input.description} />,
|
label: <ToolTitle icon={<Bot className="h-4 w-4" />} label="Task" params={input?.description} />,
|
||||||
children: (
|
children: (
|
||||||
<div>
|
<div>
|
||||||
{output?.map((item) => (
|
{Array.isArray(output) &&
|
||||||
<div key={item.type}>
|
output.map((item) => (
|
||||||
<div>{item.type === 'text' ? <Markdown>{item.text}</Markdown> : item.text}</div>
|
<div key={item.type}>
|
||||||
</div>
|
<div>{item.type === 'text' ? <Markdown>{item.text}</Markdown> : item.text}</div>
|
||||||
))}
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,9 +38,10 @@ const getStatusConfig = (status: TodoItem['status']) => {
|
|||||||
export function TodoWriteTool({
|
export function TodoWriteTool({
|
||||||
input
|
input
|
||||||
}: {
|
}: {
|
||||||
input: TodoWriteToolInputType
|
input?: TodoWriteToolInputType
|
||||||
}): NonNullable<CollapseProps['items']>[number] {
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
const doneCount = input.todos.filter((todo) => todo.status === 'completed').length
|
const todos = Array.isArray(input?.todos) ? input.todos : []
|
||||||
|
const doneCount = todos.filter((todo) => todo.status === 'completed').length
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: AgentToolsType.TodoWrite,
|
key: AgentToolsType.TodoWrite,
|
||||||
@ -49,12 +50,12 @@ export function TodoWriteTool({
|
|||||||
icon={<ListTodo className="h-4 w-4" />}
|
icon={<ListTodo className="h-4 w-4" />}
|
||||||
label="Todo Write"
|
label="Todo Write"
|
||||||
params={`${doneCount} Done`}
|
params={`${doneCount} Done`}
|
||||||
stats={`${input.todos.length} ${input.todos.length === 1 ? 'item' : 'items'}`}
|
stats={`${todos.length} ${todos.length === 1 ? 'item' : 'items'}`}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
children: (
|
children: (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{input.todos.map((todo, index) => {
|
{todos.map((todo, index) => {
|
||||||
const statusConfig = getStatusConfig(todo.status)
|
const statusConfig = getStatusConfig(todo.status)
|
||||||
return (
|
return (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
|
|||||||
@ -8,12 +8,12 @@ export function WebFetchTool({
|
|||||||
input,
|
input,
|
||||||
output
|
output
|
||||||
}: {
|
}: {
|
||||||
input: WebFetchToolInput
|
input?: WebFetchToolInput
|
||||||
output?: WebFetchToolOutput
|
output?: WebFetchToolOutput
|
||||||
}): NonNullable<CollapseProps['items']>[number] {
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
return {
|
return {
|
||||||
key: 'tool',
|
key: 'tool',
|
||||||
label: <ToolTitle icon={<Globe className="h-4 w-4" />} label="Web Fetch" params={input.url} />,
|
label: <ToolTitle icon={<Globe className="h-4 w-4" />} label="Web Fetch" params={input?.url} />,
|
||||||
children: <div>{output}</div>
|
children: <div>{output}</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export function WebSearchTool({
|
|||||||
input,
|
input,
|
||||||
output
|
output
|
||||||
}: {
|
}: {
|
||||||
input: WebSearchToolInput
|
input?: WebSearchToolInput
|
||||||
output?: WebSearchToolOutput
|
output?: WebSearchToolOutput
|
||||||
}): NonNullable<CollapseProps['items']>[number] {
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
// 如果有输出,计算结果数量
|
// 如果有输出,计算结果数量
|
||||||
@ -20,7 +20,7 @@ export function WebSearchTool({
|
|||||||
<ToolTitle
|
<ToolTitle
|
||||||
icon={<Globe className="h-4 w-4" />}
|
icon={<Globe className="h-4 w-4" />}
|
||||||
label="Web Search"
|
label="Web Search"
|
||||||
params={input.query}
|
params={input?.query}
|
||||||
stats={output ? `${resultCount} ${resultCount === 1 ? 'result' : 'results'}` : undefined}
|
stats={output ? `${resultCount} ${resultCount === 1 ? 'result' : 'results'}` : undefined}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|||||||
@ -7,12 +7,12 @@ import type { WriteToolInput, WriteToolOutput } from './types'
|
|||||||
export function WriteTool({
|
export function WriteTool({
|
||||||
input
|
input
|
||||||
}: {
|
}: {
|
||||||
input: WriteToolInput
|
input?: WriteToolInput
|
||||||
output?: WriteToolOutput
|
output?: WriteToolOutput
|
||||||
}): NonNullable<CollapseProps['items']>[number] {
|
}): NonNullable<CollapseProps['items']>[number] {
|
||||||
return {
|
return {
|
||||||
key: 'tool',
|
key: 'tool',
|
||||||
label: <ToolTitle icon={<FileText className="h-4 w-4" />} label="Write" params={input.file_path} />,
|
label: <ToolTitle icon={<FileText className="h-4 w-4" />} label="Write" params={input?.file_path} />,
|
||||||
children: <div>{input.content}</div>
|
children: <div>{input?.content}</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user