MentorAgent.Blazor
1.0.0-preview.5
dotnet add package MentorAgent.Blazor --version 1.0.0-preview.5
NuGet\Install-Package MentorAgent.Blazor -Version 1.0.0-preview.5
<PackageReference Include="MentorAgent.Blazor" Version="1.0.0-preview.5" />
<PackageVersion Include="MentorAgent.Blazor" Version="1.0.0-preview.5" />
<PackageReference Include="MentorAgent.Blazor" />
paket add MentorAgent.Blazor --version 1.0.0-preview.5
#r "nuget: MentorAgent.Blazor, 1.0.0-preview.5"
#:package MentorAgent.Blazor@1.0.0-preview.5
#addin nuget:?package=MentorAgent.Blazor&version=1.0.0-preview.5&prerelease
#tool nuget:?package=MentorAgent.Blazor&version=1.0.0-preview.5&prerelease
MentorAgent.Blazor
Preview Release — MentorAgent is currently in public preview. APIs may change before the stable release.
Blazor WebAssembly client for MentorAgent. Install this in your Blazor WASM / Blazor Auto client project.
Connects to a MentorAgent.Server hub via SignalR and provides the same <ChatWidget /> experience as Blazor Server — same features, same API, no code changes needed when switching between render modes.
All AI processing happens server-side — configured in your server project with AddMentorAgent(). The client sends messages, registers page context and UI actions, and receives streaming events.
Table of Contents
- What MentorAgent can do
- Package Family
- Getting started
- Widget customization
- Page context and UI actions
- Page navigation
- UI Action overloads reference
[MentorPage]parameters (server project)- All
AddMentorAgentBlazor()options - How it works
- Events — IMentorStateService
- Blazor Auto tip
- Requirements
- Related Packages
- License
What MentorAgent can do
All features below are available. Configure them server-side in AddMentorAgent().
| Feature | Description |
|---|---|
| 🤖 Multi-agent orchestration | Coordinator + specialized agents via Handoff Workflow |
| 👥 Group Chat teams | Multiple agents collaborate before acting |
| 🛠️ Tool discovery | C# methods become AI tools via [Description] or [MentorAction] |
| 🎯 UI Actions | AI invokes page-level actions (highlight rows, open modals, pre-fill forms) as individually named tools with typed parameters and async support |
| 📖 Agent Skills | Domain knowledge loaded on demand (load_skill) — progressive disclosure |
| 🧠 Contextual memory | Remembers user preferences across sessions |
| 🗺️ Page navigation | AI navigates to pages decorated with [MentorPage] |
| 📚 RAG | Inject relevant documents from any vector DB into every AI response |
| 🔌 MCP Client | Consume external MCP servers as additional tools |
| 🖥️ MCP Server | Expose [MentorAction] methods to Claude Desktop, VS Code, Cursor |
| 🌐 A2A Consumer | Connect to remote A2A agents in the Handoff workflow |
| 📡 A2A Server | Expose as a federatable A2A agent |
| 🎨 Customizable widget | Themes, colors, position, avatar, bot name |
| 🌍 Multi-language | 10 languages for AI responses and widget UI |
| 🎤 Voice input/output | Browser Speech Recognition + Speech Synthesis — speaks while the answer streams, stops when the user takes the floor, optional hands-free loop |
| 🧭 Onboarding tour | First-run guide generated on the server from its own pages and tools |
| 🃏 Generative UI cards | A server-side tool returns a card and the widget renders it — fields, accent, buttons |
| 🖼️ Multimodal image input | Attach images to a message — upload, clipboard paste, drag & drop or URL |
| 🌐 Hosted tools | The model provider runs web search, a code-interpreter sandbox and file search — configured server-side, results arrive in the stream |
| 🔒 Safety check | AI-based prompt injection detection |
| ⏱️ Rate limiting | Per-user message limit |
| ✅ Confirmation dialogs | Destructive actions ask for approval (HITL) — including MCP tools, in either the MentorAgent or the Agent Framework native flow |
| 🔐 Role-based actions | Actions restricted by ASP.NET Core identity roles |
| 💸 Token & cost optimization | Slim cache-friendly prompt, semantic tool filtering, history compaction, RAG/memory gating |
Package Family
| Package | Install when |
|---|---|
| MentorAgent | Blazor Server app |
| MentorAgent.Server | Server project (Web API / Blazor Auto server) |
| MentorAgent.Blazor ← you are here | Blazor WASM / Blazor Auto client project |
| MentorAgent.Abstractions | Never directly — it arrives with any of the above |
| MentorAgent.Declarative | Optional — Level-2 specialists in YAML, added on the server project |
Getting started
Installation
# Client project
dotnet add package MentorAgent.Blazor --prerelease
# Server project — MentorAgent is included automatically as a transitive dependency
dotnet add package MentorAgent.Server --prerelease
Server project setup
All AI behaviour is configured on the server (the transitive
MentorAgentcore): agents, RAG, memory, skills, MCP/A2A and the token/cost optimizations below. The client only renders the widget.
// Server/Program.cs
builder.Services.AddMentorAgent(options =>
{
options.AppName = "My App";
options.AppDescription = "An order management application";
options.ChatClient = chatClient;
options.ScanAssemblies = [typeof(Program).Assembly];
// All features configured here: agents, RAG, MCP, A2A, memory, skills...
options.UseMemoryContext = true;
options.UseRag = true;
options.McpServerEnabled = true;
options.A2AServerEnabled = true;
options.EnableSkills = true;
options.RateLimitPerUser = 20;
});
builder.Services.AddMentorAgentServer();
app.MapMentorAgentServer(); // /mentor-hub + /mentor/chat
app.MapMentorAgentMcp(); // optional
app.MapMentorAgentA2A(); // optional
Token & cost optimization + reliable memory (server project)
These options cut the tokens sent per request and make memory reliable — all on the server. Full details: MentorAgent core README → Token & cost optimization.
builder.Services.AddMentorAgent(options =>
{
// ...ChatClient, ScanAssemblies as above...
// Embedding model — powers semantic tool filtering AND semantic memory relevance.
options.EmbeddingGenerator = new AzureOpenAIClient(endpoint, credential)
.GetEmbeddingClient("text-embedding-3-small").AsIEmbeddingGenerator();
// Send only the tools semantically relevant to the message (requires EmbeddingGenerator).
options.EnableToolFiltering = true;
options.ToolFilterMinScore = 0.35f;
// Compact long conversation history before each call.
options.EnableCompaction = true;
options.CompactionTokenThreshold = 4000;
// Memory: reliable post-turn fact capture (default true) + inject only relevant memories.
options.UseMemoryContext = true;
options.MemoryAutoCapture = true; // default — reliable writer on Path A
options.MemoryRelevanceFiltering = true; // requires EmbeddingGenerator
});
Robustness, observability & cost dashboard (server project)
Also configured on the server — see the MentorAgent core README for full details.
builder.Services.AddMentorAgent(options =>
{
// Middleware hooks
options.OnException = ex => ex.Message.Contains("rate", StringComparison.OrdinalIgnoreCase)
? "The service is busy, please retry shortly." : null;
options.ConfigureChatClientPipeline = b => b.UseLogging();
// Observability (add an OpenTelemetry exporter to the app as usual)
options.EnableObservability = true;
// Dashboard cost pricing (supply your own; none built in)
options.ModelPricing = new Dictionary<string, ModelPrice>(StringComparer.OrdinalIgnoreCase)
{
["gpt-4o"] = new ModelPrice(2.50m, 10.00m), ["gpt-4o-mini"] = new ModelPrice(0.15m, 0.60m),
};
});
The admin dashboard ships as a Blazor component. Drop it on a protected page (you own the authorization). On Blazor Server / Auto it reads IMentorMetrics in-process; on standalone WASM, fetch GET /mentor/admin/metrics from the server and pass the snapshot:
@* Blazor Server / Auto — in-process (the component reads IMentorMetrics itself) *@
@attribute [Authorize(Roles = "Admin")]
@using MentorAgent.Abstractions.Components
<MentorDashboard Currency="$" />
@* Standalone WASM — fetch the snapshot from the server and pass it in *@
@attribute [Authorize(Roles = "Admin")]
@using System.Net.Http.Json
@using MentorAgent.Abstractions.Components
@using MentorAgent.Abstractions.Models
@inject HttpClient Http
<MentorDashboard Snapshot="_snapshot" OnRefresh="LoadAsync" Currency="$" />
@code {
private MentorMetricsSnapshot? _snapshot;
protected override Task OnInitializedAsync() => LoadAsync();
// HttpClient must target the server; the endpoint is gated by options.DashboardRole.
private async Task LoadAsync() =>
_snapshot = await Http.GetFromJsonAsync<MentorMetricsSnapshot>("mentor/admin/metrics");
}
Also configured/available on the server (see the core README for full examples):
- Rich responses — with
options.EnableRichResponses(server, default on) the assistant formats structured data as Markdown; this widget renders the tables & lists automatically — no client wiring, XSS-safe, tables scroll horizontally on narrow screens. - Model routing (
options.StrongChatClient+options.RoutingStrategy:Semantic/Classifier/Cascade/Custom) — cheap↔strong per turn. - Structured outputs — inject
IMentorStructured(GenerateAsync<T>) for typed results / auto-filled forms. - Evaluation — inject
MentorEvaluator(wraps the Agent Framework's nativeagent.EvaluateAsync) in tests to gate CI on token/quality regressions; plugFoundryEvals/MEAI evaluators for quality & safety.
Client project setup
// Client/Program.cs
using MentorAgent.Blazor.Extensions;
builder.Services.AddMentorAgentBlazor(options =>
{
options.HubUrl = "/mentor-hub"; // URL of MentorAgent.Server hub
options.BotName = "My Assistant";
options.Language = MentorLanguage.English;
options.Theme = MentorTheme.Default;
options.PrimaryColor = "#2563eb";
});
⚠️
HubUrl— relative vs absolute.
- Same origin (Blazor Auto hosted, or WASM served by the same ASP.NET Core host): use a relative path —
options.HubUrl = "/mentor-hub".- Different origin (standalone WASM on
:5001connecting to a server on:5169): use the server's absolute URL —options.HubUrl = "http://localhost:5169/mentor-hub"— and configure CORS on the server (see the MentorAgent.Server README → CORS). Without server-side CORS the SignalR handshake is silently blocked by the browser.
// Standalone WASM example — different origin
builder.Services.AddMentorAgentBlazor(options =>
{
options.HubUrl = "http://localhost:5169/mentor-hub"; // absolute — server on a different port
options.BotName = "My Assistant";
});
Add the widget
In MainLayout.razor or any page:
@using MentorAgent.Abstractions.Components
<ChatWidget />
Important — Blazor WASM requires manual CSS/JS links in index.html.
Unlike Blazor Server (where the widget injects CSS automatically via <HeadContent>), Blazor WASM uses a static index.html that is served before the .NET runtime starts. Add these two lines to your wwwroot/index.html:
<head>
...
<link href="_content/MentorAgent.Abstractions/css/MentorAgent.css?v=10" rel="stylesheet" />
</head>
<body>
...
<script src="_content/MentorAgent.Abstractions/js/MentorAgent.js?v=10"></script>
</body>
Without this, the widget will render unstyled until after WASM initializes (flash of unstyled content).
⚠️ Keep the
?v=and bump it on every upgrade. On Blazor Server the widget writes these tags itself and versions them for you; here they are yours, nothing fingerprints them, and a returning visitor's browser will reuse the copy it already has. A staleMentorAgent.jsfails silently and selectively — the widget still works, but the calls that did not exist in the older file are swallowed by theirtry/catch, so the onboarding tour never appears and voice falls back to reading the whole answer at the end. If a feature seems missing after an upgrade, check the served file before anything else: it should containflagGetandbeginSpeech.
On Blazor Server, <ChatWidget /> injects its own CSS and JS automatically — no changes to _Host.cshtml or App.razor needed.
Widget customization
Theme and appearance
options.Theme = MentorTheme.Minimal; // Default | Dark | Minimal | Custom
options.PrimaryColor = "#7c3aed"; // any hex color
options.Position = ChatPosition.BottomRight; // BottomRight | BottomLeft | TopRight | TopLeft | SideRight | SideLeft
options.AvatarUrl = "/my-avatar.png";
options.BotName = "ShopFlow Assistant";
Welcome message and input
options.WelcomeMessage = "Hello! How can I help you today?";
options.InputPlaceholder = "Ask anything...";
options.EnableSuggestions = true; // show suggestion chips in the welcome panel
Language (10 supported)
options.Language = MentorLanguage.Italian;
// English | Italian | French | German | Spanish | Portuguese | Dutch | Polish | Japanese | Chinese
Voice
options.EnableVoiceInput = true; // microphone button (browser Speech Recognition)
options.EnableVoiceOutput = true; // text-to-speech for AI responses
options.VoiceStreaming = true; // default — speak sentence by sentence while the answer streams
options.VoiceBargeIn = true; // default — taking the floor stops playback
options.VoiceHandsFree = false; // opt-in — keep the conversation going by voice alone
options.VoiceRate = 1.0; // 0.5–2.0
Unlike image input and the hosted-tools badge below, these are not a mirror of server settings. Voice is entirely browser behaviour: the audio never leaves the page, the server sees only the transcript as an ordinary message, and these options are the real thing rather than a copy of something the server enforces.
VoiceStreaming is what makes voice output usable on anything longer than a sentence. Without it the assistant stays silent for the whole response and then recites it. Text is buffered to a sentence boundary and queued as its own utterance, so speech keeps pace with the stream — and the boundary detection knows that 8.459 is one number and that a fenced code block must be dropped whole rather than read out.
VoiceBargeIn stops playback when the user presses the microphone, sends a message, presses ■ Stop or starts a new conversation. It is press-to-interrupt, not acoustic: a browser cannot listen through its own playback without echo cancellation.
VoiceHandsFree sends on silence and reopens the microphone after the spoken answer ends. It needs both voice options on — with nothing to listen to there is nothing to wait for, and the loop would transcribe the assistant. If you set only one, it is ignored.
Onboarding tour
options.EnableOnboardingTour = true; // shown once, on this user's first open of the widget
options.TourUrl = "/mentor/tour"; // default — only change it if you remapped the server endpoints
The steps are generated on the server — only it knows the registered [MentorPage] pages and the assistant's tools — and fetched from GET /mentor/tour. So the server also needs options.EnableOnboardingTour = true; the client flag only decides whether to show it.
That split follows the same rule as the hosted-tools badge: the server is the single source of truth, and a client that reproduces server state locally eventually displays something the server no longer agrees with.
Each step can carry a ready-made question the user sends with one tap — which is the point, since the usual failure of an in-app assistant is not that people cannot find it but that they do not know what to ask. A ? button in the header replays the tour later.
If the fetch fails the widget opens normally and logs a warning: a missing tour is a missing nicety, not a broken chat.
Generative UI cards
Nothing to configure on the client. When a server-side tool returns a MentorCard, the server pushes it on the Cards hub event and the widget renders it under the reply — fields, accent colour and buttons.
Buttons work the same as in Blazor Server: SendMessage sends the text as a user turn, Navigate routes to the URL, UIAction runs an action the current page registered (resolved at click time, so a stale handler from a page you have navigated away from is skipped rather than invoked).
To render a card kind yourself, supply a template and fall back to the built-in renderer for the rest:
<ChatWidget>
<CardTemplate Context="card">
@if (card.Kind == "order") { <OrderCard Card="card" /> }
else { <MentorCardView Card="card" /> }
</CardTemplate>
</ChatWidget>
Cards are built by server-side application code, not by the model, which is why they can safely carry buttons: nothing said in the conversation can add one or change where it points.
Image input (multimodal)
Lets the user attach images to a message. The widget accepts them four ways — 📎 file picker, Ctrl+V clipboard paste, drag & drop onto the composer, and 🔗 remote URL — and shows removable thumbnails before sending and inside the message bubble afterwards.
// Client Program.cs — mirrors the server settings (the server is what actually enforces them)
builder.Services.AddMentorAgentBlazor(options =>
{
options.EnableImageInput = true;
options.MaxImageBytes = 4 * 1024 * 1024; // per image (default 4 MB)
options.MaxImagesPerMessage = 4; // per turn (default 4)
options.AllowedImageTypes = ["image/png", "image/jpeg", "image/webp"]; // MIME allow-list
});
The server must also enable it, with a vision-capable model:
// Server Program.cs
builder.Services.AddMentorAgent(options =>
{
options.ChatClient = azure.GetChatClient("gpt-4.1").AsIChatClient(); // vision-capable
options.EnableImageInput = true;
});
The client values only drive the UI and give the user instant feedback; every attachment is re-validated server-side (allow-list, size, count) before it reaches the model. Images travel over the hub as SendMessage(text, attachments) — the trailing argument is optional, so nothing changes for text-only turns.
Sending images from your own code:
@inject IMentorOrchestrator Orchestrator
await Orchestrator.SendMessageAsync("Cosa non va in questo screenshot?", [
new MentorAttachment { MimeType = "image/png", DataBase64 = base64, FileName = "error.png" },
new MentorAttachment { MimeType = "image/jpeg", Url = "https://cdn.example.com/product.jpg" },
]);
Image bytes are streamed to .NET through
IJSStreamReference, never marshalled as one big interop payload — so this works unchanged on Blazor Server too, without touchingHubOptions.MaximumReceiveMessageSize.
RAG citations
options.ShowRagSources = true; // show citation chips below AI responses
MCP and A2A status badges
When the server has MCP client servers or remote A2A agents configured, the widget can display live status badges in the header. Because the WASM client doesn't read the server configuration directly, you must mirror the relevant settings:
// Client Program.cs
builder.Services.AddMentorAgentBlazor(options =>
{
// MCP badge — mirror McpServers names from the server
options.ShowMcpStatus = true;
options.HasMcpServers = true;
options.McpServerNames = ["time", "filesystem"]; // must match server McpServers[].Name
// A2A badge — mirror RemoteAgents from the server
options.ShowA2AStatus = true;
options.HasRemoteAgents = true;
options.RemoteAgentDisplays = [
new AgentDisplayInfo { Name = "ShopFlow-B", AgentCardUrl = "http://localhost:5001" }
];
});
The MCP badge updates dynamically — when a server connects or disconnects the hub fires McpServerStatusChanged and the badge turns green/red. The A2A badge is static (shows configured agents, no live status).
Note:
McpServerNamesmust match theNamefields inMentorMcpServer[]configured on the server. A mismatch shows a stale "connecting" badge.
Hosted tools badge
If the server enables hosted tools (provider-side web search, code interpreter, file search, image generation, remote MCP), an amber pill lists them in the header. Do not list them here — turn the badge on and let the server say what it enabled:
options.ShowHostedToolsStatus = true; // that's all
The server sends its active set on the HostedToolsDeclared hub event the moment the client connects, and the widget prefers it over any local value. Setting options.HostedTools by hand still works as a pre-connection placeholder, but it is a copy that goes stale: change the server's configuration and the badge starts claiming tools that are not there — which is worse than no badge, because it is believed.
Unlike MCP the badge has no live status afterwards: hosted tools are configuration, not a connection.
Live activity needs no configuration here. When the server has ShowHostedToolActivity on, the widget already shows what the provider is doing, over the hub events it consumes anyway:
- "Ricerca sul web… · .NET 10" in the feedback line, via
ActionExecuting/ActionCompleted - pages cited by web search as citation chips, via
RagSourcesReady— the same panel as RAG, so it also needsShowRagSources - generated images attached to the finished message, via the
GeneratedImagesevent
The WASM client handles all three out of the box.
Page context and UI actions
IMentorPageContext works identically to Blazor Server. Register context data and UI actions in any page — they are sent to the server as a snapshot before each AI message.
Inject page context
@inject IMentorPageContext PageContext
@implements IDisposable
@code {
protected override void OnInitialized()
{
PageContext
.SetPageName("Orders")
.Set("ActiveFilter", "Pending")
.Set("VisibleRows", _orders.Count);
}
public void Dispose() => PageContext.Clear();
}
Register UI actions (no parameter)
PageContext.RegisterUIAction(
"open_create_modal",
"Opens the modal to create a new order",
_ => OpenCreateModal());
Register UI actions with typed parameter
// The AI calls highlight_row(42) — parameter deserialized automatically
PageContext.RegisterUIAction<int>(
"highlight_row",
"Highlights the specified order row",
id => HighlightRow(id),
parameterHint: "integer: order ID");
Async UI actions
PageContext.RegisterUIActionAsync<OrderModel>(
"prefill_form",
"Pre-fills the edit form with order data",
async model => {
_formModel = model;
await InvokeAsync(StateHasChanged);
},
parameterHint: "JSON: { orderId, amount, status }");
Remove an action
PageContext.UnregisterUIAction("highlight_row");
Signal page ready (for pages with async UI actions)
Use OnInitializedAsync — not OnAfterRenderAsync. Calling SignalReady() from OnAfterRenderAsync silently breaks all UI actions because the AI may have already timed out waiting for the signal.
protected override async Task OnInitializedAsync()
{
await LoadDataAsync();
PageContext.SignalReady(); // must be last — tells AI that UI actions are ready
}
Page navigation
⚠️
[MentorPage]attributes are defined in the server project (scanned byScanAssemblies), not in the client project.
// Server project — scanned via options.ScanAssemblies
[MentorPage("/orders", Name = "Orders", Description = "Order management")]
public class OrdersPage { }
[MentorPage("/products", Name = "Products", HasUIActions = true, ReadyTimeout = 3000)]
public class ProductsPage { }
The AI calls navigate_to("/orders") — the WASM widget handles navigation automatically via Blazor's NavigationManager.
UI Action overloads reference
Four overloads are available, from simple to fully typed and async:
| Overload | Parameter | Execution | Use when |
|---|---|---|---|
RegisterUIAction(name, desc, Action<object?>) |
Raw object? |
Synchronous | Simple no-param or legacy code |
RegisterUIAction<TParam>(name, desc, Action<TParam>) |
Auto-deserialized from JSON | Synchronous | Typed param, sync handler |
RegisterUIActionAsync(name, desc, Func<object?, Task>) |
Raw object? |
Async | No-param async actions (e.g. async _ => { await LoadAsync(); }) |
RegisterUIActionAsync<TParam>(name, desc, Func<TParam, Task>) |
Auto-deserialized from JSON | Async | Typed param, async handler (recommended) |
UnregisterUIAction(name) |
— | — | Remove a specific action dynamically |
Automatic parameterHint generation
For typed overloads, parameterHint is auto-generated from the type when omitted:
TParam |
Auto-generated hint |
|---|---|
int, long |
"integer" |
float, double, decimal |
"number" |
bool |
"boolean" |
string |
"string" |
Guid |
"string (GUID)" |
DateTime |
"string (ISO 8601 date)" |
Status (enum) |
"string (Active\|Inactive\|Pending)" |
List<int> |
"array<integer>" |
OrderFormModel (class) |
"{ customerId: integer, productName: string, ... }" |
Override only when extra clarity is needed:
.RegisterUIAction<int>(
"highlight_row", "Highlights an order row",
id => HighlightRow(id),
parameterHint: "integer: order ID") // ← manual override
Async handlers are awaited — the AI waits for completion before continuing. This makes multi-action chains reliable.
[MentorPage] parameters (server project)
| Parameter | Required | Description |
|---|---|---|
Url |
✅ | Page URL (e.g. "/orders") |
Name |
✅ | Human-readable page name injected into the system prompt |
Description |
— | Optional feature description |
HasUIActions |
— | If true, AI waits for PageContext.SignalReady() before UI actions. Default: false |
ReadyTimeout |
— | Timeout in ms for SignalReady(). Default: 2000 |
All AddMentorAgentBlazor() options
| Option | Type | Default | Description |
|---|---|---|---|
HubUrl |
string |
"/mentor-hub" |
URL of the MentorAgent.Server SignalR hub |
BotName |
string |
"Mentor AI" |
Bot name in the widget header |
WelcomeMessage |
string? |
null |
Welcome message (HTML supported) |
AvatarUrl |
string? |
null |
Custom avatar URL |
InputPlaceholder |
string? |
null |
Input box placeholder |
Theme |
MentorTheme |
Default |
Widget visual theme |
Position |
ChatPosition |
BottomRight |
Widget position on screen |
PrimaryColor |
string? |
null |
Custom hex accent color |
Language |
MentorLanguage |
English |
Language for widget UI strings (10 languages supported) |
EnableVoiceInput |
bool |
false |
Show microphone button (browser Speech Recognition) |
EnableVoiceOutput |
bool |
false |
Text-to-speech for AI responses (browser Speech Synthesis) |
VoiceStreaming |
bool |
true |
Speak each sentence as it streams instead of reading the finished answer back |
VoiceBargeIn |
bool |
true |
Stop playback when the user takes the floor |
VoiceHandsFree |
bool |
false |
Voice-only conversation loop. Ignored unless both voice options are on |
VoiceRate |
double |
1.0 |
SpeechSynthesisUtterance.rate — useful range 0.5–2.0 |
EnableOnboardingTour |
bool |
false |
Show the guided tour on first open. The server must enable it too |
TourUrl |
string |
"/mentor/tour" |
Where to fetch the tour steps from, relative to the app base address |
EnableSuggestions |
bool |
false |
Suggestion chips in the welcome panel |
EnableImageInput |
bool |
false |
Image attachments — 📎 upload, paste, drag & drop and 🔗 URL. Must also be enabled server-side |
MaxImageBytes |
int |
4194304 |
Client-side size cap per image (4 MB). Mirrors the server option |
MaxImagesPerMessage |
int |
4 |
Client-side cap on images per message. Mirrors the server option |
AllowedImageTypes |
List<string> |
png, jpeg, gif, webp |
Client-side MIME allow-list. Mirrors the server option |
ShowRagSources |
bool |
false |
Citation chips below AI responses |
ShowHostedToolsStatus |
bool |
false |
Show the hosted-tools badge (provider-side web search / code interpreter / file search / image generation / remote MCP) |
HostedTools |
MentorHostedTools |
None |
Pre-connection placeholder only. The server publishes its actual set on HostedToolsDeclared at connect and the widget prefers that — leave this unset rather than keeping a copy that goes stale |
ShowMcpStatus |
bool |
false |
Show MCP server connection status badge in the widget header |
HasMcpServers |
bool |
false |
Whether the server has MCP client servers configured (enables the MCP badge) |
McpServerNames |
List<string> |
[] |
Names of MCP servers configured on the server — pre-populates the badge in "connecting" state at startup |
ShowA2AStatus |
bool |
false |
Show A2A remote agent status badge in the widget header |
HasRemoteAgents |
bool |
false |
Whether the server has remote A2A agents configured (enables the A2A badge) |
RemoteAgentDisplays |
List<AgentDisplayInfo> |
[] |
Remote A2A agents to display in the A2A badge and detail bar |
How it works
[Blazor WASM Browser]
ChatWidget
↓ IMentorOrchestrator (WasmMentorOrchestrator)
↓ SignalR
[ASP.NET Core Server — MentorAgent.Server]
MentorHub
↓ MentorOrchestrator (full AI pipeline)
↓ AI Provider (Azure OpenAI, OpenAI, Ollama...)
↑ Streaming events (chunks, confirmations, navigation, UI actions...)
↑ SignalR
ChatWidget renders response
- Page context (page name, data, UI action descriptions) is sent to the server before every message via
UpdatePageContext. - UI action invocations arrive from the server as
UIActionRequestedand are executed locally in the browser byWasmMentorStateService. - Confirmation dialogs (HITL) are shown by
ConfirmationBanner. The response is sent viaPOST /mentor/approve?actionId=...&approved=true|false(HTTP) — not via a hub method. SignalR processes hub messages sequentially per connection, so callingRespondToApprovalvia hub whileSendMessageis awaiting would deadlock.WasmMentorStateServicehandles this automatically. - Stop (cancelling an in-flight turn) is sent via
POST /mentor/cancel?connectionId=...(HTTP) — not via theCancelRequesthub method, for the same sequential-dispatch reason: whileSendMessageis streaming, SignalR cannot dispatch another hub invocation on that connection, so the hub call would only run after the turn it was meant to abort.WasmMentorOrchestratorhandles this automatically. - Navigation triggered by the AI arrives as
NavigationRequestedand is handled by Blazor'sNavigationManager.
Events — IMentorStateService
ChatWidget handles all events automatically. If you need to subscribe to events directly in your own components, inject IMentorStateService:
@inject IMentorStateService State
@implements IDisposable
protected override void OnInitialized()
{
State.OnStreamingChunk += OnChunk;
State.OnStreamingCompleted += OnCompleted;
State.OnBusyChanged += OnBusy;
State.OnError += OnError;
State.OnActionExecuting += OnActionExecuting;
State.OnActionCompleted += OnActionCompleted;
State.OnActionFailed += OnActionFailed;
State.OnConfirmationRequired += OnConfirmationRequired;
State.OnNavigationRequested += OnNavigation;
State.OnRagSourcesReady += OnRagSources;
State.OnTeamMemberSpeaking += OnTeamSpeaking;
State.OnUIActionExecuting += OnUIActionStart;
State.OnUIActionCompleted += OnUIActionEnd;
}
public void Dispose()
{
State.OnStreamingChunk -= OnChunk;
// ... unsubscribe all
}
Complete event reference
| Event | Signature | Fired when | Typical use |
|---|---|---|---|
OnStreamingChunk |
Action<string> |
Each streaming token | Append text to a custom chat bubble |
OnStreamingCompleted |
Action |
Full response received | Finalize message, re-enable input |
OnBusyChanged |
Action<bool> |
AI starts/stops processing | Show/hide spinner |
OnError |
Action<string> |
Critical error (rate limit, safety block) | Show error banner |
OnActionExecuting |
Action<string> |
Tool/agent is executing | Show action feedback bar |
OnActionCompleted |
Action<string> |
Tool execution succeeded | Hide feedback bar |
OnActionFailed |
Action<string> |
Tool execution failed | Show error in feedback |
OnConfirmationRequired |
Action<ConfirmationRequest> |
Destructive action needs user approval | Show custom confirmation dialog |
OnApprovalResponse |
Action<ConfirmationRequest, bool> |
User confirmed/rejected | Internal — used by orchestrator |
OnNavigationRequested |
Action<string> |
AI triggered navigation | Custom routing logic |
OnRagSourcesReady |
Action<IReadOnlyList<MentorRagResult>> |
RAG documents retrieved | Show custom citation UI |
OnTeamMemberSpeaking |
Action<string, string> |
GroupChat member speaking | Show "Team · Role" feedback |
OnUIActionExecuting |
Action<string> |
UI action started | Custom feedback |
OnUIActionCompleted |
Action<string> |
UI action completed | Custom feedback |
OnMcpServerStatusChanged |
Action<string, bool> |
MCP server connects (true) or disconnects (false) |
Update the MCP status badge — forwarded over the hub as McpServerStatusChanged |
OnCardsReady |
Action<IReadOnlyList<MentorCard>> |
A tool returned generative-UI cards | Render them yourself instead of using CardTemplate |
OnGeneratedImages |
Action<IReadOnlyList<string>> |
The hosted image tool produced images | Show them in your own gallery. Each string is a data: URI or URL |
OnHostedToolsDeclared |
Action<MentorHostedTools> |
Once per connection, right after connect | Drive your own capability badge from what the server actually enabled, rather than from a client-side guess |
All eighteen events are listed above — that is the whole of IMentorStateService's read side.
All events fire on a background thread from the SignalR connection. Always use
InvokeAsync(StateHasChanged)when updating Blazor component state from these handlers.
Sending state changes — the Notify* side
Each event has a matching Notify* method on the same interface (NotifyStreamingChunk,
NotifyCardsReady, …). Those are how the orchestrator raises the events; application code
subscribes and does not call them.
The two members you do call are the HITL replies:
| Method | Purpose |
|---|---|
ConfirmAsync(Guid actionId) |
Approve the pending action |
Cancel(Guid actionId) |
Reject it |
actionId is ConfirmationRequest.ActionId from OnConfirmationRequired. In WASM these travel to
the server over POST /mentor/approve, never as a hub method — SignalR dispatches one hub call at a
time per connection, so an approval sent through the hub would deadlock behind the turn that is
waiting for it.
Custom HITL confirmation dialog
By default, ChatWidget renders a built-in ConfirmationBanner. If you want to replace it with your own modal or dialog, subscribe to OnConfirmationRequired and call ConfirmAsync / Cancel yourself.
This works identically whichever
MentorOptions.HitlModethe server uses (Blockingor the Agent Framework'sNativeflow) — the WASM client sees the sameConfirmationRequiredevent and replies over the samePOST /mentor/approveendpoint, so switching mode server-side needs no client change.
@inject IMentorStateService State
@implements IDisposable
@if (_pendingConfirmation is not null)
{
<div class="my-confirm-dialog">
<p>@_pendingConfirmation.Message</p>
<button @onclick="Approve">Conferma</button>
<button @onclick="Reject">Annulla</button>
</div>
}
@code {
private ConfirmationRequest? _pendingConfirmation;
protected override void OnInitialized()
{
State.OnConfirmationRequired += OnConfirmationRequired;
State.OnApprovalResponse += OnApprovalResponse;
}
private void OnConfirmationRequired(ConfirmationRequest req)
=> InvokeAsync(() => { _pendingConfirmation = req; StateHasChanged(); });
private void OnApprovalResponse(ConfirmationRequest req, bool approved)
=> InvokeAsync(() => { _pendingConfirmation = null; StateHasChanged(); });
private async Task Approve()
{
if (_pendingConfirmation is null) return;
await State.ConfirmAsync(_pendingConfirmation.ActionId);
}
private void Reject()
{
if (_pendingConfirmation is null) return;
State.Cancel(_pendingConfirmation.ActionId);
}
public void Dispose()
{
State.OnConfirmationRequired -= OnConfirmationRequired;
State.OnApprovalResponse -= OnApprovalResponse;
}
}
ConfirmationRequesthas three properties:ActionId(Guid),ToolName(string), andMessage(string). UseToolNameto customize the dialog copy per action type if needed.
Blazor Auto tip
In a Blazor Auto app, you can keep <ChatWidget /> with @rendermode="InteractiveServer" — it stays server-side with no changes to your existing MentorAgent setup. Use MentorAgent.Blazor only when you need the widget to run fully in WebAssembly.
@* Keep server-side in Blazor Auto — zero changes needed *@
<ChatWidget @rendermode="InteractiveServer" />
Requirements
- .NET 10.0+
- Server project must have
MentorAgent+MentorAgent.Serverinstalled and configured - Browser with WebAssembly support
Related Packages
| Package | Purpose |
|---|---|
| MentorAgent | Blazor Server app — AI orchestration engine |
| MentorAgent.Server | Any ASP.NET Core backend — SignalR hub + SSE + MCP + A2A |
| MentorAgent.Abstractions | Shared UI components (transitive dep — no need to install directly) |
| MentorAgent.Declarative | Optional — define Level-2 specialist agents in YAML instead of C# |
License
MIT — the full text ships in the repository's LICENSE file.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- MentorAgent.Abstractions (>= 1.0.0-preview.5)
- Microsoft.AspNetCore.SignalR.Client (>= 10.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0-preview.5 | 37 | 8/12/2026 |
| 1.0.0-preview.4 | 57 | 8/4/2026 |
| 1.0.0-preview.3 | 58 | 7/24/2026 |
| 1.0.0-preview.2 | 68 | 6/22/2026 |
| 1.0.0-preview | 74 | 6/22/2026 |
1.0.0-preview.5
=== Generative UI Level 2 - cards ===========================================
- NEW - the widget renders MentorCards sent by the server on the "Cards" hub event. Buttons send a message, navigate, or run a UI action the current page registered.
- NEW - ChatWidget.CardTemplate: supply your own rendering for a card kind and fall back to the built-in renderer for the rest.
=== Voice ===================================================================
- CHANGED - voice output speaks sentence by sentence while the answer streams, instead of reading the finished reply back.
- NEW - VoiceStreaming, VoiceBargeIn, VoiceHandsFree, VoiceRate. Unlike image input and the hosted-tools badge these are not a mirror of server settings: voice is browser behaviour and the server only ever sees the transcript.
=== Onboarding tour =========================================================
- NEW - EnableOnboardingTour and TourUrl: the steps are generated on the server, the only side that knows the registered pages and tools, and fetched from GET /mentor/tour. The server must enable the tour too. If the fetch fails the widget opens normally.