Elements

33 / 122 · Tool use

Tool failure

One call failed. The error, the attempt count, and a retry that doesn't restart the turn.

fetchhttps://api.example.com/v1/issues1/3
ETIMEDOUT after 30000ms

Installation

npx shadcn@latest add "@assistant-ui/elements-tool-error"

Usage

import { ToolError } from "@/components/elements/tool-error";

<ToolError
  name="fetch"
  target="https://api.example.com/v1/issues"
  message="ETIMEDOUT after 30000ms"
  attempt={2}
  maxAttempts={3}
  retrying={false}
  onRetry={retry}
/>

Props

name*stringTool that failed.
target*stringWhat it was called against.
message*stringThe raw error, shown in mono rather than reworded.
attempt*numberWhich attempt this was.
maxAttempts*numberHow many the runtime will make.
retrying*booleanSwaps the retry control for a spinner and disables it.
onRetry() => voidCalled to run the call again without restarting the turn.
onSkip() => voidCalled to give up on this call and let the turn continue.
classNamestringExtra classes merged onto the root.

Source

tool-error.tsx
"use client";

import { AlertCircleIcon, Loader2Icon, RotateCwIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import { field, mono, paper } from "./surfaces";

export function ToolError({
  name,
  target,
  message,
  attempt,
  maxAttempts,
  retrying,
  onRetry,
  onSkip,
  className,
}: {
  name: string;
  target: string;
  message: string;
  attempt: number;
  maxAttempts: number;
  retrying: boolean;
  onRetry?: () => void;
  onSkip?: () => void;
  className?: string;
}) {
  return (
    <div
      className={cn(
        paper,
        "flex w-full max-w-sm flex-col gap-3 rounded-2xl p-3.5",
        className,
      )}
    >
      <div className="flex items-center gap-2.5">
        <AlertCircleIcon className="size-3.5 shrink-0 text-red-500" />
        <span className={cn(mono, "text-foreground/55 shrink-0")}>{name}</span>
        <span className="text-foreground/80 min-w-0 flex-1 truncate text-[13px]">
          {target}
        </span>
        <span className={cn(mono, "text-foreground/30 shrink-0 tabular-nums")}>
          {attempt}/{maxAttempts}
        </span>
      </div>

      <div
        className={cn(
          field,
          "rounded-xl px-3 py-2 font-mono text-[11px] leading-relaxed text-red-700 dark:text-red-300",
        )}
      >
        {message}
      </div>

      <div className="flex items-center justify-end gap-2">
        <button
          type="button"
          onClick={onSkip}
          disabled={!onSkip}
          className="text-foreground/45 hover:bg-foreground/[0.06] hover:text-foreground/90 h-7 rounded-full px-2.5 text-xs font-medium transition-[background-color,color,scale] duration-150 active:scale-[0.96] disabled:pointer-events-none disabled:opacity-30"
        >
          Skip
        </button>
        <button
          type="button"
          onClick={onRetry}
          disabled={retrying}
          className="text-foreground/70 hover:bg-foreground/[0.06] hover:text-foreground/95 flex h-7 items-center gap-1.5 rounded-full px-2.5 text-xs font-medium transition-[background-color,color,scale] duration-150 active:scale-[0.96] disabled:pointer-events-none"
        >
          {retrying ? (
            <Loader2Icon className="size-3 animate-spin motion-reduce:animate-none" />
          ) : (
            <RotateCwIcon className="size-3" />
          )}
          {retrying ? "Retrying" : "Retry"}
        </button>
      </div>
    </div>
  );
}