Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ private static async Task Main()
/// <summary>
/// Executor that starts the concurrent processing by sending messages to the agents.
/// </summary>
internal sealed class ConcurrentStartExecutor() :
Executor<string>("ConcurrentStartExecutor")
internal sealed partial class ConcurrentStartExecutor() :
Executor("ConcurrentStartExecutor")
{
/// <summary>
/// Starts the concurrent processing by sending messages to the agents.
Expand All @@ -83,7 +83,8 @@ internal sealed class ConcurrentStartExecutor() :
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A task representing the asynchronous operation</returns>
public override async ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
[MessageHandler]
public async ValueTask HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Broadcast the message to all connected agents. Receiving agents will queue
// the message but will not start processing until they receive a turn token.
Expand Down
13 changes: 13 additions & 0 deletions dotnet/samples/GettingStarted/Workflows/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project>

<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />

<!-- Include Workflows source generator for samples using [MessageHandler] attribute -->
<ItemGroup>
<ProjectReference Include="$(RepoRoot)/dotnet/src/Microsoft.Agents.AI.Workflows.Generators/Microsoft.Agents.AI.Workflows.Generators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"
GlobalPropertiesToRemove="TargetFramework" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;

namespace Microsoft.Agents.AI.Workflows.Generators.Models;

/// <summary>
/// A wrapper around <see cref="ImmutableArray{T}"/> that provides value-based equality.
/// This is necessary for incremental generator caching since ImmutableArray uses reference equality.
/// </summary>
/// <remarks>
/// Creates a new <see cref="EquatableArray{T}"/> from an <see cref="ImmutableArray{T}"/>.
/// </remarks>
internal readonly struct EquatableArray<T>(ImmutableArray<T> array) : IEquatable<EquatableArray<T>>, IEnumerable<T>
where T : IEquatable<T>
{
private readonly ImmutableArray<T> _array = array.IsDefault ? ImmutableArray<T>.Empty : array;

/// <summary>
/// Gets the underlying array.
/// </summary>
public ImmutableArray<T> AsImmutableArray() => this._array;

/// <summary>
/// Gets the number of elements in the array.
/// </summary>
public int Length => this._array.Length;

/// <summary>
/// Gets the element at the specified index.
/// </summary>
public T this[int index] => this._array[index];

/// <summary>
/// Gets whether the array is empty.
/// </summary>
public bool IsEmpty => this._array.IsEmpty;

/// <inheritdoc/>
public bool Equals(EquatableArray<T> other)
{
if (this._array.Length != other._array.Length)
{
return false;
}

for (int i = 0; i < this._array.Length; i++)
{
if (!this._array[i].Equals(other._array[i]))
{
return false;
}
}

return true;
}

/// <inheritdoc/>
public override bool Equals(object? obj)
{
return obj is EquatableArray<T> other && this.Equals(other);
}

/// <inheritdoc/>
public override int GetHashCode()
{
if (this._array.IsEmpty)
{
return 0;
}

var hashCode = 17;
foreach (var item in this._array)
{
hashCode = hashCode * 31 + (item?.GetHashCode() ?? 0);
}

return hashCode;
}

/// <inheritdoc/>
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)this._array).GetEnumerator();
}

/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}

/// <summary>
/// Equality operator.
/// </summary>
public static bool operator ==(EquatableArray<T> left, EquatableArray<T> right)
{
return left.Equals(right);
}

/// <summary>
/// Inequality operator.
/// </summary>
public static bool operator !=(EquatableArray<T> left, EquatableArray<T> right)
{
return !left.Equals(right);
}

/// <summary>
/// Creates an empty <see cref="EquatableArray{T}"/>.
/// </summary>
public static EquatableArray<T> Empty => new(ImmutableArray<T>.Empty);

/// <summary>
/// Implicit conversion from <see cref="ImmutableArray{T}"/>.
/// </summary>
public static implicit operator EquatableArray<T>(ImmutableArray<T> array) => new(array);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Agents.AI.Workflows;

/// <summary>
/// Declares that an executor may yield messages of the specified type as workflow outputs.
/// </summary>
/// <remarks>
/// <para>
/// Apply this attribute to an <see cref="Executor"/> class to declare the types of messages
/// it may yield via <see cref="IWorkflowContext.YieldOutputAsync"/>. This information is used
/// for protocol validation and documentation.
/// </para>
/// <para>
/// This attribute can be applied multiple times to declare multiple output types.
/// It is inherited by derived classes, allowing base executors to declare common output types.
/// </para>
/// </remarks>
/// <example>
/// <code>
/// [YieldsMessage(typeof(FinalResult))]
/// [YieldsMessage(typeof(StreamChunk))]
/// public partial class MyExecutor : Executor
/// {
/// // ...
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class YieldsMessageAttribute : Attribute
{
/// <summary>
/// Gets the type of message that the executor may yield.
/// </summary>
public Type Type { get; }

/// <summary>
/// Initializes a new instance of the <see cref="YieldsMessageAttribute"/> class.
/// </summary>
/// <param name="type">The type of message that the executor may yield.</param>
/// <exception cref="ArgumentNullException"><paramref name="type"/> is <see langword="null"/>.</exception>
public YieldsMessageAttribute(Type type)
{
this.Type = Throw.IfNull(type);
}
}
2 changes: 2 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.Workflows/Executor.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

#pragma warning disable CS0618 // Type or member is obsolete - Internal use of obsolete types for backward compatibility

using System;
using System.Collections.Generic;
using System.Diagnostics;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -9,6 +10,12 @@ namespace Microsoft.Agents.AI.Workflows.Reflection;
/// A message handler interface for handling messages of type <typeparamref name="TMessage"/>.
/// </summary>
/// <typeparam name="TMessage"></typeparam>
/// <remarks>
/// This interface is obsolete. Use the <see cref="MessageHandlerAttribute"/> on methods in a partial class
/// deriving from <see cref="Executor"/> instead.
/// </remarks>
[Obsolete("Use [MessageHandler] attribute on methods in a partial class deriving from Executor. " +
"This interface will be removed in a future version.")]
public interface IMessageHandler<TMessage>
{
/// <summary>
Expand All @@ -28,6 +35,12 @@ public interface IMessageHandler<TMessage>
/// </summary>
/// <typeparam name="TMessage">The type of message to handle.</typeparam>
/// <typeparam name="TResult">The type of result returned after handling the message.</typeparam>
/// <remarks>
/// This interface is obsolete. Use the <see cref="MessageHandlerAttribute"/> on methods in a partial class
/// deriving from <see cref="Executor"/> instead.
/// </remarks>
[Obsolete("Use [MessageHandler] attribute on methods in a partial class deriving from Executor. " +
"This interface will be removed in a future version.")]
public interface IMessageHandler<TMessage, TResult>
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

#pragma warning disable CS0618 // Type or member is obsolete - Internal use of obsolete types for backward compatibility

using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Agents.AI.Workflows.Reflection;
Expand All @@ -10,6 +11,12 @@ namespace Microsoft.Agents.AI.Workflows.Reflection;
/// <typeparam name="TExecutor">The actual type of the <see cref="ReflectingExecutor{TExecutor}"/>.
/// This is used to reflectively discover handlers for messages without violating ILTrim requirements.
/// </typeparam>
/// <remarks>
/// This type is obsolete. Use the <see cref="MessageHandlerAttribute"/> on methods in a partial class
/// deriving from <see cref="Executor"/> instead.
/// </remarks>
[Obsolete("Use [MessageHandler] attribute on methods in a partial class deriving from Executor. " +
"This type will be removed in a future version.")]
public class ReflectingExecutor<
[DynamicallyAccessedMembers(
ReflectionDemands.RuntimeInterfaceDiscoveryAndInvocation)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

#pragma warning disable CS0618 // Type or member is obsolete - Internal use of obsolete types for backward compatibility

using System;
using System.Collections.Generic;
using System.Diagnostics;
Expand Down
2 changes: 2 additions & 0 deletions dotnet/src/Microsoft.Agents.AI.Workflows/StatefulExecutor.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

#pragma warning disable CS0618 // Type or member is obsolete - Internal use of obsolete types for backward compatibility

using System;
using System.Threading;
using System.Threading.Tasks;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

#pragma warning disable CS0618 // Type or member is obsolete - Testing legacy reflection-based pattern

using System;
using System.Threading;
using System.Threading.Tasks;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

#pragma warning disable CS0618 // Type or member is obsolete - Testing legacy reflection-based pattern

using System;
using System.IO;
using System.Linq;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

#pragma warning disable CS0618 // Type or member is obsolete - Testing legacy reflection-based pattern

using System;
using System.IO;
using System.Linq;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.

#pragma warning disable CS0618 // Type or member is obsolete - Testing legacy reflection-based pattern

using System;
using System.IO;
using System.Threading;
Expand Down
Loading
Loading