packages/angular/README.md
Angular bindings for CopilotKit core and AG-UI agents. This package provides services, directives, and utilities for building custom, headless Copilot UIs.
# npm
npm install @copilotkit/{core,angular}
@angular/core and @angular/common (19+)@angular/cdk (match your Angular major)rxjsConfigure runtime and tools in your app config:
import { ApplicationConfig } from "@angular/core";
import { provideCopilotKit } from "@copilotkit/angular";
export const appConfig: ApplicationConfig = {
providers: [
provideCopilotKit({
licenseKey: "ck_pub_your_public_api_key",
runtimeUrl: "http://localhost:3001/api/copilotkit",
headers: { Authorization: "Bearer ..." },
properties: { app: "demo" },
}),
],
};
injectAgentStoreimport { 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.
CopilotKitConfigprovideCopilotKit accepts a CopilotKitConfig object:
export interface CopilotKitConfig {
runtimeUrl?: string;
headers?: Record<string, string>;
licenseKey?: string;
properties?: Record<string, unknown>;
agents?: Record<string, AbstractAgent>;
tools?: ClientTool[];
renderToolCalls?: RenderToolCallConfig[];
frontendTools?: FrontendToolConfig[];
humanInTheLoop?: HumanInTheLoopConfig[];
}
runtimeUrl: URL to your CopilotKit runtime.headers: Default headers sent to the runtime.licenseKey: Copilot Cloud public API key (ck_pub_...), required by provideCopilotKit.properties: Arbitrary props forwarded to agent runs.agents: Local, in-browser agents keyed by agentId.tools: Tool definitions advertised to the runtime (no handler).renderToolCalls: Components to render tool calls in the UI.frontendTools: Client-side tools with handlers.humanInTheLoop: Tools that pause for user input.provideCopilotKit(config): Provider for CopilotKitConfig.CopilotKit serviceagents: Signal<Record<string, AbstractAgent>>runtimeConnectionStatus: Signal<CopilotKitCoreRuntimeConnectionStatus>runtimeUrl: Signal<string | undefined>runtimeTransport: Signal<CopilotRuntimeTransport> ("rest" | "single")headers: Signal<Record<string, string>>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>; properties?: Record<string, unknown>; agents?: Record<string, AbstractAgent>; }): voidcore: The underlying CopilotKitCore instance.injectAgentStoreconst 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 subscriptionsIf 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.
CopilotkitAgentFactoryAdvanced factory for creating AgentStore signals. Most apps should use injectAgentStore instead.
connectAgentContextConnect 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,
});
provideCopilotKitprovideCopilotKit({
licenseKey: "ck_pub_your_public_api_key",
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.
RenderToolCalls componentRenderToolCalls 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 include toolCalls)messages: full Message[] list (used to find tool results)isLoading: whether the agent is currently runningTool arguments are parsed with partialJSONParse, so incomplete JSON during streaming still renders.
runtimeUrl to your CopilotKit runtime endpoint.CopilotKit.updateRuntime(...).runtimeTransport supports "rest" or "single" (SSE single-stream transport).This package also exports a full set of chat UI components under src/lib/components/chat. Those APIs are intentionally omitted from this README.