Back to Copilotkit

registerRenderToolCall

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

1.61.17.7 KB
Original Source

Overview

registerRenderToolCall registers a renderer for a tool call. It does not run a handler. It tells CopilotKit which standalone Angular component to render whenever a tool call with a matching name appears in the conversation. Use it to visualize tool calls that execute somewhere other than the browser, for example a server-side tool or an agentic (long-running) tool, where you only want to show progress and results in the chat.

You call registerRenderToolCall inside an Angular injection context (a component or service constructor, or a field initializer). The renderer registers immediately, and CopilotKit removes it automatically when the owning component or service is destroyed.

This is the Angular equivalent of React's useRenderTool() / useRenderToolCall(). If you want to both run a browser handler and render UI, use registerFrontendTool with its component field instead.

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

Signature

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

function registerRenderToolCall<Args extends Record<string, unknown>>(
  renderToolCall: RenderToolCallConfig<Args>,
): void;

Parameters

<PropertyReference name="renderToolCall" type="RenderToolCallConfig<Args>" required> The renderer definition object. <PropertyReference name="name" type="string" required> The name of the tool call to render. Use the literal `"*"` to register a catch-all renderer that handles any tool call that has no more specific renderer registered. </PropertyReference> <PropertyReference name="args" type="StandardSchemaV1<unknown, Args>" required> A [Standard Schema](https://standardschema.dev) (for example a Zod object) that describes the tool call arguments. It is used to type the `args` exposed to your component. </PropertyReference> <PropertyReference name="component" type="Type<ToolRenderer<Args>>" required> The standalone Angular component class to render for this tool call. CopilotKit instantiates it and binds a `toolCall` signal input. </PropertyReference> <PropertyReference name="agentId" type="string"> Optional agent scope. When set, the renderer only applies to tool calls emitted by the agent with this id. When omitted, the renderer applies across agents. </PropertyReference> <PropertyReference name="passAgent" type="boolean" default="false"> When `true`, CopilotKit also binds an `agent` signal input on the component with the agent instance that emitted the tool call. Leave it `false` (the default) if your renderer does not need the agent. </PropertyReference> </PropertyReference>

Return Value

registerRenderToolCall returns void. It registers the renderer as a side effect and wires up automatic cleanup.

The renderer component

Your component implements the ToolRenderer<Args> interface. CopilotKit binds the inputs by name, so declare them with Angular's input():

ts
import { Signal } from "@angular/core";
import { AbstractAgent } from "@ag-ui/client";

interface ToolRenderer<Args extends Record<string, unknown>> {
  // Required: the current state of the tool call.
  toolCall: Signal<AngularToolCall<Args>>;
  // Bound only when the config sets passAgent: true.
  agent?: Signal<AbstractAgent | undefined>;
}

AngularToolCall

toolCall() returns a discriminated union keyed on status. The shape of args and result depends on the status:

ts
type AngularToolCall<Args extends Record<string, unknown>> =
  | {
      name?: string;
      args: Partial<Args>; // streaming, may be incomplete
      status: "in-progress";
      result: undefined;
    }
  | {
      name?: string;
      args: Args; // fully parsed
      status: "executing";
      result: undefined;
    }
  | {
      name?: string;
      args: Args; // fully parsed
      status: "complete";
      result: string; // the tool result, as a string
    };
  • in-progress means the arguments are still streaming in, so args is Partial<Args> and result is undefined.
  • executing means the arguments have fully arrived and the tool is running, so args is the complete Args and result is still undefined.
  • complete means the tool finished, so args is complete and result holds the string result.

Usage

A renderer component for a server-side tool

This renderer shows a spinner while the tool runs and then renders the result. It narrows on status so the template only reads result once it is available.

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

type SearchArgs = { query: string };

@Component({
  selector: "app-search-tool-view",
  standalone: true,
  template: `
    @let call = toolCall();
    @switch (call.status) {
      @case ("in-progress") {
        <div class="text-sm opacity-70">Preparing search...</div>
      }
      @case ("executing") {
        <div class="text-sm opacity-70">Searching for "{{ call.args.query }}"...</div>
      }
      @case ("complete") {
        <pre class="rounded border p-3 text-sm">{{ call.result }}</pre>
      }
    }
  `,
})
export class SearchToolViewComponent implements ToolRenderer<SearchArgs> {
  readonly toolCall = input.required<AngularToolCall<SearchArgs>>();
}

Register it for the tool name your agent emits:

ts
import { Component } from "@angular/core";
import { z } from "zod";
import { registerRenderToolCall } from "@copilotkit/angular";
import { SearchToolViewComponent } from "./search-tool-view.component";

@Component({
  selector: "app-chat",
  standalone: true,
  template: ``,
})
export class ChatComponent {
  constructor() {
    registerRenderToolCall({
      name: "searchDocuments",
      args: z.object({ query: z.string() }),
      component: SearchToolViewComponent,
    });
  }
}

Receiving the agent instance

Set passAgent: true to have CopilotKit bind the agent signal input as well. Declare it on the component as an optional input().

ts
import { Component, input } from "@angular/core";
import { AbstractAgent } from "@ag-ui/client";
import { AngularToolCall, ToolRenderer } from "@copilotkit/angular";

type HandoffArgs = { target: string };

@Component({
  selector: "app-handoff-tool-view",
  standalone: true,
  template: `<div>Handing off to {{ toolCall().args.target }}</div>`,
})
export class HandoffToolViewComponent implements ToolRenderer<HandoffArgs> {
  readonly toolCall = input.required<AngularToolCall<HandoffArgs>>();
  readonly agent = input<AbstractAgent | undefined>();
}
ts
registerRenderToolCall({
  name: "handoff",
  args: z.object({ target: z.string() }),
  component: HandoffToolViewComponent,
  passAgent: true,
});
<Cards> <Card title="registerFrontendTool" description="Register a client-side tool with an async handler and an optional renderer component." href="/reference/angular/functions/registerFrontendTool" /> <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>