<< All versions
Backend:
Frontend:
Skill v1.2.0
currentAutomated scan100/100othmanadi/openui-forge/openui-forge-anthropic
──Details
PublishedOctober 3, 2026 at 11:06 AM
Content Hashsha256:600dab399bb71e3d...
Git SHA
──Files
Files (1 file, 5.9 KB)
SKILL.md5.9 KBactive
SKILL.md · 183 lines · 5.9 KB
name: openui-forge-anthropic description: OpenUI generative UI with Anthropic Claude SDK backend. Stream conversion to OpenAI NDJSON format. version: 1.2.0 author: OthmanAdi
OpenUI Forge — Anthropic
Build generative UI apps with OpenUI + Anthropic Claude. Converts Anthropic streaming events to OpenAI-compatible NDJSON.
Activation Triggers
- "openui anthropic", "openui claude", "openui sonnet"
- "generative ui claude", "claude streaming ui"
Prerequisites
- Node.js >= 22 (24 LTS recommended), React >= 18.3.1 (19+ recommended)
ANTHROPIC_API_KEYenvironment variable set- Next.js project (App Router recommended)
Quick Start
- Install dependencies:
bash
npm install @openuidev/react-ui @openuidev/react-headless @openuidev/react-lang lucide-react zod @anthropic-ai/sdk
- Add the CSS import to
app/layout.tsx:
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
The backend streams from Anthropic and converts each event into OpenAI-compatible SSE chunks that openAIAdapter() expects (data: {json}\n\n lines, terminated by data: [DONE]).
typescript
import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib";import Anthropic from "@anthropic-ai/sdk";const client = new Anthropic();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."],});// ANTHROPIC_MODEL alternatives: claude-opus-4-8, claude-haiku-4-5, claude-fable-5const stream = client.messages.stream({model: process.env.ANTHROPIC_MODEL ?? "claude-sonnet-4-6",max_tokens: 4096,system: systemPrompt,messages,});const encoder = new TextEncoder();const readableStream = new ReadableStream({async start(controller) {const id = `chatcmpl-${Date.now()}`;for await (const event of stream) {if (event.type === "content_block_delta" &&event.delta.type === "text_delta") {const chunk = {id,object: "chat.completion.chunk",choices: [{index: 0,delta: { content: event.delta.text },finish_reason: null,},],};controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`));}}const done = {id,object: "chat.completion.chunk",choices: [{ index: 0, delta: {}, finish_reason: "stop" }],};controller.enqueue(encoder.encode(`data: ${JSON.stringify(done)}\n\n`));controller.enqueue(encoder.encode("data: [DONE]\n\n"));controller.close();},});return new Response(readableStream, {headers: { "Content-Type": "text/event-stream" },});}
Frontend: app/chat/page.tsx
tsx
"use client";import { FullScreen } from "@openuidev/react-ui";import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib";import {openAIAdapter,openAIMessageFormat,} from "@openuidev/react-headless";export default function ChatPage() {return (<FullScreencomponentLibrary={openuiChatLibrary}streamProtocol={openAIAdapter()}messageFormat={openAIMessageFormat}apiUrl="/api/chat"/>);}
The backend emits SSE (data: {json}\n\n). Pair it withopenAIAdapter()on the frontend —openAIReadableStreamAdapter()is for NDJSON (nodata:prefix) and will silently produce no output here.
Component Creation
tsx
import { defineComponent } from "@openuidev/react-lang";import { z } from "zod";export const StatusCard = defineComponent({name: "StatusCard",description: "Displays a status with label and color indicator",props: z.object({label: z.string().describe("Status label text"),status: z.enum(["ok", "warning", "error"]).describe("Current status level"),}),component: ({ props }) => {const colors = { ok: "#22c55e", warning: "#eab308", error: "#ef4444" };return (<div style={{ display: "flex", alignItems: "center", gap: 8 }}><span style={{ width: 10, height: 10, borderRadius: "50%", background: colors[props.status] }} /><span>{props.label}</span></div>);},});
System Prompt Generation
bash
npx @openuidev/cli generate ./src/lib/library.ts --out src/generated/system-prompt.txt
Or at runtime: openuiChatLibrary.prompt({ preamble: "...", additionalRules: [...] }).
Validation Checklist
- [ ]
ANTHROPIC_API_KEYis set in.env.local - [ ] CSS import present in root layout
- [ ] Backend converts Anthropic
content_block_deltaevents to OpenAI-compatible SSE chunks - [ ] Final chunk has
finish_reason: "stop"and ends withdata: [DONE] - [ ] Frontend uses
streamProtocol={openAIAdapter()}andopenAIMessageFormat - [ ]
componentLibrary={openuiChatLibrary}prop passed toFullScreen
Error Patterns
| Error | Cause | Fix | |
|---|---|---|---|
| 401 from Anthropic | Missing or invalid API key | Set ANTHROPIC_API_KEY in .env.local | |
| Stream hangs | Missing [DONE] sentinel or controller.close() | Ensure final chunk and [DONE] are sent | |
| Garbled output | Not wrapping in data: ... SSE format | Each chunk must be data: {json}\n\n | |
| Components render as text | Library not passed to FullScreen | Add componentLibrary={openuiChatLibrary} prop | |
| Nothing renders, no error | Used openAIReadableStreamAdapter() (NDJSON) on SSE stream, or adapter= prop (silently ignored) | Use streamProtocol={openAIAdapter()} | |
max_tokens required | Anthropic API requires explicit max_tokens | Always set max_tokens (e.g., 4096) |