showcase/shell-docs/src/content/reference/vue/hooks/useFrontendTool.mdx
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.
<Callout type="info"> 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`](/reference/vue/components/CopilotKitProvider). </Callout>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;
<PropertyReference name="handler" type="(args: T, context: { toolCall, agent, signal? }) => Promise<unknown>"
An async function that executes when the agent calls the tool. Receives the
validated, typed arguments and a context object: `toolCall` (the raw tool
call metadata), `agent` (the agent instance that invoked the tool), and
`signal` (an `AbortSignal` that is aborted when the user stops the agent).
Long-running handlers can check `signal.aborted` or pass `signal` to
`fetch` to cancel cooperatively. The resolved value is surfaced back to the
agent as the tool result.
<PropertyReference name="render" type="Component<RenderProps<T>> | ((props: RenderProps<T>) => VNodeChild)"
An optional Vue component or render function used to visualize tool
execution in the chat. It receives a props object: `name` (the tool name),
`toolCallId` (the unique id of this tool call), `args` (the arguments,
partial while streaming during `InProgress`, complete during `Executing`
and `Complete`), `status` (one of `ToolCallStatus.InProgress`,
`ToolCallStatus.Executing`, or `ToolCallStatus.Complete`), and `result`
(the string result, available only when status is `Complete`).
watch uses { immediate: true }).name already exists for the same agentId, 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.render is provided, it is registered as a tool call renderer. The renderer is registered even when parameters is omitted, so parameter-free tools (for example confirm dialogs) can still render UI in the chat.tool.name, tool.available, or any deps watch source changes. Pass changing reactive values through deps.render registered alongside the tool is intentionally left in place so earlier chat history can still render it.void.<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>
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>
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>