.NET: [BREAKING] Implement Polymorphic Routing (#3792)

* feat: Implement Polymorphic Routing

* feat: Add support for Send/Yield annotations with basic Executor

* Adds annotations to Declarative workflow executors

* fix: Address PR Comments

* Implicit filter in collection loops
* Remove debug / usused / superfluous code
* Fix ProtocolBuilder implicit output registrations
* Fix logic error in ExecuteRouteGeneratorTests.ClassWithManualConfigureProtocol_DoesNotGenerate

* fix: Solidify type checks and send/yield type registrations

* fix: Suppress generation of TurnTokens out of AggregateTurnMessagesExecutor

* Fixes an issue where ConcurrentEndExecutor is not expecting TurnTokens.

* fix: Add ProtocolBuilder support for chained-delegation

* Updates Declarative pacakge to rely on chained-delegation Send/Yield registration
* Renames DeclarativeActionExectuor's new ExecuteAsync to ExecuteActionAsync to avoid colliding with Executor.ExecutoeAsync

* fix: Address PR Comments

* Fixes type mapping in FanInEdgeRunner
* Fixes and expalins send/yield type registration in FunctionExecutor

* fixup: build-break

* fix: Add missing SendsMesage declaration to InvokeAzureAgentExecutor
This commit is contained in:
Jacob Alber
2026-02-19 14:09:03 +00:00
committed by GitHub
parent b05fc9e849
commit 6a3d22598f
82 changed files with 1581 additions and 819 deletions
@@ -201,14 +201,43 @@ internal sealed class InProcessRunner : ISuperStepRunner, ICheckpointingHandle
this.StepTracer.TraceActivated(receiverId);
while (envelopes.TryDequeue(out var envelope))
{
await executor.ExecuteAsync(
envelope.Message,
envelope.MessageType,
(object message, TypeId messageType) = await TranslateMessageAsync(envelope).ConfigureAwait(false);
await executor.ExecuteCoreAsync(
message,
messageType,
this.RunContext.BindWorkflowContext(receiverId, envelope.TraceContext),
this.TelemetryContext,
cancellationToken
).ConfigureAwait(false);
}
async ValueTask<(object, TypeId)> TranslateMessageAsync(MessageEnvelope envelope)
{
object? value = envelope.Message;
TypeId messageType = envelope.MessageType;
if (!envelope.IsExternal)
{
Executor source = await this.RunContext.EnsureExecutorAsync(envelope.SourceId, this.StepTracer, cancellationToken).ConfigureAwait(false);
Type? actualType = source.Protocol.SendTypeTranslator.MapTypeId(envelope.MessageType);
if (actualType == null)
{
// In principle, this should never happen, since we always use the SendTypeTranslator to generate the outgoing TypeId in the first place.
throw new InvalidOperationException($"Cannot translate message type ID '{envelope.MessageType}' from executor '{source.Id}'.");
}
messageType = new(actualType);
if (value is PortableValue portableValue &&
!portableValue.IsType(actualType, out value))
{
throw new InvalidOperationException($"Cannot interpret incoming message of type '{portableValue.TypeId}' as type '{actualType.FullName}'.");
}
}
return (value, messageType);
}
}
private async ValueTask RunSuperstepAsync(StepContext currentStep, CancellationToken cancellationToken)