Back to Copilotkit

registerFrontendTool

showcase/shell-docs/src/content/reference/angular/functions/registerFrontendTool.mdx

1.61.110.2 KB
Original Source

Overview

registerFrontendTool registers a client-side tool with CopilotKit. When the agent decides to call the tool, the provided handler function executes in the browser, and the value it resolves to is surfaced back to the agent as the tool result. Optionally, you can supply a component (a standalone Angular component) to render custom UI in the chat that shows the tool call's progress and result.

You call registerFrontendTool inside an Angular injection context (a component or service constructor, or a field initializer). The tool registers immediately, and CopilotKit removes it automatically when the owning component or service is destroyed. Parameter schemas are defined with a Standard Schema such as a Zod object, which is used for both validation and type inference of the handler's arguments.

This is the Angular equivalent of React's useFrontendTool(). The handler runs inside the same injector that registered the tool, so it can call inject() to reach your other Angular services.

<Callout type="info"> Import from the package root, `@copilotkit/angular`. There is no `/v2` subpath. `registerFrontendTool` must run in an injection context that has [`provideCopilotKit`](/reference/angular/functions/provideCopilotKit) in scope. </Callout>

Signature

ts
import { registerFrontendTool } from "@copilotkit/angular";

function registerFrontendTool<Args extends Record<string, unknown>>(
  frontendTool: FrontendToolConfig<Args>,
): void;

Parameters

<PropertyReference name="frontendTool" type="FrontendToolConfig<Args>" required> The tool definition object. <PropertyReference name="name" type="string" required> A unique name for the tool. The agent references this name when it decides to call the tool. Registering another tool with the same name (for the same `agentId`) adds a second registration, so use distinct names per tool. </PropertyReference> <PropertyReference name="description" type="string" required> A natural-language description that tells the agent what the tool does and when to use it. </PropertyReference> <PropertyReference name="parameters" type="StandardSchemaV1<unknown, Args>" required> A [Standard Schema](https://standardschema.dev) (for example a Zod object) that defines the tool's input parameters. The schema drives both validation and the type inference of the `handler` arguments. For a tool that takes no parameters, pass an empty object schema such as `z.object({})`. </PropertyReference>

<PropertyReference name="handler" type="(args: Args, context: FrontendToolHandlerContext) => Promise<unknown>" required

An async function that runs when the agent calls the tool. It receives the
validated, typed `args` and a `context` object, and runs inside the
injection context that registered the tool, so you can call `inject()`
inside it. The resolved value is returned to the agent as the tool result.

<PropertyReference name="context.toolCall" type="ToolCall">
  The raw AG-UI tool call metadata, including the tool call `id`.
</PropertyReference>

<PropertyReference name="context.agent" type="AbstractAgent">
  The agent instance that invoked the tool.
</PropertyReference>

<PropertyReference name="context.signal" type="AbortSignal">
  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.
</PropertyReference>
</PropertyReference> <PropertyReference name="component" type="Type<ToolRenderer<Args>>"> An optional standalone Angular component class used to visualize the tool call in the chat. CopilotKit instantiates it and binds a `toolCall` signal input that exposes the streaming `args`, the `status`, and the `result`. See [`registerRenderToolCall`](/reference/angular/functions/registerRenderToolCall) for the full `ToolRenderer` and `AngularToolCall` shapes. </PropertyReference> <PropertyReference name="followUp" type="boolean"> Whether the agent should automatically continue the run after the tool result is returned. </PropertyReference> <PropertyReference name="agentId" type="string"> Optional agent scope. When set, the tool is only offered to the agent with this id. Cleanup on destroy is scoped to the same `agentId`. </PropertyReference> </PropertyReference>

Return Value

registerFrontendTool returns void. It performs the registration as a side effect and wires up automatic cleanup.

Behavior

  • Immediate registration. The tool is added to CopilotKit as soon as the function runs, so call it during construction or field initialization, not inside an event handler.
  • Runs in the injection context. The handler is invoked with runInInjectionContext using the injector that registered the tool, so inject() works inside it.
  • Automatic cleanup. CopilotKit removes the tool when the owning component or service is destroyed, using the same name and agentId you registered with. There is no manual unregister step.
  • Optional renderer. When you pass a component, it is registered alongside the tool so the chat can render the tool call's progress and result.

Usage

Basic tool with a Zod schema and async handler

Register the tool in the component constructor. The handler reads and writes a local signal, then returns a string result for the agent.

ts
import { Component, signal } from "@angular/core";
import { z } from "zod";
import { registerFrontendTool } from "@copilotkit/angular";

@Component({
  selector: "app-todo",
  standalone: true,
  template: `
    <ul>
      @for (todo of todos(); track todo) {
        <li>{{ todo }}</li>
      }
    </ul>
  `,
})
export class TodoComponent {
  readonly todos = signal<string[]>([]);

  constructor() {
    registerFrontendTool({
      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 }) => {
        this.todos.update((current) => [...current, text]);
        return `Added "${text}" with ${priority} priority`;
      },
    });
  }
}

