mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: .NET: Executor source gen for workflow executor routing (#3131)
* Roslyn Source Generators for Workflow Executor Routing. * Update dotnet/src/Microsoft.Agents.AI.Workflows.Generators/ExecutorRouteGenerator.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * WIP. * All fixed up except dangling sends/yields attriutes, working on that next. * Add protocol-only generation for SendsMessage/YieldsOutput attributes * Ensuring collections that can change order are sorted to enable pipeline caching. * Improvents per PR feedback. --------- Co-authored-by: alliscode <bentho@microsoft.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
Copilot
alliscode
parent
4940d0ef36
commit
ea7818d390
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Generators.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the result of analyzing a class with [MessageHandler] attributed methods.
|
||||
/// Combines the executor info (if valid) with any diagnostics to report.
|
||||
/// Note: Instances of this class should not be used within the analyzers caching
|
||||
/// layer because it directly contains a collection of <see cref="Diagnostic"/> objects.
|
||||
/// </summary>
|
||||
/// <param name="executorInfo">The executor information.</param>
|
||||
/// <param name="diagnostics">Any diagnostics to report.</param>
|
||||
internal sealed class AnalysisResult(ExecutorInfo? executorInfo, ImmutableArray<Diagnostic> diagnostics)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the executor information.
|
||||
/// </summary>
|
||||
public ExecutorInfo? ExecutorInfo { get; } = executorInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the diagnostics to report.
|
||||
/// </summary>
|
||||
public ImmutableArray<Diagnostic> Diagnostics { get; } = diagnostics.IsDefault ? ImmutableArray<Diagnostic>.Empty : diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a successful result with executor info and no diagnostics.
|
||||
/// </summary>
|
||||
public static AnalysisResult Success(ExecutorInfo info) =>
|
||||
new(info, ImmutableArray<Diagnostic>.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a result with only diagnostics (no valid executor info).
|
||||
/// </summary>
|
||||
public static AnalysisResult WithDiagnostics(ImmutableArray<Diagnostic> diagnostics) =>
|
||||
new(null, diagnostics);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a result with executor info and diagnostics.
|
||||
/// </summary>
|
||||
public static AnalysisResult WithInfoAndDiagnostics(ExecutorInfo info, ImmutableArray<Diagnostic> diagnostics) =>
|
||||
new(info, diagnostics);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an empty result (no info, no diagnostics).
|
||||
/// </summary>
|
||||
public static AnalysisResult Empty => new(null, ImmutableArray<Diagnostic>.Empty);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Generators.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents protocol type information extracted from class-level [SendsMessage] or [YieldsOutput] attributes.
|
||||
/// Used by the incremental generator pipeline to capture classes that declare protocol types
|
||||
/// but may not have [MessageHandler] methods (e.g., when ConfigureRoutes is manually implemented).
|
||||
/// </summary>
|
||||
/// <param name="ClassKey">Unique identifier for the class (fully qualified name).</param>
|
||||
/// <param name="Namespace">The namespace of the class.</param>
|
||||
/// <param name="ClassName">The name of the class.</param>
|
||||
/// <param name="GenericParameters">The generic type parameters (e.g., "<T>"), or null if not generic.</param>
|
||||
/// <param name="IsNested">Whether the class is nested inside another class.</param>
|
||||
/// <param name="ContainingTypeChain">The chain of containing types for nested classes. Empty if not nested.</param>
|
||||
/// <param name="IsPartialClass">Whether the class is declared as partial.</param>
|
||||
/// <param name="DerivesFromExecutor">Whether the class derives from Executor.</param>
|
||||
/// <param name="HasManualConfigureRoutes">Whether the class has a manually defined ConfigureRoutes method.</param>
|
||||
/// <param name="ClassLocation">Location info for diagnostics.</param>
|
||||
/// <param name="TypeName">The fully qualified type name from the attribute.</param>
|
||||
/// <param name="AttributeKind">Whether this is from a SendsMessage or YieldsOutput attribute.</param>
|
||||
internal sealed record ClassProtocolInfo(
|
||||
string ClassKey,
|
||||
string? Namespace,
|
||||
string ClassName,
|
||||
string? GenericParameters,
|
||||
bool IsNested,
|
||||
string ContainingTypeChain,
|
||||
bool IsPartialClass,
|
||||
bool DerivesFromExecutor,
|
||||
bool HasManualConfigureRoutes,
|
||||
DiagnosticLocationInfo? ClassLocation,
|
||||
string TypeName,
|
||||
ProtocolAttributeKind AttributeKind)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an empty result for invalid targets.
|
||||
/// </summary>
|
||||
public static ClassProtocolInfo Empty { get; } = new(
|
||||
string.Empty, null, string.Empty, null, false, string.Empty,
|
||||
false, false, false, null, string.Empty, ProtocolAttributeKind.Send);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Workflows.Generators.Diagnostics;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Generators.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents diagnostic information in a form that supports value equality.
|
||||
/// Location is stored as file path + span, which can be used to recreate a Location.
|
||||
/// </summary>
|
||||
internal sealed record DiagnosticInfo(
|
||||
string DiagnosticId,
|
||||
string FilePath,
|
||||
TextSpan Span,
|
||||
LinePositionSpan LineSpan,
|
||||
ImmutableEquatableArray<string> MessageArgs)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a DiagnosticInfo from a location and message arguments.
|
||||
/// </summary>
|
||||
public static DiagnosticInfo Create(string diagnosticId, Location location, params string[] messageArgs)
|
||||
{
|
||||
FileLinePositionSpan lineSpan = location.GetLineSpan();
|
||||
return new DiagnosticInfo(
|
||||
diagnosticId,
|
||||
lineSpan.Path ?? string.Empty,
|
||||
location.SourceSpan,
|
||||
lineSpan.Span,
|
||||
new ImmutableEquatableArray<string>(System.Collections.Immutable.ImmutableArray.Create(messageArgs)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this info back to a Roslyn Diagnostic.
|
||||
/// </summary>
|
||||
public Diagnostic ToRoslynDiagnostic(SyntaxTree? syntaxTree)
|
||||
{
|
||||
DiagnosticDescriptor? descriptor = DiagnosticDescriptors.GetById(this.DiagnosticId);
|
||||
if (descriptor is null)
|
||||
{
|
||||
// Fallback - should not happen
|
||||
object[] fallbackArgs = new object[this.MessageArgs.Count];
|
||||
for (int i = 0; i < this.MessageArgs.Count; i++)
|
||||
{
|
||||
fallbackArgs[i] = this.MessageArgs[i];
|
||||
}
|
||||
|
||||
return Diagnostic.Create(
|
||||
DiagnosticDescriptors.InsufficientParameters,
|
||||
Location.None,
|
||||
fallbackArgs);
|
||||
}
|
||||
|
||||
Location location;
|
||||
if (syntaxTree is not null)
|
||||
{
|
||||
location = Location.Create(syntaxTree, this.Span);
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(this.FilePath))
|
||||
{
|
||||
location = Location.Create(this.FilePath, this.Span, this.LineSpan);
|
||||
}
|
||||
else
|
||||
{
|
||||
location = Location.None;
|
||||
}
|
||||
|
||||
object[] args = new object[this.MessageArgs.Count];
|
||||
for (int i = 0; i < this.MessageArgs.Count; i++)
|
||||
{
|
||||
args[i] = this.MessageArgs[i];
|
||||
}
|
||||
|
||||
return Diagnostic.Create(descriptor, location, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Generators.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents location information in a form that supports value equality making it friendly for source gen caching.
|
||||
/// </summary>
|
||||
internal sealed record DiagnosticLocationInfo(
|
||||
string FilePath,
|
||||
TextSpan Span,
|
||||
LinePositionSpan LineSpan)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a DiagnosticLocationInfo from a Roslyn Location.
|
||||
/// </summary>
|
||||
public static DiagnosticLocationInfo? FromLocation(Location? location)
|
||||
{
|
||||
if (location is null || location == Location.None)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
FileLinePositionSpan lineSpan = location.GetLineSpan();
|
||||
return new DiagnosticLocationInfo(
|
||||
lineSpan.Path ?? string.Empty,
|
||||
location.SourceSpan,
|
||||
lineSpan.Span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts back to a Roslyn Location.
|
||||
/// </summary>
|
||||
public Location ToRoslynLocation()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(this.FilePath))
|
||||
{
|
||||
return Location.None;
|
||||
}
|
||||
|
||||
return Location.Create(this.FilePath, this.Span, this.LineSpan);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Generators.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Contains all information needed to generate code for an executor class.
|
||||
/// Uses record for automatic value equality, which is required for incremental generator caching.
|
||||
/// </summary>
|
||||
/// <param name="Namespace">The namespace of the executor class.</param>
|
||||
/// <param name="ClassName">The name of the executor class.</param>
|
||||
/// <param name="GenericParameters">The generic type parameters of the class (e.g., "<T, U>"), or null if not generic.</param>
|
||||
/// <param name="IsNested">Whether the class is nested inside another class.</param>
|
||||
/// <param name="ContainingTypeChain">The chain of containing types for nested classes (e.g., "OuterClass.InnerClass"). Empty string if not nested.</param>
|
||||
/// <param name="BaseHasConfigureRoutes">Whether the base class has a ConfigureRoutes method that should be called.</param>
|
||||
/// <param name="Handlers">The list of handler methods to register.</param>
|
||||
/// <param name="ClassSendTypes">The types declared via class-level [SendsMessage] attributes.</param>
|
||||
/// <param name="ClassYieldTypes">The types declared via class-level [YieldsOutput] attributes.</param>
|
||||
internal sealed record ExecutorInfo(
|
||||
string? Namespace,
|
||||
string ClassName,
|
||||
string? GenericParameters,
|
||||
bool IsNested,
|
||||
string ContainingTypeChain,
|
||||
bool BaseHasConfigureRoutes,
|
||||
ImmutableEquatableArray<HandlerInfo> Handlers,
|
||||
ImmutableEquatableArray<string> ClassSendTypes,
|
||||
ImmutableEquatableArray<string> ClassYieldTypes)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets whether any protocol type overrides should be generated.
|
||||
/// </summary>
|
||||
public bool ShouldGenerateProtocolOverrides =>
|
||||
!this.ClassSendTypes.IsEmpty ||
|
||||
!this.ClassYieldTypes.IsEmpty ||
|
||||
this.HasHandlerWithSendTypes ||
|
||||
this.HasHandlerWithYieldTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether any handler has explicit Send types.
|
||||
/// </summary>
|
||||
public bool HasHandlerWithSendTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var handler in this.Handlers)
|
||||
{
|
||||
if (!handler.SendTypes.IsEmpty)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether any handler has explicit Yield types or output types.
|
||||
/// </summary>
|
||||
public bool HasHandlerWithYieldTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var handler in this.Handlers)
|
||||
{
|
||||
if (!handler.YieldTypes.IsEmpty)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (handler.HasOutput)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Generators.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the signature kind of a message handler method.
|
||||
/// </summary>
|
||||
internal enum HandlerSignatureKind
|
||||
{
|
||||
/// <summary>Void synchronous: void Handler(T, IWorkflowContext) or void Handler(T, IWorkflowContext, CT)</summary>
|
||||
VoidSync,
|
||||
|
||||
/// <summary>Void asynchronous: ValueTask Handler(T, IWorkflowContext[, CT])</summary>
|
||||
VoidAsync,
|
||||
|
||||
/// <summary>Result synchronous: TResult Handler(T, IWorkflowContext[, CT])</summary>
|
||||
ResultSync,
|
||||
|
||||
/// <summary>Result asynchronous: ValueTask<TResult> Handler(T, IWorkflowContext[, CT])</summary>
|
||||
ResultAsync
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains information about a single message handler method.
|
||||
/// Uses record for automatic value equality, which is required for incremental generator caching.
|
||||
/// </summary>
|
||||
/// <param name="MethodName">The name of the handler method.</param>
|
||||
/// <param name="InputTypeName">The fully-qualified type name of the input message type.</param>
|
||||
/// <param name="OutputTypeName">The fully-qualified type name of the output type, or null if the handler is void.</param>
|
||||
/// <param name="SignatureKind">The signature kind of the handler.</param>
|
||||
/// <param name="HasCancellationToken">Whether the handler method has a CancellationToken parameter.</param>
|
||||
/// <param name="YieldTypes">The types explicitly declared in the Yield property of [MessageHandler].</param>
|
||||
/// <param name="SendTypes">The types explicitly declared in the Send property of [MessageHandler].</param>
|
||||
internal sealed record HandlerInfo(
|
||||
string MethodName,
|
||||
string InputTypeName,
|
||||
string? OutputTypeName,
|
||||
HandlerSignatureKind SignatureKind,
|
||||
bool HasCancellationToken,
|
||||
ImmutableEquatableArray<string> YieldTypes,
|
||||
ImmutableEquatableArray<string> SendTypes)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets whether this handler returns a value (either sync or async).
|
||||
/// </summary>
|
||||
public bool HasOutput => this.SignatureKind == HandlerSignatureKind.ResultSync || this.SignatureKind == HandlerSignatureKind.ResultAsync;
|
||||
}
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Generators.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Provides an immutable list implementation which implements sequence equality.
|
||||
/// Copied from: https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/SourceGenerators/ImmutableEquatableArray.cs
|
||||
/// </summary>
|
||||
internal sealed class ImmutableEquatableArray<T> : IEquatable<ImmutableEquatableArray<T>>, IReadOnlyList<T>
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new empty <see cref="ImmutableEquatableArray{T}"/>.
|
||||
/// </summary>
|
||||
public static ImmutableEquatableArray<T> Empty { get; } = new ImmutableEquatableArray<T>(Array.Empty<T>());
|
||||
|
||||
private readonly T[] _values;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the element at the specified index.
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
public T this[int index] => this._values[index];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of elements contained in the collection.
|
||||
/// </summary>
|
||||
public int Count => this._values.Length;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the array is empty.
|
||||
/// </summary>
|
||||
public bool IsEmpty => this._values.Length == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the ImmutableEquatableArray{T} class that contains the elements from the specified
|
||||
/// collection.
|
||||
/// </summary>
|
||||
/// <remarks>The elements from the provided collection are copied into the immutable array. Subsequent
|
||||
/// changes to the original collection do not affect the contents of this array.</remarks>
|
||||
/// <param name="values">The collection of elements to initialize the array with. Cannot be null.</param>
|
||||
public ImmutableEquatableArray(IEnumerable<T> values) => this._values = values.ToArray();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(ImmutableEquatableArray<T>? other) => other != null && ((ReadOnlySpan<T>)this._values).SequenceEqual(other._values);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is ImmutableEquatableArray<T> other && this.Equals(other);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hash = 0;
|
||||
foreach (T value in this._values)
|
||||
{
|
||||
hash = HashHelpers.Combine(hash, value is null ? 0 : value.GetHashCode());
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Enumerator GetEnumerator() => new(this._values);
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => ((IEnumerable<T>)this._values).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => this._values.GetEnumerator();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public struct Enumerator
|
||||
{
|
||||
private readonly T[] _values;
|
||||
private int _index;
|
||||
|
||||
internal Enumerator(T[] values)
|
||||
{
|
||||
this._values = values;
|
||||
this._index = -1;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool MoveNext()
|
||||
{
|
||||
int newIndex = this._index + 1;
|
||||
|
||||
if ((uint)newIndex < (uint)this._values.Length)
|
||||
{
|
||||
this._index = newIndex;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The element at the current position of the enumerator.
|
||||
/// </summary>
|
||||
public readonly T Current => this._values[this._index];
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ImmutableEquatableArray
|
||||
{
|
||||
public static ImmutableEquatableArray<T> ToImmutableEquatableArray<T>(this IEnumerable<T> values) where T : IEquatable<T>
|
||||
=> new(values);
|
||||
}
|
||||
|
||||
// Copied from https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Numerics/Hashing/HashHelpers.cs#L6
|
||||
internal static class HashHelpers
|
||||
{
|
||||
public static int Combine(int h1, int h2)
|
||||
{
|
||||
// RyuJIT optimizes this to use the ROL instruction
|
||||
// Related GitHub pull request: https://github.com/dotnet/coreclr/pull/1830
|
||||
uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27);
|
||||
return ((int)rol5 + h1) ^ h2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Generators.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the result of analyzing a single method with [MessageHandler].
|
||||
/// Contains both the method's handler info and class context for grouping.
|
||||
/// Uses value-equatable types to support incremental generator caching.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Class-level validation (IsPartialClass, DerivesFromExecutor, HasManualConfigureRoutes)
|
||||
/// is extracted here but validated once per class in CombineMethodResults to avoid
|
||||
/// redundant validation work when a class has multiple handlers.
|
||||
/// </remarks>
|
||||
internal sealed record MethodAnalysisResult(
|
||||
// Class identification for grouping
|
||||
string ClassKey,
|
||||
|
||||
// Class-level info (extracted once per method, will be same for all methods in class)
|
||||
string? Namespace,
|
||||
string ClassName,
|
||||
string? GenericParameters,
|
||||
bool IsNested,
|
||||
string ContainingTypeChain,
|
||||
bool BaseHasConfigureRoutes,
|
||||
ImmutableEquatableArray<string> ClassSendTypes,
|
||||
ImmutableEquatableArray<string> ClassYieldTypes,
|
||||
|
||||
// Class-level facts (used for validation in CombineMethodResults)
|
||||
bool IsPartialClass,
|
||||
bool DerivesFromExecutor,
|
||||
bool HasManualConfigureRoutes,
|
||||
|
||||
// Class location for diagnostics (value-equatable)
|
||||
DiagnosticLocationInfo? ClassLocation,
|
||||
|
||||
// Method-level info (null if method validation failed)
|
||||
HandlerInfo? Handler,
|
||||
|
||||
// Method-level diagnostics only (class-level diagnostics created in CombineMethodResults)
|
||||
ImmutableEquatableArray<DiagnosticInfo> Diagnostics)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets an empty result for invalid targets (e.g., attribute on non-method).
|
||||
/// </summary>
|
||||
public static MethodAnalysisResult Empty { get; } = new(
|
||||
string.Empty, null, string.Empty, null, false, string.Empty,
|
||||
false, ImmutableEquatableArray<string>.Empty, ImmutableEquatableArray<string>.Empty,
|
||||
false, false, false,
|
||||
null, null, ImmutableEquatableArray<DiagnosticInfo>.Empty);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Agents.AI.Workflows.Generators.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the kind of protocol attribute.
|
||||
/// </summary>
|
||||
internal enum ProtocolAttributeKind
|
||||
{
|
||||
/// <summary>
|
||||
/// The [SendsMessage] attribute.
|
||||
/// </summary>
|
||||
Send,
|
||||
|
||||
/// <summary>
|
||||
/// The [YieldsOutput] attribute.
|
||||
/// </summary>
|
||||
Yield
|
||||
}
|
||||
Reference in New Issue
Block a user