Skip to content

Commit bb830c1

Browse files
committed
Add support for workflow events to OAI responses impl
1 parent 9070c53 commit bb830c1

File tree

5 files changed

+95
-3
lines changed

5 files changed

+95
-3
lines changed

dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/EndpointRouteBuilderExtensions.Responses.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
using System;
44
using System.Diagnostics.CodeAnalysis;
5-
using System.IO;
6-
using System.Text.Json;
75
using System.Threading;
86
using Microsoft.Agents.AI;
97
using Microsoft.Agents.AI.Hosting.OpenAI.Responses;

dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AIAgentResponsesProcessor.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Linq;
55
using System.Net.ServerSentEvents;
66
using System.Text.Json;
7-
using System.Text.Json.Serialization;
87
using System.Threading;
98
using System.Threading.Tasks;
109
using Microsoft.Agents.AI.Hosting.OpenAI.Responses.Models;

dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AgentRunResponseUpdateExtensions.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
using System.Collections.Generic;
55
using System.Linq;
66
using System.Runtime.CompilerServices;
7+
using System.Text.Json;
78
using System.Threading;
89
using System.Threading.Tasks;
910
using Microsoft.Agents.AI.Hosting.OpenAI.Responses.Converters;
1011
using Microsoft.Agents.AI.Hosting.OpenAI.Responses.Models;
1112
using Microsoft.Agents.AI.Hosting.OpenAI.Responses.Streaming;
13+
using Microsoft.Agents.AI.Workflows;
1214
using Microsoft.Extensions.AI;
1315

1416
namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses;
@@ -50,6 +52,13 @@ internal static async IAsyncEnumerable<StreamingResponseEvent> ToStreamingRespon
5052
cancellationToken.ThrowIfCancellationRequested();
5153
var update = updateEnumerator.Current;
5254

55+
// Special-case for agent framework workflow events.
56+
if (update.RawRepresentation is WorkflowEvent workflowEvent)
57+
{
58+
yield return CreateWorkflowEventResponse(workflowEvent, seq.Increment(), outputIndex);
59+
continue;
60+
}
61+
5362
if (!IsSameMessage(update, previousUpdate))
5463
{
5564
// Finalize the current generator when moving to a new message.
@@ -192,4 +201,47 @@ static bool IsSameValue(string? str1, string? str2) =>
192201
static bool IsSameRole(ChatRole? value1, ChatRole? value2) =>
193202
!value1.HasValue || !value2.HasValue || value1.Value == value2.Value;
194203
}
204+
205+
private static StreamingWorkflowEventComplete CreateWorkflowEventResponse(WorkflowEvent workflowEvent, int sequenceNumber, int outputIndex)
206+
{
207+
// Extract executor_id if this is an ExecutorEvent
208+
string? executorId = null;
209+
if (workflowEvent is ExecutorEvent execEvent)
210+
{
211+
executorId = execEvent.ExecutorId;
212+
}
213+
JsonElement eventData;
214+
if (JsonSerializer.IsReflectionEnabledByDefault)
215+
{
216+
var eventDataDict = new Dictionary<string, object?>
217+
{
218+
["event_type"] = workflowEvent.GetType().Name,
219+
["data"] = workflowEvent.Data,
220+
["executor_id"] = executorId,
221+
["timestamp"] = DateTime.UtcNow.ToString("O")
222+
};
223+
224+
#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
225+
#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
226+
eventData = JsonSerializer.SerializeToElement(eventDataDict, ResponsesJsonSerializerOptions.Default);
227+
#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
228+
#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
229+
}
230+
else
231+
{
232+
eventData = JsonSerializer.SerializeToElement(
233+
"Unsupported. Workflow event serialization is currently only supported when JsonSerializer.IsReflectionEnabledByDefault is true.",
234+
ResponsesJsonContext.Default.String);
235+
}
236+
237+
// Create the properly typed streaming workflow event
238+
return new StreamingWorkflowEventComplete
239+
{
240+
SequenceNumber = sequenceNumber,
241+
OutputIndex = outputIndex,
242+
Data = eventData,
243+
ExecutorId = executorId,
244+
ItemId = $"wf_{Guid.NewGuid().ToString("N")[..8]}"
245+
};
246+
}
195247
}

dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/StreamingResponseEvent.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses.Models;
2626
[JsonDerivedType(typeof(StreamingFunctionCallArgumentsDone), StreamingFunctionCallArgumentsDone.EventType)]
2727
[JsonDerivedType(typeof(StreamingReasoningSummaryTextDelta), StreamingReasoningSummaryTextDelta.EventType)]
2828
[JsonDerivedType(typeof(StreamingReasoningSummaryTextDone), StreamingReasoningSummaryTextDone.EventType)]
29+
[JsonDerivedType(typeof(StreamingWorkflowEventComplete), StreamingWorkflowEventComplete.EventType)]
2930
internal abstract record StreamingResponseEvent
3031
{
3132
/// <summary>
@@ -517,3 +518,44 @@ internal sealed record StreamingReasoningSummaryTextDone : StreamingResponseEven
517518
[JsonPropertyName("text")]
518519
public required string Text { get; init; }
519520
}
521+
522+
/// <summary>
523+
/// Represents a streaming response event containing a workflow event.
524+
/// This event is sent during workflow execution to provide observability into workflow steps,
525+
/// executor invocations, errors, and other workflow lifecycle events.
526+
/// </summary>
527+
internal sealed record StreamingWorkflowEventComplete : StreamingResponseEvent
528+
{
529+
/// <summary>
530+
/// The constant event type identifier for workflow event events.
531+
/// </summary>
532+
public const string EventType = "response.workflow_event.complete";
533+
534+
/// <inheritdoc/>
535+
[JsonIgnore]
536+
public override string Type => EventType;
537+
538+
/// <summary>
539+
/// Gets or sets the index of the output in the response.
540+
/// </summary>
541+
[JsonPropertyName("output_index")]
542+
public int OutputIndex { get; set; }
543+
544+
/// <summary>
545+
/// Gets or sets the workflow event data containing event type, executor ID, and event-specific data.
546+
/// </summary>
547+
[JsonPropertyName("data")]
548+
public JsonElement? Data { get; set; }
549+
550+
/// <summary>
551+
/// Gets or sets the executor ID if this is an executor-scoped event.
552+
/// </summary>
553+
[JsonPropertyName("executor_id")]
554+
public string? ExecutorId { get; set; }
555+
556+
/// <summary>
557+
/// Gets or sets the item ID for tracking purposes.
558+
/// </summary>
559+
[JsonPropertyName("item_id")]
560+
public string? ItemId { get; set; }
561+
}

dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/ResponsesJsonContext.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses;
3030
[JsonSerializable(typeof(StreamingOutputTextDone))]
3131
[JsonSerializable(typeof(StreamingFunctionCallArgumentsDelta))]
3232
[JsonSerializable(typeof(StreamingFunctionCallArgumentsDone))]
33+
[JsonSerializable(typeof(StreamingWorkflowEventComplete))]
3334
[JsonSerializable(typeof(ReasoningOptions))]
3435
[JsonSerializable(typeof(ResponseUsage))]
3536
[JsonSerializable(typeof(ResponseError))]

0 commit comments

Comments
 (0)