registerRenderToolCall

Angular function for registering a renderer component for a tool call with CopilotKit, with access to streaming arguments, status, and result


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.

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

Signature

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

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

Parameters

Prop

Type

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():

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:

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.

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

src/app/chat.component.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().

src/app/handoff-tool-view.component.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>();
}
src/app/chat.component.ts
registerRenderToolCall({
  name: "handoff",
  args: z.object({ target: z.string() }),
  component: HandoffToolViewComponent,
  passAgent: true,
});

Related