Open Generative UI
Let agents generate fully interactive HTML/CSS/JS UIs that stream live into the chat.
"""Strands agent specialization for the open-gen-ui demos (Wave 2).Minimal and Advanced variants share the same core behavior: on every userturn, the agent calls the frontend-registered `generateSandboxedUi` toolexactly once, producing a self-contained sandboxed UI cell.The runtime's `OpenGenerativeUIMiddleware` (enabled via the`openGenerativeUI` option on the CopilotRuntime in`src/app/api/copilotkit-ogui/route.ts`) converts that streaming tool callinto `open-generative-ui` activity events.Since the shared Strands agent dispatches the frontend-registered tool viathe ag_ui_strands proxy, no dedicated Python Agent instance is required.This module documents the system prompt that specializes the shared agentvia `useAgentContext` on the frontend."""OPEN_GEN_UI_SYSTEM_PROMPT = """\You are a UI-generating assistant for an Open Generative UI demo. On everyuser turn you MUST call the `generateSandboxedUi` frontend tool exactly once.Design a visually polished, self-contained HTML + CSS + SVG widget thatteaches the requested concept.The frontend injects a detailed "design skill" as agent context describingthe palette, typography, labelling, and motion conventions expected —follow it closely. Key invariants:- Output ONE call to `generateSandboxedUi` per user turn.- Respect the `initialHeight`, `placeholderMessages`, `css`, `html` contract defined by the design skill.- For the advanced variant, the frontend may register sandbox functions the iframe can call via `Websandbox.connection.remote.<name>(...)`. Use these where it makes the demo visibly exercise the iframe <-> host bridge."""def build_open_gen_ui_agent(): """Build a Strands Agent for the open-gen-ui demos. Not currently wired into agent_server.py; the shared agent handles the `generateSandboxedUi` tool call via ag_ui_strands' frontend-tool proxy. This module's prompt is mirrored by the frontend design skill passed to CopilotKit's `openGenerativeUI.designSkill`. """ from strands import Agent from strands.models.openai import OpenAIModel import os api_key = os.getenv("OPENAI_API_KEY", "") if not api_key: raise RuntimeError( "OPENAI_API_KEY must be set for the open-gen-ui Strands agent" ) model = OpenAIModel( client_args={"api_key": api_key}, model_id="gpt-4o", ) return Agent( model=model, system_prompt=OPEN_GEN_UI_SYSTEM_PROMPT, tools=[], )What is this?#
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.
Free course: See this pattern built end-to-end in Build Interactive Agents with Generative UI — a free DeepLearning.AI short course taught by CopilotKit's CEO covering the full Generative UI spectrum (Controlled, Declarative, and Open-Ended).
Key benefits:
- No predefined components — the agent creates any UI it needs, on demand
- Live streaming — HTML streams into a preview as it's generated
- CDN libraries — the generated UI can load Chart.js, D3, Three.js, etc. via
<script>tags - Secure sandboxing — content runs in an isolated iframe without same-origin access
- Sandbox functions — optionally expose host functions to the generated UI for two-way communication
Minimal setup#
Turning on Open Generative UI takes one flag in the runtime plus a plain
<CopilotChat /> on the frontend; the built-in activity renderer is
auto-registered by CopilotKit, so no extra wiring is needed.
Enable it in the runtime#
Add OpenGenerativeUIMiddleware to your runtime configuration:
runtime: new CopilotRuntime({ // @ts-ignore -- see main route.ts agents, openGenerativeUI: { agents: ["open-gen-ui", "open-gen-ui-advanced"], }, }),The OpenGenerativeUIMiddleware then converts the agent's streamed
generateSandboxedUi tool call into open-generative-ui activity events,
which the built-in OpenGenerativeUIActivityRenderer mounts inside a
sandboxed iframe.
Drop <CopilotChat /> into the page#
Wrap your app in CopilotKit and render <CopilotChat> — no extra props needed:
return ( <CopilotKit runtimeUrl="/api/copilotkit-ogui" agent="open-gen-ui" openGenerativeUI={{ designSkill: VISUALIZATION_DESIGN_SKILL }} > <div className="flex justify-center items-center h-screen w-full"> <div className="h-full w-full max-w-4xl flex flex-col p-3"> <Chat /> </div> </div> </CopilotKit> );That's it. Ask the agent "build me a simple greeting card" to see HTML stream into a sandboxed preview live.
Advanced: With app tool calling#
Sandbox functions let the generated UI call back into your host application — a generated settings panel can toggle your app's theme, a product card can push items into your cart, or a data view can ask the host to fetch data the iframe can't reach directly.
"""Strands agent specialization for the open-gen-ui demos (Wave 2).Minimal and Advanced variants share the same core behavior: on every userturn, the agent calls the frontend-registered `generateSandboxedUi` toolexactly once, producing a self-contained sandboxed UI cell.The runtime's `OpenGenerativeUIMiddleware` (enabled via the`openGenerativeUI` option on the CopilotRuntime in`src/app/api/copilotkit-ogui/route.ts`) converts that streaming tool callinto `open-generative-ui` activity events.Since the shared Strands agent dispatches the frontend-registered tool viathe ag_ui_strands proxy, no dedicated Python Agent instance is required.This module documents the system prompt that specializes the shared agentvia `useAgentContext` on the frontend."""OPEN_GEN_UI_SYSTEM_PROMPT = """\You are a UI-generating assistant for an Open Generative UI demo. On everyuser turn you MUST call the `generateSandboxedUi` frontend tool exactly once.Design a visually polished, self-contained HTML + CSS + SVG widget thatteaches the requested concept.The frontend injects a detailed "design skill" as agent context describingthe palette, typography, labelling, and motion conventions expected —follow it closely. Key invariants:- Output ONE call to `generateSandboxedUi` per user turn.- Respect the `initialHeight`, `placeholderMessages`, `css`, `html` contract defined by the design skill.- For the advanced variant, the frontend may register sandbox functions the iframe can call via `Websandbox.connection.remote.<name>(...)`. Use these where it makes the demo visibly exercise the iframe <-> host bridge."""def build_open_gen_ui_agent(): """Build a Strands Agent for the open-gen-ui demos. Not currently wired into agent_server.py; the shared agent handles the `generateSandboxedUi` tool call via ag_ui_strands' frontend-tool proxy. This module's prompt is mirrored by the frontend design skill passed to CopilotKit's `openGenerativeUI.designSkill`. """ from strands import Agent from strands.models.openai import OpenAIModel import os api_key = os.getenv("OPENAI_API_KEY", "") if not api_key: raise RuntimeError( "OPENAI_API_KEY must be set for the open-gen-ui Strands agent" ) model = OpenAIModel( client_args={"api_key": api_key}, model_id="gpt-4o", ) return Agent( model=model, system_prompt=OPEN_GEN_UI_SYSTEM_PROMPT, tools=[], )Runtime is unchanged#
The server-side flag is identical to the minimal cell; the advanced behaviour is a pure frontend addition.
runtime: new CopilotRuntime({ // @ts-ignore -- see main route.ts agents, openGenerativeUI: { agents: ["open-gen-ui", "open-gen-ui-advanced"], }, }),Register sandbox functions on the provider#
Each sandbox function is a Zod-validated, host-side bridge the agent can
invoke from inside the generated iframe via
Websandbox.connection.remote.<name>(args). The handler runs in the host
page and its description is appended to the agent's context, so the agent
knows which bridges are available when generating HTML/JS.
import React from "react";import { CopilotKit, CopilotChat, useConfigureSuggestions,} from "@copilotkit/react-core/v2";import { openGenUiSandboxFunctions } from "./sandbox-functions";import { openGenUiSuggestions } from "./suggestions";export default function OpenGenUiAdvancedDemo() { return ( // Pass the sandbox-function array on the `openGenerativeUI` provider prop. // The built-in `OpenGenerativeUIActivityRenderer` wires these as callable // remotes inside the agent-authored iframe. <CopilotKit runtimeUrl="/api/copilotkit-ogui" agent="open-gen-ui-advanced" openGenerativeUI={{ sandboxFunctions: openGenUiSandboxFunctions }} > <div className="flex justify-center items-center h-screen w-full"> <div className="h-full w-full max-w-4xl"> <Chat /> </div> </div> </CopilotKit>import { z } from "zod";/** * Host-side functions that agent-authored, sandboxed UIs can invoke from * inside the iframe via `Websandbox.connection.remote.<name>(args)`. */export const openGenUiSandboxFunctions = [ { name: "evaluateExpression", description: "Safely evaluate a basic arithmetic expression on the host page and return the numeric result. " + "Supports +, -, *, /, parentheses, and decimal numbers. " + "Use this from inside a calculator or spreadsheet UI.", parameters: z.object({ expression: z .string() .describe("An arithmetic expression, e.g. '12 * (3 + 4.5)'"), }), handler: async ({ expression }: { expression: string }) => { if (!/^[\d+\-*/().\s]+$/.test(expression)) { return { ok: false, error: "Unsupported characters in expression." }; } try { // eslint-disable-next-line no-new-func const value = Function(`"use strict"; return (${expression});`)(); if (typeof value !== "number" || !Number.isFinite(value)) { return { ok: false, error: "Not a finite number." }; } console.log( "[open-gen-ui/advanced] evaluateExpression", expression, "=", value, ); return { ok: true, value }; } catch (err) { return { ok: false, error: err instanceof Error ? err.message : String(err), }; } }, }, { name: "notifyHost", description: "Send a short notification message from the sandboxed UI to the host page. " + "The host logs the message and returns a confirmation object.", parameters: z.object({ message: z.string().describe("A short status message."), }), handler: async ({ message }: { message: string }) => { console.log("[open-gen-ui/advanced] notifyHost:", message); return { ok: true, receivedAt: new Date().toISOString(), message }; }, },];How the sandbox calls you back
Inside the generated UI, the agent writes JS that calls
await Websandbox.connection.remote.notifyHost({ message: "hi" }).
The call is proxied back to the host page, where your handler runs
with the validated args.
Common use cases#
- Theme toggling — generated UI controls your app's appearance
- Cart / state management — product cards push items into host state
- Navigation — generated UI triggers route changes in the host app
- Data fetching — sandbox asks the host to fetch data the iframe can't reach directly
How streaming works#
The agent generates the tool call's parameters in an order optimized for the user experience:
placeholderMessages— shown immediately while generatingcss— all styles first; the preview starts once CSS is completehtml— streams live into the preview as it's generatedjsFunctions— reusable helpers injected before expressionsjsExpressions— executed one by one; the user sees each take effect
The middleware parses the tool-call arguments incrementally and emits activity events as each parameter completes, so the preview updates progressively.
Using CDN libraries#
The sandboxed iframe can load external libraries from CDNs; just include
<script> or <link> tags in the generated HTML <head>. Chart.js, D3,
Three.js, and any other CDN-hosted library work out of the box.
<head>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<canvas id="myChart"></canvas>
</body>