Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9fab0b9
.NET: Scaffold the AG-UI A2UI toolkit and its test project
ranst91 Jun 10, 2026
086bd9a
.NET: Port the A2UI toolkit unit test suite ahead of the implementation
ranst91 Jun 10, 2026
cf19858
.NET: Implement the A2UI toolkit
ranst91 Jun 10, 2026
f8c61c9
.NET: Add A2UIAgent adapter exposing the generate_a2ui tool
ranst91 Jun 10, 2026
e11a7fa
.NET: Add A2UI demo agents to the AGUIDojoServer sample
ranst91 Jun 10, 2026
981ac53
.NET: Return the parsed envelope from generate_a2ui
ranst91 Jun 10, 2026
85babf3
.NET: Stream OpenAI tool-call argument fragments as incremental AG-UI…
ranst91 Jun 11, 2026
39f8b8a
.NET: Add zero-configuration A2UI chat demo
ranst91 Jun 11, 2026
10ccd65
.NET: Reset raw tool-call streaming state per call and close open cal…
ranst91 Jun 11, 2026
b5a9364
.NET: Harden A2UI adapter context routing and history capture
ranst91 Jun 11, 2026
1734079
.NET: Tighten AGUIDojoServer sample configuration and metadata
ranst91 Jun 11, 2026
a2fa458
.NET: Preserve caller run options in A2UIAgent and tighten the AG-UI …
ranst91 Jun 11, 2026
11358f5
.NET: Polish A2UI toolkit docs and test assertions
ranst91 Jun 11, 2026
afe6560
.NET: Clarify the A2UI recovery activity-type constant doc
ranst91 Jun 11, 2026
9425ec4
.NET: Stream the generate_a2ui subagent path for progressive rendering
ranst91 Jun 11, 2026
3066b46
.NET: Balance the forwarded render_a2ui call with a tool result
ranst91 Jun 11, 2026
bdc57bb
.NET: Harden the streaming subagent loop — coalesce render args, clos…
ranst91 Jun 11, 2026
f2735ef
.NET: Polish the streaming subagent loop — preserve planner narration…
ranst91 Jun 11, 2026
a0642b5
.NET: Document the AG-UI Dojo Server sample
ranst91 Jun 11, 2026
deacd89
.NET: Preserve caller run options in the A2UIAgent closing turn
ranst91 Jun 12, 2026
12230f0
.NET: Report empty-string component ids as missing, not duplicate
ranst91 Jun 12, 2026
dd53421
.NET: Validate singular child references, not only plural children
ranst91 Jun 12, 2026
fd7bc50
.NET: Close coalesced streamed tool calls via an O(1) id-to-index map
ranst91 Jun 12, 2026
089f916
.NET: Detect child-reference cycles in the A2UI validator
ranst91 Jun 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dotnet/agent-framework-dotnet.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,7 @@
<Project Path="src/Microsoft.Agents.AI.A2A/Microsoft.Agents.AI.A2A.csproj" />
<Project Path="src/Microsoft.Agents.AI.Abstractions/Microsoft.Agents.AI.Abstractions.csproj" />
<Project Path="src/Microsoft.Agents.AI.AGUI/Microsoft.Agents.AI.AGUI.csproj" />
<Project Path="src/Microsoft.Agents.AI.AGUI.A2UI/Microsoft.Agents.AI.AGUI.A2UI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Anthropic/Microsoft.Agents.AI.Anthropic.csproj" />
<Project Path="src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj" />
<Project Path="src/Microsoft.Agents.AI.CopilotStudio/Microsoft.Agents.AI.CopilotStudio.csproj" />
Expand Down Expand Up @@ -656,6 +657,7 @@
<Project Path="tests/Microsoft.Agents.AI.A2A.UnitTests/Microsoft.Agents.AI.A2A.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Abstractions.UnitTests/Microsoft.Agents.AI.Abstractions.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AGUI.UnitTests/Microsoft.Agents.AI.AGUI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AGUI.A2UI.UnitTests/Microsoft.Agents.AI.AGUI.A2UI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Anthropic.UnitTests/Microsoft.Agents.AI.Anthropic.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/Microsoft.Agents.AI.CosmosNoSql.UnitTests.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) Microsoft. All rights reserved.

namespace AGUIDojoServer.A2UI;

