Openui forge anthropic
Skill OthmanAdi/openui-forge/.codex/skills/openui-forge-anthropic
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.
npx -y skills add OthmanAdi/openui-forge --skill openui-forge-anthropicAssembled 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 Anthropic Claude SDK backend. Stream conversion to OpenAI NDJSON format.
SKILL.md
5.9 KB, as published. Nobody here has run it
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:
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:
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]).
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-5
const 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
"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 (
<FullScreen
componentLibrary={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
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
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) |