This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Everything runs through bun (never npm/npx/yarn):
bun install
bun run typecheck # tsc --noEmit for root AND packages/agui — ALWAYS use this script;
# a bare `tsc` may hit an older PATH TypeScript that rejects this
# repo's ignoreDeprecations setting (TS5103)
bun run lint # biome check . — fix violations with: bunx biome check --write .
bun run test # vitest run (happy-dom), all suites
bun run test -- packages/agui/src/events.test.ts # single test file
bun run build # design tokens → tsup (core dist/) → styles → packages/agui dist/
bun run playground:full # vite :5173 + bun API server :3030 (LLM + mock AG-UI agent)
bun run playground:build # vite build — this is what Vercel deploys (vercel.json)Gate order before declaring work done: typecheck → lint → test → build. CI (.github/workflows/ci.yml) runs lint → typecheck → test → build on push/PR to main. Pushing main deploys to Vercel — never push without being asked.
- TDD is the workflow here: write the failing test first, watch it fail, then implement. Tests live next to sources (
*.test.ts), vitest + happy-dom, no mocks unless unavoidable. - Pure functions over classes; the registry is the only global state in the core.
- Conventional-commit messages; no backticks inside commit messages.
DESIGN.mdis the design-token theme file (YAML consumed bybun run tokens→src/design-tokens.css, and injected into the LLM system prompt) — not an architecture doc.
Three layers, one direction of data flow: agents emit ComponentSpec JSON → registry dispatches to pure renderers → DOM. User interactions flow back as ActionEvent { action, payload? }.
types.ts— theComponentSpecdiscriminated union (~23 kinds) +BUILTIN_KINDSruntime list.schemas.ts— hand-authored plain-JSON-Schema per builtin kind. Must stay in sync withtypes.ts— adapters derive the agent-facing tool schema from these. Child-spec arrays reference#/$defs/componentSpec; the tool-schema assembler supplies that def.registry.ts—register(kind, renderer, schema?)/getSchema/listKinds;createElementnever throws (unknown kinds render a fallback node).components.ts— every builtin is a pure(spec, onAction?) => HTMLElement, callable without the registry.index.ts— auto-registers builtins on import;render()(replaces + preserves focus/selection),append(),clear().
Connects any AG-UI protocol agent (https://docs.ag-ui.com) to the renderer. Deps: @ag-ui/core + fast-json-patch only (deliberately NOT @ag-ui/client — rationale in docs/agui-integration-design.md).
events.ts— dispatcher: AG-UI events → DOM. Streamed text → progressive paragraphs;render_ui/append_uitool calls →render()/append(); CHUNK expansion;MESSAGES_SNAPSHOTmay carry tool calls some servers never stream. Philosophy: warn-and-skip, never throw on agent output.agent.ts— run loop owning transcript, threadId, abort, follow-up runs. Key protocol fact: AG-UI has no client→server event channel — everything client-initiated (user messages, tool results, readables→context, state, interrupt resume entries) rides the next run'sRunAgentInput.http.ts— reference POST + SSE connector, events validated via@ag-ui/coreEventSchemas. Transport-agnostic: anything(input, signal) => AsyncIterable<event>works assource.hitl.ts/readables.ts/state.ts—renderAndWait(first ActionEvent resolves; abort rejectsRunCancelledError), context readables, shared-state store (RFC 6902 deltas, skip-on-failure). HITL supports both classic tool-result and first-class interrupt/resume flows.
api/*-core.ts modules are runtime-agnostic and shared by both the Vercel functions (api/agent.ts, api/agui.ts) and the local bun server (playground/server.ts, port 3030):
agent-core.ts— LLM agent (Vercel AI SDK via AI Gateway) emitting the ad-hoc{thinking|render|append|done|error}SSE protocol for the original playground page.agui-core.ts— keyword-routed mock agent speaking schema-valid AG-UI events; needs no API key; drives/agui.html.
Playground is a vite multi-page app (index.html = LLM demo, agui.html = AG-UI demo); vite proxies /api and /agui to :3030.
The repo is a bun workspace where the core package stays at the root and packages/agui depends on it by published name. Because bun can't workspace:-link the root package and file: deps are stale copies, dev-time resolution of @baruch-eric/stream-ui is handled by:
resolve.aliasin bothvitest.config.tsandvite.config.ts→src/index.ts- a
pathsmapping inpackages/agui/tsconfig.json(safe: no composite project references in this repo; that tsconfig is noEmit-only — tsup owns the build viapackages/agui/tsup.config.ts, which marks core/@ag-ui/core/fast-json-patchexternal)
Don't "simplify" this without re-testing all four consumers (tsc, vitest, vite dev, vite build).
@baruch-eric/stream-ui is not yet published to npm; the adapter's peer dep is temporarily peerDependenciesMeta.optional: true so installs don't 404 — remove that flag when the core is published.
- The playground server walks parent dirs loading
.envfiles (e.g.~/Arik/dev/.env). The AI SDK gateway readsAI_GATEWAY_API_KEY;VERCEL_AI_GATEWAY_API_KEYis aliased byapplyGatewayKeyAlias()— it must be re-applied after env loading (module-load ordering bug happened once already). - The AG-UI protocol is pre-1.0 and moving (SDK 0.0.57, 33 event types, TypeScript SDK lives at
sdks/typescript/packages/*in their repo). Unknown event types are deliberately warn-and-skip at both transport and dispatcher level. - Playground themes (settings ⚙️, localStorage) override design tokens — the Heritage theme paints
primaryred; don't mistake it for a variant bug. docs/agui-listing.mdholds prepared ag-ui Clients-table material; their CONTRIBUTING requires an issue-first flow and npm-published packages before submitting.