dotnet / dotnet/aspnetcore

[Blazor][Components.AI] Add a built-in default renderer for `UIActionBlock` that auto-invokes client tool calls

Open
#69,191 1 comment 0 reactions 0 assignees View on GitHub
area-blazor feature-blazor-builtin-components
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

# Summary

`UIActionBlock` (added in #68325) currently has no built-in default rendering behavior. Unlike other interactive/content blocks in the pipeline, an app must register a custom `BlockRenderer` and manually call `Block.InvokeAsync()` (see the `AutoInvokeAction.razor` test asset in src/Components/AI/testassets/DojoClient/Components/Scenarios/AgenticChat/) just to get a client tool call to execute at all. Without that boilerplate, the tool never runs and the conversation silently stalls.

## Problem

Looking at `MessageListContext.RenderBlock`'s fallback (used when no custom `BlockRenderer` is registered):

```c#
return builder =>
{
if (block is RichContentBlock rich) { ... }
else if (block is FunctionApprovalBlock approval)
{
RenderApprovalBlock(builder, approval); // built-in Approve/Reject UI, wired up
}
else if (block is not FunctionInvocationContentBlock)
{
// unknown-block debug placeholder <- UIActionBlock lands here
}
};
```

Every other block type that needs some form of resolution before the conversation continues already has a working default:

Block | Needs to happen before continuing | Built-in default today
-- | -- | --
RichContentBlock | Nothing (pure display) | ✅ Renders chat bubble markup
FunctionInvocationContentBlock | Nothing (already executed upstream by IChatClient middleware) | ✅ Renders nothing (correct no-op)
FunctionApprovalBlock | Human clicks Approve/Reject | ✅ RenderApprovalBlock — full working UI
UIActionBlock | App code must call InvokeAsync() in the Blazor circuit | ❌ Unknown-block placeholder; never invoked

UIActionBlock is the only block type where baseline functionality (the tool actually running) requires hand-written app code, even when no custom UI/confirmation step is desired.

## Proposal

Add a built-in default renderer for UIActionBlock, following the same pattern as RenderApprovalBlock, that:

Renders minimal status markup (e.g., pending/complete state, tool name) using the existing sc-ai-* CSS convention.
Calls `Block.InvokeAsync()` once automatically if the block is not yet complete.
This mirrors the existing `AutoInvokeAction.razor` test asset behavior, but ships it as part of the framework so it doesn't need to be reinvented per app.

## Non-goals / preserved behavior

- Does not change approval semantics. Client actions that require human approval still arrive wrapped in `FunctionApprovalBlock` (via `ToolApprovalRequestContent`), so auto-invoke is never bypassed for gated tools -only ungated UIActionBlocks get the new default.
- Fully overridable: apps that register `BlockRenderer` continue to take precedence over the built-in fallback (per existing registration-first lookup in `MessageListContext.RenderBlock`), so custom confirmation UI, progress indicators, or deferred invocation remain fully supported.

Contributor guide

Open the contributing guide

Research direction

Start at MessageListContext.RenderBlock and compare its existing FunctionApprovalBlock fallback with the AutoInvokeAction.razor test asset in src/Components/AI/testassets/DojoClient/Components/Scenarios/AgenticChat/. Add the built-in UIActionBlock fallback with minimal status markup and one automatic InvokeAsync call for incomplete blocks. Done means ungated UIActionBlocks invoke without custom registration while custom renderers and approval behavior remain unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
frontend, web-dev
Issue type
Feature
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.