Because the handler runs in the injection context, you can call inject() for a service instead of capturing it from the component:

ts
import { Component, inject } from "@angular/core";
import { z } from "zod";
import { registerFrontendTool } from "@copilotkit/angular";
import { WeatherService } from "./weather.service";

@Component({
  selector: "app-weather",
  standalone: true,
  template: ``,
})
export class WeatherComponent {
  constructor() {
    registerFrontendTool({
      name: "getWeather",
      description: "Fetch 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 weather = inject(WeatherService);
        const result = await weather.fetch(city, units, { signal });
        return JSON.stringify(result);
      },
    });
  }
}

Tool with a custom Angular renderer component

Pass a standalone component as component to visualize the tool call in chat. The renderer implements ToolRenderer<Args>, so it exposes a toolCall signal input that carries the status, the (possibly partial) args, and the result.

ts
import { Component, input } from "@angular/core";
import { AngularToolCall, ToolRenderer } from "@copilotkit/angular";

type WeatherArgs = { city: string; units: "celsius" | "fahrenheit" };

@Component({
  selector: "app-weather-tool-view",
  standalone: true,
  template: `
    @let call = toolCall();
    @if (call.status === "in-progress") {
      <div class="animate-pulse">Fetching weather for {{ call.args.city }}...</div>
    } @else if (call.status === "complete") {
      @let data = parse(call.result);
      <div class="rounded border p-4">
        <h3>{{ data.city }}</h3>
        <p>{{ data.temperature }} {{ data.units }}</p>
        <p>{{ data.conditions }}</p>
      </div>
    }
  `,
})
export class WeatherToolViewComponent implements ToolRenderer<WeatherArgs> {
  readonly toolCall = input.required<AngularToolCall<WeatherArgs>>();

  protected parse(result: string) {
    return JSON.parse(result);
  }
}

Register the tool with that component:

ts
import { Component, inject } from "@angular/core";
import { z } from "zod";
import { registerFrontendTool } from "@copilotkit/angular";
import { WeatherService } from "./weather.service";
import { WeatherToolViewComponent } from "./weather-tool-view.component";

@Component({
  selector: "app-weather",
  standalone: true,
  template: ``,
})
export class WeatherComponent {
  constructor() {
    registerFrontendTool({
      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 result = await inject(WeatherService).fetch(city, units, { signal });
        return JSON.stringify(result);
      },
      component: WeatherToolViewComponent,
    });
  }
}
<Cards> <Card title="registerRenderToolCall" description="Register a renderer for a tool call (including server-side and agentic tools) with full access to status and results." href="/reference/angular/functions/registerRenderToolCall" /> <Card title="registerHumanInTheLoop" description="Register a tool that pauses the agent and waits for the user to respond from a rendered component." href="/reference/angular/functions/registerHumanInTheLoop" /> <Card title="CopilotKit service" description="The central service that holds agents, tools, and the runtime connection." href="/reference/angular/services/CopilotKit" /> </Cards>