refactor: simplify tool call display and path handling

- Remove displayName from tool calls, use name directly with icon
- Add medium font weight for tool call badges
- Strip workspace root from file paths at parsing level using Zod schema
- Add accentBorder to tool call badges and git diff file blocks
- Fix user message alignment and vertical spacing
- Deduplicate stripCwdPrefix into tool-call-parsers.ts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2026-01-07 12:38:04 +07:00
co-authored by Claude Opus 4.5
parent 4f3b2317ab
commit 66881511b6
18 changed files with 475 additions and 445 deletions
@@ -283,15 +283,11 @@ export function AgentStreamView({
case "tool_call": {
const { payload } = item;
console.log("[TOOL_CALL_DEBUG]", JSON.stringify(payload, null, 2));
if (payload.source === "agent") {
const data = payload.data;
const toolLabel = data.displayName ?? `${data.server}/${data.tool}`;
content = (
<ToolCall
toolName={toolLabel}
kind={data.kind}
toolName={data.name}
args={data.input}
result={data.result}
error={data.error}
@@ -299,6 +295,7 @@ export function AgentStreamView({
parsedEditEntries={data.parsedEdits}
parsedReadEntries={data.parsedReads}
parsedCommandDetails={data.parsedCommand ?? null}
cwd={agent.cwd}
/>
);
} else {
@@ -315,7 +315,7 @@ const styles = StyleSheet.create((theme) => ({
overflow: "hidden",
backgroundColor: theme.colors.muted,
borderWidth: theme.borderWidth[1],
borderColor: theme.colors.border,
borderColor: theme.colors.accentBorder,
marginBottom: theme.spacing[2],
},
fileHeader: {
+50 -10
View File
@@ -29,6 +29,7 @@ import { Colors } from "@/constants/theme";
import * as Clipboard from "expo-clipboard";
import type { TodoEntry, ThoughtStatus } from "@/types/stream";
import type { CommandDetails, EditEntry, ReadEntry } from "@/utils/tool-call-parsers";
import { extractPrincipalParam } from "@/utils/tool-call-parsers";
import { resolveToolCallPreview } from "./tool-call-preview";
import { useToolCallSheet } from "./tool-call-sheet";
@@ -41,15 +42,16 @@ const userMessageStylesheet = StyleSheet.create((theme) => ({
container: {
flexDirection: "row",
justifyContent: "flex-end",
marginBottom: theme.spacing[3],
paddingHorizontal: theme.spacing[4],
marginBottom: theme.spacing[4],
marginTop: theme.spacing[4],
paddingHorizontal: theme.spacing[2],
},
bubble: {
backgroundColor: theme.colors.muted,
borderRadius: theme.borderRadius["2xl"],
borderTopRightRadius: theme.borderRadius.sm,
paddingHorizontal: theme.spacing[4],
paddingVertical: theme.spacing[3],
paddingVertical: theme.spacing[4],
maxWidth: "80%",
},
text: {
@@ -187,7 +189,7 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({
pressable: {
borderRadius: theme.borderRadius.lg,
borderWidth: theme.borderWidth[1],
borderColor: theme.colors.border,
borderColor: theme.colors.accentBorder,
backgroundColor: theme.colors.secondary,
paddingHorizontal: theme.spacing[2],
paddingVertical: theme.spacing[1],
@@ -209,13 +211,24 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({
backgroundColor: "transparent",
},
label: {
color: theme.colors.foreground,
fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.medium,
flexShrink: 0,
},
secondaryLabel: {
flex: 1,
color: theme.colors.mutedForeground,
fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.normal,
marginLeft: theme.spacing[2],
},
spacer: {
flex: 1,
},
chevron: {
marginLeft: theme.spacing[1],
flexShrink: 0,
},
detailWrapper: {
marginTop: theme.spacing[2],
@@ -829,6 +842,7 @@ interface AgentThoughtMessageProps {
interface ExpandableBadgeProps {
label: string;
secondaryLabel?: string;
icon?: ComponentType<{ size?: number; color?: string }>;
isExpanded: boolean;
onToggle: () => void;
@@ -839,6 +853,7 @@ interface ExpandableBadgeProps {
const ExpandableBadge = memo(function ExpandableBadge({
label,
secondaryLabel,
icon,
isExpanded,
onToggle,
@@ -914,6 +929,13 @@ const ExpandableBadge = memo(function ExpandableBadge({
<Text style={expandableBadgeStylesheet.label} numberOfLines={1}>
{label}
</Text>
{secondaryLabel ? (
<Text style={expandableBadgeStylesheet.secondaryLabel} numberOfLines={1}>
{secondaryLabel}
</Text>
) : (
<View style={expandableBadgeStylesheet.spacer} />
)}
{hasDetails ? (
<ChevronRight
size={14}
@@ -1084,7 +1106,6 @@ export function AgentThoughtMessage({ message, status = "ready" }: AgentThoughtM
interface ToolCallProps {
toolName: string;
kind?: string; // Optional kind for ACP tool calls
args: any;
result?: any;
error?: any;
@@ -1092,6 +1113,7 @@ interface ToolCallProps {
parsedEditEntries?: EditEntry[];
parsedReadEntries?: ReadEntry[];
parsedCommandDetails?: CommandDetails | null;
cwd?: string;
}
// Icon mapping for tool kinds
@@ -1102,9 +1124,19 @@ const toolKindIcons: Record<string, any> = {
search: Search,
};
// Derive tool kind from tool name for icon selection
function getToolKindFromName(toolName: string): string {
const lower = toolName.toLowerCase();
if (lower === "read" || lower === "read_file" || lower.startsWith("read")) return "read";
if (lower === "edit" || lower === "write" || lower === "apply_patch") return "edit";
if (lower === "bash" || lower === "shell") return "execute";
if (lower === "grep" || lower === "glob" || lower === "web_search") return "search";
return "tool";
}
export const ToolCall = memo(function ToolCall({
toolName,
kind,
args,
result,
error,
@@ -1112,12 +1144,18 @@ export const ToolCall = memo(function ToolCall({
parsedEditEntries,
parsedReadEntries,
parsedCommandDetails,
cwd,
}: ToolCallProps) {
const { openToolCall } = useToolCallSheet();
const IconComponent = kind
? toolKindIcons[kind.toLowerCase()] || Wrench
: Wrench;
const kind = getToolKindFromName(toolName);
const IconComponent = toolKindIcons[kind] || Wrench;
// Extract principal param for secondary label (memoized)
const principalParam = useMemo(
() => extractPrincipalParam(args, cwd),
[args, cwd]
);
// Check if there's any content to display in the sheet
const hasDetails = args !== undefined || result !== undefined || error !== undefined;
@@ -1133,8 +1171,9 @@ export const ToolCall = memo(function ToolCall({
parsedEditEntries,
parsedReadEntries,
parsedCommandDetails,
cwd,
});
}, [openToolCall, toolName, kind, status, args, result, error, parsedEditEntries, parsedReadEntries, parsedCommandDetails]);
}, [openToolCall, toolName, kind, status, args, result, error, parsedEditEntries, parsedReadEntries, parsedCommandDetails, cwd]);
// Dummy renderDetails to make badge tappable - actual content is rendered in the sheet
const dummyRenderDetails = useCallback(() => null, []);
@@ -1142,6 +1181,7 @@ export const ToolCall = memo(function ToolCall({
return (
<ExpandableBadge
label={toolName}
secondaryLabel={principalParam}
icon={IconComponent}
isExpanded={false}
onToggle={handleOpenSheet}
@@ -17,6 +17,7 @@ import { Pencil, Eye, SquareTerminal, Search, Wrench, X } from "lucide-react-nat
import { DiffViewer } from "./diff-viewer";
import {
extractKeyValuePairs,
stripCwdPrefix,
type EditEntry,
type ReadEntry,
type CommandDetails,
@@ -36,6 +37,7 @@ export interface ToolCallSheetData {
parsedEditEntries?: EditEntry[];
parsedReadEntries?: ReadEntry[];
parsedCommandDetails?: CommandDetails | null;
cwd?: string;
}
interface ToolCallSheetContextValue {
@@ -234,6 +236,7 @@ function ToolCallSheetContent({ data, onClose }: ToolCallSheetContentProps) {
parsedEditEntries,
parsedReadEntries,
parsedCommandDetails,
cwd,
} = data;
const IconComponent = kind
@@ -453,7 +456,7 @@ function ToolCallSheetContent({ data, onClose }: ToolCallSheetContentProps) {
<Text style={styles.sectionTitle}>Diff</Text>
{entry.filePath ? (
<View style={styles.fileBadge}>
<Text style={styles.fileBadgeText}>{entry.filePath}</Text>
<Text style={styles.fileBadgeText}>{stripCwdPrefix(entry.filePath, cwd)}</Text>
</View>
) : null}
<View style={styles.diffContainer}>
@@ -478,7 +481,7 @@ function ToolCallSheetContent({ data, onClose }: ToolCallSheetContentProps) {
<Text style={styles.sectionTitle}>Read Result</Text>
{entry.filePath ? (
<View style={styles.fileBadge}>
<Text style={styles.fileBadgeText}>{entry.filePath}</Text>
<Text style={styles.fileBadgeText}>{stripCwdPrefix(entry.filePath, cwd)}</Text>
</View>
) : null}
<ScrollView