/// <summary>
/// Project-specific composition rules for the A2UI subagent — tells it how to use the
/// pre-made domain components shipped in the dojo's dynamic catalog. Mirrors the
/// LangGraph dojo examples so all integrations exercise the same demos.
/// </summary>
internal static class A2UICompositionGuides
{
/// <summary>The catalog id of the dojo's dynamic component catalog.</summary>
public const string DynamicCatalogId = "https://a2ui.org/demos/dojo/dynamic_catalog.json";

/// <summary>The planner system prompt for the dynamic-schema and recovery demos.</summary>
public const string PlannerInstructions = """
You are a helpful assistant that creates rich visual UI on the fly.

When the user asks for visual content (product comparisons, dashboards, lists, cards, etc.),
use the generate_a2ui tool to create a dynamic A2UI surface.
IMPORTANT: After calling the tool, do NOT repeat the data in your text response. The tool renders UI automatically. Just confirm what was rendered.
""";

/// <summary>The composition guide for the dynamic-schema demo.</summary>
public const string DynamicSchema = """
## Available Pre-made Components

You have 4 components. Use Row as the root with structural children to repeat a card per item.

### Row
Layout container. Use structural children to repeat a card template:
{"id":"root","component":"Row","children":{"componentId":"card","path":"/items"}}

### HotelCard
Props: name, location, rating (number 0-5), pricePerNight, amenities (optional), action
Example:
{"id":"card","component":"HotelCard","name":{"path":"name"},"location":{"path":"location"},
"rating":{"path":"rating"},"pricePerNight":{"path":"pricePerNight"},
"action":{"event":{"name":"book","context":{"name":{"path":"name"}}}}}

### ProductCard
Props: name, price, rating (number 0-5), description (optional), badge (optional), action
Example:
{"id":"card","component":"ProductCard","name":{"path":"name"},"price":{"path":"price"},
"rating":{"path":"rating"},"description":{"path":"description"},
"action":{"event":{"name":"select","context":{"name":{"path":"name"}}}}}

### TeamMemberCard
Props: name, role, department (optional), email (optional), avatarUrl (optional), action
Example:
{"id":"card","component":"TeamMemberCard","name":{"path":"name"},"role":{"path":"role"},
"department":{"path":"department"},"email":{"path":"email"},
"action":{"event":{"name":"contact","context":{"name":{"path":"name"}}}}}

## RULES
- Root is ALWAYS a Row with structural children: {"componentId":"<card-id>","path":"/items"}
- Inside templates, use RELATIVE paths (no leading slash): {"path":"name"} not {"path":"/name"}
- Always provide data in the "data" argument as {"items":[...]}
- Pick the card type that best matches the user's request
- Generate 3-4 realistic items with diverse data
""";

/// <summary>The composition guide for the recovery demo (structural validation showcase).</summary>
public const string Recovery = """
## Available Pre-made Components

Use Row as the root with structural children to repeat a card per item.

### Row
Layout container. Repeat a card template via structural children:
{"id":"root","component":"Row","children":{"componentId":"card","path":"/items"}}

### HotelCard / ProductCard / TeamMemberCard
Card components bound to per-item data (relative paths inside the template).

## RULES
- Root is ALWAYS a Row with structural children: {"componentId":"<card-id>","path":"/items"}
- ALWAYS include the referenced card component in the components array.
- Inside templates, use RELATIVE paths (no leading slash): {"path":"name"} not {"path":"/name"}
- Always provide data in the "data" argument as {"items":[...]}
- Generate 3-4 realistic items with diverse data.
""";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright (c) Microsoft. All rights reserved.

using System.ComponentModel;
using System.Text.Json.Nodes;
using Microsoft.Agents.AI.AGUI.A2UI;
using Microsoft.Extensions.AI;

namespace AGUIDojoServer.A2UI;

/// <summary>
/// Fixed-schema A2UI tools: pre-built component layouts for flight and hotel cards.
/// The agent only supplies the data; layout/styling is fixed in code. Demonstrates the
/// "controlled gen-UI" pattern — the author owns the UI shape, the agent owns the data.
/// </summary>
internal static class A2UIFixedSchemaTools
{
private const string CatalogId = "https://a2ui.org/demos/dojo/fixed_catalog.json";
private const string FlightSurfaceId = "flight-search-results";
private const string HotelSurfaceId = "hotel-search-results";

/// <summary>Creates the <c>search_flights</c> tool.</summary>
public static AIFunction CreateSearchFlightsTool() => AIFunctionFactory.Create(
SearchFlights,
"search_flights",
"Search for flights and display the results as rich cards. Each flight " +
"must have: id, airline (e.g. 'United Airlines'), airlineLogo (use Google " +
"favicon API like 'https://www.google.com/s2/favicons?domain=united.com&sz=128'), " +
"flightNumber, origin, destination, date (e.g. 'Tue, Mar 18'), departureTime, " +
"arrivalTime, duration (e.g. '4h 25m'), status ('On Time' or 'Delayed'), " +
"and price (e.g. '$289').",
AGUIDojoServerSerializerContext.Default.Options);

/// <summary>Creates the <c>search_hotels</c> tool.</summary>
public static AIFunction CreateSearchHotelsTool() => AIFunctionFactory.Create(
SearchHotels,
"search_hotels",
"Search for hotels and display the results as rich cards with star ratings. " +
"Each hotel must have: id, name (e.g. 'The Plaza'), location " +
"(e.g. 'Midtown Manhattan, NYC'), rating (float 0-5, e.g. 4.5), and " +
"price (per night, e.g. '$350'). Generate 3-4 realistic results.",
AGUIDojoServerSerializerContext.Default.Options);

private static string SearchFlights(
[Description("Array of flight result objects.")] JsonArray flights)
=> RenderOperations(FlightSurfaceId, FlightSchema(), "flights", flights);

private static string SearchHotels(
[Description("Array of hotel result objects.")] JsonArray hotels)
=> RenderOperations(HotelSurfaceId, HotelSchema(), "hotels", hotels);

/// <summary>
/// Wraps the fixed layout + agent-supplied data as the A2UI operations envelope the
/// AG-UI A2UI middleware detects in tool results.
/// </summary>
private static string RenderOperations(string surfaceId, JsonArray schema, string dataKey, JsonArray items)
=> A2UIToolkit.WrapAsOperationsEnvelope(A2UIToolkit.AssembleOps(
"create",
surfaceId,
CatalogId,
schema,
new JsonObject { [dataKey] = items.DeepClone() }));

// Flight search layout — the agent supplies the `flights` array; rendering is fixed.
private static JsonArray FlightSchema() => new(
new JsonObject
{
["id"] = "root",
["component"] = "Row",
["children"] = new JsonObject { ["componentId"] = "flight-card", ["path"] = "/flights" },
["gap"] = 16,
},
new JsonObject
{
["id"] = "flight-card",
["component"] = "FlightCard",
["airline"] = new JsonObject { ["path"] = "airline" },
["airlineLogo"] = new JsonObject { ["path"] = "airlineLogo" },
["flightNumber"] = new JsonObject { ["path"] = "flightNumber" },
["origin"] = new JsonObject { ["path"] = "origin" },
["destination"] = new JsonObject { ["path"] = "destination" },
["date"] = new JsonObject { ["path"] = "date" },
["departureTime"] = new JsonObject { ["path"] = "departureTime" },
["arrivalTime"] = new JsonObject { ["path"] = "arrivalTime" },
["duration"] = new JsonObject { ["path"] = "duration" },
["status"] = new JsonObject { ["path"] = "status" },
["price"] = new JsonObject { ["path"] = "price" },
["action"] = new JsonObject
{
["event"] = new JsonObject
{
["name"] = "book_flight",
["context"] = new JsonObject
{
["flightNumber"] = new JsonObject { ["path"] = "flightNumber" },
["origin"] = new JsonObject { ["path"] = "origin" },
["destination"] = new JsonObject { ["path"] = "destination" },
["price"] = new JsonObject { ["path"] = "price" },
},
},
},
});

// Hotel search layout — the agent supplies the `hotels` array; rendering is fixed.
private static JsonArray HotelSchema() => new(
new JsonObject
{
["id"] = "root",
["component"] = "Row",
["children"] = new JsonObject { ["componentId"] = "hotel-card", ["path"] = "/hotels" },
["gap"] = 16,
},
new JsonObject
{
["id"] = "hotel-card",
["component"] = "HotelCard",
["name"] = new JsonObject { ["path"] = "name" },
["location"] = new JsonObject { ["path"] = "location" },
["rating"] = new JsonObject { ["path"] = "rating" },
// Deliberate cross-name: the HotelCard prop is "pricePerNight" but the
// agent-supplied data field (per the search_hotels description) is "price".
["pricePerNight"] = new JsonObject { ["path"] = "price" },
["action"] = new JsonObject
{
["event"] = new JsonObject
{
["name"] = "book_hotel",
["context"] = new JsonObject
{
["hotelName"] = new JsonObject { ["path"] = "name" },
["price"] = new JsonObject { ["path"] = "price" },
},
},
},
});
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>b9c3f1e1-2fb4-5g29-0e52-53e2b7g9gf21</UserSecretsId>
<UserSecretsId>1d558f5d-c5c3-4178-bc08-edc445249828</UserSecretsId>
</PropertyGroup>

<ItemGroup>
Expand All @@ -17,6 +17,7 @@
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Hosting.AspNetCore\Microsoft.Agents.AI.Hosting.AspNetCore.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AGUI.A2UI\Microsoft.Agents.AI.AGUI.A2UI.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

namespace AGUIDojoServer;

[JsonSerializable(typeof(System.Text.Json.Nodes.JsonArray))]
[JsonSerializable(typeof(System.Text.Json.Nodes.JsonObject))]
[JsonSerializable(typeof(WeatherInfo))]
[JsonSerializable(typeof(Recipe))]
[JsonSerializable(typeof(Ingredient))]
Expand Down
Loading