// Copyright (c) Microsoft. All rights reserved. using Microsoft.Agents.AI.DurableTask; using Microsoft.Agents.AI.Workflows.Checkpointing; using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata; using Microsoft.Extensions.Logging; namespace Microsoft.Agents.AI.Hosting.AzureFunctions; internal sealed class DurableWorkflowFunctionMetadataTransformer : IFunctionMetadataTransformer { private readonly ILogger _logger; private readonly DurableWorkflowOptions _options; public DurableWorkflowFunctionMetadataTransformer(ILogger logger, DurableOptions durableOptions) { this._logger = logger ?? throw new ArgumentNullException(nameof(logger)); ArgumentNullException.ThrowIfNull(durableOptions); this._options = durableOptions.Workflows; } public string Name => nameof(DurableWorkflowFunctionMetadataTransformer); public void Transform(IList original) { this._logger.LogTransformStart(original.Count); foreach (var workflow in this._options.Workflows) { this._logger.LogAddingWorkflowFunction(workflow.Key); // Currently due to how durable executor is registered, we are not able to bind TaskOrechestrationContext parameter properly // because the InputBinding for TOC happens inside the DurableExecutor (rathen than in an input converter). // So for now, we are going to use single orchestration function for all workflows. //original.Add(CreateOrchestrationTrigger(workflow.Key)); // We also want to create an HTTP trigger for this orchestration so users can start it via HTTP. this._logger.LogAddingHttpTrigger(workflow.Key); original.Add(CreateHttpTrigger(workflow.Key, $"workflows/{workflow.Key}/run")); // Create activity/entity functions for each executor in the workflow based on their type // Extract executor IDs from edges and start executor var executorIds = new HashSet { workflow.Value.StartExecutorId }; var reflectedEdges = workflow.Value.ReflectEdges(); foreach (var (sourceId, edgeSet) in reflectedEdges) { executorIds.Add(sourceId); foreach (var edge in edgeSet) { foreach (var sinkId in edge.Connection.SinkIds) { executorIds.Add(sinkId); } } } Dictionary executorInfos = workflow.Value.ReflectExecutors(); foreach (string executorId in executorIds) { if (executorInfos.TryGetValue(executorId, out ExecutorInfo? executorInfo)) { string functionName = $"dafx-{executorId.Split("_")[0]}"; // Check if the executor type is an agent-related type if (WorkflowHelper.IsAgentExecutorType(executorInfo.ExecutorType)) { this._logger.LogAddingAgentEntityFunction(executorId, executorInfo.ExecutorType.TypeName, workflow.Key); //original.Add(CreateAgentTrigger(functionName)); } else { this._logger.LogAddingActivityFunction(executorId, executorInfo.ExecutorType.TypeName, workflow.Key); original.Add(CreateActivityTrigger(functionName)); } } } } this._logger.LogTransformFinished(original.Count); } private static DefaultFunctionMetadata CreateHttpTrigger(string name, string route) { return new DefaultFunctionMetadata() { Name = $"{BuiltInFunctions.HttpPrefix}{name}", Language = "dotnet-isolated", RawBindings = [ $"{{\"name\":\"req\",\"type\":\"httpTrigger\",\"direction\":\"In\",\"authLevel\":\"function\",\"methods\": [\"post\"],\"route\":\"{route}\"}}", "{\"name\":\"$return\",\"type\":\"http\",\"direction\":\"Out\"}", "{\"name\":\"client\",\"type\":\"durableClient\",\"direction\":\"In\"}" ], EntryPoint = BuiltInFunctions.RunWorkflowOrechstrtationHttpFunctionEntryPoint, ScriptFile = BuiltInFunctions.ScriptFile }; } //private static DefaultFunctionMetadata CreateOrchestrationTrigger(string name) //{ // return new DefaultFunctionMetadata() // { // Name = AgentSessionId.ToEntityName(name), // Language = "dotnet-isolated", // RawBindings = // [ // // """{"name":"context","type":"orchestrationTrigger","direction":"In"}""", // """{"name":"taskOrchestrationContext","type":"orchestrationTrigger","direction":"In"}""", // ], // EntryPoint = BuiltInFunctions.RunWorkflowOrechstrtationFunctionEntryPoint, // ScriptFile = BuiltInFunctions.ScriptFile, // }; //} private static DefaultFunctionMetadata CreateActivityTrigger(string functionName) { return new DefaultFunctionMetadata() { Name = functionName, Language = "dotnet-isolated", RawBindings = [ """{"name":"input","type":"activityTrigger","direction":"In","dataType":"String"}""", """{"name":"durableTaskClient","type":"durableClient","direction":"In"}""" ], EntryPoint = BuiltInFunctions.InvokeWorkflowActivityFunctionEntryPoint, ScriptFile = BuiltInFunctions.ScriptFile, }; } //private static DefaultFunctionMetadata CreateAgentTrigger(string functionName) //{ // return new DefaultFunctionMetadata() // { // Name = functionName, // Language = "dotnet-isolated", // RawBindings = // [ // """{"name":"encodedEntityRequest","type":"entityTrigger","direction":"In"}""", // """{"name":"client","type":"durableClient","direction":"In"}""" // ], // EntryPoint = BuiltInFunctions.RunAgentEntityFunctionEntryPoint, // ScriptFile = BuiltInFunctions.ScriptFile, // }; //} }