From 4001b7dc0ae527df7bedb119ca93defce0735f09 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 28 Nov 2025 19:38:42 +0000 Subject: [PATCH] refactor: remove heuristic parsing, use pure type-based tool rendering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove all heuristic parsing code from message.tsx (hasCommandDetails, commandSection, editSections, readSections, hasStructuredContent, etc.) - Replace with simple raw JSON fallback for tools without structured results - Fix server-side support for Claude SDK's old_string/new_string params (in addition to old_str/new_str) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/app/src/components/message.tsx | 253 ++++-------------- .../server/agent/providers/claude-agent.ts | 7 +- 2 files changed, 62 insertions(+), 198 deletions(-) diff --git a/packages/app/src/components/message.tsx b/packages/app/src/components/message.tsx index 35df2274e..3aa80a676 100644 --- a/packages/app/src/components/message.tsx +++ b/packages/app/src/components/message.tsx @@ -1286,22 +1286,7 @@ export const ToolCall = memo(function ToolCall({ result, error, status, - parsedEditEntries, - parsedReadEntries, - parsedCommandDetails, }: ToolCallProps) { - const { editEntries, readEntries, commandDetails } = useMemo( - () => - resolveToolCallPreview({ - args, - result, - parsedEditEntries, - parsedReadEntries, - parsedCommandDetails, - }), - [args, result, parsedEditEntries, parsedReadEntries, parsedCommandDetails] - ); - const [isExpanded, setIsExpanded] = useState(false); // Check if result has a type field for structured rendering @@ -1404,166 +1389,6 @@ export const ToolCall = memo(function ToolCall({ [] ); - const hasCommandDetails = Boolean( - commandDetails && - (commandDetails.command || - commandDetails.cwd || - commandDetails.exitCode !== undefined || - commandDetails.output) - ); - - function renderJsonSection( - label: string, - text: string, - isError = false - ): ReactNode { - const displayText = text.length ? text : "(empty)"; - - return ( - - {label} - - - {displayText} - - - - ); - } - - const commandSection = useMemo(() => { - if (!hasCommandDetails || !commandDetails) { - return null; - } - return ( - - Command - {commandDetails.command ? ( - - - {commandDetails.command} - - - ) : null} - {commandDetails.cwd ? ( - - Directory - {commandDetails.cwd} - - ) : null} - {commandDetails.exitCode !== undefined ? ( - - Exit Code - - {commandDetails.exitCode === null - ? "Unknown" - : commandDetails.exitCode} - - - ) : null} - {commandDetails.output ? ( - - - {commandDetails.output} - - - ) : null} - - ); - }, [commandDetails, hasCommandDetails]); - - const editSections = useMemo( - () => - editEntries.map((entry, index) => ( - - Diff - {entry.filePath ? ( - - {entry.filePath} - - ) : null} - - - - - )), - [editEntries] - ); - - const readSections = useMemo( - () => - readEntries.map((entry, index) => ( - - Read Result - {entry.filePath ? ( - - {entry.filePath} - - ) : null} - - {entry.content} - - - )), - [readEntries] - ); - - // Check if we have structured content that makes raw JSON redundant - const hasStructuredContent = Boolean( - commandDetails?.output || editEntries.length > 0 || readEntries.length > 0 - ); - - const jsonSections = useMemo(() => { - const sections: ReactNode[] = []; - if (args !== undefined) { - sections.push(renderJsonSection("Arguments", serializedArgs)); - } - // Only show raw Result JSON if we don't have structured content showing the same data - if (result !== undefined && !hasStructuredContent) { - sections.push(renderJsonSection("Result", serializedResult)); - } - if (error !== undefined) { - sections.push(renderJsonSection("Error", serializedError, true)); - } - return sections; - }, [args, serializedArgs, result, serializedResult, error, serializedError, hasStructuredContent]); - const renderDetails = useCallback(() => { // If we have a structured result, use type-based rendering if (structuredResult) { @@ -1743,15 +1568,63 @@ export const ToolCall = memo(function ToolCall({ return {sections}; } - // Fall back to heuristic parsing for backwards compatibility - const showReadSections = !commandDetails?.output && readSections.length > 0; + // No structured result - show raw JSON + const sections: ReactNode[] = []; - if ( - !commandSection && - editSections.length === 0 && - !showReadSections && - jsonSections.length === 0 - ) { + if (args !== undefined) { + sections.push( + + Arguments + + {serializedArgs} + + + ); + } + + if (result !== undefined) { + sections.push( + + Result + + {serializedResult} + + + ); + } + + if (error !== undefined) { + sections.push( + + Error + + + {serializedError} + + + + ); + } + + if (sections.length === 0) { return ( No additional details available @@ -1759,14 +1632,7 @@ export const ToolCall = memo(function ToolCall({ ); } - return ( - - {commandSection} - {editSections} - {showReadSections ? readSections : null} - {jsonSections} - - ); + return {sections}; }, [ structuredResult, extractCommandFromStructured, @@ -1778,11 +1644,6 @@ export const ToolCall = memo(function ToolCall({ args, result, error, - commandSection, - commandDetails?.output, - editSections, - readSections, - jsonSections, ]); return ( diff --git a/packages/server/src/server/agent/providers/claude-agent.ts b/packages/server/src/server/agent/providers/claude-agent.ts index 97fc828f6..54e0dbcaf 100644 --- a/packages/server/src/server/agent/providers/claude-agent.ts +++ b/packages/server/src/server/agent/providers/claude-agent.ts @@ -1122,12 +1122,15 @@ class ClaudeAgentSession implements AgentSession { normalizedTool === "apply_diff" ) { if (input && typeof input.file_path === "string") { + // Support both old_str/new_str and old_string/new_string parameter names + const oldContent = typeof input.old_str === "string" ? input.old_str : typeof input.old_string === "string" ? input.old_string : undefined; + const newContent = typeof input.new_str === "string" ? input.new_str : typeof input.new_string === "string" ? input.new_string : undefined; return { type: "file_edit", filePath: input.file_path, diff: typeof input.patch === "string" ? input.patch : typeof input.diff === "string" ? input.diff : undefined, - oldContent: typeof input.old_str === "string" ? input.old_str : undefined, - newContent: typeof input.new_str === "string" ? input.new_str : undefined, + oldContent, + newContent, }; } }