はじめに
HyperGenを5分で動かす。2つのファイルをコピーし、SSEエンドポイントを設定し、iframeをマウントして、初めてのAI生成UIをストリーミング。
はじめに
HyperGenは、AIエージェントがHTMX属性付きのHTMLフラグメントをServer-Sent Events (SSE) 経由でサンドボックス化されたiframeにストリーミングすることで、インタラクティブなユーザーインターフェースを生成できるオープンプロトコルです。インストールするSDKも、学ぶべきJSONスキーマも、フレームワークへのロックインもありません。HTML文字列を出力できるシステムなら何でもHyperGenを使えます。
前提条件
- ランタイム: Node.js 20+、Deno 2+、またはBun 1+
- HTTPフレームワーク: 標準
ResponseAPIをサポートするもの(Hono、Express、Next.js Route Handlers、Deno.serveなど) - フロントエンド: 任意のフレームワーク(React、Vue、Svelte)またはバニラHTML/JS
ステップ 1: ドロップインファイルをコピー
HyperGenはコードオーナーシップモデルに従います。ソースファイルをプロジェクトにコピーして、自分のものにします。npm installは不要です。
packages/dropin/src/からこれらのファイルをコピーしてください:
| ファイル | 配置場所 | 目的 |
|---|---|---|
hypergen-server.ts | サーバー | SSEストリーミング、ブートストラップHTML生成 |
hypergen-client.ts | フロントエンド | iframeマウント、テーマ注入、ライフサイクル |
hypergen-theme.ts | どちらでも(任意) | CSS変数のTypeScript型定義 |
両ファイルとも外部依存関係ゼロ — 標準Web APIのみを使用します。
ステップ 2: サーバーをセットアップ
2つのHTTPエンドポイントが必要です:
- ブートストラップエンドポイント — iframe内に配置されるHTMLドキュメントを提供
- SSEエンドポイント — iframeにHTMLフラグメントをストリーミング
// server.ts
import { bootstrapHtml, createSSEStream, HyperGenResponse } from "./hypergen-server";
// iframeブートストラップドキュメントを提供
app.get("/bootstrap", () => {
return new Response(
bootstrapHtml({ sseEndpoint: "/api/stream" }),
{ headers: { "Content-Type": "text/html" } },
);
});
// SSE経由でHTMLフラグメントをストリーミング
app.get("/api/stream", () => {
const stream = createSSEStream(async function* () {
yield "<h1>Hello from HyperGen!</h1>";
yield "<p>This HTML was streamed via SSE.</p>";
});
return HyperGenResponse(stream);
});bootstrapHtml()は、CDNからHTMXを読み込み、SSE拡張を接続し、ホスト通信用のpostMessageブリッジと自動サイズ調整用のResizeObserverを含む完全なHTMLドキュメントを生成します。
createSSEStream()は、HTML文字列の非同期ジェネレーターを受け取り、適切なSSEフォーマットとキープアライブpingを含むReadableStreamを返します。
HyperGenResponse()は、ストリームを正しいSSEヘッダー付きのResponseでラップします。
ステップ 3: フロントエンドにiframeをマウント
// client.ts
import { mountHyperGen } from "./hypergen-client";
const controller = mountHyperGen(
document.getElementById("agent-ui")!,
{ bootstrapUrl: "/bootstrap" },
);
// オプション: アプリのテーマを注入
controller.setTheme({
"--hg-accent": "#7c3aed",
"--hg-surface": "#ffffff",
});
// オプション: コンテンツサイズの変更に反応
controller.onResize((height) => {
console.log("Content height:", height);
});mountHyperGen()は、sandbox="allow-scripts allow-same-origin allow-forms"のサンドボックス化された<iframe>を作成し、ブートストラップドキュメントを読み込み、iframeのライフサイクルを管理するHyperGenControllerを返します。
ステップ 4: エージェントからHTMLを生成
サーバーサイドの非同期ジェネレーターが、エージェントがUIを生成する場所です。各yieldは、HTMXがiframeにレンダリングするHTMLフラグメントを含む1つのSSEイベントを送信します。
app.get("/api/stream", () => {
const stream = createSSEStream(async function* () {
// AIエージェントを呼び出す
const response = await agent.chat("Show me a dashboard");
// 生成されたHTMLをストリーミング
yield response.html;
// または構造化データからHTMLを構築
yield `
<div style="padding: 16px; background: var(--hg-surface-elevated); border-radius: var(--hg-radius);">
<h2 style="color: var(--hg-text);">Dashboard</h2>
<button
hx-post="/api/action"
hx-vals='{"action": "refresh"}'
hx-target="closest div"
hx-swap="outerHTML"
style="background: var(--hg-accent); color: var(--hg-accent-fg); border: none; padding: 8px 16px; border-radius: var(--hg-radius-sm); cursor: pointer;">
Refresh
</button>
</div>
`;
});
return HyperGenResponse(stream);
});
// HTMXから送信されるユーザーインタラクションを処理
app.post("/api/action", async (c) => {
const body = await c.req.parseBody();
// HTMXがスワップする新しいHTMLを返す
return c.html("<div>Updated at " + new Date().toLocaleTimeString() + "</div>");
});重要なポイント: エージェントの出力がそのままUIです。JSON変換もコンポーネントカタログの参照もありません — HTMX属性によるインタラクティビティと--hg-* CSS変数によるテーマ設定が施されたHTMLだけです。
完全な最小限の例
Honoを使った完全に動作する例です(任意のフレームワークに置き換え可能):
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { bootstrapHtml, createSSEStream, HyperGenResponse } from "./hypergen-server";
const app = new Hono();
app.get("/", (c) => c.html(`<!DOCTYPE html>
<html><head><title>HyperGen Demo</title></head>
<body>
<h1>My App</h1>
<div id="agent-ui"></div>
<script type="module">
import { mountHyperGen } from "/hypergen-client.js";
mountHyperGen(document.getElementById("agent-ui"), {
bootstrapUrl: "/bootstrap",
});
</script>
</body></html>`));
app.get("/bootstrap", (c) => c.html(
bootstrapHtml({ sseEndpoint: "/api/stream", swapStrategy: "beforeend" })
));
app.get("/api/stream", () => HyperGenResponse(
createSSEStream(async function* () {
yield '<p style="color: var(--hg-text);">Loading dashboard...</p>';
await new Promise((r) => setTimeout(r, 1000));
yield `<div style="background: var(--hg-surface-elevated); padding: 16px; border-radius: var(--hg-radius); border: 1px solid var(--hg-border);">
<h2>Welcome</h2>
<p>This UI was streamed by an AI agent.</p>
<button hx-post="/api/action" hx-swap="outerHTML"
style="background: var(--hg-accent); color: var(--hg-accent-fg); border: none; padding: 8px 16px; border-radius: var(--hg-radius-sm); cursor: pointer;">
Click me
</button>
</div>`;
})
));
app.post("/api/action", (c) => c.html(
'<div style="color: var(--hg-accent); font-weight: bold;">Button clicked!</div>'
));
serve({ fetch: app.fetch, port: 3000 });次のステップ
- サーバーセットアップガイド —
bootstrapHtml()、createSSEStream()、フレームワーク固有のパターンの詳細 - クライアントセットアップガイド — iframeマウント、React/Vue/Svelte統合、リサイズ処理
- テーマ設定ガイド — CSS変数、ライト/ダークテーマ、動的切り替え
- エージェント統合ガイド — Claude、OpenAI、Ollama、カスタムエージェントの接続
- 仕様 — 正式なプロトコル定義