AI Elements
Tool
Displays a tool call the agent makes, from input to output.
Tool is a tool call the agent makes, such as running a command or reading a file. The line follows the call from input to output, including errors and calls that wait for approval.
Tool is built with UiTrace with a UiTraceCollapsible for each call that has output.
Installation
Tool is built with the Trace and Code Block components.
Usage
<template>
<UiTrace>
<UiTraceCollapsible>
<UiTraceCollapsibleTrigger>
<UiTraceLine>
<UiTraceIcon>
<UiIcon name="lucide:terminal" />
</UiTraceIcon>
<UiTraceTitle>Ran git status --short</UiTraceTitle>
</UiTraceLine>
</UiTraceCollapsibleTrigger>
<UiTraceCollapsibleContent>
<UiCodeBlock size="sm">
<UiCodeBlockContent wrap class="max-h-64">
{{ output }}
</UiCodeBlockContent>
</UiCodeBlock>
</UiTraceCollapsibleContent>
</UiTraceCollapsible>
</UiTrace>
</template>
States
Trace does not read tool parts. Map each part's state to a status and a title.
| Tool part state | status | Title |
|---|---|---|
input-streaming, input-available | "running" while the chat streams | Running … |
input-streaming, input-available after the chat stops | "error" | Failed to run … |
approval-requested | - | Not a line. An UiItem outside UiTrace with Approve and Deny buttons. |
approval-responded | "running" if approved, none if not | Running … if approved, Denied … if not |
output-available | none | Ran … |
output-error | "error" | Failed to run … |
output-denied | none | Denied … |
A tool is a plain UiTraceLine while it runs, and a UiTraceCollapsible once its output arrives.
<template>
<UiTraceLine v-if="denied">
<UiTraceIcon>
<UiIcon name="lucide:terminal" />
</UiTraceIcon>
<UiTraceTitle>Denied {{ command }}</UiTraceTitle>
</UiTraceLine>
<UiTraceCollapsible
v-else-if="part.state === 'output-available' || part.state === 'output-error'"
>
<UiTraceCollapsibleTrigger>
<UiTraceLine :status="failed ? 'error' : undefined">
<UiTraceIcon>
<UiIcon name="lucide:terminal" />
</UiTraceIcon>
<UiTraceTitle>{{ failed ? "Failed to run" : "Ran" }} {{ command }}</UiTraceTitle>
</UiTraceLine>
</UiTraceCollapsibleTrigger>
<UiTraceCollapsibleContent>
<UiCodeBlock size="sm">
<UiCodeBlockContent wrap class="max-h-64">
{{ part.state === "output-error" ? part.errorText : part.output.stdout }}
</UiCodeBlockContent>
</UiCodeBlock>
</UiTraceCollapsibleContent>
</UiTraceCollapsible>
<UiTraceLine v-else :status="streaming ? 'running' : 'error'">
<UiTraceIcon>
<UiIcon name="lucide:terminal" />
</UiTraceIcon>
<UiTraceTitle>{{ streaming ? "Running" : "Failed to run" }} {{ command }}</UiTraceTitle>
</UiTraceLine>
</template>
<script lang="ts" setup>
const props = defineProps<{
part: ToolPart;
streaming: boolean;
}>();
const command = computed(() => props.part.input?.command);
const denied = computed(
() =>
props.part.state === "output-denied" ||
(props.part.state === "approval-responded" && !props.part.approval.approved)
);
const failed = computed(() => props.part.state === "output-error");
</script>
<template>
<UiTrace>
<ToolLine
v-for="part in message.parts.filter((part) => part.type === 'tool-runCommand')"
:key="part.toolCallId"
:part="part"
:streaming="streaming"
/>
</UiTrace>
</template>
A tool that runs on the client stays input-available after the chat stops until you call addToolOutput. Treat it as waiting, not failed.
Output in a Sheet
For long output, wrap the UiTraceLine in a UiSheetTrigger and show the output in the sheet.
See Trace.
Approval
A pending approval is a question for the person, not a step the agent took, so it sits outside UiTrace as an UiItem. Once the person answers, the tool goes back to being a line. See Confirmation.
<template>
<UiItem v-if="part.state === 'approval-requested'" variant="outline" size="sm">
<UiItemMedia variant="icon">
<TerminalIcon />
</UiItemMedia>
<UiItemContent>
<UiItemTitle>Run {{ part.input.command }}?</UiItemTitle>
</UiItemContent>
<UiItemActions>
<UiButton
size="sm"
variant="outline"
@click="chat.addToolApprovalResponse({ id: part.approval.id, approved: false })"
>
Deny
</UiButton>
<UiButton
size="sm"
@click="chat.addToolApprovalResponse({ id: part.approval.id, approved: true })"
>
Approve
</UiButton>
</UiItemActions>
</UiItem>
</template>
Pass sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses to the Chat so the chat continues once every approval has an answer.
API Reference
See Trace.