Skip to content

feat(dotnet): native A2UI for the ag-ui .NET SDK + dojo demos - #2262

Open
ranst91 wants to merge 7 commits into
mainfrom
claude/a2ui-dotnet-review-783bd9
Open

feat(dotnet): native A2UI for the ag-ui .NET SDK + dojo demos#2262
ranst91 wants to merge 7 commits into
mainfrom
claude/a2ui-dotnet-review-783bd9

Conversation

@ranst91

@ranst91 ranst91 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

What

Adds A2UI (agent-generated UI) to the ag-ui .NET SDK natively — no external framework dependency — plus four AG-UI Dojo demos for the ag-ui-dotnet integration. A .NET agent can now produce A2UI v0.9 surfaces over the AG-UI protocol, cross-language-aligned with the TypeScript (@ag-ui/a2ui-toolkit) and Python (ag-ui-a2ui-toolkit) siblings.

The four A2UI pillars (all delivered and verified)

  1. Auto-inject (easy devex, opt-out-able + customizable). A2UIChatClient (a DelegatingChatClient) auto-injects generate_a2ui. Injection is off by default and turns on when the run forwards injectA2UITool (boolean or a custom render-tool name) or the backend opts in via A2UIChatClientOptions.InjectA2UITool — matching the "no injectA2UITool, no injection" contract of the ADK / Strands / Mastra / CrewAI siblings. A client-forwarded value wins over the backend opt-in (a client false/"" opts out); USER-PREVAILS (a dev-wired generate_a2ui is left untouched). The middleware's injected render_a2ui proxy is dropped from the planner's tool list so the model calls generate_a2ui instead of painting a surface directly and bypassing the subagent + recovery loop. Customizable via A2UIChatClientOptions / A2UIToolParams.
  2. Progressive streaming. The inner render_a2ui argument fragments reach the wire as incremental TOOL_CALL_START/ARGS/END via a provider-neutral tap on AGUI.Server (MapStreamingToolCallArguments). Verified: 172 incremental frames on real gpt-4o; unit-tested.
  3. Error recovery. Shared validate-and-retry loop; invalid surfaces never paint; exhaustion returns a structured a2ui_recovery_exhausted envelope rendered as a tasteful failure. Verified end to end + unit-tested.
  4. Subagent-based. generate_a2ui delegates to a forced render_a2ui structured-output subagent through the toolkit recovery loop.

Packages

  • AGUI.A2UI.Toolkit (new): framework-agnostic primitives (validator, recovery loop, envelope builder, prior-surface walk) ported behavior-for-behavior from the TS/Python siblings. Pure BCL — no Microsoft.Extensions.AI or framework dependency — so it can back a hand-rolled integration the way ag-ui-a2ui-toolkit / @ag-ui/a2ui-toolkit do.
  • AGUI.A2UI (new): the A2UIChatClient IChatClient decorator built on the toolkit. AGUI.Server stays provider-neutral; the OpenAI fragment extractor lives only in the sample.
  • AGUIDojoServer sample: /a2ui_dynamic_schema, /a2ui_recovery, /a2ui_advanced, /a2ui_fixed_schema endpoints. The fixed-schema demo is the agent-owns-data / author-owns-layout path: plain backend tools (search_flights / search_hotels) return a pre-authored a2ui_operations envelope — no generate_a2ui, subagent, or recovery.
  • apps/dojo: four demos wired for ag-ui-dotnet (paint-only middleware; the backend auto-injects and handles generation).

Testing

  • Verified against the AG-UI Dojo on both aimock and real OpenAI (gpt-4o): all four demos render surfaces progressively, recovery recovers/exhausts gracefully, the wire stays balanced.
  • Unit tests: AGUI.A2UI.UnitTests (112 — ported toolkit suite, decorator mid-stream-failure + planner-history-preservation tests, and a 13-case injection gate test pinning the flag precedence + render-proxy drop) and AGUI.Server.UnitTests streaming-tap tests. e2e Playwright specs per feature. Full solution builds clean; the render_a2ui streaming path is at parity with the LangGraph reference (0 protocol-balance warnings across repeated runs).
  • CI: AGUI.A2UI.Toolkit + AGUI.A2UI now build and test in unit-dotnet-sdk.yml.

Related - microsoft/agent-framework#6494

ranst91 added 3 commits July 28, 2026 16:26
…streaming)

Adds A2UI (agent-generated UI) support to the ag-ui .NET SDK natively, so a
.NET agent can produce A2UI v0.9 surfaces over the AG-UI protocol without any
external framework dependency. Delivers all four A2UI pillars: auto-inject,
progressive streaming, error recovery, and subagent-based generation.

