Skip to content

Agent Interface SDK#692

Merged
abhithesys merged 107 commits into
mainfrom
abhishek/openui-chat
Jun 29, 2026
Merged

Agent Interface SDK#692
abhithesys merged 107 commits into
mainfrom
abhishek/openui-chat

Conversation

@ankit-thesys

@ankit-thesys ankit-thesys commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

What & why

This PR lands the Agent Interface SDK — a ground-up redesign of OpenUI's chat surface. It replaces the legacy flat-prop chat shells (Shell, CopilotShell, BottomTray, OpenUIChat stories) with a single composable <AgentInterface> component built on an adapter-based headless core (storage: ChatStorage + llm: ChatLLM), reworks artifact/panel state into purpose-built stores, ships a real tool-call activity model with accessible rendering, renames the Artifact* panel API to DetailedView*, adds two reference example apps (OpenUI Cloud + OpenAI Responses), and replaces the old chat/* docs with a full Agent Interface documentation section.

It is a large, intentionally breaking change: +19,752 / −13,719 across 381 files. The deletions are almost entirely the retired shells and their stories/docs.

Heads-up for reviewers: this is a breaking release for @openuidev/react-headless and @openuidev/react-ui. The consolidated Breaking changes & migration section near the bottom is the contract consumers must act on; docs/agent/guides/migrating documents the same mapping for end users.


