// Copyright (c) Microsoft. All rights reserved. using Microsoft.DurableTask; using Microsoft.DurableTask.Client; using Microsoft.DurableTask.Entities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace Microsoft.Agents.AI.DurableTask; /// /// A context for durable agents that provides access to orchestration capabilities. /// This class provides thread-static access to the current agent context. /// public class DurableAgentContext { private static readonly AsyncLocal s_currentContext = new(); private readonly IServiceProvider _services; private readonly CancellationToken _cancellationToken; internal DurableAgentContext( TaskEntityContext entityContext, DurableTaskClient client, IHostApplicationLifetime lifetime, IServiceProvider services) { this.EntityContext = entityContext; this.CurrentSession = new DurableAgentSession(entityContext.Id); this.Client = client; this._services = services; this._cancellationToken = lifetime.ApplicationStopping; } /// /// Gets the current durable agent context instance. /// /// Thrown when no agent context is available. public static DurableAgentContext Current => s_currentContext.Value ?? throw new InvalidOperationException("No agent context found!"); /// /// Gets the entity context for this agent. /// public TaskEntityContext EntityContext { get; } /// /// Gets the durable task client for this agent. /// public DurableTaskClient Client { get; } /// /// Gets the current agent thread. /// public DurableAgentSession CurrentSession { get; } /// /// Sets the current durable agent context instance. /// This is called internally by the agent entity during execution. /// /// The context instance to set. internal static void SetCurrent(DurableAgentContext context) { if (s_currentContext.Value is not null) { throw new InvalidOperationException("A DurableAgentContext has already been set for this AsyncLocal context."); } s_currentContext.Value = context; } /// /// Clears the current durable agent context instance. /// This is called internally by the agent entity after execution. /// internal static void ClearCurrent() { s_currentContext.Value = null; } /// /// Schedules a new orchestration instance. /// /// /// When run in the context of a durable agent tool, the actual scheduling of the orchestration /// occurs after the completion of the tool call. This allows the durable scheduling of the orchestration /// and the agent state update to be committed atomically in a single transaction. /// /// The name of the orchestration to schedule. /// The input to the orchestration. /// The options for the orchestration. /// The instance ID of the scheduled orchestration. public string ScheduleNewOrchestration( TaskName name, object? input = null, StartOrchestrationOptions? options = null) { return this.EntityContext.ScheduleNewOrchestration(name, input, options); } /// /// Gets the status of an orchestration instance. /// /// The instance ID of the orchestration to get the status of. /// Whether to include detailed information about the orchestration. /// The status of the orchestration. public Task GetOrchestrationStatusAsync(string instanceId, bool includeDetails = false) { return this.Client.GetInstanceAsync(instanceId, includeDetails, this._cancellationToken); } /// /// Raises an event on an orchestration instance. /// /// The instance ID of the orchestration to raise the event on. /// The name of the event to raise. /// The data to send with the event. #pragma warning disable CA1030 // Use events where appropriate public Task RaiseOrchestrationEventAsync(string instanceId, string eventName, object? eventData = null) #pragma warning restore CA1030 // Use events where appropriate { return this.Client.RaiseEventAsync(instanceId, eventName, eventData, this._cancellationToken); } /// /// Asks the for an object of the specified type, . /// /// The type of the object being requested. /// An optional key to identify the service instance. /// The service instance, or if the service is not found. /// /// Thrown when is not and the service provider does not support keyed services. /// public TService? GetService(object? serviceKey = null) { return this.GetService(typeof(TService), serviceKey) is TService service ? service : default; } /// /// Asks the for an object of the specified type, . /// /// The type of the object being requested. /// An optional key to identify the service instance. /// The service instance, or if the service is not found. /// /// Thrown when is not and the service provider does not support keyed services. /// public object? GetService(Type serviceType, object? serviceKey = null) { if (serviceKey is not null) { if (this._services is not IKeyedServiceProvider keyedServiceProvider) { throw new InvalidOperationException("The service provider does not support keyed services."); } return keyedServiceProvider.GetKeyedService(serviceType, serviceKey); } return this._services.GetService(serviceType); } }