-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Open
Labels
Description
Create MAUI-specific AG-UI helpers library
Parent: #??? AG-UI Integration for .NET MAUI
Description
Create a MAUI-specific helper library that wraps AG-UI client functionality with mobile/desktop-friendly features like main thread dispatching, connectivity awareness, and observable collections for data binding.
Goals
- Make AG-UI feel native to MAUI apps
- Handle UI thread marshalling automatically
- Integrate with MAUI Essentials (connectivity, lifecycle)
- Provide data binding helpers
- Add mobile-friendly retry patterns
Why This Library?
Raw AGUIChatClient works but doesn't account for MAUI-specific needs:
- No automatic main thread dispatching for UI updates
- No connectivity awareness
- No app lifecycle handling (suspend/resume)
- No observable collection helpers for XAML binding
- No mobile-optimized retry logic
Proposed Features
1. Main Thread Dispatching
Automatically marshal streaming updates to the UI thread.
public class MauiAGUIChatClient : AGUIChatClient
{
public MauiAGUIChatClient(HttpClient httpClient, string serverUrl)
: base(httpClient, serverUrl) { }
// Automatic main thread dispatching
public async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingOnMainThreadAsync(
IEnumerable<ChatMessage> messages,
AgentThread thread)
{
await foreach (var update in base.RunStreamingAsync(messages, thread))
{
// Yield on main thread for UI binding
AgentRunResponseUpdate mainThreadUpdate = update;
await MainThread.InvokeOnMainThreadAsync(() => { });
yield return mainThreadUpdate;
}
}
}2. Connectivity Integration
Check network status before sending requests.
public class ConnectivityAwareAGUIClient : MauiAGUIChatClient
{
private readonly IConnectivity _connectivity;
public ConnectivityAwareAGUIClient(
HttpClient httpClient,
string serverUrl,
IConnectivity connectivity)
: base(httpClient, serverUrl)
{
_connectivity = connectivity;
}
public override async Task<ChatCompletion> CompleteAsync(...)
{
if (_connectivity.NetworkAccess != NetworkAccess.Internet)
throw new NetworkException("No internet connection available");
return await base.CompleteAsync(...);
}
public bool IsConnected =>
_connectivity.NetworkAccess == NetworkAccess.Internet;
}3. Observable Chat History
XAML-bindable chat history with streaming support.
public class ObservableChatHistory : ObservableCollection<ChatMessage>
{
public async Task AddUserMessageAsync(string text)
{
var message = new ChatMessage(ChatRole.User, text);
await MainThread.InvokeOnMainThreadAsync(() => Add(message));
}
public async Task AddStreamingAssistantResponseAsync(
IAsyncEnumerable<AgentRunResponseUpdate> updates)
{
// Create placeholder message
var message = new ChatMessage(ChatRole.Assistant, "");
await MainThread.InvokeOnMainThreadAsync(() => Add(message));
// Stream updates into the message
await foreach (var update in updates)
{
foreach (var content in update.Contents)
{
if (content is TextContent textContent)
{
await MainThread.InvokeOnMainThreadAsync(() =>
message.Text += textContent.Text);
}
}
}
}
}4. App Lifecycle Handling
Handle app suspend/resume gracefully.
public class LifecycleAwareAGUIClient : MauiAGUIChatClient
{
private CancellationTokenSource _cts;
public LifecycleAwareAGUIClient(...)
{
// Listen to app lifecycle
Application.Current.RequestedThemeChanged += OnAppStateChanged;
}
private void OnAppStateChanged(object sender, AppThemeChangedEventArgs e)
{
// Cancel ongoing requests when app suspends
_cts?.Cancel();
}
public override async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(...)
{
_cts = new CancellationTokenSource();
await foreach (var update in base.RunStreamingAsync(...))
{
if (_cts.Token.IsCancellationRequested)
yield break;
yield return update;
}
}
}5. Retry Policy
Mobile-friendly retry with exponential backoff.
public class RetryableAGUIClient : MauiAGUIChatClient
{
private readonly int _maxRetries = 3;
private readonly TimeSpan _baseDelay = TimeSpan.FromSeconds(1);
public override async Task<ChatCompletion> CompleteAsync(...)
{
for (int retry = 0; retry < _maxRetries; retry++)
{
try
{
return await base.CompleteAsync(...);
}
catch (HttpRequestException) when (retry < _maxRetries - 1)
{
var delay = _baseDelay * Math.Pow(2, retry);
await Task.Delay(delay);
}
}
throw new InvalidOperationException("Max retries exceeded");
}
}6. DI Registration Helpers
Easy setup with dependency injection.
public static class MauiAGUIExtensions
{
public static IServiceCollection AddMauiAGUI(
this IServiceCollection services,
string serverUrl)
{
services.AddSingleton<IConnectivity>(Connectivity.Current);
services.AddSingleton<AGUIChatClient>(sp =>
{
var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(60) };
var connectivity = sp.GetRequiredService<IConnectivity>();
return new ConnectivityAwareAGUIClient(
httpClient,
serverUrl,
connectivity);
});
services.AddTransient<ObservableChatHistory>();
return services;
}
}
// Usage in MauiProgram.cs
builder.Services.AddMauiAGUI("http://localhost:8888");Notes
- Keep wrapper thin - just MAUI-specific concerns
- Don't hide base AG-UI functionality
- Focus on mobile/desktop developer experience
- All helpers should be optional (can still use raw
AGUIChatClient) - Consider making helpers composable (decorator pattern)