diff --git a/dotnet/src/Microsoft.Agents.Workflows/Executor.cs b/dotnet/src/Microsoft.Agents.Workflows/Executor.cs index 194632b56a..ece973d7d1 100644 --- a/dotnet/src/Microsoft.Agents.Workflows/Executor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows/Executor.cs @@ -25,9 +25,15 @@ public abstract class Executor : IIdentified /// /// A optional unique identifier for the executor. If null, a type-tagged /// UUID will be generated. - protected Executor(string? id = null) + protected Executor(string? id = null) : this(ExecutorOptions.Default, id) + { + } + + private readonly ExecutorOptions _options; + internal Executor(ExecutorOptions options, string? id = null) { this.Id = id ?? $"{this.GetType().Name}/{Guid.NewGuid():N}"; + this._options = options; } /// @@ -96,7 +102,7 @@ public abstract class Executor : IIdentified } // If we had a real return type, raise it as a SendMessage; TODO: Should we have a way to disable this behaviour? - if (result.Result != null && ExecutorOptions.Default.AutoSendMessageHandlerResultObject) + if (result.Result != null && this._options.AutoSendMessageHandlerResultObject) { await context.SendMessageAsync(result.Result).ConfigureAwait(false); } diff --git a/dotnet/src/Microsoft.Agents.Workflows/ExecutorIsh.cs b/dotnet/src/Microsoft.Agents.Workflows/ExecutorIsh.cs index 4520e286ce..a117f1ae0b 100644 --- a/dotnet/src/Microsoft.Agents.Workflows/ExecutorIsh.cs +++ b/dotnet/src/Microsoft.Agents.Workflows/ExecutorIsh.cs @@ -110,7 +110,7 @@ public sealed class ExecutorIsh : { Type.Unbound => throw new InvalidOperationException($"Executor with ID '{this.Id}' is unbound."), Type.Executor => () => this._executorValue!, - Type.InputPort => () => new RequestInputExecutor(this._inputPortValue!), + Type.InputPort => () => new RequestInfoExecutor(this._inputPortValue!), Type.Agent => () => new AIAgentHostExecutor(this._aiAgentValue!), _ => throw new InvalidOperationException($"Unknown ExecutorIsh type: {this.ExecutorType}") }; diff --git a/dotnet/src/Microsoft.Agents.Workflows/ExecutorOptions.cs b/dotnet/src/Microsoft.Agents.Workflows/ExecutorOptions.cs index dfb335b077..a19630bf31 100644 --- a/dotnet/src/Microsoft.Agents.Workflows/ExecutorOptions.cs +++ b/dotnet/src/Microsoft.Agents.Workflows/ExecutorOptions.cs @@ -12,7 +12,7 @@ public sealed class ExecutorOptions /// public static ExecutorOptions Default { get; } = new(); - private ExecutorOptions() { } + internal ExecutorOptions() { } /// /// If , the result of a message handler that returns a value will be sent as a message to the workflow. diff --git a/dotnet/src/Microsoft.Agents.Workflows/InProc/InProcessRunnerContext.cs b/dotnet/src/Microsoft.Agents.Workflows/InProc/InProcessRunnerContext.cs index a797832524..c2206fdea6 100644 --- a/dotnet/src/Microsoft.Agents.Workflows/InProc/InProcessRunnerContext.cs +++ b/dotnet/src/Microsoft.Agents.Workflows/InProc/InProcessRunnerContext.cs @@ -34,7 +34,7 @@ internal class InProcessRunnerContext : IRunnerContext this._executors[executorId] = executor = provider(); - if (executor is RequestInputExecutor requestInputExecutor) + if (executor is RequestInfoExecutor requestInputExecutor) { requestInputExecutor.AttachRequestSink(this); } diff --git a/dotnet/src/Microsoft.Agents.Workflows/Specialized/RequestInputExecutor.cs b/dotnet/src/Microsoft.Agents.Workflows/Specialized/RequestInfoExecutor.cs similarity index 65% rename from dotnet/src/Microsoft.Agents.Workflows/Specialized/RequestInputExecutor.cs rename to dotnet/src/Microsoft.Agents.Workflows/Specialized/RequestInfoExecutor.cs index ea181d3991..4cb151666f 100644 --- a/dotnet/src/Microsoft.Agents.Workflows/Specialized/RequestInputExecutor.cs +++ b/dotnet/src/Microsoft.Agents.Workflows/Specialized/RequestInfoExecutor.cs @@ -7,22 +7,40 @@ using Microsoft.Shared.Diagnostics; namespace Microsoft.Agents.Workflows.Specialized; -internal class RequestInputExecutor : Executor +internal class RequestInfoExecutor : Executor { private InputPort Port { get; } private IExternalRequestSink? RequestSink { get; set; } - public RequestInputExecutor(InputPort port) : base(port.Id) + private static ExecutorOptions DefaultOptions => new() + { + // We need to be able to return the ExternalRequest/Result objects so they can be bubbled up + // through the event system, but we do not want to forward the Request message. + AutoSendMessageHandlerResultObject = false + }; + + private readonly bool _allowWrapped; + public RequestInfoExecutor(InputPort port, bool allowWrapped = true) : base(RequestInfoExecutor.DefaultOptions, port.Id) { this.Port = port; + + this._allowWrapped = allowWrapped; } protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) { - return routeBuilder + routeBuilder = routeBuilder // Handle incoming requests (as raw request payloads) .AddHandler(this.Port.Request, this.HandleAsync) - .AddHandler(typeof(object), this.HandleAsync) + .AddHandler(typeof(object), this.HandleAsync); + + if (this._allowWrapped) + { + routeBuilder = routeBuilder + .AddHandler((request, context) => this.HandleAsync(request.Data, context)); + } + + return routeBuilder // Handle incoming responses (as wrapped Response object) .AddHandler(this.HandleAsync); } @@ -53,6 +71,7 @@ internal class RequestInputExecutor : Executor $"Message type {message.Data.GetType().Name} is not assignable to the response type {this.Port.Response.Name} of input port {this.Port.Id}."); } + await context.SendMessageAsync(message).ConfigureAwait(false); await context.SendMessageAsync(message.Data).ConfigureAwait(false); return message;