Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 101 additions & 6 deletions src/Dapr.AI.Microsoft.Extensions/DaprChatClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,105 @@ namespace Dapr.AI.Microsoft.Extensions;
[Experimental("DAPR_CONVERSATION", UrlFormat = "https://docs.dapr.io/developing-applications/building-blocks/conversation/conversation-overview/")]
public static class DaprChatClientExtensions
{
/// <summary>
/// Registers a keyed <see cref="DaprChatClient"/> as a service that implements <see cref="IChatClient"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="serviceKey">The key used to resolve the chat client.</param>
/// <param name="conversationComponentName">The name of the Dapr Conversation component.</param>
/// <param name="serviceLifetime">The <see cref="ServiceLifetime"/> of the service. Defaults to <see cref="ServiceLifetime.Singleton"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddDaprChatClient(
this IServiceCollection services,
string serviceKey,
string conversationComponentName,
ServiceLifetime serviceLifetime = ServiceLifetime.Scoped)
{
ArgumentNullException.ThrowIfNull(services, nameof(services));
ArgumentException.ThrowIfNullOrWhiteSpace(serviceKey, nameof(serviceKey));
ArgumentException.ThrowIfNullOrWhiteSpace(conversationComponentName, nameof(conversationComponentName));

return AddKeyedDaprChatClient(services, serviceKey, options =>
{
options.ConversationComponentName = conversationComponentName;
}, serviceLifetime);
}

/// <summary>
/// Registers a keyed <see cref="DaprChatClient"/> as a service that implements <see cref="IChatClient"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="serviceKey">The key used to resolve the chat client.</param>
/// <param name="conversationComponentName">The name of the Dapr Conversation component.</param>
/// <param name="configure">An optional <see cref="Action{T}"/> to configure the <see cref="DaprChatClientOptions"/>.</param>
/// <param name="serviceLifetime">The <see cref="ServiceLifetime"/> of the service. Defaults to <see cref="ServiceLifetime.Singleton"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddDaprChatClient(
this IServiceCollection services,
string serviceKey,
string conversationComponentName,
Action<DaprChatClientOptions>? configure,
ServiceLifetime serviceLifetime = ServiceLifetime.Scoped)
{
ArgumentNullException.ThrowIfNull(services, nameof(services));
ArgumentException.ThrowIfNullOrWhiteSpace(serviceKey, nameof(serviceKey));
ArgumentException.ThrowIfNullOrWhiteSpace(conversationComponentName, nameof(conversationComponentName));

return AddKeyedDaprChatClient(services, serviceKey, options =>
{
options.ConversationComponentName = conversationComponentName;
configure?.Invoke(options);
}, serviceLifetime);
}

private static IServiceCollection AddKeyedDaprChatClient(
IServiceCollection services,
string serviceKey,
Action<DaprChatClientOptions> configure,
ServiceLifetime serviceLifetime)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentException.ThrowIfNullOrWhiteSpace(serviceKey);
ArgumentNullException.ThrowIfNull(configure);

services.AddOptions<DaprChatClientOptions>(serviceKey).Configure(configure);

static IChatClient Factory(IServiceProvider serviceProvider, string key)
{
var daprConversationClient = serviceProvider.GetRequiredService<DaprConversationClient>();
var optionsMonitor = serviceProvider.GetRequiredService<IOptionsMonitor<DaprChatClientOptions>>();
var options = optionsMonitor.Get(key);
return new DaprChatClient(daprConversationClient, serviceProvider, Options.Create(options));
}

switch (serviceLifetime)
{
case ServiceLifetime.Singleton:
services.AddKeyedSingleton<IChatClient>(serviceKey, (sp, _) => Factory(sp, serviceKey));
break;
case ServiceLifetime.Scoped:
services.AddKeyedScoped<IChatClient>(serviceKey, (sp, _) => Factory(sp, serviceKey));
break;
case ServiceLifetime.Transient:
services.AddKeyedTransient<IChatClient>(serviceKey, (sp, _) => Factory(sp, serviceKey));
break;
default:
throw new ArgumentOutOfRangeException(nameof(serviceLifetime), serviceLifetime, "Unsupported service lifetime.");
}

return services;
}

/// <summary>
/// Registers <see cref="DaprChatClient"/> as a service that implements <see cref="IChatClient"/>.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
/// <param name="conversationComponentName">The name of the Dapr Conversation component.</param>
/// <param name="serviceLifetime">The <see cref="ServiceLifetime"/> of the service. Defaults to <see cref="ServiceLifetime.Singleton"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
/// <remarks>
/// This overload also registers a keyed <see cref="IChatClient"/> using the conversation component name as the key.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown at runtime if <see cref="DaprConversationClient"/> is not registered in the DI container.
/// </exception>
Expand All @@ -40,10 +132,7 @@ public static IServiceCollection AddDaprChatClient(this IServiceCollection servi
ArgumentNullException.ThrowIfNull(services, nameof(services));
ArgumentException.ThrowIfNullOrWhiteSpace(conversationComponentName, nameof(conversationComponentName));

return services.AddDaprChatClient(options =>
{
options.ConversationComponentName = conversationComponentName;
}, serviceLifetime);
return services.AddDaprChatClient(conversationComponentName, configure: null, serviceLifetime);
}

/// <summary>
Expand All @@ -54,6 +143,9 @@ public static IServiceCollection AddDaprChatClient(this IServiceCollection servi
/// <param name="configure">An optional <see cref="Action{T}"/> to configure the <see cref="DaprChatClientOptions"/>.</param>
/// <param name="serviceLifetime">The <see cref="ServiceLifetime"/> of the service. Defaults to <see cref="ServiceLifetime.Singleton"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
/// <remarks>
/// This overload also registers a keyed <see cref="IChatClient"/> using the conversation component name as the key.
/// </remarks>
/// <exception cref="InvalidOperationException">
/// Thrown at runtime if <see cref="DaprConversationClient"/> is not registered in the DI container.
/// </exception>
Expand All @@ -63,11 +155,14 @@ public static IServiceCollection AddDaprChatClient(this IServiceCollection servi
ArgumentNullException.ThrowIfNull(services);
ArgumentException.ThrowIfNullOrWhiteSpace(conversationComponentName);

return services.AddDaprChatClient(options =>
Action<DaprChatClientOptions> configureOptions = options =>
{
options.ConversationComponentName = conversationComponentName;
configure?.Invoke(options);
}, serviceLifetime);
};

services.AddDaprChatClient(configureOptions, serviceLifetime);
return AddKeyedDaprChatClient(services, conversationComponentName, configureOptions, serviceLifetime);
}

/// <summary>
Expand Down
Loading