// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Agents.AI.Workflows.Execution; namespace Microsoft.Agents.AI.Workflows.UnitTests; public class TestRunContext : IRunnerContext { private sealed class BoundContext( string executorId, TestRunContext runnerContext, IReadOnlyDictionary? traceContext) : IWorkflowContext { public ValueTask AddEventAsync(WorkflowEvent workflowEvent, CancellationToken cancellationToken = default) => runnerContext.AddEventAsync(workflowEvent, cancellationToken); public ValueTask YieldOutputAsync(object output, CancellationToken cancellationToken = default) => this.AddEventAsync(new WorkflowOutputEvent(output, executorId), cancellationToken); public ValueTask RequestHaltAsync() => this.AddEventAsync(new RequestHaltEvent()); public ValueTask QueueClearScopeAsync(string? scopeName = null, CancellationToken cancellationToken = default) => default; public ValueTask QueueStateUpdateAsync(string key, T? value, string? scopeName = null, CancellationToken cancellationToken = default) => default; public ValueTask ReadStateAsync(string key, string? scopeName = null, CancellationToken cancellationToken = default) => new(default(T?)); public ValueTask> ReadStateKeysAsync(string? scopeName = null, CancellationToken cancellationToken = default) => new([]); public ValueTask SendMessageAsync(object message, string? targetId = null, CancellationToken cancellationToken = default) => runnerContext.SendMessageAsync(executorId, message, targetId, cancellationToken); public ValueTask ReadOrInitStateAsync(string key, Func initialStateFactory, string? scopeName = null, CancellationToken cancellationToken = default) { return new(initialStateFactory()); } public IReadOnlyDictionary? TraceContext => traceContext; public bool ConcurrentRunsEnabled => runnerContext.ConcurrentRunsEnabled; } public List Events { get; } = []; public ValueTask AddEventAsync(WorkflowEvent workflowEvent, CancellationToken cancellationToken) { this.Events.Add(workflowEvent); return default; } public IWorkflowContext Bind(string executorId, Dictionary? traceContext = null) => new BoundContext(executorId, this, traceContext); public List ExternalRequests { get; } = []; public ValueTask PostAsync(ExternalRequest request) { this.ExternalRequests.Add(request); return default; } internal Dictionary> QueuedMessages { get; } = []; public ValueTask SendMessageAsync(string sourceId, object message, string? targetId = null, CancellationToken cancellationToken = default) { if (!this.QueuedMessages.TryGetValue(sourceId, out List? deliveryQueue)) { this.QueuedMessages[sourceId] = deliveryQueue = []; } deliveryQueue.Add(new(message, sourceId, targetId: targetId)); return default; } ValueTask IRunnerContext.AdvanceAsync(CancellationToken cancellationToken) => throw new NotImplementedException(); public Dictionary Executors { get; set; } = []; public string StartingExecutorId { get; set; } = string.Empty; public bool WithCheckpointing => throw new NotSupportedException(); public bool ConcurrentRunsEnabled => throw new NotSupportedException(); ValueTask IRunnerContext.EnsureExecutorAsync(string executorId, IStepTracer? tracer, CancellationToken cancellationToken) => new(this.Executors[executorId]); public ValueTask> GetStartingExecutorInputTypesAsync(CancellationToken cancellationToken = default) { if (this.Executors.TryGetValue(this.StartingExecutorId, out Executor? executor)) { return new(executor.InputTypes); } throw new InvalidOperationException($"No executor with ID '{this.StartingExecutorId}' is registered in this context."); } public ValueTask ForwardWorkflowEventAsync(WorkflowEvent workflowEvent, CancellationToken cancellationToken = default) => this.AddEventAsync(workflowEvent, cancellationToken); public ValueTask SendMessageAsync(string senderId, [System.Diagnostics.CodeAnalysis.DisallowNull] TMessage message, CancellationToken cancellationToken = default) => this.SendMessageAsync(senderId, message, cancellationToken); ValueTask ISuperStepJoinContext.AttachSuperstepAsync(ISuperStepRunner superStepRunner, CancellationToken cancellationToken) => new(string.Empty); ValueTask ISuperStepJoinContext.DetachSuperstepAsync(string joinId) => new(false); }