// Copyright (c) Microsoft. All rights reserved. using Microsoft.Agents.AI.DurableTask; using Microsoft.Agents.AI.DurableTask.Workflows; using Microsoft.Azure.Functions.Worker.Builder; using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; namespace Microsoft.Agents.AI.Hosting.AzureFunctions; /// /// Extension methods for the class. /// public static class FunctionsApplicationBuilderExtensions { /// /// Configures the application to use durable agents with a builder pattern. /// /// The functions application builder. /// A delegate to configure the durable agents. /// The functions application builder. public static FunctionsApplicationBuilder ConfigureDurableAgents( this FunctionsApplicationBuilder builder, Action configure) { ArgumentNullException.ThrowIfNull(configure); // Create/get shared options BEFORE the DurableTask library call so it can find them. FunctionsDurableOptions sharedOptions = GetOrCreateSharedOptions(builder.Services); // The main agent services registration is done in Microsoft.DurableTask.Agents. builder.Services.ConfigureDurableAgents(configure); // Ensure all agents registered through this path have default FunctionsAgentOptions. // This distinguishes them from agents auto-registered by workflows. DurableAgentsOptionsExtensions.EnsureDefaultOptionsForAll(sharedOptions.Agents.GetAgentFactories().Keys); builder.Services.TryAddSingleton(_ => new DefaultFunctionsAgentOptionsProvider(DurableAgentsOptionsExtensions.GetAgentOptionsSnapshot())); builder.Services.AddSingleton(); // Handling of built-in function execution for Agent HTTP, MCP tool, or Entity invocations. builder.UseWhen(static context => string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentHttpFunctionEntryPoint, StringComparison.Ordinal) || string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentMcpToolFunctionEntryPoint, StringComparison.Ordinal) || string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentEntityFunctionEntryPoint, StringComparison.Ordinal)); builder.Services.AddSingleton(); return builder; } /// /// Configures durable options for the functions application, allowing customization of Durable Task framework /// settings. /// /// This method ensures that a single shared instance is used across all /// configuration calls. If any workflows have been added, it configures the necessary orchestrations and registers /// required middleware. /// The functions application builder to configure. Cannot be null. /// An action that configures the instance. Cannot be null. /// The updated instance, enabling method chaining. public static FunctionsApplicationBuilder ConfigureDurableOptions( this FunctionsApplicationBuilder builder, Action configure) { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(configure); // Ensure FunctionsDurableOptions is registered BEFORE the core extension creates a plain DurableOptions FunctionsDurableOptions sharedOptions = GetOrCreateSharedOptions(builder.Services); builder.Services.ConfigureDurableOptions(configure); if (DurableAgentsOptionsExtensions.GetAgentOptionsSnapshot().Count > 0) { builder.Services.TryAddSingleton(_ => new DefaultFunctionsAgentOptionsProvider(DurableAgentsOptionsExtensions.GetAgentOptionsSnapshot())); builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); } if (sharedOptions.Workflows.Workflows.Count > 0) { builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); } EnsureMiddlewareRegistered(builder); return builder; } /// /// Configures durable workflow support for the specified Azure Functions application builder. /// /// The instance to configure for durable workflows. /// An action that configures the , allowing customization of durable workflow behavior. /// The updated instance, enabling method chaining. public static FunctionsApplicationBuilder ConfigureDurableWorkflows( this FunctionsApplicationBuilder builder, Action configure) { ArgumentNullException.ThrowIfNull(configure); return builder.ConfigureDurableOptions(options => configure(options.Workflows)); } private static void EnsureMiddlewareRegistered(FunctionsApplicationBuilder builder) { // Guard against registering the middleware filter multiple times in the pipeline. if (builder.Services.Any(d => d.ServiceType == typeof(BuiltInFunctionExecutor))) { return; } builder.UseWhen(static context => string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentHttpFunctionEntryPoint, StringComparison.Ordinal) || string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentMcpToolFunctionEntryPoint, StringComparison.Ordinal) || string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunAgentEntityFunctionEntryPoint, StringComparison.Ordinal) || string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunWorkflowOrchestrationHttpFunctionEntryPoint, StringComparison.Ordinal) || string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunWorkflowOrchestrationFunctionEntryPoint, StringComparison.Ordinal) || string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.InvokeWorkflowActivityFunctionEntryPoint, StringComparison.Ordinal) || string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.GetWorkflowStatusHttpFunctionEntryPoint, StringComparison.Ordinal) || string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RespondToWorkflowHttpFunctionEntryPoint, StringComparison.Ordinal) || string.Equals(context.FunctionDefinition.EntryPoint, BuiltInFunctions.RunWorkflowMcpToolFunctionEntryPoint, StringComparison.Ordinal) ); builder.Services.TryAddSingleton(); } /// /// Gets or creates a shared instance from the service collection. /// private static FunctionsDurableOptions GetOrCreateSharedOptions(IServiceCollection services) { ServiceDescriptor? existingDescriptor = services.FirstOrDefault( d => d.ServiceType == typeof(DurableOptions) && d.ImplementationInstance is not null); if (existingDescriptor?.ImplementationInstance is FunctionsDurableOptions existing) { return existing; } FunctionsDurableOptions options = new(); services.AddSingleton(options); services.AddSingleton(options); return options; } }