Highlights

  • Adapter core. ChatProvider no longer takes apiUrl/threadApiUrl/streamProtocol/messageFormat + 6 callbacks. It now takes llm: ChatLLM (required) and storage?: ChatStorage (defaults to in-memory). Two batteries-included factories cover the common case: fetchLLM({ url, streamAdapter }) and restStorage({ baseUrl }).
  • One component, composable slots. <AgentInterface> provides an opinionated two-panel desktop layout (collapsible sidebar + thread + resizable detailed-view panel) and a mobile slide-over, all overridable through a static slot namespace (AgentInterface.Sidebar, .Composer, .Workspace, .Route, .Welcome, …).
  • Real tool-call status. A ToolActivity discriminated union (streaming → executing → complete | error) driven by genuine AG-UI stream events (TOOL_CALL_CHUNK/_END/_RESULT), replacing the old "guess from whether content arrived" heuristic. Backed by an accessible ToolCall primitive set and a staggered-reveal timeline.
  • Artifact → DetailedView rename. The Artifact panel factory/hooks/components are replaced by identically-shaped DetailedView equivalents (config key panelactual). Artifact registration (the browser rail) is now a separate concern via defineArtifactRenderer + defineArtifactCategories.
  • Accurate artifact classification. ParsedArtifact.meta gains an optional type; the renderer registers each artifact under its real kind when the parser sets it, so a renderer that serves multiple kinds labels each correctly in the browser.
  • Streaming correctness fixes. A shared SSE line iterator fixes cross-chunk line-splitting that silently dropped large payloads; the OpenAI-conversation format no longer duplicates "Behind the scenes" groups on reload.
  • Two new example apps + every existing example migrated to AgentInterface.
  • Docs: new agent/* section (18 pages) + navbar tab; the old chat/* section is removed.

1 · Headless core — adapter-based ChatProvider (@openuidev/react-headless)

The monolithic ChatProviderProps is replaced by a two-adapter API.

  • ChatProviderProps is now { llm: ChatLLM; storage?: ChatStorage; artifactRenderers?; artifactCategories?; children }. Removed: apiUrl, threadApiUrl, streamProtocol, messageFormat, fetchThreadList, createThread, deleteThread, updateThread, loadThread, processMessage.
  • createChatStore now takes { storage, llm }. All thread CRUD delegates to storage.thread.*; sending delegates to llm.send({ threadId, messages, signal }); the stream protocol is read from llm.streamProtocol. The old ephemeral-thread fallback is gone — a createThread always happens on first send (no-storage = in-memory threads with real UUIDs).
  • New adapters/ layer:
    • fetchLLM({ url, streamAdapter, messageFormat?, headers?, fetch? }): ChatLLM — POSTs an AG-UI RunAgentInput-shaped body and streams the response through the chosen adapter.
    • restStorage({ baseUrl, messageFormat?, headers?, fetch? }): ChatStorage — implements the thread channel against the same REST conventions the old threadApiUrl used (/get, /create, /get/:id, /update/:id, /delete/:id). Artifact storage is supplied separately as storage.artifact.
    • createDefaultInMemoryStorage() — internal default (not exported).
  • New exported types: ChatLLM, ChatStorage, ThreadStorage, ArtifactStorage, Artifact, ArtifactSummary, ArtifactCategory, ArtifactListParams, FetchLLMOptions, RestStorageOptions.
  • @ag-ui/core bumped ^0.0.45 → ^0.0.53.

2 · State model — DetailedView, ThreadContext & renderer registries

The old single ArtifactContext/createArtifactStore (tracked only activeArtifactId) is deleted and split into focused stores + stateless registries, wired as nested contexts in ChatProvider (Chat → DetailedView → ThreadContext → ArtifactRenderers → ArtifactStorage → ArtifactCategories):

  • DetailedView store — one active detailed-view id at a time, shared across apps/artifacts/custom consumers (useDetailedView(viewId), useActiveDetailedView(), useDetailedViewPortalTarget()).
  • ThreadContext store — per-thread artifact registry keyed by (id, version); ArtifactEntry { id, version, heading, type, updatedAt? }, versions kept sorted ascending. Read via useArtifactList(filter?).
  • ArtifactRenderersContext — a prebuilt { byToolName, byType } Map registry with first-wins dedup on duplicate toolName/type (dev-mode warning). lookupArtifactRenderer / lookupArtifactRendererByType. Captured once at mount — changing the artifactRenderers prop after mount is ignored with a dev warning, so consumers must memoize or use a module constant.
  • defineArtifactRenderer<Props>(config){ type, toolName: string | string[], parser({ args, response }, { isStreaming }) → ParsedArtifact<Props> | null, preview, actual, icon?, label? }. ParsedArtifact.meta is { id, version, heading, type? } | nullmeta: null renders but skips registration (the common streaming case), and the optional type lets a parser stamp the artifact's real kind.
  • defineArtifactCategories(groups) — accepts [{ name, renderers, icon? }] and returns { artifactRenderers, artifactCategories } ready to spread onto <AgentInterface>; each category's filter.type is derived from its renderers.

Removed (breaking): ArtifactContext, useArtifactStore, ArtifactState/Actions/Store, useActiveArtifact, useArtifact, useArtifactPortalTarget, openArtifact/closeArtifact/resetArtifacts.

3 · Streaming & message formats

  • Shared sseLineIterator (stream/adapters/_shared/sseLines.ts) buffers the partial line straddling a network-chunk boundary and flushes the tail at end-of-stream — fixing a bug that silently dropped large SSE payloads. ag-ui, openai-completions, and openai-readable-stream adopt it; openai-responses and langgraph get the equivalent buffer/flush inline (and move if (done) break after block processing so the final block isn't dropped).
  • processStreamedMessage gains first-class tool-call handling: TOOL_CALL_CHUNK (AG-UI combined form — lazily starts a call, fills a late-arriving name, accumulates args; previously dropped), TOOL_CALL_END (marks the call executing), TOOL_CALL_RESULT (creates/upserts a real ToolMessage, mapping isError/error). RUN_ERROR clears in-flight executing flags without synthesizing empty tool messages (no blank renderer flash). TEXT_MESSAGE_START no longer deletes+recreates the assistant message — avoiding ordering corruption when tool messages already landed. deleteMessage is removed; markToolExecuting/clearToolExecuting are added (optional).
  • ThreadState.executingToolCallIds: Set<string> drives the "executing" status; cleared on run start, thread switch, cancel, and the run's finally (so adapters that emit TOOL_CALL_END with no TOOL_CALL_RESULT don't stick).
  • OpenAI-conversation format fromItems now buffers all tool messages of a multi-call assistant turn and flushes them together — eliminating duplicate "Behind the scenes" groups on thread reload.
  • openai-responses handles function_call_output items (response.output_item.added), emitting TOOL_CALL_RESULT with call_id/output.

4 · Tool-call activity & rendering (@openuidev/react-ui)

  • ToolActivity<T> + pairToolActivity (react-headless/store/toolActivity.ts): a 4-state union pairing calls↔results by toolCallId (not positional), reading executingToolCallIds, attributing orphan tool messages, with partialJSONParse for tolerant mid-stream args. Exposed via useToolActivities(message, allMessages).
  • ToolCall compound primitives (Root/Trigger/Content/StatusIcon/StatusText/ToolName/Parameters/Result): every part takes an optional render escape hatch. StatusText emits role="status" aria-live="polite"; Trigger emits aria-expanded/aria-controls; Content emits role="region". Plus DefaultToolCard (chevron collapsible, opens on error) and TimelineToolCard (dot+connector row).
  • ToolCallTimeline replaces the legacy BehindTheScenes + ToolCallComponent + ToolResult composition: staggered reveal for live messages (historical start fully revealed), a settled toggle label showing failure count (Behind the scenes · 2 failed), and a visually-hidden live region. "Thinking" derives from useThread(s => s.isRunning) + last status — no magic _request/_response keys.
  • Dispatchers (_shared/tool-renderer/): ToolCallEntry/TimelineEntry look up useArtifactRenderer(toolName) and render the matched renderer xor the default card — never both. ToolActivityRenderer runs renderer.parser and:
    • registers the artifact under meta.type ?? renderer.type so a shared renderer classifies each kind correctly in the browser rail;
    • renders ToolCallErrorFallback inline if the parser throws (one bad renderer can't blank the thread);
    • suppresses the preview when the tool call errored even if the parser produced props — matching reload behavior, since the persisted artifact was never created.
    • ToolMessageRenderer is kept as a @deprecated backward-compatible wrapper.
  • Built-in webSearchRenderer (renderers/webSearchRenderer.tsx) for web_search/webSearch — reads { query, reasoning, sources } via partialJSONParse (meta: null, inline-only), with SourceIcon favicon + 16×16 fallback. Not auto-registered; consumers pass it in artifactRenderers.
  • Accessibility: run-gated animations (shimmer/spin only while streaming|executing && isRunning && isLast), a comprehensive prefers-reduced-motion block, aria-expanded/aria-controls on toggles, role="region" panels, and focus-visible rings.

5 · <AgentInterface> component suite (@openuidev/react-ui)

A complete, opinionated layout wrapping ChatProvider + ThemeProvider + Nav/Starters/Labels/Store providers.

  • Props (additive over ChatProviderProps): componentLibrary, components ({ AssistantMessage?, UserMessage? }), theme, disableThemeProvider, logoUrl, agentName, labels, starters, starterVariant, path/defaultPath/onNavigate (controlled/uncontrolled nav), scrollVariant ('always' | 'user-message-anchor'), scrollOnLoad (default true). Children are slot-extracted; duplicate slots warn and use the first.
  • Static slot namespace: Sidebar, SidebarHeader/Content/Separator/Item, ArtifactNav, Workspace, Route, MobileHeader, ThreadHeader, Welcome, Composer, NewChatButton, ThreadList, Messages, MessageLoading, ScrollArea.
  • Navigation & routing: useNav() controlled/uncontrolled split (onNavigate present → controlled). AgentInterface.Route declares exact-path custom pages that replace the thread region. The artifacts/ path prefix is reserved and matched before user Routes.
  • Sidebar / ThreadList: animated collapse/expand (desktop) and slide-over (mobile, auto-detected via element width); ThreadList groups by Today/Yesterday/7d/30d/Year/Older with a per-thread Delete action.
  • Workspace (right rail): lists the active thread's artifacts, one section per artifactCategories entry; tabs/filtering were dropped — all sections show, and artifacts matching no category land in a trailing fallback section (never dropped). Hidden when empty, when a DetailedView is active, or off the thread view.
  • Artifact browser: ArtifactBrowserPage (debounced search + cursor pagination via ArtifactStorage.list, with an explicit not-found state for unknown categories) and ArtifactViewPage (renders renderer.actual from stored content with args: undefined, isStreaming: false). ArtifactNav renders one item per category (or a single "Artifacts" item), nothing when artifact storage is absent.
  • Detailed-view subsystem (new src/detailed-view/ top-level export): DetailedView<P>(config) factory (title, preview, actual, panelProps), DetailedViewPanel (portals into the nearest target, error-boundary wrapped, configurable header), DetailedViewOverlay (mobile slide-in/out), DetailedViewPortalTarget. ResizableSeparator implements the WAI-ARIA window-splitter pattern (drag + ArrowKeys/Shift/Home/End); useDetailedViewResize clamps the chat panel to [420px, 80%] and auto-collapses the sidebar when a detailed view opens.

6 · Legacy shell retirement (BREAKING)

  • Deleted packages/react-ui/src/components/Shell/, CopilotShell/, BottomTray/, and the OpenUIChat stories — including their .scss, stories, and orphaned assets (the bulk of the −13,719 lines). The Shell/CopilotShell namespace star-exports are removed from index.ts.
  • Artifact*DetailedView* rename in the public surface:
    • Artifact factory → DetailedView (config key panelactual); ArtifactConfig/ControlsDetailedViewConfig/Controls.
    • ArtifactPanel/Overlay/PortalTarget (+ prop types) → DetailedView* equivalents.
    • useActiveArtifact/useArtifactuseActiveDetailedView/useDetailedView.
    • index.ts now does export * from '@openuidev/react-headless' and export * from './components/AgentInterface'. ⚠️ The re-exported headless Artifact domain-model type now shadows the old Artifact factory name — code doing Artifact({...}) gets a type error (caught at build), not a silent runtime bug.
  • Wire format: utils/contentParser.ts (XML <content>/<context> envelope) replaced by utils/sentinelParser.ts (the ]]>openui: inline-sentinel format) with an internal parseLegacyXml fallback so previously persisted messages still render.
  • useScrollToBottom gains scrollOnLoad (default true, surfaced on AgentInterfaceProps) to suppress initial-load auto-scroll without affecting mid-stream following. restore user-message styling fix included.

7 · Example apps & build config

  • examples/openui-cloud (new) — OpenUI Cloud two-plane integration: generation plane (/api/chat llm proxy, THESYS_* key) + read/edit plane (useOpenuiCloudStorage over /v1 with fct_ frontend-token mint/refresh). Consumes the vendored @openuidev/thesys, wires defineArtifactCategories for slides/report. Excluded from the pnpm workspace (!examples/openui-cloud in pnpm-workspace.yaml) because it depends on a gitignored file: tarball and a cross-repo link: that don't exist in CI — keeping monorepo pnpm install --frozen-lockfile green.
  • examples/openui-responses-chat (new) — OpenAI Conversations+Responses reference: restStorage + openAIResponsesAdapter, a MAX_TOOL_TURNS agentic loop with file-based thread persistence, and a create_code_artifact renderer.
  • examples/openui-artifact-demo migrated from processMessage/openAIAdapter to fetchLLM; the ArtifactCodeBlock openui-lang component is dropped in favor of a create_code_block tool + codeBlockRenderer.
  • All other examples (hands-on-table-chat, shadcn-chat, material-ui-chat, langgraph-chat, mastra-chat, openui-chat, harnesses, react-email) and the fastapi-backend frontend migrated off the legacy shells to AgentInterface. The openui-cli scaffold template is intentionally kept on FullScreen for now (reverted).
  • lang-core stream parser fix: createStreamParser scans completed statements over the preprocessed (cleaned) buffer and recovers when a code fence/prose prefix shifts offsets — fixing statement-boundary corruption when markdown precedes the openui-lang program.

8 · Docs

  • New docs/content/docs/agent/ section (18 MDX pages: Getting Started / Core Concepts / Customize / Reference / Guides) + a new "Agent Interface" navbar tab. Reference pages cover all AgentInterface props, adapters/formats, defineArtifactRenderer, the hook set, components/slots, and self-hosting; guides/migrating documents the full legacy→AgentInterface mapping.
  • Removed the entire old docs/content/docs/chat/* section and retargeted READMEs/skill off the deleted shells.
  • Added multimedia (videos/images) and a self-contained static build guide served from the docs site.

Breaking changes & migration

Legacy (removed) New
ChatProvider/AgentInterface apiUrl, threadApiUrl, streamProtocol, messageFormat llm={fetchLLM({ url, streamAdapter })}, storage={restStorage({ baseUrl })}
fetchThreadList/createThread/deleteThread/updateThread/loadThread/processMessage props storage.thread.* (e.g. restStorage) + llm.send
Artifact({...}) factory, ArtifactConfig/Controls DetailedView({...}) (config panelactual), DetailedViewConfig/Controls
ArtifactPanel/ArtifactOverlay/ArtifactPortalTarget (+ props) DetailedViewPanel/Overlay/PortalTarget (+ props)
useArtifact, useActiveArtifact, useArtifactPortalTarget useDetailedView, useActiveDetailedView, useDetailedViewPortalTarget
ArtifactContext, useArtifactStore, openArtifact/closeArtifact useDetailedViewStore, setActiveDetailedView/reset
import * as Shell / import * as CopilotShell removed — compose <AgentInterface> slots
processStreamedMessage(..., deleteMessage) markToolExecuting/clearToolExecuting (optional)

Notes consumers should know: artifactRenderers must be memoized/module-constant (registry frozen at mount); restStorage covers only the thread channel (provide storage.artifact separately); toolName matching is exact-match only in this PR (no RegExp/* dispatch yet); the top-level Artifact name now resolves to the headless domain type, not the old factory.

Test plan

  • react-headless unit suites: restStorage, processStreamedMessage (+ tool-status), toolActivity, sseLines, artifactRendererRegistry (first-wins + dev warns), createThreadContextStore, createDetailedViewStore, thread-switch resets.
  • Adapter migration: confirm old apiUrl/threadApiUrl props are TS errors; fetchLLM + restStorage drive a thread end-to-end.
  • Tool calls: TOOL_CALL_CHUNK-only stream renders; TOOL_CALL_ENDexecuting; run ends without result → no perpetual shimmer; errored call suppresses the preview; Behind the scenes · N failed count; web_search renders preview + side panel.
  • AgentInterface: no artifact storage → ArtifactNav/Workspace/browser render nothing; unknown category → not-found + "View all artifacts"; uncategorized artifacts appear in the fallback section; DetailedView opens → sidebar collapses, splitter clamps [420px, 80%], keyboard resize works; <768px → slide-over sidebar.
  • Shell removal: import * as Shell / import { Artifact } (as factory) fail to compile; legacy XML-envelope messages still render via parseLegacyXml; new ]]>openui: sentinel parses.
  • a11y: prefers-reduced-motion stops all reveal/shimmer/spin animations; status changes announce via the live region.
  • Examples: openui-cloud (with vendored tarball + keys) loads threads and opens slides/report panels; openui-responses-chat multi-turn tools + code artifact; migrated examples build and run; monorepo pnpm install --frozen-lockfile stays green with openui-cloud excluded.
  • lang-core: an openui-lang stream prefixed by markdown prose/fences parses without losing completed statements.
  • Docs: /docs/agent/* renders with the new navbar tab; old /docs/chat/* is gone; migration guide links resolve.

Known follow-ups / caveats

  • docs/agent/getting-started/openui-cloud.mdx is a "Coming soon" stub; several concept/customize pages carry inline TODO/ASSUMED and "add video/image" placeholders (notably tools.mdx).
  • The allowBuilds stanza added to pnpm-workspace.yaml has placeholder string values and is inert until populated with real booleans.
  • openui-cloud is not installed by a root pnpm install (workspace-excluded); its vendor/ tarball must be produced from genui-sdk first.
  • The retired Shell/CopilotShell/BottomTray directories are fully deleted (no dead code left on disk).

abhithesys and others added 30 commits May 4, 2026 16:43
…istries + hooks

refactor: rename Artifact API to DetailedView across react-headless and react-ui
  panel via defineAppRenderer (react-headless). Renderers match by
  toolName (literal string or RegExp), captured at ChatProvider mount
  with first-wins on duplicates and dev-mode warnings on ambiguity.

  ToolMessageRenderer (react-ui) dispatches each tool message to its
  matching renderer or falls back to the default ToolResult. Wired
  into Shell, CopilotShell, and BottomTray Thread components.

  Args and response are passed raw to parsers; isStreaming is reserved
  (always false) until streaming protocol lands.
… migration

  Map tool-call results to custom inline preview + detailed-view side panel
  via renderers, and migrate openui-artifact-demo to demonstrate.

  react-headless:
  - defineAppRenderer / defineArtifactRenderer factories tag the config with
    `kind` so a matched renderer registers in the apps or artifacts
    ThreadContext slice
  - AppRenderersContext + useAppRenderer for toolName lookup at mount
    (literal Map + regex array; first-wins; dev-mode duplicate + ambiguity
    warnings)
  - processStreamedMessage handles TOOL_CALL_RESULT: creates a ToolMessage
    via createMessage (signature broadened from AssistantMessage to Message)
  - TEXT_MESSAGE_START is now a no-op — the previous delete+recreate broke
    ordering when tool messages had already been appended; persistence
    layers should map ids on save instead. Drop the unused deleteMessage
    callback from processStreamedMessage's interface.

  react-ui:
  - ToolMessageRenderer dispatches matched tool messages; RendererInstance
    runs parser → meta → register/unregister (routed by `kind`) and renders
    preview inline + actual via DetailedViewPanel
  - GenUIAssistantMessage renders matched tool messages outside
    BehindTheScenes so the inline preview appears in the chat surface
  - withChatProvider forwards `appRenderers` to ChatProvider (was missing
    from the prop allowlist)

  examples/openui-artifact-demo:
  - Replace ArtifactCodeBlock openui-lang component with a `create_code_block`
    tool + codeBlockRenderer (defineArtifactRenderer)
  - enrichedArgsAdapter splits the backend's {_request, _response} envelope
    into standard TOOL_CALL_ARGS + TOOL_CALL_RESULT events so processStreamed
    Message and the bridge see the same shape as a standard agentic flow
  - Override chat library preamble so the LLM is allowed to mix openui-lang
    with tool calls
  - Add create_code_block tool definition (declarative; returns {ok:true})
  - "Workspace" heading with collapse/expand toggle
  - Apps section reads useAppList(); artifacts section reads useArtifactList()
  - Each item is the latest version per id; clicking activates the matching
    DetailedView via setActiveDetailedView("${id}:${version}")
  - Active item highlighted; empty states per section + overall hint
  - Auto-collapses when a DetailedView opens (focus on the view) and
    auto-expands when it closes; manual toggles between transitions are
    preserved
  - Hidden on mobile layout (desktop only for v1)
  - Uses existing cssUtils tokens (spacing, typography, colors, radius)
  - Wired into FullScreen via ComposedStandalone
  - ShellStore gains isWorkspaceOpen / setIsWorkspaceOpen
  handling

  AppRenderers now fire on tool calls whose args are still streaming, before the
  tool result is paired in. Same React instance persists across the
  streaming → completed transition (keyed by toolCall.id), so renderers can
  swap between partial and final UI without remounting.

  - react-ui: ToolMessageRenderer accepts ToolMessage|null and signals
    isStreaming when null; RendererInstance accepts an isStreaming prop and
    propagates to controls + meta ctx; GenUIAssistantMessage dispatches every
    tool call (paired or not).
  - react-headless: openAIResponsesAdapter handles function_call_output items
    in response.output_item.added → yields TOOL_CALL_RESULT. Backends that
    inject synthetic events for server-side tool execution can now surface
    tool results to the SDK store (OpenAI itself silently absorbs
    function_call_output items into the conversation without echoing them).
  - AppRendererControls.isStreaming JSDoc rewritten — was "always false in
    v1; reserved for streaming protocol", now describes actual behavior.
    AppRendererConfig.parser JSDoc notes args may be partial JSON and response
    may be null during streaming. AppRendererConfig.meta documents the
    ctx.isStreaming parameter.
…es APIs

  behind the SDK's AG-UI wire shape.

  - Persistent chat: thread storage via OpenAI Conversations API with a JSON
    file index at .data/threads.json (Conversations API has no list method)
  - Hosted LLM: route streams from openai.responses.create with
    conversation: threadId so OpenAI auto-persists user input + responses
  - Tool execution: manual loop (Responses API has no runTools helper),
    capped at MAX_TOOL_TURNS, with a synthetic response.output_item.added
    event injected after each tool execution so the SDK surfaces results live
  - Streaming artifact: create_code_artifact tool with strict mode + property
    order (language → title → code) and a partial-JSON parser, rendered via
    defineArtifactRenderer using controls.isStreaming
  - Frontend wires openAIResponsesAdapter + openAIConversationMessageFormat;
    processMessage sends only the latest message since OpenAI holds history
  Collapse the legacy flat-props ChatProvider config behind two
  adapter interfaces:

  - ChatStorage (thread, pinning?, share?) drives the storage channel
  - ChatLLM (send, streamProtocol) drives the LLM channel

  Drops apiUrl, threadApiUrl, processMessage, loadThread, fetchThreadList,
  createThread/deleteThread/updateThread props, messageFormat, and the
  top-level streamProtocol prop. Their behavior is now configured per
  adapter; the bundled fetchLLM factory captures the previous default
  fetch + messageFormat path.

  react-headless:
  - Add src/adapters: ChatStorage / ChatLLM / Thread/Pinning/ShareStorage
    types, fetchLLM factory, internal _defaultStorage (in-memory, not
    exported) used when ChatProvider has no storage prop
  - Rewrite createChatStore to consume { storage, llm }; thread CRUD now
    delegates to storage.thread.*, message send to llm.send
  - ChatProvider props collapse to { storage?, llm, appRenderers, children }
  - Guard process.env reads with `typeof process !== "undefined"` so the
    provider/registry/detailed-view store work in non-Node runtimes
  - Migrate createChatStore / threadContextSwitch / detailedViewThreadSwitch
    tests behind a shared makeStore() helper; drop the apiUrl /
    threadApiUrl / messageFormat / ephemeral-thread describe blocks (those
    paths now live in the fetchLLM adapter)

  react-ui:
  - withChatProvider HOC: replace prop-key filtering with a destructure
    over the new shape
  - Add shared __test-helpers/mockChat.ts (makeMockStorage, makeMockLLM,
    mockSSEResponse) used by every story
  - Migrate Shell, BottomTray, CopilotShell, and OpenUIChat stories to
    <ChatProvider storage={...} llm={...}>

  examples/openui-artifact-demo: import fetchLLM from react-headless.
# Conflicts:
#	packages/react-ui/src/components/CopilotShell/Thread.tsx
#	packages/react-ui/src/components/Shell/Sidebar.tsx
#	packages/react-ui/src/components/Shell/thread.scss
#	packages/react-ui/src/components/index.scss
#	pnpm-lock.yaml
  REST ChatStorage hitting the exact endpoints the removed threadApiUrl
  prop used (/get · /create · /get/:id · /update/:id · /delete/:id), so
  consumers migrate by swapping one prop for one factory call. 8 tests
  cover URL/method/body/headers per method, messageFormat round-trip,
  and res.ok error surfacing.
- store._nextCursor: `any` → `string | undefined`, consistent with the
  ThreadStorage interface's `string` cursor.
- react-ui re-exports the adapter construction surface (fetchLLM,
  restStorage, ChatStorage, ChatLLM, ThreadStorage, Pinning/Share types,
  FetchLLMOptions, RestStorageOptions) so AgentInterface consumers don't
  need to reach into @openuidev/react-headless.
renderers, per-thread Workspace rail

Storage (react-headless):
- ArtifactStorage channel on ChatStorage.artifact — list (server-side
  name/type filters, string-cursor pagination) · get · update({id,
  content}) for editable artifacts. ArtifactSummary carries a required
  threadId (drives "go to thread"); Artifact.content must match the
  tool-response shape so renderers parse both sources identically.
- ArtifactCategory config ({ name, filter: { type[] } }) on ChatProvider;
  useArtifactStorage() + useArtifactCategories() hooks.
- BREAKING: PinningStorage / ShareStorage / ShareTarget removed (dead
  interfaces, zero consumers).

Renderer unification (BREAKING):
- defineAppRenderer, kind, AppRendererKind, AppEntry, useAppList deleted.
- defineArtifactRenderer gains type (links renderer ↔ categories ↔ stored
  artifacts) and toolName: string | string[] (RegExp matching removed);
  parser + meta merged into parser(raw, {isStreaming}) → {props, meta} |
  null. Registry is a flat byToolName map + byType index;
  useArtifactRenderer replaces useAppRenderer.
- ThreadContext unified into a single artifacts registry; entries carry
  type; useArtifactList(filter?: { type?: string[] }).
- ChatProvider prop renamed appRenderers → artifactRenderers (+ new
  artifactCategories).

Artifact browser + Workspace (react-ui / AgentInterface):
- AgentInterface.ArtifactNav — one SidebarItem per category (single
  "Artifacts" when uncategorized), auto-included in the default sidebar
  when storage.artifact is configured.
- Reserved nav paths matched before user Routes:
  artifacts/{category} → searchable list (debounced title search +
  category type filter, cursor "Load more");
  artifacts/{category}/{id} → full-page artifact view (Back, Go to
  thread, renderer actual via byType lookup, no DetailedView).
- AgentInterface.Workspace — per-thread right rail listing registered
  artifacts grouped by category; auto-shows on first artifact, hidden on
  Route/browser pages and mobile; item click opens the in-thread
  DetailedView (rail auto-collapses). Modes A/C.
- WorkspaceSidebar (Shell) sections now category-driven.
- react-ui re-exports the artifact surface (defineArtifactRenderer,
  ArtifactStorage/Category/Summary types).

Consumers migrated: openui-responses-chat page.tsx (was still on
pre-adapter flat props; now restStorage + custom llm.send),
codeBlockRenderer + codeArtifactRenderer to merged parser + type,
withChatProvider, both tool-renderer copies, mockChat/makeStore.

Tests: registry/threadContext suites rewritten for the unified model
(83 passing). Stories: ArtifactBrowser, ArtifactBrowserUncategorized,
WithWorkspace (scripted tool-call SSE), WorkspaceCustomChildren — all
Storybook-verified.
Example (examples/openui-cloud): Next.js app showing OpenUI Cloud's two-plane
integration — server-side generation with the master key, browser-direct
reads/edits with a short-lived frontend token — plus artifact renderers over the
@openuidev/thesys viewers and a one-call ChatStorage (openuiCloud). openuiCloud's
apiBaseUrl is optional and defaults to https://api.thesys.dev. The vendored
@openuidev/thesys tarball is gitignored (obtain via the registry, not this repo).

SDK:
- react-headless: upsert tool messages by toolCallId (artifact morph), SSE
  cross-read buffering + artifact_call.delta accumulation, and multi-tool reload
  grouping in fromItems.
- react-ui: artifact viewId migration, inline-sentinel message format, scoped
  ShellStoreProvider under AgentInterface, and content-header round-trip in
  GenUIAssistantMessage.
…2e fixes

- react-headless: openai-responses adapter delivers the accumulated sentinel
  carrier directly on artifact_call.delta (no JSON re-wrap).
- react-ui: rename contentParser -> sentinelParser (unified ]]>openui: family,
  adds parseArtifactSentinel); RendererInstance now follows an edit's NEW version
  in an already-open detailed view (cross-instance activeDetailedView migration);
  update sentinelParser importers.
- example: parseArtifact parses the sentinel carrier; artifactStorage.get returns
  the bare program (fixes the global artifact browser 'could not parse'); next.config
  aliases lucide dynamic-icon subpaths + the cross-repo @openuidev/thesys-server
  entry for Turbopack.
… provider

- Replace invalid `typography(primary, default)` (no such category — it
  compiled to no font/letter-spacing) with `typography(body, default)` for
  chat message text, user bubble, and artifact browser/view loading/error/
  empty text.
- SidebarHeader rendered `<img src="">` when logoUrl was empty (browsers
  resolve that to the page URL → spurious request + broken-image icon);
  skip the <img> when logoUrl is empty.
- Hoist a single `Tooltip.Provider` into Container and drop the per-instance
  providers in AgentInterfaceTooltip/SidebarTooltip so Radix's hover-delay /
  skip-delay behavior is shared across all tooltips.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ResizableSeparator between the chat and detailed-view panels was
mouse-drag only with no accessibility. Implement the WAI-ARIA window-
splitter pattern:

- role="separator", aria-orientation, tabIndex=0, aria-label, and
  aria-controls referencing both the chat and detailed-view panels;
  live aria-valuenow/min/max/valuetext as % of the container.
- Keyboard: Left/Right resize by 16px (Shift = 64px), Home/End snap to
  min/max; clamped to [420px, 80% of container].
- :focus-visible outline + visible handle.

The hook now tracks the chat-panel width in a ref (kept in sync with
style.width) so keyboard steps and ARIA read a stable value rather than
measuring mid-CSS-transition, and resets it when the detailed view
closes so the separator's mount-time ARIA is correct on reopen. Added
zero-width-container guards so resizing can't collapse the panel to 0px.

Verified in Storybook: arrows/Shift/Home/End, min/max clamping, ARIA
values, focus ring, mouse-drag regression, and reopen-after-resize.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…g-ui/core to 0.0.53

The user-message renderer only handled text and binary parts that had a
`url` (rendered as a raw <img>), silently dropping base64 (`data`)
attachments, non-image mime types, and filenames.

- Bump @openuidev/react-headless's @ag-ui/core ^0.0.45 -> ^0.0.53. This is
  additive for content (InputContent gains first-class image|audio|video|
  document parts alongside text|binary) and non-breaking for the streaming
  protocol (no EventType members removed; react-headless typecheck + build
  + 83 tests pass).
- Extract UserMessageContent into its own module and handle the full
  InputContent union exhaustively (a `default: never` guard surfaces future
  variants at compile time — it's what forced the new branches after the bump):
  - image/audio/video -> media elements; document/other -> downloadable file chip.
  - binary routed by mimeType.
  - sources (url + base64 `data`) vetted against a scheme/mime allowlist
    (reject javascript:/blob:/etc.); unusable or failed-to-load media degrade
    to a file chip instead of being dropped or showing a broken glyph.
  - filenames surfaced; real alt/aria-label; loading="lazy".

Verified in Storybook: url/data images, audio, pdf/zip chips, and a
javascript: source were all handled correctly (no unsafe src reaches the DOM).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Navigating to a reserved `artifacts/{category}` path whose category isn't
configured (stale/renamed path, hand-edited or bookmarked URL) previously
fell through to an unfiltered `storage.list()` and rendered EVERY artifact
under the bogus category name — looking like a real category that contains
everything.

- Detect the case (`categoryName` set but not in `artifactCategories`) and
  render a "No category named X" state with a "View all artifacts" button;
  skip the storage query entirely.
- Remove the dead, required `title` prop from `ArtifactPreviewIllustration`
  (the component is aria-hidden and never rendered it) and its call sites,
  clearing a pre-existing unused-var lint error on this file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tup reproducible

- page.tsx: use the useOpenuiCloudStorage() hook in-component (the module-scope
  openuiCloud() factory was replaced upstream in @openuidev/thesys on ap-server).
- README: add a "Local dependency wiring" section so teammates can reproduce the
  gitignored vendor setup (build genui-sdk c1 + c1-server on ap-server, pack/copy
  into vendor/, pnpm install --force); refresh the hook + renderer references.
- Add .env.example template + a .gitignore exception (!.env.example) so it ships
  while .env.local stays ignored.
- next.config: turbopack alias @openuidev/thesys-server -> vendored build, plus
  lucide dynamic-icon stubs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ankit-thesys and others added 2 commits June 29, 2026 19:31
Delete the openui-artifact-demo example app in its entirety.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
@ankit-thesys ankit-thesys changed the title Abhishek/openui chat Agent Interface SDK Jun 29, 2026
ankit-thesys and others added 14 commits June 29, 2026 19:54
# Conflicts:
#	docs/content/docs/openui-lang/examples/harnesses/pi-agent-harness.mdx
#	examples/harnesses/pi-agent-harness/src/app/page.tsx
#	pnpm-lock.yaml
Delete the openui-responses-chat example app in its entirety.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
Remove a TODO and internal branch references from the DetailedViewPanel
height-fix comment; keep the technical rationale.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
abhithesys
abhithesys previously approved these changes Jun 29, 2026
@abhithesys abhithesys enabled auto-merge (squash) June 29, 2026 18:09
@abhithesys abhithesys disabled auto-merge June 29, 2026 18:09
@abhithesys abhithesys enabled auto-merge (squash) June 29, 2026 18:11
@abhithesys abhithesys merged commit 17c1dc8 into main Jun 29, 2026
1 check passed
@abhithesys abhithesys deleted the abhishek/openui-chat branch June 29, 2026 18:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants