Skip to content

Commit 8cb12e7

Browse files
committed
🤖 Fix tool error display and WRITE DENIED collapse
Two fixes: 1. Tool error display: GenericToolCall now properly detects and displays tool errors in both formats ({ error: '...' } from AI SDK and { success: false, error: '...' } from tool implementations). Status is correctly shown as 'failed' instead of 'completed' for errors. 2. WRITE DENIED collapse: FileEditToolCall now uses useEffect to collapse when WRITE DENIED result arrives, fixing issue where initial render (before result available) would expand the tool call. Both formats are now consistently handled across all tool types.
1 parent 014e0cc commit 8cb12e7

File tree

3 files changed

+47
-4
lines changed

3 files changed

+47
-4
lines changed

src/components/tools/FileEditToolCall.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,18 @@ export const FileEditToolCall: React.FC<FileEditToolCallProps> = ({
102102
const isWriteDenied = result && !result.success && result.error?.startsWith(WRITE_DENIED_PREFIX);
103103
const initialExpanded = !isWriteDenied;
104104

105-
const { expanded, toggleExpanded } = useToolExpansion(initialExpanded);
105+
const { expanded, setExpanded, toggleExpanded } = useToolExpansion(initialExpanded);
106106
const [showRaw, setShowRaw] = React.useState(false);
107107
const [copied, setCopied] = React.useState(false);
108108

109+
// Update expanded state when result changes from undefined to WRITE DENIED
110+
// This handles the case where the component renders before the result is available
111+
React.useEffect(() => {
112+
if (result && !result.success && result.error?.startsWith(WRITE_DENIED_PREFIX)) {
113+
setExpanded(false);
114+
}
115+
}, [result, setExpanded]);
116+
109117
const filePath = "file_path" in args ? args.file_path : undefined;
110118

111119
const handleCopyPatch = async () => {

src/components/tools/GenericToolCall.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ export const GenericToolCall: React.FC<GenericToolCallProps> = ({
3333
}) => {
3434
const { expanded, toggleExpanded } = useToolExpansion();
3535

36+
// Check if result contains an error
37+
// Handles two formats:
38+
// 1. Tool implementation errors: { success: false, error: "..." }
39+
// 2. AI SDK tool-error events: { error: "..." }
40+
const hasError =
41+
result &&
42+
typeof result === "object" &&
43+
"error" in result &&
44+
typeof result.error === "string" &&
45+
result.error.length > 0 &&
46+
(!("success" in result) || result.success === false);
47+
3648
const hasDetails = args !== undefined || result !== undefined;
3749

3850
return (
@@ -52,7 +64,16 @@ export const GenericToolCall: React.FC<GenericToolCallProps> = ({
5264
</DetailSection>
5365
)}
5466

55-
{result !== undefined && (
67+
{hasError ? (
68+
<DetailSection>
69+
<DetailLabel>Error</DetailLabel>
70+
<div className="text-danger bg-danger-overlay border-danger rounded border-l-2 px-2 py-1.5 text-[11px]">
71+
{String((result as { error: string }).error)}
72+
</div>
73+
</DetailSection>
74+
) : null}
75+
76+
{result !== undefined && !hasError && (
5677
<DetailSection>
5778
<DetailLabel>Result</DetailLabel>
5879
<DetailContent>{formatValue(result)}</DetailContent>

src/utils/messages/StreamingMessageAggregator.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -673,8 +673,22 @@ export class StreamingMessageAggregator {
673673
timestamp: part.timestamp ?? baseTimestamp,
674674
});
675675
} else if (isDynamicToolPart(part)) {
676-
const status =
677-
part.state === "output-available"
676+
// Check if output contains an error
677+
// Handles two formats:
678+
// 1. Tool implementation errors: { success: false, error: "..." }
679+
// 2. AI SDK tool-error events: { error: "..." }
680+
const hasError =
681+
part.state === "output-available" &&
682+
part.output &&
683+
typeof part.output === "object" &&
684+
"error" in part.output &&
685+
typeof part.output.error === "string" &&
686+
part.output.error.length > 0 &&
687+
(!("success" in part.output) || part.output.success === false);
688+
689+
const status = hasError
690+
? "failed"
691+
: part.state === "output-available"
678692
? "completed"
679693
: part.state === "input-available" && message.metadata?.partial
680694
? "interrupted"

0 commit comments

Comments
 (0)