# Autofix API > Fix invalid OpenUI Lang from any model with one API call. Source: https://www.openui.com/docs/autofix The Autofix API repairs invalid OpenUI Lang after your model has generated it. Keep your existing model provider, inference setup, and agent framework. Send the conversation with the generation as the last assistant turn, and the API returns the repair result. You do not need to generate through Gateway's Chat Completions or Responses API. Use Autofix for correction after generation, or [Gateway](/docs/gateway) for model access with in-built correction. **Endpoint:** `POST https://api.thesys.dev/v1/autofix` **OpenAI compatible.** The request and the answer use the OpenAI chat completion shape. Point an OpenAI SDK at the base URL `https://api.thesys.dev/v1/autofix` and it posts to `/v1/autofix/chat/completions`. Both paths take the same body and answer the same way. ## Use the server package [#use-the-server-package] Import `createAutofix` from `@openuidev/server/openai` for Chat Completions or `@openuidev/server/vercel` for the Vercel AI SDK. Use the spec from `openui generate --spec` for the same library as your renderer. Keep the API key on the server. OpenAI Vercel AI SDK Non-streaming ```ts title="lib/autofix.ts" import { createAutofix } from "@openuidev/server/openai"; import library from "./openui.spec.json"; export const autofix = createAutofix({ apiKey: process.env.THESYS_API_KEY!, library, }); ``` ```ts title="app/api/chat/route.ts" import OpenAI from "openai"; import { autofix } from "../../../lib/autofix"; const model = new OpenAI(); export async function POST(request: Request) { const { messages } = await request.json(); const source = await model.chat.completions.create( { model: "gpt-5.5", messages, stream: true }, { signal: request.signal }, ); return autofix.completions .stream({ stream: source, messages, signal: request.signal }) .toResponse(); } ``` ```ts title="lib/autofix.ts" import { createAutofix } from "@openuidev/server/vercel"; import library from "./openui.spec.json"; export const autofix = createAutofix({ apiKey: process.env.THESYS_API_KEY!, library, }); ``` ```ts title="app/api/chat/route.ts" import { streamText, toUIMessageStream } from "ai"; import { autofix } from "../../../lib/autofix"; export async function POST(request: Request) { const { messages } = await request.json(); const result = streamText({ model: "openai/gpt-5.5", system: systemPrompt, // Generated from the same OpenUI component library. messages, abortSignal: request.signal, }); return autofix.ai .stream({ stream: toUIMessageStream({ stream: result.stream }), signal: request.signal, }) .toResponse(); } ``` ```ts title="lib/autofix.ts" import { createAutofix } from "@openuidev/server/openai"; // import { createAutofix } from "@openuidev/server/vercel"; import library from "./openui.spec.json"; export const autofix = createAutofix({ apiKey: process.env.THESYS_API_KEY!, library, }); ``` ```ts title="server.ts" import { autofix } from "./lib/autofix"; const result = await autofix.completions.fix({ generation, messages, signal }); // const result = await autofix.ai.fix({ generation, messages, signal }); if (result.status === "fix_failed") { console.warn(result.unfixedErrors); } else { // Render or persist result.content. } ``` OpenAI: return SSE for the frontend `openAIAdapter()`. Vercel: pass `toUIMessageStream({ stream: result.stream })`. `toResponse()` is the UI message SSE protocol used by `useChat`. Non-streaming: `generation` is the completed OpenUI text. Optional `messages` is the conversation before that generation. Use `completions.fix` or `ai.fix` to match the import. ### Consume the stream [#consume-the-stream] Use either `chunks` or `toResponse()` once. `result` settles after that consumer finishes. | Member | Purpose | | -------------- | ------------------------------------------------------------------------ | | `chunks` | Native SDK events, including any correction. | | `toResponse()` | SSE `Response` for the matching frontend. | | `result` | Settled Autofix result. Persist `content` when present, not joined text. | `result` is `null` when Autofix did not run. A failed repair throws with `code: "fix_failed"`. Use `fix()` on the same helper when you already have completed text. ## Call the API directly [#call-the-api-directly] Parse first, and only call Autofix when the generation is invalid. ```ts title="lib/detect.ts" import type { ParseResult } from "@openuidev/lang-core"; export function isValid({ root, meta }: ParseResult): boolean { return ( !meta.incomplete && meta.errors.length === 0 && meta.unresolved.length === 0 && meta.orphaned.length === 0 && root !== null ); } ``` Judge the generation once, after the stream ends. While it streams, `incomplete` and `unresolved` are normal: the parser has not seen the rest yet. ```ts title="server.ts" import { createStreamingParser } from "@openuidev/lang-core"; import OpenAI from "openai"; import { autofix } from "./lib/autofix"; import { isValid } from "./lib/detect"; import library from "./openui.spec.json"; const model = new OpenAI(); // any provider that returns OpenUI Lang export async function generate(messages: OpenAI.ChatCompletionMessageParam[]) { const parser = createStreamingParser(library.schema, library.root); let generation = ""; let result = parser.getResult(); const stream = await model.chat.completions.create({ model: "gpt-5.5", messages, stream: true }); for await (const chunk of stream) { const delta = chunk.choices[0]?.delta?.content ?? ""; if (!delta) continue; generation += delta; result = parser.push(delta); send(delta); // the browser renders as the text arrives } if (isValid(result)) return; const completion = await autofix(messages, generation); if (completion.fix_summary.status === "fix_failed") { sendFallback(completion.fix_summary.unfixed_errors); } else { replace(completion.choices[0].message.content); // the whole valid generation } } ``` `send`, `replace`, and `sendFallback` stand for your own transport to the browser: forward a delta, swap the rendered generation for the fixed one, and show a fallback. `parse` and `push` never throw. They take fenced or bare OpenUI Lang and report prop errors in `meta.errors`, with the codes listed under [Error codes](#error-codes). For a generation you already hold in full, `createParser(library.schema, library.root).parse(generation)` gives the same result. ## Fix an invalid generation [#fix-an-invalid-generation] Point the OpenAI client at `/v1/autofix`. Your component library goes first, as a system turn written by `generateSystemPrompt({ cloud: true, library })`; the generation goes last, as an assistant turn. Fenced or bare OpenUI Lang both work, and text outside the fence is ignored. The API adds `fix_summary` to the chat completion, which the OpenAI types do not know about, so the example casts the request and the answer. ```ts title="lib/autofix.ts" import { generateSystemPrompt } from "@openuidev/lang-core"; import OpenAI from "openai"; import type { ChatCompletion } from "openai/resources/chat/completions"; import library from "./openui.spec.json"; const client = new OpenAI({ apiKey: process.env.THESYS_API_KEY, baseURL: "https://api.thesys.dev/v1/autofix", }); // The library, in the form the API reads it: a system turn carrying the spec. const libraryTurn = { role: "system", content: generateSystemPrompt({ cloud: true, library }), } as const; type FixError = { code: string; message: string; statementId?: string }; type FixSummary = { status: "already_valid" | "fixed" | "fix_failed"; fixed_errors: FixError[]; unfixed_errors: FixError[]; }; type AutofixCompletion = ChatCompletion & { fix_summary: FixSummary }; export async function autofix(messages: OpenAI.ChatCompletionMessageParam[], generation: string) { return (await client.chat.completions.create({ model: "openui/autofix", messages: [libraryTurn, ...messages, { role: "assistant", content: generation }], } as OpenAI.ChatCompletionCreateParamsNonStreaming)) as AutofixCompletion; } ``` `openui.spec.json` is the library spec that `openui generate --spec` writes for your components. See [Generate OpenUI Lang](/docs/gateway/generate-openui-lang) for how it is created. `generateSystemPrompt({ cloud: true, library })` turns it into a two-line `]]>openui:config` block, the same one the Gateway reads; the API reads the library from the first message only, and counts no config block as conversation. The API always needs the library: a generation is only valid against the components your app renders, so there is no built-in default. `model` is accepted so that an SDK client has something to send, and it is ignored: the fix always runs on the same correction model. ### Without an SDK [#without-an-sdk] The plain path takes the same body. Server calls use the API key described in [Authentication](/docs/gateway/authentication). ```ts title="lib/autofix-fetch.ts" import { generateSystemPrompt } from "@openuidev/lang-core"; import library from "./openui.spec.json"; type Message = { role: string; content: string | unknown[] }; const libraryTurn: Message = { role: "system", content: generateSystemPrompt({ cloud: true, library }), }; export async function autofix(messages: Message[], generation: string) { const response = await fetch("https://api.thesys.dev/v1/autofix", { method: "POST", headers: { Authorization: `Bearer ${process.env.THESYS_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ messages: [libraryTurn, ...messages, { role: "assistant", content: generation }], }), }); if (!response.ok) { throw new Error(`Autofix failed: ${response.status}`); } return response.json(); } ``` ## Request [#request] | Field | Type | Required | Purpose | | ---------- | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `messages` | array | Yes | The conversation, in the OpenAI chat message shape. The first message is a `system` turn that carries your library as the `]]>openui:config` block that `generateSystemPrompt({ cloud: true, library })` writes; it is read as the library, not as conversation. The last turn must be an `assistant` turn whose text is the generation to fix, up to 100,000 characters. The turns in between are the context: only the text of `user`, `assistant`, `system`, and `developer` turns is read, and tool turns and non-text parts are dropped. | | `model` | string | No | Accepted so that an SDK client can name a model, and ignored. The answer always reports `openui/autofix`. | | `stream` | bool | No | Streaming is not supported. A fix has nothing to send until it is finished, so `stream: true` is a `400`. | The other OpenAI fields, such as `temperature` and `tools`, are ignored. ## Read the result [#read-the-result] The answer is a chat completion. `choices[0].message.content` holds the complete generation, not a patch: replace the model output with it and render it. `fix_summary.status` says what happened. | `status` | `content` | Errors | Charged | | --------------- | ------------------------ | ---------------------------------------------------- | ------- | | `already_valid` | The input, unchanged | `fixed_errors` is empty | No | | `fixed` | The corrected generation | `fixed_errors` lists what was wrong and is now fixed | Yes | | `fix_failed` | `null` | `unfixed_errors` lists what is still wrong | Yes | `fixed_errors` and `unfixed_errors` are always present, and one of them is usually empty. A fixed generation: ```json { "id": "fix_P4TPtYimJFMpSgsViggie", "object": "chat.completion", "created": 1789019848, "model": "openui/autofix", "choices": [ { "index": 0, "finish_reason": "stop", "message": { "role": "assistant", "content": "root = Card([header])\nheader = Header(\"Q3 Results\")" } } ], "usage": { "prompt_tokens": 8014, "completion_tokens": 17, "total_tokens": 8031 }, "fix_summary": { "status": "fixed", "fixed_errors": [ { "code": "unresolved", "statementId": "followUp", "message": "reference \"followUp\" is never defined" } ], "unfixed_errors": [] } } ``` A generation that could not be fixed: ```json { "id": "fix_dRvfleg31F5g-Amn3v4-W", "object": "chat.completion", "created": 1789026253, "model": "openui/autofix", "choices": [ { "index": 0, "finish_reason": "stop", "message": { "role": "assistant", "content": null } } ], "usage": { "prompt_tokens": 360, "completion_tokens": 146, "total_tokens": 506 }, "fix_summary": { "status": "fix_failed", "fixed_errors": [], "unfixed_errors": [ { "code": "null-required", "component": "B", "path": "/child", "statementId": "z", "message": "required field \"/child\" cannot be null" } ] } } ``` When the fix fails, fall back to what your application does for any unusable model output, such as showing the text or a generic error state. ## Error codes [#error-codes] Each entry in `fixed_errors` or `unfixed_errors` names one problem. `code` is always present. `component`, `path`, and `statementId` are present when the validator can name the component type, the property, and the statement that carried the problem. | Code | Meaning | | ------------------- | ---------------------------------------------------------------------------------- | | `unknown-component` | A component name that is not in the library. | | `missing-required` | A required property was left out. | | `null-required` | A required property was set to `null`. | | `type-mismatch` | A property has the wrong type, or a value outside its allowed set. | | `excess-args` | A component received more arguments than its signature has. | | `inline-reserved` | `Query()` or `Mutation()` was used inside an expression instead of as a statement. | | `incomplete` | The generation stopped in the middle of a statement. | | `unresolved` | A statement is referenced but never defined. | | `orphaned` | A statement is defined but not reachable from `root`. | | `missing-root` | The generation has no valid `root` statement. | These are the same codes the OpenUI SDK reports in the browser, so an application can handle both with one path. See [Reliability Monitoring](/docs/reliability) for how they appear in the Thesys Console. ## Framing [#framing] The generation may be bare OpenUI Lang, a fenced code block, or an assistant turn as the OpenUI SDK stores it after the user has touched a form inside it: `]]>openui:content`, the program, `]]>openui:context` with the form state, `]]>openui:end`. Only the program inside is checked and repaired. A fixed generation comes back in the same wrapping it arrived in, header line, context section and end marker unchanged, so the SDK reads the answer the way it reads history. ## Limits [#limits] * The generation can be up to 100,000 characters. * The turns before it can hold up to 20 usable turns and 8,000 characters of text in total, counted after tool turns, non-text parts, and config blocks are removed. * Requests exceeding either context limit are rejected with `400`. Trim older history before sending; the API does not drop it automatically. * Up to two attempts are made to fix one generation. * Only non-streaming responses are supported. `stream: true` is a `400`. ## Data [#data] The conversation you send is passed to the model that fixes the generation, and is not stored beyond the request logs. ## Pricing [#pricing] Each call that runs a fix is charged a flat price from your credits. A generation that is already valid is free. See [Pricing](/docs/gateway/pricing-credits) for the current rate. `usage` in the answer reports the tokens the fix used, for your own tracking. The charge does not depend on it. ## Errors [#errors] The endpoint returns the same error shape as the Chat Completions and Responses APIs. | Status | Type | When | | ------ | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `400` | `invalid_request_error` | The first message is not a library turn, or its block is malformed; the last turn is not an assistant turn with a generation; a limit is passed; or `stream` is `true`. | | `401` | `authentication_error` | The API key is missing or invalid. | | `429` | `rate_limit_error` | The organization has no credits, or billing is suspended. | | `500` | `internal_server_error` | The fixing model could not be reached. Nothing is charged. | ## Reliability Monitoring [#reliability-monitoring] Autofix requests automatically appear in the [Reliability Monitoring](/docs/reliability) under the model name `openui/autofix`. **No Observability SDK** installation is required to track these requests. A successful fix counts as a sanitizer correction, with the same error codes as corrections made during Gateway generation. # Deploy your app > Deploy an OpenUI app to Vercel with the OpenUI CLI. Source: https://www.openui.com/docs/deploy Deploy a working OpenUI app from your project folder: ```bash npx @openuidev/cli@latest deploy ``` The command defaults to Vercel as the deployment platform. The CLI will prompt you to authenticate and connect the project to your Vercel organization and suitable project. ## Before you start [#before-you-start] * Have a working OpenUI web app. * Run your app's build command and try a prompt locally. * Have a Vercel account and the API keys your app needs. Your app must be compatible with Vercel. If it uses a separately hosted backend, deploy that backend separately and configure a reachable URL. ## Deploy [#deploy] Run the command from the folder containing your app's `package.json`, or pass the folder explicitly: ```bash npx @openuidev/cli@latest deploy ./my-app ``` On first use, follow the prompts to sign in and select a Vercel team and project. Later runs reuse the project link. To choose the deployment environment explicitly: ```bash # Preview npx @openuidev/cli@latest deploy --target preview # Production npx @openuidev/cli@latest deploy --prod ``` Without either flag, Vercel chooses the deployment environment. Check the destination and access settings before sharing the URL. ## Configure API keys [#configure-api-keys] The CLI reads supported keys, such as `THESYS_API_KEY` and `OPENAI_API_KEY`, from your shell, `.env.local`, and `.env`. Shell values take priority, followed by `.env.local`. These values are attached to the deployment's build and runtime. The CLI also asks whether to save missing file values to the Vercel project for future deployments. That save applies to **production, preview, and development** wherever the key is missing. Existing saved values are not overwritten. Declining still uses local values for the current deployment. To manage environment variables in Vercel yourself and skip automatic attachment and saving, use: ```bash npx @openuidev/cli@latest deploy --skip-env ``` Only supported keys are forwarded automatically; configure other application variables in Vercel. Keep server API keys out of browser-exposed variables such as `NEXT_PUBLIC_*`. See the [CLI reference](/docs/api-reference/cli#deploy) for all options. # Getting Started > Create a new OpenUI application or add OpenUI to an existing agent. Source: https://www.openui.com/docs/getting-started ## Create a new app [#create-a-new-app] You need Node.js 20 or later. Run: ```bash npx @openuidev/cli@latest create ``` The interactive setup asks how to connect to a model and which agent framework to use, then creates the project, installs its dependencies, and can start the development server for you. See the [CLI reference](/docs/api-reference/cli) for templates, framework options, and deploying a preview. ## End-to-end integration [#end-to-end-integration] Add OpenUI to an existing application: define the components the model can use, call a model with a system prompt generated from them, and render the response. ### 1. Create a component library [#1-create-a-component-library] Create a library containing the components the model can use: ```tsx title="src/lib/openui.tsx" import { createLibrary } from "@openuidev/react-lang"; import { Chart, Metric, Stack } from "./components"; export const library = createLibrary({ root: "Stack", components: [Stack, Metric, Chart], }); ``` Learn more about defining components in the [OpenUI Lang documentation](/docs/openui-lang/defining-components). ### 2. Export the library specification [#2-export-the-library-specification] Use the OpenUI CLI to serialize the component library for your server: ```bash npx @openuidev/cli@latest generate src/lib/openui.tsx --spec --out src/lib/openui.spec.json ``` The generated specification describes the available components and their properties. Regenerate it whenever the component library changes. ### 3. Call a model [#3-call-a-model] Generate the system prompt from the library specification and send it with the user's message. OpenUI Gateway (recommended) OpenAI ```ts title="server.ts" import OpenAI from "openai"; import { generateSystemPrompt } from "@openuidev/lang-core"; import library from "./lib/openui.spec.json"; const gateway = new OpenAI({ apiKey: process.env.THESYS_API_KEY, baseURL: "https://api.thesys.dev/v1/embed", }); const completion = await gateway.chat.completions.create({ model: "openai/gpt-5", messages: [ { role: "system", content: generateSystemPrompt({ cloud: true, library }) }, { role: "user", content: "Compare quarterly revenue by region." }, ], stream: true, }); ``` ```ts title="server.ts" import OpenAI from "openai"; import { generateSystemPrompt } from "@openuidev/lang-core"; import { createAutofix } from "@openuidev/server/openai"; import library from "./lib/openui.spec.json"; const model = new OpenAI(); const autofix = createAutofix({ apiKey: process.env.THESYS_API_KEY!, library }); const messages = [{ role: "user" as const, content: "Compare quarterly revenue by region." }]; const source = await model.chat.completions.create({ model: "gpt-5", messages: [{ role: "system", content: generateSystemPrompt({ library }) }, ...messages], stream: true, }); // Autofix adds the library itself, so pass only the conversation. // Return this from your API route as a server-sent events response. const response = autofix.completions.stream({ stream: source, messages }).toResponse(); ``` Both examples use a `THESYS_API_KEY`. Run the command below to sign in with Thesys, create a key, and write it to your `.env` file. Keep the key on the server and never expose it to browser code. ```bash npx @openuidev/cli@latest generate-api-key ``` The OpenAI example uses [Autofix](/docs/autofix) to repair invalid OpenUI Lang against your library before it reaches your application. Gateway does this for you as the response streams. You can also use Anthropic, Gemini, or any other LLM provider. Send the generated system prompt with your provider's SDK. ### 4. Render the UI [#4-render-the-ui] Pass the accumulated OpenUI Lang text to ``. Set `isStreaming` until the server stream closes. ```tsx title="src/components/generated-interface.tsx" "use client"; import { Renderer } from "@openuidev/react-lang"; import { library } from "@/lib/openui"; export function GeneratedInterface({ response, isStreaming }) { return ( console.log(action)} /> ); } ``` The renderer displays the interface progressively as the response arrives. ## Next steps [#next-steps] * **Add a chat interface:** use [Agent Interface](/docs/agent/getting-started/introduction) for a complete chat UI, add OpenUI to [assistant-ui or CopilotKit](/docs/build-agents), or render generated UI in a [custom chat UI](/docs/build-agents/custom-chat-ui). * **Go deeper on OpenUI Lang:** learn about [defining components](/docs/openui-lang/defining-components), [system prompts](/docs/openui-lang/system-prompts), and the [renderer](/docs/openui-lang/renderer). * **Run it in production:** read [OpenUI in production](/docs/production), set up [Reliability Monitoring](/docs/reliability), and [deploy your app](/docs/deploy). # Introduction > OpenUI documentation for Introduction. Source: https://www.openui.com/docs OpenUI is the open standard for Generative UI. At its core is **OpenUI Lang**, a compact language that lets models respond with live, interactive interfaces built from your own components, instead of only text. OpenUI Lang works with any model, agent framework, or chat UI.
[Try the live demo →](/chat) Most AI applications are limited to returning text as Markdown or rendering pre-built UI responses. Markdown isn't interactive, and pre-built responses are rigid. They don't adapt to the context of the conversation. With Generative UI, the model composes the interface itself. It selects, configures, and combines components from your component library to build an interface for each request, whether that's an interactive chart, a form, or a multi-tab dashboard. ## How it works [#how-it-works] You define the components. The model decides how to combine them for each request, and your application renders the result. 1. **Define your component library:** the components the model can use to generate UI. 2. **Generate the system prompt:** OpenUI turns the library into instructions for the model. 3. **Call a model:** through [OpenUI Gateway](/docs/gateway) (recommended), which validates and fixes the generation as it streams, or directly with your own provider, using [Autofix](/docs/autofix) to repair invalid output. 4. **Render the stream:** the `Renderer` draws each component as its line arrives. See it end to end in [Getting Started](/docs/getting-started). ## What's in OpenUI [#whats-in-openui] OpenUI gives you the tools to build Generative UI into your application and run it reliably in production. To build Generative UI: * **[OpenUI Lang](/docs/openui-lang):** Describes interfaces with your own components. Models generate it in less than half the tokens of JSON-based formats, and the renderer displays it progressively as it streams. * **[Agent Interface](/docs/agent/getting-started/introduction):** A React SDK for a complete chat interface, with threads, streaming messages, tool activity, and generated components. Connect it to your existing agent backend, or add the renderer to assistant-ui, CopilotKit, or your own chat UI instead. To run it in production: * **[Gateway](/docs/gateway):** OpenRouter for Generative UI. One API for models across providers, with automatic failover when a provider is unavailable and Autofix built in to correct invalid OpenUI Lang as it streams. * **[Autofix](/docs/autofix):** Repairs invalid OpenUI Lang after generation with one API call. Keep your existing model provider, inference setup, and agent framework. * **[Reliability Monitoring](/docs/reliability):** Shows how often generated interfaces fail, which errors recur, and the generation behind each failure. It's enabled automatically with Gateway and Autofix, or through the Observability SDK with your own inference. Learn about correction and monitoring options in [OpenUI in production](/docs/production). # OpenUI in production > Track generation failures, fix invalid OpenUI Lang, and keep model requests working in production. Source: https://www.openui.com/docs/production Running Generative UI reliably in production takes more than getting an interface to render once. Models can use unknown components, supply invalid properties, leave references unresolved, or stop before completing a response. Model Provider outages can also interrupt generation. In our [**reliability benchmark**](/blog/generative-ui-benchmark), OpenUI achieved the highest overall structural validity and the fewest blank screens among the tested frameworks, but no framework was error-free. Production applications need ways to track these failures, fix invalid output, and recover from provider interruptions. OpenUI provides the following tools: OpenRouter for Generative UI. Access models across providers with automatic fallbacks and correction of invalid OpenUI Lang during streaming. Fix invalid OpenUI Lang while keeping your existing model provider and inference setup. Track generation failures, investigate affected interfaces, and identify recurring errors. ## Gateway [#gateway] OpenUI Gateway provides a unified API to access models across providers through a single endpoint. It sits between your application and the LLM, validates generated OpenUI Lang, and fixes errors as the response streams to your application. It also provides automatic provider fallbacks. Integrate OpenUI Gateway with your existing backend stack, including LangGraph, Vercel AI SDK, or the OpenAI SDK. Gateway provides OpenAI-compatible Chat Completions and Responses APIs: ```ts title="server.ts" import OpenAI from "openai"; const gateway = new OpenAI({ apiKey: process.env.THESYS_API_KEY, baseURL: "https://api.thesys.dev/v1/embed", }); const completion = await gateway.chat.completions.create({ model: "openai/gpt-5", messages: [ { role: "user", content: "Compare quarterly revenue by region." }, ], stream: true, }); ``` ```ts title="server.ts" import OpenAI from "openai"; const gateway = new OpenAI({ apiKey: process.env.THESYS_API_KEY, baseURL: "https://api.thesys.dev/v1/embed", }); const response = await gateway.responses.create({ model: "openai/gpt-5", input: "Compare quarterly revenue by region.", stream: true, }); ``` Learn more about OpenUI Gateway [here](/docs/gateway). ## Autofix [#autofix] When you call a model provider directly, invalid OpenUI Lang reaches your application without any correction. The Autofix API fixes it while you keep your existing inference setup. After generation completes, validate the output with the OpenUI Lang parser. If validation fails, send the generation, component specification, and conversation context to Autofix, which returns corrected OpenUI Lang. Learn more in the [Autofix docs](/docs/autofix). ## Reliability Monitoring [#reliability-monitoring] Fixing errors keeps interfaces working, but it doesn't tell you why they fail. Reliability Monitoring tracks how often generated interfaces fail and which errors recur, with insights into how to fix them. Inspect the affected OpenUI Lang to find the root cause, then improve your component library, system prompt, or model selection. Monitoring is automatically enabled for Gateway generations and Autofix requests. If you use neither, connect the Observability SDK to track parsing and rendering errors in your application. Learn more in the [Reliability Monitoring docs](/docs/reliability). # @openuidev/a2ui > A2UI v1.0 protocol support with OpenUI Lang component updates and an optional React renderer. Source: https://www.openui.com/docs/api-reference/a2ui `@openuidev/a2ui` implements the [A2UI v1.0 candidate](https://github.com/a2ui-project/a2ui/blob/main/specification/v1_0/docs/a2ui_protocol.md) surface lifecycle while using OpenUI Lang for component payloads. The package deliberately changes one part of the protocol: `createSurface.components` and `updateComponents.components` are arrays of OpenUI Lang statements instead of A2UI component JSON. All other lifecycle and message envelopes retain their A2UI v1.0 shapes, including surface creation and deletion, data-model updates, actions, function calls and responses, errors, capabilities, and renderer metadata. ```json { "version": "v1.0", "updateComponents": { "surfaceId": "main", "components": [ "root = Stack([title, save])", "title = TextContent(\"Account settings\")", "save = Button(\"Save\", onClick: @ToAssistant(\"Save these settings\"))" ] } } ``` This gives you A2UI's stateful, bidirectional protocol and OpenUI Lang's compact, streaming-friendly component representation. ## Package structure [#package-structure] The package has two public entrypoints: | Entrypoint | Environment | Responsibility | | :---------------------- | :----------------- | :--------------------------------------------------------------------------------------------- | | `@openuidev/a2ui` | Framework-agnostic | Protocol validation, surfaces, statement patches, data model, actions, functions, and metadata | | `@openuidev/a2ui/react` | React (optional) | Subscribes to a surface and renders its OpenUI Lang source with `@openuidev/react-lang` | The core client depends on `@openuidev/lang-core`; it does not depend on React. A Vue, Svelte, native, or server-side host can use the same client and provide its own rendering adapter. ## Installation [#installation] Install the framework-neutral client and its Zod peer: ```bash npm install @openuidev/a2ui zod ``` For React rendering, also install the optional renderer peers: ```bash npm install react @openuidev/react-lang ``` Install the package that provides your component library as well. For example, the built-in OpenUI library is exported by `@openuidev/react-ui`: ```bash npm install @openuidev/react-ui ``` ## Protocol flow [#protocol-flow] A typical surface moves through the following lifecycle: 1. The agent sends `createSurface` to allocate a surface and optionally seed its components and data model. 2. The agent sends one or more `updateComponents` messages. Each message patches OpenUI Lang statements by statement ID. 3. The agent can send `updateDataModel` at any time. Renderer state changes can flow back through action context or renderer metadata. 4. User interactions produce A2UI `action` messages from the renderer to the agent. 5. Either side can invoke catalog functions with `callRendererFunction` or `callAgentFunction` and settle them with the corresponding function-response envelope. 6. The agent sends `deleteSurface` when the UI is no longer needed. The package owns protocol state, not transport. Your application is responsible for carrying messages over SSE, NDJSON, WebSocket, AG-UI, an agent SDK, or another channel, and for passing each decoded message to `client.process()`. ### Supported messages [#supported-messages] | Direction | Message | Purpose | | :--------------- | :------------------------- | :------------------------------------------------------------- | | Agent → renderer | `createSurface` | Creates a surface, optionally with components and a data model | | Agent → renderer | `updateComponents` | Adds, replaces, or removes OpenUI Lang statements | | Agent → renderer | `updateDataModel` | Applies a JSON Pointer update to surface data | | Agent → renderer | `deleteSurface` | Removes a surface and rejects its pending function calls | | Agent → renderer | `callRendererFunction` | Invokes a registered renderer function | | Agent → renderer | `agentFunctionResponse` | Settles a function call initiated by the renderer | | Renderer → agent | `action` | Reports a user or component interaction | | Renderer → agent | `callAgentFunction` | Invokes an agent function | | Renderer → agent | `rendererFunctionResponse` | Returns the result of a requested renderer function | | Renderer → agent | `error` | Reports validation, lifecycle, or function errors | ## Create a client [#create-a-client] Create one client for the set of surfaces handled by a renderer: ```ts import { createA2UIClient } from "@openuidev/a2ui"; import { openuiLibrary } from "@openuidev/react-ui"; const client = createA2UIClient({ schema: openuiLibrary.toJSONSchema(), rootName: openuiLibrary.root, rendererCapabilities: { "v1.0": { supportedCatalogIds: ["com.example:openui"], }, }, onMessage(message, metadata) { transport.send({ message, metadata }); }, }); ``` ### `A2UIClientOptions` [#a2uiclientoptions] | Option | Type | Description | | :--------------------- | :----------------------------------------- | :------------------------------------------------------------------------------------- | | `schema` | `LibraryJSONSchema` | Required OpenUI component schema, normally from `library.toJSONSchema()` | | `rootName` | `string` | Expected root component name from the library | | `functions` | `Record` | Renderer functions that incoming `callRendererFunction` messages may invoke | | `rendererCapabilities` | `RendererCapabilities` | Catalog IDs and optional inline catalogs advertised in outgoing metadata | | `onMessage` | `(message, metadata) => void` | Receives renderer-to-agent messages emitted by actions, functions, and protocol errors | | `now` | `() => Date` | Optional clock override, useful for deterministic action timestamps in tests | | `createId` | `() => string` | Optional ID generator for renderer-initiated agent function calls | The schema and the rendered library must describe the same components. `catalogId` is a protocol identifier and capability check; the client does not download a catalog or choose a component library from that ID. ## Process agent messages [#process-agent-messages] Pass decoded agent-to-renderer messages to `process()` in arrival order: ```ts await client.process({ version: "v1.0", createSurface: { surfaceId: "main", catalogId: "com.example:openui", sendDataModel: true, dataModel: { user: { name: "Alice" }, }, }, }); await client.process({ version: "v1.0", updateComponents: { surfaceId: "main", components: ["root = Stack([greeting])", 'greeting = TextContent("Hello, " + $user.name)'], }, }); ``` `process(input)` validates the envelope at runtime and returns: ```ts interface ProcessResult { ok: boolean; outbound: RendererToAgentMessage[]; issues?: Array<{ path: string; message: string }>; } ``` `outbound` contains the messages emitted while processing that input. The same messages are also delivered to `onMessage` and `subscribeMessages()` listeners. A host can use either mechanism, but should avoid sending the same outbound message twice. ## Component updates [#component-updates] Each `components` entry is an OpenUI Lang statement or multiline statement block. Updates are merged by statement ID: ```json { "version": "v1.0", "updateComponents": { "surfaceId": "main", "components": ["status = Badge(\"Saving\")"] } } ``` * A new ID adds a statement. * An existing ID replaces its previous statement without changing its position. * `statementId = null` removes that statement. * `root` remains the surface root. * Statements may arrive before they are reachable from `root`; the client preserves them so a later patch can attach them. * Entries are applied in array order, so a later entry for the same ID wins. For example: ```ts await client.process({ version: "v1.0", updateComponents: { surfaceId: "main", components: [ 'status = Badge("Saved")', // replace status "oldHelpText = null", // remove oldHelpText ], }, }); ``` ### Streaming component generation [#streaming-component-generation] An `updateComponents` message is a statement patch, not a raw text-delta event. When an LLM is generating OpenUI Lang, keep the current accumulated statement or statement block and send replacements under the same statement ID: ```ts for await (const sourceSoFar of generateOpenUILang()) { await client.process({ version: "v1.0", updateComponents: { surfaceId: "main", components: [sourceSoFar], }, }); } ``` For true progressive rendering: * Send `createSurface` as soon as the surface is known. * Forward patches as model output arrives instead of buffering the complete generation. * Send the accumulated source for an ID, not only the latest token delta. Statement replacement then produces a coherent surface snapshot. * Stream raw OpenUI Lang without Markdown code fences. A fenced block cannot be interpreted until its closing fence arrives. * Set the React adapter's `isStreaming` prop while the turn is active. The underlying OpenUI renderer can then render the usable prefix and tolerate incomplete trailing syntax. * Mark streaming complete only after the final patch has been processed. Transport boundaries and Lang statement boundaries do not need to match, but every `client.process()` call must receive a valid A2UI JSON envelope. ## Surface state [#surface-state] Read or subscribe to the framework-neutral surface store: ```ts const unsubscribe = client.subscribe(() => { const surface = client.getSurface("main"); console.log(surface?.source, surface?.dataModel, surface?.revision); }); const allSurfaces = client.getSurfaces(); unsubscribe(); ``` Each snapshot has the following shape: ```ts interface SurfaceSnapshot { surfaceId: string; catalogId?: string; metadata?: A2UIMessageMetadata; sendDataModel: boolean; source: string; dataModel: JsonObject; parseResult: ParseResult | null; errors: OpenUIError[]; revision: number; } ``` `source` is the merged OpenUI Lang document. `parseResult` and `errors` come from `@openuidev/lang-core`. `revision` increases whenever any surface changes and is suitable for external-store subscriptions. ## Data-model updates [#data-model-updates] `updateDataModel` applies a JSON Pointer patch to a surface: ```ts await client.process({ version: "v1.0", updateDataModel: { surfaceId: "main", path: "/user/name", value: "Bob", }, }); ``` The data model is always an object. The update behavior is: | Input | Result | | :----------------------------- | :----------------------------------------------------------------- | | Missing `path`, `""`, or `"/"` | Replaces the root object | | Nested object path | Creates missing object or array containers as needed | | `null` at a property path | Deletes that property | | `null` at an array index | Removes that item with `splice` semantics | | `null` at the root | Resets the model to `{}` | | Primitive or array at the root | Produces a validation error because the root must remain an object | OpenUI Lang bindings use the same top-level keys. A data-model value such as `{ user: { name: "Bob" } }` is exposed to Lang as `$user`, so `$user.name` resolves to `"Bob"`. The React adapter also synchronizes form state back into the surface data model. Use the framework-neutral method directly when building another adapter: ```ts client.updateSurfaceFromOpenUIState("main", openUIState); ``` ## Render a surface in React [#render-a-surface-in-react] Import the renderer from the optional React entrypoint: ```tsx import { A2UIRenderer } from "@openuidev/a2ui/react"; import { openuiLibrary } from "@openuidev/react-ui"; export function Surface({ isStreaming }: { isStreaming: boolean }) { return ( console.error(errors)} /> ); } ``` The component subscribes to the client with `useSyncExternalStore`, reads the requested surface, and passes its merged source and data model to the `@openuidev/react-lang` renderer. It returns `null` until that surface exists. ### `A2UIRendererProps` [#a2uirendererprops] | Prop | Type | Description | | :-------------- | :------------------------------ | :---------------------------------------------------------------------------------------- | | `client` | `A2UIClient` | Required protocol client | | `surfaceId` | `string` | Surface to subscribe to and render | | `library` | `Library` | React component library matching the schema given to the client | | `isStreaming` | `boolean` | Whether the surrounding transport is still delivering the current agent turn | | `mapAction` | `(event, surface) => options` | Maps an OpenUI action to A2UI name, source component, user message, context, and metadata | | `onAction` | `(event, surface) => void` | Observes actions after built-in handling | | `onOpenUrl` | `(url, event, surface) => void` | Handles `@OpenUrl`; defaults to a safe `window.open` in browsers | | `onStateUpdate` | `(state, surface) => void` | Observes OpenUI state after it has been merged into the A2UI data model | | `onParseResult` | `(result) => void` | Receives OpenUI Lang parse results | | `onError` | `(errors) => void` | Receives OpenUI parser/runtime errors | | `formStateKeys` | `readonly string[]` | Adds top-level data-model keys that should also hydrate form namespaces | | `toolProvider` | `RendererProps["toolProvider"]` | Overrides the default A2UI routing for OpenUI Lang `Query()` and `Mutation()` calls | | `queryLoader` | `ReactNode` | UI shown while a query is loading | `@OpenUrl` is handled locally and is not emitted as an A2UI action. Other OpenUI actions are converted to A2UI actions automatically. Without a `toolProvider`, the adapter routes OpenUI Lang `Query()` and `Mutation()` calls to the agent with `callAgentFunction`. Pass a `toolProvider` to execute those calls locally instead. The client's `functions` option handles the opposite direction: `callRendererFunction` messages sent by the agent. ## Actions [#actions] You can dispatch an action directly from any renderer adapter: ```ts client.dispatchAction({ surfaceId: "main", sourceComponentId: "saveButton", name: "save", context: { section: "profile" }, }); ``` This emits an A2UI renderer-to-agent message through `onMessage`: ```json { "version": "v1.0", "action": { "name": "save", "surfaceId": "main", "sourceComponentId": "saveButton", "timestamp": "2026-01-01T12:00:00.000Z", "context": { "section": "profile" } } } ``` The React adapter calls `dispatchOpenUIAction()` for you. By default it uses the OpenUI action type as `name`, forwards the human-friendly message as `userMessage`, includes action params and form state in `context`, and uses `"root"` as the source component. Customize that mapping when your agent expects a more specific component ID or domain-specific action name: ```tsx ({ name: event.type === "continue_conversation" ? "submit" : event.type, sourceComponentId: "profileForm", context: { origin: "settings" }, })} /> ``` ## Agent functions [#agent-functions] Use `callAgentFunction()` when the renderer needs a correlated result from an agent-side function: ```ts const response = client.callAgentFunction({ surfaceId: "main", call: "saveProfile", args: { section: "profile" }, }); ``` The client emits the current renderer-to-agent envelope: ```json { "version": "v1.0", "callAgentFunction": { "surfaceId": "main", "functionCallId": "generated-call-id", "callFunction": { "call": "saveProfile", "args": { "section": "profile" } } } } ``` Pass the matching agent response back to the client: ```ts await client.process({ version: "v1.0", agentFunctionResponse: { functionCallId: "generated-call-id", value: { saved: true }, }, }); await response; // { saved: true } ``` An `agentFunctionResponse.error` rejects the promise with `A2UIFunctionError`. The React adapter uses this same path for OpenUI Lang `Query()` and `Mutation()` calls when no local `toolProvider` is supplied. ## Renderer functions [#renderer-functions] Register functions that the agent may ask the renderer to execute: ```ts const client = createA2UIClient({ schema: openuiLibrary.toJSONSchema(), functions: { getLocation: { catalogId: "com.example:functions", allowedCallers: "agentOnly", async handler() { return { latitude: 37.7749, longitude: -122.4194 }; }, }, }, onMessage(message, metadata) { transport.send({ message, metadata }); }, }); ``` Incoming call: ```json { "version": "v1.0", "callRendererFunction": { "functionCallId": "call-1", "callFunction": { "call": "getLocation", "catalogId": "com.example:functions", "args": {} } } } ``` The client always emits a correlated result: ```json { "version": "v1.0", "rendererFunctionResponse": { "functionCallId": "call-1", "value": { "latitude": 37.7749, "longitude": -122.4194 } } } ``` Registrations support `"rendererOnly"`, `"agentOnly"`, and `"rendererOrAgent"`. Object registrations default to `"rendererOnly"`, so set `allowedCallers` and `catalogId` explicitly for agent-callable functions. A direct function value is renderer-only shorthand and is never callable by an incoming agent message. Unknown functions, disallowed calls, and handler failures produce protocol error messages correlated by `functionCallId`. ## Capabilities and renderer metadata [#capabilities-and-renderer-metadata] Configure capabilities when creating the client: ```ts const client = createA2UIClient({ schema, rendererCapabilities: { "v1.0": { supportedCatalogIds: ["com.example:openui"], inlineCatalogs: [], }, }, }); ``` If the renderer advertises at least one catalog ID, `createSurface` rejects an unsupported `catalogId`. The package does not automatically negotiate or install component libraries; your agent and renderer must be configured with the same OpenUI Lang component schema. When a surface sets `sendDataModel: true`, `getRendererMetadata()` includes its current data model: ```ts const metadata = client.getRendererMetadata(); // { // a2uiRendererCapabilities: { ... }, // a2uiRendererDataModel: { // version: "v1.0", // surfaces: { main: { ... } } // } // } ``` The metadata is supplied as the second argument to `onMessage` and message subscribers. Your transport decides whether it is attached to a model request, agent context, or another protocol-specific location. ## Runtime validation and TypeScript types [#runtime-validation-and-typescript-types] Zod is the source of truth for runtime validation and inferred TypeScript types. ```ts import { agentToRendererMessageSchema, rendererToAgentMessageSchema, validateAgentToRendererMessage, type AgentToRendererMessage, type RendererToAgentMessage, } from "@openuidev/a2ui"; const result = agentToRendererMessageSchema.safeParse(input); const validated = validateAgentToRendererMessage(input); ``` The package exports runtime Zod schemas for every message and shared protocol structure, including capabilities and renderer data-model metadata. The public TypeScript message types are inferred from those definitions, keeping runtime behavior and static types aligned without maintaining a second schema format. ## Client API [#client-api] | Method | Description | | :------------------------------------------------- | :---------------------------------------------------------------------- | | `process(input)` | Validates and applies one agent-to-renderer message | | `getSurface(surfaceId)` | Returns one surface snapshot, or `undefined` | | `getSurfaces()` | Returns all current surface snapshots | | `subscribe(listener)` | Subscribes to surface-store changes and returns an unsubscribe function | | `subscribeMessages(listener)` | Subscribes to emitted renderer-to-agent messages and metadata | | `dispatchAction(input)` | Emits an A2UI action | | `dispatchOpenUIAction(surfaceId, event, options?)` | Converts an OpenUI action event into an A2UI action | | `callAgentFunction(input)` | Emits `callAgentFunction` and returns its correlated response promise | | `updateSurfaceFromOpenUIState(surfaceId, state)` | Merges OpenUI bindings and form state into a surface data model | | `getRendererDataModel()` | Returns opted-in surface models in the A2UI renderer-data-model shape | | `getRendererMetadata()` | Returns configured capabilities and opted-in data-model metadata | | `dispose()` | Rejects pending function calls, clears surfaces, and removes listeners | ## Errors and cleanup [#errors-and-cleanup] Malformed envelopes are rejected before they mutate state. When the invalid input identifies a surface or function call, the client also emits a correlated A2UI error message. OpenUI Lang validation errors are stored on the surface and emitted as `VALIDATION_FAILED` messages. Call `dispose()` when the client is no longer needed: ```ts client.dispose(); ``` Deleting a surface or disposing the client rejects pending agent-function promises for that surface with `A2UIFunctionError`. ## Compatibility notes [#compatibility-notes] * This package is currently experimental and implements the v1.0 message profile described above. * It is not wire-compatible with a renderer that expects A2UI JSON component objects in `components`; both sides must agree that the array contains OpenUI Lang. * The protocol client is framework-neutral. Only `@openuidev/a2ui/react` requires React and `@openuidev/react-lang`. * The protocol does not define your transport. Preserve message order per surface and pass complete JSON envelopes to `process()`. * The agent and renderer must use the same OpenUI component library/schema. # @openuidev/angular-lang > API reference for Angular component libraries, streaming rendering, actions, state, and validation. Source: https://www.openui.com/docs/api-reference/angular-lang Use this package to define Angular components a model can render, generate prompts from their schemas, and render streamed OpenUI Lang. It provides standalone Angular components and injection helpers. You supply your component library, styling, chat interface, and model transport. For a runnable chat integration with NG-ZORRO X, see the [Angular example](https://github.com/thesysdev/openui/tree/main/examples/app-frameworks/angular). ## Install [#install] ```bash npm install @openuidev/angular-lang zod rxjs ``` Version `0.3.0` requires `@angular/core` and `@angular/common` `^22.1.5`, RxJS `^7.8.2`, and Zod `^3.25.0 || ^4.0.0`. Use the `zod/v4` API when defining schemas. Your Angular application supplies its normal compiler and build tooling; the renderer does not require `zone.js` or `@angular/platform-browser-dynamic`. ## Define a library [#define-a-library] `defineComponent(config)` accepts a name, description, ordered Zod object schema, and Angular component class. It returns a `DefinedComponent` whose `.ref` can be used in another component's schema, such as `z.array(Child.ref)`. Registered components should accept the `props`, `renderNode`, and `statementId` inputs. Props may be incomplete while the model streams; use optional access and empty-array fallbacks in templates. ```ts import { Component, Input } from "@angular/core"; import { createLibrary, defineComponent } from "@openuidev/angular-lang"; import { z } from "zod/v4"; @Component({ selector: "demo-greeting", template: `

Hello, {{ props?.name }}!

`, }) export class GreetingComponent { @Input() props: { name?: string } | null = null; @Input() renderNode: ((value: unknown) => unknown) | null = null; @Input() statementId: string | undefined; } const Greeting = defineComponent({ name: "Greeting", description: "Greet the user by name.", props: z.object({ name: z.string() }), component: GreetingComponent, }); export const library = createLibrary({ components: [Greeting], root: "Greeting", }); const systemPrompt = library.prompt({ preamble: "You are a helpful assistant.", examples: ['root = Greeting("Alice")'], }); const schema = library.toJSONSchema(); ``` `createLibrary(definition)` accepts `components` and an optional `root`, `id`, or `componentGroups`. It returns the component registry, `prompt(options?)`, and `toJSONSchema()`. Use `PromptOptions` for prompt options such as `preamble`, `additionalRules`, and `examples`. The order of schema properties determines the positional argument order in OpenUI Lang. Generate the model prompt from the same definitions the renderer uses. For a Node backend, share the schema definitions and use `@openuidev/lang-core` to generate prompts without importing Angular component classes. The example demonstrates this split. ## `Renderer` [#renderer] `Renderer` is an alias for `OpenUiRendererComponent`. Import it in the host component's `imports` and use the `` selector. Pass the accumulated response text as it streams. ```ts import { Component, Input } from "@angular/core"; import { Renderer, type ActionEvent } from "@openuidev/angular-lang"; import { library } from "./library"; @Component({ selector: "assistant-message", imports: [Renderer], template: ` `, }) export class AssistantMessageComponent { @Input() response: string | null = null; @Input() isStreaming = false; readonly library = library; handleAction(event: ActionEvent): void { // Forward continuation actions to your application's normal send-message path. console.log(event.humanFriendlyMessage, event.formState); } } ``` ### Inputs [#inputs] | Input | Type | Purpose | | :------------- | :------------------------------------- | :------------------------------------------------------------------ | | `response` | `string \| null` | Accumulated OpenUI Lang text; defaults to `null` | | `library` | `Library \| null` | Library returned by `createLibrary`; supply it to render components | | `isStreaming` | `boolean` | Whether the model is still producing text; defaults to `false` | | `initialState` | `Record \| undefined` | State for initial hydration | | `toolProvider` | `OpenUiToolProvider` | Async function map or MCP-like client; defaults to `null` | | `queryLoader` | `Type \| null` | Angular component displayed while a query is loading | ### Outputs [#outputs] | Output | Payload | Purpose | | :-------------- | :------------------------ | :--------------------------------------------------- | | `(action)` | `ActionEvent` | Continuation or URL action dispatched by a component | | `(stateUpdate)` | `Record` | Updated store snapshot | | `(parseResult)` | `ParseResult \| null` | Current parse result | | `(error)` | `OpenUIError[]` | Structured parser, tool, and render errors | Use Angular event bindings such as `(action)`, not React-style callback attributes. The exported `RendererProps` / `OpenUiRendererProps` interface also describes callback names for host integrations; the Angular component itself exposes the outputs above. The renderer preserves matching component instances as their props change during streaming. A component render failure emits a `render-error` and preserves the last successfully rendered subtree. ## Nested components with `RenderNode` [#nested-components-with-rendernode] `RenderNode` is an alias for `OpenUiRenderNodeComponent`, with selector ``. It renders values, arrays, and parsed element nodes. Inputs are `value`, `library`, `context`, and optional `formName`. Use the parent renderer's context when rendering nested children: ```ts import { Component, Input } from "@angular/core"; import { RenderNode, injectOpenUiContext } from "@openuidev/angular-lang"; @Component({ selector: "demo-container", imports: [RenderNode], template: `
`, }) export class ContainerComponent { @Input() props: { children?: unknown[] } | null = null; @Input() renderNode: ((value: unknown) => unknown) | null = null; @Input() statementId: string | undefined; readonly openUi = injectOpenUiContext(); } ``` Register the container with `defineComponent` and describe its allowed children using their `.ref` schemas. Passing `formName` to `RenderNode` provides that name to its descendant components through `OPENUI_FORM_NAME`. ## Context and signals [#context-and-signals] Call injection helpers in a component constructor or field initializer inside the renderer subtree. They rely on `OPENUI_CONTEXT`. | Helper | Returns | | :-------------------------- | :-------------------------------- | | `injectOpenUiContext()` | Full `OpenUiContextValue` | | `injectRenderNode()` | `(value: unknown) => unknown` | | `injectTriggerAction()` | Runtime `triggerAction` function | | `injectIsStreaming()` | `Signal` | | `injectIsQueryLoading()` | `Signal` | | `injectGetFieldValue()` | Runtime `getFieldValue` function | | `injectSetFieldValue()` | Runtime `setFieldValue` function | | `injectFormName()` | Current form name, or `undefined` | | `injectStore()` | Underlying runtime store | | `injectEvaluationContext()` | Runtime evaluation context | Capture the signals once, then invoke them in templates or event handlers: ```ts readonly isStreaming = injectIsStreaming(); readonly isQueryLoading = injectIsQueryLoading(); // Template: ``` The full context's `isStreaming` and `isQueryLoading` properties are reactive boolean getters. `getFieldValue(formName, name)` tracks store changes when read from a template or `computed()`. Do not save only its initial value when the UI needs to update. ### Actions [#actions] ```ts triggerAction( userMessage: string, formName?: string, action?: ActionPlan | ActionConfig, ): void | Promise; ``` Calling `triggerAction("Explain the chart")` emits a `BuiltinActionType.ContinueConversation` action. Handle `(action)` in the host to send a new model turn. Preserve `humanFriendlyMessage`, relevant `params.context`, and `formState` when serializing the turn. The renderer does not call an LLM itself. `ActionEvent` includes `type`, `params`, `humanFriendlyMessage`, and optional `formState` and `formName`. `BuiltinActionType.OpenUrl` is a separate action for the host to handle. `ActionPlan` steps can also update/reset state or run queries and mutations; those steps execute in the runtime. ### Field state and defaults [#field-state-and-defaults] The context provides these functions: ```ts getFieldValue(formName: string | undefined, name: string): unknown; setFieldValue( formName: string | undefined, componentType: string | undefined, name: string, value: unknown, shouldTriggerSaveCallback?: boolean, ): void; ``` `setDefaultValue(options, context?)` persists a default when `existingValue` is `undefined`, `defaultValue` is defined, and `isStreaming` is false. `SetDefaultValueOptions` accepts those three values plus `name`, optional `formName`, `componentType`, and `shouldTriggerSaveCallback` (defaults to `false`). In lifecycle hooks such as `ngOnChanges`, pass an already-injected context: `setDefaultValue(options, this.openUi)`. Lifecycle hooks are not Angular injection contexts. Pass the current streaming state explicitly when applying defaults to streamed fields. ## Form validation [#form-validation] Validation helpers are opt-in for your custom form components: | Export | Purpose | | :----------------------------------- | :--------------------------------------------------------------------- | | `createFormValidation()` | Create a `FormValidationContextValue` | | `provideFormValidation(value)` | Return Angular providers sharing a validation context with descendants | | `injectFormValidation()` | Inject the closest context, or `null` when none was provided | | `OPENUI_FORM_VALIDATION` | Injection token for the validation context | | `parseRules`, `parseStructuredRules` | Convert validation declarations into parsed rules | | `validate`, `builtInValidators` | Run validation or access built-in validators | The context exposes an `errors` writable signal, `getFieldError`, `validateField`, `registerField`, `unregisterField`, `validateForm`, and `clearFieldError`. Register fields with `registerField(name, rules, getValue)`, unregister them when destroyed, and call `validateForm()` before triggering the submit action. A validation context reports errors; your components render those messages and enforce submission behavior. ## Tool providers [#tool-providers] `OpenUiToolProvider` is a map of async functions, an `McpClientLike` object, or `null`. A function-map entry receives an argument object and returns a promise. MCP-like clients expose `callTool({ name, arguments })`. ```ts toolProvider = { async get_status(args: Record) { const response = await fetch(`/api/status?id=${encodeURIComponent(String(args["id"]))}`); if (!response.ok) throw new Error("Could not load status"); return response.json(); }, }; ``` Pass it through `[toolProvider]`. The runtime executes `Query()` calls and mutations invoked by action steps. Keep secrets and privileged operations in your server endpoints. Use `[queryLoader]` and `injectIsQueryLoading()` to provide loading feedback. ## Parser and errors [#parser-and-errors] Parser and prompt utilities are re-exported from `@openuidev/lang-core`. A parser takes the library's JSON schema and optional root name: ```ts import { createParser } from "@openuidev/angular-lang"; const parser = createParser(library.toJSONSchema(), "Greeting"); const result = parser.parse('root = Greeting("Alice")'); console.log(result.root, result.meta.errors, result.meta.unresolved); ``` Other exports include `createStreamingParser`, `parse`, `mergeStatements`, `generatePrompt`, and `generateSystemPrompt`. In server-only code, import these directly from `@openuidev/lang-core`. Inspect `ParseResult.meta.errors` and the renderer's `(error)` output. `OpenUIError.code` distinguishes `missing-required`, `null-required`, `unknown-component`, `excess-args`, `tool-not-found`, `tool-error`, `mcp-error`, and `render-error`. Check unresolved references and the presence of a root before treating a complete model response as valid. ## Types [#types] The package exports `Library`, `LibraryDefinition`, `DefinedComponent`, `ComponentRenderer` (an Angular `Type`), `ComponentRenderProps`, `ComponentGroup`, `PromptOptions`, `RendererProps`, `OpenUiRendererProps`, `OpenUiToolProvider`, `OpenUiContextValue`, `SetDefaultValueOptions`, `FormValidationContextValue`, and the shared Lang Core action, parser, schema, and error types. # @openuidev/assistant-ui > API reference for rendering OpenUI Lang tool calls in assistant-ui applications. Source: https://www.openui.com/docs/api-reference/assistant-ui Use this package when assistant-ui owns the conversation and tool lifecycle and OpenUI should render streaming, interactive interfaces inside assistant messages. ## Install [#install] pnpm bun yarn npm ```bash pnpm add @openuidev/assistant-ui @assistant-ui/react @openuidev/react-ui @openuidev/react-lang @openuidev/react-headless react react-dom zod zustand@^4.5.5 ``` ```bash bun add @openuidev/assistant-ui @assistant-ui/react @openuidev/react-ui @openuidev/react-lang @openuidev/react-headless react react-dom zod zustand@^4.5.5 ``` ```bash yarn add @openuidev/assistant-ui @assistant-ui/react @openuidev/react-ui @openuidev/react-lang @openuidev/react-headless react react-dom zod zustand@^4.5.5 ``` ```bash npm install @openuidev/assistant-ui @assistant-ui/react @openuidev/react-ui @openuidev/react-lang @openuidev/react-headless react react-dom zod zustand@^4.5.5 ``` Import the OpenUI styles once in the application: ```css @import "@openuidev/react-ui/layered/styles/index.css"; ``` ## `openuiIntegration` [#openuiintegration] The default integration combines a toolkit and model instructions generated from the same `openuiChatLibrary` component vocabulary: ```ts interface OpenUIIntegration { toolkit: Toolkit; instructions: string; toolNames: { present: string; prompt: string; }; } const openuiIntegration: OpenUIIntegration; ``` Register both values inside the assistant runtime's model-context client: ```tsx "use client"; import { AssistantRuntimeProvider, AuiConfig, AuiProvider, Tools, useAui, useAssistantInstructions, } from "@assistant-ui/react"; import { openuiIntegration } from "@openuidev/assistant-ui"; function OpenUIModelInstructions() { useAssistantInstructions(openuiIntegration.instructions); return null; } function OpenUIConfigProvider({ children }: { children: React.ReactNode }) { const aui = useAui(); const config = AuiConfig({ tools: Tools({ toolkit: openuiIntegration.toolkit }), }); return ( {children} ); } export function OpenUIRuntimeProvider({ runtime, children }) { return ( {children} ); } ``` The default toolkit registers two standalone tools: | Tool | assistant-ui type | Behavior | | :--------------- | :---------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- | | `present_openui` | `frontend` | Renders a complete OpenUI Lang response. Follow-ups and optional list drill-down actions start new user turns; the tool completes when `ui` is ready. | | `prompt_openui` | `human` | Waits for the required OpenUI `@ToAssistant` action, then submits its message and form state. | ## `createOpenUIIntegration(options)` [#createopenuiintegrationoptions] Creates an aligned toolkit and instruction string for a custom library, tool names, renderer, or prompt configuration. ```ts interface CreateOpenUIIntegrationOptions extends OpenUIToolUIOptions { promptOptions?: PromptOptions; presentToolName?: string; promptToolName?: string; presentDescription?: string; promptDescription?: string; preamble?: string; additionalRules?: string[]; } function createOpenUIIntegration(options?: CreateOpenUIIntegrationOptions): OpenUIIntegration; ``` ```ts import { createOpenUIIntegration } from "@openuidev/assistant-ui"; import { library } from "./library"; const openui = createOpenUIIntegration({ library, presentToolName: "show_panel", promptToolName: "ask_panel", }); ``` ## Toolkit APIs [#toolkit-apis] ```ts function createOpenUIToolkit(options?: CreateOpenUIToolkitOptions): Toolkit; function createOpenUIToolParameters(root?: string): z.ZodObject; const openuiToolkit: Toolkit; const openuiToolParameters: z.ZodObject; const OPENUI_PRESENT_TOOL_NAME = "present_openui"; const OPENUI_PROMPT_TOOL_NAME = "prompt_openui"; ``` `CreateOpenUIToolkitOptions` accepts the library, tool names and descriptions, plus the renderer options described below. Display and prompt tools must use different, non-empty names. ## Renderer APIs [#renderer-apis] ```ts function OpenUIContent(props: OpenUIContentProps): React.ReactNode; function OpenUIPresent(props: OpenUIPresentProps): React.ReactNode; function OpenUIPrompt(props: OpenUIPromptProps): React.ReactNode; function createOpenUIPresent(options?: OpenUIToolUIOptions): ToolCallMessagePartComponent; function createOpenUIPrompt(options?: OpenUIToolUIOptions): ToolCallMessagePartComponent; ``` Shared configuration: ```ts interface OpenUIToolUIOptions { library?: Library; rendererProps?: OpenUIRendererProps; ErrorFallback?: OpenUIErrorFallback | null; onError?: (errors: OpenUIError[]) => void; } ``` `OpenUIContent` forwards supported props to the OpenUI `Renderer`. Parser errors may be transient while `ui` is streaming, so the default error fallback appears only after streaming finishes. Set `ErrorFallback: null` to suppress it. `OpenUIPresent` appends `FollowUpBlock` clicks and optional `ListBlock` `@ToAssistant` actions to the assistant-ui thread as new user messages. OpenUI `@OpenUrl` actions open in a new isolated tab. `OpenUIPrompt` calls assistant-ui's `addResult` once for a terminal `@ToAssistant` action. Its result contains the action type, message, parameters, optional form name, and form state. Replayed messages use the saved form state as the renderer's initial state. ## Instruction APIs [#instruction-apis] ```ts function createOpenUIInstructions(options?: CreateOpenUIInstructionsOptions): string; function useOpenUIInstructions(options?: UseOpenUIInstructionsOptions): void; function OpenUIInstructions(options: UseOpenUIInstructionsOptions): null; const openuiInstructions: string; ``` The generated instructions tell the model when to use each tool, include the selected library's OpenUI Lang prompt, and keep the display and interactive tool contracts distinct. ## Runtime support and continuation [#runtime-support-and-continuation] The core `@openuidev/assistant-ui` entrypoint is runtime-agnostic. Its toolkit, renderers, and instructions work with any assistant-ui runtime that forwards tool schemas and results. After `prompt_openui` receives a result, a runtime adapter should continue the conversation once all tools in the current step are complete. A completed `present_openui` call remains the final display response and should not automatically start another model step. ### Vercel AI SDK [#vercel-ai-sdk] The optional `/ai-sdk` subpath implements that continuation policy for Vercel AI SDK runtimes. The `ai` package is an optional peer dependency and is not required by the core entrypoint. ```tsx import { useChatRuntime } from "@assistant-ui/react-ai-sdk"; import { shouldContinueAfterOpenUIPrompt } from "@openuidev/assistant-ui/ai-sdk"; const runtime = useChatRuntime({ sendAutomaticallyWhen: shouldContinueAfterOpenUIPrompt, }); ``` For custom tool names: ```ts import { createShouldContinueAfterOpenUIPrompt } from "@openuidev/assistant-ui/ai-sdk"; const shouldContinue = createShouldContinueAfterOpenUIPrompt({ promptToolName: "ask_panel", }); ``` Other runtimes can implement the same policy through their continuation API. Additional runtime-specific helpers can be published as separate subpath exports without coupling the core integration to one transport or model SDK. ## Exports [#exports] | Export | Description | | :-------------------------------------- | :-------------------------------------------------------------- | | `openuiIntegration` | Default aligned toolkit and instructions | | `createOpenUIIntegration` | Integration factory for custom libraries and tool names | | `openuiToolkit` / `createOpenUIToolkit` | Default toolkit and toolkit factory | | `openuiInstructions` | Default generated model instructions | | `createOpenUIInstructions` | Instruction factory | | `OpenUIInstructions` | Component that registers generated instructions | | `useOpenUIInstructions` | Hook that registers generated instructions | | `OpenUIContent` | OpenUI renderer wrapper for streamed tool arguments | | `OpenUIPresent` / `createOpenUIPresent` | Display-tool renderer and factory | | `OpenUIPrompt` / `createOpenUIPrompt` | Human-tool renderer and factory | | `DefaultOpenUIErrorFallback` | Default post-stream renderer error message | | `shouldContinueAfterOpenUIPrompt` | Vercel AI SDK continuation predicate from the `/ai-sdk` subpath | | `createShouldContinueAfterOpenUIPrompt` | Vercel AI SDK predicate factory from the `/ai-sdk` subpath | # @openuidev/cli > API reference for the OpenUI CLI to scaffold apps, mint Gateway API keys, and generate system prompts or library specs, and deploy projects. Source: https://www.openui.com/docs/api-reference/cli A command-line tool for scaffolding OpenUI chat apps, minting OpenUI Gateway API key, and generating system prompts, JSON schemas, or serialized library specs from library definitions, and deploying those apps. ## Installation [#installation] Run without installing: pnpm bun yarn npm ```bash pnpx @openuidev/cli@latest ``` ```bash bunx @openuidev/cli@latest ``` ```bash yarn dlx @openuidev/cli@latest ``` ```bash npx @openuidev/cli@latest ``` Or install globally: pnpm bun yarn npm ```bash pnpm add -g @openuidev/cli ``` ```bash bun add -g @openuidev/cli ``` ```bash yarn global add @openuidev/cli ``` ```bash npm install -g @openuidev/cli ``` ## `create` [#create] Scaffolds a new Next.js app pre-configured with OpenUI Chat. ``` openui create [options] ``` **Options** | Flag | Description | | --------------------------------- | ----------------------------------------------------------------------------- | | `-n, --name ` | Project name (interactive default: `openui-agent`) | | `-t, --template