"use client"; import { Check, ChevronDown, CircleAlert, LoaderCircle, ShieldCheck, X, } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { type ReactNode, useCallback, useEffect, useId, useRef, useState, } from "react"; import { AgentCode, type AgentCodeLanguage, } from "@/components/agents/agent-code"; import { AgentDisclosure } from "@/components/agents/agent-disclosure"; import { EASE_OUT, SPRING_PRESS, SPRING_SWAP } from "@/lib/ease"; import { cn } from "@/lib/utils"; export type ToolApprovalStatus = | "pending" | "approving" | "approved" | "denied" | "running" | "complete" | "error"; export interface ToolApprovalParameter { id: string; label: ReactNode; value: ReactNode; } export interface ToolApprovalCodeProps { code: string; language?: AgentCodeLanguage; className?: string; } export interface ToolApprovalProps { tool: ReactNode; title?: ReactNode; description?: ReactNode; parameters?: ToolApprovalParameter[]; status?: ToolApprovalStatus; open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean) => void; onApprove?: () => void; onAlwaysAllow?: () => void; onDeny?: () => void; className?: string; } function getStatusCopy(status: ToolApprovalStatus) { if (status === "approving") return "Approving"; if (status === "approved") return "Approved"; if (status === "denied") return "Denied"; if (status === "running") return "Running"; if (status === "complete") return "Completed"; if (status === "error") return "Failed"; return "Approval required"; } function getStatusBadgeClass(status: ToolApprovalStatus) { if (status === "pending") { return "border-amber-500/30 bg-amber-500/10 text-amber-600 dark:text-amber-400"; } if (status === "approving" || status === "running") { return "border-blue-500/30 bg-blue-500/10 text-blue-600 dark:text-blue-400"; } if (status === "approved" || status === "complete") { return "border-emerald-500/30 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"; } return "border-rose-500/30 bg-rose-500/10 text-rose-600 dark:text-rose-400"; } export function ToolApprovalCode({ code, language = "bash", className, }: ToolApprovalCodeProps) { return ( ); } export function ToolApproval({ tool, title = "Allow this tool to run?", description, parameters = [], status = "pending", open, defaultOpen = false, onOpenChange, onApprove, onAlwaysAllow, onDeny, className, }: ToolApprovalProps) { const reduce = useReducedMotion() ?? false; const baseId = useId(); const detailsId = `${baseId}-details`; const previousStatus = useRef(status); const [internalOpen, setInternalOpen] = useState(defaultOpen); const currentOpen = open ?? internalOpen; const setOpen = useCallback( (next: boolean) => { if (open === undefined) setInternalOpen(next); onOpenChange?.(next); }, [onOpenChange, open], ); const busy = status === "approving" || status === "running"; const pending = status === "pending"; const error = status === "error"; useEffect(() => { if (previousStatus.current === "pending" && status !== "pending") { setOpen(false); } previousStatus.current = status; }, [setOpen, status]); return (
{title}
{tool}
{getStatusCopy(status)}
{description ? (

{description}

) : null} {parameters.length ? ( ) : null}
{parameters.map((parameter) => (
{parameter.label}
{parameter.value}
))}
{pending ? ( Allow once {onAlwaysAllow ? ( Always allow ) : null} ) : null}
); }