Chat Client Provider
The IChatClientProvider interface is the bridge between the AI conversation service and your AI backend.
Default Behavior
Section titled “Default Behavior”By default, a built-in InjectedChatClientProvider is registered that resolves IChatClient from your DI container. For most apps, simply register an IChatClient and you’re done:
builder.Services.AddChatClient(new OpenAIClient("your-api-key").GetChatClient("gpt-4o").AsIChatClient());
builder.Services.AddShinyAiConversation(opts =>{ // No need to call SetChatClientProvider — the default resolves IChatClient from DI});If no IChatClient is registered and no custom provider is set, an InvalidOperationException is thrown at runtime.
Structured output support
Section titled “Structured output support”Providers also declare how their endpoint can be asked for a structured turn.
StructuredOutputMode is a default interface member, so you only override it when the default is wrong
for your endpoint:
public class MyChatClientProvider : IChatClientProvider{ public Task<IChatClient> GetChatClient(CancellationToken cancelToken = default) => ...;
public AiStructuredOutputMode StructuredOutputMode => AiStructuredOutputMode.Json;}| Mode | Request | Use for |
|---|---|---|
JsonSchema (default) |
Native schema-constrained response format | OpenAI and compatible endpoints |
Json |
JSON response format, shape described in the prompt | GitHub Copilot, models without schema support |
Prompt |
Shape in the prompt only, no format constraint | Endpoints that reject both of the above |
None |
Plain text — opts out entirely | Providers that can’t do any of it |
Both GitHub Copilot providers default to Json because the Copilot proxy passes json_schema support
through per-model. A wrong value degrades rather than breaks — the service retries unstructured and falls
back to plain text.
Built-in Providers
Section titled “Built-in Providers”Two ready-made providers are available as separate NuGet packages so you don’t have to implement IChatClientProvider yourself.
Shiny.AiConversation.OpenAi
Section titled “Shiny.AiConversation.OpenAi”A static provider for any OpenAI-compatible endpoint (OpenAI, Azure OpenAI, Ollama, etc.). Creates the client once at startup with logging and function invocation middleware.
dotnet add package Shiny.AiConversation.OpenAibuilder.Services.AddShinyAiConversation(opts =>{ opts.AddStaticOpenAIChatClient( apiToken: "your-api-key", endpointUri: "https://api.openai.com/v1", modelName: "gpt-4o" );});Works great for Blazor WASM, server apps, or any MAUI app where you have an API key up front.
Shiny.AiConversation.Maui.GithubCopilot
Section titled “Shiny.AiConversation.Maui.GithubCopilot”A MAUI-specific provider that authenticates via the GitHub OAuth device code flow and uses the Copilot API for chat completions. The entire authentication experience is self-contained:
- When authentication is needed, a popup appears with the device code
- The code is automatically copied to the clipboard
- The user presses OK and a browser opens to GitHub’s verification page
- The provider polls in the background until the user authorizes
- The OAuth token is exchanged for a Copilot API token and cached
- Tokens are persisted in
SecureStorageacross app restarts
dotnet add package Shiny.AiConversation.Maui.GithubCopilotbuilder.Services.AddShinyAiConversation(opts =>{ opts.AddGithubCopilotChatClient();});No login page, token storage, or event wiring needed — one line of registration handles everything.
Additional API on GitHubCopilotChatClientProvider:
| Member | Description |
|---|---|
IsAuthenticated |
Whether a stored token exists |
StartAuthentication(ct) |
Manually trigger the device code flow |
CancelAuthentication() |
Cancel an in-progress auth flow |
SignOut() |
Clear stored tokens |
AccessTokenChanged |
Event fired when auth state changes (token or null) |
Custom Implementation
Section titled “Custom Implementation”For scenarios not covered by the built-in providers, implement IChatClientProvider:
Interface
Section titled “Interface”public interface IChatClientProvider{ Task<IChatClient> GetChatClient(CancellationToken cancelToken = default);}The service calls GetChatClient() before each chat request, so your implementation can handle token refresh, re-authentication, or client rotation.
Registration
Section titled “Registration”builder.Services.AddShinyAiConversation(opts =>{ opts.SetChatClientProvider<MyChatClientProvider>();});Example: Ollama (Local)
Section titled “Example: Ollama (Local)”using Microsoft.Extensions.AI;using Shiny.AiConversation;
public class OllamaChatClientProvider : IChatClientProvider{ public Task<IChatClient> GetChatClient(CancellationToken cancelToken = default) { var client = new OllamaChatClient( new Uri("http://localhost:11434"), "llama3.1" ); return Task.FromResult<IChatClient>(client); }}Authentication Patterns
Section titled “Authentication Patterns”| Pattern | Provider |
|---|---|
| API key from config | AddStaticOpenAIChatClient() or register IChatClient in DI |
| GitHub Copilot (device code flow) | AddGithubCopilotChatClient() |
| Managed identity / custom | Implement IChatClientProvider |
| Token refresh with caching | Implement IChatClientProvider |


