diff --git a/src/components/ai-elements/tool.tsx b/src/components/ai-elements/tool.tsx index 0d32a1a..b26ac6d 100644 --- a/src/components/ai-elements/tool.tsx +++ b/src/components/ai-elements/tool.tsx @@ -22,6 +22,7 @@ import { useTranslations } from "next-intl" import { isValidElement } from "react" import { CodeBlock } from "./code-block" +import { MessageResponse } from "./message" export type ToolProps = ComponentProps @@ -315,12 +316,34 @@ function isDuplicateErrorOutput( export type ToolOutputProps = ComponentProps<"div"> & { output: ToolPart["output"] errorText: ToolPart["errorText"] + renderAsMarkdown?: boolean +} + +const MD_INDICATORS = [ + /^#{1,6}\s/m, + /^\s*[-*+]\s/m, + /^\s*\d+\.\s/m, + /\*\*[^*]+\*\*/, + /\[.+\]\(.+\)/, + /```[\s\S]*?```/, + /^\s*>/m, + /^\|.+\|$/m, +] + +function looksLikeMarkdown(text: string): boolean { + let count = 0 + for (const re of MD_INDICATORS) { + if (re.test(text)) count++ + if (count >= 2) return true + } + return false } export const ToolOutput = ({ className, output, errorText, + renderAsMarkdown, ...props }: ToolOutputProps) => { const t = useTranslations("Folder.chat.tool") @@ -342,8 +365,18 @@ export const ToolOutput = ({ ) } else if (typeof output === "string") { - const language = detectOutputLanguage(output) - Output = + const shouldRenderMd = + renderAsMarkdown ?? (detectOutputLanguage(output) === "log" && looksLikeMarkdown(output)) + if (shouldRenderMd) { + Output = ( +
+ {output} +
+ ) + } else { + const language = detectOutputLanguage(output) + Output = + } } return ( diff --git a/src/components/message/content-parts-renderer.tsx b/src/components/message/content-parts-renderer.tsx index 6596f54..d691c79 100644 --- a/src/components/message/content-parts-renderer.tsx +++ b/src/components/message/content-parts-renderer.tsx @@ -871,6 +871,7 @@ function getToolIcon( if (name === "task") return getTaskToolIcon(input ?? null) if (name === "taskcreate" || name === "taskupdate" || name === "tasklist") return + if (name === "agent") return getTaskToolIcon(input ?? null) if (name === "skill") return if (name === "enterplanmode" || name === "exitplanmode") return @@ -1000,6 +1001,13 @@ function deriveToolTitle( if (desc) return `${prefix}${ellipsis(desc, 60 - prefix.length)}` if (subagent) return subagent } + if (name === "agent") { + const subagent = getField("subagent_type") + const desc = getField("description") + const prefix = subagent ? `${subagent}: ` : "" + if (desc) return `${prefix}${ellipsis(desc, 60 - prefix.length)}` + if (subagent) return subagent + } if (name === "taskcreate") { const subj = getField("subject") if (subj) return `TaskCreate: ${ellipsis(subj, 50)}` @@ -2156,7 +2164,8 @@ const ToolCallPart = memo(function ToolCallPart({ input={part.input} /> )} - {toolNameLower === "task" && part.output ? ( + {(toolNameLower === "task" || toolNameLower === "agent") && + part.output ? (
{part.output}
diff --git a/src/lib/tool-call-normalization.ts b/src/lib/tool-call-normalization.ts index 5b48dfb..8456fa8 100644 --- a/src/lib/tool-call-normalization.ts +++ b/src/lib/tool-call-normalization.ts @@ -32,6 +32,29 @@ const EXACT_TOOL_NAME_ALIASES: Record = { web_search: "websearch", context7_query_docs: "context7_query-docs", context7_resolve_library_id: "context7_resolve-library-id", + agent: "agent", + // Gemini CLI + searchtext: "grep", + search_text: "grep", + writefile: "write", + editfile: "edit", + // Codex + update_plan: "task", + request_user_input: "question", + // OpenCode + delegate_task: "task", + call_omo_agent: "agent", + ast_grep_search: "grep", + ast_grep_replace: "edit", + background_task: "task", + background_cancel: "task", + background_output: "task", + slashcommand: "skill", + question: "question", + lsp_diagnostics: "lsp", + lsp_document_symbols: "lsp", + lsp_goto_definition: "lsp", + lsp_servers: "lsp", execute: "bash", search: "grep", fetch: "webfetch", @@ -65,6 +88,10 @@ function inferFromFreeformName(input: string): string | null { if (/^glob(?:\b|[_\s:-])/.test(normalized)) return "glob" if (/^webfetch(?:\b|[_\s:-])/.test(normalized)) return "webfetch" if (/^websearch(?:\b|[_\s:-])/.test(normalized)) return "websearch" + if (/\bweb[_\s-]?search\b/.test(normalized)) return "websearch" + if (/\bgrep\b/.test(normalized)) return "grep" + if (/\bagent\b/.test(normalized)) return "agent" + if (/\blsp\b/.test(normalized)) return "lsp" if (/^todowrite(?:\b|[_\s:-])/.test(normalized)) return "todowrite" if (/^taskupdate(?:\b|[_\s:-])/.test(normalized)) return "taskupdate" if (/^taskcreate(?:\b|[_\s:-])/.test(normalized)) return "taskcreate"