agentsclimarketplace

Openui forge vercel

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

OpenUI generative UI with Vercel AI SDK. streamText, toUIMessageStreamResponse, and tools support.From its SKILL.md

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.

3 things to look at

  • reads credentialsReads from 2 credential sources: `OPENAI_API_KEY` and 1 more.
  • 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.
  • runs commandsInstructs the agent to run 3 commands, including `npm install @openuidev/react-ui @openuidev/react-lang lucide-react zod ai @ai-sdk/openai @ai-sdk/react` and 2 more.

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 705 authors here whose files we hold, read 2026-09-06

  • Run as non-root userin 34 of 607, across 27 files
  • Use multi-stage buildsin 29 of 607
  • Set resource requests and limitsin 24 of 607, across 20 files
  • Configure liveness and readiness probesin 18 of 607, across 14 files
  • Use named volumes for persistent datain 14 of 607, across 9 files
  • Pin base image versionsin 14 of 607
  • Set up environment variablesin 14 of 607, across 10 files
  • Pin provider versionsin 14 of 607
  • Apply least privilege RBAC permissionsin 10 of 607, across 7 files
  • Create a dockerignore filein 10 of 607
  • Use remote state with lockingin 9 of 607
  • Pin base images by digestin 9 of 607, across 8 files

Said here and by no other author read

  • Add the CSS import to root layout
  • Convert UI messages before passing to model
  • Return result using toUIMessageStreamResponse
  • Drive conversation with useChat
  • Render assistant messages with Renderer

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 325,949. 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.