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.
Custom Implementation
Section titled “Custom Implementation”For advanced scenarios (on-demand authentication, token refresh, re-authentication on 401), 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: OpenAI
Section titled “Example: OpenAI”using Microsoft.Extensions.AI;using OpenAI;using Shiny.AiConversation;
public class OpenAiChatClientProvider(IConfiguration config) : IChatClientProvider{ public Task<IChatClient> GetChatClient(CancellationToken cancelToken = default) { var client = new OpenAIClient(config["OpenAI:ApiKey"]); return Task.FromResult( client.GetChatClient("gpt-4o").AsIChatClient() ); }}Example: GitHub Copilot (Device Code Flow)
Section titled “Example: GitHub Copilot (Device Code Flow)”The sample apps include a full GitHub Copilot implementation that demonstrates:
- OAuth 2.0 device code flow for authentication
- Copilot API token exchange and caching
- Custom HTTP pipeline policy for required headers
- Automatic re-authentication on 401 responses
public class GitHubCopilotChatClientProvider : IChatClientProvider{ public async Task<IChatClient> GetChatClient(CancellationToken cancelToken = default) { var copilotToken = await GetCopilotToken(cancelToken);
var options = new OpenAIClientOptions { Endpoint = new Uri("https://api.githubcopilot.com") }; // Copilot API requires editor identification headers options.AddPolicy(new CopilotHeadersPolicy(), PipelinePosition.PerCall);
var client = new OpenAIClient( new ApiKeyCredential(copilotToken), options );
return client.GetChatClient("gpt-4o").AsIChatClient(); }}See the full implementation in the MAUI sample or Blazor sample.
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”Your provider can handle authentication however you need:
| Pattern | When to use |
|---|---|
| API key from config | Simple server-side apps |
| OAuth device code flow | Mobile apps needing user login |
| Managed identity | Azure-hosted services |
| Token refresh with caching | Any provider with expiring tokens |
The service calls GetChatClient() on every request, so you can cache the client and refresh tokens transparently.