First-party Angular bindings for CopilotKit core and AG-UI agents. The package ships standalone chat, popup, and sidebar components as well as signal-based headless APIs, tool and activity renderers, threads, memories, interrupts, attachments, A2UI, Open Generative UI, and opt-in MCP Apps support.
Want to contribute? Read the Angular contribution guide.
# npm
npm install @copilotkit/angularPeer dependencies you provide in your app:
@angular/coreand@angular/common(Angular 22)@angular/cdk(match your Angular major)rxjs7.8 or newer
The exact versions exercised by the packed-consumer release matrix are stored
in package.json under copilotkit.angularSupport. The library is compiled at
the Angular 22 baseline and installed with strict peer checking against that
supported major.
Configure runtime and tools in your app config:
import { ApplicationConfig } from "@angular/core";
import { provideCopilotKit } from "@copilotkit/angular";
export const appConfig: ApplicationConfig = {
providers: [
provideCopilotKit({
runtimeUrl: "http://localhost:3001/api/copilotkit",
headers: { Authorization: "Bearer ..." },
properties: { app: "demo" },
}),
],
};import { Component, inject, signal } from "@angular/core";
import { Message } from "@ag-ui/client";
import { CopilotKit, injectAgentStore } from "@copilotkit/angular";
import { randomUUID } from "@copilotkit/shared";
@Component({
template: `
@for (let message of messages(); track message.id) {
<div>
<em>{{ message.role }}</em>
<p>{{ message.content }}</p>
</div>
}
<input
[value]="input()"
(input)="input.set($any($event.target).value)"
(keyup.enter)="send()"
/>
<button (click)="send()" [disabled]="store().isRunning()">Send</button>
`,
})
export class HeadlessChatComponent {
readonly copilotKit = inject(CopilotKit);
readonly store = injectAgentStore("default");
readonly messages = this.store().messages;
readonly input = signal("");
async send() {
const content = this.input().trim();
if (!content) return;
const agent = this.store().agent;
agent.addMessage({
id: randomUUID(),
role: "user",
content,
});
this.input.set("");
await this.copilotKit.core.runAgent({ agent });
}
}The agent is an AG-UI AbstractAgent. Refer to your AG-UI agent implementation for available methods and message formats.
provideCopilotKit accepts a CopilotKitConfig object:
export interface CopilotKitConfig {
runtimeUrl?: string;
headers?: Record<string, string>;
credentials?: RequestCredentials;
licenseKey?: string;
properties?: Record<string, unknown>;
agents?: Record<string, AbstractAgent>;
selfManagedAgents?: Record<string, AbstractAgent>;
tools?: ClientTool[];
renderToolCalls?: RenderToolCallConfig[];
renderActivityMessages?: RenderActivityMessageConfig[];
suggestionsConfig?: SuggestionsConfig[];
frontendTools?: FrontendToolConfig[];
humanInTheLoop?: HumanInTheLoopConfig[];
defaultToolRendering?: boolean;
a2ui?: A2UIConfig;
openGenerativeUI?: OpenGenerativeUIConfig;
}runtimeUrl: URL to your CopilotKit runtime.headers: Default headers sent to the runtime.credentials: Fetch credentials mode. Use"include"for cross-origin HTTP-only cookies.properties: Arbitrary props forwarded to agent runs.agents: Local, in-browser agents keyed byagentId.selfManagedAgents: AG-UI agents managed directly by the application.tools: Tool definitions advertised to the runtime (no handler).renderToolCalls: Components to render tool calls in the UI.renderActivityMessages: Components to render AG-UI activity messages.suggestionsConfig: Static or runtime-generated chat suggestions.frontendTools: Client-side tools with handlers.humanInTheLoop: Tools that pause for user input.defaultToolRendering: Opt in to the text-only renderer for unknown tools. It is disabled by default so missing renderers remain visible integration errors rather than silently changing the experience.a2ui: Theme, catalog, schema, loading UI, and recovery policy for A2UI. Surfaces render with the catalog from@copilotkit/angular/a2ui(see below); without one, A2UI stays off.openGenerativeUI: Sandboxed UI functions and optional design guidance.
provideCopilotKit(config): Provider forCopilotKitConfig.
agents:Signal<Record<string, AbstractAgent>>runtimeConnectionStatus:Signal<CopilotKitCoreRuntimeConnectionStatus>runtimeUrl:Signal<string | undefined>runtimeTransport:Signal<CopilotRuntimeTransport>("rest" | "single")headers:Signal<Record<string, string>>credentials:Signal<RequestCredentials | undefined>toolCallRenderConfigs:Signal<RenderToolCallConfig[]>clientToolCallRenderConfigs:Signal<FrontendToolConfig[]>humanInTheLoopToolRenderConfigs:Signal<HumanInTheLoopConfig[]>
getAgent(agentId: string): AbstractAgent | undefinedaddFrontendTool(config: FrontendToolConfig & { injector: Injector }): voidaddRenderToolCall(config: RenderToolCallConfig): voidaddHumanInTheLoop(config: HumanInTheLoopConfig): voidremoveTool(toolName: string, agentId?: string): voidupdateRuntime(options: { runtimeUrl?: string; runtimeTransport?: CopilotRuntimeTransport; headers?: Record<string,string>; credentials?: RequestCredentials; properties?: Record<string, unknown>; agents?: Record<string, AbstractAgent>; selfManagedAgents?: Record<string, AbstractAgent>; }): void
core: The underlyingCopilotKitCoreinstance.
const store = injectAgentStore("default");
// or: injectAgentStore(signal(agentId))Returns a Signal<AgentStore>. The store exposes:
agent:AbstractAgentmessages:Signal<Message[]>state:Signal<any>isRunning:Signal<boolean>teardown(): Clean up subscriptions
If the agent is not available locally but a runtimeUrl is configured, a proxy agent is created while the runtime connects. If the agent still cannot be resolved, an error is thrown that includes the configured runtime and known agent IDs.
Advanced factory for creating AgentStore signals. Most apps should use injectAgentStore instead.
Connect AG-UI context to the runtime (auto-cleanup when the effect is destroyed):
import { connectAgentContext } from "@copilotkit/angular";
connectAgentContext({
description: "User preferences",
value: { theme: "dark" },
});You must call it within an injection context (e.g., inside a component constructor or runInInjectionContext), or pass an explicit Injector:
connectAgentContext(contextSignal, { injector });export interface RenderToolCallConfig<Args> {
name: string; // tool name, or "*" for wildcard
args: z.ZodType<Args>; // Zod schema for args
component: Type<ToolRenderer<Args>>;
agentId?: string; // optional agent scope
}
export interface FrontendToolConfig<Args> {
name: string;
description: string;
parameters: z.ZodType<Args>;
component?: Type<ToolRenderer<Args>>; // optional UI renderer
handler: (args: Args, context: FrontendToolHandlerContext) => Promise<unknown>;
agentId?: string;
}
export interface HumanInTheLoopConfig<Args> {
name: string;
description: string;
parameters: z.ZodType<Args>;
component: Type<HumanInTheLoopToolRenderer<Args>>;
agentId?: string;
}
export type ClientTool<Args> = Omit<FrontendTool<Args>, \"handler\"> & {
renderer?: Type<ToolRenderer<Args>>;
};Renderer components receive a signal:
export interface ToolRenderer<Args> {
toolCall: Signal<AngularToolCall<Args>>;
}
export interface HumanInTheLoopToolRenderer<Args> {
toolCall: Signal<HumanInTheLoopToolCall<Args>>; // includes respond(result)
}AngularToolCall / HumanInTheLoopToolCall expose args, status ("in-progress" | "executing" | "complete"), and result.
These helpers auto-remove tools when the current injection context is destroyed:
Call them from an injection context (e.g., a component constructor, directive, or runInInjectionContext).
import {
registerFrontendTool,
registerRenderToolCall,
registerHumanInTheLoop,
} from "@copilotkit/angular";
import { z } from "zod";
registerFrontendTool({
name: "lookup",
description: "Fetch a record",
parameters: z.object({ id: z.string() }),
handler: async ({ id }) => ({ id, ok: true }),
});
registerRenderToolCall({
name: "*", // wildcard renderer
args: z.any(),
component: MyToolCallRenderer,
});
registerHumanInTheLoop({
name: "approval",
description: "Request approval",
parameters: z.object({ reason: z.string() }),
component: ApprovalRenderer,
});provideCopilotKit({
frontendTools: [
/* FrontendToolConfig[] */
],
renderToolCalls: [
/* RenderToolCallConfig[] */
],
humanInTheLoop: [
/* HumanInTheLoopConfig[] */
],
tools: [
/* ClientTool[] */
],
});tools are advertised to the runtime. If you include renderer + parameters on a ClientTool, CopilotKit will also register a renderer for tool calls.
All UI exports are standalone Angular components. Import the component classes
directly and import @copilotkit/angular/styles.css once in the application's
global stylesheet.
import { Component } from "@angular/core";
import { CopilotChat } from "@copilotkit/angular";
@Component({
selector: "app-assistant",
imports: [CopilotChat],
template: `<copilot-chat [agentId]="'default'" />`,
})
export class AssistantComponent {}Use CopilotPopup for a floating dialog and CopilotSidebar for responsive
overlay or docked presentation. Their open inputs are model signals, so
[(open)] supports controlled application state. Both include focus trapping,
Escape handling, focus restoration, accessible dialog naming, reduced-motion
behavior, and safe-area-aware mobile layouts.
import { Component, signal } from "@angular/core";
import { CopilotPopup, CopilotSidebar } from "@copilotkit/angular";
@Component({
imports: [CopilotPopup, CopilotSidebar],
template: `
<copilot-popup [(open)]="popupOpen" title="Support assistant" />
<copilot-sidebar
[(open)]="sidebarOpen"
mode="docked"
position="right"
title="Workspace assistant"
/>
`,
})
export class AssistantSurfacesComponent {
readonly popupOpen = signal(false);
readonly sidebarOpen = signal(false);
}CopilotChatView, message, input, toolbar, button, attachment, and slot
components are supported public customization primitives. See
API.md for the exhaustive export inventory; use the higher-level
components unless you are replacing part of the default composition.
RenderToolCalls renders tool call components under an assistant message based on registered render configs.
<copilot-render-tool-calls
[message]="assistantMessage"
[messages]="messages"
[isLoading]="isRunning"
></copilot-render-tool-calls>Inputs:
message:AssistantMessage(must includetoolCalls)messages: fullMessage[]list (used to find tool results)isLoading: whether the agent is currently running
Tool arguments are parsed with partialJSONParse, so incomplete JSON during streaming still renders.
- Set
runtimeUrlto your CopilotKit runtime endpoint. - If you need to change runtime settings at runtime, call
CopilotKit.updateRuntime(...). runtimeTransportsupports"rest"or"single"(SSE single-stream transport).- For cross-origin cookie authentication, set
credentials: "include"and enable credentialed CORS for the Angular app's exact origin.
Register application activity renderers with registerRenderActivityMessage
or the renderActivityMessages provider option. Application registrations
take precedence over optional built-ins.
- A2UI is enabled when the runtime advertises the capability or when
a2ui.catalogis supplied. An explicit catalog enables its renderers and agent context even when runtime/infodoes not advertise A2UI, matching the React provider contract. Configure recovery exposure independently of server-provided lifecycle content. - Open Generative UI is enabled with
openGenerativeUI: { ... }. Generated UI runs in an isolated WebSandbox; expose only narrowly scopedsandboxFunctionsand never place credentials in browser configuration. - MCP Apps is intentionally a secondary entry point. Add
provideMCPApps()to application providers and import advanced host APIs from@copilotkit/angular/mcp-apps. MCP resource and tool requests travel through the selected AG-UI agent; the browser provider does not accept a server URL. The renderer uses the same inlinesrcdocsandbox, sandbox permissions, and resource-domain CSP as the React SDK.
@copilotkit/angular/a2ui renders A2UI with ordinary standalone Angular
components. A2UI needs a catalog: without one it stays off, even when the
runtime enables it, and CopilotKit logs a warning. basicCatalog
implements the A2UI basic components (Text, Row, Column, Card, Button,
TextField, and the rest):
import { basicCatalog } from "@copilotkit/angular/a2ui";
provideCopilotKit({
runtimeUrl: "/api/copilotkit",
a2ui: { catalog: basicCatalog },
});For your own components, build a catalog with createAngularCatalog. It
contains exactly the components you register unless you pass
includeBasicCatalog: true, which adds the basic components; a definition
with the same name replaces the basic one. A component receives the resolved
props through a single props input, or through inputs named after the
schema keys. Props the agent has not sent yet arrive as undefined, so put
defaults in the template (label() ?? "…") rather than in input("…").
Containers render children with <copilot-a2ui-child>.
import {
ActionSchema,
ChildListSchema,
CopilotA2UIChild,
DynamicStringSchema,
createAngularCatalog,
type A2UICatalogDefinitions,
type A2UIProps,
} from "@copilotkit/angular/a2ui";
const definitions = {
Column: { props: z.object({ children: ChildListSchema }) },
Card: {
props: z.object({
title: DynamicStringSchema,
child: z.string().optional(),
}),
},
PrimaryButton: {
props: z.object({ label: z.string(), action: ActionSchema.optional() }),
},
} satisfies A2UICatalogDefinitions;
@Component({
selector: "app-a2ui-card",
imports: [CopilotA2UIChild],
template: `
<article>
<h3>{{ props().title }}</h3>
<copilot-a2ui-child [child]="props().child" />
</article>
`,
})
export class CardComponent {
readonly props = input.required<A2UIProps<typeof definitions, "Card">>();
}
export const catalog = createAngularCatalog(
definitions,
{
Column: ColumnComponent,
Card: CardComponent,
PrimaryButton: PrimaryButtonComponent,
},
{ catalogId: "copilotkit://dashboard-catalog", includeBasicCatalog: true },
);
provideCopilotKit({ runtimeUrl: "/api/copilotkit", a2ui: { catalog } });Props declared with ActionSchema arrive as callable closures, and dynamic
props gain set* setters that write back to the data model. Inject
injectA2UIComponentContext() to dispatch custom actions or write data-model
paths directly. <copilot-a2ui-surface> renders operations outside the chat
with the same catalog. Icon renders Material Symbols ligatures, so load that
font if the agent uses icons.
A catalog entry can also be a Custom Element, such as an existing design
system's web component: pass { tagName, element } instead of an Angular
component. CopilotKit registers the element, renders it in place of the node,
and assigns the node's A2UI ComponentContext to its context property on
every update; the element reads its props and dispatches actions from there.
createAngularCatalog(
{ Badge: { props: z.object({ label: z.string() }) } },
{ Badge: { tagName: "acme-badge", element: AcmeBadge } },
);The basic catalog and the surface are styled with CSS variables. Set them on
any ancestor; the CopilotKit stylesheet switches the colors in dark mode. The
agent's primaryColor theme sets --a2ui-color-primary for its surface.
| Variable | Default | Used for |
|---|---|---|
--a2ui-color-primary |
#007bff |
primary buttons, active tabs, selected chips |
--a2ui-color-on-primary |
#fff |
text on the primary color |
--a2ui-color-surface |
#fff |
cards, buttons, chips, dialogs |
--a2ui-color-border |
#ccc |
borders and dividers |
--a2ui-color-muted |
#666 |
captions and secondary text |
--a2ui-color-error |
red |
validation errors |
--a2ui-color-input, --a2ui-color-on-input |
system field colors | text inputs |
--a2ui-color-placeholder, --a2ui-color-placeholder-highlight |
#f3f4f6, #e5e7eb |
loading and pending placeholders |
--a2ui-spacing-m |
8px |
component margins and gaps |
--a2ui-row-gap, --a2ui-column-gap, --a2ui-list-gap |
--a2ui-spacing-m |
space between children |
--a2ui-border-radius |
8px |
cards, inputs, dialogs |
--a2ui-font-size-s, --a2ui-font-size-xs |
14px, 12px |
labels, captions |
--a2ui-card-padding, --a2ui-card-shadow |
16px, subtle shadow |
Card |
--a2ui-button-padding, --a2ui-button-border-radius |
8px 16px, 4px |
Button, tabs |
--a2ui-input-padding |
8px |
TextField, DateTimeInput |
--a2ui-chip-border-radius |
16px |
ChoicePicker chips |
--a2ui-icon-size |
24px |
Icon |
--a2ui-modal-backdrop, --a2ui-modal-padding, --a2ui-modal-z-index |
rgba(0, 0, 0, 0.5), 24px, 1000 |
Modal |
--a2ui-primary-color, which the React and Vue renderers use, still works as
a fallback for --a2ui-color-primary.
As in the A2UI renderers, Row, Column and List space their children with a gap
rather than margins on each component, and a component's weight becomes its
flex grow factor inside a Row or Column.
Call injectAgentStore, connectAgentContext, registerFrontendTool,
registerRenderToolCall, registerRenderActivityMessage, injectInterrupt,
injectThreads, and injectMemories from an Angular injection context. The
helpers bind subscriptions, effects, timers, runtime registrations, and
observers to the owning DestroyRef. Changing a signal-based agent ID tears
down the previous agent subscription before connecting the replacement.
Do not create these helpers in module-level code or cache an injected
controller beyond the lifetime of its injector. AgentStore.teardown() is
public for advanced manually constructed stores; stores returned by
injectAgentStore are cleaned up automatically and should not need a manual
call.
Application-owned asynchronous work remains application-owned. Cancel fetches or other side effects started by a frontend-tool handler when its host is destroyed, and do not resolve an interrupt after its controller has left the view.
The package is designed for standalone, OnPush, signal-based applications and
is tested with provideZonelessChangeDetection(). No Zone.js dependency is
required. Keep application state in signals or Angular outputs so zoneless
change detection can observe updates.
Browser-only DOM setup is deferred to render lifecycle hooks or guarded by the platform where the package owns it. For SSR and hydration:
- provide the same CopilotKit configuration and initial
open/agentIdvalues on the server and first client render; - do not access returned agents or run tools during server rendering;
- make runtime URLs absolute when the server and browser use different
origins, or proxy a same-origin
/api/copilotkitendpoint; - enable A2UI, Open Generative UI, audio recording, and MCP Apps in the browser; their interactive sandboxes, custom elements, media APIs, and iframes become active after hydration;
- avoid branching the component tree on
windowbefore hydration. Use Angular platform guards andafterNextRenderfor application-owned browser work.
API.md lists every supported export from the root and MCP Apps
entry points and identifies the single internal extension token. A package test
compares that inventory to TypeScript's resolved entry-point exports so a new
public symbol cannot be introduced without documentation.