Skip to content

feat(angular): add CopilotActivity for standalone activity rendering - #6033

Merged
rainerhahnekamp merged 3 commits into
CopilotKit:mainfrom
manfredsteyer:feat/angular-copilot-activity
Sep 6, 2026
Merged

rainerhahnekamp merged 3 commits into
CopilotKit:mainfrom
manfredsteyer:feat/angular-copilot-activity

Conversation

@manfredsteyer

@manfredsteyer manfredsteyer commented Jul 17, 2026 •

Copy link
Copy Markdown
Contributor

There is currently no dedicated way to render a single activity message in headless mode. Building it yourself means duplicating the private resolution logic of CopilotChatMessageView.

Tool calls already have a public component for this (RenderToolCalls), activities do not. This is an asymmetry in the Angular API and a gap compared to React's useRenderActivityMessage().

This PR adds CopilotActivity (<copilot-activity [message] [agentId]>), a standalone host for a single activity message. CopilotChatMessageView now delegates to it, so there is one implementation for the activity role. No behavior change. The renderer resolution moves into an internal pickActivityRenderer that is not exported.

Summary by CodeRabbit

  • New Features

    • Added the public CopilotActivity Angular component for rendering individual activity messages.
    • Supports activity renderer selection by activity type, agent scope, global registration, and wildcard fallback.
    • Exposes message and optional agentId inputs for custom activity surfaces.
  • Improvements

    • Updated CopilotChatMessageView to use CopilotActivity for activity rendering.
  • Documentation

    • Added usage documentation and clarified activity rendering behavior.

Rendering activity messages outside of CopilotChatMessageView (custom chat
shells, dashboards, headless setups) currently requires instantiating the
whole message view or copying its private resolution logic. Tool calls
already have a public component for this (RenderToolCalls), activities do
not.

This adds CopilotActivity (<copilot-activity [message] [agentId]>), a
standalone host for a single activity message. CopilotChatMessageView now
delegates to it, so there is one implementation for the activity role. No
behavior change. The renderer resolution moves into an internal
pickActivityRenderer that is not exported.

Co-Authored-By: Claude Fable 5 <[email protected]>
@manfredsteyer
manfredsteyer force-pushed the feat/angular-copilot-activity branch from ff503eb to bda5c41 Compare August 27, 2026 13:24
@manfredsteyer manfredsteyer changed the title feat(angular): extract activity rendering into standalone CopilotActivity component feat(angular): add CopilotActivity for standalone activity rendering Aug 27, 2026
@rainerhahnekamp
rainerhahnekamp force-pushed the feat/angular-copilot-activity branch from a1d079e to bda5c41 Compare August 27, 2026 14:06

@rainerhahnekamp rainerhahnekamp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Serwas @manfredsteyer, I did the review fully manually, so there are a few. comments I've left. we'll use the information for the review to come up with specific review skills in the future.

/cc @wolfmanfx


