using Microsoft.Agents.AI.Workflows;
namespace MicrosoftAgentFrameworkLesson.ConsoleApp.Events;
// ── Data Models ──────────────────────────────────────────
public sealed class FoodOrder
{
public int OrderId { get; set; }
public string DishName { get; set; } = string.Empty;
public int Quantity { get; set; }
}
public sealed class PreparedDish
{
public int OrderId { get; set; }
public string DishName { get; set; } = string.Empty;
public int Quantity { get; set; }
public string Chef { get; set; } = string.Empty;
public int CookMinutes { get; set; }
}
public sealed class QualityReport
{
public int OrderId { get; set; }
public string DishName { get; set; } = string.Empty;
public int Score { get; set; }
public string Verdict { get; set; } = string.Empty;
public override string ToString() =>
$"Order #{OrderId} '{DishName}' — Score: {Score}/10 — {Verdict}";
}
// ── Custom Events ────────────────────────────────────────
/// <summary>Custom event: emitted when the kitchen starts cooking.</summary>
internal sealed class CookingStartedEvent(string message) : WorkflowEvent(message) { }
/// <summary>Custom event: emitted after quality inspection.</summary>
internal sealed class QualityCheckedEvent(string message) : WorkflowEvent(message) { }
// ── Executors (Executor<TIn, TOut> pattern) ──────────────
/// <summary>Receives a FoodOrder, assigns a chef and cook time, returns PreparedDish.</summary>
internal sealed class KitchenExecutor()
: Executor<FoodOrder, PreparedDish>("Kitchen")
{
public override async ValueTask<PreparedDish> HandleAsync(
FoodOrder order, IWorkflowContext ctx, CancellationToken ct = default)
{
// Emit custom event
await ctx.AddEventAsync(
new CookingStartedEvent($"Chef started preparing '{order.DishName}' x{order.Quantity}"));
int cookMinutes = order.Quantity * 5;
string chef = order.Quantity > 3 ? "Head Chef" : "Line Cook";
return new PreparedDish
{
OrderId = order.OrderId,
DishName = order.DishName,
Quantity = order.Quantity,
Chef = chef,
CookMinutes = cookMinutes
};
}
}
/// <summary>Inspects the dish and returns a quality verdict string.</summary>
internal sealed class QualityCheckExecutor()
: Executor<PreparedDish, string>("QualityCheck")
{
public override async ValueTask<string> HandleAsync(
PreparedDish dish, IWorkflowContext ctx, CancellationToken ct = default)
{
int score = dish.Chef == "Head Chef" ? 9 : 7;
string verdict = score >= 8 ? "Excellent — ready to serve" : "Good — needs garnish";
await ctx.AddEventAsync(
new QualityCheckedEvent($"Quality check: '{dish.DishName}' scored {score}/10"));
var report = new QualityReport
{
OrderId = dish.OrderId,
DishName = dish.DishName,
Score = score,
Verdict = verdict
};
return report.ToString();
}
}
// ── Demo Runner ──────────────────────────────────────────
public static class RestaurantEventsDemo
{
public static async Task RunAsync()
{
Console.WriteLine("=== Microsoft Agent Framework — Events Demo ===");
Console.WriteLine();
await Demo1_BuiltInEvents();
await Demo2_CustomEvents();
await Demo3_FilterEvents();
}
// ─────────────────────────────────────────────────────
// Demo 1: Built-in Events
// Iterate run.OutgoingEvents to view every event type
// ─────────────────────────────────────────────────────
private static async Task Demo1_BuiltInEvents()
{
Console.WriteLine("── Demo 1: Built-in Events ──");
var workflow = BuildWorkflow();
Run run = await InProcessExecution.RunAsync(
workflow,
new FoodOrder { OrderId = 101, DishName = "Margherita Pizza", Quantity = 2 });
foreach (WorkflowEvent evt in run.OutgoingEvents)
{
switch (evt)
{
case SuperStepStartedEvent:
Console.WriteLine(" [SUPERSTEP] ── step started ──");
break;
case ExecutorInvokedEvent inv:
Console.WriteLine($" [INVOKED] {inv.ExecutorId}");
break;
case ExecutorCompletedEvent comp:
Console.WriteLine($" [COMPLETED] {comp.ExecutorId} → {comp.Data}");
break;
case SuperStepCompletedEvent:
Console.WriteLine(" [SUPERSTEP] ── step completed ──");
break;
case WorkflowOutputEvent output:
Console.WriteLine($" [OUTPUT] {output.Data}");
break;
case WorkflowErrorEvent error:
Console.WriteLine($" [ERROR] {error.Exception?.Message}");
break;
// Custom events also appear here
case CookingStartedEvent cook:
Console.WriteLine($" [CUSTOM] {cook.Data}");
break;
case QualityCheckedEvent qc:
Console.WriteLine($" [CUSTOM] {qc.Data}");
break;
default:
Console.WriteLine($" [EVENT] {evt.GetType().Name}: {evt.Data}");
break;
}
}
Console.WriteLine();
}
// ─────────────────────────────────────────────────────
// Demo 2: Custom Events
// Focus only on CookingStartedEvent and QualityCheckedEvent
// ─────────────────────────────────────────────────────
private static async Task Demo2_CustomEvents()
{
Console.WriteLine("── Demo 2: Custom Events ──");
var workflow = BuildWorkflow();
Run run = await InProcessExecution.RunAsync(
workflow,
new FoodOrder { OrderId = 202, DishName = "Beef Stew", Quantity = 5 });
foreach (WorkflowEvent evt in run.OutgoingEvents)
{
switch (evt)
{
case CookingStartedEvent cook:
Console.WriteLine($" [COOKING] {cook.Data}");
break;
case QualityCheckedEvent qc:
Console.WriteLine($" [QUALITY] {qc.Data}");
break;
case WorkflowOutputEvent output:
Console.WriteLine($" [OUTPUT] {output.Data}");
break;
}
}
Console.WriteLine();
}
// ─────────────────────────────────────────────────────
// Demo 3: Filtering — count all events, display only output
// ─────────────────────────────────────────────────────
private static async Task Demo3_FilterEvents()
{
Console.WriteLine("── Demo 3: Filtering Events ──");
var workflow = BuildWorkflow();
Run run = await InProcessExecution.RunAsync(
workflow,
new FoodOrder { OrderId = 303, DishName = "Caesar Salad", Quantity = 1 });
int total = 0;
foreach (WorkflowEvent evt in run.OutgoingEvents)
{
total++;
if (evt is WorkflowOutputEvent output)
Console.WriteLine($" [RESULT] {output.Data}");
else if (evt is WorkflowErrorEvent error)
Console.WriteLine($" [ERROR] {error.Exception?.Message}");
}
Console.WriteLine($" (Total events: {total} — only output/error displayed)");
Console.WriteLine();
}
// ── Helper ───────────────────────────────────────────
private static Workflow BuildWorkflow()
{
var kitchen = new KitchenExecutor().BindExecutor();
var quality = new QualityCheckExecutor().BindExecutor();
return new WorkflowBuilder(kitchen)
.AddEdge(kitchen, quality)
.WithOutputFrom(quality)
.Build();
}
}