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:
Ben Thomas
2026-01-22 16:02:12 +00:00
committed by GitHub
co-authored by Copilot alliscode
parent 4940d0ef36
commit ea7818d390
25 changed files with 3478 additions and 0 deletions
@@ -0,0 +1,70 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
namespace Microsoft.Agents.AI.Workflows;
/// <summary>
/// Marks a method as a message handler for source-generated route configuration.
/// The method signature determines the input type and optional output type.
/// </summary>
/// <remarks>
/// <para>
/// Methods marked with this attribute must have a signature matching one of the following patterns:
/// <list type="bullet">
/// <item><c>void Handler(TMessage, IWorkflowContext)</c></item>
/// <item><c>void Handler(TMessage, IWorkflowContext, CancellationToken)</c></item>
/// <item><c>ValueTask Handler(TMessage, IWorkflowContext)</c></item>
/// <item><c>ValueTask Handler(TMessage, IWorkflowContext, CancellationToken)</c></item>
/// <item><c>TResult Handler(TMessage, IWorkflowContext)</c></item>
/// <item><c>TResult Handler(TMessage, IWorkflowContext, CancellationToken)</c></item>
/// <item><c>ValueTask&lt;TResult&gt; Handler(TMessage, IWorkflowContext)</c></item>
/// <item><c>ValueTask&lt;TResult&gt; Handler(TMessage, IWorkflowContext, CancellationToken)</c></item>
/// </list>
/// </para>
/// <para>
/// The containing class must be <c>partial</c> and derive from <see cref="Executor"/>.
/// </para>
/// </remarks>
/// <example>
/// <code>
/// public partial class MyExecutor : Executor
/// {
/// [MessageHandler]
/// private async ValueTask&lt;MyResponse&gt; HandleQueryAsync(
/// MyQuery query, IWorkflowContext ctx, CancellationToken ct)
/// {
/// return new MyResponse();
/// }
///
/// [MessageHandler(Yield = [typeof(StreamChunk)], Send = [typeof(InternalMessage)])]
/// private void HandleStream(StreamRequest req, IWorkflowContext ctx)
/// {
/// // Handler with explicit yield and send types
/// }
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public sealed class MessageHandlerAttribute : Attribute
{
/// <summary>
/// Gets or sets the types that this handler may yield as workflow outputs.
/// </summary>
/// <remarks>
/// If not specified, the return type (if any) is used as the default yield type.
/// Use this property to explicitly declare additional output types or to override
/// the default inference from the return type.
/// </remarks>
public Type[]? Yield { get; set; }
/// <summary>
/// Gets or sets the types that this handler may send as messages to other executors.
/// </summary>
/// <remarks>
/// Use this property to declare the message types that this handler may send
/// via <see cref="IWorkflowContext.SendMessageAsync"/> during its execution.
/// This information is used for protocol validation and documentation.
/// </remarks>
public Type[]? Send { get; set; }
}
@@ -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 send messages of the specified type.
/// </summary>
/// <remarks>
/// <para>
/// Apply this attribute to an <see cref="Executor"/> class to declare the types of messages
/// it may send via <see cref="IWorkflowContext.SendMessageAsync"/>. This information is used
/// for protocol validation and documentation.
/// </para>
/// <para>
/// This attribute can be applied multiple times to declare multiple message types.
/// It is inherited by derived classes, allowing base executors to declare common message types.
/// </para>
/// </remarks>
/// <example>
/// <code>
/// [SendsMessage(typeof(PollToken))]
/// [SendsMessage(typeof(StatusUpdate))]
/// public partial class MyExecutor : Executor
/// {
/// // ...
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class SendsMessageAttribute : Attribute
{
/// <summary>
/// Gets the type of message that the executor may send.
/// </summary>
public Type Type { get; }
/// <summary>
/// Initializes a new instance of the <see cref="SendsMessageAttribute"/> class.
/// </summary>
/// <param name="type">The type of message that the executor may send.</param>
/// <exception cref="ArgumentNullException"><paramref name="type"/> is <see langword="null"/>.</exception>
public SendsMessageAttribute(Type type)
{
this.Type = Throw.IfNull(type);
}
}
@@ -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>
/// [YieldsOutput(typeof(FinalResult))]
/// [YieldsOutput(typeof(StreamChunk))]
/// public partial class MyExecutor : Executor
/// {
/// // ...
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public sealed class YieldsOutputAttribute : 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="YieldsOutputAttribute"/> 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 YieldsOutputAttribute(Type type)
{
this.Type = Throw.IfNull(type);
}
}
@@ -25,6 +25,15 @@
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.Agents.AI.Workflows.UnitTests" />
<InternalsVisibleTo Include="Microsoft.Agents.AI.Workflows.Generators.UnitTests" />
</ItemGroup>
<!-- Include source generator -->
<ItemGroup>
<ProjectReference Include="..\Microsoft.Agents.AI.Workflows.Generators\Microsoft.Agents.AI.Workflows.Generators.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"
GlobalPropertiesToRemove="TargetFramework" />
</ItemGroup>
<ItemGroup>