Skip to content
Mizen UI
Docs

IntroductionInstallationCLIStylingFoundationsThemingCompositionIntegrationsAccessibilityFor agents

useCopyToClipboard

AccordionAspect RatioCardCollapsibleFrameGridGroupInlineScroll AreaSectionSeparatorSpacerStack

BlockquoteCitationCodeHeadingKbdLabelLinkTextVisually Hidden

ButtonButton GroupToggleToggle groupToolbar

CalendarCheckboxCheckbox groupComboboxDate pickerDate range pickerDate-Time InputFieldFieldsetFile UploadFormInputInput groupNumber fieldOTP FieldRadio groupSelectSliderSwitchTextareaTime Input

BreadcrumbMenubarNavigation MenuPaginationSegmented ControlStepperTabs

Alert dialogCommandContext MenuDialogDrawerMenuPopoverPreview CardSheetTooltip

AlertBadgeEmptyError StateMeterProgressSkeletonSpinnerStatus dotToast

Activity FeedAvatarAvatar GroupData TableListMetricOverflow ListProperty ListTableTokenTree

Area ChartBar ListCalendar HeatmapCategory ComparisonChart FrameChart LegendChart TooltipDistributionFunnelHeatmapMetric TrendPart-to-WholeProgress RingsRadar ChartRadial BarsSankey ChartScatter ChartScore GaugeTime Series

Diagram

AttachmentCarouselCode BlockLightboxMarkdownThumbnailTimestamp

Agent ThinkingApproval CardComposerConversationInline CitationMessageMessage ScrollerQuestion CardSourcesStreaming TextSuggestionTask ListTerminalTool Call

On this page

  • Preview
  • Usage
  • With the AI SDK
  • With TanStack AI
  • Examples
  • Failed call
  • Recommendations
  • Usage boundaries
  • API reference

Agentic

Tool Call

Shows an agent's tool call as one line that opens to its arguments and result, with Retry on errors and Approve or Deny when a call needs a person.

idle

Usage

import { ToolCallGroup } from "@mizen-labs/ui";

With the AI SDK

Each tool-<name> part becomes one call, keyed by its toolCallId. Map the part's state to a status, and answer approvals with addToolApprovalResponse.

import { useChat } from "@ai-sdk/react";
import { ToolCallGroup, type ToolCallState } from "@mizen-labs/ui";

function statusOf(part: { state: string; approval?: { approved?: boolean } }): ToolCallState {
	if (part.state === "approval-requested") return "approval";
	if (part.state === "output-available") return "complete";
	if (part.state === "output-error") return "error";
	if (part.state === "output-denied" || part.approval?.approved === false) {
		return "denied";
	}
	return "running"; // input-streaming, input-available, approval-responded
}

export function AssistantTools() {
	const { addToolApprovalResponse, messages } = useChat();
	const reply = messages.findLast((message) => message.role === "assistant");
	const calls = (reply?.parts ?? []).flatMap((part) => {
		if (!part.type.startsWith("tool-")) return [];
		return [
			{
				error: part.errorText,
				id: part.toolCallId,
				input: part.input,
				name: part.type.slice("tool-".length),
				onApprove: () =>
					addToolApprovalResponse({ approved: true, id: part.approval.id }),
				onDeny: () =>
					addToolApprovalResponse({ approved: false, id: part.approval.id }),
				output: part.output,
				status: statusOf(part),
			},
		];
	});
	return calls.length > 0 ? <ToolCallGroup calls={calls} /> : null;
}

With TanStack AI

tool-call parts carry id, name, input, output, and state. The docs do not show how an app answers an approval, so wire Approve and Deny to your own handler.

import { fetchServerSentEvents, useChat } from "@tanstack/ai-react";
import { ToolCallGroup, type ToolCallState } from "@mizen-labs/ui";

const status: Record<string, ToolCallState> = {
	"approval-requested": "approval",
	complete: "complete",
	error: "error",
};

export function AssistantTools() {
	const { messages } = useChat({
		connection: fetchServerSentEvents("/api/chat"),
	});
	const reply = messages.findLast((message) => message.role === "assistant");
	const calls = (reply?.parts ?? []).flatMap((part) =>
		part.type === "tool-call"
			? [
					{
						id: part.id,
						input: part.input,
						name: part.name,
						output: part.output,
						// awaiting-input, input-streaming, input-complete, approval-responded
						status: status[part.state] ?? "running",
						// Answer approvals with your app's handler for part.approval.
					},
				]
			: []
	);
	return calls.length > 0 ? <ToolCallGroup calls={calls} /> : null;
}

Examples

Failed call

An error opens the call with its message and Retry.

Recommendations

GuidancePractice
Do

Map each SDK tool part to one call with its toolCallId, and group a turn's calls with ToolCallGroup. Write the title in present tense while running and past tense when complete.

Usage boundaries

GuidanceBoundary
Scope

The application runs tools and stores approvals. Tool Call shows each call's state; Approve and Deny only report the choice.

Scope

Arguments and results render as given. Remove secrets before passing them in.

API reference

Public props from @mizen-labs/ui. Use exported prop types for the complete TypeScript contract.

PropTypeDefaultDescription
status"running" | "approval" | "complete" | "error" | "denied"—running while input streams or the tool works, approval while it waits for a person, then complete, error, or denied. Errors and approvals open on their own.
namestring—The tool's id, shown in monospace.
titlestring—What the call does. Present tense while running, past tense once complete.
input / outputunknown—Arguments and result, shown in code blocks with Copy. Text shows as is; anything else as JSON.
error / onRetry / onApprove / onDenystring / () => void—An error shows its message with Retry; an approval shows Approve and Deny. Each button appears only with its handler.
durationnumberMeasuredSeconds the call took. Measured while the component watches it run; pass it for a call from history.
ToolCallGroup callsreadonly ToolCallGroupCall[]—ToolCallGroup takes calls with an id each. It stays open under Using tools… while they work, then folds into Used 3 tools · 1 failed · 2.7s, unless the reader opened or closed it.
PreviousTerminal
Arguments
{
  "timeoutMs": 8000,
  "url": "https://web.dev/blog/cq-stable"
}

The request timed out after 8s. web.dev did not respond.