- New AGUI.A2UI package:
  - Toolkit/ : framework-agnostic validator, validate-and-retry recovery loop
    (structured a2ui_recovery_exhausted envelope on exhaustion), prior-surface
    walk, envelope assembly. Ported behaviour-for-behaviour from the TS/Python
    siblings (validation codes, MaxA2UIAttempts, envelope shape all aligned).
  - A2UIChatClient : a DelegatingChatClient that auto-injects generate_a2ui and
    drives a forced render_a2ui subagent through the recovery loop, streaming the
    inner argument fragments so surfaces paint progressively. USER-PREVAILS
    (leaves a dev-wired generate_a2ui untouched), per-run injectA2UITool gate
    from forwarded RunAgentInput, error classification (transient -> retryable,
    cancellation/programmer errors rethrow), and try/finally wire-balancing.
  - Sanitizes the subagent conversation to drop the in-flight generate_a2ui tool
    call, which providers reject as an unbalanced tool_calls message.
- AGUI.Server: provider-neutral incremental tool-call argument streaming via a
  new AGUIStreamOptions.MapStreamingToolCallArguments hook + AGUIToolCallArgumentFragment.
  Keeps AGUI.Server free of any provider SDK dependency; callers register an
  extractor for their provider.
- AGUIDojoServer sample: /a2ui_dynamic_schema, /a2ui_recovery, /a2ui_advanced
  endpoints, with an OpenAI fragment extractor isolated to the sample.

Verified end to end against the AG-UI Dojo (aimock + real OpenAI): surfaces
generate and stream progressively, recovery recovers, wire stays balanced.
Wires the AG-UI Dojo for three A2UI demos on the ag-ui-dotnet integration,
served by the .NET AGUIDojoServer sample.

- menu.ts: a2ui_dynamic_schema, a2ui_advanced, a2ui_recovery features.
- agents.ts: plain HttpAgent endpoints. The .NET adapter auto-injects and
  handles generate_a2ui server-side, so the dojo runtime only attaches the A2UI
  painting middleware (injectsA2UITool is false for this integration); no
  client-side tool injection and no route.ts change needed.
- files.json: regenerated.
- e2e specs for the three features (aimock fixtures are prompt-matched and
  integration-agnostic).
CR-loop fixes over the native .NET A2UI work: house-rule compliance, streaming
retry protocol-balance, public-API tracking, and test coverage.

- Toolkit: convert all record types to sealed classes and split to one-type-per-file
  (AGUI.A2UI house rules); demote XML docs on internal members; ThrowIfNull consistency.
- AGUI.A2UI: add PublicAPI.Shipped/Unshipped.txt so the public surface is analyzer-tracked.
- Streaming tap (AGUI.Server): close a stale provider-index tool call when a new first
  fragment reuses the index, so a retried render_a2ui after a mid-stream failure is not
  misattributed and never doubles a TOOL_CALL_START; mirror the atomic path's MapCall
  mapping on the streaming-close branch.
- A2UIChatClient: capture the render call id from streamed fragments (new
  StreamingToolCallArgumentExtractor option) so a mid-stream-failed attempt still emits a
  balancing tool result, keeping the persisted conversation valid.
- Tests: new AGUI.A2UI.UnitTests (ported toolkit suite) resolving the dangling solution
  reference; StreamingToolCallArgsTest for the tap; a decorator mid-stream-failure test.
- Sample: correct the recovery demo comment; robuster OpenAI fragment unwrap.
- Dojo: order the recovery e2e assertions after the hard-failure signal so the
  no-faulty-paint check is not trivially true.
@ranst91
ranst91 requested a review from a team as a code owner July 28, 2026 14:49
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Python Preview Packages

Version 0.0.0.dev1785945329 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.dev1785945329' --index testpypi

# Integrations (each already depends on the matching ag-ui-protocol preview)
uv add 'ag-ui-langgraph==0.0.0.dev1785945329' --index testpypi
uv add 'ag-ui-crewai==0.0.0.dev1785945329' --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.dev1785945329' --index testpypi
uv add 'ag_ui_adk==0.0.0.dev1785945329' --index testpypi
uv add 'ag_ui_strands==0.0.0.dev1785945329' --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.dev1785945329

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


Commit: d00a68b

@pkg-pr-new

pkg-pr-new Bot commented Jul 28, 2026

Copy link
Copy Markdown

Open in StackBlitz

@ag-ui/a2a-middleware

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

