Update files
This commit is contained in:
@@ -47,7 +47,6 @@ import type { Agent } from "@/contexts/session-context";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import { useFileExplorerActions } from "@/hooks/use-file-explorer-actions";
|
||||
import type { DaemonClient } from "@server/client/daemon-client";
|
||||
import { parseToolCallDisplay } from "@/utils/tool-call-parsers";
|
||||
import { ToolCallDetailsContent } from "./tool-call-details";
|
||||
import { QuestionFormCard } from "./question-form-card";
|
||||
import { ToolCallSheetProvider } from "./tool-call-sheet";
|
||||
@@ -363,11 +362,11 @@ export function AgentStreamView({
|
||||
return (
|
||||
<ToolCall
|
||||
toolName={data.name}
|
||||
provider={data.provider}
|
||||
args={data.input}
|
||||
result={data.result}
|
||||
error={data.error}
|
||||
status={data.status as "executing" | "completed" | "failed"}
|
||||
status={data.status}
|
||||
detail={data.detail}
|
||||
cwd={agent.cwd}
|
||||
metadata={data.metadata}
|
||||
isLastInSequence={isLastInSequence}
|
||||
@@ -904,17 +903,6 @@ function PermissionRequestCard({
|
||||
return undefined;
|
||||
}, [request]);
|
||||
|
||||
const toolCallDisplay = useMemo(() => {
|
||||
if (isPlanRequest) {
|
||||
return null;
|
||||
}
|
||||
return parseToolCallDisplay({
|
||||
name: request.name ?? "unknown",
|
||||
provider: request.provider,
|
||||
input: request.input,
|
||||
});
|
||||
}, [isPlanRequest, request.name, request.provider, request.input]);
|
||||
|
||||
const markdownStyles = useMemo(() => createMarkdownStyles(theme), [theme]);
|
||||
|
||||
const markdownRules = useMemo(() => {
|
||||
@@ -1133,8 +1121,12 @@ function PermissionRequestCard({
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{!isPlanRequest && toolCallDisplay ? (
|
||||
<ToolCallDetailsContent detail={toolCallDisplay.detail} maxHeight={200} />
|
||||
{!isPlanRequest ? (
|
||||
<ToolCallDetailsContent
|
||||
input={request.input ?? null}
|
||||
output={null}
|
||||
maxHeight={200}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Text
|
||||
|
||||
@@ -62,18 +62,22 @@ import {
|
||||
useUnistyles,
|
||||
UnistylesRuntime,
|
||||
} from "react-native-unistyles";
|
||||
import { baseColors, theme } from "@/styles/theme";
|
||||
import { theme } from "@/styles/theme";
|
||||
import {
|
||||
createMarkdownStyles,
|
||||
} from "@/styles/markdown-styles";
|
||||
import { Colors, Fonts } from "@/constants/theme";
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
import type { TodoEntry } from "@/types/stream";
|
||||
import { parseToolCallDisplay, type ToolCallKind } from "@/utils/tool-call-parsers";
|
||||
import type { ToolCallDetail } from "@server/server/agent/agent-sdk-types";
|
||||
import {
|
||||
buildToolCallDisplayModel,
|
||||
formatToolCallError,
|
||||
type ToolCallKind,
|
||||
} from "@/utils/tool-call-display";
|
||||
import { getNowMs, isPerfLoggingEnabled, perfLog } from "@/utils/perf";
|
||||
import { parseInlinePathToken, type InlinePathTarget } from "@/utils/inline-path";
|
||||
export type { InlinePathTarget } from "@/utils/inline-path";
|
||||
import { resolveToolCallPreview } from "./tool-call-preview";
|
||||
import { useToolCallSheet } from "./tool-call-sheet";
|
||||
import { ToolCallDetailsContent } from "./tool-call-details";
|
||||
|
||||
@@ -106,6 +110,29 @@ function useDisableOuterSpacing(disableOuterSpacing: boolean | undefined) {
|
||||
return disableOuterSpacing ?? contextValue;
|
||||
}
|
||||
|
||||
function hexToRgba(hexColor: string, alpha: number) {
|
||||
const clampedAlpha = Math.max(0, Math.min(1, alpha));
|
||||
const hex = hexColor.replace("#", "");
|
||||
const normalized =
|
||||
hex.length === 3
|
||||
? hex
|
||||
.split("")
|
||||
.map((char) => `${char}${char}`)
|
||||
.join("")
|
||||
: hex;
|
||||
|
||||
if (!/^[\da-fA-F]{6}$/.test(normalized)) {
|
||||
return `rgba(255, 255, 255, ${clampedAlpha})`;
|
||||
}
|
||||
|
||||
const intValue = Number.parseInt(normalized, 16);
|
||||
const red = (intValue >> 16) & 255;
|
||||
const green = (intValue >> 8) & 255;
|
||||
const blue = intValue & 255;
|
||||
|
||||
return `rgba(${red}, ${green}, ${blue}, ${clampedAlpha})`;
|
||||
}
|
||||
|
||||
const userMessageStylesheet = StyleSheet.create((theme) => ({
|
||||
container: {
|
||||
flexDirection: "row",
|
||||
@@ -418,8 +445,9 @@ const expandableBadgeStylesheet = StyleSheet.create((theme) => ({
|
||||
},
|
||||
shimmerOverlay: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
top: 3,
|
||||
bottom: 3,
|
||||
borderRadius: 999,
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -1026,6 +1054,14 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
||||
}, [isLoading]);
|
||||
|
||||
const shimmerBandWidth = 14;
|
||||
const shimmerGradientColors = useMemo(
|
||||
() => ({
|
||||
edge: hexToRgba(theme.colors.foreground, 0),
|
||||
soft: hexToRgba(theme.colors.foreground, 0.08),
|
||||
core: hexToRgba(theme.colors.foreground, 0.24),
|
||||
}),
|
||||
[theme.colors.foreground]
|
||||
);
|
||||
const shimmerStyle = useAnimatedStyle(() => {
|
||||
const travel = badgeWidth + shimmerBandWidth;
|
||||
return {
|
||||
@@ -1112,13 +1148,13 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
||||
x2="1"
|
||||
y2="0"
|
||||
>
|
||||
<Stop offset="0" stopColor={baseColors.white} stopOpacity="0" />
|
||||
<Stop offset="0.34" stopColor={baseColors.white} stopOpacity="0" />
|
||||
<Stop offset="0.46" stopColor={baseColors.white} stopOpacity="0.12" />
|
||||
<Stop offset="0.5" stopColor={baseColors.white} stopOpacity="0.34" />
|
||||
<Stop offset="0.54" stopColor={baseColors.white} stopOpacity="0.12" />
|
||||
<Stop offset="0.66" stopColor={baseColors.white} stopOpacity="0" />
|
||||
<Stop offset="1" stopColor={baseColors.white} stopOpacity="0" />
|
||||
<Stop offset="0" stopColor={shimmerGradientColors.edge} />
|
||||
<Stop offset="0.38" stopColor={shimmerGradientColors.edge} />
|
||||
<Stop offset="0.48" stopColor={shimmerGradientColors.soft} />
|
||||
<Stop offset="0.5" stopColor={shimmerGradientColors.core} />
|
||||
<Stop offset="0.52" stopColor={shimmerGradientColors.soft} />
|
||||
<Stop offset="0.62" stopColor={shimmerGradientColors.edge} />
|
||||
<Stop offset="1" stopColor={shimmerGradientColors.edge} />
|
||||
</LinearGradient>
|
||||
</Defs>
|
||||
<Rect width="100%" height="100%" fill="url(#shimmerGrad)" />
|
||||
@@ -1155,11 +1191,11 @@ const ExpandableBadge = memo(function ExpandableBadge({
|
||||
|
||||
interface ToolCallProps {
|
||||
toolName: string;
|
||||
provider?: string;
|
||||
args: any;
|
||||
result?: any;
|
||||
error?: any;
|
||||
status: "executing" | "completed" | "failed";
|
||||
status: "executing" | "running" | "completed" | "failed" | "canceled";
|
||||
detail?: ToolCallDetail;
|
||||
cwd?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
isLastInSequence?: boolean;
|
||||
@@ -1183,11 +1219,11 @@ const TOOL_CALL_COMMIT_THRESHOLD_MS = 16;
|
||||
|
||||
export const ToolCall = memo(function ToolCall({
|
||||
toolName,
|
||||
provider,
|
||||
args,
|
||||
result,
|
||||
error,
|
||||
status,
|
||||
detail,
|
||||
cwd,
|
||||
metadata,
|
||||
isLastInSequence = false,
|
||||
@@ -1204,20 +1240,14 @@ export const ToolCall = memo(function ToolCall({
|
||||
UnistylesRuntime.breakpoint === "xs" ||
|
||||
UnistylesRuntime.breakpoint === "sm";
|
||||
|
||||
const displayInfo = useMemo(
|
||||
() =>
|
||||
parseToolCallDisplay({
|
||||
name: toolName,
|
||||
provider,
|
||||
input: args,
|
||||
output: result,
|
||||
error,
|
||||
metadata,
|
||||
cwd,
|
||||
}),
|
||||
[toolName, provider, args, result, error, metadata, cwd]
|
||||
const displayModel = useMemo(
|
||||
() => buildToolCallDisplayModel({ name: toolName, detail, metadata, cwd }),
|
||||
[toolName, detail, metadata, cwd]
|
||||
);
|
||||
const { kind, displayName, summary, detail, errorText } = displayInfo;
|
||||
const displayName = displayModel.displayName;
|
||||
const kind: ToolCallKind = displayModel.kind;
|
||||
const summary = displayModel.summary;
|
||||
const errorText = useMemo(() => formatToolCallError(error), [error]);
|
||||
const IconComponent = toolKindIcons[kind] || Wrench;
|
||||
|
||||
// Check if there's any content to display
|
||||
@@ -1229,11 +1259,19 @@ export const ToolCall = memo(function ToolCall({
|
||||
toggleStartRef.current = getNowMs();
|
||||
}
|
||||
if (isMobile) {
|
||||
openToolCall(displayInfo);
|
||||
openToolCall({
|
||||
kind,
|
||||
displayName,
|
||||
summary,
|
||||
detail,
|
||||
input: args,
|
||||
output: result,
|
||||
errorText,
|
||||
});
|
||||
} else {
|
||||
setIsExpanded((prev) => !prev);
|
||||
}
|
||||
}, [isMobile, openToolCall, displayInfo]);
|
||||
}, [isMobile, openToolCall, kind, displayName, summary, detail, args, result, errorText]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isMobile || !isPerfLoggingEnabled()) {
|
||||
@@ -1292,8 +1330,16 @@ export const ToolCall = memo(function ToolCall({
|
||||
// Render inline details for desktop
|
||||
const renderDetails = useCallback(() => {
|
||||
if (isMobile) return null;
|
||||
return <ToolCallDetailsContent detail={detail} errorText={errorText} maxHeight={400} />;
|
||||
}, [isMobile, detail, errorText]);
|
||||
return (
|
||||
<ToolCallDetailsContent
|
||||
detail={detail}
|
||||
input={args}
|
||||
output={result}
|
||||
errorText={errorText}
|
||||
maxHeight={400}
|
||||
/>
|
||||
);
|
||||
}, [isMobile, detail, args, result, errorText]);
|
||||
|
||||
return (
|
||||
<ExpandableBadge
|
||||
@@ -1310,7 +1356,7 @@ export const ToolCall = memo(function ToolCall({
|
||||
? () => null
|
||||
: undefined
|
||||
}
|
||||
isLoading={status === "executing"}
|
||||
isLoading={status === "executing" || status === "running"}
|
||||
isError={status === "failed"}
|
||||
isLastInSequence={isLastInSequence}
|
||||
disableOuterSpacing={disableOuterSpacing}
|
||||
|
||||
@@ -3,10 +3,10 @@ import { View, Text, Platform, ScrollView as RNScrollView } from "react-native";
|
||||
import { ScrollView as GHScrollView } from "react-native-gesture-handler";
|
||||
import { StyleSheet } from "react-native-unistyles";
|
||||
import { Fonts } from "@/constants/theme";
|
||||
import type { ToolCallDetail } from "@server/server/agent/agent-sdk-types";
|
||||
import {
|
||||
buildLineDiff,
|
||||
parseUnifiedDiff,
|
||||
type ToolCallDetail,
|
||||
} from "@/utils/tool-call-parsers";
|
||||
import { DiffViewer } from "./diff-viewer";
|
||||
import { getCodeInsets } from "./code-insets";
|
||||
@@ -16,34 +16,39 @@ const ScrollView = Platform.OS === "web" ? RNScrollView : GHScrollView;
|
||||
// ---- Content Component ----
|
||||
|
||||
interface ToolCallDetailsContentProps {
|
||||
detail: ToolCallDetail;
|
||||
detail?: ToolCallDetail;
|
||||
input?: unknown | null;
|
||||
output?: unknown | null;
|
||||
errorText?: string;
|
||||
maxHeight?: number;
|
||||
}
|
||||
|
||||
export function ToolCallDetailsContent({
|
||||
detail,
|
||||
input,
|
||||
output,
|
||||
errorText,
|
||||
maxHeight = 300,
|
||||
}: ToolCallDetailsContentProps) {
|
||||
// Compute diff lines for edit type
|
||||
const diffLines = useMemo(() => {
|
||||
if (detail.type !== "edit") return undefined;
|
||||
if (!detail || detail.type !== "edit") return undefined;
|
||||
// Use pre-computed unified diff if available (e.g., from apply_patch)
|
||||
if (detail.unifiedDiff) {
|
||||
return parseUnifiedDiff(detail.unifiedDiff);
|
||||
}
|
||||
return buildLineDiff(detail.oldString, detail.newString);
|
||||
return buildLineDiff(detail.oldString ?? "", detail.newString ?? "");
|
||||
}, [detail]);
|
||||
|
||||
const sections: ReactNode[] = [];
|
||||
const isFullBleed = detail.type === "edit" || detail.type === "shell";
|
||||
const isFullBleed =
|
||||
detail?.type === "edit" || detail?.type === "shell" || detail?.type === "write";
|
||||
const codeBlockStyle = isFullBleed ? styles.fullBleedBlock : styles.diffContainer;
|
||||
|
||||
if (detail.type === "shell") {
|
||||
if (detail?.type === "shell") {
|
||||
const command = detail.command.replace(/\n+$/, "");
|
||||
const output = detail.output.replace(/^\n+/, "");
|
||||
const hasOutput = output.length > 0;
|
||||
const commandOutput = (detail.output ?? "").replace(/^\n+/, "");
|
||||
const hasOutput = commandOutput.length > 0;
|
||||
sections.push(
|
||||
<View key="shell" style={styles.section}>
|
||||
<View style={codeBlockStyle}>
|
||||
@@ -63,7 +68,7 @@ export function ToolCallDetailsContent({
|
||||
<Text selectable style={styles.scrollText}>
|
||||
<Text style={styles.shellPrompt}>$ </Text>
|
||||
{command}
|
||||
{hasOutput ? `\n\n${output}` : ""}
|
||||
{hasOutput ? `\n\n${commandOutput}` : ""}
|
||||
</Text>
|
||||
</View>
|
||||
</ScrollView>
|
||||
@@ -71,7 +76,7 @@ export function ToolCallDetailsContent({
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
} else if (detail.type === "edit") {
|
||||
} else if (detail?.type === "edit") {
|
||||
sections.push(
|
||||
<View key="edit" style={styles.section}>
|
||||
{diffLines ? (
|
||||
@@ -81,7 +86,28 @@ export function ToolCallDetailsContent({
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
} else if (detail.type === "read") {
|
||||
} else if (detail?.type === "write") {
|
||||
sections.push(
|
||||
<View key="write" style={styles.section}>
|
||||
{detail.content ? (
|
||||
<ScrollView
|
||||
style={[styles.scrollArea, { maxHeight }]}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator={true}
|
||||
>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text selectable style={styles.scrollText}>{detail.content}</Text>
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
} else if (detail?.type === "read") {
|
||||
sections.push(
|
||||
<View key="read" style={styles.section}>
|
||||
{(detail.offset !== undefined || detail.limit !== undefined) ? (
|
||||
@@ -109,68 +135,49 @@ export function ToolCallDetailsContent({
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
} else if (detail.type === "thinking") {
|
||||
// Thinking: display the content as plain text
|
||||
} else if (detail?.type === "search") {
|
||||
sections.push(
|
||||
<View key="thinking" style={styles.section}>
|
||||
<ScrollView
|
||||
style={[styles.scrollArea, { maxHeight }]}
|
||||
contentContainerStyle={styles.scrollContent}
|
||||
nestedScrollEnabled
|
||||
showsVerticalScrollIndicator={true}
|
||||
>
|
||||
<Text selectable style={styles.scrollText}>{detail.content}</Text>
|
||||
</ScrollView>
|
||||
<View key="search" style={styles.section}>
|
||||
<Text selectable style={styles.scrollText}>{detail.query}</Text>
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
// Generic tool: show input/output as key-value pairs
|
||||
if (detail.input.length > 0) {
|
||||
sections.push(
|
||||
<View key="input-header" style={styles.groupHeader}>
|
||||
<Text style={styles.groupHeaderText}>Input</Text>
|
||||
</View>
|
||||
);
|
||||
detail.input.forEach((pair, index) => {
|
||||
sections.push(
|
||||
<View key={`input-${index}-${pair.key}`} style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>{pair.key}</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={styles.jsonScroll}
|
||||
contentContainerStyle={styles.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text selectable style={styles.scrollText}>{pair.value}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
}
|
||||
const sectionsFromTopLevel = [
|
||||
{ title: "Input", value: input },
|
||||
{ title: "Output", value: output },
|
||||
].filter((entry) => entry.value !== null && entry.value !== undefined);
|
||||
|
||||
if (detail.output.length > 0) {
|
||||
for (const section of sectionsFromTopLevel) {
|
||||
let value = "";
|
||||
try {
|
||||
value =
|
||||
typeof section.value === "string"
|
||||
? section.value
|
||||
: JSON.stringify(section.value, null, 2);
|
||||
} catch {
|
||||
value = String(section.value);
|
||||
}
|
||||
if (!value.length) {
|
||||
continue;
|
||||
}
|
||||
sections.push(
|
||||
<View key="output-header" style={styles.groupHeader}>
|
||||
<Text style={styles.groupHeaderText}>Output</Text>
|
||||
<View key={`${section.title}-header`} style={styles.groupHeader}>
|
||||
<Text style={styles.groupHeaderText}>{section.title}</Text>
|
||||
</View>
|
||||
);
|
||||
sections.push(
|
||||
<View key={`${section.title}-value`} style={styles.section}>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={styles.jsonScroll}
|
||||
contentContainerStyle={styles.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text selectable style={styles.scrollText}>{value}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
detail.output.forEach((pair, index) => {
|
||||
sections.push(
|
||||
<View key={`output-${index}-${pair.key}`} style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>{pair.key}</Text>
|
||||
<ScrollView
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
style={styles.jsonScroll}
|
||||
contentContainerStyle={styles.jsonContent}
|
||||
showsHorizontalScrollIndicator={true}
|
||||
>
|
||||
<Text selectable style={styles.scrollText}>{pair.value}</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { resolveToolCallPreview } from "./tool-call-preview";
|
||||
import type { CommandDetails, EditEntry, ReadEntry } from "@/utils/tool-call-parsers";
|
||||
|
||||
describe("resolveToolCallPreview", () => {
|
||||
it("prefers parsed hydration payloads when provided", () => {
|
||||
const parsedEdits: EditEntry[] = [
|
||||
{
|
||||
filePath: "README.md",
|
||||
diffLines: [
|
||||
{ type: "header", content: "@@ -1 +1 @@" },
|
||||
{ type: "remove", content: "-Old" },
|
||||
{ type: "add", content: "+New" },
|
||||
],
|
||||
},
|
||||
];
|
||||
const parsedReads: ReadEntry[] = [
|
||||
{
|
||||
filePath: "README.md",
|
||||
content: "# Hydrated\nFinal text\n",
|
||||
},
|
||||
];
|
||||
const parsedCommand: CommandDetails = {
|
||||
command: "ls",
|
||||
cwd: "/tmp/hydrated",
|
||||
output: "README.md\npackages\n",
|
||||
exitCode: 0,
|
||||
};
|
||||
|
||||
const preview = resolveToolCallPreview({
|
||||
parsedEditEntries: parsedEdits,
|
||||
parsedReadEntries: parsedReads,
|
||||
parsedCommandDetails: parsedCommand,
|
||||
});
|
||||
|
||||
expect(preview.editEntries).toBe(parsedEdits);
|
||||
expect(preview.readEntries).toBe(parsedReads);
|
||||
expect(preview.commandDetails).toBe(parsedCommand);
|
||||
});
|
||||
|
||||
it("falls back to derived parser output when hydration metadata is missing", () => {
|
||||
const args = {
|
||||
type: "mcp_tool_use",
|
||||
id: "call_fallback",
|
||||
name: "apply_patch",
|
||||
server: "editor",
|
||||
input: {
|
||||
file_path: "README.md",
|
||||
patch: "*** Begin Patch\n*** Update File: README.md\n@@\n-Old\n+New\n*** End Patch",
|
||||
},
|
||||
};
|
||||
const result = {
|
||||
changes: [
|
||||
{
|
||||
file_path: "README.md",
|
||||
previous_content: "Old\n",
|
||||
content: "New\n",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const preview = resolveToolCallPreview({
|
||||
args,
|
||||
result,
|
||||
});
|
||||
|
||||
expect(preview.editEntries[0]?.diffLines.length).toBeGreaterThan(0);
|
||||
expect(
|
||||
preview.editEntries[0]?.diffLines.some((line) => line.content.includes("+New"))
|
||||
).toBe(true);
|
||||
expect(preview.commandDetails).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -1,40 +0,0 @@
|
||||
import {
|
||||
extractCommandDetails,
|
||||
extractEditEntries,
|
||||
extractReadEntries,
|
||||
type CommandDetails,
|
||||
type EditEntry,
|
||||
type ReadEntry,
|
||||
} from "@/utils/tool-call-parsers";
|
||||
|
||||
export type ToolCallPreviewSource = {
|
||||
args?: unknown;
|
||||
result?: unknown;
|
||||
parsedEditEntries?: EditEntry[] | undefined;
|
||||
parsedReadEntries?: ReadEntry[] | undefined;
|
||||
parsedCommandDetails?: CommandDetails | null | undefined;
|
||||
};
|
||||
|
||||
export type ToolCallPreview = {
|
||||
editEntries: EditEntry[];
|
||||
readEntries: ReadEntry[];
|
||||
commandDetails: CommandDetails | null | undefined;
|
||||
};
|
||||
|
||||
export function resolveToolCallPreview({
|
||||
args,
|
||||
result,
|
||||
parsedEditEntries,
|
||||
parsedReadEntries,
|
||||
parsedCommandDetails,
|
||||
}: ToolCallPreviewSource): ToolCallPreview {
|
||||
const fallbackEditEntries = extractEditEntries(args, result);
|
||||
const fallbackReadEntries = extractReadEntries(result, args);
|
||||
const fallbackCommandDetails = extractCommandDetails(args, result);
|
||||
|
||||
return {
|
||||
editEntries: parsedEditEntries ?? fallbackEditEntries,
|
||||
readEntries: parsedReadEntries ?? fallbackReadEntries,
|
||||
commandDetails: parsedCommandDetails ?? fallbackCommandDetails,
|
||||
};
|
||||
}
|
||||
@@ -16,12 +16,21 @@ import {
|
||||
BottomSheetBackgroundProps,
|
||||
} from "@gorhom/bottom-sheet";
|
||||
import { Pencil, Eye, SquareTerminal, Search, Bot, Wrench, X } from "lucide-react-native";
|
||||
import type { ToolCallDisplayInfo } from "@/utils/tool-call-parsers";
|
||||
import type { ToolCallDetail } from "@server/server/agent/agent-sdk-types";
|
||||
import type { ToolCallKind } from "@/utils/tool-call-display";
|
||||
import { ToolCallDetailsContent } from "./tool-call-details";
|
||||
|
||||
// ----- Types -----
|
||||
|
||||
export type ToolCallSheetData = ToolCallDisplayInfo;
|
||||
export type ToolCallSheetData = {
|
||||
kind: ToolCallKind;
|
||||
displayName: string;
|
||||
summary?: string;
|
||||
detail?: ToolCallDetail;
|
||||
input?: unknown | null;
|
||||
output?: unknown | null;
|
||||
errorText?: string;
|
||||
};
|
||||
|
||||
interface ToolCallSheetContextValue {
|
||||
openToolCall: (data: ToolCallSheetData) => void;
|
||||
@@ -133,7 +142,7 @@ interface ToolCallSheetContentProps {
|
||||
}
|
||||
|
||||
function ToolCallSheetContent({ data, onClose }: ToolCallSheetContentProps) {
|
||||
const { kind, displayName, detail, errorText } = data;
|
||||
const { kind, displayName, detail, input, output, errorText } = data;
|
||||
|
||||
const IconComponent = toolKindIcons[kind] || Wrench;
|
||||
|
||||
@@ -157,7 +166,12 @@ function ToolCallSheetContent({ data, onClose }: ToolCallSheetContentProps) {
|
||||
style={styles.content}
|
||||
contentContainerStyle={styles.contentContainer}
|
||||
>
|
||||
<ToolCallDetailsContent detail={detail} errorText={errorText} />
|
||||
<ToolCallDetailsContent
|
||||
detail={detail}
|
||||
input={input}
|
||||
output={output}
|
||||
errorText={errorText}
|
||||
/>
|
||||
</BottomSheetScrollView>
|
||||
</View>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user