diff --git a/dotnet/samples/DurableAgents/ConsoleApps/08_SingleWorkflow/Program.cs b/dotnet/samples/DurableAgents/ConsoleApps/08_SingleWorkflow/Program.cs index f871e507fc..e453bcd75a 100644 --- a/dotnet/samples/DurableAgents/ConsoleApps/08_SingleWorkflow/Program.cs +++ b/dotnet/samples/DurableAgents/ConsoleApps/08_SingleWorkflow/Program.cs @@ -50,8 +50,8 @@ IHost host = Host.CreateDefaultBuilder(args) await host.StartAsync(); -// Get the DurableExecutionEnvironment from DI - no need to manually resolve DurableTaskClient -DurableExecutionEnvironment durableExecution = host.Services.GetRequiredService(); +// Get the IWorkflowClient from DI - no need to manually resolve DurableTaskClient +IWorkflowClient workflowClient = host.Services.GetRequiredService(); Console.WriteLine("Durable Workflow Sample"); Console.WriteLine("Workflow: OrderLookup (2s) -> OrderCancel (5s) -> SendEmail (1s)"); @@ -75,7 +75,7 @@ while (true) try { - await StartNewWorkflowAsync(input, cancelOrder, durableExecution); + await StartNewWorkflowAsync(input, cancelOrder, workflowClient); } catch (Exception ex) { @@ -87,13 +87,13 @@ while (true) await host.StopAsync(); -// Start a new workflow using DurableExecutionEnvironment (no DurableTaskClient needed) -async Task StartNewWorkflowAsync(string orderId, Workflow workflow, DurableExecutionEnvironment execution) +// Start a new workflow using IWorkflowClient (no DurableTaskClient needed) +async Task StartNewWorkflowAsync(string orderId, Workflow workflow, IWorkflowClient client) { Console.WriteLine($"Starting workflow for order '{orderId}'..."); // RunAsync returns IRun, cast to DurableRun for durable-specific features like WaitForCompletionAsync - await using DurableRun run = (DurableRun)await execution.RunAsync(workflow, orderId); + await using DurableRun run = (DurableRun)await client.RunAsync(workflow, orderId); Console.WriteLine($"Instance ID: {run.InstanceId}"); try diff --git a/dotnet/samples/DurableAgents/ConsoleApps/09_Workflow_Concurrency/Program.cs b/dotnet/samples/DurableAgents/ConsoleApps/09_Workflow_Concurrency/Program.cs index 1a24a99330..fcf0a8abe0 100644 --- a/dotnet/samples/DurableAgents/ConsoleApps/09_Workflow_Concurrency/Program.cs +++ b/dotnet/samples/DurableAgents/ConsoleApps/09_Workflow_Concurrency/Program.cs @@ -77,8 +77,8 @@ IHost host = Host.CreateDefaultBuilder(args) await host.StartAsync(); -// Get the DurableExecutionEnvironment from DI - no need to manually resolve DurableTaskClient -DurableExecutionEnvironment durableExecution = host.Services.GetRequiredService(); +// Get the IWorkflowClient from DI - no need to manually resolve DurableTaskClient +IWorkflowClient workflowClient = host.Services.GetRequiredService(); // Console UI Console.ForegroundColor = ConsoleColor.Cyan; @@ -111,7 +111,7 @@ while (true) try { // Cast to DurableRun for durable-specific features like InstanceId and WaitForCompletionAsync - await using DurableRun run = (DurableRun)await durableExecution.RunAsync(workflow, input); + await using DurableRun run = (DurableRun)await workflowClient.RunAsync(workflow, input); Console.ForegroundColor = ConsoleColor.Gray; Console.WriteLine($"Instance: {run.InstanceId}"); Console.ResetColor(); diff --git a/dotnet/samples/DurableAgents/ConsoleApps/10_Workflow_HITL/Program.cs b/dotnet/samples/DurableAgents/ConsoleApps/10_Workflow_HITL/Program.cs index ca87971636..f0f58ead2b 100644 --- a/dotnet/samples/DurableAgents/ConsoleApps/10_Workflow_HITL/Program.cs +++ b/dotnet/samples/DurableAgents/ConsoleApps/10_Workflow_HITL/Program.cs @@ -43,8 +43,8 @@ IHost host = Host.CreateDefaultBuilder(args) await host.StartAsync(); -// Get the DurableExecutionEnvironment from DI - no need to manually resolve DurableTaskClient -DurableExecutionEnvironment durableExecution = host.Services.GetRequiredService(); +// Get the IWorkflowClient from DI - no need to manually resolve DurableTaskClient +IWorkflowClient workflowClient = host.Services.GetRequiredService(); // Start the workflow with an expense ID as input string expenseId = "EXP-2025-001"; @@ -52,7 +52,7 @@ Console.WriteLine($"Starting expense reimbursement workflow for expense: {expens // Start the workflow and get a streaming handle // Cast to DurableStreamingRun for durable-specific features like InstanceId and SendResponseAsync -await using DurableStreamingRun run = (DurableStreamingRun)await durableExecution.StreamAsync(expenseApproval, expenseId); +await using DurableStreamingRun run = (DurableStreamingRun)await workflowClient.StreamAsync(expenseApproval, expenseId); Console.WriteLine($"Workflow started with instance ID: {run.InstanceId}"); Console.WriteLine("Watching for workflow events...\n"); diff --git a/dotnet/samples/DurableAgents/ConsoleApps/11_WorkflowEvents/Program.cs b/dotnet/samples/DurableAgents/ConsoleApps/11_WorkflowEvents/Program.cs index c673800ca3..4f774b9b92 100644 --- a/dotnet/samples/DurableAgents/ConsoleApps/11_WorkflowEvents/Program.cs +++ b/dotnet/samples/DurableAgents/ConsoleApps/11_WorkflowEvents/Program.cs @@ -54,8 +54,8 @@ IHost host = Host.CreateDefaultBuilder(args) await host.StartAsync(); -// Get the DurableExecutionEnvironment from DI - no need to manually resolve DurableTaskClient -DurableExecutionEnvironment durableExecution = host.Services.GetRequiredService(); +// Get the IWorkflowClient from DI - no need to manually resolve DurableTaskClient +IWorkflowClient workflowClient = host.Services.GetRequiredService(); Console.WriteLine("Workflow Events Demo - Enter order ID (or 'exit'):"); @@ -70,7 +70,7 @@ while (true) try { - await RunWorkflowWithStreamingAsync(input, cancelOrder, durableExecution); + await RunWorkflowWithStreamingAsync(input, cancelOrder, workflowClient); } catch (Exception ex) { @@ -83,11 +83,11 @@ while (true) await host.StopAsync(); // Runs a workflow and streams events as they occur -async Task RunWorkflowWithStreamingAsync(string orderId, Workflow workflow, DurableExecutionEnvironment execution) +async Task RunWorkflowWithStreamingAsync(string orderId, Workflow workflow, IWorkflowClient client) { // StreamAsync starts the workflow and returns a handle for observing events // Cast to DurableStreamingRun for durable-specific features like InstanceId - await using DurableStreamingRun run = (DurableStreamingRun)await execution.StreamAsync(workflow, orderId); + await using DurableStreamingRun run = (DurableStreamingRun)await client.StreamAsync(workflow, orderId); Console.WriteLine($"Started: {run.InstanceId}"); // WatchStreamAsync yields events as they're emitted by executors diff --git a/dotnet/samples/DurableAgents/ConsoleApps/12_WorkflowLoop/Program.cs b/dotnet/samples/DurableAgents/ConsoleApps/12_WorkflowLoop/Program.cs index ad6f7677ea..3e0622d26a 100644 --- a/dotnet/samples/DurableAgents/ConsoleApps/12_WorkflowLoop/Program.cs +++ b/dotnet/samples/DurableAgents/ConsoleApps/12_WorkflowLoop/Program.cs @@ -61,8 +61,8 @@ IHost host = Host.CreateDefaultBuilder(args) await host.StartAsync(); -// Get the DurableExecutionEnvironment from DI - no need to manually resolve DurableTaskClient -DurableExecutionEnvironment durableExecution = host.Services.GetRequiredService(); +// Get the IWorkflowClient from DI - no need to manually resolve DurableTaskClient +IWorkflowClient workflowClient = host.Services.GetRequiredService(); Console.WriteLine("Workflow Events Demo - Enter input for slogan generation (or 'exit'):"); @@ -77,7 +77,7 @@ while (true) try { - await RunWorkflowWithStreamingAsync(input, workflow, durableExecution); + await RunWorkflowWithStreamingAsync(input, workflow, workflowClient); } catch (Exception ex) { @@ -90,11 +90,11 @@ while (true) await host.StopAsync(); // Runs a workflow and streams events as they occur -async Task RunWorkflowWithStreamingAsync(string orderId, Workflow workflow, DurableExecutionEnvironment execution) +async Task RunWorkflowWithStreamingAsync(string orderId, Workflow workflow, IWorkflowClient client) { // StreamAsync starts the workflow and returns a handle for observing events // Cast to DurableStreamingRun for durable-specific features like InstanceId - await using DurableStreamingRun run = (DurableStreamingRun)await execution.StreamAsync(workflow, orderId); + await using DurableStreamingRun run = (DurableStreamingRun)await client.StreamAsync(workflow, orderId); Console.WriteLine($"Started: {run.InstanceId}"); // WatchStreamAsync yields events as they're emitted by executors diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableExecutionEnvironment.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableExecutionEnvironment.cs deleted file mode 100644 index 26b68d6b28..0000000000 --- a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableExecutionEnvironment.cs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.Agents.AI.Workflows; -using Microsoft.DurableTask.Client; - -namespace Microsoft.Agents.AI.DurableTask; - -/// -/// Provides a DI-friendly execution environment for running workflows as durable orchestrations. -/// -/// -/// This class wraps the and provides methods to run workflows -/// without requiring the client to be passed explicitly. Register this class in DI using -/// . -/// -public sealed class DurableExecutionEnvironment -{ - private readonly DurableTaskClient _client; - - /// - /// Initializes a new instance of the class. - /// - /// The durable task client for orchestration operations. - public DurableExecutionEnvironment(DurableTaskClient client) - { - this._client = client ?? throw new ArgumentNullException(nameof(client)); - } - - /// - /// Runs a workflow as a durable orchestration and returns a handle to monitor its execution. - /// - /// The type of the input to the workflow. - /// The workflow to execute. - /// The input to pass to the workflow's starting executor. - /// Optional instance ID for the orchestration. If not provided, a new ID will be generated. - /// A cancellation token to observe. - /// An that can be used to monitor the workflow execution. - /// Thrown when workflow is null. - /// Thrown when the workflow does not have a valid name. - public ValueTask RunAsync( - Workflow workflow, - TInput input, - string? instanceId = null, - CancellationToken cancellationToken = default) - where TInput : notnull - => DurableWorkflow.RunAsync(workflow, input, this._client, instanceId, cancellationToken); - - /// - /// Runs a workflow as a durable orchestration with string input. - /// - /// The workflow to execute. - /// The string input to pass to the workflow. - /// Optional instance ID for the orchestration. - /// A cancellation token to observe. - /// An that can be used to monitor the workflow execution. - public ValueTask RunAsync( - Workflow workflow, - string input, - string? instanceId = null, - CancellationToken cancellationToken = default) - => DurableWorkflow.RunAsync(workflow, input, this._client, instanceId, cancellationToken); - - /// - /// Starts a workflow as a durable orchestration and returns a streaming handle to watch events. - /// - /// The type of the input to the workflow. - /// The workflow to execute. - /// The input to pass to the workflow's starting executor. - /// Optional instance ID for the orchestration. If not provided, a new ID will be generated. - /// A cancellation token to observe. - /// An that can be used to stream workflow events. - /// Thrown when workflow is null. - /// Thrown when the workflow does not have a valid name. - public ValueTask StreamAsync( - Workflow workflow, - TInput input, - string? instanceId = null, - CancellationToken cancellationToken = default) - where TInput : notnull - => DurableWorkflow.StreamAsync(workflow, input, this._client, instanceId, cancellationToken); - - /// - /// Starts a workflow as a durable orchestration with string input and returns a streaming handle. - /// - /// The workflow to execute. - /// The string input to pass to the workflow. - /// Optional instance ID for the orchestration. - /// A cancellation token to observe. - /// An that can be used to stream workflow events. - public ValueTask StreamAsync( - Workflow workflow, - string input, - string? instanceId = null, - CancellationToken cancellationToken = default) - => DurableWorkflow.StreamAsync(workflow, input, this._client, instanceId, cancellationToken); - - /// - /// Attaches to an existing workflow orchestration instance. - /// - /// The instance ID of the orchestration to attach to. - /// The name of the workflow being executed. - /// An that can be used to monitor the workflow execution. - public IRun Attach(string instanceId, string workflowName) - => DurableWorkflow.Attach(instanceId, workflowName, this._client); - - /// - /// Attaches to an existing workflow orchestration instance for streaming. - /// - /// The instance ID of the orchestration to attach to. - /// The workflow being executed. - /// An that can be used to stream workflow events. - public IStreamingRun AttachStream(string instanceId, Workflow workflow) - => DurableWorkflow.AttachStream(instanceId, workflow, this._client); -} diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowClient.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowClient.cs new file mode 100644 index 0000000000..75feeb39e2 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowClient.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Agents.AI.Workflows; +using Microsoft.DurableTask.Client; + +namespace Microsoft.Agents.AI.DurableTask; + +/// +/// Provides a durable task-based implementation of for running +/// workflows as durable orchestrations. +/// +/// +/// This class wraps the and provides methods to run workflows +/// without requiring the client to be passed explicitly. Register this class in DI using +/// . +/// +public sealed class DurableWorkflowClient : IWorkflowClient +{ + private readonly DurableTaskClient _client; + + /// + /// Initializes a new instance of the class. + /// + /// The durable task client for orchestration operations. + public DurableWorkflowClient(DurableTaskClient client) + { + this._client = client ?? throw new ArgumentNullException(nameof(client)); + } + + /// + public ValueTask RunAsync( + Workflow workflow, + TInput input, + string? runId = null, + CancellationToken cancellationToken = default) + where TInput : notnull + => DurableWorkflow.RunAsync(workflow, input, this._client, runId, cancellationToken); + + /// + public ValueTask RunAsync( + Workflow workflow, + string input, + string? runId = null, + CancellationToken cancellationToken = default) + => DurableWorkflow.RunAsync(workflow, input, this._client, runId, cancellationToken); + + /// + public ValueTask StreamAsync( + Workflow workflow, + TInput input, + string? runId = null, + CancellationToken cancellationToken = default) + where TInput : notnull + => DurableWorkflow.StreamAsync(workflow, input, this._client, runId, cancellationToken); + + /// + public ValueTask StreamAsync( + Workflow workflow, + string input, + string? runId = null, + CancellationToken cancellationToken = default) + => DurableWorkflow.StreamAsync(workflow, input, this._client, runId, cancellationToken); + + /// + /// Attaches to an existing workflow orchestration instance. + /// + /// + /// This is a durable-specific method not available on . + /// + /// The instance ID of the orchestration to attach to. + /// The name of the workflow being executed. + /// An that can be used to monitor the workflow execution. + public IRun Attach(string instanceId, string workflowName) + => DurableWorkflow.Attach(instanceId, workflowName, this._client); + + /// + /// Attaches to an existing workflow orchestration instance for streaming. + /// + /// + /// This is a durable-specific method not available on . + /// + /// The instance ID of the orchestration to attach to. + /// The workflow being executed. + /// An that can be used to stream workflow events. + public IStreamingRun AttachStream(string instanceId, Workflow workflow) + => DurableWorkflow.AttachStream(instanceId, workflow, this._client); +} diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowServiceCollectionExtensions.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowServiceCollectionExtensions.cs index 36da0050f8..f083e2b42d 100644 --- a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowServiceCollectionExtensions.cs +++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableWorkflowServiceCollectionExtensions.cs @@ -113,8 +113,10 @@ public static class DurableWorkflowServiceCollectionExtensions services.AddDurableTaskClient(clientBuilder); } - // Register the DurableExecutionEnvironment for DI-friendly workflow execution - services.TryAddSingleton(); + // Register the DurableWorkflowClient for DI-friendly workflow execution + // Register both the concrete type and the interface + services.TryAddSingleton(); + services.TryAddSingleton(sp => sp.GetRequiredService()); return services; } diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/IWorkflowClient.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/IWorkflowClient.cs new file mode 100644 index 0000000000..d2c732b3d9 --- /dev/null +++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/IWorkflowClient.cs @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Agents.AI.Workflows; + +namespace Microsoft.Agents.AI.DurableTask; + +/// +/// Defines a client for running and managing workflow executions. +/// +/// +/// This interface provides a unified API for running workflows across different execution +/// environments. Implementations handle the underlying infrastructure details, allowing +/// callers to work with workflows without knowledge of the specific execution backend. +/// +public interface IWorkflowClient +{ + /// + /// Runs a workflow and returns a handle to monitor its execution. + /// + /// The type of the input to the workflow. + /// The workflow to execute. + /// The input to pass to the workflow's starting executor. + /// Optional identifier for the run. If not provided, a new ID will be generated. + /// A cancellation token to observe. + /// An that can be used to monitor the workflow execution. + ValueTask RunAsync( + Workflow workflow, + TInput input, + string? runId = null, + CancellationToken cancellationToken = default) + where TInput : notnull; + + /// + /// Runs a workflow with string input and returns a handle to monitor its execution. + /// + /// The workflow to execute. + /// The string input to pass to the workflow. + /// Optional identifier for the run. If not provided, a new ID will be generated. + /// A cancellation token to observe. + /// An that can be used to monitor the workflow execution. + ValueTask RunAsync( + Workflow workflow, + string input, + string? runId = null, + CancellationToken cancellationToken = default); + + /// + /// Starts a workflow and returns a streaming handle to watch events. + /// + /// The type of the input to the workflow. + /// The workflow to execute. + /// The input to pass to the workflow's starting executor. + /// Optional identifier for the run. If not provided, a new ID will be generated. + /// A cancellation token to observe. + /// An that can be used to stream workflow events. + ValueTask StreamAsync( + Workflow workflow, + TInput input, + string? runId = null, + CancellationToken cancellationToken = default) + where TInput : notnull; + + /// + /// Starts a workflow with string input and returns a streaming handle to watch events. + /// + /// The workflow to execute. + /// The string input to pass to the workflow. + /// Optional identifier for the run. If not provided, a new ID will be generated. + /// A cancellation token to observe. + /// An that can be used to stream workflow events. + ValueTask StreamAsync( + Workflow workflow, + string input, + string? runId = null, + CancellationToken cancellationToken = default); +}