@ag-ui/a2ui-middleware

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

@ag-ui/event-throttle-middleware

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/event-throttle-middleware@2262

@ag-ui/mcp-apps-middleware

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

@ag-ui/mcp-middleware

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

@ag-ui/a2a

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

@ag-ui/adk

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

@ag-ui/ag2

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

@ag-ui/agno

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

@ag-ui/aws-strands

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

@ag-ui/claude-agent-sdk

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

@ag-ui/claude-managed-agents

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/claude-managed-agents@2262

@ag-ui/crewai

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

@ag-ui/langchain

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

@ag-ui/langgraph

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

@ag-ui/llamaindex

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

@ag-ui/mastra

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

@ag-ui/pydantic-ai

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

@ag-ui/vercel-ai-sdk

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

@ag-ui/watsonx

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/watsonx@2262

@ag-ui/a2ui-toolkit

pnpm add https://pkg.pr.new/ag-ui-protocol/ag-ui/@ag-ui/a2ui-toolkit@2262

create-ag-ui-app

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

@ag-ui/client

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

@ag-ui/core

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

@ag-ui/encoder

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

@ag-ui/proto

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

commit: d49fd1a

Adds the fourth A2UI demo (a2ui_fixed_schema) for the ag-ui-dotnet integration,
completing parity with the other frameworks. Fixed-schema is the author-owns-layout /
agent-owns-data pattern: search_flights / search_hotels are plain backend tools whose
return value IS a pre-authored a2ui_operations envelope (createSurface + updateComponents
with a fixed FlightCard/HotelCard schema + updateDataModel from the tool args). The A2UI
painting middleware detects the envelope in the tool result; no generate_a2ui, no subagent,
no recovery loop.

- AGUIDojoServer sample: A2UIFixedSchemaTools.cs (ported), the /a2ui_fixed_schema endpoint,
  and JsonArray/JsonObject registrations in the serializer context (the tools take JsonArray
  params). Component names + catalog id match the dojo's fixedSchemaCatalog.
- Dojo: a2ui_fixed_schema wired for ag-ui-dotnet (plain HttpAgent; route.ts already treats it
  paint-only). Regenerated files.json. Added the e2e spec.

Verified on aimock (flight + hotel + multi-surface specs) and against real OpenAI (the tool
return envelope survives MEAI serialization and the surface paints; no unbalanced-history 400).
ranst91 and others added 3 commits August 5, 2026 11:06
…t split, gate test

Injection semantics (Finding 1):
- Default injection to OFF, matching the "no injectA2UITool, no injection"
  contract of the ADK/Strands/Mastra/CrewAI siblings. Turns on only when the
  run forwards injectA2UITool (bool or custom name) or the backend opts in via
  A2UIChatClientOptions.InjectA2UITool; a client-forwarded value wins.
- Drop the middleware's injected render_a2ui proxy from the planner's tool list
  so the model calls generate_a2ui instead of painting a surface directly and
  bypassing the subagent + recovery loop. Custom proxy name via
  InjectedRenderToolName or the forwarded string form.
- ResolveInjection/TryReadForwardedFlag replace ShouldInject.
- Sample backend opts in (InjectA2UITool ?? true) so the dojo demos still inject.

Gate test:
- A2UIInjectionGateTest.cs — 13 cases pinning flag precedence + the proxy drop
  through the production ToChatRequestContext mapping (98 -> 111 tests).

Packaging split (north-star parity with langgraph/adk/strands):
- New AGUI.A2UI.Toolkit: pure-BCL validator/recovery/envelope/prompt primitives,
  no Microsoft.Extensions.AI or framework dependency, so it can back a
  hand-rolled integration like ag-ui-a2ui-toolkit / @ag-ui/a2ui-toolkit.
- AGUI.A2UI keeps only the IChatClient decorator and references the toolkit.
- PublicAPI, AGUI.slnx, and unit-dotnet-sdk.yml updated for both projects.
…r history

The A2UIChatClient planner loop hand-picked only TextContent and generate_a2ui
calls into the assistant turn it fed back next round, dropping any of the
developer's own tool call/result pairs that the inner function-invocation layer
resolved in the same turn. A round-2 planner was then stranded without the tool
context it had just produced.

Coalesce the planner's full streamed turn into whole messages
(plannerUpdates.ToChatResponse().Messages) and append all of them to history,
so narration, the terminal generate_a2ui call, and balanced dev tool call/result
pairs all survive. New test RoundTwoHistory_PreservesDeveloperToolCallAndResult
pins it (112 tests).
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