Install
$ agentstack add skill-othmanadi-openui-forge-openui-forge-vercel ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
OpenUI Forge — Vercel AI SDK
Build generative UI apps with OpenUI + Vercel AI SDK. Native streaming with streamText and toUIMessageStreamResponse().
Activation Triggers
- "openui vercel", "openui vercel ai", "openui ai sdk"
- "generative ui vercel", "vercel ai streaming ui"
- "useChat openui", "streamText openui"
Prerequisites
- Node.js >= 22 (24 LTS recommended), React >= 18.3.1 (19+ recommended)
OPENAI_API_KEYenvironment variable set- Next.js project (App Router)
Quick Start
- Install dependencies:
npm install @openuidev/react-ui @openuidev/react-lang lucide-react zod ai @ai-sdk/openai @ai-sdk/react
Pin to the AI SDK v6 line: ai@^6, @ai-sdk/openai@^3, @ai-sdk/react@^3.
- Add the CSS import to
app/layout.tsx:
import "@openuidev/react-ui/components.css";
- Create the API route and frontend page below
- Run
npm run devand test
Full Code
Backend: app/api/chat/route.ts
import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib";
import { convertToModelMessages, streamText } from "ai";
import { openai } from "@ai-sdk/openai";
export async function POST(req: Request) {
const { messages } = await req.json();
const systemPrompt = openuiChatLibrary.prompt({
preamble: "You are a helpful assistant that generates interactive UIs.",
additionalRules: ["Always use Stack as root when combining multiple components."],
});
// AI SDK v6: convert the UI message stream into model messages before passing to the model.
const modelMessages = await convertToModelMessages(messages);
const result = streamText({
model: openai(process.env.OPENAI_MODEL ?? "gpt-5.5"),
system: systemPrompt,
messages: modelMessages,
});
return result.toUIMessageStreamResponse();
}
Backend with Tools: app/api/chat/route.ts
import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib";
import { convertToModelMessages, streamText, tool, stepCountIs } from "ai";
import { openai } from "@ai-sdk/openai";
import { z } from "zod";
export async function POST(req: Request) {
const { messages } = await req.json();
const systemPrompt = openuiChatLibrary.prompt({
preamble: "You are a helpful assistant that generates interactive UIs. Use tools to fetch data before rendering.",
});
// AI SDK v6: convert the UI message stream into model messages before passing to the model.
const modelMessages = await convertToModelMessages(messages);
const result = streamText({
model: openai(process.env.OPENAI_MODEL ?? "gpt-5.5"),
system: systemPrompt,
messages: modelMessages,
tools: {
getWeather: tool({
description: "Get current weather for a city",
inputSchema: z.object({
city: z.string().describe("City name"),
}),
execute: async ({ city }) => {
return { city, temp: 22, condition: "sunny" };
},
}),
},
// AI SDK v6: stopWhen replaces the removed `maxSteps` option.
stopWhen: stepCountIs(3),
});
return result.toUIMessageStreamResponse();
}
Frontend (useChat + Renderer): app/chat/page.tsx
Drive the conversation with useChat from @ai-sdk/react, then render each assistant message with a per-message ` from @openuidev/react-lang. The Renderer takes the assistant text as response, the component library as library (NOT componentLibrary), an isStreaming flag for the in-flight message, and an onAction` handler for built-in actions like continuing the conversation.
"use client";
import { useChat } from "@ai-sdk/react";
import { Renderer, BuiltinActionType } from "@openuidev/react-lang";
import type { ActionEvent } from "@openuidev/react-lang";
import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib";
import { useState } from "react";
export default function ChatPage() {
const [input, setInput] = useState("");
const { messages, sendMessage, status } = useChat();
const isLoading = status === "submitted" || status === "streaming";
const handleSend = (text: string) => {
const trimmed = text.trim();
if (!trimmed || isLoading) return;
setInput("");
sendMessage({ text: trimmed });
};
const handleAction = (event: ActionEvent) => {
if (event.type === BuiltinActionType.ContinueConversation && event.humanFriendlyMessage) {
handleSend(event.humanFriendlyMessage);
}
};
return (
{messages.map((message, i) => {
const isLast = i === messages.length - 1;
if (message.role === "user") {
const text = message.parts
.filter((p): p is { type: "text"; text: string } => p.type === "text")
.map((p) => p.text)
.join("");
return {text};
}
// assistant: render generative UI from the text parts
const response = message.parts
.filter((p): p is { type: "text"; text: string } => p.type === "text")
.map((p) => p.text)
.join("");
return (
);
})}
{
e.preventDefault();
handleSend(input);
}}
>
setInput(e.target.value)} />
Send
);
}
Component Creation
import { defineComponent } from "@openuidev/react-lang";
import { z } from "zod";
export const WeatherCard = defineComponent({
name: "WeatherCard",
description: "Displays weather information for a location",
props: z.object({
city: z.string().describe("City name"),
temp: z.number().describe("Temperature in Celsius"),
condition: z.enum(["sunny", "cloudy", "rainy", "snowy"]).describe("Weather condition"),
}),
component: ({ props }) => (
{props.city}
{props.temp}C
{props.condition}
),
});
System Prompt Generation
npx @openuidev/cli generate ./src/lib/library.ts --out src/generated/system-prompt.txt
Or at runtime via openuiChatLibrary.prompt() as shown in the route.
Validation Checklist
- [ ]
OPENAI_API_KEYis set in.env.local - [ ]
ai,@ai-sdk/openai, and@ai-sdk/reactpackages installed (v6 line:ai@^6,@ai-sdk/openai@^3,@ai-sdk/react@^3) - [ ] Route converts UI messages with
convertToModelMessages(messages)and passesmessages: modelMessagestostreamText - [ ] Route uses
streamTextand returnsresult.toUIMessageStreamResponse() - [ ] Frontend drives the chat with
useChatfrom@ai-sdk/react(messages,sendMessage,status) - [ ] Each assistant message is rendered with `
from@openuidev/react-lang` - [ ] Renderer prop is
library={openuiChatLibrary}(NOTcomponentLibrary) - [ ] CSS import in root layout
- [ ] If using tools:
stopWhen: stepCountIs(n)is set (AI SDK v6 replacement for the removedmaxSteps), tool results feed back to model - [ ] Tools declared with
inputSchema:(v6 rename ofparameters:)
Error Patterns
| Error | Cause | Fix | |-------|-------|-----| | ai module not found | Missing Vercel AI SDK | npm install ai @ai-sdk/openai @ai-sdk/react | | useChat is not exported / not found | Importing the hook from ai | Import useChat from @ai-sdk/react and install @ai-sdk/react@^3 | | Empty / mismatched model messages | Passing raw UI messages straight to streamText | const modelMessages = await convertToModelMessages(messages), then pass messages: modelMessages | | Type error on maxSteps | maxSteps removed in AI SDK v6 | Import stepCountIs from ai and use stopWhen: stepCountIs(3) | | Type error on tool parameters | Renamed to inputSchema in AI SDK v6 | Rename parameters: to inputSchema: in every tool({...}) definition | | Blank response | Wrong export from @ai-sdk/openai | Use openai("gpt-5.5") not new OpenAI() | | Generative UI does not render | componentLibrary prop passed to Renderer, or rendering message.content instead of joined text parts | Use library={openuiChatLibrary} and pass the joined text parts as response={...} |
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: OthmanAdi
- Source: OthmanAdi/openui-forge
- License: MIT
- Homepage: https://spruce-prism-8yya.here.now/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.