.NET: [BREAKING] Propagate CancellationToken into Workflow Executors and message handlers (#1280)

* feat: Propagate CancellationToken to Executors

* Also adds cancellation propagation to `Executor`-accessible APIs
* Adds registrators for cancellable handlers to `RouteBuilder`
* [BREAKING]: Adds `CancellationToken` to `IMessageHandler.HandleAsync`

* test: Re-enable Concurrent Orchestration test

* refactor: Delete unused IInputCoordinator

* refactor: Remove superfluous argument qualifications
This commit is contained in:
Jacob Alber
2025-10-08 00:28:44 +00:00
committed by GitHub
parent eb049c43a6
commit 5902bcb10a
74 changed files with 910 additions and 557 deletions
@@ -28,7 +28,7 @@ internal sealed class ClearAllVariablesExecutor(ClearAllVariables model, Workflo
if (scope is not null)
{
await context.QueueClearScopeAsync(scope).ConfigureAwait(false);
await context.QueueClearScopeAsync(scope, cancellationToken).ConfigureAwait(false);
Debug.WriteLine(
$"""
STATE: {this.GetType().Name} [{this.Id}]
@@ -69,5 +69,5 @@ internal sealed class ConditionGroupExecutor : DeclarativeActionExecutor<Conditi
}
public async ValueTask DoneAsync(IWorkflowContext context, ActionExecutorResult _, CancellationToken cancellationToken) =>
await context.RaiseCompletionEventAsync(this.Model).ConfigureAwait(false);
await context.RaiseCompletionEventAsync(this.Model, cancellationToken).ConfigureAwait(false);
}
@@ -17,7 +17,7 @@ internal sealed class CreateConversationExecutor(CreateConversation model, Workf
{
string conversationId = await agentProvider.CreateConversationAsync(cancellationToken).ConfigureAwait(false);
await this.AssignAsync(this.Model.ConversationId?.Path, FormulaValue.New(conversationId), context).ConfigureAwait(false);
await context.QueueConversationUpdateAsync(conversationId).ConfigureAwait(false);
await context.QueueConversationUpdateAsync(conversationId, cancellationToken: cancellationToken).ConfigureAwait(false);
return default;
}
@@ -68,11 +68,11 @@ internal sealed class ForeachExecutor : DeclarativeActionExecutor<Foreach>
{
FormulaValue value = this._values[this._index];
await context.QueueStateUpdateAsync(Throw.IfNull(this.Model.Value), value).ConfigureAwait(false);
await context.QueueStateUpdateAsync(Throw.IfNull(this.Model.Value), value, cancellationToken).ConfigureAwait(false);
if (this.Model.Index is not null)
{
await context.QueueStateUpdateAsync(this.Model.Index.Path, FormulaValue.New(this._index)).ConfigureAwait(false);
await context.QueueStateUpdateAsync(this.Model.Index.Path, FormulaValue.New(this._index), cancellationToken).ConfigureAwait(false);
}
this._index++;
@@ -83,15 +83,15 @@ internal sealed class ForeachExecutor : DeclarativeActionExecutor<Foreach>
{
try
{
await context.QueueStateResetAsync(Throw.IfNull(this.Model.Value)).ConfigureAwait(false);
await context.QueueStateResetAsync(Throw.IfNull(this.Model.Value), cancellationToken).ConfigureAwait(false);
if (this.Model.Index is not null)
{
await context.QueueStateResetAsync(this.Model.Index).ConfigureAwait(false);
await context.QueueStateResetAsync(this.Model.Index, cancellationToken).ConfigureAwait(false);
}
}
finally
{
await context.RaiseCompletionEventAsync(this.Model).ConfigureAwait(false);
await context.RaiseCompletionEventAsync(this.Model, cancellationToken).ConfigureAwait(false);
}
}
}
@@ -75,7 +75,7 @@ internal sealed class QuestionExecutor(Question model, WorkflowFormulaState stat
{
int count = await this._promptCount.ReadAsync(context).ConfigureAwait(false);
InputRequest inputRequest = new(this.FormatPrompt(this.Model.Prompt));
await context.SendMessageAsync(inputRequest).ConfigureAwait(false);
await context.SendMessageAsync(inputRequest, cancellationToken: cancellationToken).ConfigureAwait(false);
await this._promptCount.WriteAsync(context, count + 1).ConfigureAwait(false);
}
@@ -85,7 +85,7 @@ internal sealed class QuestionExecutor(Question model, WorkflowFormulaState stat
if (string.IsNullOrWhiteSpace(message.Value))
{
string unrecognizedResponse = this.FormatPrompt(this.Model.UnrecognizedPrompt);
await context.AddEventAsync(new MessageActivityEvent(unrecognizedResponse.Trim())).ConfigureAwait(false);
await context.AddEventAsync(new MessageActivityEvent(unrecognizedResponse.Trim()), cancellationToken).ConfigureAwait(false);
}
else
{
@@ -97,7 +97,7 @@ internal sealed class QuestionExecutor(Question model, WorkflowFormulaState stat
else
{
string invalidResponse = this.FormatPrompt(this.Model.InvalidPrompt);
await context.AddEventAsync(new MessageActivityEvent(invalidResponse.Trim())).ConfigureAwait(false);
await context.AddEventAsync(new MessageActivityEvent(invalidResponse.Trim()), cancellationToken).ConfigureAwait(false);
}
}
@@ -115,7 +115,7 @@ internal sealed class QuestionExecutor(Question model, WorkflowFormulaState stat
public async ValueTask CompleteAsync(IWorkflowContext context, ActionExecutorResult message, CancellationToken cancellationToken)
{
await context.RaiseCompletionEventAsync(this.Model).ConfigureAwait(false);
await context.RaiseCompletionEventAsync(this.Model, cancellationToken).ConfigureAwait(false);
}
private async ValueTask PromptAsync(IWorkflowContext context, CancellationToken cancellationToken)
@@ -128,7 +128,7 @@ internal sealed class QuestionExecutor(Question model, WorkflowFormulaState stat
DataValue defaultValue = this.Evaluator.GetValue(defaultValueExpression).Value;
await this.AssignAsync(this.Model.Variable?.Path, defaultValue.ToFormula(), context).ConfigureAwait(false);
string defaultValueResponse = this.FormatPrompt(this.Model.DefaultValueResponse);
await context.AddEventAsync(new MessageActivityEvent(defaultValueResponse.Trim())).ConfigureAwait(false);
await context.AddEventAsync(new MessageActivityEvent(defaultValueResponse.Trim()), cancellationToken).ConfigureAwait(false);
await context.SendResultMessageAsync(this.Id, result: null, cancellationToken).ConfigureAwait(false);
}
else
@@ -17,7 +17,7 @@ internal sealed class ResetVariableExecutor(ResetVariable model, WorkflowFormula
protected override async ValueTask<object?> ExecuteAsync(IWorkflowContext context, CancellationToken cancellationToken = default)
{
Throw.IfNull(this.Model.Variable, $"{nameof(this.Model)}.{nameof(model.Variable)}");
await context.QueueStateResetAsync(this.Model.Variable).ConfigureAwait(false);
await context.QueueStateResetAsync(this.Model.Variable, cancellationToken).ConfigureAwait(false);
Debug.WriteLine(
$"""
STATE: {this.GetType().Name} [{this.Id}]
@@ -18,7 +18,7 @@ internal sealed class SendActivityExecutor(SendActivity model, WorkflowFormulaSt
{
string activityText = this.Engine.Format(messageActivity.Text).Trim();
await context.AddEventAsync(new MessageActivityEvent(activityText.Trim())).ConfigureAwait(false);
await context.AddEventAsync(new MessageActivityEvent(activityText.Trim()), cancellationToken).ConfigureAwait(false);
}
return default;