// 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);
}
}