<< All versions

Skill v1.2.0

currentAutomated scan100/100
othmanadi/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_KEY environment variable set
  • Next.js project (App Router recommended)

Quick Start

  1. Install dependencies:
bash
npm install @openuidev/react-ui @openuidev/react-headless @openuidev/react-lang lucide-react zod @anthropic-ai/sdk
  1. Add the CSS import to app/layout.tsx:
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

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-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

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 with openAIAdapter() on the frontend — openAIReadableStreamAdapter() is for NDJSON (no data: 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_KEY is set in .env.local
  • [ ] CSS import present in root layout
  • [ ] Backend converts Anthropic content_block_delta events to OpenAI-compatible SSE chunks
  • [ ] Final chunk has finish_reason: "stop" and ends with data: [DONE]
  • [ ] Frontend uses streamProtocol={openAIAdapter()} and openAIMessageFormat
  • [ ] componentLibrary={openuiChatLibrary} prop passed to FullScreen

Error Patterns

ErrorCauseFix
401 from AnthropicMissing or invalid API keySet ANTHROPIC_API_KEY in .env.local
Stream hangsMissing [DONE] sentinel or controller.close()Ensure final chunk and [DONE] are sent
Garbled outputNot wrapping in data: ... SSE formatEach chunk must be data: {json}\n\n
Components render as textLibrary not passed to FullScreenAdd componentLibrary={openuiChatLibrary} prop
Nothing renders, no errorUsed openAIReadableStreamAdapter() (NDJSON) on SSE stream, or adapter= prop (silently ignored)Use streamProtocol={openAIAdapter()}
max_tokens requiredAnthropic API requires explicit max_tokensAlways set max_tokens (e.g., 4096)
All versions