Files
Alem Tuzlak 18c7a6001d feat: multimodal attachments — UI polish, deprecations, docs, codemod
Attachment queue & previews:
- Image lightbox with View Transition API morph animation
- Video lightbox with native controls and play button overlay
- Document lightbox (PDF via blob URL, text inline, info card fallback)
- Drop zone overlay with upload icon
- Filename preservation via InputContent metadata
- Proper video thumbnail sizing and play/pause indicator
- Fix attachment queue positioning (max-w-3xl constraint)
- Padding between X button and content for audio/document cards
- Document filenames wrap instead of truncating

Attachments config:
- onUploadFailed callback for validation/upload errors (file-too-large, invalid-type, upload-failed)
- onUpload accepts sync or async returns
- AttachmentUploadResult discriminated union with explicit interfaces
- Metadata field on Attachment and onUpload return type

AG-UI version bump:
- Bump @ag-ui/client, @ag-ui/core, @ag-ui/encoder, @ag-ui/proto to 0.0.51
- Remove process.env Vite workaround (fixed upstream in 0.0.51)

Deprecation lifecycle:
- @deprecated JSDoc on all legacy image upload APIs
- ImageRenderer, ImageRendererProps, ImageUpload type, imageUploadsEnabled prop,
  inputFileAccept prop, ImageRenderer prop, AIMessage.image, ImageData
- Codemod at codemods/migrate-attachments.ts (15 tests)
- Migration guide updated with codemod instructions and new type shapes

Docs:
- New guide: docs/(root)/multimodal-attachments.mdx
- Updated migration guide with onUpload return type, metadata, codemod section
- Cross-links from prebuilt-components and migration guide
- Label change: "Add photos or files" → "Add attachments"

Tests:
- CopilotChat.attachments.test.tsx — 5 tests for onUploadFailed
- migrate-attachments codemod — 15 tests
2026-04-06 14:55:06 +02:00
..

CopilotKit for Angular

Angular bindings for CopilotKit core and AG-UI agents. This package provides services, directives, and utilities for building custom, headless Copilot UIs.

Installation

# npm
npm install @copilotkit/{core,angular}
  • @angular/core and @angular/common (19+)
  • @angular/cdk (match your Angular major)
  • rxjs

Quick start

1) Provide CopilotKit

Configure 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" },
    }),
  ],
};

2) Build a custom UI with injectAgentStore

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.

Core configuration

CopilotKitConfig

provideCopilotKit 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.

Injection helpers

  • provideCopilotKit(config): Provider for CopilotKitConfig.

CopilotKit service

Readonly signals

  • agents: 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[]>

Methods

  • getAgent(agentId: string): AbstractAgent | undefined
  • addFrontendTool(config: FrontendToolConfig & { injector: Injector }): void
  • addRenderToolCall(config: RenderToolCallConfig): void
  • addHumanInTheLoop(config: HumanInTheLoopConfig): void
  • removeTool(toolName: string, agentId?: string): void
  • updateRuntime(options: { runtimeUrl?: string; runtimeTransport?: CopilotRuntimeTransport; headers?: Record<string,string>; properties?: Record<string, unknown>; agents?: Record<string, AbstractAgent>; }): void

Advanced

  • core: The underlying CopilotKitCore instance.

Agents

injectAgentStore

const store = injectAgentStore("default");
// or: injectAgentStore(signal(agentId))

Returns a Signal<AgentStore>. The store exposes:

  • agent: AbstractAgent
  • messages: 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.

CopilotkitAgentFactory

Advanced factory for creating AgentStore signals. Most apps should use injectAgentStore instead.

Agent context

connectAgentContext

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 });

Tools and tool rendering

Types

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.

Register tools with DI

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,
});

Configure tools in provideCopilotKit

provideCopilotKit({
  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 component

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 include toolCalls)
  • messages: full Message[] 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.

Runtime notes

  • Set runtimeUrl to your CopilotKit runtime endpoint.
  • If you need to change runtime settings at runtime, call CopilotKit.updateRuntime(...).
  • runtimeTransport supports "rest" or "single" (SSE single-stream transport).

Not documented here

This package also exports a full set of chat UI components under src/lib/components/chat. Those APIs are intentionally omitted from this README.