registerFrontendTool

Angular function for registering a client-side frontend tool with CopilotKit, with an optional Angular renderer component, that runs in the browser when an agent calls it


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.

Import from the package root, @copilotkit/angular. There is no /v2 subpath. registerFrontendTool must run in an injection context that has provideCopilotKit in scope.

Signature

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

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

Parameters

Prop

Type

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.

src/app/todo.component.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:

src/app/weather.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.

src/app/weather-tool-view.component.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:

src/app/weather.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,
    });
  }
}

Related