diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx index c90e63c262..5db8d93d98 100644 --- a/dotnet/agent-framework-dotnet.slnx +++ b/dotnet/agent-framework-dotnet.slnx @@ -35,6 +35,8 @@ + + diff --git a/dotnet/samples/AzureFunctions/09_Workflow/Program.cs b/dotnet/samples/AzureFunctions/09_Workflow/Program.cs index 5a160e6781..4261fb9533 100644 --- a/dotnet/samples/AzureFunctions/09_Workflow/Program.cs +++ b/dotnet/samples/AzureFunctions/09_Workflow/Program.cs @@ -26,7 +26,7 @@ AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey) // Set up an AI agent following the standard Microsoft Agent Framework pattern. const string JokerName = "Joker"; const string JokerInstructions = "You are good at telling jokes."; -const string AnalysisInstructions = """ +const string FeedbackAnalysisInstructions = """ You are a Customer Feedback Analyzer. Your task is to analyze customer survey responses and categorize them accurately. INPUT: You will receive customer feedback text that may include a rating and comments. @@ -44,31 +44,44 @@ CATEGORIZATION RULES: - "Support Incident Status": Follow-ups on existing tickets, status inquiries about previous issues RESPONSE FORMAT: Return only the category name exactly as shown above, with no additional text or explanation. - -Examples: -- "The app crashes when I try to export" → Bug Report -- "Love the new design! Great work" → General Feedback -- "Why was I charged twice this month?" → Billing Question -- "What's the status of ticket #12345?" → Support Incident Status """; -AIAgent agent = client.GetChatClient(deploymentName).CreateAIAgent(JokerInstructions, JokerName); -AIAgent surveyFeedbackAgent = client.GetChatClient(deploymentName).CreateAIAgent(AnalysisInstructions, "FeedbackAnalysisBot"); +AIAgent agent = client.GetChatClient(deploymentName).CreateAIAgent(JokerInstructions, JokerName); + +// 3 Executors usd by the workflow. +// 1. Class based executor to parse survey response. SurveyResponseParserExecutor surveyResponseParserExecutor = new(); -ResponseRouterExecutor responseRouterExecutor = new(); + +// 2. AI Agent to analyze feedback and categorize it. +AIAgent surveyFeedbackAgent = client.GetChatClient(deploymentName).CreateAIAgent(FeedbackAnalysisInstructions, "FeedbackAnalyzerAgent"); + +// 3. Function delegate executor to route response based on category. +Func responseHandlingExecutorFunc = input => input switch +{ + var s when s.Contains("billing", StringComparison.OrdinalIgnoreCase) + => "Will notify Billing Team", + + var s when s.Contains("Bug Report", StringComparison.OrdinalIgnoreCase) + => "Will notify Technical Support Team", + + _ => "Will notify General Support Team" +}; +var responseRouterExecutor = responseHandlingExecutorFunc.BindAsExecutor("ResponseRouterExecutor"); WorkflowBuilder builder = new(surveyResponseParserExecutor); builder.AddEdge(surveyResponseParserExecutor, surveyFeedbackAgent); builder.AddEdge(surveyFeedbackAgent, responseRouterExecutor).WithOutputFrom(responseRouterExecutor); - var workflow = builder.WithName("HandleSurveyResponse").Build(); -// Configure the function app to host AI agents and workflows in a unified way. -// This will automatically generate HTTP API endpoints for agents and workflows. -var functionBuilder = FunctionsApplication.CreateBuilder(args); -functionBuilder.ConfigureFunctionsWebApplication().ConfigureDurableOptions(options => -{ - // Configure workflows - options.Workflows.AddWorkflow(workflow); -}); -functionBuilder.Build().Run(); +FunctionsApplication.CreateBuilder(args) + .ConfigureFunctionsWebApplication() + //.ConfigureDurableAgents(options => options.AddAIAgent(agent)) + .ConfigureDurableOptions(options => + { + // Configure workflows + options.Workflows.AddWorkflow(workflow); + + // Optional - Configure AI agents + // options.Agents.AddAIAgent(agent); + }) + .Build().Run(); diff --git a/dotnet/samples/AzureFunctions/09_Workflow/ResponseRouterExecutor.cs b/dotnet/samples/AzureFunctions/09_Workflow/ResponseRouterExecutor.cs deleted file mode 100644 index fdf1090ec2..0000000000 --- a/dotnet/samples/AzureFunctions/09_Workflow/ResponseRouterExecutor.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.Agents.AI.Workflows; - -namespace SingleAgent; - -/// -/// Routes survey responses to appropriate teams based on rating and category. -/// -public sealed class ResponseRouterExecutor() : Executor("ResponseRouterExecutor") -{ - public override ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) - { - if (message.Contains("billing", StringComparison.OrdinalIgnoreCase)) - { - return ValueTask.FromResult("Routed to Billing Team"); - } - else if (message.Contains("technical", StringComparison.OrdinalIgnoreCase)) - { - return ValueTask.FromResult("Routed to Technical Support Team"); - } - else - { - return ValueTask.FromResult("Routed to General Support Team"); - } - } -} diff --git a/dotnet/samples/AzureFunctions/09_Workflow/SurveyResponseParserExecutor.cs b/dotnet/samples/AzureFunctions/09_Workflow/SurveyResponseParserExecutor.cs index f6a66c9038..0697dd4edf 100644 --- a/dotnet/samples/AzureFunctions/09_Workflow/SurveyResponseParserExecutor.cs +++ b/dotnet/samples/AzureFunctions/09_Workflow/SurveyResponseParserExecutor.cs @@ -23,12 +23,12 @@ internal sealed partial class SurveyResponseParserExecutor() : Executor HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) { - SurveyResponse response = this.ParseSurveyResponse(message); + SurveyResponse response = ParseSurveyResponse(message); string jsonResult = JsonSerializer.Serialize(response, s_jsonOptions); return ValueTask.FromResult(jsonResult); } - private SurveyResponse ParseSurveyResponse(string message) + private static SurveyResponse ParseSurveyResponse(string message) { // Parse the message to extract rating and comment int? rating = null; diff --git a/dotnet/samples/AzureFunctions/09_Workflow/demo.http b/dotnet/samples/AzureFunctions/09_Workflow/demo.http index d1a4ba0738..6408795c5a 100644 --- a/dotnet/samples/AzureFunctions/09_Workflow/demo.http +++ b/dotnet/samples/AzureFunctions/09_Workflow/demo.http @@ -6,3 +6,9 @@ POST {{authority}}/api/workflows/HandleSurveyResponse/run Content-Type: text/plain Rating: 10. Why was I charged $99 when my plan should be $49? I need a refund for the overcharge + + +POST {{authority}}/api/workflows/HandleSurveyResponse/run +Content-Type: text/plain + +Rating: 10. Why was I charged $99 when my plan should be $49? I need a refund for the overcharge \ No newline at end of file diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAgentsOptions.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAgentsOptions.cs index 15a5cae43d..7023cc2956 100644 --- a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAgentsOptions.cs +++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAgentsOptions.cs @@ -171,9 +171,19 @@ public sealed class DurableAgentsOptions /// Determines whether an agent is configured as workflow-only (no HTTP triggers). /// /// The name of the agent. - /// True if the agent is workflow-only; otherwise, false. + /// if the agent is workflow-only; otherwise, . internal bool IsWorkflowOnly(string agentName) { return this._workflowOnlyAgents.Contains(agentName); } + + /// + /// Determines whether an agent with the specified name is already registered. + /// + /// The name of the agent. + /// if an agent with the name is registered; otherwise, . + internal bool ContainsAgent(string agentName) + { + return this._agentFactories.ContainsKey(agentName); + } } diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowOptions.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowOptions.cs index 8eea0591ce..255e36a215 100644 --- a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowOptions.cs +++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowOptions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using Microsoft.Agents.AI.Workflows; +using Microsoft.Agents.AI.Workflows.Checkpointing; namespace Microsoft.Agents.AI.DurableTask; @@ -25,13 +26,10 @@ public sealed class DurableWorkflowOptions /// /// Gets the collection of workflows available in the current context, keyed by their unique names. /// - /// The returned dictionary is read-only and reflects the current set of registered workflows. - /// Changes to the underlying workflow collection are immediately visible through this property. Accessing a - /// workflow by name that does not exist will result in a KeyNotFoundException. public IReadOnlyDictionary Workflows => this._workflows; /// - /// Gets the executor registry. + /// Gets the executor registry for direct executor lookup. /// internal ExecutorRegistry Executors { get; } @@ -41,8 +39,10 @@ public sealed class DurableWorkflowOptions /// The workflow instance to add. Cannot be null. /// /// When a workflow is added, any AI agent executors in the workflow will be automatically - /// registered with the DurableAgentsOptions if it was provided during construction. + /// registered with the if it was provided during construction. /// + /// Thrown when is null. + /// Thrown when the workflow does not have a valid name. public void AddWorkflow(Workflow workflow) { ArgumentNullException.ThrowIfNull(workflow); @@ -54,48 +54,33 @@ public sealed class DurableWorkflowOptions this._workflows[workflow.Name] = workflow; - // Register executors in the registry for direct lookup RegisterExecutors(workflow, this.Executors); - // Register any agentic executors with DurableAgentsOptions if available through parent - DurableAgentsOptions? agentOptions = this.TryGetAgentOptions(); + DurableAgentsOptions? agentOptions = this._parentOptions?.Agents; if (agentOptions is not null) { RegisterAgenticExecutors(workflow, agentOptions); } } - private DurableAgentsOptions? TryGetAgentOptions() + private static void RegisterExecutors(Workflow workflow, ExecutorRegistry registry) { - return this._parentOptions?.Agents; + foreach (KeyValuePair executor in workflow.ReflectExecutors()) + { + int underscoreIndex = executor.Key.IndexOf('_'); + string executorName = underscoreIndex > 0 ? executor.Key[..underscoreIndex] : executor.Key; + registry.Register(executorName, executor.Key, workflow); + } } private static void RegisterAgenticExecutors(Workflow workflow, DurableAgentsOptions agentOptions) { - // Use the public EnumerateAgentExecutors method to get all AIAgent instances foreach (AIAgent agent in workflow.EnumerateAgentExecutors()) { - try + if (agent.Name is not null && !agentOptions.ContainsAgent(agent.Name)) { - // Register the agent as workflow-only (no HTTP trigger) agentOptions.AddAIAgent(agent, workflowOnly: true); } - catch (ArgumentException) - { - // Agent with this name is already registered, skip it - // This is expected behavior when multiple workflows use the same agent - } - } - } - - private static void RegisterExecutors(Workflow workflow, ExecutorRegistry registry) - { - // Register all executors from the workflow in the registry - foreach (KeyValuePair executor in workflow.ReflectExecutors()) - { - // Extract the executor name (without GUID suffix) - string executorName = executor.Key.Split('_')[0]; - registry.Register(executorName, executor.Key, workflow); } } } diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/ExecutorRegistry.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/ExecutorRegistry.cs index b4d3bd70d4..26871f76da 100644 --- a/dotnet/src/Microsoft.Agents.AI.DurableTask/ExecutorRegistry.cs +++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/ExecutorRegistry.cs @@ -7,52 +7,40 @@ namespace Microsoft.Agents.AI.DurableTask; /// /// Provides a registry for storing and retrieving executor bindings independently from workflows. /// -/// -/// This registry allows executors to be looked up by name without needing to search through all workflows, -/// which is useful for activity function execution where only the executor name is known. -/// internal sealed class ExecutorRegistry { private readonly Dictionary _executors = new(StringComparer.OrdinalIgnoreCase); + /// + /// Gets the number of registered executors. + /// + public int Count => this._executors.Count; + + /// + /// Attempts to get an executor registration by name. + /// + /// The executor name to look up. + /// When this method returns, contains the registration if found; otherwise, null. + /// if the executor was found; otherwise, . + public bool TryGetExecutor(string executorName, out ExecutorRegistration? registration) + { + return this._executors.TryGetValue(executorName, out registration); + } + /// /// Registers an executor binding from a workflow. /// /// The executor name (without GUID suffix). /// The full executor ID (may include GUID suffix). /// The workflow containing the executor. - /// - /// If an executor with the same name is already registered, it will be skipped. - /// This is expected behavior when the same executor is used across multiple workflows. - /// internal void Register(string executorName, string executorId, Workflow workflow) { ArgumentException.ThrowIfNullOrEmpty(executorName); ArgumentException.ThrowIfNullOrEmpty(executorId); ArgumentNullException.ThrowIfNull(workflow); - // Only register if not already present (first registration wins) - if (!this._executors.ContainsKey(executorName)) - { - this._executors[executorName] = new ExecutorRegistration(executorId, workflow); - } + this._executors.TryAdd(executorName, new ExecutorRegistration(executorId, workflow)); } - - /// - /// Attempts to get an executor registration by name. - /// - /// The executor name to look up. - /// When this method returns, contains the registration if found; otherwise, null. - /// true if the executor was found; otherwise, false. - public bool TryGetExecutor(string executorName, out ExecutorRegistration? registration) - { - return this._executors.TryGetValue(executorName, out registration); - } - - /// - /// Gets the number of registered executors. - /// - public int Count => this._executors.Count; } /// diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctionExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctionExecutor.cs index c1c8378be1..20da9e9d12 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctionExecutor.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctionExecutor.cs @@ -44,7 +44,6 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor string? encodedEntityRequest = null; DurableTaskClient? durableTaskClient = null; ToolInvocationContext? mcpToolInvocationContext = null; - //string? encodedTaskOrchestrationContext = null; foreach (var binding in values) { @@ -62,9 +61,6 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor case ToolInvocationContext toolContext: mcpToolInvocationContext = toolContext; break; - //case string orchestrationContext: - // encodedTaskOrchestrationContext = orchestrationContext; - // break; } } @@ -84,7 +80,7 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor context.GetInvocationResult().Value = await BuiltInFunctions.RunAgentHttpAsync( httpRequestData, - durableTaskClient!, + durableTaskClient, context); return; } @@ -97,7 +93,7 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor } context.GetInvocationResult().Value = await BuiltInFunctions.InvokeAgentAsync( - durableTaskClient!, + durableTaskClient, encodedEntityRequest, context); return; @@ -111,7 +107,7 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor } context.GetInvocationResult().Value = - await BuiltInFunctions.RunMcpToolAsync(mcpToolInvocationContext, durableTaskClient!, context); + await BuiltInFunctions.RunMcpToolAsync(mcpToolInvocationContext, durableTaskClient, context); return; } @@ -124,7 +120,7 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor context.GetInvocationResult().Value = await BuiltInFunctions.RunWorkflowOrechstrtationHttpTriggerAsync( httpRequestData, - durableTaskClient!, + durableTaskClient, context); return; } diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs index 150c1de6ff..879c94ba8a 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctions.cs @@ -35,6 +35,9 @@ internal static class BuiltInFunctions [ActivityTrigger] string input, FunctionContext functionContext) { + ArgumentNullException.ThrowIfNull(input); + ArgumentNullException.ThrowIfNull(functionContext); + string activityFunctionName = functionContext.FunctionDefinition.Name; DurableWorkflowRunner runner = functionContext.InstanceServices.GetRequiredService(); diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DuableWorkflowRunRequest.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DuableWorkflowRunRequest.cs index 325fed1af5..9b3fa7db5e 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DuableWorkflowRunRequest.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DuableWorkflowRunRequest.cs @@ -5,18 +5,18 @@ using System.Text.Json.Serialization; namespace Microsoft.Agents.AI.Hosting.AzureFunctions; /// -/// Represents a request to run a workflow in the Duable system. +/// Represents a request to run a durable workflow. /// -public sealed class DuableWorkflowRunRequest +internal sealed class DuableWorkflowRunRequest { /// - /// Gets or sets the name of the workflow. + /// Gets or sets the name of the workflow to execute. /// [JsonPropertyName("workflowName")] public string WorkflowName { get; set; } = string.Empty; /// - /// Gets or sets the input string to be processed or analyzed. + /// Gets or sets the input for the workflow. /// [JsonPropertyName("input")] public string Input { get; set; } = string.Empty; diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableOptionsExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableOptionsExtensions.cs index b85a065dc3..4b801f2e99 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableOptionsExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableOptionsExtensions.cs @@ -98,6 +98,9 @@ public static class DurableOptionsExtensions private static void ConfigureWorkflowOrchestration(FunctionsApplicationBuilder builder) { + // Registering a single orchestration function to handle all workflow runs. + // This is due to a gap in durable extension today and can be replace with dynamic orchestration registration in future, per workflow. + builder.ConfigureDurableWorker().AddTasks(tasks => tasks.AddOrchestratorFunc>( "WorkflowRunnerOrchestration", diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableWorkflowRunner.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableWorkflowRunner.cs index 537ee961cc..350ead2417 100644 --- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableWorkflowRunner.cs +++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/DurableWorkflowRunner.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Diagnostics.CodeAnalysis; using System.Text.Json; using Microsoft.Agents.AI.DurableTask; using Microsoft.Agents.AI.Workflows; @@ -113,7 +114,7 @@ internal sealed class DurableWorkflowRunner { WorkflowExecutorInfo executorInfo = level.Executors[0]; string input = GetExecutorInput(executorInfo.ExecutorId, initialInput, results, plan); - results[executorInfo.ExecutorId] = await this.ExecuteExecutorAsync(context, workflow, executorInfo, input, logger).ConfigureAwait(true); + results[executorInfo.ExecutorId] = await this.ExecuteExecutorAsync(context, executorInfo, input, logger).ConfigureAwait(true); } else { @@ -121,7 +122,7 @@ internal sealed class DurableWorkflowRunner foreach (WorkflowExecutorInfo executorInfo in level.Executors) { string input = GetExecutorInput(executorInfo.ExecutorId, initialInput, results, plan); - tasks.Add(this.ExecuteExecutorWithIdAsync(context, workflow, executorInfo, input, logger)); + tasks.Add(this.ExecuteExecutorWithIdAsync(context, executorInfo, input, logger)); } foreach ((string id, string result) in await Task.WhenAll(tasks).ConfigureAwait(true)) @@ -136,18 +137,16 @@ internal sealed class DurableWorkflowRunner private async Task<(string Id, string Result)> ExecuteExecutorWithIdAsync( TaskOrchestrationContext context, - Workflow workflow, WorkflowExecutorInfo executorInfo, string input, ILogger logger) { - string result = await this.ExecuteExecutorAsync(context, workflow, executorInfo, input, logger).ConfigureAwait(true); + string result = await this.ExecuteExecutorAsync(context, executorInfo, input, logger).ConfigureAwait(true); return (executorInfo.ExecutorId, result); } private async Task ExecuteExecutorAsync( TaskOrchestrationContext context, - Workflow workflow, WorkflowExecutorInfo executorInfo, string input, ILogger logger) @@ -158,10 +157,10 @@ internal sealed class DurableWorkflowRunner return await context.CallActivityAsync(triggerName, input).ConfigureAwait(true); } - return await this.ExecuteAgentAsync(context, executorInfo, input, logger).ConfigureAwait(true); + return await ExecuteAgentAsync(context, executorInfo, input, logger).ConfigureAwait(true); } - private async Task ExecuteAgentAsync( + private static async Task ExecuteAgentAsync( TaskOrchestrationContext context, WorkflowExecutorInfo executorInfo, string input, @@ -259,15 +258,15 @@ internal sealed class DurableWorkflowRunner return underscoreIndex > 0 ? executorId[..underscoreIndex] : executorId; } - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Serializing known types.")] - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Serializing known types.")] + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Serializing known types.")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Serializing known types.")] private static string SerializeToJson(List values) { return JsonSerializer.Serialize(values); } - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Serializing workflow types registered at startup.")] - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Serializing workflow types registered at startup.")] + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Serializing workflow types registered at startup.")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Serializing workflow types registered at startup.")] private static string SerializeResult(object? result) { if (result is null) @@ -289,8 +288,8 @@ internal sealed class DurableWorkflowRunner return JsonSerializer.Serialize(result, resultType); } - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Deserializing workflow types registered at startup.")] - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Deserializing workflow types registered at startup.")] + [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Deserializing workflow types registered at startup.")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Deserializing workflow types registered at startup.")] private static object DeserializeInput(string input, Type targetType) { if (targetType == typeof(string))