Skip to content

feat(chat): suppress streaming JSON for A2UI tool calls#245

Closed
blove wants to merge 4 commits into
mainfrom
claude/genui-stream-suppression
Closed

feat(chat): suppress streaming JSON for A2UI tool calls#245
blove wants to merge 4 commits into
mainfrom
claude/genui-stream-suppression

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 11, 2026

Summary

When the LLM calls `generate_a2ui_schema` or `generate_json_render_spec`, the tool's args stream back as raw JSON in the assistant bubble — visible for ~3s before the rendered `` mounts and replaces it. This PR suppresses that JSON streaming and shows a `` instead.

  • New `` primitive — card-shaped placeholder with three shimmer rows + `✨ Building UI…` label.
  • `` now detects GenUI tool-call messages (both the streaming assistant AIMessage with `tool_calls` and the resulting ToolMessage) and renders the skeleton in place of the normal body.
  • Tool name set is overridable via a new `genuiToolNames` input on `` (default: the two A2UI / json-render tool names).

Spec: `docs/superpowers/specs/2026-05-11-nav-v2-polish-palette-and-genui-suppression-design.md` (`GenUI stream suppression` section).

Test plan

  • `nx test chat --testFile chat-genui-skeleton.component.spec.ts` — 2 tests green
  • `nx test chat --testFile chat-message.component.spec.ts` — 5 new suppression tests green, original tests still pass (463 total in chat lib)
  • `nx build chat` + `nx lint chat` green
  • Live smoke: trigger an A2UI render prompt (e.g. "Render a settings card with a dark-mode toggle, language dropdown, and Save button") — verify no JSON visible during streaming and skeleton appears immediately; rendered surface mounts on tool completion
  • Live smoke: trigger a non-GenUI tool prompt (e.g. "Search the docs for signal()") — verify normal streaming body renders
  • CI green

🤖 Generated with Claude Code

blove added 3 commits May 11, 2026 11:17
Card-shaped placeholder rendered in place of streaming
tool-call JSON while an A2UI / json-render surface is being
built. Three shimmer rows + 'Building UI…' status label.
Themed via existing chat-tokens (separator color, surface-alt
background) so it inherits A2UI theme overrides.
When the bound Message represents (or results from) a tool call to
a known GenUI tool (default: generate_a2ui_schema /
generate_json_render_spec), render <chat-genui-skeleton> in place
of the streaming body. Skeleton persists after streaming since the
actual rendered surface mounts via the existing agent.events\$
channel in a separate slot.

The DEFAULT_GENUI_TOOL_NAMES set is overridable via the new
genuiToolNames input, following the same convention as the
LangGraph adapter's subagentToolNames.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cacheplane Ready Ready Preview, Comment May 11, 2026 6:41pm

Request Review

…I sentinel

The original detection only fired post-streaming when tool_calls populates,
which is way too late: the user sees the entire stream of JSON args (or
the emit-phase a2ui_JSON payload) before the skeleton kicks in.

Three detection layers now cover the full streaming pipeline:

1a. Post-stream tool_calls[].name (unchanged).
1b. Live-stream OpenAI Responses-API content array — checks for any
    `{ type: 'function_call', name: <genui tool> }` block. This shape
    arrives on the FIRST stream chunk, before tool_calls populates.
1c. Emit-phase assistant message whose content starts with the A2UI
    sentinel `---a2ui_JSON---` (or any prefix of it during the first
    few tokens, so the skeleton fires from token 1).
2.  Tool result message tagged with the GenUI tool name (unchanged).

Adds 4 new tests covering the function_call block, full sentinel,
partial sentinel, and a negative case that doesn't accidentally match
unrelated dash-prefixed content.

Verified live against the dev server with the "settings card" prompt:
skeleton now renders from the first token of both the tool-call and
emit phases.
blove added a commit that referenced this pull request May 11, 2026
Seven phases, single PR. Reuses chat-genui-skeleton from PR #245
(lifted into Task 1.1), classifier rename + patience fix (Phase
2), chat-tool-calls excludeToolNames filter (Phase 3), full
chat-message revert (Phase 4), composition-level isGenuiTurn +
skeleton + filter wiring (Phase 5), public-api export (Phase 6),
controller-handled api-docs + PR open + #245 close (Phase 7).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@blove
Copy link
Copy Markdown
Contributor Author

blove commented May 11, 2026

Closing in favor of #246, which moves the GenUI orchestration from chat-message (wrong layer — hid the surface mounting slot) to the chat composition (right layer — sibling of the surface branch). See spec at docs/superpowers/specs/2026-05-11-genui-streaming-architecture-design.md.

@blove blove closed this May 11, 2026
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.

1 participant