Omni-IR
A line-oriented streaming protocol for generative UI. An AI model describes a screen in short, flat lines of Omni-IR; a trusted client parses each line as it streams in and renders it with its own components. The model never writes HTML, CSS or code, and can only trigger backend actions the app has explicitly allowed.
Status: early (v0.15). The parser, schema, streaming server and playground work and are tested, with renderers for the web (React), iPhone and iPad (SwiftUI) and Android (Jetpack Compose). The format is defined in SPEC.md, with a conformance suite that all three parsers pass. Changes are listed in CHANGELOG.md.
root = Card([title, amount, note, actions])
title = Heading("Confirm payment")
$amount = 42.50
amount = Text($amount, format="currency", currency="USD")
$note = ""
note = Input($note, label="Note for merchant (optional)")
actions = Stack([confirm, cancel], direction="row")
confirm = Button("Pay now", action="pay")
confirmPay = McpMutation(confirm, tool="payments.confirm", params={amount: $amount, note: $note})
cancel = Button("Cancel", variant="secondary")
root comes first, so the card appears immediately and its parts fill in as their lines arrive. confirm has an action, so it only becomes clickable once its McpMutation line names a permitted tool. cancel has none, so it can never reach the server.
One description, drawn natively on iPhone, Android and the web, and kept current by the app. Watch the 50-second demo (pre-written screens; no AI model was called).
Website: jdsouza1.github.io/omni-ir · Try the playground (runs in your browser, free) · Docs · Discussions
Use it in your app
npm install @omni-ir/core @omni-ir/react
@omni-ir/core parses and validates a stream; @omni-ir/react renders it with the Trusted Catalog. Not on React? @omni-ir/elements gives you <omni-screen> for Vue, Svelte, Angular or plain HTML. Their READMEs have examples.
iPhone, iPad and Mac (SwiftUI): add the package https://github.com/jdsouza1/omni-ir with Swift Package Manager and use OmniIRSwiftUI (iOS 17+, macOS 14+). See swift/README.md.
Android (Jetpack Compose): omni-ir-compose in android/ (Android 8.0+). It isn't published to Maven yet; until it is, include the modules from this repo.
All three parsers (TypeScript, Swift and Kotlin) pass the same conformance suite, so a stream behaves identically everywhere. How a screen travels from a server to an app is specified too, with transport cases for the three clients, and screens can also travel over AG-UI, the event protocol many agent frameworks speak (@omni-ir/core/ag-ui).
Try it locally
Requires Node.js 22.22+ or 24.15+.
npm install
npm run playground
Open http://localhost:5173, then pick an example or describe a screen. The playground uses a mock model that streams pre-written screens, so it needs no API key and costs nothing. Try the demo: … prompts to see how errors are handled.
How it works
Diagram and step-by-step walkthrough: docs/ARCHITECTURE.md.
model text ──► /api/generate (SSE) ──► line buffer ──► tokenizer ──► schema ──► store ──► React renderer
(bytes→lines) (text→AST) (Zod rules) (snapshots) (Trusted Catalog)
button click ──► McpMutationBoundary ──► /api/mutate ──► server re-validates ──► tool handler
- Streaming: network chunks are split into complete lines (multi-byte characters and
\r\nhandled), and each line is parsed as soon as it arrives. A reference to a line that hasn't arrived yet renders as a placeholder; if it never arrives, it becomes a small "failed to load" box instead of collapsing the layout. - Bad lines never stop the stream: an invalid line is reported with its line number and skipped.
- Stable rendering: every component is keyed by its Omni-IR id, and only the component whose line just arrived re-renders.
Security model
- No code, markup or styling from the model. The grammar is flat assignments only, and all text is rendered as text. A build-time guard fails on
innerHTML,dangerouslySetInnerHTML,evalornew Function. - A fixed component catalog. The schema rejects unknown components and unknown or free-form props (
style,className, HTML attributes). Every visual option is a fixed set of values. - Governed actions. A button that changes backend state must be wrapped by an
McpMutationnaming a tool from the app's registry. Until then it is disabled. - Checked three times: the parser rejects unknown tools, the browser validates params against the tool's schema before sending, and the server re-validates both before running anything. The server doesn't trust the browser.
- Contained failures. A component that crashes shows a fallback; the rest of the screen keeps working. (In SwiftUI, which can't catch a failing view, components only ever receive validated props, so rendering can't fail.)
- Real actions are authorized. The reference server signs people in by emailed link, gives every tool an access rule, checks ownership against its own data, and uses idempotency keys so no action runs twice. Validation proves a request is well-formed; these prove it is allowed.
How it compares
The same screens in each format; sizes are counted offline the way OpenUI's benchmark counts them, and validity was checked in fresh Claude.ai chats (docs/COMPARISON.md; sizes reproduce with npm run bench):
- Compact: on the nine model-check screens, Omni-IR uses 39% fewer tokens than A2UI, 55% fewer than json-render, 50% fewer than HTML with Tailwind and 18% fewer than React JSX. OpenUI Lang, the closest relative, is 4–10% smaller still: it allows positional arguments and components nested inside other components.
- Streams line by line: the first content can be drawn after about 30–40 tokens. A2UI sent as one message, and generated React code, show nothing until the reply is complete.
- Tighter control than the alternatives: no logic in the stream, pictures only from the app's registry, every data-changing action governed with its params checked, a 98-case conformance suite (plus 22 transport cases), and native web, iOS and Android renderers that pass it.
- A smaller catalog: 27 components against OpenUI's 53. Tables, dropdowns, switches, tabs and notices arrived in Step 10, bar, line and pie charts in Step 11; Omni-IR now draws 5 of OpenUI's 7 benchmark scenarios.
- Reliable, and safer when asked for something risky: in fresh Claude.ai chats, Claude wrote both Omni-IR and OpenUI Lang validly on the first try for 9 of 9 requests. Asked for a button that permanently deletes the account, with no tool for it, Omni-IR left the button unwired and said so, while the OpenUI Lang reply wired it to an invented action.
Components
Stack, Card, Heading, Text, Input (one line, or several with lines), DateInput, Select, Switch, Button, Divider, Badge, Notice, Skeleton, Image, Rating, List, ListItem, Table, TableRow, Tabs, Tab, Message, BarChart, LineChart, PieChart, Series, Slice, plus McpMutation for governed actions. Select, Switch, Table, Tabs and Notice are new in 0.2.0, the charts in 0.3.0. Charts carry data only: the model sends a title, labels and numbers, and each renderer chooses the colours, legend and readouts. Images come only from the app's asset registry (app/assets.ts), named by the model, never as URLs. npm run prompt:print shows every prop and allowed value, generated from the schema.
Using a real model (optional, costs money)
The server includes a Claude adapter that is off by default. It runs only if you set OMNI_MODEL=claude and provide Anthropic API credentials, and each request is billed by Anthropic. A daily cap (OMNI_DAILY_CAP, default 50) limits generations. Everything else in this repo, including CI, uses the free mock model.
To check how well a model follows the protocol without paying for API calls, paste the output of npm run prompt:print into a Claude.ai chat, save its replies to files, and run npm run validate on them.
Project layout
The format is specified in SPEC.md. Design decisions and build history are in PLAN.md, PLAN-SERVER.md, PLAN-PLAYGROUND.md, PLAN-SPEC.md, PLAN-COMPONENTS.md, PLAN-NPM.md, PLAN-IOS.md, PLAN-ANDROID.md, PLAN-CATALOG.md and PLAN-CHARTS.md.
Roadmap
What is done and what is planned, with dates: docs/ROADMAP.md.
- Bi-directional AST sync tooling
Contributing
Contributions are welcome: bugs, spec proposals, new components, other implementations and docs. Start with CONTRIBUTING.md. How the spec changes and who decides: GOVERNANCE.md. Please follow the code of conduct, and report security problems privately as described in SECURITY.md.
