refactor: remove heuristic parsing, use pure type-based tool rendering
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -1286,22 +1286,7 @@ export const ToolCall = memo(function ToolCall({
|
|||||||
result,
|
result,
|
||||||
error,
|
error,
|
||||||
status,
|
status,
|
||||||
parsedEditEntries,
|
|
||||||
parsedReadEntries,
|
|
||||||
parsedCommandDetails,
|
|
||||||
}: ToolCallProps) {
|
}: ToolCallProps) {
|
||||||
const { editEntries, readEntries, commandDetails } = useMemo(
|
|
||||||
() =>
|
|
||||||
resolveToolCallPreview({
|
|
||||||
args,
|
|
||||||
result,
|
|
||||||
parsedEditEntries,
|
|
||||||
parsedReadEntries,
|
|
||||||
parsedCommandDetails,
|
|
||||||
}),
|
|
||||||
[args, result, parsedEditEntries, parsedReadEntries, parsedCommandDetails]
|
|
||||||
);
|
|
||||||
|
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
|
|
||||||
// Check if result has a type field for structured rendering
|
// 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 (
|
|
||||||
<View key={label} style={toolCallStylesheet.section}>
|
|
||||||
<Text style={toolCallStylesheet.sectionTitle}>{label}</Text>
|
|
||||||
<ScrollView
|
|
||||||
horizontal
|
|
||||||
nestedScrollEnabled
|
|
||||||
style={[
|
|
||||||
toolCallStylesheet.jsonScroll,
|
|
||||||
isError ? toolCallStylesheet.jsonScrollError : null,
|
|
||||||
]}
|
|
||||||
contentContainerStyle={toolCallStylesheet.jsonContent}
|
|
||||||
showsHorizontalScrollIndicator={true}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
toolCallStylesheet.scrollText,
|
|
||||||
isError ? toolCallStylesheet.errorText : null,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{displayText}
|
|
||||||
</Text>
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const commandSection = useMemo(() => {
|
|
||||||
if (!hasCommandDetails || !commandDetails) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<View style={toolCallStylesheet.section}>
|
|
||||||
<Text style={toolCallStylesheet.sectionTitle}>Command</Text>
|
|
||||||
{commandDetails.command ? (
|
|
||||||
<ScrollView
|
|
||||||
horizontal
|
|
||||||
nestedScrollEnabled
|
|
||||||
style={toolCallStylesheet.jsonScroll}
|
|
||||||
contentContainerStyle={toolCallStylesheet.jsonContent}
|
|
||||||
showsHorizontalScrollIndicator={true}
|
|
||||||
>
|
|
||||||
<Text style={toolCallStylesheet.scrollText}>
|
|
||||||
{commandDetails.command}
|
|
||||||
</Text>
|
|
||||||
</ScrollView>
|
|
||||||
) : null}
|
|
||||||
{commandDetails.cwd ? (
|
|
||||||
<View style={toolCallStylesheet.metaRow}>
|
|
||||||
<Text style={toolCallStylesheet.metaLabel}>Directory</Text>
|
|
||||||
<Text style={toolCallStylesheet.metaValue}>{commandDetails.cwd}</Text>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
{commandDetails.exitCode !== undefined ? (
|
|
||||||
<View style={toolCallStylesheet.metaRow}>
|
|
||||||
<Text style={toolCallStylesheet.metaLabel}>Exit Code</Text>
|
|
||||||
<Text style={toolCallStylesheet.metaValue}>
|
|
||||||
{commandDetails.exitCode === null
|
|
||||||
? "Unknown"
|
|
||||||
: commandDetails.exitCode}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
{commandDetails.output ? (
|
|
||||||
<ScrollView
|
|
||||||
style={toolCallStylesheet.scrollArea}
|
|
||||||
contentContainerStyle={toolCallStylesheet.scrollContent}
|
|
||||||
nestedScrollEnabled
|
|
||||||
showsVerticalScrollIndicator={true}
|
|
||||||
>
|
|
||||||
<Text style={toolCallStylesheet.scrollText}>
|
|
||||||
{commandDetails.output}
|
|
||||||
</Text>
|
|
||||||
</ScrollView>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}, [commandDetails, hasCommandDetails]);
|
|
||||||
|
|
||||||
const editSections = useMemo(
|
|
||||||
() =>
|
|
||||||
editEntries.map((entry, index) => (
|
|
||||||
<View
|
|
||||||
key={`${entry.filePath ?? "diff"}-${index}`}
|
|
||||||
style={toolCallStylesheet.section}
|
|
||||||
>
|
|
||||||
<Text style={toolCallStylesheet.sectionTitle}>Diff</Text>
|
|
||||||
{entry.filePath ? (
|
|
||||||
<View style={toolCallStylesheet.fileBadge}>
|
|
||||||
<Text style={toolCallStylesheet.fileBadgeText}>{entry.filePath}</Text>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
<View style={toolCallStylesheet.diffContainer}>
|
|
||||||
<DiffViewer diffLines={entry.diffLines} maxHeight={240} />
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)),
|
|
||||||
[editEntries]
|
|
||||||
);
|
|
||||||
|
|
||||||
const readSections = useMemo(
|
|
||||||
() =>
|
|
||||||
readEntries.map((entry, index) => (
|
|
||||||
<View
|
|
||||||
key={`${entry.filePath ?? "read"}-${index}`}
|
|
||||||
style={toolCallStylesheet.section}
|
|
||||||
>
|
|
||||||
<Text style={toolCallStylesheet.sectionTitle}>Read Result</Text>
|
|
||||||
{entry.filePath ? (
|
|
||||||
<View style={toolCallStylesheet.fileBadge}>
|
|
||||||
<Text style={toolCallStylesheet.fileBadgeText}>{entry.filePath}</Text>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
<ScrollView
|
|
||||||
style={toolCallStylesheet.scrollArea}
|
|
||||||
contentContainerStyle={toolCallStylesheet.scrollContent}
|
|
||||||
nestedScrollEnabled
|
|
||||||
showsVerticalScrollIndicator={true}
|
|
||||||
>
|
|
||||||
<Text style={toolCallStylesheet.scrollText}>{entry.content}</Text>
|
|
||||||
</ScrollView>
|
|
||||||
</View>
|
|
||||||
)),
|
|
||||||
[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(() => {
|
const renderDetails = useCallback(() => {
|
||||||
// If we have a structured result, use type-based rendering
|
// If we have a structured result, use type-based rendering
|
||||||
if (structuredResult) {
|
if (structuredResult) {
|
||||||
@@ -1743,15 +1568,63 @@ export const ToolCall = memo(function ToolCall({
|
|||||||
return <View style={toolCallStylesheet.detailContent}>{sections}</View>;
|
return <View style={toolCallStylesheet.detailContent}>{sections}</View>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fall back to heuristic parsing for backwards compatibility
|
// No structured result - show raw JSON
|
||||||
const showReadSections = !commandDetails?.output && readSections.length > 0;
|
const sections: ReactNode[] = [];
|
||||||
|
|
||||||
if (
|
if (args !== undefined) {
|
||||||
!commandSection &&
|
sections.push(
|
||||||
editSections.length === 0 &&
|
<View key="args" style={toolCallStylesheet.section}>
|
||||||
!showReadSections &&
|
<Text style={toolCallStylesheet.sectionTitle}>Arguments</Text>
|
||||||
jsonSections.length === 0
|
<ScrollView
|
||||||
) {
|
horizontal
|
||||||
|
nestedScrollEnabled
|
||||||
|
style={toolCallStylesheet.jsonScroll}
|
||||||
|
contentContainerStyle={toolCallStylesheet.jsonContent}
|
||||||
|
showsHorizontalScrollIndicator={true}
|
||||||
|
>
|
||||||
|
<Text style={toolCallStylesheet.scrollText}>{serializedArgs}</Text>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result !== undefined) {
|
||||||
|
sections.push(
|
||||||
|
<View key="result" style={toolCallStylesheet.section}>
|
||||||
|
<Text style={toolCallStylesheet.sectionTitle}>Result</Text>
|
||||||
|
<ScrollView
|
||||||
|
horizontal
|
||||||
|
nestedScrollEnabled
|
||||||
|
style={toolCallStylesheet.jsonScroll}
|
||||||
|
contentContainerStyle={toolCallStylesheet.jsonContent}
|
||||||
|
showsHorizontalScrollIndicator={true}
|
||||||
|
>
|
||||||
|
<Text style={toolCallStylesheet.scrollText}>{serializedResult}</Text>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error !== undefined) {
|
||||||
|
sections.push(
|
||||||
|
<View key="error" style={toolCallStylesheet.section}>
|
||||||
|
<Text style={toolCallStylesheet.sectionTitle}>Error</Text>
|
||||||
|
<ScrollView
|
||||||
|
horizontal
|
||||||
|
nestedScrollEnabled
|
||||||
|
style={[toolCallStylesheet.jsonScroll, toolCallStylesheet.jsonScrollError]}
|
||||||
|
contentContainerStyle={toolCallStylesheet.jsonContent}
|
||||||
|
showsHorizontalScrollIndicator={true}
|
||||||
|
>
|
||||||
|
<Text style={[toolCallStylesheet.scrollText, toolCallStylesheet.errorText]}>
|
||||||
|
{serializedError}
|
||||||
|
</Text>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sections.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Text style={toolCallStylesheet.emptyStateText}>
|
<Text style={toolCallStylesheet.emptyStateText}>
|
||||||
No additional details available
|
No additional details available
|
||||||
@@ -1759,14 +1632,7 @@ export const ToolCall = memo(function ToolCall({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <View style={toolCallStylesheet.detailContent}>{sections}</View>;
|
||||||
<View style={toolCallStylesheet.detailContent}>
|
|
||||||
{commandSection}
|
|
||||||
{editSections}
|
|
||||||
{showReadSections ? readSections : null}
|
|
||||||
{jsonSections}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}, [
|
}, [
|
||||||
structuredResult,
|
structuredResult,
|
||||||
extractCommandFromStructured,
|
extractCommandFromStructured,
|
||||||
@@ -1778,11 +1644,6 @@ export const ToolCall = memo(function ToolCall({
|
|||||||
args,
|
args,
|
||||||
result,
|
result,
|
||||||
error,
|
error,
|
||||||
commandSection,
|
|
||||||
commandDetails?.output,
|
|
||||||
editSections,
|
|
||||||
readSections,
|
|
||||||
jsonSections,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1122,12 +1122,15 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
normalizedTool === "apply_diff"
|
normalizedTool === "apply_diff"
|
||||||
) {
|
) {
|
||||||
if (input && typeof input.file_path === "string") {
|
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 {
|
return {
|
||||||
type: "file_edit",
|
type: "file_edit",
|
||||||
filePath: input.file_path,
|
filePath: input.file_path,
|
||||||
diff: typeof input.patch === "string" ? input.patch : typeof input.diff === "string" ? input.diff : undefined,
|
diff: typeof input.patch === "string" ? input.patch : typeof input.diff === "string" ? input.diff : undefined,
|
||||||
oldContent: typeof input.old_str === "string" ? input.old_str : undefined,
|
oldContent,
|
||||||
newContent: typeof input.new_str === "string" ? input.new_str : undefined,
|
newContent,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user