Skip to content

feat(cli): add openui-cloud template (cloud auth + telemetry)#673

Open
Aditya-thesys wants to merge 104 commits into
mainfrom
cli-openui-cloud-template
Open

feat(cli): add openui-cloud template (cloud auth + telemetry)#673
Aditya-thesys wants to merge 104 commits into
mainfrom
cli-openui-cloud-template

Conversation

@Aditya-thesys

@Aditya-thesys Aditya-thesys commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

No description provided.

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 24 commits June 28, 2026 16:51
…een for now

The CLI template is decoupled from the workspace shell deletion: scaffolded
projects pin the PUBLISHED @openuidev/react-ui ("latest", which still ships
FullScreen), and templates are copied verbatim by build-templates.js (the CLI's
tsc excludes src/templates — they are never compiled here). So migrating the
template to AgentInterface was not required for the shell-retirement PR. Defer it
to the follow-up that migrates the other published-deps consumers (fastapi,
genui-sdk) once react-ui is republished without the shells.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
… sections

The AgentInterface workspace rail hardcoded an "all"/"artifacts"/"apps" tab set
and magic-matched the "apps" tab on the category's display NAME
(`category.name.toLowerCase() === "apps"`) instead of deriving from the
consumer-supplied artifactCategories. Per "no top bar, no filtering for now":
remove the whole tab layer — the WorkspaceTab union, WORKSPACE_TAB_ORDER,
isAppsCategory, getAvailableWorkspaceTabs, the WorkspaceTabs component, the top
bar, and the activeTab filter. The rail now renders one section per category that
has entries (or a single Artifacts section), unfiltered.

Supersedes the parked SHOW_WORKSPACE_TOP_BAR=false flag. The labels.tabs API and
the tab SCSS are intentionally left in place (unused) for when category-driven
tabs are added properly later. react-ui builds + tsc + tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
The shell retirement (ff5416f) extracted only the ASSISTANT half of the deleted
Shell's thread.scss into OpenUIChat/assistantMessage.scss. GenUIUserMessage —
which AgentInterface renders for every user turn — still emits
`.openui-shell-thread-message-user` and `…__content`, but their styling was left
behind in the deleted file, so the user message lost its right-alignment
(`justify-content: flex-end`) and its bubble (background, padding, radius).

Restore both rules alongside the assistant block (same file is already forwarded
by openUIChat.scss → components/index.scss, so no index change), with the
mobile/detailed-view ANCESTOR selectors retargeted shell→agent to match the
assistant extraction. Verified: class present in dist/styles/index.css and the
user message renders as a right-aligned bubble in openui-cloud.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
ToolActivityRenderer fed an errored tool-call's result string into the artifact
parser as `response`; the parser can't read it and returns null, so the live
preview (e.g. a streaming slide deck) was replaced by the fallback card — the
artifact appeared to "disappear" before the model's retry.

Retain the last successful parse (a ref updated whenever parse is non-null) and,
on an error frame, render it instead of blanking to the fallback. Calls that
error before producing any parse still fall through to the fallback card, and
registration/meta still key off the live parse (an error never registers).

Verified: react-ui tsc + 6 tests pass + build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
…rors"

This reverts dd7c9ce. A failed tool call has no real artifact — it never
registers or persists, so it correctly vanishes on refresh. The guard retained
the failed call's in-flight preview live, creating a live-vs-refresh
inconsistency (two decks live, one after reload). Desired behavior: a failed
call shows no artifact, matching the persisted state. Restores the original
fallback-on-null rendering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
The c1 artifact parser renders a preview from the streamed tool args
(artifact_content), independent of the result — so a FAILED tool call still
produced a non-null parse and showed a phantom artifact card that was never
registered and vanished on refresh (a live-vs-reload inconsistency the user
hit). The prior `parsed === null` guard never fired for this path.

Render the fallback (raw tool card) when `activity.isError`, so the UI only
shows artifacts that actually succeeded — consistent live and after reload.
Streaming previews are unaffected (isError is only set once an error result
lands).

Verified: react-ui tsc + 6 tests + build; dist carries `isError || parsed === null`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
…ection

The per-thread Workspace rail grouped registered artifacts into one section
per configured artifactCategories entry. An artifact whose registered
entry.type matched no category's filter.type rendered in no section and
silently vanished from the rail (its inline chat preview still showed).
entry.type is dynamic (meta.type ?? renderer.type), so a parser stamping an
artifact's real kind can produce a type absent from every category filter.

Append a fallback section for entries matching no configured category so a
registered artifact is never dropped — mirroring the no-categories path that
already lists everything.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JsBw2b6YnH9J5GAhkDyonF
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
# Conflicts:
#	docs/content/docs/openui-lang/examples/harnesses/pi-agent-harness.mdx
#	examples/harnesses/pi-agent-harness/src/app/page.tsx
#	pnpm-lock.yaml
Base automatically changed from abhishek/openui-chat to main 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