useFrontendTool
Vue composable for registering a client-side tool with an optional Vue renderer
Overview
useFrontendTool registers a client-side tool with CopilotKit at the current component scope. When the agent decides to call the tool, the provided handler function executes in the browser. Optionally, you can supply a render (a Vue component or render function) to display custom UI in the chat showing the tool's execution progress and result.
The composable manages the full registration lifecycle: it registers the tool (and its optional renderer) immediately, re-runs the registration when reactive dependencies change, and removes the tool automatically when the owning scope is disposed. Parameter schemas are defined with a Standard Schema such as a Zod object, which is used for both validation and type inference.
Import from the /v2 subpath: @copilotkit/vue/v2. Like all CopilotKit
composables, useFrontendTool must be called from within a component that is a
descendant of CopilotKitProvider.
Signature
import type { WatchSource } from "vue";
import { useFrontendTool } from "@copilotkit/vue/v2";
function useFrontendTool<T extends Record<string, unknown>>(
tool: VueFrontendTool<T>,
deps?: WatchSource<unknown>[],
): void;Parameters
Prop
Type
Prop
Type
Behavior
- Immediate registration. The tool is registered as soon as the composable runs (the underlying
watchuses{ immediate: true }). - Duplicate handling. If a tool with the same
namealready exists for the sameagentId, the previous registration is removed, the new one is added, and an override warning is logged. Only one tool per name (per agent) is active at a time. - Renderer registration. When
renderis provided, it is registered as a tool call renderer. The renderer is registered even whenparametersis omitted, so parameter-free tools (for example confirm dialogs) can still render UI in the chat. - Reactivity. The registration re-runs when
tool.name,tool.available, or anydepswatch source changes. Pass changing reactive values throughdeps. - Automatic cleanup. The tool is removed when the watch cleans up, including when the owning component scope is disposed. A
renderregistered alongside the tool is intentionally left in place so earlier chat history can still render it. - No return value. The composable returns
void.
Usage
Basic tool with a Zod schema and async handler
<script setup lang="ts">
import { ref } from "vue";
import { z } from "zod";
import { useFrontendTool } from "@copilotkit/vue/v2";
const todos = ref<string[]>([]);
useFrontendTool({
name: "addTodo",
description: "Add a new item to the user's todo list",
parameters: z.object({
text: z.string().describe("The todo item text"),
priority: z.enum(["low", "medium", "high"]).describe("Priority level"),
}),
handler: async ({ text, priority }) => {
todos.value.push(text);
return `Added "${text}" with ${priority} priority`;
},
});
</script>
<template>
<ul>
<li v-for="(todo, i) in todos" :key="i">{{ todo }}</li>
</ul>
</template>Tool with a custom Vue renderer
Provide a render component to visualize the tool call in chat. The component receives args, status, and result as props.
<!-- WeatherToolView.vue -->
<script setup lang="ts">
import { ToolCallStatus } from "@copilotkit/core";
defineProps<{
name: string;
toolCallId: string;
args: { city?: string; units?: "celsius" | "fahrenheit" };
status: ToolCallStatus;
result?: string;
}>();
</script>
<template>
<div v-if="status === ToolCallStatus.InProgress" class="animate-pulse">
Fetching weather for {{ args.city }}...
</div>
<div v-else-if="status === ToolCallStatus.Complete && result" class="rounded border p-4">
<h3>{{ JSON.parse(result).city }}</h3>
<p>{{ JSON.parse(result).temperature }}° {{ JSON.parse(result).units }}</p>
<p>{{ JSON.parse(result).conditions }}</p>
</div>
</template><!-- App.vue -->
<script setup lang="ts">
import { z } from "zod";
import { useFrontendTool } from "@copilotkit/vue/v2";
import WeatherToolView from "./WeatherToolView.vue";
useFrontendTool({
name: "getWeather",
description: "Fetch and display weather information for a city",
parameters: z.object({
city: z.string().describe("City name"),
units: z.enum(["celsius", "fahrenheit"]).default("celsius"),
}),
handler: async ({ city, units }, { signal }) => {
const response = await fetch(`/api/weather?city=${city}&units=${units}`, {
signal,
});
return JSON.stringify(await response.json());
},
render: WeatherToolView,
});
</script>Conditionally available tool
Expose available as a getter that reads a reactive value. The registration watch re-reads available when that value changes, so the tool is shown or hidden automatically. (Passing the value through deps as well is harmless but not required.)
<script setup lang="ts">
import { computed, toRef } from "vue";
import { z } from "zod";
import { useFrontendTool } from "@copilotkit/vue/v2";
const props = defineProps<{ isAdmin: boolean }>();
const isAdmin = toRef(props, "isAdmin");
useFrontendTool(
{
name: "deleteUser",
description: "Delete a user account by ID (admin only)",
parameters: z.object({
userId: z.string().describe("The ID of the user to delete"),
}),
handler: async ({ userId }) => {
await fetch(`/api/users/${userId}`, { method: "DELETE" });
return `User ${userId} deleted`;
},
// Expose `available` as a getter so the registration watch re-reads it
// whenever `isAdmin` changes.
get available() {
return isAdmin.value;
},
},
[isAdmin],
);
</script>Related
useComponent
Convenience composable that renders a Vue component from tool args. Built on useFrontendTool.
useRenderTool
Register a renderer for a tool call with full access to status (in progress, executing, complete) and results.
CopilotKitProvider
The provider that must wrap any component calling useFrontendTool.