Skip to content

Commit ff6eb8f

Browse files
authored
feat: A2UI v0.9 + Open Generative UI — BYOC catalogs, dark mode, sandboxed UI generation (CopilotKit#3588)
## Summary - **A2UI v0.9**: New protocol with `a2ui_operations` container, custom BYOC catalogs (FlightCard, HotelCard, ProductCard, TeamMemberCard), dark mode support via CSS variables, ActionButton with click state, component-neutral LLM guidelines - **Open Generative UI**: Sandboxed HTML/CSS/JS UI generation streamed live into chat via iframe, with designSkill styling guidelines and auto-resize - **Runtime**: `includeBasicCatalog` defaults to false, removed `a2ui: true` boolean shorthand, `before_agent` SystemMessage ID reuse fix - **CLI**: Added `a2ui` and `opengenui` scaffold options - **Python SDK**: `copilotkit.a2ui` helpers, `before_agent` ID reuse fix (0.1.84a2) - **Dojo**: Custom A2UI components shared across demos, aimock fixtures updated, e2e flaky test fix ## Pre-release packages - ag-ui: `pkg.pr.new@10147076ab8247ce088a4e43e6c7a1660e9fa291` - Python SDK: `copilotkit==0.1.84a2` ## Test plan - [ ] A2UI fixed schema (flight + hotel search) renders correctly - [ ] A2UI dynamic schema (hotel, product, team) renders correctly - [ ] Button clicks produce agent responses (action dispatch) - [ ] Dark mode support works - [ ] Open Generative UI sandbox renders in iframe - [ ] `npx copilotkit create` with `a2ui` and `opengenui` options work - [ ] E2e tests pass in ag-ui dojo 🤖 Generated with [Claude Code](https://claude.com/claude-code)
2 parents c08f353 + 434ccd8 commit ff6eb8f

196 files changed

Lines changed: 12846 additions & 13488 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/stupid-parents-run.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@copilotkit/a2ui-renderer": minor
3+
"@copilotkit/react-core": minor
4+
"@copilotkit/runtime": minor
5+
"@copilotkit/shared": patch
6+
"@copilotkit/cli": patch
7+
---
8+
9+
A2UI v0.9 + Open Generative UI: BYOC catalogs, dark mode, sandboxed UI generation

.pnpmfile.cjs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
const path = require("path");
2+
const fs = require("fs");
3+
4+
/**
5+
* When A2UI_LOCAL=1, rewrites @a2ui/* dependencies to link against
6+
* locally-built packages from the sibling A2UI repo.
7+
*
8+
* Usage:
9+
* A2UI_LOCAL=1 pnpm install # link to local A2UI packages
10+
* pnpm install # install from npm (default)
11+
*
12+
* Expects:
13+
* /some/path/CopilotKit/ (this repo)
14+
* /some/path/A2UI/ (sibling A2UI repo)
15+
*/
16+
17+
const A2UI_ROOT = path.resolve(__dirname, "..", "A2UI");
18+
19+
const A2UI_PACKAGES = {
20+
"@a2ui/web_core": "renderers/web_core",
21+
"@a2ui/react": "renderers/react",
22+
};
23+
24+
function readPackage(pkg) {
25+
if (!process.env.A2UI_LOCAL) return pkg;
26+
27+
for (const [dep, relPath] of Object.entries(A2UI_PACKAGES)) {
28+
if (pkg.dependencies && pkg.dependencies[dep]) {
29+
const localPath = path.join(A2UI_ROOT, relPath);
30+
if (fs.existsSync(localPath)) {
31+
pkg.dependencies[dep] = `link:${localPath}`;
32+
}
33+
}
34+
}
35+
36+
return pkg;
37+
}
38+
39+
module.exports = { hooks: { readPackage } };

docs/components/ui/integrations-sidebar/integration-selector.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,10 @@ const IntegrationSelector = ({
199199
return;
200200
}
201201

202-
// Only clear selection if we're specifically on /integrations page
203-
if (pathname === "/integrations" && selectedIntegration) {
202+
// Clear selection on any non-integration route so the picker returns to
203+
// its default "Select integration..." state on the homepage and other
204+
// docs sections.
205+
if (selectedIntegration) {
204206
setSelectedIntegration(null);
205207
}
206208
}, [pathname, selectedIntegration, setSelectedIntegration]);

docs/components/ui/sidebar/folder.tsx

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,39 @@ interface FolderProps {
2020
onNavigate?: () => void;
2121
}
2222

23+
function getFolderDisplayName(node: Node & { children?: Node[] }): string {
24+
const folderName = typeof node.name === "string" ? node.name : "";
25+
const folderUrl =
26+
typeof (node as any).index?.url === "string"
27+
? (node as any).index.url
28+
: typeof node.url === "string"
29+
? node.url
30+
: "";
31+
32+
const childUrls = (node.children || [])
33+
.map((child) => {
34+
if (
35+
child.type === "folder" &&
36+
typeof (child as any).index?.url === "string"
37+
) {
38+
return (child as any).index.url;
39+
}
40+
return typeof child.url === "string" ? child.url : "";
41+
})
42+
.filter(Boolean);
43+
44+
const isA2UIFolder =
45+
folderName === "A2UI" ||
46+
folderUrl.endsWith("/a2ui") ||
47+
childUrls.some((url) => url.includes("/a2ui/"));
48+
49+
if (isA2UIFolder) {
50+
return "Declerative Gen-UI (A2UI)";
51+
}
52+
53+
return folderName;
54+
}
55+
2356
/**
2457
* Checks if a folder's index URL matches the current pathname.
2558
* Handles:
@@ -77,6 +110,9 @@ const Folder = ({ node }: FolderProps) => {
77110
const router = useRouter();
78111
const folderUrl = node.index?.url;
79112
const folderId = node.$id;
113+
const displayName = getFolderDisplayName(
114+
node as Node & { children?: Node[] },
115+
);
80116

81117
// Check if folder should be open by default from meta.json defaultOpen property
82118
// Fumadocs exposes this property from meta.json files
@@ -138,7 +174,7 @@ const Folder = ({ node }: FolderProps) => {
138174
onClick={handleLinkClick}
139175
className="flex gap-2 justify-between items-center px-3 w-full h-10 cursor-pointer"
140176
>
141-
<span className="w-max text-sm shrink-0">{node.name}</span>
177+
<span className="w-max text-sm shrink-0">{displayName}</span>
142178
<ChevronDownIcon className={cn(isOpen ? "rotate-180" : "")} />
143179
</button>
144180
</li>

docs/content/docs/(root)/generative-ui/a2ui.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
title: "A2UI"
2+
title: "Declarative Gen-UI (A2UI)"
33
icon: "lucide/FileJson"
44
description: "Render declarative UI components using the A2UI specification."
55
hideTOC: true

docs/content/docs/(root)/generative-ui/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"tool-rendering",
77
"state-rendering",
88
"mcp-apps",
9+
"open-generative-ui",
910
"a2ui"
1011
]
1112
}
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
---
2+
title: "Open Generative UI"
3+
icon: "lucide/Code"
4+
description: "Let agents generate fully interactive HTML/CSS/JS UIs that stream live into the chat."
5+
---
6+
7+
import { Tabs, Tab } from "fumadocs-ui/components/tabs";
8+
9+
## What is this?
10+
11+
Open Generative UI lets the agent generate complete, sandboxed UI on the fly — HTML, CSS, and JavaScript — and stream it live into the chat. The user sees the interface build in real time: styles apply first, then HTML streams in progressively, and finally JavaScript expressions execute one by one.
12+
13+
Key benefits:
14+
- **No predefined components** — the agent creates any UI it needs, on demand
15+
- **Live streaming** — HTML streams into a preview as it's generated
16+
- **CDN libraries** — the generated UI can load external libraries (Chart.js, D3, Three.js, etc.) via `<script>` tags
17+
- **Secure sandboxing** — content runs in an isolated iframe without same-origin access
18+
- **Sandbox functions** — expose host application functions to the sandboxed UI for two-way communication
19+
20+
## Basic setup
21+
22+
Enable Open Generative UI by setting `openGenerativeUI: true` in your runtime configuration. That's it — the middleware handles the rest.
23+
24+
<Steps>
25+
<Step>
26+
### Add the dependencies
27+
28+
```npm
29+
npm install @copilotkit/react-ui @copilotkit/react-core @copilotkit/runtime
30+
```
31+
</Step>
32+
<Step>
33+
### Configure the runtime
34+
35+
```typescript title="app/api/copilotkit/route.ts"
36+
import {
37+
CopilotRuntime,
38+
createCopilotEndpoint,
39+
InMemoryAgentRunner,
40+
} from "@copilotkit/runtime";
41+
import { BuiltInAgent } from "@copilotkit/runtime/v2";
42+
import { handle } from "hono/vercel";
43+
44+
const agent = new BuiltInAgent({
45+
model: "openai/gpt-5.2",
46+
prompt: "You are a helpful assistant.",
47+
});
48+
49+
const runtime = new CopilotRuntime({
50+
agents: { default: agent },
51+
runner: new InMemoryAgentRunner(),
52+
// Enable Open Generative UI
53+
openGenerativeUI: true,
54+
});
55+
56+
const app = createCopilotEndpoint({
57+
runtime,
58+
basePath: "/api/copilotkit",
59+
});
60+
61+
export const GET = handle(app);
62+
export const POST = handle(app);
63+
```
64+
65+
You can also pass options instead of `true`:
66+
67+
```typescript
68+
openGenerativeUI: {
69+
instructions: "Always use a dark color scheme.",
70+
}
71+
```
72+
73+
The `instructions` string is appended to the tool description, letting you customize what the agent generates.
74+
</Step>
75+
<Step>
76+
### Configure the frontend
77+
78+
```tsx title="app/page.tsx"
79+
"use client";
80+
81+
import { CopilotKit } from "@copilotkit/react-core/v2";
82+
import { CopilotChat } from "@copilotkit/react-core/v2";
83+
import "@copilotkit/react-ui/v2/styles.css";
84+
85+
export default function Page() {
86+
return (
87+
<CopilotKit runtimeUrl="/api/copilotkit">
88+
<CopilotChat />
89+
</CopilotKit>
90+
);
91+
}
92+
```
93+
94+
No special frontend prop is needed — Open Generative UI rendering is built in.
95+
</Step>
96+
</Steps>
97+
98+
That's it. Ask the agent to "build a calculator" or "show me a bar chart of sales data" and it will generate and stream the UI live.
99+
100+
## Advanced: Sandbox functions
101+
102+
Sandbox functions let the generated UI call back into your host application. This enables two-way communication — for example, a generated settings panel that can toggle your app's theme, or a product card that adds items to your cart.
103+
104+
### Defining sandbox functions
105+
106+
Pass `sandboxFunctions` to the `openGenerativeUI` prop on `CopilotKit`:
107+
108+
```tsx title="app/page.tsx"
109+
"use client";
110+
111+
import { CopilotKit, CopilotChat } from "@copilotkit/react-core/v2";
112+
import "@copilotkit/react-ui/v2/styles.css";
113+
import { useState } from "react";
114+
import { z } from "zod";
115+
116+
export default function Page() {
117+
const [theme, setTheme] = useState<"light" | "dark">("light");
118+
119+
return (
120+
<CopilotKit
121+
runtimeUrl="/api/copilotkit"
122+
openGenerativeUI={{
123+
sandboxFunctions: [
124+
{
125+
name: "setTheme",
126+
description: "Switch the app theme between light and dark mode.",
127+
parameters: z.object({
128+
mode: z.enum(["light", "dark"]).describe("The theme mode to set"),
129+
}),
130+
handler: async ({ mode }) => {
131+
setTheme(mode);
132+
return `Theme set to ${mode}`;
133+
},
134+
},
135+
],
136+
}}
137+
>
138+
<CopilotChat />
139+
</CopilotKit>
140+
);
141+
}
142+
```
143+
144+
### How sandbox functions work
145+
146+
Each sandbox function has:
147+
148+
| Property | Type | Description |
149+
|---|---|---|
150+
| `name` | `string` | Function name, callable from the sandbox |
151+
| `description` | `string` | Tells the agent what this function does |
152+
| `parameters` | Zod schema | Validates arguments passed from the sandbox |
153+
| `handler` | `(args) => Promise<string>` | Runs in the host app, returns a result string |
154+
155+
Inside the generated UI, the agent calls these functions via:
156+
157+
```javascript
158+
// Inside the sandboxed iframe
159+
const result = await Websandbox.connection.remote.setTheme({ mode: "dark" });
160+
```
161+
162+
The function descriptions are automatically included in the agent's context, so it knows which functions are available and how to use them.
163+
164+
### Use cases
165+
166+
- **Theme toggling** — generated UI controls your app's appearance
167+
- **Cart / state management** — product cards that add items to the host app's state
168+
- **Navigation** — generated UI that triggers route changes in the host app
169+
- **Data fetching** — sandbox calls the host to fetch data the iframe can't access directly (due to same-origin restrictions)
170+
171+
## How streaming works
172+
173+
The agent generates parameters in a specific order optimized for the user experience:
174+
175+
1. **`placeholderMessages`** — shown immediately while generating
176+
2. **`css`** — all styles generated first; the preview starts once CSS is complete
177+
3. **`html`** — streams live into the preview as it's generated
178+
4. **`jsFunctions`** — reusable helper functions injected before expressions
179+
5. **`jsExpressions`** — executed one by one; the user sees each take effect
180+
181+
The middleware parses the tool call arguments incrementally and emits activity events as each parameter completes, so the frontend can show progressive updates.
182+
183+
## Using CDN libraries
184+
185+
The sandboxed iframe can load external libraries from CDNs. Include `<script>` or `<link>` tags in the generated HTML `<head>`:
186+
187+
```html
188+
<head>
189+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
190+
</head>
191+
<body>
192+
<canvas id="myChart"></canvas>
193+
</body>
194+
```
195+
196+
Libraries like Chart.js, D3, Three.js, x-data-spreadsheet, and any other CDN-hosted library work out of the box.
197+
198+
## Middleware API
199+
200+
If you prefer to apply the middleware to specific agents rather than globally, use `OpenGenerativeUIMiddleware` directly:
201+
202+
```typescript
203+
import { OpenGenerativeUIMiddleware } from "@copilotkit/runtime";
204+
205+
const agent = new BuiltInAgent({
206+
model: "openai/gpt-5.2",
207+
prompt: "You are a helpful assistant.",
208+
}).use(
209+
new OpenGenerativeUIMiddleware({
210+
instructions: "Use a modern, minimal design.",
211+
}),
212+
);
213+
```

docs/content/docs/integrations/langgraph/generative-ui/a2ui.mdx

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)