Shared AG-UI + A2UI protocol bridge — contract validation, event vocabulary, an injection guard, and a runtime-agnostic tool-call combinator for agents that render A2UI surfaces.
Two layers, one package: the CORE (dependency-light, zod-validated contracts and plain logic, no React) is the default entry point. An optional
@qte77/a2ui-agui-kit/reactentry point adds the presentation layer (A2UISurface, CatalogViewer, EventStream), and@qte77/a2ui-agui-kit/styles.cssships the A2UI surface styles. Both are optional — a consumer that only needs contract validation never pulls in React.
Extracted from agenthud-agui-a2ui (the canonical superset) and ldnmxx-hack (the domain-agnostic subset) so both apps — and any future one — validate and prompt against the same A2UI contract instead of hand-copying it:
contract.ts— zod schemas for the A2UI wire format (beginRendering / surfaceUpdate / dataModelUpdate), with an acyclic-tree guard, plus a generic recording/decision-tree shapeevents.ts— the AG-UI event-type vocabulary,EventLogEntry/AgentEvent, andappendLogEntry(coalesces streamed text deltas)applyA2UIEvent.ts— the seam that validates an event's A2UI batch and hands it to an injectedrendercallback; unknown event types pass through untouchedguard.ts—detectInjection: a cheap first-pass prompt-injection/jailbreak checkrenderTool.ts— therender_uitool JSON schema +isSelfContainedBatchvalidatorproviders.ts—Provider/ToolSpec/runChain: a transport-agnostic first-valid-wins combinator for chaining forced-tool-call backendsprompt.ts—buildSystemPrompt: a parameterized A2UI system-prompt builder (the app supplies its own domain instructions)
Optional React presentation layer (@qte77/a2ui-agui-kit/react, peer-deps react +
@a2ui/react):
A2UISurfaceProvider/A2UISurface— wraps@a2ui/react'sA2UIProvider+A2UIRenderer, pre-registers the default component catalog, and applies the kit's theme.onActionis a plain callback prop ((actionName: string) => void) — wire it to your own agent/transport, or omit it for a no-op.CatalogViewer— a modal listing the A2UI standard component catalog.EventStream— renders anEventLogEntry[]event log; accepts an optionalrenderExtra?: (entry) => ReactNodeto append per-entry content (e.g. a consumer's own usage chip) without forking the component.qteA2uiTheme(froma2uiTheme.ts) — routes the A2UI catalog's themeable components to the kit'sqte-*class hooks, styled instyles/a2ui.css.
@qte77/a2ui-agui-kit/styles.css — the .a2ui-surface .qte-* component rules qteA2uiTheme
targets, plus the surface's motion/skeleton/busy states. Subtle functional depth (per qte77's
brand/DESIGN.md "Motion & effects"): cards and the "working" chip use var(--shadow-card) for a
warm, low-contrast elevation, and the loading skeleton uses a token-driven gradient shimmer; the
chip is not a pill. Motion is limited to a subtle entrance and low-contrast pulses, all collapsed
under prefers-reduced-motion. It expects @qte77/ui-theme's CSS custom properties (--color-* /
--radius-* / --font-* / --shadow-card) to already be in scope — import that stylesheet first.
GitHub Packages requires an auth token even to install a public package. Add an
.npmrc next to your package.json (see .npmrc.example):
@qte77:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}Locally, export NODE_AUTH_TOKEN as a PAT (or fine-grained token) with read:packages.
In CI, set it from the job's GITHUB_TOKEN (with permissions: packages: read) or a
dedicated secret.
npm install @qte77/a2ui-agui-kit zodimport {
A2UIMessageBatchSchema,
applyA2UIEvent,
appendLogEntry,
detectInjection,
RENDER_UI_TOOL,
isSelfContainedBatch,
runChain,
buildSystemPrompt,
type Provider,
type ToolSpec,
} from "@qte77/a2ui-agui-kit";
const entry = applyA2UIEvent(event, Date.now(), (messages) => renderSurface(messages));
const log = appendLogEntry(previousLog, entry);To use the React presentation layer, install react, react-dom, and @a2ui/react alongside
this package, import @qte77/ui-theme (or your own tokens) and this kit's styles once, then
render the surface:
import "@qte77/ui-theme/tailwind/tokens.css"; // --color-*/--radius-*/--font-* tokens
import "@qte77/a2ui-agui-kit/styles.css";
import { A2UISurfaceProvider, A2UISurface, EventStream } from "@qte77/a2ui-agui-kit/react";<A2UISurfaceProvider onAction={(name) => sendAction(name)}>
<A2UISurface fallback={<div className="qte-skeleton" />} />
</A2UISurfaceProvider>
<EventStream events={log} renderExtra={(entry) => <UsageChip entry={entry} />} />Two apps (agenthud-agui-a2ui, ldnmxx-hack) independently grew the same A2UI contract, event-log shape, and injection guard, with agenthud's superset drifting from ldnmxx-hack's subset each time either changed. Extracting the shared, dependency-light pieces into one package keeps both — and any future AG-UI/A2UI consumer — validating against one schema instead of two that can silently diverge. Executes qte77/agenthud-agui-a2ui#211.
- A2UI specification · AG-UI
- Contributing — dev setup, tests, releasing
- AGENTS.md — behavioral rules for AI agents working on this repo
- docs/decisions/ — ADRs (why A2UI as the render layer + revisit triggers)
- agenthud-agui-a2ui — superset source
- ldnmxx-hack — subset source
Apache-2.0 — see LICENSE. Ported code is attributed in NOTICE.