Skip to content

feat(a2ui-middleware): streaming surfaces, dynamic schema, and action handlers#1321

Open
ataibarkai wants to merge 13 commits intomainfrom
atai/0313/a2ui-etc
Open

feat(a2ui-middleware): streaming surfaces, dynamic schema, and action handlers#1321
ataibarkai wants to merge 13 commits intomainfrom
atai/0313/a2ui-etc

Conversation

@ataibarkai
Copy link
Copy Markdown
Contributor

Summary

Extends the A2UI middleware with three rendering modes and interactive action handlers. Companion to CopilotKit#3453.

Architecture

1. Fixed Schema (non-streaming)

The middleware intercepts TOOL_CALL_RESULT events and checks for a2ui_operations in the JSON payload. When found, it maps the operations array to ACTIVITY_SNAPSHOT events that the frontend A2UI renderer consumes. The agent tool returns the full surface (schema + data + beginRendering) in one shot via a2ui.render().

2. Fixed Schema (streaming)

streamingSurfaces config in the CopilotRuntime declares tool-to-schema mappings. When the middleware sees a TOOL_CALL_START for a registered tool, it immediately emits the schema (surfaceUpdate + beginRendering). As TOOL_CALL_ARGS stream in, partial JSON parsing extracts complete items from the dataKey array and emits progressive dataModelUpdates. Cards appear one-by-one.

3. Dynamic Schema

The render_a2ui tool name is auto-detected. The secondary LLM generates both schema and data as tool call args. The middleware extracts components first (waits for the array to close), then streams items progressively — same partial-parse approach as streaming surfaces.

4. Action Handlers

Pre-declared optimistic UI responses attached to surfaces. When a button is clicked, the matching handler's operations replace the surface instantly (no round-trip). Supports exact name match and "*" catch-all. Available on both tool results (a2ui_action_handlers key) and streaming surface config (surface.actionHandlers).

Dojo Items

  • a2ui_fixed_schema, a2ui_fixed_schema_streaming, a2ui_dynamic_schema — LangGraph Python (FastAPI) agents + frontend pages
  • Missing: e2e tests for these dojo items

Key Changes

  • middlewares/a2ui-middleware/src/ — streaming surfaces, dynamic schema detection, action handler pass-through, partial JSON parsing, surface isolation by toolCallId
  • integrations/langgraph/python/examples/agents/a2ui_* — three agent implementations
  • apps/dojo/src/ — menu entries, agent configs, frontend pages for all three A2UI features

What's Missing (for the person taking this over)

  1. E2e tests — the three A2UI dojo items need playwright tests (like the other dojo features have)
  2. Dojo integration coverage — currently only LangGraph Python (FastAPI). Should expand to other integrations where applicable
  3. Local-install for the dojo — a local-install script was prototyped but reverted due to version compatibility issues between local CopilotKit and the dojo's runtime. The backup branch atai/0313/a2ui-etc-dojo-backup preserves that work

Note to Reviewer

Feel free to modify any and all implementations and developer experiences. The implementations here are a starting point. What matters is supporting the "real world hello world" use cases — a developer should be able to go from zero to a working A2UI surface (fixed, streaming, or dynamic) by following the patterns in the dojo and docs.

Test plan

  • E2e tests for a2ui_fixed_schema dojo item
  • E2e tests for a2ui_fixed_schema_streaming dojo item
  • E2e tests for a2ui_dynamic_schema dojo item
  • Middleware unit tests pass (pnpm test)

🤖 Generated with Claude Code

ataibarkai and others added 11 commits March 13, 2026 19:55
…2UIOperations

Tools can now return {"a2ui_operations": [...]} for explicit detection.
Legacy bare-array and single-object formats still work.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shared constant for the container key used by copilotkit.a2ui.render()
(Python) and A2UIMessageRenderer (React).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add metadata field to AG-UI ToolSchema
- Add A2UIToolSchema type and schemas config to A2UIMiddlewareConfig
- On TOOL_CALL_START for registered tools, emit surfaceUpdate + beginRendering
- On TOOL_CALL_ARGS, partial-parse and emit dataModelUpdate progressively
- Add toA2UIContents/toA2UITypedValue helpers for JS→A2UI type conversion

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

Only the explicit { a2ui_operations: [...] } container is now recognized.
Bare arrays and single operation objects are no longer auto-detected.
Tests updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Config is now an explicit array of { toolName, surface } objects
instead of a Record<string, schema>. Clearer developer experience.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
A2UISurfaceConfig now accepts actionHandlers — same interface as
the tool-declared a2ui_action_handlers. Streaming surfaces pass
actionHandlers through in both schema emission (TOOL_CALL_START)
and progressive data updates. tryParseA2UIOperations now returns
A2UIParseResult with optional actionHandlers.

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

messageId now includes toolCallId: "a2ui-surface-{surfaceId}-{toolCallId}"
so each tool call creates a distinct activity message. Prevents second
invocations of the same tool from overwriting earlier surfaces in chat.

Updated all four paths: createA2UIActivityEvents, emitStreamingData,
auto-detected TOOL_CALL_RESULT, and processSendA2UIToolCall.

2 new tests: distinct messageIds for send_a2ui and auto-detected tools.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hardcoded "operations" key with A2UI_OPERATIONS_KEY constant
in emitStreamingData and createA2UIActivityEvents. Updates tests to
match the new container key format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Unified streaming pipeline for both fixed-schema and dynamic render_a2ui
tool calls. The middleware extracts schema from streaming args, then
progressively parses data items using the same extractCompleteItems +
emitStreamingData pipeline as fixed-schema surfaces.

- Track render_a2ui tool calls alongside fixed-schema streaming surfaces
- extractCompleteItems: add bracket depth tracking to stop at array boundary
- extractCompleteItemsWithStatus: report arrayClosed for schema timing
- extractStringField: extract string values from partial JSON
- Auto-inject beginRendering for early surface display
- Deferred schema emission until components array fully closed
- groupBySurface helper for per-surface snapshot emission

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract actionHandlers from render_a2ui streaming args and include
them in ACTIVITY_SNAPSHOT content. Re-emit snapshot when handlers
arrive after initial data emission. Support dynamic actionHandlers
fallback in emitStreamingData alongside fixed-schema handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Skip extractCompleteItems/extractStringField/JSON.parse calls when the
TOOL_CALL_ARGS delta contains no structural JSON characters (} or ]).
This eliminates ~90% of parsing work during streaming since most deltas
are string content, not object/array boundaries.

- Check delta for closing braces/brackets before extraction calls
- Guard JSON.parse for actionHandlers with string-end check
- Apply same optimization to send_a2ui_json_to_client path
- ~17x reduction in parsing overhead per stream

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ataibarkai ataibarkai requested a review from a team as a code owner March 18, 2026 07:07
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 18, 2026

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

Project Deployment Actions Updated (UTC)
ag-ui-dojo Error Error Mar 18, 2026 7:31am

Request Review

@github-actions
Copy link
Copy Markdown

Python Preview Packages

Version 0.0.0.dev1773817668 published to TestPyPI.

Warning: These packages are built from contributor code that may not yet have been vetted for correctness or security. Install at your own risk and do not use in production.

Install with uv

Add the TestPyPI index to your pyproject.toml:

[[tool.uv.index]]
name = "testpypi"
url = "https://test.pypi.org/simple/"
explicit = true

Then install the packages you need:

# Core SDK
uv add 'ag-ui-protocol==0.0.0.dev1773817668' --index testpypi

# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1773817668' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1773817668' --index testpypi
# NOTE: ag-ui-agent-spec depends on pyagentspec (git-only, not on PyPI).
# You will need to install pyagentspec separately from its git repo.
uv add 'ag-ui-agent-spec==0.0.0.dev1773817668' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1773817668' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1773817668' --index testpypi

Install with pip

pip install \
  --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ \
  ag-ui-protocol==0.0.0.dev1773817668

Use --extra-index-url https://pypi.org/simple/ so pip can resolve
transitive dependencies (pydantic, fastapi, etc.) from real PyPI.


Commit: 7ebcf16

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Mar 18, 2026

Open in StackBlitz

@ag-ui/a2a-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a-middleware@1321

@ag-ui/a2ui-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-middleware@1321

@ag-ui/mcp-apps-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mcp-apps-middleware@1321

@ag-ui/middleware-starter

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/middleware-starter@1321

@ag-ui/a2a

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2a@1321

@ag-ui/adk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/adk@1321

@ag-ui/ag2

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/ag2@1321

@ag-ui/agno

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/agno@1321

@ag-ui/aws-strands

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/aws-strands@1321

@ag-ui/claude-agent-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-agent-sdk@1321

@ag-ui/crewai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/crewai@1321

@ag-ui/langchain

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langchain@1321

@ag-ui/langgraph

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langgraph@1321

@ag-ui/langroid

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/langroid@1321

@ag-ui/llamaindex

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/llamaindex@1321

@ag-ui/mastra

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/mastra@1321

@ag-ui/pydantic-ai

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/pydantic-ai@1321

@ag-ui/server-starter

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/server-starter@1321

@ag-ui/server-starter-all-features

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/server-starter-all-features@1321

@ag-ui/vercel-ai-sdk

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/vercel-ai-sdk@1321

create-ag-ui-app

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/create-ag-ui-app@1321

@ag-ui/client

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/client@1321

@ag-ui/core

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/core@1321

@ag-ui/encoder

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/encoder@1321

@ag-ui/proto

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/proto@1321

commit: 3a07357

- Remove old a2ui_chat (used outdated createA2UIMessageRenderer API)
- Add a2ui_fixed_schema: pre-built flight card schema, no streaming
- Add a2ui_fixed_schema_streaming: progressive card rendering via streamingSurfaces
- Add a2ui_dynamic_schema: secondary LLM generates UI schema from conversation
- Add a2ui_advanced: dynamic schema + useA2UIActionHandler for optimistic button responses
- Add local-install script (.pnpmfile.cjs + local-install.sh) for linking CopilotKit locally
- Configure CopilotRuntime with a2ui + streamingSurfaces for streaming flight search
- Auto-detect cross-repo symlinks in next.config.ts for outputFileTracingRoot

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Now that the CopilotKit infrastructure fix (useRenderTool overrides
built-in) is in place, the custom violet progress renderer with live
component/item counters works correctly without the built-in duplicate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* been received). Callers that need the complete array (e.g., schema
* components) should wait for `arrayClosed === true`.
*/
export function extractCompleteItemsWithStatus(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This relies too much on AI generated code for something we should use a library for. Use https://github.com/dscape/clarinet or an equivalent instead.

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.

2 participants