From b7cfb4e1209c775333b2083ccde3ce43537e08a3 Mon Sep 17 00:00:00 2001
From: Visharad Kashyap <154831195+vishxrad@users.noreply.github.com>
Date: Thu, 16 Jul 2026 14:52:35 +0530
Subject: [PATCH 1/2] docs(skill): add OpenUI Cloud integration runbooks
---
skills/openui/SKILL.md | 31 +-
skills/openui/references/cloud-integration.md | 416 ++++++++++++++++++
skills/openui/references/cloud-migration.md | 119 +++++
3 files changed, 565 insertions(+), 1 deletion(-)
create mode 100644 skills/openui/references/cloud-integration.md
create mode 100644 skills/openui/references/cloud-migration.md
diff --git a/skills/openui/SKILL.md b/skills/openui/SKILL.md
index 2cb921558..ab91dd460 100644
--- a/skills/openui/SKILL.md
+++ b/skills/openui/SKILL.md
@@ -1,6 +1,6 @@
---
name: openui
-description: "Use for building, debugging, integrating, or documenting OpenUI, OpenUI Lang, Agent Interface, OpenUI Cloud, @openuidev packages, streaming generative UI rendering, component libraries, and migrations from JSON UI formats."
+description: "Use for building, debugging, integrating, migrating, or documenting OpenUI, OpenUI Lang, Agent Interface, OpenUI Cloud, @openuidev packages, streaming generative UI rendering, component libraries, existing-project Cloud integration, self-hosted-to-Cloud migration, and migrations from JSON UI formats."
---
# OpenUI
@@ -46,6 +46,20 @@ Choose the package for the target runtime. For backend-only parsing or prompt/sc
- If the user wants OpenUI Lang rendering in an existing React project without the full React UI surface, use `@openuidev/react-lang`.
- If the host app is Vue or Svelte, use `@openuidev/vue-lang` or `@openuidev/svelte-lang`. Use `@openuidev/lang-core` for framework-agnostic parsing, prompt generation, schemas, or backend/runtime work.
+## Route Cloud Integration and Migration Tasks
+
+Inspect the target project's framework and router, package manifest and lockfile, server runtime, authentication, existing OpenUI imports, chat transport, storage, component library, tools, and artifacts. Preserve its package manager, route conventions, auth boundary, design system, and working behavior.
+
+Choose the matching path:
+
+| Starting point and goal | Required runbook |
+| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Existing React app, add managed Cloud chat | Read [references/cloud-integration.md](references/cloud-integration.md) completely before editing |
+| Existing non-React app, add managed Cloud chat | Read [references/cloud-integration.md](references/cloud-integration.md); require a current first-party client/runtime or report the verified React-only boundary |
+| Existing self-hosted/open-source app, replace or supplement it with Cloud | Read both [references/cloud-migration.md](references/cloud-migration.md) and [references/cloud-integration.md](references/cloud-integration.md) completely before editing |
+
+If “migrate” does not establish whether Cloud should replace the self-hosted path or run beside it, infer the intent from the project and request. Ask only when the choice remains material and ambiguous; never silently delete a working backend. Treat code migration and historical-data import as separate tasks, and do not claim a data migration without a verified first-party import API.
+
## Common Workflows
### Scaffold
@@ -85,6 +99,16 @@ Version-sensitive: verify exact Cloud template env vars, `@openuidev/thesys*` ex
- Cloud-provided component sets, artifact renderers, and categories come from `@openuidev/thesys`.
- Generate keys in the Thesys console: `https://console.thesys.dev/keys`.
+For existing-project Cloud work, keep these invariants intact:
+
+- Keep the two Cloud planes separate: `ChatLLM` posts to the app's `/api/chat` proxy, while `useOpenuiCloudStorage()` accesses Cloud storage with a short-lived token minted by `/api/frontend-token`.
+- Send only the latest message with `openAIConversationMessageFormat.toApi(messages.slice(-1))`; Cloud replays history from `conversation: threadId`. Pair that format with `openAIResponsesAdapter()`.
+- Derive the frontend token's `user_id` from authenticated server state in production. Authenticate and rate-limit both routes independently, treat `threadId` as untrusted, and authorize it through a verified host mapping or documented Cloud membership check for the installed version. Do not assume the installed SDK exports an ownership helper.
+- Do not deploy a demo identity unchanged. Replace it with host authentication, rate limiting, and conversation authorization; disable both routes and report the blocker until those controls exist.
+- In Next.js, isolate `@openuidev/thesys` imports in a client component and follow the installed first-party template's dynamic-rendering boundary. If the production build still evaluates browser-only dependencies during prerender, add a small `dynamic(..., { ssr: false })` client loader.
+- Preserve abort propagation and close the SSE stream when the upstream stream ends.
+- Do not invent a Cloud history-import API, custom-tool execution loop, or custom-library instruction API. Verify current first-party support and preserve the self-hosted path when a required capability is unsupported.
+
### Wire Agent Interface
Use `AgentInterface` from `@openuidev/react-ui` for the full chat surface. It owns the layout, sidebar, thread list, composer, routing, and workspace rail. Configure the backend through two independent channels:
@@ -143,6 +167,7 @@ const llm: ChatLLM = {
- Version-sensitive: when adding React UI to an existing React app, inspect installed `@openuidev/*` peer ranges and package-manager errors; add direct peers only when they are missing or incompatible.
- Next.js App Router: render `Renderer` or `AgentInterface` from a client component; add `"use client"` at the top of the file that imports or renders them.
+- Next.js with OpenUI Cloud: keep Cloud imports in a separate client module, retain the existing server page/layout for host authentication and product shell concerns, and verify the installed template's dynamic-rendering pattern with a production build.
- Vite or strict TypeScript: before side-effect CSS imports, ensure the app has `/// ` or a declaration such as `declare module "*.css";`.
- Import React UI CSS once, normally `@openuidev/react-ui/components.css` plus `@openuidev/react-ui/styles/index.css`; use `@openuidev/react-ui/layered/styles/index.css` when the app needs cascade-layered overrides.
- Examples/docs may import adapters from `@openuidev/react-headless`; React UI apps can also import those adapters from `@openuidev/react-ui` because it re-exports headless APIs.
@@ -336,6 +361,10 @@ Version-sensitive: verify renderer props against installed exports; there is no
- Run the host app's TypeScript/build checks after existing-app integrations, especially when adding React UI CSS imports or Next client components.
- Validate canned OpenUI Lang with `createParser(...).parse(...)` and inspect `result.meta.errors`; do not look for top-level `result.errors`.
- Treat parse/runtime errors surfaced through `Renderer` `onError` or parser results as LLM-correctable feedback: unknown components, missing required props, excess positional args, inline `Query`/`Mutation`, runtime errors, or unresolved refs should be fed back into the next model turn.
+- For Cloud, confirm the server key never appears in client code, the client sends only the latest message, the adapter/format pair matches, and the frontend token uses a scoped authenticated identity.
+- Test invalid request bodies and provider-item injection, missing configuration, upstream failures, abort handling, and stream closure without a real key when possible.
+- Verify logged-out requests cannot use either Cloud route and one authenticated user cannot address another user's conversation id.
+- With an authorized test key, smoke-test streaming, reload persistence, user isolation, and one managed report or presentation artifact.
- Vite large chunk warnings from default React UI/chat libraries are not automatically failures; chart/UI dependencies can be substantial.
- For scoped agent tests, keep caches/stores inside the assigned workspace when needed, for example `npm_config_cache=$PWD/.npm-cache npm install` or `pnpm install --store-dir .pnpm-store`.
diff --git a/skills/openui/references/cloud-integration.md b/skills/openui/references/cloud-integration.md
new file mode 100644
index 000000000..6865fbc22
--- /dev/null
+++ b/skills/openui/references/cloud-integration.md
@@ -0,0 +1,416 @@
+# Integrate OpenUI Cloud into an Existing Project
+
+Use this runbook to add the stock OpenUI Cloud Agent Interface to an existing React application. Use the host application's framework, package manager, route conventions, authentication, layout, and design system. Verify version-sensitive details against the installed packages, generated templates, and current first-party OpenUI sources before editing.
+
+## Contents
+
+1. [Supported Contract](#supported-contract)
+2. [Audit the Host](#audit-the-host)
+3. [Install and Configure](#install-and-configure)
+4. [Wire the Client](#wire-the-client)
+5. [Authorize Cloud Conversations](#authorize-cloud-conversations)
+6. [Add the Generation Proxy](#add-the-generation-proxy)
+7. [Add the Frontend Token Route](#add-the-frontend-token-route)
+8. [Adapt Beyond Next.js](#adapt-beyond-nextjs)
+9. [Verify](#verify)
+
+## Supported Contract
+
+The verified happy path provides:
+
+- React `AgentInterface` backed by OpenUI Cloud.
+- Cloud conversation and artifact persistence.
+- The managed `chatLibrary` component set.
+- Managed report and presentation artifacts.
+- A server-side Responses proxy and a server-side frontend-token mint.
+
+Do not imply that a browser-only app can safely integrate Cloud: it needs a trusted server boundary. Treat custom Cloud tool execution, historical data import, and generation with a custom component library as separate capabilities that require current first-party support. Do not assume the installed SDK exports a conversation-ownership helper; a production generation proxy needs the explicit ownership design below.
+
+## Audit the Host
+
+Before editing:
+
+1. Detect the package manager and installed React/Next versions.
+2. Find the correct client route or component where chat belongs; do not replace the application root unless requested.
+3. Find the server route convention and runtime. Prefer a Node-compatible runtime for the OpenAI SDK recipe.
+4. Identify the host's server-side authentication API and stable end-user id.
+5. Identify how the host can authorize a Cloud `threadId` for that user. Use a host-owned mapping only when the app has a trusted way to create or bind the Cloud conversation id. Use a token-scoped Cloud membership check only when its exact endpoint, header, and pagination contract are documented for the installed version. If neither path is supported, leave generation disabled in production and report the blocker.
+6. Inventory existing chat state, rate limits, routes, CSP/proxy rules, styles, themes, tools, and artifact behavior.
+7. Inspect installed `@openuidev/*` declarations when they differ from the repository template.
+
+## Install and Configure
+
+Add only missing packages using the host package manager. The canonical Cloud template uses:
+
+```text
+@openuidev/lang-core
+@openuidev/react-headless
+@openuidev/react-lang
+@openuidev/react-ui
+@openuidev/thesys
+@openuidev/thesys-server
+openai
+zod
+zustand@^4.5.5
+```
+
+`@openuidev/react-ui` re-exports headless APIs, but keep `@openuidev/react-headless` installed when required as a peer dependency. Import the re-exported APIs from `@openuidev/react-ui` in React UI applications.
+
+Configure server-only environment values:
+
+```bash
+THESYS_API_KEY=sk-th-your-key
+OPENUI_MODEL=google/gemini-3.1-pro-free
+```
+
+Use a current supported `provider/model` value for `OPENUI_MODEL`; prefer the generated template or console over a stale hardcoded list. A scaffold may also use `DEMO_USER_ID=demo-user`, but production must derive the user id from authenticated server state.
+
+## Wire the Client
+
+In Next.js, keep the existing page/layout as a server component so it can retain
+server-side authentication and the product shell. Keep the Cloud UI plus all
+`@openuidev/thesys` imports in a separate client component. Follow the dynamic
+rendering boundary used by the installed first-party template. The current App
+Router template marks the server page as dynamic:
+
+```tsx
+// app/assistant/page.tsx
+import "@openuidev/react-ui/components.css";
+import "@openuidev/thesys/styles.css";
+import CloudAgent from "./cloud-agent";
+
+export const dynamic = "force-dynamic";
+
+export default function AssistantPage() {
+ return ;
+}
+```
+
+Apply the host's normal server auth guard on that page when its parent layout
+does not already enforce authentication. If the installed Cloud client still
+fails during the production prerender, add a small client loader that imports
+`cloud-agent.tsx` with `dynamic(..., { ssr: false })`; do not impose that fallback
+without reproducing the need. Import each stylesheet once in a location allowed
+by the host framework.
+
+```tsx
+"use client";
+
+import {
+ AgentInterface,
+ defineArtifactCategories,
+ openAIConversationMessageFormat,
+ openAIResponsesAdapter,
+ type ChatLLM,
+} from "@openuidev/react-ui";
+import {
+ chatLibrary,
+ presentationArtifactRenderer,
+ reportArtifactRenderer,
+ useOpenuiCloudStorage,
+} from "@openuidev/thesys";
+
+const artifacts = defineArtifactCategories([
+ { name: "Presentations", renderers: [presentationArtifactRenderer] },
+ { name: "Reports", renderers: [reportArtifactRenderer] },
+]);
+
+const llm: ChatLLM = {
+ send: ({ threadId, messages, signal }) =>
+ fetch("/api/chat", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ threadId,
+ input: openAIConversationMessageFormat.toApi(messages.slice(-1)),
+ }),
+ signal,
+ }),
+ streamProtocol: openAIResponsesAdapter(),
+};
+
+export default function CloudAgent() {
+ const storage = useOpenuiCloudStorage({
+ token: "/api/frontend-token",
+ apiBaseUrl: "https://api.thesys.dev",
+ features: { artifact: true },
+ });
+
+ return (
+
+ );
+}
+```
+
+Preserve the host's routing, slots, theme, logo, starters, and layout. The Cloud-specific changes are the `llm`, `storage`, component library, artifact props, and Cloud stylesheet.
+
+## Authorize Cloud Conversations
+
+The frontend token scopes browser storage calls to a `user_id`, but `/api/chat`
+uses the server key. Possession of a `threadId` is therefore not sufficient
+authorization for the generation proxy.
+
+Use one of these designs:
+
+1. **Host-owned mapping:** only when the app has a trusted conversation-creation
+ or binding path, transactionally store `{ conversationId, ownerUserId }` in
+ the host database. Browser-created ids are not trusted bindings by
+ themselves. This is the preferred constant-time check at scale.
+2. **Verified Cloud membership check:** only when the installed package,
+ generated template, or current first-party documentation exposes one,
+ authenticate the request and verify `threadId` through a token scoped to that
+ exact user. Copy the documented endpoint, auth header, response shape, and
+ pagination behavior instead of inferring them. Cache a positive result in a
+ host-owned mapping when appropriate.
+
+Do not add a browser endpoint that merely “claims” a supplied id; without an
+independent Cloud check, a malicious user could claim another user's id. The
+frontend-token mint needed by `useOpenuiCloudStorage()` is separate from this
+ownership decision:
+
+```ts
+// lib/openui-cloud.server.ts
+type FrontendToken = { token: string; expires_at: number };
+
+export async function mintCloudFrontendToken(userId: string): Promise {
+ const apiKey = process.env.THESYS_API_KEY;
+ if (!apiKey) throw new Error("THESYS_API_KEY is not configured");
+
+ const response = await fetch("https://api.thesys.dev/v1/frontend-tokens", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${apiKey}`,
+ },
+ body: JSON.stringify({ user_id: userId }),
+ cache: "no-store",
+ });
+
+ if (!response.ok) {
+ throw new Error(`Cloud frontend-token mint failed with ${response.status}`);
+ }
+ return (await response.json()) as FrontendToken;
+}
+```
+
+Minting a token for a user does not itself prove that an arbitrary `threadId`
+belongs to that user. If the stock browser storage path cannot populate a host
+mapping and no documented membership lookup exists, do not emit placeholder
+authorization or call the production integration complete.
+
+## Add the Generation Proxy
+
+For Next.js App Router, add or adapt `app/api/chat/route.ts`. API routes do not
+inherit page/layout auth: authenticate and rate-limit this route independently.
+Validate input, authorize the untrusted `threadId` for the authenticated user,
+keep the key server-side, forward the abort signal, and return Responses SSE
+without converting protocols.
+
+Create a server-only `parseCloudChatRequest(req)` helper before enabling the
+route. Require JSON, accept a bounded `threadId`, allow exactly one bounded text item with
+`{ type: "message", role: "user" }`, and reconstruct the provider item from
+those allowlisted fields. Do not forward the browser's `ResponseInputItem[]`
+verbatim: it could contain system, developer, function-call, or oversized input.
+If the product supports images or files, extend the allowlist and size limits
+deliberately instead of weakening it to the entire Responses union.
+
+Configure the host framework's request-body byte limit before JSON parsing. If
+the framework cannot enforce one, read the request stream with an explicit cap.
+Validate the bounded JSON with the host's schema library, then reconstruct this
+allowlisted shape rather than returning the parsed browser object directly:
+
+```ts
+type CloudChatRequest = {
+ threadId: string;
+ input: [{ type: "message"; role: "user"; content: string }];
+};
+```
+
+Reject extra root and message fields. If the existing UI sends attachments or
+other content parts, preserve its old path until the installed Cloud contract is
+verified and the allowlist is extended deliberately. If the UI exposes a model
+selector, accept only ids from a server-maintained allowlist; never pass an
+arbitrary browser-supplied model to Cloud.
+
+The route below is a structural baseline, not a standalone drop-in.
+`getAuthenticatedUserId`, `parseCloudChatRequest`, and
+`userOwnsCloudConversation` are host-supplied boundaries, not OpenUI exports.
+Implement and test all three before enabling the route; if the host cannot
+provide trusted conversation ownership, stop at the production blocker instead
+of weakening the check.
+
+```ts
+import { getAuthenticatedUserId } from "@/lib/auth.server";
+import { parseCloudChatRequest } from "@/lib/cloud-request";
+import { userOwnsCloudConversation } from "@/lib/conversation-ownership.server";
+import { artifactTool, createResponsesInstructions } from "@openuidev/thesys-server";
+import OpenAI from "openai";
+
+export async function POST(req: Request) {
+ const userId = await getAuthenticatedUserId(req);
+ if (!userId) return Response.json({ error: { message: "Unauthorized" } }, { status: 401 });
+
+ const request = await parseCloudChatRequest(req);
+ if (!request.ok) {
+ return Response.json({ error: { message: request.message } }, { status: request.status });
+ }
+ const { threadId, input } = request.value;
+
+ let authorized: boolean;
+ try {
+ authorized = await userOwnsCloudConversation(userId, threadId);
+ } catch (error) {
+ console.error("[api/chat] Unable to verify Cloud conversation ownership", error);
+ return Response.json(
+ { error: { message: "Unable to verify conversation access" } },
+ { status: 503 },
+ );
+ }
+ if (!authorized) {
+ return Response.json({ error: { message: "Forbidden" } }, { status: 403 });
+ }
+
+ const apiKey = process.env.THESYS_API_KEY;
+ if (!apiKey) {
+ return Response.json(
+ { error: { message: "THESYS_API_KEY is not configured" } },
+ { status: 500 },
+ );
+ }
+
+ const client = new OpenAI({
+ baseURL: "https://api.thesys.dev/v1/embed",
+ apiKey,
+ });
+
+ let stream: AsyncIterable>;
+ try {
+ stream = (await client.responses.create(
+ {
+ model: process.env.OPENUI_MODEL || "google/gemini-3.1-pro-free",
+ conversation: threadId,
+ input,
+ stream: true,
+ store: true,
+ tools: [artifactTool({ artifacts: ["slides", "report"] })],
+ instructions: createResponsesInstructions(),
+ // The Cloud artifact tool extends the stock OpenAI Responses tool union.
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ } as any,
+ { signal: req.signal },
+ )) as unknown as AsyncIterable>;
+ } catch (error) {
+ const upstream = error as { status?: number; error?: unknown; message?: string };
+ console.error("[api/chat] Cloud request failed", upstream.status, upstream.message);
+ return Response.json(
+ { error: { message: "Cloud request failed" } },
+ { status: upstream.status === 429 ? 429 : 502 },
+ );
+ }
+
+ const encoder = new TextEncoder();
+ const body = new ReadableStream({
+ async start(controller) {
+ try {
+ for await (const event of stream) {
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
+ }
+ } catch (error) {
+ const message = error instanceof Error ? error.message : String(error);
+ console.error("[api/chat] Cloud stream failed", message);
+ controller.enqueue(
+ encoder.encode(
+ `data: ${JSON.stringify({ type: "error", message: "Cloud stream failed" })}\n\n`,
+ ),
+ );
+ } finally {
+ controller.close();
+ }
+ },
+ });
+
+ return new Response(body, {
+ headers: {
+ "Content-Type": "text/event-stream",
+ "Cache-Control": "no-cache, no-transform",
+ Connection: "keep-alive",
+ },
+ });
+}
+```
+
+Adapt the `getAuthenticatedUserId` import to the application's real server auth
+API and apply its normal rate limiter. A host-owned ownership lookup may replace
+the sample `userOwnsCloudConversation` boundary; alternatively implement a
+documented Cloud membership lookup for the installed version. Copy or adapt the
+bounded request parser rather than replacing it with `req.json()` plus a type assertion. Keep
+the compatibility cast scoped to this request object. Use the exact cast required
+by the installed OpenAI SDK and `@openuidev/thesys-server` versions; do not weaken
+unrelated types across the host application.
+
+## Add the Frontend Token Route
+
+The route must mint a token for the authenticated user and use the host's normal
+rate limiter. API routes do not inherit page/layout auth. Never accept the
+authoritative `user_id` from an untrusted request body.
+
+```ts
+import { getAuthenticatedUserId } from "@/lib/auth.server";
+import { mintCloudFrontendToken } from "@/lib/openui-cloud.server";
+
+export async function POST(req: Request) {
+ const userId = await getAuthenticatedUserId(req);
+ if (!userId) return Response.json({ error: { message: "Unauthorized" } }, { status: 401 });
+
+ let frontendToken: { token: string; expires_at: number };
+ try {
+ frontendToken = await mintCloudFrontendToken(userId);
+ } catch (error) {
+ console.error("[frontend-token] Cloud rejected token mint", error);
+ return Response.json({ error: { message: "Unable to mint frontend token" } }, { status: 502 });
+ }
+
+ return Response.json(frontendToken, { headers: { "Cache-Control": "private, no-store" } });
+}
+```
+
+Adapt `getAuthenticatedUserId` to the project's real server-side session lookup.
+The token route and generation-route ownership check must derive exactly the
+same stable user id. Treat a scaffold's `DEMO_USER_ID` as local-demo identity
+only; replace it with real authentication, authorization, and rate limiting
+before enabling either route in production.
+
+## Adapt Beyond Next.js
+
+Keep the same contracts in other server frameworks:
+
+- Client `ChatLLM.send` posts `{ threadId, input }` to the host server.
+- The generation endpoint validates input, calls `POST https://api.thesys.dev/v1/embed/responses` or the equivalent OpenAI SDK base URL, and streams SSE unchanged.
+- The token endpoint resolves the user from trusted server auth, calls `POST https://api.thesys.dev/v1/frontend-tokens`, and returns `{ token, expires_at }`.
+- The generation endpoint verifies the untrusted conversation id through a host mapping or a documented Cloud membership contract for the installed version.
+- The browser never receives `THESYS_API_KEY`.
+- If the host uses a restrictive CSP, allow `https://api.thesys.dev` in
+ `connect-src` for the browser-to-Cloud storage plane.
+
+The managed client packages are React packages. Do not promise a Vue, Svelte, React Native, or browser-only Cloud client without a current first-party example or package contract.
+
+## Verify
+
+1. Run formatter, typecheck, tests, and a production build.
+2. In Next.js, match the installed first-party template's dynamic-rendering
+ boundary and run the actual production bundler. Add `ssr: false` only if the
+ installed Cloud client otherwise fails during prerender.
+3. Confirm `THESYS_API_KEY` appears only in server code and environment documentation.
+4. Confirm `messages.slice(-1)`, `conversation: threadId`, `stream: true`, and `store: true` remain intact.
+5. Confirm `openAIResponsesAdapter()` is paired with `openAIConversationMessageFormat`.
+6. Test invalid JSON/content type, request and message size limits, non-user/provider input injection, missing configuration, token-mint failure, Cloud 4xx/5xx, abort, and stream close.
+7. Verify logged-out requests receive `401` from both routes and rate limits apply.
+8. Verify ownership-check failures return a service error rather than accidentally authorizing or misreporting them as `403`.
+9. Verify a user cannot proxy generation into another user's `threadId`, then confirm two authenticated users receive isolated thread lists.
+10. With an authorized test key, stream a reply, reload the page to verify persistence, then create and open one report or presentation.
diff --git a/skills/openui/references/cloud-migration.md b/skills/openui/references/cloud-migration.md
new file mode 100644
index 000000000..14a4e4b1f
--- /dev/null
+++ b/skills/openui/references/cloud-migration.md
@@ -0,0 +1,119 @@
+# Migrate Self-Hosted OpenUI to OpenUI Cloud
+
+Use this runbook for application-code migration from open-source/self-hosted OpenUI to the managed Cloud backend. Read [cloud-integration.md](cloud-integration.md) as well; it contains the canonical Cloud client and server contracts.
+
+## Contents
+
+1. [Define the Migration](#define-the-migration)
+2. [Classify the Existing App](#classify-the-existing-app)
+3. [Apply the Migration Map](#apply-the-migration-map)
+4. [Migrate AgentInterface Apps](#migrate-agentinterface-apps)
+5. [Handle Renderer-Only and Custom-Library Apps](#handle-renderer-only-and-custom-library-apps)
+6. [Handle Dual Mode](#handle-dual-mode)
+7. [Respect Unsupported Boundaries](#respect-unsupported-boundaries)
+8. [Verify](#verify)
+
+## Define the Migration
+
+Distinguish these goals before removing code:
+
+- **Replace:** Cloud becomes the only generation and storage backend.
+- **Dual mode:** Keep self-hosted behavior and add Cloud as a separately configured backend.
+- **Code migration:** Rewire the application to Cloud and start with Cloud-owned conversations.
+- **Data migration:** Import historical threads or artifacts into Cloud.
+
+If the request says only “migrate to Cloud,” default to code migration and preserve historical data in place. Do not claim data migration unless a current first-party import API is documented and verified.
+
+## Classify the Existing App
+
+Inventory the project before editing:
+
+| Shape | Typical signals | Migration character |
+| ---------------------------- | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
+| Self-hosted `AgentInterface` | `fetchLLM`, `ChatLLM`, `restStorage`, `openuiLibrary` or `openuiChatLibrary` | Mostly mechanical adapter/storage/library swap |
+| Legacy flat-prop chat | `apiUrl`, `threadApiUrl`, `processMessage`, old renderer names | First migrate to current `AgentInterface`, then migrate backend |
+| Renderer-only GenUI | `Renderer`, `library.prompt()`, app-owned messages and model stream | Architectural change; not a backend-only swap |
+| Custom component library | `defineComponent`, `createLibrary`, custom prompt options | Rendering may be reusable; Cloud generation instructions require verification |
+| Tool-heavy agent | provider tool loop or AG-UI tool events | Preserve self-hosted until the Cloud custom-tool loop is documented |
+
+Record the current provider, model selector, message wire format, attachments,
+storage ownership, user identity, route behavior, tools, component library,
+artifacts, custom slots/theme, and tests.
+
+## Apply the Migration Map
+
+| Self-hosted/open-source | OpenUI Cloud |
+| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
+| Provider API key and provider route | Server-only `THESYS_API_KEY` and Cloud Responses proxy |
+| Full message history sent per turn | Latest message only plus `conversation: threadId` |
+| `openAIReadableStreamAdapter()` or `openAIAdapter()` | `openAIResponsesAdapter()` |
+| `openAIMessageFormat` | `openAIConversationMessageFormat` |
+| In-memory, `restStorage`, or custom `ChatStorage` | `useOpenuiCloudStorage({ token: "/api/frontend-token" })` |
+| `openuiLibrary`/`openuiChatLibrary` | `chatLibrary` from `@openuidev/thesys` for the stock Cloud path |
+| `library.prompt(...)` in the provider route | `createResponsesInstructions()` in the Cloud route |
+| App-owned artifact loop/renderers | Managed `artifactTool({ artifacts: ["slides", "report"] })` plus managed renderers for stock artifacts |
+| No browser storage credential | Short-lived frontend token scoped to the authenticated `user_id` |
+
+Preserve branding, theme, starters, slots, navigation, route placement, error boundaries, analytics, and authentication unless the user requests a redesign.
+
+## Migrate AgentInterface Apps
+
+1. Read the integration runbook and add the missing Cloud packages and server-only configuration.
+2. Replace the client `llm` with a direct `ChatLLM` that sends only the latest formatted message and parses Responses SSE.
+3. In Next.js, move the Cloud UI into a separate client component and match the
+ installed first-party template's dynamic-rendering boundary. Add an
+ `ssr: false` client loader only when the production build requires it.
+4. Replace self-hosted storage with `useOpenuiCloudStorage()` and add the frontend-token route.
+5. Replace the stock open-source component library with `chatLibrary` and register the managed report/presentation renderers.
+6. Replace the provider `/api/chat` implementation with the Cloud proxy while preserving independent API authentication, conversation authorization, rate limiting, request validation, abort propagation, error handling, and the route URL expected by the client.
+7. Keep the previous provider/storage code until the Cloud path builds and passes tests. Remove it only for an explicitly confirmed replacement migration.
+8. Remove provider dependencies, environment variables, storage routes, and dead adapters only when no other application path uses them.
+9. Update environment examples and deployment configuration without committing secrets.
+
+Do not send both full history and a Cloud conversation id. Doing so can duplicate context. Do not leave `library.prompt()` in the stock Cloud route; `createResponsesInstructions()` supplies the managed instructions.
+
+## Handle Renderer-Only and Custom-Library Apps
+
+A Renderer-only app owns its messages, model stream, and layout. OpenUI Cloud's verified managed path is `AgentInterface` plus Cloud storage, so migration is not a drop-in provider URL change.
+
+For a stock Cloud migration:
+
+1. Introduce `AgentInterface` at the requested route or surface.
+2. Move reusable branding and surrounding layout into `AgentInterface` props/slots.
+3. Add the two Cloud server routes and managed library/artifacts from the integration runbook.
+4. Retain the old Renderer surface until behavior parity is verified; then remove it only for replacement migrations.
+
+For a custom component library, separate two questions:
+
+- **Can the client render it?** `AgentInterface` accepts a `componentLibrary` prop.
+- **Will Cloud generation receive matching component instructions?** Verify the installed `@openuidev/thesys-server` API and current first-party docs. Client-side composition alone does not prove that the managed model knows the custom signatures.
+
+If there is no verified Cloud instruction-composition API, do not pretend the custom library migrated. Keep that generation path self-hosted, migrate only the stock Cloud surface, or report the unsupported boundary.
+
+## Handle Dual Mode
+
+Keep each backend internally consistent. Define two complete configurations rather than mixing adapters and storage:
+
+```text
+selfHosted = full-history transport + provider route + self-hosted storage + OSS library prompt
+cloud = latest-message Responses transport + Cloud proxy + Cloud storage + Cloud instructions
+```
+
+Select the mode on the server or through trusted deployment configuration. Do not expose secrets or allow an untrusted browser value to choose arbitrary upstream credentials. Namespace storage and routes when necessary to avoid thread-id collisions.
+
+## Respect Unsupported Boundaries
+
+- **Historical conversations/artifacts:** no import path is established by the repository sources. Preserve the old store read-only or export it separately; do not fabricate Cloud records.
+- **Custom tool execution:** declaring a function tool is not the same as executing it. The app must catch the streamed tool call, execute it, and submit the result through the supported Responses continuation flow. Keep the self-hosted loop unless current Cloud docs provide the full contract.
+- **Custom artifact-producing tools:** managed `artifactTool()` covers the documented report and slide path. Do not infer support for arbitrary custom artifacts.
+- **Attachments and media:** preserve an attachment-capable self-hosted path until the installed Cloud client, generation input, storage, and size-limit contracts are verified end to end.
+- **Non-React clients:** the verified managed client surface is React. Require a first-party runtime/example before promising another framework.
+
+## Verify
+
+1. Run formatter, typecheck, tests, and production build.
+2. Search for stale adapter/format pairs, full-history Cloud sends, browser-exposed keys, and obsolete provider routes.
+3. Exercise both modes independently when dual mode is retained.
+4. Verify existing self-hosted data remains accessible or deliberately archived; do not describe it as imported without evidence.
+5. With an authorized test key, verify streaming, persistence after reload, user isolation, and a managed artifact.
+6. Compare user-visible behavior—theme, starters, navigation, tools, and custom components—and explicitly list any capability intentionally left self-hosted.
From 047beb74a9b77e651045e10718c4249ce0ebecd8 Mon Sep 17 00:00:00 2001
From: Visharad Kashyap <154831195+vishxrad@users.noreply.github.com>
Date: Thu, 16 Jul 2026 15:50:09 +0530
Subject: [PATCH 2/2] docs(skill): address OSS migration review
---
skills/openui/SKILL.md | 2 +-
...ud-migration.md => oss-to-cloud-migration.md} | 16 +++++++++-------
2 files changed, 10 insertions(+), 8 deletions(-)
rename skills/openui/references/{cloud-migration.md => oss-to-cloud-migration.md} (87%)
diff --git a/skills/openui/SKILL.md b/skills/openui/SKILL.md
index ab91dd460..d99b20442 100644
--- a/skills/openui/SKILL.md
+++ b/skills/openui/SKILL.md
@@ -56,7 +56,7 @@ Choose the matching path:
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Existing React app, add managed Cloud chat | Read [references/cloud-integration.md](references/cloud-integration.md) completely before editing |
| Existing non-React app, add managed Cloud chat | Read [references/cloud-integration.md](references/cloud-integration.md); require a current first-party client/runtime or report the verified React-only boundary |
-| Existing self-hosted/open-source app, replace or supplement it with Cloud | Read both [references/cloud-migration.md](references/cloud-migration.md) and [references/cloud-integration.md](references/cloud-integration.md) completely before editing |
+| Existing self-hosted/open-source app, replace or supplement it with Cloud | Read both [references/oss-to-cloud-migration.md](references/oss-to-cloud-migration.md) and [references/cloud-integration.md](references/cloud-integration.md) completely before editing |
If “migrate” does not establish whether Cloud should replace the self-hosted path or run beside it, infer the intent from the project and request. Ask only when the choice remains material and ambiguous; never silently delete a working backend. Treat code migration and historical-data import as separate tasks, and do not claim a data migration without a verified first-party import API.
diff --git a/skills/openui/references/cloud-migration.md b/skills/openui/references/oss-to-cloud-migration.md
similarity index 87%
rename from skills/openui/references/cloud-migration.md
rename to skills/openui/references/oss-to-cloud-migration.md
index 14a4e4b1f..97a3c9a80 100644
--- a/skills/openui/references/cloud-migration.md
+++ b/skills/openui/references/oss-to-cloud-migration.md
@@ -1,6 +1,6 @@
-# Migrate Self-Hosted OpenUI to OpenUI Cloud
+# OSS to Cloud Migration
-Use this runbook for application-code migration from open-source/self-hosted OpenUI to the managed Cloud backend. Read [cloud-integration.md](cloud-integration.md) as well; it contains the canonical Cloud client and server contracts.
+Use this runbook for application-code migration from OSS (open-source or self-hosted) OpenUI to the managed Cloud backend. Read [cloud-integration.md](cloud-integration.md) as well; it contains the canonical Cloud client and server contracts.
## Contents
@@ -18,12 +18,14 @@ Use this runbook for application-code migration from open-source/self-hosted Ope
Distinguish these goals before removing code:
- **Replace:** Cloud becomes the only generation and storage backend.
-- **Dual mode:** Keep self-hosted behavior and add Cloud as a separately configured backend.
+- **Dual mode:** Keep the OSS path beside Cloud when a required capability lacks verified Cloud parity, or when the rollout needs a reversible comparison before replacement.
- **Code migration:** Rewire the application to Cloud and start with Cloud-owned conversations.
- **Data migration:** Import historical threads or artifacts into Cloud.
If the request says only “migrate to Cloud,” default to code migration and preserve historical data in place. Do not claim data migration unless a current first-party import API is documented and verified.
+If the app uses OSS components, ask whether the user wants to keep them or switch to Cloud's `chatLibrary`. Treat backend, storage, and component-library migration as separate choices; moving to Cloud does not automatically authorize replacing the visible component set.
+
## Classify the Existing App
Inventory the project before editing:
@@ -49,7 +51,7 @@ artifacts, custom slots/theme, and tests.
| `openAIReadableStreamAdapter()` or `openAIAdapter()` | `openAIResponsesAdapter()` |
| `openAIMessageFormat` | `openAIConversationMessageFormat` |
| In-memory, `restStorage`, or custom `ChatStorage` | `useOpenuiCloudStorage({ token: "/api/frontend-token" })` |
-| `openuiLibrary`/`openuiChatLibrary` | `chatLibrary` from `@openuidev/thesys` for the stock Cloud path |
+| `openuiLibrary`/`openuiChatLibrary` | `chatLibrary` from `@openuidev/thesys` when the user chooses Cloud components |
| `library.prompt(...)` in the provider route | `createResponsesInstructions()` in the Cloud route |
| App-owned artifact loop/renderers | Managed `artifactTool({ artifacts: ["slides", "report"] })` plus managed renderers for stock artifacts |
| No browser storage credential | Short-lived frontend token scoped to the authenticated `user_id` |
@@ -58,13 +60,13 @@ Preserve branding, theme, starters, slots, navigation, route placement, error bo
## Migrate AgentInterface Apps
-1. Read the integration runbook and add the missing Cloud packages and server-only configuration.
+1. Read [cloud-integration.md](cloud-integration.md) and add the missing Cloud packages and server-only configuration.
2. Replace the client `llm` with a direct `ChatLLM` that sends only the latest formatted message and parses Responses SSE.
3. In Next.js, move the Cloud UI into a separate client component and match the
installed first-party template's dynamic-rendering boundary. Add an
`ssr: false` client loader only when the production build requires it.
4. Replace self-hosted storage with `useOpenuiCloudStorage()` and add the frontend-token route.
-5. Replace the stock open-source component library with `chatLibrary` and register the managed report/presentation renderers.
+5. If the user chose Cloud components, replace the stock OSS library with `chatLibrary` and register the managed report/presentation renderers. If they chose OSS components, keep them and verify that managed generation receives compatible component instructions; otherwise retain that generation path in OSS or dual mode.
6. Replace the provider `/api/chat` implementation with the Cloud proxy while preserving independent API authentication, conversation authorization, rate limiting, request validation, abort propagation, error handling, and the route URL expected by the client.
7. Keep the previous provider/storage code until the Cloud path builds and passes tests. Remove it only for an explicitly confirmed replacement migration.
8. Remove provider dependencies, environment variables, storage routes, and dead adapters only when no other application path uses them.
@@ -80,7 +82,7 @@ For a stock Cloud migration:
1. Introduce `AgentInterface` at the requested route or surface.
2. Move reusable branding and surrounding layout into `AgentInterface` props/slots.
-3. Add the two Cloud server routes and managed library/artifacts from the integration runbook.
+3. Add the two Cloud server routes and managed library/artifacts from [cloud-integration.md](cloud-integration.md).
4. Retain the old Renderer surface until behavior parity is verified; then remove it only for replacement migrations.
For a custom component library, separate two questions: