Skip to content

Create MAUI-specific AG-UI helpers library #32560

@mattleibow

Description

@mattleibow

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions