showcase/shell-docs/src/content/docs/frontends/angular/guides/frontend-tools-generative-ui.mdx
Frontend tools let an agent call code inside the user's browser. Add a renderer to the same registration when the tool should show progress or a result in chat.
Call registerFrontendTool from an Angular injection context. The live
Showcase example builds a typed tool config around a writable signal:
Register that config from a component or service with
registerFrontendTool(createBackgroundTool(background)). The registration is
removed when that injector is destroyed.
The schema advertises the input shape and supplies TypeScript inference. Runtime arguments arrive as parsed JSON; validate inside the handler when the action needs a hard trust boundary. The handler receives the calling agent, the raw tool call, and an optional abort signal as its second argument.
A renderer is a standalone component with a required toolCall signal input.
Its status moves through "in-progress", "executing", and "complete".
import { Component, input } from "@angular/core";
import {
type AngularToolCall,
type ToolRenderer,
} from "@copilotkit/angular";
type WeatherArgs = { city: string };
@Component({
selector: "app-weather-card",
template: `
@let call = toolCall();
@if (call.status === "complete") {
<article>
<strong>{{ call.args.city }}</strong>
<p>{{ call.result }}</p>
</article>
} @else {
<p>Loading weather for {{ call.args.city ?? "…" }}</p>
}
`,
})
export class WeatherCardComponent implements ToolRenderer<WeatherArgs> {
readonly toolCall = input.required<AngularToolCall<WeatherArgs>>();
}
Pass the class as component when the tool runs in the browser:
registerFrontendTool({
name: "getWeather",
description: "Get the current weather for a city",
parameters: z.object({ city: z.string() }),
component: WeatherCardComponent,
handler: async ({ city }, { signal }) => {
const response = await fetch(`/api/weather?city=${encodeURIComponent(city)}`, {
signal,
});
return response.text();
},
});
Use registerRenderToolCall instead when the tool runs on the server and the
browser only renders its call:
registerRenderToolCall({
name: "getWeather",
args: z.object({ city: z.string() }),
component: WeatherCardComponent,
});
| Path | Best fit | Angular setup |
|---|---|---|
| Your components | Known data shapes and application actions | registerFrontendTool or registerRenderToolCall with a component |
| A2UI | A server emits A2UI operations or snapshots | Runtime capability turns on the built-in renderer; an optional a2ui config supplies a catalog or theme |
| Open Generative UI | The agent produces streamed HTML, CSS, and script expressions | Set openGenerativeUI in provideCopilotKit |
| MCP Apps | An MCP server returns an interactive app resource | Add provideMCPApps() from @copilotkit/angular/mcp-apps |
An openGenerativeUI object opts the frontend into the built-in sandboxed
renderer. Expose narrow host functions when generated UI must ask the
application to act.
import { ApplicationConfig } from "@angular/core";
import {
provideCopilotKit,
type SandboxFunction,
} from "@copilotkit/angular";
import { z } from "zod";
const setDashboardFilter: SandboxFunction<{ filter: string }> = {
name: "setDashboardFilter",
description: "Set the active dashboard filter",
parameters: z.object({ filter: z.string() }),
handler: async ({ filter }) => {
sessionStorage.setItem("dashboard-filter", filter);
return { applied: filter };
},
};
export const appConfig: ApplicationConfig = {
providers: [
provideCopilotKit({
runtimeUrl: "/api/copilotkit",
openGenerativeUI: {
sandboxFunctions: [setDashboardFilter],
},
}),
],
};
Generated code runs in a sandboxed iframe without same-origin access. It calls
only the host functions you list in sandboxFunctions.
import { ApplicationConfig } from "@angular/core";
import { provideCopilotKit } from "@copilotkit/angular";
import { provideMCPApps } from "@copilotkit/angular/mcp-apps";
export const appConfig: ApplicationConfig = {
providers: [
provideCopilotKit({ runtimeUrl: "/api/copilotkit" }),
provideMCPApps(),
],
};
MCP resource and tool requests travel through the selected AG-UI agent. The browser provider does not take a server URL.