Back to Copilotkit

registerFrontendTool

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

1.63.210.5 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. When the owning injector is destroyed, CopilotKit removes tool and renderer registrations with the same name and optional agent id. Parameter schemas use a Standard Schema, such as a Zod object, for the advertised tool schema and TypeScript inference. Runtime arguments are JSON-parsed but are not validated against that schema.

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. Core skips a second tool with the same name and `agentId`, although Angular still appends its frontend-tool render config. Avoid duplicate keys. </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 advertised tool input schema and drives type inference for the `handler` arguments. Runtime arguments are JSON-parsed and cast to `Args`; this helper does not validate them against the schema. 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
parsed `args` cast to the inferred type 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 | undefined">
  An optional `AbortSignal` for the active run. When present, it is aborted
  when the user stops the agent. Check that it exists before reading
  `signal.aborted` or pass it as an optional `fetch` signal.
</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 name-and-agent-id cleanup for the owning injector.

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.
  • Cleanup by key. When the owning component or service is destroyed, CopilotKit removes frontend tools, human-in-the-loop tools, and tool-call renderers with the same name and optional agentId. Registrations sharing that key are removed together.
  • 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>