// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
namespace Microsoft.Agents.AI.DurableTask.Workflows;
///
/// Represents a durable workflow run that tracks execution status and provides access to workflow events.
///
[DebuggerDisplay("{WorkflowName} ({RunId})")]
internal sealed class DurableWorkflowRun : IAwaitableWorkflowRun
{
private readonly DurableTaskClient _client;
private readonly List _eventSink = [];
private int _lastBookmark;
///
/// Initializes a new instance of the class.
///
/// The durable task client for orchestration operations.
/// The unique instance ID for this orchestration run.
/// The name of the workflow being executed.
internal DurableWorkflowRun(DurableTaskClient client, string instanceId, string workflowName)
{
this._client = client;
this.RunId = instanceId;
this.WorkflowName = workflowName;
}
///
public string RunId { get; }
///
/// Gets the name of the workflow being executed.
///
public string WorkflowName { get; }
///
/// Waits for the workflow to complete and returns the result.
///
/// The expected result type.
/// A cancellation token to observe.
/// The result of the workflow execution.
/// Thrown when the workflow failed.
/// Thrown when the workflow was terminated or ended with an unexpected status.
public async ValueTask WaitForCompletionAsync(CancellationToken cancellationToken = default)
{
OrchestrationMetadata metadata = await this._client.WaitForInstanceCompletionAsync(
this.RunId,
getInputsAndOutputs: true,
cancellation: cancellationToken).ConfigureAwait(false);
if (metadata.RuntimeStatus == OrchestrationRuntimeStatus.Completed)
{
return DurableStreamingWorkflowRun.ExtractResult(metadata.SerializedOutput);
}
if (metadata.RuntimeStatus == OrchestrationRuntimeStatus.Failed)
{
if (metadata.FailureDetails is not null)
{
// Use TaskFailedException to preserve full failure details including stack trace and inner exceptions
throw new TaskFailedException(
taskName: this.WorkflowName,
taskId: 0,
failureDetails: metadata.FailureDetails);
}
throw new InvalidOperationException(
$"Workflow '{this.WorkflowName}' (RunId: {this.RunId}) failed without failure details.");
}
throw new InvalidOperationException(
$"Workflow '{this.WorkflowName}' (RunId: {this.RunId}) ended with unexpected status: {metadata.RuntimeStatus}");
}
///
/// Waits for the workflow to complete and returns the string result.
///
/// A cancellation token to observe.
/// The string result of the workflow execution.
public ValueTask WaitForCompletionAsync(CancellationToken cancellationToken = default)
=> this.WaitForCompletionAsync(cancellationToken);
///
/// Gets all events that have been collected from the workflow.
///
public IEnumerable OutgoingEvents => this._eventSink;
///
/// Gets the number of events collected since the last access to .
///
public int NewEventCount => this._eventSink.Count - this._lastBookmark;
///
/// Gets all events collected since the last access to .
///
public IEnumerable NewEvents
{
get
{
if (this._lastBookmark >= this._eventSink.Count)
{
return [];
}
int currentBookmark = this._lastBookmark;
this._lastBookmark = this._eventSink.Count;
return this._eventSink.Skip(currentBookmark);
}
}
}