"use client";
import { memo } from "react";
import { IconCheck, IconX, IconTerminal } from "@tabler/icons-react";
import { GridSpinner } from "@/components/grid-spinner";
import { transformPathsInText } from "@/lib/utils";
import type { Message } from "@/lib/types/http";
import { ExpandableRow } from "./expandable-row";
import { useExpandState } from "./use-expand-state";
type ShellExecOutput = {
exit_code?: number;
stdout?: string;
stderr?: string;
};
type ShellExecPayload = {
command?: string;
work_dir?: string;
output?: ShellExecOutput;
};
type ToolExecuteMetadata = {
tool_call_id?: string;
status?: "pending" | "running" | "complete" | "error";
normalized?: { shell_exec?: ShellExecPayload };
};
type ToolExecuteMessageProps = {
comment: Message;
worktreePath?: string;
};
function ExecuteStatusIcon({
status,
exitCode,
}: {
status: string | undefined;
exitCode: number | undefined;
}) {
if (status === "complete") {
return exitCode === 0 ? (
) : (
);
}
if (status === "error") return ;
if (status === "running") return ;
return null;
}
type ExecuteOutputProps = {
displayCommand: string;
displayWorkDir: string | null;
workDir: string | undefined;
output: ShellExecOutput | undefined;
};
function ExecuteOutputContent({
displayCommand,
displayWorkDir,
workDir,
output,
}: ExecuteOutputProps) {
return (
{displayCommand}
{displayWorkDir && (
cwd:{" "}
{displayWorkDir}
)}
{output?.stdout && (
{output.stdout}
)}
{output?.stderr && (
{output.stderr}
)}
);
}
function isExecuteSuccess(
status: ToolExecuteMetadata["status"],
output: ShellExecOutput | undefined,
): boolean {
if (status !== "complete") return false;
return output?.exit_code === 0 || output?.exit_code === undefined;
}
function parseExecuteMetadata(comment: Message) {
const metadata = comment.metadata as ToolExecuteMetadata | undefined;
const status = metadata?.status;
const shellExec = metadata?.normalized?.shell_exec;
const output = shellExec?.output;
const workDir = shellExec?.work_dir;
const isSuccess = isExecuteSuccess(status, output);
return { status, output, workDir, isSuccess };
}
function CommandHeader({ displayCommand }: { displayCommand: string }) {
return (
{displayCommand}
);
}
export const ToolExecuteMessage = memo(function ToolExecuteMessage({
comment,
worktreePath,
}: ToolExecuteMessageProps) {
const { status, output, workDir, isSuccess } = parseExecuteMetadata(comment);
const autoExpanded = status === "running";
const { isExpanded, handleToggle } = useExpandState(status, autoExpanded);
const displayCommand = transformPathsInText(comment.content, worktreePath);
const displayWorkDir = workDir ? transformPathsInText(workDir, worktreePath) : null;
return (
}
header={
{!isSuccess && (
)}
}
hasExpandableContent
isExpanded={isExpanded}
onToggle={handleToggle}
>
);
});