const parseResult = renderer.content.safeParse(message.content);
if (parseResult.success === false) {
console.warn(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know. In Angular we usually log in ngDevMode. I'll leave that to your decision @manfredsteyer

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's fine for you, I would stick with it because ngDevMode is currently not used in the code base and because we already have other places where a warning is print to the console.

* Kept internal for now (not part of the public API); it can be exposed later
* without a breaking change if there is demand for a headless resolver.
*/
export function pickActivityRenderer(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that could actually be a private function of CopilotKitActivity.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was indeed the case before my changes. The reason for this change is mainly symmetry with the counter part pickToolCallHandler. Do you want it to be changed back to CopilotKitActivity anyway?

* without a breaking change if there is demand for a headless resolver.
*/
export function pickActivityRenderer(
options: PickActivityRendererOptions,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i am not a big fan of having an explicit type for three paramters. it would be more readable if that function just has three parameters.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this was done for symmetry with pickToolCallHandler. Do you want it to be still changed back?

`,
})
export class CopilotActivity {
readonly #copilotKit = inject(CopilotKit);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think with the upcoming private in Angular 22,1, we should not use # anymore. Just private. angular/angular#70188

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imports: [NgComponentOutlet],
changeDetection: ChangeDetectionStrategy.Eager,
template: `
@let render = resolveRender(message());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not really happy with calling a function here. Why not use a computed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

100% agree.


@Component({
selector: "secondary-activity-renderer",
changeDetection: ChangeDetectionStrategy.Eager,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default


@Component({
selector: "wildcard-activity-renderer",
changeDetection: ChangeDetectionStrategy.Eager,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default

expect(rendered?.getAttribute("data-content")).toBe(
JSON.stringify({ operations: [] }),
);
expect(getAgent).toHaveBeenCalledWith("demo-button");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is that necessary? i think the other assertions must failed if the agent wasn't called.

...overrides,
});

describe("CopilotActivity", () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test which could be added

  1. once the Component is using a computed, we could also add a test where an agentid switches.
  2. multiple messages and how the rendering changes.

...overrides,
});

describe("pickActivityRenderer", () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd say pickRenderer is an implementation detail of CopilotActivity and should therefore be tested there. In fact, we could do specific picking tests in a nested describe.

@manfredsteyer

Copy link
Copy Markdown
Contributor Author

Thanks @rainerhahnekamp for your review. Please see my answers above.

@rainerhahnekamp

Copy link
Copy Markdown
Collaborator

Serwas @manfredsteyer, yeah, so I fully understand that you were following the existing coding standards, but here's the thing: We don't fully see the existing codebase as "Angularized," and we won't have the resources to do a one-time "Angularization" project. Instead, we want to do this incrementally, and we have to start somewhere.

So here's what we can do. We can merge your PR as-is. Its public API is perfeclty fine nothing will change there. The new component will be available with the next Angular release. @wolfmanfx or me would then apply the mentioned comments in a separate PR.


In the meantime, we two could then continue with #6075

@manfredsteyer

Copy link
Copy Markdown
Contributor Author

@rainerhahnekamp Sounds great. And I’ll keep these comments regarding modern Angular (OnPush, ...) in mind for future PRs.

@rainerhahnekamp

Copy link
Copy Markdown
Collaborator

Yeah, we should probably also collect all the reviews and make a code guidelines skill out of them. Then the models do it automatically.

@rainerhahnekamp
rainerhahnekamp merged commit 0782606 into CopilotKit:main Sep 6, 2026
1 check was pending
@rainerhahnekamp

Copy link
Copy Markdown
Collaborator

Danke Manfred!

@coderabbitai

coderabbitai Bot commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: c09c2813-7584-4fe1-94d3-05c2de7369d1

📥 Commits

Reviewing files that changed from the base of the PR and between b5727e4 and 0d46ef5.

📒 Files selected for processing (14)
  • .changeset/angular-copilot-activity.md
  • packages/angular/API.md
  • packages/angular/src/lib/components/activity/__tests__/activity-renderer-stubs.ts
  • packages/angular/src/lib/components/activity/__tests__/copilot-activity.spec.ts
  • packages/angular/src/lib/components/activity/__tests__/pick-activity-renderer.spec.ts
  • packages/angular/src/lib/components/activity/copilot-activity.ts
  • packages/angular/src/lib/components/activity/pick-activity-renderer.ts
  • packages/angular/src/lib/components/chat/__tests__/copilot-chat-message-view.component.spec.ts
  • packages/angular/src/lib/components/chat/__tests__/dummy-activity-renderer.component.ts
  • packages/angular/src/lib/components/chat/__tests__/fallback-activity-renderer.component.ts
  • packages/angular/src/lib/components/chat/copilot-chat-message-view.ts
  • packages/angular/src/public-api.ts
  • showcase/shell-docs/src/content/reference/angular/components/CopilotActivity.mdx
  • showcase/shell-docs/src/content/reference/angular/components/CopilotChatMessageView.mdx

📝 Walkthrough

Walkthrough

Adds standalone CopilotActivity rendering for Angular. It selects activity renderers by type and agent scope, validates content, supports wildcard fallbacks, routes chat activity messages through the component, and exposes the component through package exports and documentation.

Changes

Angular activity rendering

Layer / File(s) Summary
Renderer selection and precedence
packages/angular/src/lib/components/activity/pick-activity-renderer.ts, packages/angular/src/lib/components/activity/__tests__/pick-activity-renderer.spec.ts
Adds renderer selection for exact, agent-scoped, global, wildcard, and unmatched activity types.
CopilotActivity component and validation
packages/angular/src/lib/components/activity/copilot-activity.ts, packages/angular/src/lib/components/activity/__tests__/*
Adds the standalone component, renderer input wiring, agent lookup, schema validation, warning handling, dynamic rendering, and renderer test stubs.
Chat integration and public surface
packages/angular/src/lib/components/chat/*, packages/angular/src/public-api.ts, packages/angular/API.md, showcase/shell-docs/src/content/reference/angular/components/*, .changeset/angular-copilot-activity.md
Routes chat activity messages through CopilotActivity, updates integration tests, exports the component, adds documentation, and declares a minor package release.

Estimated code review effort: 3 (Moderate) | ~25 minutes

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@manfredsteyer

Copy link
Copy Markdown
Contributor Author

I thank you, @rainerhahnekamp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants