// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Checkpointing;
using Microsoft.Agents.AI.Workflows.Execution;
using Microsoft.Agents.AI.Workflows.Observability;
using Microsoft.Agents.AI.Workflows.Reflection;
namespace Microsoft.Agents.AI.Workflows;
///
/// A component that processes messages in a .
///
[DebuggerDisplay("{GetType().Name}{Id}")]
public abstract class Executor : IIdentified
{
///
/// A unique identifier for the executor.
///
public string Id { get; }
private static readonly string s_namespace = typeof(Executor).Namespace!;
private static readonly ActivitySource s_activitySource = new(s_namespace);
// TODO: Add overloads for binding with a configuration/options object once the Configured hierarchy goes away.
///
/// Initialize the executor with a unique identifier
///
/// A unique identifier for the executor.
/// Configuration options for the executor. If null, default options will be used.
/// Declare that this executor may be used simultaneously by multiple runs safely.
protected Executor(string id, ExecutorOptions? options = null, bool declareCrossRunShareable = false)
{
this.Id = id;
this.Options = options ?? ExecutorOptions.Default;
//if (declareCrossRunShareable && this is IResettableExecutor)
//{
// // We need a way to be able to let the user override this at the workflow level too, because knowing the fine
// // details of when to use which of these paths seems like it could be tricky, and we should not force users
// // to do this; instead container agents should set this when they intiate the run (via WorkflowHostAgent).
// throw new ArgumentException("An executor that is declared as cross-run shareable cannot also be resettable.");
//}
this.IsCrossRunShareable = declareCrossRunShareable;
}
internal bool IsCrossRunShareable { get; }
///
/// Gets the configuration options for the executor.
///
protected ExecutorOptions Options { get; }
///
/// Override this method to register handlers for the executor.
///
protected abstract RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder);
///
/// Perform any asynchronous initialization required by the executor. This method is called once per executor instance,
///
/// The workflow context in which the executor executes.
/// The to monitor for cancellation requests.
/// The default is .
/// A representing the asynchronous operation.
protected internal virtual ValueTask InitializeAsync(IWorkflowContext context, CancellationToken cancellationToken = default)
=> default;
///
/// Override this method to declare the types of messages this executor can send.
///
///
protected virtual ISet ConfigureSentTypes() => new HashSet([typeof(object)]);
///
/// Override this method to declare the types of messages this executor can yield as workflow outputs.
///
///
protected virtual ISet ConfigureYieldTypes()
{
if (this.Options.AutoYieldOutputHandlerResultObject)
{
return this.Router.DefaultOutputTypes;
}
return new HashSet();
}
private MessageRouter? _router;
internal MessageRouter Router
{
get
{
if (this._router is null)
{
RouteBuilder routeBuilder = this.ConfigureRoutes(new RouteBuilder());
this._router = routeBuilder.Build();
}
return this._router;
}
}
///
/// Process an incoming message using the registered handlers.
///
/// The message to be processed by the executor.
/// The "declared" type of the message (captured when it was being sent). This is
/// used to enable routing messages as their base types, in absence of true polymorphic type routing.
/// The workflow context in which the executor executes.
/// The to monitor for cancellation requests.
/// The default is .
/// A ValueTask representing the asynchronous operation, wrapping the output from the executor.
/// No handler found for the message type.
/// An exception is generated while handling the message.
public async ValueTask