This package provides native Angular components, directives, and providers to build Copilot chat UIs powered by the CopilotKit runtime and AG-UI agents. It mirrors the React experience with idiomatic Angular APIs.
- Install:
pnpm add @copilotkitnext/angular - Add styles: Add
@copilotkitnext/angular/styles.cssto your Angular app styles array or@import "@copilotkitnext/angular/styles.css";in a global stylesheet - Provide CopilotKit: Set the runtime URL and optional labels via providers
- Use the chat: Drop
<copilot-chat />into any template
Install @copilotkitnext/angular in your Angular app (supports Angular 18 and 19):
# pnpm (recommended)
pnpm add @copilotkitnext/angular
# npm
npm install @copilotkitnext/angular
# yarn
yarn add @copilotkitnext/angularEnsure these are present (matching your Angular major):
@angular/core@angular/common@angular/cdk(use^18with Angular 18,^19with Angular 19)rxjstslib
Reference the package CSS so the components render correctly:
Option 1: In angular.json:
"styles": [
"@copilotkitnext/angular/styles.css",
"src/styles.css"
]Option 2: In your global stylesheet:
@import "@copilotkitnext/angular/styles.css";Add CopilotKit providers in your application config to set labels and runtime URL.
import {
provideCopilotKit,
provideCopilotChatConfiguration,
} from "@copilotkitnext/angular";
export const appConfig: ApplicationConfig = {
providers: [
importProvidersFrom(BrowserModule),
...provideCopilotKit({
// runtimeUrl can also be set via template directive; see below
}),
provideCopilotChatConfiguration({
labels: {
chatInputPlaceholder: "Ask me anything...",
chatDisclaimerText: "AI responses may need verification.",
},
}),
],
};You can declare the CopilotKit runtime endpoint directly in templates via the CopilotKitConfigDirective.
<div
[copilotkitConfig]="{ runtimeUrl: runtimeUrl }"
style="display:block;height:100vh"
>
<copilot-chat></copilot-chat>
</div>export class AppComponent {
runtimeUrl = "http://localhost:3001/api/copilotkit";
}<copilot-chat></copilot-chat><copilot-chat [agentId]="'sales'"></copilot-chat>- If
agentIdis omitted, the component uses the default agent (ID:default)
When building custom input components for CopilotKit Angular, use the service-based pattern with CopilotChatConfigurationService for message submission. This is the idiomatic Angular approach leveraging dependency injection.
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { CopilotChatConfigurationService } from '@copilotkitnext/angular';
@Component({
selector: 'my-custom-input',
standalone: true,
imports: [FormsModule],
template: `
<div class="custom-input-wrapper">
<input
[(ngModel)]="inputValue"
(keyup.enter)="submitMessage()"
placeholder="Type your message..."
/>
<button (click)="submitMessage()">Send</button>
</div>
`
})
export class MyCustomInputComponent {
inputValue = '';
constructor(private chat: CopilotChatConfigurationService) {}
submitMessage() {
const value = this.inputValue.trim();
if (value) {
// Use the service to submit the message
this.chat.submitInput(value);
this.inputValue = '';
}
}
}import { Component } from '@angular/core';
import { CopilotChatViewComponent } from '@copilotkitnext/angular';
import { MyCustomInputComponent } from './my-custom-input.component';
@Component({
selector: 'app-chat',
standalone: true,
imports: [CopilotChatViewComponent],
template: `
<copilot-chat-view
[messages]="messages"
[inputComponent]="customInputComponent">
</copilot-chat-view>
`
})
export class ChatComponent {
messages = [];
customInputComponent = MyCustomInputComponent;
}- No callback props: Unlike React which uses
onSubmitMessagecallbacks, Angular uses dependency injection - Service injection: Inject
CopilotChatConfigurationServiceto accesssubmitInput() - Cross-component communication: The service handles message submission internally
- Type safety: Full TypeScript support with proper type inference
For template-level hooks, you can also use the copilotkitChatConfig directive:
<div [copilotkitChatConfig]="{
onSubmitInput: handleSubmit,
onChangeInput: handleChange
}">
<copilot-chat></copilot-chat>
</div>export class ChatComponent {
handleSubmit = (value: string) => {
console.log('Message submitted:', value);
};
handleChange = (value: string) => {
console.log('Input changed:', value);
};
}- Agent model: CopilotKit uses AG-UI's
AbstractAgentinterface (package@ag-ui/client) - Frontend vs backend:
- Backend (runtime): Host your real agents. You can use any AG-UI agent on the server
- Frontend (Angular app): Discovers remote agents from the runtime automatically, and can also host local in-browser agents if desired
- Default agent: The ID
defaultis special; when present, it is used by<copilot-chat>if noagentIdis provided - Compatibility: Any agent that supports AG-UI works. See https://docs.ag-ui.com/
Note: In most real apps, you define agents on the server (runtime). The frontend will auto-discover them when a
runtimeUrlis configured.
Example Angular server (from apps/angular/demo-server):
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { cors } from "hono/cors";
import {
CopilotRuntime,
createCopilotEndpoint,
InMemoryAgentRunner,
} from "@copilotkitnext/runtime";
import { AnyAGUIAgent } from "@ag-ui/your-desired-agent-framework";
const runtime = new CopilotRuntime({
agents: { default: new AnyAGUIAgent() },
});
// Create a main app with CORS enabled
const app = new Hono();
// Enable CORS for local dev (Angular demo at http://localhost:4200)
app.use(
"*",
cors({
origin: "http://localhost:4200",
allowMethods: ["GET", "POST", "OPTIONS", "PUT", "DELETE"],
allowHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
exposeHeaders: ["Content-Type"],
credentials: true,
maxAge: 86400,
})
);
// Create the CopilotKit endpoint
const copilotApp = createCopilotEndpoint({
runtime,
basePath: "/api/copilotkit",
});
// Mount the CopilotKit app
app.route("/", copilotApp);
const port = Number(process.env.PORT || 3001);
serve({ fetch: app.fetch, port });
console.log(
`CopilotKit runtime listening at http://localhost:${port}/api/copilotkit`
);CopilotChatComponent: Full chat UI- Inputs:
agentId?: string
- Inputs:
CopilotKitConfigDirective([copilotkitConfig]): SetruntimeUrl,headers,properties, and/oragentsdeclarativelyCopilotKitAgentDirective([copilotkitAgent]): Observe agent state; defaults to thedefaultagent if noagentIdis provided
provideCopilotKit(...): Set runtime URL, headers, properties, agents, tools, human-in-the-loop handlersprovideCopilotChatConfiguration(...): Set UI labels and behavior for chat input/view
For advanced use cases where you need full control over the chat UI, you can use the watchAgent utility directly to build a custom chat component.
The watchAgent function provides reactive signals for agent state, making it easy to build custom chat interfaces:
import { Component, effect } from "@angular/core";
import { watchAgent } from "@copilotkitnext/angular";
@Component({
selector: "my-custom-chat",
template: `
<div class="custom-chat">
<div *ngFor="let msg of messages()" class="message">
{{ msg.content }}
</div>
<input
[disabled]="isRunning()"
(keyup.enter)="sendMessage($event)"
/>
</div>
`,
})
export class MyCustomChatComponent {
protected agent!: ReturnType<typeof watchAgent>["agent"];
protected messages!: ReturnType<typeof watchAgent>["messages"];
protected isRunning!: ReturnType<typeof watchAgent>["isRunning"];
constructor() {
const w = watchAgent({ agentId: "custom" });
this.agent = w.agent;
this.messages = w.messages;
this.isRunning = w.isRunning;
// React to agent changes
effect(() => {
const currentAgent = this.agent();
if (currentAgent) {
console.log("Agent ready:", currentAgent.id);
}
});
}
async sendMessage(event: Event) {
const input = event.target as HTMLInputElement;
const content = input.value.trim();
if (!content || !this.agent()) return;
// Add user message and run agent
this.agent()!.addMessage({ role: "user", content });
input.value = "";
await this.agent()!.runAgent();
}
}Use watchAgentWith when you need to switch agents dynamically outside of the constructor:
import { Component, Injector } from "@angular/core";
import { watchAgent, watchAgentWith } from "@copilotkitnext/angular";
@Component({
selector: "agent-switcher",
template: `
<button (click)="switchToAgent('sales')">Sales Agent</button>
<button (click)="switchToAgent('support')">Support Agent</button>
<div>Current Agent: {{ agent()?.id || 'None' }}</div>
`,
})
export class AgentSwitcherComponent {
protected agent!: ReturnType<typeof watchAgent>["agent"];
protected messages!: ReturnType<typeof watchAgent>["messages"];
protected isRunning!: ReturnType<typeof watchAgent>["isRunning"];
private watcher?: ReturnType<typeof watchAgent>;
constructor(private injector: Injector) {
// Initialize with default agent
this.switchToAgent("default");
}
switchToAgent(agentId: string) {
// Clean up previous watcher
this.watcher?.unsubscribe();
// Create new watcher with the ergonomic helper
const w = watchAgentWith(this.injector, { agentId });
// Update component signals
this.agent = w.agent;
this.messages = w.messages;
this.isRunning = w.isRunning;
this.watcher = w;
}
}To render tool calls in a headless UI, register renderers in your providers and drop the lightweight view in your template.
- Register tool renderers (e.g., a wildcard that renders any tool):
import { ApplicationConfig, importProvidersFrom } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { provideCopilotKit } from '@copilotkitnext/angular';
// Simple demo renderer (Component or TemplateRef accepted)
@Component({
standalone: true,
template: `
<div style="padding:12px;border:1px solid #e5e7eb;border-radius:8px;background:#fff;margin:8px 0;">
<div style="font-weight:600;margin-bottom:6px;">Tool: {{ name }}</div>
<pre style="margin:0;white-space:pre-wrap;">{{ args | json }}</pre>
<div *ngIf="result" style="margin-top:6px;">Result: {{ result }}</div>
</div>
`,
})
export class WildcardToolRenderComponent {
@Input() name!: string;
@Input() args: any;
@Input() status: any;
@Input() result?: string;
}
export const appConfig: ApplicationConfig = {
providers: [
importProvidersFrom(BrowserModule),
...provideCopilotKit({
renderToolCalls: [
{ name: '*', render: WildcardToolRenderComponent },
],
}),
],
};- Render tool calls under assistant messages using the headless view component:
import { Component } from '@angular/core';
import { watchAgent, CopilotChatToolCallsViewComponent } from '@copilotkitnext/angular';
@Component({
standalone: true,
imports: [CopilotChatToolCallsViewComponent],
template: `
<div *ngFor="let m of messages()">
<div>{{ m.role }}: {{ m.content }}</div>
<ng-container *ngIf="m.role === 'assistant'">
<copilot-chat-tool-calls-view
[message]="m"
[messages]="messages()"
[isLoading]="isRunning()"
/>
</ng-container>
</div>
`,
})
export class HeadlessWithToolsComponent {
agent = watchAgent().agent;
messages = watchAgent().messages;
isRunning = watchAgent().isRunning;
}Notes:
- If you prefer full manual control, you can render a specific tool call with
CopilotKitToolRenderComponentand passtoolName,args,status, andresultyourself. - You can also register tool renders declaratively via the
CopilotKitFrontendToolDirectiveby using[copilotkitFrontendTool]in templates.
- Full control: Build any UI you need without constraints
- Reactive signals: Automatically update UI when agent state changes
- Type safety: Full TypeScript support with AG-UI types
- Memory efficient: Automatic cleanup via Angular's DestroyRef
- Framework agnostic: Works with any AG-UI compatible agent
From the repo root:
- Install deps:
pnpm install - Start both demo server and Angular demo app: pnpm build && pnpm demo:angular`
- Frontend: runs on http://localhost:4200
- Backend: runs on http://localhost:3001/api/copilotkit
- Prerequisite: Set
OPENAI_API_KEYinapps/angular/demo-server/.envif using the OpenAI demo agent
- Full build:
pnpm build(compiles all packages including Angular) - Clean:
pnpm clean - Package-only dev (watch):
pnpm dev
pnpm storybook:angular- Serves Storybook for Angular components on http://localhost:6007
- For live chat stories, ensure the demo server is running so the chat can connect:
pnpm --filter @copilotkitnext/angular-demo-server dev
pnpm -C apps/angular/storybook build- Node 18+ and pnpm 9+ recommended
- If using custom CORS or non-default ports, update
runtimeUrland server CORS settings accordingly - Styles must be included for proper rendering; if customizing CSS, prefer overriding classes instead of modifying the distributed CSS