"use client"; import { makeAssistantToolUI } from "@assistant-ui/react"; import { AlertCircleIcon, CheckCircle2Icon, ChevronRightIcon, Loader2Icon, TerminalIcon, XCircleIcon, } from "lucide-react"; import { useMemo, useState } from "react"; import { z } from "zod"; import { Badge } from "@/components/ui/badge"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import { cn } from "@/lib/utils"; // ============================================================================ // Zod Schemas // ============================================================================ const ExecuteArgsSchema = z.object({ command: z.string(), timeout: z.number().nullish(), }); const ExecuteResultSchema = z.object({ result: z.string().nullish(), exit_code: z.number().nullish(), output: z.string().nullish(), error: z.string().nullish(), status: z.string().nullish(), }); // ============================================================================ // Types // ============================================================================ type ExecuteArgs = z.infer; type ExecuteResult = z.infer; interface ParsedOutput { exitCode: number | null; output: string; truncated: boolean; isError: boolean; } // ============================================================================ // Helpers // ============================================================================ function parseExecuteResult(result: ExecuteResult): ParsedOutput { const raw = result.result || result.output || ""; if (result.error) { return { exitCode: null, output: result.error, truncated: false, isError: true }; } if (result.exit_code !== undefined && result.exit_code !== null) { return { exitCode: result.exit_code, output: raw, truncated: raw.includes("[Output was truncated"), isError: result.exit_code !== 0, }; } const exitMatch = raw.match(/^Exit code:\s*(\d+)/); if (exitMatch) { const exitCode = parseInt(exitMatch[1], 10); const outputMatch = raw.match(/\nOutput:\n([\s\S]*)/); const output = outputMatch ? outputMatch[1] : ""; return { exitCode, output, truncated: raw.includes("[Output was truncated"), isError: exitCode !== 0, }; } if (raw.startsWith("Error:")) { return { exitCode: null, output: raw, truncated: false, isError: true }; } return { exitCode: null, output: raw, truncated: false, isError: false }; } function truncateCommand(command: string, maxLen = 80): string { if (command.length <= maxLen) return command; return command.slice(0, maxLen) + "…"; } // ============================================================================ // Sub-Components // ============================================================================ function ExecuteLoading({ command }: { command: string }) { return (
{truncateCommand(command)}
); } function ExecuteErrorState({ command, error }: { command: string; error: string }) { return (

Execution failed

$ {command}

{error}

); } function ExecuteCancelledState({ command }: { command: string }) { return (

$ {command}

); } function ExecuteResult({ command, parsed, }: { command: string; parsed: ParsedOutput; }) { const [open, setOpen] = useState(false); const hasOutput = parsed.output.trim().length > 0; const exitBadge = useMemo(() => { if (parsed.exitCode === null) return null; const success = parsed.exitCode === 0; return ( {success ? ( ) : ( )} {parsed.exitCode} ); }, [parsed.exitCode]); return (
{truncateCommand(command)} {exitBadge}
							{parsed.output}
						
{parsed.truncated && (

Output was truncated due to size limits

)}
); } // ============================================================================ // Tool UI // ============================================================================ export const SandboxExecuteToolUI = makeAssistantToolUI({ toolName: "execute", render: function SandboxExecuteUI({ args, result, status }) { const command = args.command || "…"; if (status.type === "running" || status.type === "requires-action") { return ; } if (status.type === "incomplete") { if (status.reason === "cancelled") { return ; } if (status.reason === "error") { return ( ); } } if (!result) { return ; } if (result.error && !result.result && !result.output) { return ; } const parsed = parseExecuteResult(result); return ; }, }); export { ExecuteArgsSchema, ExecuteResultSchema, type ExecuteArgs, type ExecuteResult, };