mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
193 lines
5.7 KiB
C#
193 lines
5.7 KiB
C#
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
using System.Collections.Frozen;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Agents.Workflows.Declarative.Extensions;
|
|
using Microsoft.Bot.ObjectModel;
|
|
using Microsoft.PowerFx;
|
|
using Microsoft.PowerFx.Types;
|
|
using Microsoft.Shared.Diagnostics;
|
|
|
|
namespace Microsoft.Agents.Workflows.Declarative.PowerFx;
|
|
|
|
/// <summary>
|
|
/// Contains all variables scopes for a workflow.
|
|
/// </summary>
|
|
internal sealed class WorkflowFormulaState
|
|
{
|
|
// ISSUE #488 - Update default scope for workflows to `Workflow` (instead of `Topic`)
|
|
public const string DefaultScopeName = VariableScopeNames.Topic;
|
|
|
|
private static readonly FrozenSet<string> s_mutableScopes =
|
|
[
|
|
VariableScopeNames.Topic,
|
|
VariableScopeNames.Global,
|
|
VariableScopeNames.System,
|
|
];
|
|
|
|
private readonly Dictionary<string, WorkflowScope> _scopes;
|
|
private int _isInitialized;
|
|
|
|
public RecalcEngine Engine { get; }
|
|
|
|
public WorkflowExpressionEngine Evaluator { get; }
|
|
|
|
public WorkflowFormulaState(RecalcEngine engine)
|
|
{
|
|
this.Engine = engine;
|
|
this.Evaluator = new WorkflowExpressionEngine(engine);
|
|
this._scopes = VariableScopeNames.AllScopes.ToDictionary(scopeName => scopeName, scopeName => new WorkflowScope(scopeName));
|
|
this.Bind();
|
|
}
|
|
|
|
public FormulaValue Get(PropertyPath variablePath) => this.Get(Throw.IfNull(variablePath.VariableName), variablePath.VariableScopeName);
|
|
|
|
public FormulaValue Get(string variableName, string? scopeName = null)
|
|
{
|
|
if (this.GetScope(scopeName).TryGetValue(variableName, out FormulaValue? value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
return FormulaValue.NewBlank();
|
|
}
|
|
|
|
public void ResetAll(string? scopeName = null)
|
|
{
|
|
if (scopeName is not null)
|
|
{
|
|
this.GetScope(scopeName).ResetAll();
|
|
}
|
|
else
|
|
{
|
|
foreach (string targetScope in VariableScopeNames.AllScopes)
|
|
{
|
|
this.GetScope(targetScope).ResetAll();
|
|
}
|
|
}
|
|
|
|
this.Bind();
|
|
}
|
|
|
|
public void Reset(PropertyPath variablePath) => this.Reset(Throw.IfNull(variablePath.VariableName), variablePath.VariableScopeName);
|
|
|
|
public void Reset(string variableName, string? scopeName = null)
|
|
{
|
|
this.GetScope(scopeName).Reset(variableName);
|
|
this.Bind();
|
|
}
|
|
|
|
public void Set(PropertyPath variablePath, FormulaValue value) => this.Set(Throw.IfNull(variablePath.VariableName), value, variablePath.VariableScopeName);
|
|
|
|
public void Set(string variableName, FormulaValue value, string? scopeName = null)
|
|
{
|
|
this.GetScope(scopeName)[variableName] = value;
|
|
this.Bind();
|
|
}
|
|
|
|
public bool SetInitialized() => Interlocked.CompareExchange(ref this._isInitialized, 1, 0) == 0;
|
|
|
|
public async ValueTask RestoreAsync(IWorkflowContext context, CancellationToken cancellationToken)
|
|
{
|
|
if (!this.SetInitialized())
|
|
{
|
|
return;
|
|
}
|
|
|
|
await Task.WhenAll(s_mutableScopes.Select(scopeName => ReadScopeAsync(scopeName))).ConfigureAwait(false);
|
|
|
|
async Task ReadScopeAsync(string scopeName)
|
|
{
|
|
HashSet<string> keys = await context.ReadStateKeysAsync(scopeName).ConfigureAwait(false);
|
|
foreach (string key in keys)
|
|
{
|
|
object? value = await context.ReadStateAsync<object>(key, scopeName).ConfigureAwait(false);
|
|
if (value is null or UnassignedValue)
|
|
{
|
|
value = FormulaValue.NewBlank();
|
|
}
|
|
|
|
this.Set(key, value.ToFormula(), scopeName);
|
|
}
|
|
|
|
this.Bind(scopeName);
|
|
}
|
|
}
|
|
|
|
public RecordValue BuildRecord(string scopeName) => this.GetScope(scopeName).BuildRecord();
|
|
|
|
public void Bind(string? targetScope = null)
|
|
{
|
|
if (targetScope is not null)
|
|
{
|
|
Bind(targetScope);
|
|
}
|
|
else
|
|
{
|
|
foreach (string scopeName in VariableScopeNames.AllScopes)
|
|
{
|
|
Bind(scopeName);
|
|
}
|
|
}
|
|
|
|
void Bind(string scopeName)
|
|
{
|
|
RecordValue scopeRecord = this.BuildRecord(scopeName);
|
|
this.Engine.DeleteFormula(scopeName);
|
|
this.Engine.UpdateVariable(scopeName, scopeRecord);
|
|
}
|
|
}
|
|
|
|
private WorkflowScope GetScope(string? scopeName)
|
|
{
|
|
scopeName ??= DefaultScopeName;
|
|
|
|
if (!VariableScopeNames.IsValidName(scopeName))
|
|
{
|
|
throw new DeclarativeActionException($"Invalid variable scope name: '{scopeName}'.");
|
|
}
|
|
|
|
return this._scopes[scopeName];
|
|
}
|
|
|
|
/// <summary>
|
|
/// The set of variables for a specific action scope.
|
|
/// </summary>
|
|
private sealed class WorkflowScope(string scopeName) : Dictionary<string, FormulaValue>
|
|
{
|
|
public string Name => scopeName;
|
|
|
|
public void ResetAll()
|
|
{
|
|
foreach (string variableName in this.Keys.ToArray())
|
|
{
|
|
this.Reset(variableName);
|
|
}
|
|
}
|
|
|
|
public void Reset(string variableName)
|
|
{
|
|
if (this.TryGetValue(variableName, out FormulaValue? value))
|
|
{
|
|
this[variableName] = value.Type.NewBlank();
|
|
}
|
|
}
|
|
|
|
public RecordValue BuildRecord()
|
|
{
|
|
return FormulaValue.NewRecordFromFields(GetFields());
|
|
|
|
IEnumerable<NamedValue> GetFields()
|
|
{
|
|
foreach (KeyValuePair<string, FormulaValue> kvp in this)
|
|
{
|
|
yield return new NamedValue(kvp.Key, kvp.Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|