// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.AI.Workflows.Execution; namespace Microsoft.Agents.AI.Workflows.InProc; /// /// Provides an in-process implementation of the workflow execution environment for running, streaming, and /// checkpointing workflows within the current application domain. /// public sealed class InProcessExecutionEnvironment : IWorkflowExecutionEnvironment { internal InProcessExecutionEnvironment(ExecutionMode mode, bool enableConcurrentRuns = false, CheckpointManager? checkpointManager = null) { this.ExecutionMode = mode; this.EnableConcurrentRuns = enableConcurrentRuns; this.CheckpointManager = checkpointManager; } /// /// Configure a new execution environment, inheriting configuration for the current one with the specified /// for use in checkpointing. /// /// The CheckpointManager to use for checkpointing. /// /// A new InProcess configured for checkpointing, inheriting configuration from the current /// environment. /// public InProcessExecutionEnvironment WithCheckpointing(CheckpointManager? checkpointManager) { return new(this.ExecutionMode, this.EnableConcurrentRuns, checkpointManager); } internal ExecutionMode ExecutionMode { get; } internal bool EnableConcurrentRuns { get; } internal CheckpointManager? CheckpointManager { get; } /// public bool IsCheckpointingEnabled => this.CheckpointManager != null; internal ValueTask BeginRunAsync(Workflow workflow, string? sessionId, IEnumerable knownValidInputTypes, CancellationToken cancellationToken) { InProcessRunner runner = InProcessRunner.CreateTopLevelRunner(workflow, this.CheckpointManager, sessionId, this.EnableConcurrentRuns, knownValidInputTypes); return runner.BeginStreamAsync(this.ExecutionMode, cancellationToken); } internal ValueTask ResumeRunAsync(Workflow workflow, CheckpointInfo fromCheckpoint, IEnumerable knownValidInputTypes, CancellationToken cancellationToken = default) => this.ResumeRunAsync(workflow, fromCheckpoint, knownValidInputTypes, republishPendingEvents: true, cancellationToken); internal ValueTask ResumeRunAsync(Workflow workflow, CheckpointInfo fromCheckpoint, IEnumerable knownValidInputTypes, bool republishPendingEvents, CancellationToken cancellationToken = default) { InProcessRunner runner = InProcessRunner.CreateTopLevelRunner(workflow, this.CheckpointManager, fromCheckpoint.SessionId, this.EnableConcurrentRuns, knownValidInputTypes); return runner.ResumeStreamAsync(this.ExecutionMode, fromCheckpoint, republishPendingEvents, cancellationToken); } /// public async ValueTask OpenStreamingAsync( Workflow workflow, string? sessionId = null, CancellationToken cancellationToken = default) { AsyncRunHandle runHandle = await this.BeginRunAsync(workflow, sessionId, [], cancellationToken) .ConfigureAwait(false); return new(runHandle); } /// public async ValueTask RunStreamingAsync( Workflow workflow, TInput input, string? sessionId = null, CancellationToken cancellationToken = default) where TInput : notnull { AsyncRunHandle runHandle = await this.BeginRunAsync(workflow, sessionId, [], cancellationToken) .ConfigureAwait(false); return await runHandle.EnqueueAndStreamAsync(input, cancellationToken).ConfigureAwait(false); } [MemberNotNull(nameof(CheckpointManager))] private void VerifyCheckpointingConfigured() { if (this.CheckpointManager == null) { throw new InvalidOperationException("Checkpointing is not configured for this execution environment. Please use the InProcessExecutionEnvironment.WithCheckpointing method to attach a CheckpointManager."); } } /// public async ValueTask ResumeStreamingAsync( Workflow workflow, CheckpointInfo fromCheckpoint, CancellationToken cancellationToken = default) { this.VerifyCheckpointingConfigured(); AsyncRunHandle runHandle = await this.ResumeRunAsync(workflow, fromCheckpoint, [], cancellationToken) .ConfigureAwait(false); return new(runHandle); } /// /// Resumes a streaming workflow run from a checkpoint with control over whether /// pending request events are republished through the event stream. /// /// The workflow to resume. /// The checkpoint to resume from. /// /// When , any pending request events are republished through the event /// stream after subscribing. When , the caller is responsible for /// handling pending requests (e.g., already sends responses). /// /// Cancellation token. internal async ValueTask ResumeStreamingInternalAsync( Workflow workflow, CheckpointInfo fromCheckpoint, bool republishPendingEvents, CancellationToken cancellationToken = default) { this.VerifyCheckpointingConfigured(); AsyncRunHandle runHandle = await this.ResumeRunAsync(workflow, fromCheckpoint, [], republishPendingEvents, cancellationToken) .ConfigureAwait(false); return new(runHandle); } private async ValueTask BeginRunHandlingChatProtocolAsync(Workflow workflow, TInput input, string? sessionId = null, CancellationToken cancellationToken = default) { ProtocolDescriptor descriptor = await workflow.DescribeProtocolAsync(cancellationToken).ConfigureAwait(false); AsyncRunHandle runHandle = await this.BeginRunAsync(workflow, sessionId, descriptor.Accepts, cancellationToken) .ConfigureAwait(false); await runHandle.EnqueueMessageAsync(input, cancellationToken).ConfigureAwait(false); if (descriptor.IsChatProtocol() && input is not TurnToken) { await runHandle.EnqueueMessageAsync(new TurnToken(emitEvents: true), cancellationToken).ConfigureAwait(false); } return runHandle; } /// public async ValueTask RunAsync( Workflow workflow, TInput input, string? sessionId = null, CancellationToken cancellationToken = default) where TInput : notnull { AsyncRunHandle runHandle = await this.BeginRunHandlingChatProtocolAsync( workflow, input, sessionId, cancellationToken) .ConfigureAwait(false); Run run = new(runHandle); await run.RunToNextHaltAsync(cancellationToken).ConfigureAwait(false); return run; } /// public async ValueTask ResumeAsync( Workflow workflow, CheckpointInfo fromCheckpoint, CancellationToken cancellationToken = default) { this.VerifyCheckpointingConfigured(); AsyncRunHandle runHandle = await this.ResumeRunAsync(workflow, fromCheckpoint, [], cancellationToken) .ConfigureAwait(false); return new(runHandle); } }