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