.NET: Rename workflows projects (#975)

* Renaming Microsoft.Agent.Workflows to Microsoft.Agents.AI.Workflows

* Removing local settings.

* Removing remining old files from merge.
This commit is contained in:
Ben Thomas
2025-09-29 18:30:45 +00:00
committed by GitHub
parent aaf340096e
commit 647db9635a
340 changed files with 519 additions and 519 deletions
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
internal static class AgentProviderExtensions
{
public static async IAsyncEnumerable<AgentRunResponseUpdate> InvokeAgentAsync(
this WorkflowAgentProvider agentProvider,
string executorId,
IWorkflowContext context,
string agentName,
string? conversationId,
bool autoSend,
string? additionalInstructions = null,
IEnumerable<ChatMessage>? inputMessages = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
AIAgent agent = await agentProvider.GetAgentAsync(agentName, cancellationToken).ConfigureAwait(false);
ChatClientAgentRunOptions options =
new(
new ChatOptions()
{
Instructions = additionalInstructions,
});
AgentThread agentThread = conversationId is not null && agent is ChatClientAgent chatClientAgent ? chatClientAgent.GetNewThread(conversationId) : agent.GetNewThread();
IAsyncEnumerable<AgentRunResponseUpdate> agentUpdates =
inputMessages is not null ?
agent.RunStreamingAsync([.. inputMessages], agentThread, options, cancellationToken) :
agent.RunStreamingAsync(agentThread, options, cancellationToken);
await foreach (AgentRunResponseUpdate update in agentUpdates.ConfigureAwait(false))
{
await AssignConversationIdAsync(((ChatResponseUpdate?)update.RawRepresentation)?.ConversationId).ConfigureAwait(false);
if (autoSend)
{
await context.AddEventAsync(new AgentRunUpdateEvent(executorId, update)).ConfigureAwait(false);
}
yield return update;
}
async ValueTask AssignConversationIdAsync(string? assignValue)
{
if (assignValue is not null && conversationId is null)
{
conversationId = assignValue;
await context.QueueConversationUpdateAsync(conversationId).ConfigureAwait(false);
}
}
}
}
@@ -0,0 +1,20 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Bot.ObjectModel;
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
internal static class BotElementExtensions
{
public static string? GetParentId(this BotElement element) => element.Parent?.GetId();
public static string GetId(this BotElement element) =>
element switch
{
DialogAction action => action.Id.Value,
ConditionItem conditionItem => conditionItem.Id ?? throw new DeclarativeModelException($"Undefined identifier for {nameof(ConditionItem)} that is member of {conditionItem.GetParentId() ?? "(root)"}."),
OnActivity activity => activity.Id.Value,
SystemTrigger trigger => trigger.Id.Value,
_ => throw new DeclarativeModelException($"Unknown identify for element type: {element.GetType().Name}"),
};
}
@@ -0,0 +1,237 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx.Functions;
using Microsoft.Bot.ObjectModel;
using Microsoft.Extensions.AI;
using Microsoft.PowerFx.Types;
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
internal static class ChatMessageExtensions
{
public static RecordValue ToRecord(this ChatMessage message) =>
FormulaValue.NewRecordFromFields(message.GetMessageFields());
public static TableValue ToTable(this IEnumerable<ChatMessage> messages) =>
FormulaValue.NewTable(TypeSchema.Message.MessageRecordType, messages.Select(message => message.ToRecord()));
public static IEnumerable<ChatMessage>? ToChatMessages(this DataValue? messages)
{
if (messages is null || messages is BlankDataValue)
{
return null;
}
if (messages is TableDataValue table)
{
return table.ToChatMessages();
}
if (messages is RecordDataValue record)
{
return [record.ToChatMessage()];
}
if (messages is StringDataValue text)
{
return [text.ToChatMessage()];
}
return null;
}
public static IEnumerable<ChatMessage> ToChatMessages(this TableDataValue messages)
{
foreach (DataValue message in messages.Values)
{
if (message is RecordDataValue record)
{
if (record.Properties.Count == 1 && record.Properties.TryGetValue("Value", out DataValue? singleColumn))
{
record = singleColumn as RecordDataValue ?? record;
}
ChatMessage? convertedMessage = record.ToChatMessage();
if (convertedMessage is not null)
{
yield return convertedMessage;
}
}
else if (message is StringDataValue text)
{
yield return ToChatMessage(text);
}
}
}
public static ChatMessage? ToChatMessage(this DataValue message)
{
if (message is RecordDataValue record)
{
return record.ToChatMessage();
}
if (message is StringDataValue text)
{
return text.ToChatMessage();
}
if (message is BlankDataValue)
{
return null;
}
throw new DeclarativeActionException($"Unable to convert {message.GetDataType()} to {nameof(ChatMessage)}.");
}
public static ChatMessage ToChatMessage(this RecordDataValue message) =>
new(message.GetRole(), [.. message.GetContent()])
{
AdditionalProperties = message.GetProperty<RecordDataValue>("metadata").ToMetadata()
};
public static ChatMessage ToChatMessage(this StringDataValue message) => new(ChatRole.User, message.Value);
public static AdditionalPropertiesDictionary? ToMetadata(this RecordDataValue? metadata)
{
if (metadata is null)
{
return null;
}
AdditionalPropertiesDictionary properties = [];
foreach (KeyValuePair<string, DataValue> property in metadata.Properties)
{
properties[property.Key] = property.Value.ToObject();
}
return properties;
}
public static ChatRole ToChatRole(this AgentMessageRole role) =>
role switch
{
AgentMessageRole.Agent => ChatRole.Assistant,
AgentMessageRole.User => ChatRole.User,
_ => ChatRole.User
};
public static ChatRole ToChatRole(this AgentMessageRole? role) => role?.ToChatRole() ?? ChatRole.User;
public static AIContent? ToContent(this AgentMessageContentType contentType, string? contentValue)
{
if (string.IsNullOrEmpty(contentValue))
{
return null;
}
return
contentType switch
{
AgentMessageContentType.ImageUrl => new UriContent(contentValue, "image/*"),
AgentMessageContentType.ImageFile => new HostedFileContent(contentValue),
_ => new TextContent(contentValue)
};
}
private static ChatRole GetRole(this RecordDataValue message)
{
StringDataValue? roleValue = message.GetProperty<StringDataValue>(TypeSchema.Message.Fields.Role);
if (roleValue is null || string.IsNullOrWhiteSpace(roleValue.Value))
{
return ChatRole.User;
}
AgentMessageRole? role = null;
if (Enum.TryParse(roleValue.Value, out AgentMessageRole parsedRole))
{
role = parsedRole;
}
return role.ToChatRole();
}
private static IEnumerable<AIContent> GetContent(this RecordDataValue message)
{
TableDataValue? content = message.GetProperty<TableDataValue>(TypeSchema.Message.Fields.Content);
if (content is not null)
{
foreach (RecordDataValue contentItem in content.Values)
{
StringDataValue? contentValue = contentItem?.GetProperty<StringDataValue>(TypeSchema.Message.Fields.ContentValue);
if (contentValue is null || string.IsNullOrWhiteSpace(contentValue.Value))
{
continue;
}
yield return
contentItem?.GetProperty<StringDataValue>(TypeSchema.Message.Fields.ContentType)?.Value switch
{
TypeSchema.Message.ContentTypes.ImageUrl => new UriContent(contentValue.Value, "image/*"),
TypeSchema.Message.ContentTypes.ImageFile => new HostedFileContent(contentValue.Value),
_ => new TextContent(contentValue.Value)
};
}
}
}
private static TValue? GetProperty<TValue>(this RecordDataValue record, string name)
where TValue : DataValue
{
if (record.Properties.TryGetValue(name, out DataValue? value) && value is TValue dataValue)
{
return dataValue;
}
return null;
}
private static IEnumerable<NamedValue> GetMessageFields(this ChatMessage message)
{
yield return new NamedValue(TypeSchema.Discriminator, nameof(ChatMessage).ToFormula());
yield return new NamedValue(TypeSchema.Message.Fields.Id, message.MessageId.ToFormula());
yield return new NamedValue(TypeSchema.Message.Fields.Role, message.Role.Value.ToFormula());
yield return new NamedValue(TypeSchema.Message.Fields.Author, message.AuthorName.ToFormula());
yield return new NamedValue(TypeSchema.Message.Fields.Content, FormulaValue.NewTable(TypeSchema.Message.ContentRecordType, message.GetContentRecords()));
yield return new NamedValue(TypeSchema.Message.Fields.Text, message.Text.ToFormula());
yield return new NamedValue(TypeSchema.Message.Fields.Metadata, message.AdditionalProperties.ToRecord());
}
private static IEnumerable<RecordValue> GetContentRecords(this ChatMessage message) =>
message.Contents.Select(content => FormulaValue.NewRecordFromFields(content.GetContentFields()));
private static IEnumerable<NamedValue> GetContentFields(this AIContent content)
{
return
content switch
{
UriContent uriContent => CreateContentRecord(TypeSchema.Message.ContentTypes.ImageUrl, uriContent.Uri.ToString()),
HostedFileContent fileContent => CreateContentRecord(TypeSchema.Message.ContentTypes.ImageFile, fileContent.FileId),
TextContent textContent => CreateContentRecord(TypeSchema.Message.ContentTypes.Text, textContent.Text),
_ => []
};
static IEnumerable<NamedValue> CreateContentRecord(string type, string value)
{
yield return new NamedValue(TypeSchema.Message.Fields.ContentType, type.ToFormula());
yield return new NamedValue(TypeSchema.Message.Fields.ContentValue, value.ToFormula());
}
}
private static RecordValue ToRecord(this AdditionalPropertiesDictionary? value)
{
return FormulaValue.NewRecordFromFields(GetFields());
IEnumerable<NamedValue> GetFields()
{
if (value is not null)
{
foreach (string key in value.Keys)
{
yield return new NamedValue(key, value[key].ToFormula());
}
}
}
}
}
@@ -0,0 +1,110 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using Microsoft.Bot.ObjectModel;
using Microsoft.PowerFx.Types;
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
internal static class DataValueExtensions
{
public static FormulaValue ToFormula(this DataValue? value) =>
value switch
{
null => FormulaValue.NewBlank(),
BlankDataValue => FormulaValue.NewBlank(),
BooleanDataValue boolValue => FormulaValue.New(boolValue.Value),
NumberDataValue numberValue => FormulaValue.New(numberValue.Value),
FloatDataValue floatValue => FormulaValue.New(floatValue.Value),
StringDataValue stringValue => FormulaValue.New(stringValue.Value),
DateTimeDataValue dateTimeValue => FormulaValue.New(dateTimeValue.Value.DateTime),
DateDataValue dateValue => FormulaValue.NewDateOnly(dateValue.Value),
TimeDataValue timeValue => FormulaValue.New(timeValue.Value),
TableDataValue tableValue =>
FormulaValue.NewTable(
tableValue.Values.FirstOrDefault()?.ParseRecordType() ?? RecordType.Empty(),
tableValue.Values.Select(value => value.ToRecordValue())),
RecordDataValue recordValue => recordValue.ToRecordValue(),
OptionDataValue optionValue => FormulaValue.New(optionValue.Value.Value),
_ => FormulaValue.NewError(new Microsoft.PowerFx.ExpressionError { Message = $"Unknown literal type: {value.GetType().Name}" }),
};
public static FormulaType ToFormulaType(this DataValue? value) => value?.GetDataType().ToFormulaType() ?? FormulaType.Blank;
public static FormulaType ToFormulaType(this DataType? type) =>
type switch
{
null => FormulaType.Blank,
BooleanDataType => FormulaType.Boolean,
NumberDataType => FormulaType.Decimal,
FloatDataType => FormulaType.Number,
StringDataType => FormulaType.String,
DateTimeDataType => FormulaType.DateTime,
DateDataType => FormulaType.Date,
TimeDataType => FormulaType.Time,
ColorDataType => FormulaType.Color,
GuidDataType => FormulaType.Guid,
FileDataType => FormulaType.Blob,
RecordDataType => RecordType.Empty(),
TableDataType => TableType.Empty(),
OptionSetDataType => FormulaType.String,
AnyType => FormulaType.UntypedObject,
_ => FormulaType.Unknown,
};
public static object? ToObject(this DataValue? value) =>
value switch
{
null => null,
BlankDataValue => null,
BooleanDataValue boolValue => boolValue.Value,
NumberDataValue numberValue => numberValue.Value,
FloatDataValue floatValue => floatValue.Value,
StringDataValue stringValue => stringValue.Value,
DateTimeDataValue dateTimeValue => dateTimeValue.Value.DateTime,
DateDataValue dateValue => dateValue.Value,
TimeDataValue timeValue => timeValue.Value,
TableDataValue tableValue => tableValue.Values.Select(value => value.ToDictionary()).ToArray(),
RecordDataValue recordValue => recordValue.ToDictionary(),
OptionDataValue optionValue => optionValue.Value.Value,
_ => throw new DeclarativeModelException($"Unsupported {nameof(DataValue)} type: {value.GetType().Name}"),
};
public static FormulaValue NewBlank(this DataType? type) => FormulaValue.NewBlank(type?.ToFormulaType() ?? FormulaType.Blank);
public static RecordValue ToRecordValue(this RecordDataValue recordDataValue) =>
FormulaValue.NewRecordFromFields(
recordDataValue.Properties.Select(
property => new NamedValue(property.Key, property.Value.ToFormula())));
public static RecordType ToRecordType(this RecordDataType record)
{
RecordType recordType = RecordType.Empty();
foreach (KeyValuePair<string, PropertyInfo> property in record.Properties)
{
recordType = recordType.Add(property.Key, property.Value.Type.ToFormulaType());
}
return recordType;
}
private static RecordType ParseRecordType(this RecordDataValue record)
{
RecordType recordType = RecordType.Empty();
foreach (KeyValuePair<string, DataValue> property in record.Properties)
{
recordType = recordType.Add(property.Key, property.Value.ToFormulaType());
}
return recordType;
}
private static Dictionary<string, object?> ToDictionary(this RecordDataValue record)
{
Dictionary<string, object?> result = [];
foreach (KeyValuePair<string, DataValue> property in record.Properties)
{
result[property.Key] = property.Value.ToObject();
}
return result;
}
}
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.PowerFx;
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
internal static class DeclarativeWorkflowOptionsExtensions
{
private const int DefaultMaximumExpressionLength = 10000;
public static RecalcEngine CreateRecalcEngine(this DeclarativeWorkflowOptions? context) =>
RecalcEngineFactory.Create(context?.MaximumExpressionLength ?? DefaultMaximumExpressionLength, context?.MaximumCallDepth);
}
@@ -0,0 +1,27 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Linq;
using Microsoft.Bot.ObjectModel;
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
internal static class DialogBaseExtensions
{
public static TDialog WrapWithBot<TDialog>(this TDialog dialog) where TDialog : DialogBase
{
BotDefinition bot
= new BotDefinition.Builder
{
Components =
{
new DialogComponent.Builder
{
SchemaName = dialog.HasSchemaName ? dialog.SchemaName : "default-schema",
Dialog = dialog.ToBuilder(),
}
}
}.Build();
return bot.Descendants().OfType<TDialog>().First();
}
}
@@ -0,0 +1,292 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Dynamic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx.Functions;
using Microsoft.Bot.ObjectModel;
using Microsoft.Extensions.AI;
using Microsoft.PowerFx.Types;
using BlankType = Microsoft.PowerFx.Types.BlankType;
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
internal static class FormulaValueExtensions
{
private static readonly JsonSerializerOptions s_options = new() { WriteIndented = true };
public static FormulaValue NewBlank(this FormulaType? type) => FormulaValue.NewBlank(type ?? FormulaType.Blank);
public static FormulaValue ToFormula(this object? value) =>
value switch
{
null => FormulaValue.NewBlank(),
UnassignedValue => FormulaValue.NewBlank(),
FormulaValue formulaValue => formulaValue,
bool booleanValue => FormulaValue.New(booleanValue),
int decimalValue => FormulaValue.New(decimalValue),
long decimalValue => FormulaValue.New(decimalValue),
float decimalValue => FormulaValue.New(decimalValue),
decimal decimalValue => FormulaValue.New(decimalValue),
double numberValue => FormulaValue.New(numberValue),
string stringValue => FormulaValue.New(stringValue),
DateTime dateonlyValue when dateonlyValue.TimeOfDay == TimeSpan.Zero => FormulaValue.NewDateOnly(dateonlyValue),
DateTime datetimeValue => FormulaValue.New(datetimeValue),
TimeSpan timeValue => FormulaValue.New(timeValue),
ChatMessage chatMessage => chatMessage.ToRecord(),
ExpandoObject expandoValue => expandoValue.ToRecord(),
object when value is IDictionary dictionaryValue => dictionaryValue.ToRecord(),
object when value is IEnumerable tableValue => tableValue.ToTable(),
_ => throw new DeclarativeModelException($"Unsupported variable type: {value.GetType().Name}"),
};
public static FormulaType GetFormulaType(this object? value) =>
value switch
{
null => FormulaType.Blank,
bool => FormulaType.Boolean,
int => FormulaType.Decimal,
long => FormulaType.Decimal,
float => FormulaType.Decimal,
decimal => FormulaType.Decimal,
double => FormulaType.Number,
string => FormulaType.String,
DateTime => FormulaType.DateTime,
TimeSpan => FormulaType.Time,
object when value is IEnumerable tableValue => tableValue.ToTableType(),
ExpandoObject expandoValue => expandoValue.ToRecordType(),
_ => FormulaType.Unknown,
};
public static DataValue ToDataValue(this FormulaValue value) =>
value switch
{
BooleanValue booleanValue => BooleanDataValue.Create(booleanValue.Value),
DecimalValue decimalValue => NumberDataValue.Create(decimalValue.Value),
NumberValue numberValue => FloatDataValue.Create(numberValue.Value),
DateValue dateValue => DateDataValue.Create(dateValue.GetConvertedValue(TimeZoneInfo.Utc)),
DateTimeValue datetimeValue => DateTimeDataValue.Create(datetimeValue.GetConvertedValue(TimeZoneInfo.Utc)),
TimeValue timeValue => TimeDataValue.Create(timeValue.Value),
StringValue stringValue => StringDataValue.Create(stringValue.Value),
BlankValue => DataValue.Blank(),
VoidValue => DataValue.Blank(),
RecordValue recordValue => recordValue.ToRecord(),
TableValue tableValue => tableValue.ToTable(),
_ => throw new DeclarativeModelException($"Unsupported variable type: {value.GetType().Name}"),
};
public static DataType GetDataType(this FormulaValue value) =>
value switch
{
null => DataType.Blank,
BooleanValue => DataType.Boolean,
DecimalValue => DataType.Number,
NumberValue => DataType.Float,
DateValue => DataType.Date,
DateTimeValue => DataType.DateTime,
TimeValue => DataType.Time,
StringValue => DataType.String,
BlankValue => DataType.Blank,
ColorValue => DataType.Color,
GuidValue => DataType.Guid,
BlobValue => DataType.File,
RecordValue recordValue => recordValue.Type.ToDataType(),
TableValue tableValue => tableValue.Type.ToDataType(),
UntypedObjectValue => DataType.Any,
_ => DataType.Unspecified,
};
public static DataType ToDataType(this FormulaType type) =>
type switch
{
null => DataType.Blank,
BooleanType => DataType.Boolean,
DecimalType => DataType.Number,
NumberType => DataType.Float,
DateType => DataType.Date,
DateTimeType => DataType.DateTime,
TimeType => DataType.Time,
StringType => DataType.String,
BlankType => DataType.Blank,
ColorType => DataType.Color,
GuidType => DataType.Guid,
BlobType => DataType.File,
RecordType recordType => recordType.ToDataType(),
TableType tableType => tableType.ToDataType(),
UntypedObjectType => DataType.Any,
_ => DataType.Unspecified,
};
public static string Format(this FormulaValue value) =>
value switch
{
BooleanValue booleanValue => $"{booleanValue.Value}",
DecimalValue decimalValue => $"{decimalValue.Value}",
NumberValue numberValue => $"{numberValue.Value}",
DateValue dateValue => $"{dateValue.GetConvertedValue(TimeZoneInfo.Utc)}",
DateTimeValue datetimeValue => $"{datetimeValue.GetConvertedValue(TimeZoneInfo.Utc)}",
TimeValue timeValue => $"{timeValue.Value}",
StringValue stringValue => stringValue.Value,
BlankValue blankValue => string.Empty,
VoidValue voidValue => string.Empty,
ColorValue colorValue => colorValue.Value.ToString(),
GuidValue guidValue => guidValue.Value.ToString("N"),
TableValue tableValue => tableValue.ToJson().ToJsonString(s_options),
RecordValue recordValue => recordValue.ToJson().ToJsonString(s_options),
ErrorValue errorValue => $"Error:{Environment.NewLine}{string.Join(Environment.NewLine, errorValue.Errors.Select(error => $"{error.MessageKey}: {error.Message}"))}",
_ => $"[{value.GetType().Name}]",
};
public static TableDataValue ToTable(this TableValue value) =>
DataValue.TableFromRecords(value.Rows.Select(row => row.Value.ToRecord()).ToImmutableArray());
public static RecordDataValue ToRecord(this RecordValue value) =>
DataValue.RecordFromFields(value.OriginalFields.Select(field => field.GetKeyValuePair()));
public static RecordValue ToRecord(this IDictionary value)
{
return FormulaValue.NewRecordFromFields(GetFields());
IEnumerable<NamedValue> GetFields()
{
foreach (string key in value.Keys)
{
yield return new NamedValue(key, value[key].ToFormula());
}
}
}
private static RecordDataType ToDataType(this RecordType record)
{
RecordDataType recordType = new();
foreach (string fieldName in record.FieldNames)
{
recordType.Properties.Add(fieldName, PropertyInfo.Create(record.GetFieldType(fieldName).ToDataType()));
}
return recordType;
}
private static TableDataType ToDataType(this TableType table)
{
TableDataType tableType = new();
foreach (string fieldName in table.FieldNames)
{
tableType.Properties.Add(fieldName, PropertyInfo.Create(table.GetFieldType(fieldName).ToDataType()));
}
return tableType;
}
private static RecordType ToRecordType(this ExpandoObject value)
{
RecordType recordType = RecordType.Empty();
foreach (KeyValuePair<string, object?> property in value)
{
recordType.Add(property.Key, property.Value.GetFormulaType());
}
return recordType;
}
private static RecordValue ToRecord(this ExpandoObject value) =>
FormulaValue.NewRecordFromFields(
value.Select(
property => new NamedValue(property.Key, property.Value.ToFormula())));
private static TableType ToTableType(this IEnumerable value)
{
foreach (object? element in value)
{
if (element is not ExpandoObject expandoElement)
{
throw new DeclarativeModelException($"Invalid table element: {element.GetType().Name}");
}
return expandoElement.ToRecordType().ToTable(); // Return first element
}
return TableType.Empty();
}
private static TableValue ToTable(this IEnumerable value)
{
Type? elementType = value.GetType().GetElementType();
if (elementType is null || elementType == typeof(object))
{
IEnumerator enumerator = value.GetEnumerator();
if (enumerator.MoveNext())
{
elementType = enumerator.Current?.GetType();
}
}
return
elementType switch
{
null => FormulaValue.NewTable(RecordType.EmptySealed(), []),
_ when elementType == typeof(ExpandoObject) =>
FormulaValue.NewTable(
value.ToTableType().ToRecord(),
[.. value.OfType<ExpandoObject>().Select(element => element.ToRecord())]),
_ when typeof(ChatMessage).IsAssignableFrom(elementType) =>
FormulaValue.NewTable(
TypeSchema.Message.MessageRecordType,
[.. value.OfType<ChatMessage>().Select(message => message.ToRecord())]),
_ when typeof(IDictionary).IsAssignableFrom(elementType) => value.ToTableOfRecords(),
_ => throw new DeclarativeModelException($"Unsupported element type: {elementType.Name}"),
};
}
private static TableValue ToTableOfRecords(this IEnumerable list)
{
RecordValue[] elements = [.. list.OfType<IDictionary>().Select(table => table.ToRecord())];
return FormulaValue.NewTable(elements.First().Type, elements);
}
private static KeyValuePair<string, DataValue> GetKeyValuePair(this NamedValue value) => new(value.Name, value.Value.ToDataValue());
private static JsonNode ToJson(this FormulaValue value) =>
value switch
{
BooleanValue booleanValue => JsonValue.Create(booleanValue.Value),
DecimalValue decimalValue => JsonValue.Create(decimalValue.Value),
NumberValue numberValue => JsonValue.Create(numberValue.Value),
DateValue dateValue => JsonValue.Create(dateValue.GetConvertedValue(TimeZoneInfo.Utc)),
DateTimeValue datetimeValue => JsonValue.Create(datetimeValue.GetConvertedValue(TimeZoneInfo.Utc)),
TimeValue timeValue => JsonValue.Create($"{timeValue.Value}"),
StringValue stringValue => JsonValue.Create(stringValue.Value),
GuidValue guidValue => JsonValue.Create(guidValue.Value),
RecordValue recordValue => recordValue.ToJson(),
TableValue tableValue => tableValue.ToJson(),
BlankValue => JsonValue.Create(string.Empty),
_ => $"[{value.GetType().Name}]",
};
private static JsonArray ToJson(this TableValue value)
{
return new([.. GetJsonElements()]);
IEnumerable<JsonNode> GetJsonElements()
{
foreach (DValue<RecordValue> row in value.Rows)
{
RecordValue recordValue = row.Value;
yield return recordValue.ToJson();
}
}
}
private static JsonObject ToJson(this RecordValue value)
{
JsonObject jsonObject = [];
foreach (NamedValue field in value.OriginalFields)
{
jsonObject.Add(field.Name, field.Value.ToJson());
}
return jsonObject;
}
}
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Agents.AI.Workflows.Declarative.Interpreter;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Bot.ObjectModel;
using Microsoft.PowerFx.Types;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
internal static class IWorkflowContextExtensions
{
public static ValueTask RaiseInvocationEventAsync(this IWorkflowContext context, DialogAction action, string? priorEventId = null) =>
context.AddEventAsync(new DeclarativeActionInvokedEvent(action, priorEventId));
public static ValueTask RaiseCompletionEventAsync(this IWorkflowContext context, DialogAction action) =>
context.AddEventAsync(new DeclarativeActionCompletedEvent(action));
public static ValueTask SendResultMessageAsync(this IWorkflowContext context, string id, object? result = null, CancellationToken cancellationToken = default) =>
context.SendMessageAsync(new ActionExecutorResult(id, result));
public static ValueTask QueueStateResetAsync(this IWorkflowContext context, PropertyPath variablePath) =>
context.QueueStateUpdateAsync(Throw.IfNull(variablePath.VariableName), UnassignedValue.Instance, Throw.IfNull(variablePath.NamespaceAlias));
public static ValueTask QueueStateUpdateAsync<TValue>(this IWorkflowContext context, PropertyPath variablePath, TValue? value) =>
context.QueueStateUpdateAsync(Throw.IfNull(variablePath.VariableName), value, Throw.IfNull(variablePath.NamespaceAlias));
public static ValueTask QueueSystemUpdateAsync<TValue>(this IWorkflowContext context, string key, TValue? value) =>
DeclarativeContext(context).QueueSystemUpdateAsync(key, value);
public static FormulaValue ReadState(this IWorkflowContext context, PropertyPath variablePath) =>
context.ReadState(Throw.IfNull(variablePath.VariableName), Throw.IfNull(variablePath.NamespaceAlias));
public static FormulaValue ReadState(this IWorkflowContext context, string key, string? scopeName = null) =>
DeclarativeContext(context).State.Get(key, scopeName);
public static async ValueTask QueueConversationUpdateAsync(this IWorkflowContext context, string conversationId)
{
RecordValue conversation = (RecordValue)context.ReadState(SystemScope.Names.Conversation, VariableScopeNames.System);
conversation.UpdateField("Id", FormulaValue.New(conversationId));
await context.QueueSystemUpdateAsync(SystemScope.Names.Conversation, conversation).ConfigureAwait(false);
await context.QueueSystemUpdateAsync(SystemScope.Names.ConversationId, FormulaValue.New(conversationId)).ConfigureAwait(false);
await context.AddEventAsync(new ConversationUpdateEvent(conversationId)).ConfigureAwait(false);
}
private static DeclarativeWorkflowContext DeclarativeContext(IWorkflowContext context)
{
if (context is not DeclarativeWorkflowContext declarativeContext)
{
throw new DeclarativeActionException($"Invalid workflow context: {context.GetType().Name}.");
}
return declarativeContext;
}
}
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Microsoft.Bot.ObjectModel;
using Microsoft.PowerFx.Types;
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
internal static class RecordDataTypeExtensions
{
public static RecordValue ParseRecord(this RecordDataType recordType, JsonElement currentElement)
{
return FormulaValue.NewRecordFromFields(ParseValues());
IEnumerable<NamedValue> ParseValues()
{
foreach (KeyValuePair<string, PropertyInfo> property in recordType.Properties)
{
JsonElement propertyElement = currentElement.GetProperty(property.Key);
FormulaValue? parsedValue =
property.Value.Type switch
{
StringDataType => FormulaValue.New(propertyElement.GetString()),
NumberDataType => FormulaValue.New(propertyElement.GetDecimal()),
BooleanDataType => FormulaValue.New(propertyElement.GetBoolean()),
DateTimeDataType => FormulaValue.New(propertyElement.GetDateTime()),
DateDataType => FormulaValue.New(propertyElement.GetDateTime()),
TimeDataType => FormulaValue.New(propertyElement.GetDateTimeOffset().TimeOfDay),
RecordDataType recordType => recordType.ParseRecord(propertyElement),
TableDataType tableType => ParseTable(tableType, propertyElement),
_ => throw new InvalidOperationException($"Unsupported data type '{property.Value.Type}' for property '{property.Key}'"),
};
yield return new NamedValue(property.Key, parsedValue);
}
static TableValue ParseTable(TableDataType tableType, JsonElement propertyElement)
{
RecordDataType recordType = tableType.ToRecord();
return
FormulaValue.NewTable(
recordType.ToRecordType(),
propertyElement.EnumerateArray().Select(tableElement => ParseRecord(recordType, tableElement)));
}
}
}
}
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.RegularExpressions;
using Microsoft.PowerFx.Types;
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
internal static partial class StringExtensions
{
#if NET
[GeneratedRegex(@"^```(?:\w*)\s*([\s\S]*?)\s*```$", RegexOptions.Multiline)]
private static partial Regex TrimJsonDelimiterRegex();
#else
private static Regex TrimJsonDelimiterRegex() => s_trimJsonDelimiterRegex;
private static readonly Regex s_trimJsonDelimiterRegex = new(@"^```(?:\w*)\s*([\s\S]*?)\s*```$", RegexOptions.Compiled | RegexOptions.Multiline);
#endif
public static string TrimJsonDelimiter(this string value)
{
value = value.Trim();
Match match = TrimJsonDelimiterRegex().Match(value);
return match.Success ?
match.Groups[1].Value.Trim() :
value;
}
public static FormulaValue ToFormula(this string? value) =>
string.IsNullOrWhiteSpace(value) ? FormulaValue.NewBlank() : FormulaValue.New(value);
}
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Collections.Generic;
using System.Linq;
using Microsoft.Bot.ObjectModel;
using Microsoft.PowerFx;
namespace Microsoft.Agents.AI.Workflows.Declarative.Extensions;
internal static class TemplateExtensions
{
public static string Format(this RecalcEngine engine, IEnumerable<TemplateLine> template) =>
string.Concat(template.Select(engine.Format));
public static string Format(this RecalcEngine engine, TemplateLine? line) =>
line is not null ?
string.Concat(line.Segments.Select(engine.Format)) :
string.Empty;
public static string Format(this RecalcEngine engine, TemplateSegment segment)
{
if (segment is TextSegment textSegment)
{
return textSegment.Value ?? string.Empty;
}
if (segment is ExpressionSegment { Expression: not null } expressionSegment)
{
if (expressionSegment.Expression.ExpressionText is not null)
{
return engine.Eval(expressionSegment.Expression.ExpressionText).Format();
}
if (expressionSegment.Expression.VariableReference is not null)
{
return engine.Eval(expressionSegment.Expression.VariableReference.ToString()).Format();
}
}
throw new DeclarativeModelException($"Unsupported segment type: {segment.GetType().Name}");
}
}