Skip to content

MAUI AI Chat Control — ASP.NET AI Components convergence#274

Draft
mattleibow wants to merge 51 commits into
mainfrom
mattleibow/copilot-chat-control
Draft

MAUI AI Chat Control — ASP.NET AI Components convergence#274
mattleibow wants to merge 51 commits into
mainfrom
mattleibow/copilot-chat-control

Conversation

@mattleibow
Copy link
Copy Markdown
Member

@mattleibow mattleibow commented May 13, 2026

Summary

Drop-in MAUI AI chat control (CopilotChatView) backed by the ASP.NET Core AI Components engine. Provides a customizable, templated chat UI for any MAUI app with full tool-calling, rich text, reasoning, and approval flows.

Architecture

┌──────────────────────────────┐  ┌───────────────────────────────┐
│ Microsoft.Maui.AI.Chat       │  │ Microsoft.AspNetCore.Components│
│ .Controls                    │  │ .AI.Blazor                     │
│ (CopilotChatView, Templates, │  │ (ChatPage, BlockRenderer, etc) │
│  Themes, MAUI renderers)     │  │                                │
└──────────────┬───────────────┘  └───────────────┬───────────────┘
               └──────────────┬───────────────────┘
┌─────────────────────────────▼────────────────────────────────────┐
│  Microsoft.AspNetCore.Components.AI.Core (upstream engine)        │
│  UIAgent, AgentContext, Pipeline, Blocks, RichText, Attributes   │
└──────────────────────────────────────────────────────────────────┘

What's Included

Libraries

  • Microsoft.AspNetCore.Components.AI.Core — Headless AI agent engine (copied from dotnet/aspnetcore#javiercn/ai-components-e2e-tests)
  • Microsoft.AspNetCore.Components.AI.Blazor — Blazor UI components (ChatPage, MessageList, BlockRenderer)
  • Microsoft.AspNetCore.Components.AI.SourceGenerators[ToolBlock] source gen for typed tool parameters
  • Microsoft.Maui.AI.Chat.Controls — Native MAUI templated chat control (CopilotChatView)

Test Projects (403 tests total)

  • Microsoft.AspNetCore.Components.AI.Core.Tests (247) — Engine, Pipeline, Blocks, Samples, Recorded tests
  • Microsoft.AspNetCore.Components.AI.Blazor.Tests (96) — Component rendering with TestFramework
  • Microsoft.Maui.AI.Chat.Controls.Tests (60) — MAUI control unit tests

Sample Apps

  • AiControlsSample — Native MAUI with 8 demos (basic, tools, approval, multi-turn, reasoning, errors, state, streaming)
  • AiControlsBlazorSample — Blazor Hybrid using the Blazor ChatPage component

Key Features

  • ✅ Customizable via XAML ControlTemplate and DataTemplateSelectors
  • ✅ Full tool-calling support (backend + UI actions + approval flows)
  • ✅ Rich text rendering (markdown, code blocks, bold/italic)
  • ✅ Reasoning/thinking block display
  • ✅ Multi-turn conversations
  • ✅ Welcome states with suggestion pills
  • ✅ Streaming responses
  • ✅ Cancel/retry support
  • ✅ Source generators for typed tool blocks ([ToolBlock], [ToolParameter])

Upstream Tracking

Changes to the copied ASP.NET AI Components code are tracked in src/AIControls/Microsoft.AspNetCore.Components.AI.Core/UPSTREAM-CHANGES.md. The goal is to keep modifications minimal so we can swap in the official packages when they ship.

mattleibow and others added 13 commits May 12, 2026 19:48
A fully composable, drop-in MAUI chat control backed by IChatClient
(Microsoft.Extensions.AI) with a Copilot SDK adapter as default backend.

Architecture:
- CopilotChatView is a ContentView with ControlTemplate (TemplatedParent
  binding pattern) — never touches user's BindingContext
- Every sub-component is replaceable via DataTemplate BindableProperties:
  5 message templates + InputTemplate + ApprovalTemplate + WelcomeTemplate
  + SuggestionItemTemplate
- Users can replace the entire ControlTemplate for total visual control
- Or just override Copilot* resource keys for color/size tweaks

Backend:
- Accepts any IChatClient — works with Azure OpenAI, Ollama, etc.
- Ships CopilotSdkChatClient: IChatClient adapter wrapping GitHub Copilot SDK
- Control owns the chat flow: streaming, tool call correlation, approval
- Events for observability: MessageSending/Sent, ResponseReceived, etc.

Themes:
- DefaultTheme (normal spacing) + CompactTheme (dense layout)
- Both with AppThemeBinding for dark/light mode
- Copilot-branded indigo/purple accent, cool gray surfaces

Package: Microsoft.Maui.CopilotChat (net10.0)
Sample: samples/CopilotChat.Sample (MAUI multi-target + DevFlow)
Tests: 17 passing (template selector, message model INPC, expand/collapse)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…errors

- Microsoft.Maui.CopilotChat no longer depends on GitHub.Copilot.SDK
- New Microsoft.Maui.CopilotChat.CopilotSdk package for the adapter
- TreatWarningsAsErrors=true on all new projects
- Added CopilotSdk.Tests with 5 tests
- Fixed FunctionApprovalRequestContent usage (MEAI 10.3.0 API)
- Sample app updated to use AddCopilotChatWithCopilotSdk()

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes from Opus 4.7:
- CRITICAL: Fix approval flow dead end — store pending FunctionApprovalRequestContent,
  send FunctionApprovalResponseContent on approve/reject, resume streaming
- HIGH: Fix ClearMessages CTS race — use Interlocked.Exchange, defer disposal
- HIGH: Fix history not flushed on error — move to finally block
- MEDIUM: Fix InverseBoolConverter default (false for non-bool)
- MEDIUM: Add SystemMessage propertyChanged callback to rebuild history

Fixes from GPT 5.5:
- CRITICAL: Fix template selector bindings — CLR properties can't be bound in
  ControlTemplate XAML. Wire selector from OnApplyTemplate code instead.
  Added propertyChanged callbacks on all 5 message template properties.
- HIGH: Fix duplicate streaming — skip AssistantMessageEvent text (only emit deltas)
- HIGH: Fix error propagation — await done.Task after loop to surface exceptions
- HIGH: Fix singleton session issues — add SemaphoreSlim for init, add ResetSessionAsync
- MEDIUM: Fix Dispose sync-over-async deadlock — implement IAsyncDisposable,
  fire-and-forget in sync Dispose
- MEDIUM: Fix EnsureSessionAsync race — double-check locking with SemaphoreSlim
- MEDIUM: Add configurable StreamingTimeout (default 5min)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Unit tests (11):
- Constructor, Dispose, DisposeAsync (multiple calls safe)
- GetService returns null for unknown types
- StreamingTimeout default and setter
- Empty prompt and no-user-message yield nothing
- IChatClient and IAsyncDisposable interface conformance

Integration tests with real Copilot SDK (7):
- GetResponseAsync basic prompt returns non-empty response
- GetStreamingResponseAsync yields text chunks containing expected content
- Streaming can be cancelled via CancellationToken
- SystemMessage is respected by the model
- Multiple sequential calls maintain session context
- ResetSessionAsync clears session state
- DisposeAsync after use cleans up resources

Also fixed sample app to use ProjectReference for DevFlow agent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bugs fixed:
- Sample app: ChatClient was null — DI wasn't resolving IChatClient
  into MainPage. Fixed by injecting IChatClient via constructor DI
  instead of resolving from Handler.MauiContext.
- Sample app: Missing platform entry points (Program.cs, AppDelegate,
  MainActivity, MainApplication) — app couldn't launch
- Sample app: Obsolete MainPage setter — use CreateWindow pattern
- Suggestion chips: TapGestureRecognizer binding via RelativeSource
  AncestorType doesn't work across ControlTemplate boundaries.
  Fixed by wiring gesture recognizers from OnApplyTemplate code via
  PART_Suggestions ChildAdded event.
- CopilotSdkChatClient: Bundled CLI not found in Mac Catalyst app
  bundle. Added CliPath config option to use system-installed copilot.
- Directory.Build.props: Added ValidateXcodeVersion=false to allow
  building with newer Xcode than the workload expects.

Verified working via DevFlow:
- Welcome screen renders correctly
- User message right-aligned in indigo bubble
- AI streaming response left-aligned with shadow
- Multi-turn conversation maintains context
- Input field, send button, suggestion chips all functional

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The ConcurrentQueue + Task.Delay(50ms) polling loop was batching
all SDK events together — by the time the consumer woke from sleep,
multiple delta events had already been queued and were yielded in
one burst, appearing as a sudden complete message.

Replaced with System.Threading.Channels.Channel<ChatResponseUpdate>
which uses async producer-consumer semantics: each TryWrite from
the SDK event handler immediately wakes the consumer's ReadAllAsync,
yielding each delta chunk individually with no artificial delay.

Verified streaming works visually via DevFlow: essay response text
grows progressively on screen over multiple seconds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…SDK tests

Sample app (scaffolded from dotnet new maui -f net10.0):
- Shell with 3 tabs: Home, Chat, Settings
- Home page with tool descriptions and Open Chat button
- Chat page with CopilotChatView + 5 registered tools (weather,
  calculator, random fact, app navigation, app info)
- Settings page to configure system prompt, model, welcome text
- Proper DI: IChatClient + SampleTools injected into ChatPage

CopilotSdkChatClient fixes:
- Now passes ChatOptions.Tools to SessionConfig.Tools — the SDK
  handles tool invocation natively (no UseFunctionInvocation needed)
- Recreates session when tools change between calls

Integration tests (14 passing, all with real Copilot SDK):
- Text: GetResponse returns text, system message respected
- Streaming: multiple chunks, chunks arrive over time (not batched)
- Tool calling: single tool invoked, result in response, args received,
  correct tool selected from multiple, stream includes FunctionCall/
  FunctionResult content
- Session: context maintained, reset clears context
- Cancellation: streaming can be cancelled
- Lifecycle: dispose after use, GetService returns session

Unit tests (10 passing):
- Config defaults, property setting
- Constructor, dispose idempotent, streaming timeout
- Empty/missing prompts, interface conformance

Total: 37 tests (17 control + 10 SDK unit + 10 SDK integration)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Control enhancements:
- Avatar support: UserAvatarSource/Text, AssistantAvatarSource/Text,
  ShowAvatars, AvatarSize BindableProperties. Default templates render
  circular avatar with initials or image on each message bubble.
- Timestamps: Timestamp + IsStreaming on CopilotChatMessage, ShowTimestamps
  BindableProperty on control
- Localizable text: SendButtonText, ApproveButtonText, RejectButtonText,
  TypingIndicatorText — no more hardcoded strings in ControlTemplate
- Identity: UserDisplayName, AssistantDisplayName BindableProperties
- AddMessage populates avatar/timestamp from control defaults
- IsStreaming set to false when response completes
- Fixed fully-qualified namespace usage (System.Text.Json, Channels)

Sample app redesigned:
- Sidebar layout: main content + persistent chat panel on right
- FAB toggle for narrow screens
- Inline settings: user/assistant name, show avatars, show timestamps,
  clear chat
- 5 callable tools (weather, calculator, facts, navigation, app info)
- Scaffolded from dotnet new maui -f net10.0
- Added network.server entitlement for DevFlow

Theme:
- Avatar color keys (CopilotUserAvatar*, CopilotAssistantAvatar*)
- Localizable button text bindings in ControlTemplate

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The dotnet new maui template enables App Sandbox by default, which
blocks the Copilot SDK from spawning the copilot CLI process
(Operation not permitted). Disabled for this dev sample.

Production apps should either disable sandbox or use CopilotClientOptions.CliUrl
to connect to an external Copilot server instead of spawning a child process.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sample app is now a 'Copilot Chat Playground' with real-time
customization of every control property:

- General: welcome icon/title, placeholder, clear chat
- Identity: user/assistant names, avatar text, show avatars toggle,
  avatar size slider
- Messages: user/assistant bubble color swatches (6 presets each),
  accent color swatches, timestamps toggle, spacing slider,
  font size slider
- Localization: send/approve/reject/typing indicator text
- Tools: weather, calculator, facts, app info

Runtime resource overrides: color swatches update Copilot* resource
keys on the control and force template refresh so StaticResource
bindings pick up the new values.

Also fixed: all icons switched from FluentFilled glyphs (requires
bundled font) to standard Unicode emoji that render everywhere.

Verified via DevFlow:
- Tool calling works (GetCurrentWeather invoked, result displayed)
- Avatars render correctly (You/AI circles)
- Streaming works progressively
- All settings sections visible and functional

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…sync

Fixes:
- Theme templates now use DynamicResource (not StaticResource) for all
  Copilot* color/size keys. This means ChatView.Resources['CopilotAccent']
  = newColor instantly updates rendered bubbles without template refresh.
- Placeholder BindableProperty now has propertyChanged that syncs to
  PART_Input Entry (template binding wasn't propagating reliably)
- SendButtonText/ApproveButtonText/RejectButtonText same fix — direct
  PART_ element sync via propertyChanged callbacks
- SyncTemplateProperties() in OnApplyTemplate sets initial values on
  all PART_ elements
- Sample defers WireTextSettings to Loaded event to avoid TextChanged
  firing during Entry initialization

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The sed that converted StaticResource to DynamicResource also hit
converter references (CopilotInverseBool, CopilotIsNotNull). XAML
Converter= only accepts StaticResource — DynamicResource causes
XamlParseException at startup.

Fixed by reverting converter references back to StaticResource while
keeping all color/size keys as DynamicResource for live theming.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ggle

Avatar:
- ShowAvatar, AvatarSize, ShowTimestamp now on CopilotChatMessage model
  (DataTemplates can't bind to templated parent, only to their item)
- Templates bind to message.ShowAvatar, message.AvatarSize with IsVisible
- Control propertyChanged callbacks propagate to all existing messages
- Avatar uses StrokeShape='RoundRectangle 999' for perfect circle at any size

Timestamps:
- TimestampText property on message ('h:mm tt' format)
- Templates show timestamp below bubble when ShowTimestamp=true

Thinking indicator:
- Shows TypingIndicatorText ('Thinking…') as system message after send
- Removed when first text or tool call content arrives

Compact theme toggle:
- Sample settings has switch to swap DefaultTheme ↔ CompactTheme at runtime

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

Expert Code Review — PR #274

Methodology: 3 independent reviewers with adversarial consensus (batch-split mode: 60 files split across 3 reviewers, 20 each)

13 findings posted as inline comments (5 moderate, 8 minor)

Severity Count Details
🟡 Moderate 5 1 consensus (NavigateToPage broken), 4 batch-split (dispose race, CTS leak, missing README, unconditional integration tests)
🟢 Minor 8 Batch-split findings across build config, SDK client, and control

CI Status

  • license/cla, build (macos-latest), build (ubuntu-24.04), build (windows-latest) — multiple workflows passed
  • ⏳ Several build jobs still in progress at review time
  • Overall commit status: success

Test Coverage Assessment

The PR includes 3 test projects with 41 tests (17 control unit, 10 SDK unit, 14 integration). However, the integration tests (CopilotSdkIntegrationTests) require authenticated Copilot CLI and will fail in standard CI environments — these should be gated behind an environment check or trait.

Discarded Findings

No findings were discarded — all survived consensus or qualified under batch-split rules.

Notes

  • Due to the 60-file PR exceeding the 50-file threshold, each reviewer analyzed a different batch. Single-reviewer findings are annotated "low confidence — single reviewer (batch split)" and had severity downgraded by one level per protocol.
  • The most impactful consensus finding is the NavigateToPage tool which will throw NullReferenceException at runtime (2/3 reviewers independently identified this).

Generated by Expert Code Review · 3 independent reviewers with adversarial consensus

Generated by Expert Code Review (auto) for issue #274 · ● 12.9M ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Expert Code Review: 13 findings posted inline (5 moderate, 8 minor). See the lean summary comment for full details and methodology.

Generated by Expert Code Review (auto) for issue #274 · ● 12.9M

Comment on lines +82 to +84
MainThread.BeginInvokeOnMainThread(async () =>
{
await Shell.Current.GoToAsync(route);
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.

🟡 MODERATE · 2/3 consensus

NavigateToPage is broken: Shell.Current is null because the app uses Window(new MainPage(...)) — not Shell navigation. This will throw NullReferenceException. Additionally, BeginInvokeOnMainThread fire-and-forgets the navigation, so exceptions are unobserved and the method reports success before navigation completes (or fails).

Suggestion: Either add Shell navigation to the app, or guard with a null check + use MainThread.InvokeOnMainThreadAsync and await the result so failures propagate back to the caller.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

These files no longer exist — the entire codebase was rewritten in commits cb4ce39..f20a9ac (phases 1-9). The old CopilotChat package was replaced with a two-library architecture: Microsoft.Maui.AI.Chat (headless) + Microsoft.Maui.AI.Chat.Controls (MAUI UI). The concerns raised here (Shell navigation, race conditions, CTS leaks) are not present in the new code.

Comment on lines +47 to +53
private async Task DisposeAsyncCore()
{
if (_session is not null)
await _session.DisposeAsync().ConfigureAwait(false);
if (_client is not null)
await _client.DisposeAsync().ConfigureAwait(false);
_initLock.Dispose();
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.

🟡 MODERATE · low confidence — single reviewer (batch split)

Race condition in DisposeAsyncCore: This method disposes _initLock without first acquiring it. If another thread is inside EnsureSessionAsync holding the lock, _initLock.Dispose() will cause ObjectDisposedException in the finally { _initLock.Release(); } of that thread, leaking a partially-constructed session/client.

Suggestion: Acquire _initLock before reading/disposing _session/_client, release it, then dispose the lock. Also check _disposed after acquiring the lock in EnsureSessionAsync.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This file was deleted — CopilotSdkChatClient no longer exists. The new architecture accepts any IChatClient implementation via DI with no internal session management.

ResponseReceived?.Invoke(this, assistantMessage);
}
else if (string.IsNullOrEmpty(responseText))
AddMessage(ChatMessageKind.Assistant, "(no response)");
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.

🟢 MINOR · low confidence — single reviewer (batch split)

Spurious "(no response)" in tool-only flows: When the assistant emits only tool/approval events without text, the UI shows a "(no response)" message even though the flow is valid.

Suggestion: Suppress the fallback when tool events or approval requests occurred during the stream — only emit it for genuinely empty responses.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Resolved — the old CopilotChatView.cs in src/CopilotChat/ was deleted. The new CopilotChatView (in src/AIControls/Microsoft.Maui.AI.Chat.Controls/) uses a completely different architecture with ChatSession handling streaming separately from the UI layer.

Comment thread Directory.Build.props Outdated
<SignAssembly>false</SignAssembly>
<NoSwixBuildPlugin>true</NoSwixBuildPlugin>
<!-- Skip Xcode version check — allows building with newer Xcode than the workload expects -->
<ValidateXcodeVersion>false</ValidateXcodeVersion>
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.

🟢 MINOR · low confidence — single reviewer (batch split)

Repo-wide Xcode validation suppression: This disables ValidateXcodeVersion for all projects in the repository, not just the CopilotChat sample. Future contributors using an incompatible Xcode may produce broken iOS binaries without warning.

Suggestion: Move this to the sample's .csproj or a Directory.Build.props inside samples/CopilotChat.Sample/ to limit scope.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed — moved ValidateXcodeVersion=false out of the repo-wide Directory.Build.props and kept it only in the sample app's .csproj where it's needed. Also fixed the Android SupportedOSPlatformVersion to match the repo default (24.0).

{
options.Model = "gpt-4.1";
options.SystemMessage = "You are a helpful assistant. Be concise and friendly. When using tools, explain what you're doing.";
options.CliPath = "/opt/homebrew/bin/copilot";
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.

🟢 MINOR · low confidence — single reviewer (batch split)

Hardcoded macOS ARM Homebrew path: /opt/homebrew/bin/copilot only exists on Apple Silicon macOS. Fails on Windows, Intel Mac (/usr/local/bin/copilot), and mobile platforms.

Suggestion: Remove or comment out the hardcoded path — let the SDK resolve copilot from PATH. If needed, conditionalize with #if MACCATALYST.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Deleted — samples/CopilotChat.Sample/MauiProgram.cs no longer exists. The new sample (samples/AiControlsSample/) uses Azure OpenAI via Microsoft.Extensions.AI.OpenAI with user secrets, no Copilot CLI dependency.

{
Model = _config.Model,
Streaming = true,
OnPermissionRequest = PermissionHandler.ApproveAll,
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.

🟢 MINOR · low confidence — single reviewer (batch split)

PermissionHandler.ApproveAll hardcoded: All tool-execution permission requests are auto-approved with no override path via CopilotChatConfiguration. The PR description lists "Approval flow" as a feature, but this SDK client bypasses it at the protocol level.

Suggestion: Add Func<PermissionRequest, Task<bool>>? OnPermissionRequest to CopilotChatConfiguration with ApproveAll as the default, and wire it here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Resolved — this file (CopilotSdkChatClient.cs) was deleted in the rewrite. None of these concerns apply to the current code.

Comment on lines +32 to +37
public void Dispose()
{
if (_disposed) return;
_disposed = true;
// Fire-and-forget async cleanup to avoid sync-over-async deadlock
_ = DisposeAsyncCore();
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.

🟢 MINOR · low confidence — single reviewer (batch split)

Dispose() fire-and-forgets async cleanup: DisposeAsyncCore() is invoked without await and the task is discarded. If GC collects the object before the task completes, _session and _client are silently abandoned. Additionally, a subsequent DisposeAsync() call will no-op due to _disposed being already set, with no guarantee the first fire-and-forget has completed.

Suggestion: Consider throwing InvalidOperationException from Dispose() directing callers to use await using, or use Interlocked.Exchange to ensure at-most-once execution shared between both paths.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Resolved — this file (CopilotSdkChatClient.cs) was deleted in the rewrite. None of these concerns apply to the current code.

chunks.Add(update);

var fullText = string.Join("", chunks
.SelectMany(c => c.Contents.OfType<TextContent>())
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.

🟢 MINOR · low confidence — single reviewer (batch split)

GetResponseAsync drops tool call/result content: Only TextContent is aggregated from streaming updates. FunctionCallContent and FunctionResultContent are silently discarded. Callers inspecting response.Contents for tool-use metadata will see nothing.

Suggestion: Collect all AIContent types into the response message, not just TextContent.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Resolved — this file (CopilotSdkChatClient.cs) was deleted in the rewrite. None of these concerns apply to the current code.

{
// Always flush history, even on cancellation/error, to avoid orphan messages
if (updates.Count > 0)
_history.AddMessages(updates);
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.

🟢 MINOR · low confidence — single reviewer (batch split)

ClearMessages() race with streaming: If a user calls ClearMessages() while streaming is active, the finally block still flushes updates into _history. This means previously-cleared context can reappear in the next prompt's history.

Suggestion: Track a generation/epoch counter incremented by ClearMessages(). In the finally block, skip _history.AddMessages(updates) if the epoch changed during the stream.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Resolved — the old CopilotChatView.cs in src/CopilotChat/ was deleted. The new CopilotChatView (in src/AIControls/Microsoft.Maui.AI.Chat.Controls/) uses a completely different architecture with ChatSession handling streaming separately from the UI layer.

});

// Start inactivity timeout monitor
var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
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.

🟡 MODERATE · low confidence — single reviewer (batch split)

CancellationTokenSource leak: CreateLinkedTokenSource registers callbacks on the parent token. timeoutCts is only canceled (line 166) on the happy path but never disposed. If the iterator is abandoned early or an exception is thrown, the linked CTS callbacks remain registered — a per-message memory/handle leak that accumulates over session lifetime.

Suggestion: Wrap in try/finally ensuring timeoutCts.Dispose() is always called.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Resolved — this file (CopilotSdkChatClient.cs) was deleted in the rewrite. None of these concerns apply to the current code.

mattleibow and others added 15 commits May 13, 2026 20:40
Port AI session infrastructure from MauiDojo-agui to Microsoft.Maui.AI:
- IAgentSession/AgentSession: streaming chat with IChatClient + FunctionInvocation
- ChatMessageViewModel: merged AGUI + CopilotChat message models with MVVM Toolkit
- IAgentSessionFactory/AgentSessionFactory: factory pattern with IChatClient
- InvocationContext: tool call tracking with typed argument/result helpers
- Suggestion: suggestion chip record
- ServiceCollectionExtensions: DI registration
- AI.slnx solution filter

Key change from AGUI: replaced AIAgent with IChatClient + ChatClientBuilder.UseFunctionInvocation()

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy CopilotSdkChatClient, CopilotChatConfiguration, and
CopilotSdkServiceCollectionExtensions from
src/CopilotChat/Microsoft.Maui.CopilotChat.CopilotSdk/ with namespace
changed to Microsoft.Maui.AI.CopilotSdk. Project references
Microsoft.Maui.AI instead of CopilotChat. Updated AI.slnx to include
the new project alongside AI and AI.Controls.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create the controls package with AgentChatView backed by IAgentSession
(replacing CopilotChatView's IChatClient approach). Port themes,
converters, template selectors, SuggestionBar, and AgentLoadingIndicator
from CopilotChat and AGUI codebases.

Key changes from CopilotChatView:
- Session (IAgentSession) replaces ChatClient (IChatClient)
- Internal chat engine removed; IAgentSession handles history, streaming,
  tool execution, and human-in-the-loop
- Messages bound directly to Session.Messages
- IsBusy bound to Session.IsProcessing
- SuggestionPrompts uses IList<Suggestion> instead of IList<string>
- Removed approval bar (HITL handled by Session)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
New sample app at samples/AI.Sample/ demonstrating:
- PlaygroundPage: Customizable chat control settings with AgentChatView
- AgenticChatPage: Frontend tool (change_background) showing agent actions
- ToolRenderingPage: Weather/math/fact tools with inline results

Uses IAgentSession from Microsoft.Maui.AI, AgentChatView from
Microsoft.Maui.AI.Controls, and CopilotSdkChatClient backend.
Shell flyout with locked sidebar navigation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove src/AI/Microsoft.Maui.AI.CopilotSdk/ (now in separate branch)
- Remove src/CopilotChat/ (superseded by Microsoft.Maui.AI)
- Remove samples/CopilotChat.Sample/ (superseded by AI.Sample)
- Remove old CopilotChat test projects
- Remove GitHub.Copilot.SDK from Directory.Packages.props
- Update AI.Sample to use Azure OpenAI via user secrets
- Embed secrets.json at build time (same pattern as Garden sample)
- UserSecretsId: ai-attributes-secrets

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Merge Microsoft.Maui.AI + Microsoft.Maui.AI.Controls into single
  project at src/AIControls/Microsoft.Maui.AI.Controls/
- Folder-based separation: Services/, Controls/, Templates/,
  Converters/, Themes/, Extensions/
- Rename samples/AI.Sample → samples/AiControlsSample
- Remove old src/AI/Microsoft.Maui.AI and src/AI/Microsoft.Maui.AI.Controls
- Update MauiLabs.slnx references
- All builds pass: 0 warnings, 0 errors

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…settings

- Shell flyout: Locked → Flyout (hamburger), styled header with accent
- Playground: chat fills full width, settings in slide-up bottom panel
- Added proper nav styling (purple accent, emoji icons, themed colors)
- Removed fixed-width sidebar layout

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… clear

- Chat control fills available width (removed 840px cap from template)
- Message bubbles max at 960px for readability on ultra-wide
- Playground settings: sidebar (docked) on wide screens, overlay on narrow
- Clear chat moved to toolbar item
- Settings toggle via ⚙️ toolbar item
- All pages use same full-width chat pattern
- Shell flyout styling with branded header

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Sidebar uses 4*/1* grid columns (proportional instead of fixed 280px)
- On narrow screens, settings shows as action sheet popup
- Fixed Border StrokeShape (required for content rendering)
- Fixed tool chips: replaced FlexLayout with HorizontalStackLayout rows
- Added LineBreakMode=NoWrap so labels don't truncate
- Fixed obsolete DisplayActionSheet → DisplayActionSheetAsync

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Grid uses ColumnDefinitions='*,Auto' (stable, no dynamic resizing)
- Sidebar fixed at 300px via WidthRequest (max capped, content fits)
- Chat content centered via MaximumWidthRequest=960 + HorizontalOptions=Center
- Toggle shows/hides sidebar cleanly
- Removed flawed dynamic ColumnDefinition manipulation that caused 0-width bugs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- AgentChatView now decorates ChatMessageViewModels with avatar/show settings
- CollectionChanged handler applies avatar text/source/visibility to new messages
- RedecorateAllMessages() propagates changes to existing messages on property change
- Sidebar pinned open on wide screens (>=700px), auto-hidden on narrow
- Messages use HorizontalOptions=Fill (not Center) — centering is via MaxWidth
- Removed toggle/close buttons (sidebar is always-on for playground)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add Plan, Step, StepStatus, PlanConfirmationResult, JsonPatchOperation models
- Add test project with 31 tests (ContentTemplateSelector, InvocationContext,
  Models, AgentSession HITL)
- Add HumanInTheLoopPage and SharedStatePage demos
- Fix suggestion chips: replace Border+TapGestureRecognizer with Button
  (fixes tap not firing on Mac Catalyst)
- Add test project to AIControls.slnx
- Support both Button and custom view children in suggestion layout wiring

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Phase A (Critical Bug Fixes):
- Fix ToChatMessage: preserve FunctionCallContent/FunctionResultContent across turns
- Fix reasoning-only messages: include messages with ReasoningText even if Text empty
- Fix HITL double-waiter: each WaitForResponse caller gets own TCS
- Add Cancel() + IDisposable + Failed event on AgentSession
- Fix InvocationContext.GetArgument: handle JsonElement from M.E.AI tools

Phase B (Architecture):
- Add SidebarTemplate/HeaderTemplate/FooterTemplate + SidebarPlacement to AgentChatView
- Add StateContext property for sidebar BindingContext
- Add StopButtonText: Send button becomes Stop during streaming
- Ship JsonPatch utility (RFC 6902 add/replace/remove)
- Ship StateChannel<T> (auto-deserialize state snapshots/deltas)
- Add XmlnsDefinition for xmlns:ai="http://schemas.microsoft.com/dotnet/maui/ai"
- Split AgentChatView.cs into partial files (Properties + Logic)

Phase D (Tests):
- ChatMessageViewModelTests: 9 tests (round-trip all content types)
- JsonPatchTests: 9 tests (add/replace/remove/nested/bytes)
- InvocationContextTests: +4 JsonElement tests
- AgentSessionCancelTests: 7 tests (cancel/dispose/failed)
- FakeChatClient: configurable test double for streaming
- Total: 59 tests passing

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…resources

- Add AgenticGenerativeUIPage: auto-executing plan with real-time progress
- Add PredictiveStatePage: split-pane document editor with accept/reject
- Add HaikuPage: haiku carousel with colored backgrounds and navigation
- Extract PlanCardView reusable control from HITL page (used by HITL + GenUI)
- Add plan card theme resource keys (CopilotPlan*, CopilotConfirm, CopilotReject)
- Create CI workflow (.github/workflows/ci-aicontrols.yml)
- Register all 8 demo pages in AppShell + MauiProgram
- All 59 tests pass, 0 warnings, 0 errors

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mattleibow and others added 11 commits May 16, 2026 20:38
Critical bug fix: ToChatMessage() now strips FunctionCallContent and
FunctionResultContent from conversation history. These are handled
internally by FunctionInvokingChatClient within a single streaming
session and must not be resent — doing so caused HTTP 400 errors
("assistant message with tool_calls must be followed by tool messages")
on subsequent turns.

Also preserves DataContent (state snapshots) and correctly falls back
to Text property when Contents is empty.

Demo improvements:
- Restructured all 7 demo pages into Demos/ subfolders with READMEs
- Improved HITL system prompt so model calls create_plan AND
  confirm_plan in the same turn (enables blocking confirmation flow)
- All 7 demos verified working via DevFlow:
  * Agentic Chat: 3-turn tool calling ✅
  * Tool Rendering: weather + multi-turn ✅
  * Human in the Loop: plan + confirm + execute ✅
  * Shared State: recipe update tool ✅
  * Agentic GenUI: auto-plan execution ✅
  * Predictive State: document streaming ✅
  * Haiku Generator: JP/EN haiku with theme ✅

Tests: 60 passing, updated to verify tool content stripping behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port the headless chat session engine from MauiAIAnnotations:
- ChatSession: wraps IChatClient with streaming, tool approval, history management
- IChatSession: headless contract with Changed events, CancelAsync, approval workflow
- ChatEntry: immutable record with Timestamp, ToolName, ApprovalState
- ContentRole/ToolApprovalState/ChatSessionChangeKind enums
- ServiceCollectionExtensions.AddChatSession() DI helper

Tests cover: send/stream, streaming accumulation, error handling, system prompt,
timestamps, clear/cancel, approval full cycle, edited approvals, identity validation,
auto-reject stale approvals, independent sessions, approve-and-follow-up with
FunctionInvokingChatClient middleware.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaced the old Controls/Converters/Extensions/Models/Services/Templates
scaffold with the full chat UI library ported from
Microsoft.Extensions.AI.Maui with updated namespaces:
- Microsoft.Extensions.AI.Chat → Microsoft.Maui.AI.Chat
- Microsoft.Extensions.AI.Maui → Microsoft.Maui.AI.Controls
- Microsoft.Extensions.AI.Maui.Chat → Microsoft.Maui.AI.Controls.Chat
- Microsoft.Extensions.AI.Maui.Themes → Microsoft.Maui.AI.Controls.Themes

Includes:
- Chat/ — ContentContext, ContentTemplate, ContentTemplateSelector,
  and built-in content views (text, function call/result, error,
  default, tool approval)
- Controls/ — ChatPanelControl (XAML + code-behind)
- Themes/ — ChatTheme resource dictionary and ChatThemeKeys constants

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Delete 8 old test files (AgentSession*, ChatMessageViewModel*, ContentTemplateSelector*, etc.)
- Create ContentTemplateTests.cs with 21 tests covering:
  - TextContentTemplate: matching, role filtering, priority
  - FunctionCallTemplate: matching, tool name filtering, priority
  - FunctionResultTemplate: matching, tool name filtering
  - ToolApprovalTemplate: matching, tool name filtering
  - ErrorContentTemplate: matching
  - DefaultContentTemplate: match-everything, lowest priority
  - ContentContext: property exposure, approval resolution text
  - Priority ordering: tool-specific > generic > default
- Add InternalsVisibleTo for test access to GetPriority()
- Update AIControls.slnx with all 5 projects
- All 42 tests pass (21 chat + 21 controls)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace all AgentChatView + IAgentSession usage with the new
ChatPanelControl + ChatSession system across all 8 demo pages:

- AgenticChatPage: custom change_background tool with ChatSession
- ToolRenderingPage: default tools from DI + custom WeatherResultView
- HumanInTheLoopPage: plan sidebar with confirm/reject via chat messages
- SharedStatePage: recipe editor with update_recipe tool
- PredictiveStatePage: document writer with accept/reject buttons
- AgenticGenerativeUIPage: auto-executing plan with progress footer
- HaikuPage: haiku display with create_haiku tool
- PlaygroundPage: settings sidebar with system prompt and quick prompts

New files added for the ToolRendering demo:
- WeatherResultTemplate.cs (extends FunctionResultTemplate)
- WeatherResultView.xaml/.cs (ContentContextView for weather cards)

Also includes the MauiProgram.cs and csproj updates that register
ChatSession, IEnumerable<AITool>, and IChatClient with middleware.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- 36 new tests for bindable property defaults, roundtrips, and theme keys
- Playground settings: timestamps toggle, tool visibility, corner radius slider,
  max width slider, placeholder editor, welcome message editor
- All 78 tests pass (21 chat + 57 controls), 0 warnings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nd restructure

- Rename library: Microsoft.Maui.AI.Controls → Microsoft.Maui.AI.Chat.Controls
- Move Chat/ and Controls/ files to project root (flatten hierarchy)
- Keep Contents/ and Themes/ as subdirectories
- Move test projects to root tests/ folder:
  - tests/Microsoft.Maui.AI.Chat.Tests/
  - tests/Microsoft.Maui.AI.Chat.Controls.Tests/
- Update all namespace references, csproj paths, XAML assembly names
- Update solution files (AIControls.slnx, MauiLabs.slnx)
- All 78 tests pass, sample app builds with 0 warnings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Convert from ContentView to TemplatedView with replaceable PART_* parts:
  PART_Header, PART_Messages, PART_WelcomePanel, PART_WelcomeIcon,
  PART_WelcomeMessage, PART_BusyIndicator, PART_Suggestions, PART_Footer,
  PART_InputEntry, PART_SendButton, PART_InputArea
- Add default ControlTemplate in ChatTheme.xaml with implicit Style
- Add AppBuilderExtensions.UseChatControls() for auto-loading theme
- Add ChatThemeLoader helper for reliable resource merging
- Fix invalid StrokeThickness values in demo XAML (double, not Thickness)
- Update all sample pages, tests, and docs to use CopilotChatView name

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two fixes for the NullReferenceException during PlaygroundPage.InitializeComponent():

1. Move ChatThemeLoader.EnsureLoaded() from CopilotChatView constructor to
   OnParentSet() — mutating Application.Resources.MergedDictionaries during
   XAML parsing triggers resource re-evaluation on partially-constructed pages.

2. Initialize SuggestionPrompts with defaultValueCreator returning new List<string>()
   instead of null — the XAML source generator calls .Add() on the collection
   when processing <x:String> child elements, causing NullRef at line 620.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix ToolName mismatch: WeatherResultTemplate was looking for
  'get_current_weather' but AIFunctionFactory.Create(GetCurrentWeather)
  registers as 'GetCurrentWeather' (PascalCase method name)
- Add TESTING.md with user scenarios to all 8 demo folders
- All demos verified working via DevFlow walkthroughs:
  Playground, Agentic Chat, Tool Rendering (weather card renders),
  Human in the Loop, Shared State, Predictive State,
  Agentic Generative UI, Haiku Generator

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattleibow mattleibow changed the title Add Microsoft.Maui.CopilotChat — drop-in templated chat control Add Microsoft.Maui.AI.Chat — drop-in CopilotChatView control for MAUI May 17, 2026
mattleibow and others added 11 commits May 18, 2026 01:14
- Suppress XC0022 (compiled bindings warning) in the controls library —
  TemplateBinding in ControlTemplates triggers false positives with
  TreatWarningsAsErrors=true
- Move ValidateXcodeVersion=false from repo-wide Directory.Build.props
  to only the sample app csproj (reviewer feedback)
- Fix sample Android SupportedOSPlatformVersion: 21 → 24 to match repo default

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy headless engine (70 files) and Blazor components (18 files) from
dotnet/aspnetcore branch javiercn/ai-components-e2e-tests as the
shared foundation for AI chat convergence.

Core (net10.0, zero UI deps): UIAgent, AgentContext, Pipeline,
ContentBlock hierarchy, RichText AST (28 nodes), Attributes.

Blazor: ChatPage, MessageInput, BlockRenderer, AgentBoundary,
SuggestionList — for Blazor Hybrid support.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace all references to the deleted Microsoft.Maui.AI.Chat library
(IChatSession, ChatSession, ChatEntry, ContentRole, ToolApprovalState,
ChatSessionChangedEventArgs, ChatSessionChangeKind) with the new
Microsoft.AspNetCore.Components.AI.Core types (AgentContext,
ConversationTurn, ContentBlock, RichContentBlock,
FunctionInvocationContentBlock, FunctionApprovalBlock, etc.).

Key changes:
- csproj: ProjectReference now points to AI.Core
- ContentContext: wraps ContentBlock instead of ChatEntry
- CopilotChatView: Session is AgentContext; uses RegisterOnTurnAdded,
  RegisterOnStatusChanged, RegisterOnBlockAdded callbacks instead of
  the old Changed event; SendMessageAsync replaces SendAsync
- TextContentTemplate: matches RichContentBlock; Role is now a string
- FunctionCallTemplate/FunctionResultTemplate: match
  FunctionInvocationContentBlock (Result null vs non-null)
- ToolApprovalTemplate/View: match FunctionApprovalBlock; use
  Approve()/Reject() directly instead of IToolApprovalResponseFactory
- ErrorContentTemplate: returns false (errors via AgentContext.Status)
- Removed IToolApprovalResponseFactory.cs (no longer needed)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace deleted Microsoft.Maui.AI.Chat (ChatSession) with the Core
engine's UIAgent and AgentContext from Microsoft.AspNetCore.Components.AI.

Changes:
- Remove Microsoft.Maui.AI.Chat project reference from csproj
- Remove AddChatSession/SampleTools DI registrations from MauiProgram.cs
- Update all 7 demo pages + PlaygroundPage to construct UIAgent with
  ChatOptions (Instructions for system prompt, Tools for AI tools) and
  wrap in AgentContext
- Update XAML bindings from Path=ChatSession to Path=Session
- Fix WeatherResultView to use ContentContext.Block pattern instead of
  the removed ContentContext.Content property

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create AiControlsBlazorSample that demonstrates the AI Chat Controls
using Blazor components in a BlazorWebView. The sample uses ChatPage
from Microsoft.AspNetCore.Components.AI.Blazor with a UIAgent backed
by Azure OpenAI (IChatClient) and includes a weather tool demo.

Key implementation details:
- Uses Microsoft.AspNetCore.Components.WebView.Maui for Blazor Hybrid
- Removes transitive FrameworkReference Microsoft.AspNetCore.App via
  MSBuild target since there is no runtime pack for mobile platforms
- Falls back to a NoOpChatClient when AI secrets are not configured
- Added to AIControls.slnx solution

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bug fixes (from Opus 4.7 review):
- Fix memory leak: track and dispose ContentBlockChangedSubscription per block
- Fix tool results never appearing when ShowToolCalls=false: re-check
  visibility in OnBlockChanged and add blocks that become visible later
- Fix IsBusy state machine: disable input during AwaitingInput status too
- Fix PlaygroundPage AgentContext leak on Clear/Apply: dispose old session
- Fix PlaygroundPage OnQuickPromptClicked: guard against non-Idle state
- Fix ChatThemeLoader static flag: remove to support hot reload/tests
- Add propertyChanged hooks for HeaderTemplate, FooterTemplate,
  WelcomeIcon, and SuggestionPrompts bindable properties

Other improvements:
- Add upstream wwwroot/ai-chat.css to Blazor project (was missing)
- Fix Blazor sample Android build: add MauiIcon, remove manual manifest icon
- Update UPSTREAM-CHANGES.md with porting notes and thread-safety request
- Fix stale ChatSession references in 4 demo README files

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The suggestion chips (PART_Suggestions FlexLayout) were never becoming
visible because OnApplyTemplate ran before the XAML parser finished
adding items to the ObservableCollection<string> SuggestionPrompts.

The fix uses the Loaded event which fires after the full visual tree
is constructed and rendered, guaranteeing template parts are resolved
and all XAML-set collection items have been added.

Also replaced HandlerChanged approach with simpler Loaded handler.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bring over the Roslyn incremental source generator that processes
[ToolBlock], [ToolParameter], and [ToolResult] attributes to generate
strongly-typed ContentBlockHandler<T> implementations.

The source generator:
- Matches FunctionCallContent by tool name (not the generic catch-all)
- Deserializes arguments into typed properties
- Matches FunctionResultContent by CallId and deserializes results
- Emits an aggregate AddGeneratedToolBlocks() extension method

Changes:
- New project: Microsoft.AspNetCore.Components.AI.SourceGenerators
  (netstandard2.0, copied from upstream gen/ directory)
- ContentBlock.Id setter made public (required for generated handlers
  in consumer assemblies) — noted as upstream change
- Sample app: Added WeatherToolBlock with [ToolBlock]/[ToolParameter]/
  [ToolResult] attributes demonstrating the pattern
- Updated WeatherResultView to use strongly-typed block with JSON fallback
- ToolRenderingPage uses AddGeneratedToolBlocks() for handler registration
- Added project to MauiLabs.slnx

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When FunctionInvokingChatClient handles tool calls internally (via
UseFunctionInvocation() in the DI pipeline), the pipeline streams back
FunctionCallContent followed by FunctionResultContent. The AgentContext
adds blocks to uninvokedToolBlocks at yield time (when Result is null),
but by the time streaming ends, the pipeline has already set the Result
on those blocks.

Without filtering, AgentContext would re-invoke the tools and send a
duplicate tool result message to the LLM, causing the second turn to
either error or produce no response.

Fix: After the streaming loop, remove blocks whose Result was already
set by the pipeline (meaning the tool was handled by the chat client
middleware).

Also add debug logging when AgentContext enters error state.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copies all 80+ test files from dotnet/aspnetcore branch
javiercn/ai-components-e2e-tests (src/Components/AI/test/) including:

- Engine/ (16 tests): AgentContext, UIAgent, threads, approvals, errors
- Pipeline/ (13 tests): BlockMapping, handlers, tool blocks, state mapper
- Blocks/ (6 tests): ContentBlock, FunctionApproval/Invocation, Reasoning, RichText
- Samples/ (10 tests): S01-S10 end-to-end scenarios with recordings
- Components/ (14 tests): Blazor component tests (AgentBoundary, BlockRenderer, etc.)
- TestFramework/ (10 files): Blazor test renderer infrastructure
- TestHelpers/ (8 files): RecordingChatClient, ResponseEmitters, etc.
- Baselines/ (10 recordings): JSON replay files for recorded sessions

All 332 upstream tests pass with only two adaptations needed:
1. Added InternalsVisibleTo for the Blazor project
2. Suppressed BL0006 (RenderTree usage warning) in test project

Existing 60 MAUI control tests continue to pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reorganize the combined test project into layered projects:
- Microsoft.AspNetCore.Components.AI.Core.Tests (247 tests):
  Engine, Pipeline, Blocks, Samples, and recorded tests
- Microsoft.AspNetCore.Components.AI.Blazor.Tests (96 tests):
  Component rendering, TestFramework, baseline replay tests

Also:
- Update InternalsVisibleTo in Core and Blazor csprojs
- Add Azure.Identity package for recorded tests
- Add Microsoft.Extensions.AI.OpenAI for AsIChatClient
- Update CI workflow paths to include tests/ directories
- Move NullAntiforgeryStateProvider to Blazor.Tests only
- Copy baselines to both projects for recording replay

All 403 tests pass (247 Core + 96 Blazor + 60 MAUI).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattleibow mattleibow changed the title Add Microsoft.Maui.AI.Chat — drop-in CopilotChatView control for MAUI MAUI AI Chat Control — ASP.NET AI Components convergence May 18, 2026
- Delete trivial CopilotChatViewPropertyTests (assert default == default)
- Add MessageListTests: multi-turn, block accumulation, streaming indicator
- Add BlockRendererTests: template matching, priority, tool-specific overrides
- Add ContentContextTests: role, toolName, approval state, lifecycle
- Add ChatPageTests: session binding, error state, send guards, cancellation
- Add SuggestionListTests: suggestion prompts, welcome state
- Add TestChatClient: controllable IChatClient with streaming support
- Add SessionFactory/BlockFactory: test helpers matching Blazor patterns
- Replace Blazor.Tests baseline duplication with MSBuild file linking

Total: 418 tests (247 Core + 96 Blazor + 75 MAUI), all passing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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