Tool

A collapsible component for displaying tool invocation details in AI chatbot interfaces.

The Tool component displays a collapsible interface for showing/hiding tool details. It is designed to take the ToolUIPart type from the AI SDK and display it in a collapsible interface.

Install using CLI

AI Elements Vue
shadcn-vue CLI
npx ai-elements-vue@latest add tool

Install Manually

Copy and paste the following code in the same folder.

Tool.vue
ToolStatusBadge.vue
ToolHeader.vue
ToolContent.vue
ToolInput.vue
ToolOutput.vue
index.ts
<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import { Collapsible } from '@repo/shadcn-vue/components/ui/collapsible'
import { cn } from '@repo/shadcn-vue/lib/utils'

type ToolProps = InstanceType<typeof Collapsible>['$props']

interface Props extends /* @vue-ignore */ ToolProps {
  class?: HTMLAttributes['class']
}

const props = defineProps<Props>()
</script>

<template>
  <Collapsible
    :class="cn('group not-prose mb-4 w-full rounded-md border', props.class)"
    v-bind="$attrs"
  >
    <slot />
  </Collapsible>
</template>

Usage with AI SDK

Build a simple stateful weather app that renders the last message in a tool using useChat.

Add the following component to your frontend:

pages/index.vue
<script setup lang="ts">
import type { ToolUIPart } from 'ai'
import { useChat } from '@ai-sdk/vue'
import { DefaultChatTransport } from 'ai'
import { computed, h } from 'vue'
import { MessageResponse } from '@/components/ai-elements/message'
import {
  Tool,
  ToolContent,
  ToolHeader,
  ToolInput,
  ToolOutput,
} from '@/components/ai-elements/tool'
import { Button } from '@/components/ui/button'

interface WeatherToolInput {
  location: string
  units: 'celsius' | 'fahrenheit'
}

interface WeatherToolOutput {
  location: string
  temperature: string
  conditions: string
  humidity: string
  windSpeed: string
  lastUpdated: string
}

type WeatherToolUIPart = ToolUIPart<{
  fetch_weather_data: {
    input: WeatherToolInput
    output: WeatherToolOutput
  }
}>

const { messages, sendMessage, status } = useChat({
  transport: new DefaultChatTransport({
    api: '/api/weather',
  }),
})

function formatWeatherResult(result: WeatherToolOutput): string {
  if (!result)
    return ''
  return `**Weather for ${result.location}**

**Temperature:** ${result.temperature}
**Conditions:** ${result.conditions}
**Humidity:** ${result.humidity}
**Wind Speed:** ${result.windSpeed}

*Last updated: ${result.lastUpdated}*`
}

function handleWeatherClick() {
  sendMessage({ text: 'Get weather data for San Francisco in fahrenheit' })
}

const latestMessage = computed(() => {
  if (!messages.value || messages.value.length === 0) {
    return undefined
  }
  return messages.value[messages.value.length - 1]
})

const weatherTool = computed(() => {
  return latestMessage.value?.parts?.find(
    part => part.type === 'tool-fetch_weather_data'
  ) as WeatherToolUIPart | undefined
})

const weatherOutputVNode = computed(() => {
  if (!weatherTool.value?.output) {
    return null
  }
  const markdown = formatWeatherResult(weatherTool.value.output)
  return h(MessageResponse, { content: markdown })
})
</script>

<template>
  <div
    class="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]"
  >
    <div class="flex flex-col h-full">
      <div class="space-y-4">
        <Button
          :disabled="status !== 'ready'"
          @click="handleWeatherClick"
        >
          Get Weather for San Francisco
        </Button>

        <Tool v-if="weatherTool" :default-open="true">
          <ToolHeader
            type="tool-fetch_weather_data"
            :state="weatherTool.state"
          />
          <ToolContent>
            <ToolInput :input="weatherTool.input" />
            <ToolOutput
              :output="weatherOutputVNode"
              :error-text="weatherTool.errorText"
            />
          </ToolContent>
        </Tool>
      </div>
    </div>
  </div>
</template>

Add the following route to your backend:

server/api/agent.ts
import { convertToModelMessages, streamText, UIMessage } from 'ai'
import { defineEventHandler, readBody } from 'h3'
import { z } from 'zod'

// Allow streaming responses up to 30 seconds
export const maxDuration = 30

export default defineEventHandler(async (event) => {
  const body = await readBody(event) as { messages: UIMessage[] }
  const { messages } = body

  const result = streamText({
    model: 'openai/gpt-4o',
    messages: convertToModelMessages(messages),
    tools: {
      fetch_weather_data: {
        description: 'Fetch weather information for a specific location',
        parameters: z.object({
          location: z.string().describe('The city or location to get weather for'),
          units: z.enum(['celsius', 'fahrenheit']).default('celsius').describe('Temperature units'),
        }),
        inputSchema: z.object({
          location: z.string(),
          units: z.enum(['celsius', 'fahrenheit']).default('celsius'),
        }),
        execute: async ({ location, units }) => {
          await new Promise(resolve => setTimeout(resolve, 1500))
          const temp = units === 'celsius'
            ? Math.floor(Math.random() * 35) + 5
            : Math.floor(Math.random() * 63) + 41

          return {
            location,
            temperature: `${temp}°${units === 'celsius' ? 'C' : 'F'}`,
            conditions: 'Sunny',
            humidity: '12%',
            windSpeed: `35 ${units === 'celsius' ? 'km/h' : 'mph'}`,
            lastUpdated: new Date().toLocaleString(),
          }
        },
      },
    },
  })

  return result.toUIMessageStreamResponse()
})

Features

  • Collapsible interface for showing/hiding tool details
  • Visual status indicators with icons and badges
  • Support for multiple tool execution states (pending, running, completed, error)
  • Formatted parameter display with JSON syntax highlighting
  • Result and error handling with appropriate styling
  • Composable structure for flexible layouts
  • Accessible keyboard navigation and screen reader support
  • Consistent styling that matches your design system
  • Auto-opens completed tools by default for better UX

Examples

Input Streaming (Pending)

Shows a tool in its initial state while parameters are being processed.

Input Available (Running)

Shows a tool that's actively executing with its parameters.

Output Available (Completed)

Shows a completed tool with successful results. Opens by default to show the results. In this instance, the output is a JSON object, so we can use the CodeBlock component to display it.

Output Error

Shows a tool that encountered an error during execution. Opens by default to display the error.

Props

<Tool/>

classstring
Additional CSS classes to apply to the component.
...propsCollapsible props
Any other props are spread to the root Collapsible component.

<ToolHeader/>

titlestring
Custom title to display instead of the derived tool name.
typerequiredToolUIPart['type'] | DynamicToolUIPart['type']
The type/name of the tool.
staterequiredToolUIPart['state'] | DynamicToolUIPart['state']
The current state of the tool (input-streaming, input-available, output-available, or output-error).
toolNamestring
Required when type is "dynamic-tool" to specify the tool name.
classstring
Additional CSS classes to apply to the header.
...propsCollapsibleTrigger props
Any other props are spread to the CollapsibleTrigger.

<ToolContent/>

classstring
Additional CSS classes to apply to the component.
...propsCollapsibleContent props
Any other props are spread to the CollapsibleContent.

<ToolInput/>

inputToolPart['input']
The input parameters passed to the tool, displayed as formatted JSON.
classstring
Additional CSS classes to apply to the component.
...propsHTMLAttributes
Any other props are spread to the underlying div.

<ToolOutput/>

outputToolPart['output']
The output/result of the tool execution. Can be a VNode, object, or string.
errorTextToolPart['errorText']
An error message if the tool execution failed.
classstring
Additional CSS classes to apply to the component.
...propsHTMLAttributes
Any other props are spread to the underlying div.

Type Exports

ToolPart

Union type representing both static and dynamic tool UI parts.

type ToolPart = ToolUIPart | DynamicToolUIPart

ToolPart['state']

All possible tool states including approval states:

  • input-streaming - "Pending"
  • input-available - "Running"
  • approval-requested - "Awaiting Approval"
  • approval-responded - "Responded"
  • output-available - "Completed"
  • output-error - "Error"
  • output-denied - "Denied"