agentsclimarketplace

Openui forge vercel

Skill OthmanAdi/openui-forge/skills/openui-forge-vercel

Cross-IDE, multi-stack agent skill for OpenUI (the Open Standard for Generative UI). Adds OpenUI to existing projects across 12 backend stacks, any LLM provider, and 11 agent platforms. Scaffold, integrate, validate.

Install
npx -y skills add OthmanAdi/openui-forge --skill openui-forge-vercel

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 20 stars20 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

OpenUI generative UI with Vercel AI SDK. streamText, toUIMessageStreamResponse, and tools support.

SKILL.md

8.6 KB, ~2.3k tokens by cl100k_base, as published. Nobody here has run it

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_KEY environment variable set
  • Next.js project (App Router)

Quick Start

  1. 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. 2. Add the CSS import to app/layout.tsx:

import "@openuidev/react-ui/components.css";
  1. Create the API route and frontend page below
  2. Run npm run dev and 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 <Renderer> 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 (
    <div>
      {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 <div key={message.id}>{text}</div>;
        }

        // 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 (
          <Renderer
            key={message.id}
            response={response}
            library={openuiChatLibrary}
            isStreaming={isLoading && isLast}
            onAction={handleAction}
          />
        );
      })}

      <form
        onSubmit={(e) => {
          e.preventDefault();
          handleSend(input);
        }}
      >
        <input value={input} onChange={(e) => setInput(e.target.value)} />
        <button type="submit" disabled={isLoading}>Send</button>
      </form>
    </div>
  );
}

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 }) => (
    <div style={{ padding: 16, borderRadius: 12, background: "#f0f9ff" }}>
      <h3>{props.city}</h3>
      <div style={{ fontSize: 32 }}>{props.temp}C</div>
      <div>{props.condition}</div>
    </div>
  ),
});

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_KEY is set in .env.local
  • ai, @ai-sdk/openai, and @ai-sdk/react packages installed (v6 line: ai@^6, @ai-sdk/openai@^3, @ai-sdk/react@^3)
  • Route converts UI messages with convertToModelMessages(messages) and passes messages: modelMessages to streamText
  • Route uses streamText and returns result.toUIMessageStreamResponse()
  • Frontend drives the chat with useChat from @ai-sdk/react (messages, sendMessage, status)
  • Each assistant message is rendered with <Renderer response={...} library={openuiChatLibrary} isStreaming={...} onAction={...} /> from @openuidev/react-lang
  • Renderer prop is library={openuiChatLibrary} (NOT componentLibrary)
  • CSS import in root layout
  • If using tools: stopWhen: stepCountIs(n) is set (AI SDK v6 replacement for the removed maxSteps), tool results feed back to model
  • Tools declared with inputSchema: (v6 rename of parameters:)

Error Patterns

ErrorCauseFix
ai module not foundMissing Vercel AI SDKnpm install ai @ai-sdk/openai @ai-sdk/react
useChat is not exported / not foundImporting the hook from aiImport useChat from @ai-sdk/react and install @ai-sdk/react@^3
Empty / mismatched model messagesPassing raw UI messages straight to streamTextconst modelMessages = await convertToModelMessages(messages), then pass messages: modelMessages
Type error on maxStepsmaxSteps removed in AI SDK v6Import stepCountIs from ai and use stopWhen: stepCountIs(3)
Type error on tool parametersRenamed to inputSchema in AI SDK v6Rename parameters: to inputSchema: in every tool({...}) definition
Blank responseWrong export from @ai-sdk/openaiUse openai("gpt-5.5") not new OpenAI()
Generative UI does not rendercomponentLibrary prop passed to Renderer, or rendering message.content instead of joined text partsUse library={openuiChatLibrary} and pass the joined text parts as response={...}

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Gives 0 of the 12 instructions most containers cloud skills give in ~2.3k tokens

Counted across 607 of the 657 authors here whose files we hold, read 2026-08-07

  • Run containers as a non-root userin 66 of 607, across 46 files
  • Use multi-stage buildsin 53 of 607, across 44 files
  • Use Promise.all for independent operationsin 47 of 607, across 13 files
  • Import directly instead of barrel filesin 46 of 607, across 12 files
  • Use ternary instead of AND for conditionalsin 45 of 607, across 12 files
  • Use Set or Map for O(1) lookupsin 42 of 607, across 10 files
  • Create a .dockerignore filein 41 of 607, across 31 files
  • Read individual rule files for detailsin 39 of 607, across 9 files
  • Copy dependency files before source codein 36 of 607, across 23 files
  • Authenticate server actions like API routesin 35 of 607, across 7 files
  • Use next/dynamic for heavy componentsin 34 of 607, across 9 files
  • Use React.cache for per-request deduplicationin 34 of 607, across 10 files

Said here and by no other author read

  • install ai sdk v6 packages
  • import component css in root layout
  • import usechat from ai-sdk react
  • render assistant text parts with renderer
  • pass library prop instead of componentlibrary
  • use stopwhen stepcountis instead of maxsteps

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.

Keep looking

Skills are one crate of 327,132. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.