@switch (selectedProtocol)
{
- case Protocol.A2A:
- 🔗 A2A protocol supports long-running agentic processes
+ case Protocol.OpenAIResponses:
+ ֎ OpenAI Responses
break;
- case Protocol.AgenticFramework:
- default:
- ⚡ Direct agentic framework communication
+ case Protocol.A2A:
+ default:
+ 🔗 A2A protocol supports long-running agentic processes
break;
}
@@ -881,208 +882,212 @@
@code {
- private string currentMessage = "";
- private bool isStreaming = false;
- private bool isLoadingAgents = true;
- private string currentStreamedMessage = "";
- private string selectedAgentName = "";
- private List
availableAgents = new();
- private List conversations = new();
- private Conversation? currentConversation;
+ private string currentMessage = "";
+ private bool isStreaming = false;
+ private bool isLoadingAgents = true;
+ private string currentStreamedMessage = "";
+ private string selectedAgentName = "";
+ private List availableAgents = new();
+ private List conversations = new();
+ private Conversation? currentConversation;
- // protocol
- private Protocol selectedProtocol;
+ // protocol
+ private Protocol selectedProtocol;
- // a2a agent card
- private bool isA2AExpanded = false;
- private bool isDiscoveringCard = false;
- private string? discoveredAgentCardJson = null;
- private string? discoveryError = null;
+ // a2a agent card
+ private bool isA2AExpanded = false;
+ private bool isDiscoveringCard = false;
+ private string? discoveredAgentCardJson = null;
+ private string? discoveryError = null;
- private enum Protocol
- {
- AgenticFramework,
- A2A // Agent2Agent protocol
- }
+ private enum Protocol
+ {
+ A2A, // Agent-to-Agent protocol
+ OpenAIResponses
+ }
- private sealed class Conversation
- {
- public string SessionId { get; set; } = Guid.NewGuid().ToString("N");
- public string AgentName { get; set; } = "";
- public List Messages { get; set; } = new();
- }
+ private sealed class Conversation
+ {
+ public string SessionId { get; set; } = Guid.NewGuid().ToString("N");
+ public string AgentName { get; set; } = "";
+ public List Messages { get; set; } = new();
+ }
- protected override async Task OnInitializedAsync()
- {
- Logger.LogDebug("Initializing Agent Chat component");
+ protected override async Task OnInitializedAsync()
+ {
+ Logger.LogDebug("Initializing Agent Chat component");
- // Load agents
- try
- {
- availableAgents = await AgentClient.GetAgentsAsync();
- Logger.LogInformation("Loaded {AgentCount} agents", availableAgents.Count);
- Logger.LogInformation("Loaded Agents info: {AgentData}", JsonSerializer.Serialize(availableAgents, new JsonSerializerOptions() { WriteIndented = true }));
+ // Load agents
+ try
+ {
+ availableAgents = await AgentClient.GetAgentsAsync();
+ Logger.LogInformation("Loaded {AgentCount} agents", availableAgents.Count);
+ Logger.LogInformation("Loaded Agents info: {AgentData}", JsonSerializer.Serialize(availableAgents, new JsonSerializerOptions() { WriteIndented = true }));
- // Default to first agent and start a conversation
- if (availableAgents.Any())
- {
- selectedAgentName = availableAgents.First().Name!;
- StartNewConversation();
- }
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Failed to load agents");
- }
- finally
- {
- isLoadingAgents = false;
- }
+ // Default to first agent and start a conversation
+ if (availableAgents.Any())
+ {
+ selectedAgentName = availableAgents.First().Name!;
+ StartNewConversation();
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Failed to load agents");
+ }
+ finally
+ {
+ isLoadingAgents = false;
+ }
- // Conversations start fresh on page load
- }
+ // Conversations start fresh on page load
+ }
- private string GetAgentIcon(string agentName) => agentName?.ToLower() switch
- {
- "pirate" => "🏴☠️",
- "knights-and-knaves" => "⚔️",
- _ => "🤖"
- };
+ private string GetAgentIcon(string agentName) => agentName?.ToLower() switch
+ {
+ "pirate" => "🏴☠️",
+ "knights-and-knaves" => "⚔️",
+ _ => "🤖"
+ };
- private string GetAgentDisplayName(string agentName) => agentName?.ToLower() switch
- {
- "pirate" => "Pirate",
- "knights-and-knaves" => "Knights & Knaves",
- _ => agentName ?? "Agent"
- };
+ private string GetAgentDisplayName(string agentName) => agentName?.ToLower() switch
+ {
+ "pirate" => "Pirate",
+ "knights-and-knaves" => "Knights & Knaves",
+ _ => agentName ?? "Agent"
+ };
- private void ToggleA2AExpanded() => isA2AExpanded = !isA2AExpanded;
+ private void ToggleA2AExpanded() => isA2AExpanded = !isA2AExpanded;
- private async Task DiscoverAgentCard()
- {
- if (string.IsNullOrEmpty(selectedAgentName) || isDiscoveringCard)
- return;
+ private async Task DiscoverAgentCard()
+ {
+ if (string.IsNullOrEmpty(selectedAgentName) || isDiscoveringCard)
+ return;
- isDiscoveringCard = true;
- discoveryError = null;
- discoveredAgentCardJson = null;
- StateHasChanged();
+ isDiscoveringCard = true;
+ discoveryError = null;
+ discoveredAgentCardJson = null;
+ StateHasChanged();
- try
- {
- Logger.LogInformation("Discovering agent card for agent: {AgentName}", selectedAgentName);
- var agentCard = await A2AActorClient.GetAgentCardAsync(selectedAgentName);
- if (agentCard is not null)
- {
- discoveredAgentCardJson = JsonSerializer.Serialize(agentCard, new JsonSerializerOptions() { WriteIndented = true });
- Logger.LogInformation("Successfully discovered agent card for {AgentName}: {CardData}", selectedAgentName, discoveredAgentCardJson);
- }
- else
- {
- discoveryError = "No agent card found for this agent.";
- }
- }
- catch (Exception ex)
- {
- Logger.LogError(ex, "Failed to discover agent card for {AgentName}", selectedAgentName);
- discoveryError = $"Failed to discover agent card: {ex.Message}";
- }
- finally
- {
- isDiscoveringCard = false;
- StateHasChanged();
- }
- }
+ try
+ {
+ Logger.LogInformation("Discovering agent card for agent: {AgentName}", selectedAgentName);
+ var agentCard = await A2AActorClient.GetAgentCardAsync(selectedAgentName);
+ if (agentCard is not null)
+ {
+ discoveredAgentCardJson = JsonSerializer.Serialize(agentCard, new JsonSerializerOptions() { WriteIndented = true });
+ Logger.LogInformation("Successfully discovered agent card for {AgentName}: {CardData}", selectedAgentName, discoveredAgentCardJson);
+ }
+ else
+ {
+ discoveryError = "No agent card found for this agent.";
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Failed to discover agent card for {AgentName}", selectedAgentName);
+ discoveryError = $"Failed to discover agent card: {ex.Message}";
+ }
+ finally
+ {
+ isDiscoveringCard = false;
+ StateHasChanged();
+ }
+ }
- private void StartNewConversation()
- {
- if (string.IsNullOrEmpty(selectedAgentName))
- return;
+ private void StartNewConversation()
+ {
+ if (string.IsNullOrEmpty(selectedAgentName))
+ return;
- var newConversation = new Conversation
+ var newConversation = new Conversation
{
AgentName = selectedAgentName
};
- conversations.Add(newConversation);
- currentConversation = newConversation;
+ conversations.Add(newConversation);
+ currentConversation = newConversation;
- Logger.LogInformation("Started new conversation with agent: {AgentName}, session: {SessionId}",
- newConversation.AgentName, newConversation.SessionId);
+ Logger.LogInformation("Started new conversation with agent: {AgentName}, session: {SessionId}",
+ newConversation.AgentName, newConversation.SessionId);
- StateHasChanged();
- }
+ StateHasChanged();
+ }
- private void SelectConversation(string sessionId)
- {
- currentConversation = conversations.FirstOrDefault(c => c.SessionId == sessionId);
- if (currentConversation is not null)
- {
- selectedAgentName = currentConversation.AgentName;
- Logger.LogDebug("Selected conversation with session: {SessionId}", sessionId);
- }
- StateHasChanged();
- }
+ private void SelectConversation(string sessionId)
+ {
+ currentConversation = conversations.FirstOrDefault(c => c.SessionId == sessionId);
+ if (currentConversation is not null)
+ {
+ selectedAgentName = currentConversation.AgentName;
+ Logger.LogDebug("Selected conversation with session: {SessionId}", sessionId);
+ }
+ StateHasChanged();
+ }
- private void CloseConversation(string sessionId)
- {
- var conversationToRemove = conversations.FirstOrDefault(c => c.SessionId == sessionId);
- if (conversationToRemove is not null)
- {
- conversations.Remove(conversationToRemove);
+ private void CloseConversation(string sessionId)
+ {
+ var conversationToRemove = conversations.FirstOrDefault(c => c.SessionId == sessionId);
+ if (conversationToRemove is not null)
+ {
+ conversations.Remove(conversationToRemove);
- if (currentConversation?.SessionId == sessionId)
- {
- currentConversation = conversations.FirstOrDefault();
- if (currentConversation is not null)
- {
- selectedAgentName = currentConversation.AgentName;
- }
- }
+ if (currentConversation?.SessionId == sessionId)
+ {
+ currentConversation = conversations.FirstOrDefault();
+ if (currentConversation is not null)
+ {
+ selectedAgentName = currentConversation.AgentName;
+ }
+ }
- Logger.LogInformation("Closed conversation with session: {SessionId}", sessionId);
- }
- StateHasChanged();
- }
+ Logger.LogInformation("Closed conversation with session: {SessionId}", sessionId);
+ }
+ StateHasChanged();
+ }
- private async Task SendMessage()
- {
- if (string.IsNullOrWhiteSpace(currentMessage) || isStreaming || currentConversation is null)
- return;
+ private async Task SendMessage()
+ {
+ if (string.IsNullOrWhiteSpace(currentMessage) || isStreaming || currentConversation is null)
+ return;
- var userMessage = currentMessage.Trim();
- currentMessage = "";
+ var userMessage = currentMessage.Trim();
+ currentMessage = "";
- Logger.LogInformation("User sending message: '{UserMessage}' to agent {AgentName} in session {SessionId}",
- userMessage, currentConversation.AgentName, currentConversation.SessionId);
+ Logger.LogInformation("User sending message: '{UserMessage}' to agent {AgentName} in session {SessionId}",
+ userMessage, currentConversation.AgentName, currentConversation.SessionId);
- // Add user message to chat
- currentConversation.Messages.Add(new ChatMessage(ChatRole.User, userMessage));
- StateHasChanged();
- await ScrollToBottom();
+ // Add user message to chat
+ currentConversation.Messages.Add(new ChatMessage(ChatRole.User, userMessage));
+ StateHasChanged();
+ await ScrollToBottom();
- // Start streaming response
- isStreaming = true;
- currentStreamedMessage = "";
- StateHasChanged();
+ // Start streaming response
+ isStreaming = true;
+ currentStreamedMessage = "";
+ StateHasChanged();
- StringBuilder responseContent = new();
- var hasReceivedContent = false;
+ StringBuilder responseContent = new();
+ var hasReceivedContent = false;
- using var timeoutCts = new CancellationTokenSource(
+ using var timeoutCts = new CancellationTokenSource(
#if DEBUG
TimeSpan.FromSeconds(120)
#else
- TimeSpan.FromSeconds(20)
+ TimeSpan.FromSeconds(20)
#endif
- );
+ );
- try
- {
+ try
+ {
- // Select the appropriate client based on protocol
+ // Select the appropriate client based on protocol
+ IAgentClient agentClient = selectedProtocol switch
+ {
+ Protocol.OpenAIResponses => OpenAIResponsesAgentClient,
+ Protocol.A2A or _ => A2AActorClient
+ };
- var agentClient = A2AActorClient;
var messages = new List { new(ChatRole.User, userMessage) };
await foreach (var update in agentClient.RunStreamingAsync(
diff --git a/dotnet/samples/AgentWebChat/AgentWebChat.Web/OpenAIResponsesAgentClient.cs b/dotnet/samples/AgentWebChat/AgentWebChat.Web/OpenAIResponsesAgentClient.cs
new file mode 100644
index 0000000000..8ec81c0e48
--- /dev/null
+++ b/dotnet/samples/AgentWebChat/AgentWebChat.Web/OpenAIResponsesAgentClient.cs
@@ -0,0 +1,50 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.ClientModel;
+using System.Runtime.CompilerServices;
+using A2A;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+using OpenAI;
+using OpenAI.Responses;
+
+namespace AgentWebChat.Web;
+
+///
+/// Is a simple frontend client which exercises the ability of exposed agent to communicate via OpenAI Responses protocol.
+///
+internal sealed class OpenAIResponsesAgentClient : IAgentClient
+{
+ private readonly Uri _baseUri;
+
+ public OpenAIResponsesAgentClient(string baseUri)
+ {
+ this._baseUri = new Uri(baseUri.TrimEnd('/'));
+ }
+
+ public async IAsyncEnumerable RunStreamingAsync(
+ string agentName,
+ IList messages,
+ string? threadId = null,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ OpenAIClientOptions options = new()
+ {
+ Endpoint = new Uri(this._baseUri, $"/{agentName}/v1/")
+ };
+
+ var openAiClient = new OpenAIResponseClient(model: "myModel!", credential: new ApiKeyCredential("dummy-key"), options: options).AsIChatClient();
+ var chatOptions = new ChatOptions()
+ {
+ ConversationId = threadId
+ };
+
+ await foreach (var update in openAiClient.GetStreamingResponseAsync(messages, chatOptions, cancellationToken: cancellationToken))
+ {
+ yield return new AgentRunResponseUpdate(update);
+ }
+ }
+
+ public Task GetAgentCardAsync(string agentName, CancellationToken cancellationToken = default)
+ => Task.FromResult(null!);
+}
diff --git a/dotnet/samples/AgentWebChat/AgentWebChat.Web/Program.cs b/dotnet/samples/AgentWebChat/AgentWebChat.Web/Program.cs
index 0467990e1f..2cee27e269 100644
--- a/dotnet/samples/AgentWebChat/AgentWebChat.Web/Program.cs
+++ b/dotnet/samples/AgentWebChat/AgentWebChat.Web/Program.cs
@@ -23,6 +23,7 @@ Uri a2aAddress = new("http://localhost:5390/a2a");
builder.Services.AddHttpClient(client => client.BaseAddress = baseAddress);
builder.Services.AddSingleton(sp => new A2AAgentClient(sp.GetRequiredService>(), a2aAddress));
+builder.Services.AddSingleton(sp => new OpenAIResponsesAgentClient("http://localhost:5390"));
var app = builder.Build();
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/EndpointRouteBuilderExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/EndpointRouteBuilderExtensions.cs
new file mode 100644
index 0000000000..ca58dfd27b
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/EndpointRouteBuilderExtensions.cs
@@ -0,0 +1,98 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.ClientModel.Primitives;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using Microsoft.Agents.AI.Hosting.OpenAI.Responses;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.DependencyInjection;
+using OpenAI.Responses;
+
+namespace Microsoft.Agents.AI.Hosting.OpenAI;
+
+///
+/// Provides extension methods for mapping OpenAI Responses capabilities to an .
+///
+public static class EndpointRouteBuilderExtensions
+{
+ ///
+ /// Maps OpenAI Responses API endpoints to the specified for the given .
+ ///
+ /// The to add the OpenAI Responses endpoints to.
+ /// The name of the AI agent service registered in the dependency injection container. This name is used to resolve the instance from the keyed services.
+ /// Custom route path for the responses endpoint.
+ /// Custom route path for the conversations endpoint.
+ public static void MapOpenAIResponses(
+ this IEndpointRouteBuilder endpoints,
+ string agentName,
+ [StringSyntax("Route")] string? responsesPath = null,
+ [StringSyntax("Route")] string? conversationsPath = null)
+ {
+ ArgumentNullException.ThrowIfNull(endpoints);
+ ArgumentNullException.ThrowIfNull(agentName);
+ if (responsesPath is null || conversationsPath is null)
+ {
+ ValidateAgentName(agentName);
+ }
+
+ var agent = endpoints.ServiceProvider.GetRequiredKeyedService(agentName);
+
+ responsesPath ??= $"/{agentName}/v1/responses";
+ var responsesRouteGroup = endpoints.MapGroup(responsesPath);
+ MapResponses(responsesRouteGroup, agent);
+
+ // Will be included once we obtain the API to operate with thread (conversation).
+
+ // conversationsPath ??= $"/{agentName}/v1/conversations";
+ // var conversationsRouteGroup = endpoints.MapGroup(conversationsPath);
+ // MapConversations(conversationsRouteGroup, agent, loggerFactory);
+ }
+
+ private static void MapResponses(IEndpointRouteBuilder routeGroup, AIAgent agent)
+ {
+ var endpointAgentName = agent.DisplayName;
+ var responsesProcessor = new AIAgentResponsesProcessor(agent);
+
+ routeGroup.MapPost("/", async (HttpContext requestContext, CancellationToken cancellationToken) =>
+ {
+ var requestBinary = await BinaryData.FromStreamAsync(requestContext.Request.Body, cancellationToken).ConfigureAwait(false);
+
+ var responseOptions = new ResponseCreationOptions();
+ var responseOptionsJsonModel = responseOptions as IJsonModel;
+ Debug.Assert(responseOptionsJsonModel is not null);
+
+ responseOptions = responseOptionsJsonModel.Create(requestBinary, ModelReaderWriterOptions.Json);
+ if (responseOptions is null)
+ {
+ return Results.BadRequest("Invalid request payload.");
+ }
+
+ return await responsesProcessor.CreateModelResponseAsync(responseOptions, cancellationToken).ConfigureAwait(false);
+ }).WithName(endpointAgentName + "/CreateResponse");
+ }
+
+#pragma warning disable IDE0051 // Remove unused private members
+ private static void MapConversations(IEndpointRouteBuilder routeGroup, AIAgent agent)
+#pragma warning restore IDE0051 // Remove unused private members
+ {
+ var endpointAgentName = agent.DisplayName;
+ var conversationsProcessor = new AIAgentConversationsProcessor(agent);
+
+ routeGroup.MapGet("/{conversation_id}", (string conversationId, CancellationToken cancellationToken)
+ => conversationsProcessor.GetConversationAsync(conversationId, cancellationToken)
+ ).WithName(endpointAgentName + "/RetrieveConversation");
+ }
+
+ private static void ValidateAgentName([NotNull] string agentName)
+ {
+ var escaped = Uri.EscapeDataString(agentName);
+ if (!string.Equals(escaped, agentName, StringComparison.OrdinalIgnoreCase))
+ {
+ throw new ArgumentException($"Agent name '{agentName}' contains characters invalid for URL routes.", nameof(agentName));
+ }
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Microsoft.Agents.AI.Hosting.OpenAI.csproj b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Microsoft.Agents.AI.Hosting.OpenAI.csproj
new file mode 100644
index 0000000000..cc618b54bc
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Microsoft.Agents.AI.Hosting.OpenAI.csproj
@@ -0,0 +1,36 @@
+
+
+
+ $(ProjectsCoreTargetFrameworks)
+ $(ProjectsCoreTargetFrameworks)
+ $(NoWarn);IDE1006;IDE0130;NU1504;OPENAI001
+ Microsoft.Agents.AI.Hosting.OpenAI
+ alpha
+ $(InterceptorsNamespaces);Microsoft.AspNetCore.Http.Generated
+ true
+
+
+
+
+ true
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AIAgentConversationsProcessor.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AIAgentConversationsProcessor.cs
new file mode 100644
index 0000000000..b846d4c32c
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AIAgentConversationsProcessor.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses;
+
+internal sealed class AIAgentConversationsProcessor
+{
+#pragma warning disable IDE0052 // Remove unread private members
+ private readonly AIAgent _aiAgent;
+#pragma warning restore IDE0052 // Remove unread private members
+
+ public AIAgentConversationsProcessor(AIAgent aiAgent)
+ {
+ this._aiAgent = aiAgent ?? throw new ArgumentNullException(nameof(aiAgent));
+ }
+
+ public async Task GetConversationAsync(string conversationId, CancellationToken cancellationToken)
+ {
+ // TODO come back to it later
+ throw new NotImplementedException();
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AIAgentResponsesProcessor.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AIAgentResponsesProcessor.cs
new file mode 100644
index 0000000000..0eefa37f2c
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/AIAgentResponsesProcessor.cs
@@ -0,0 +1,184 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Buffers;
+using System.ClientModel.Primitives;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Net.ServerSentEvents;
+using System.Runtime.CompilerServices;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Agents.AI;
+using Microsoft.Agents.AI.Hosting.OpenAI.Responses.Model;
+using Microsoft.Agents.AI.Hosting.OpenAI.Responses.Utils;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.AI;
+using OpenAI.Responses;
+
+namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses;
+
+///
+/// OpenAI Responses processor associated with a specific .
+///
+internal sealed class AIAgentResponsesProcessor
+{
+ private readonly AIAgent _agent;
+
+ public AIAgentResponsesProcessor(AIAgent agent)
+ {
+ this._agent = agent ?? throw new ArgumentNullException(nameof(agent));
+ }
+
+ public async Task CreateModelResponseAsync(ResponseCreationOptions responseCreationOptions, CancellationToken cancellationToken)
+ {
+ var options = new OpenAIResponsesRunOptions();
+ AgentThread? agentThread = null; // not supported to resolve from conversationId
+
+ var inputItems = responseCreationOptions.GetInput();
+ var chatMessages = inputItems.AsChatMessages();
+
+ if (responseCreationOptions.GetStream())
+ {
+ return new OpenAIStreamingResponsesResult(this._agent, chatMessages);
+ }
+
+ var agentResponse = await this._agent.RunAsync(chatMessages, agentThread, options, cancellationToken).ConfigureAwait(false);
+ return new OpenAIResponseResult(agentResponse);
+ }
+
+ private sealed class OpenAIResponseResult(AgentRunResponse agentResponse) : IResult
+ {
+ public async Task ExecuteAsync(HttpContext httpContext)
+ {
+ // note: OpenAI SDK types provide their own serialization implementation
+ // so we cant simply return IResult wrap for the typed-object.
+ // instead writing to the response body can be done.
+
+ var cancellationToken = httpContext.RequestAborted;
+ var response = httpContext.Response;
+
+ var chatResponse = agentResponse.AsChatResponse();
+ var openAIResponse = chatResponse.AsOpenAIResponse();
+ var openAIResponseJsonModel = openAIResponse as IJsonModel;
+ Debug.Assert(openAIResponseJsonModel is not null);
+
+ var writer = new Utf8JsonWriter(response.BodyWriter, new JsonWriterOptions { SkipValidation = false });
+ openAIResponseJsonModel.Write(writer, ModelReaderWriterOptions.Json);
+ await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ private sealed class OpenAIStreamingResponsesResult(AIAgent agent, IEnumerable chatMessages) : IResult
+ {
+ public Task ExecuteAsync(HttpContext httpContext)
+ {
+ var cancellationToken = httpContext.RequestAborted;
+ var response = httpContext.Response;
+
+ // Set SSE headers
+ response.Headers.ContentType = "text/event-stream";
+ response.Headers.CacheControl = "no-cache,no-store";
+ response.Headers.Connection = "keep-alive";
+ response.Headers.ContentEncoding = "identity";
+ httpContext.Features.GetRequiredFeature().DisableBuffering();
+
+ return SseFormatter.WriteAsync(
+ source: this.GetStreamingResponsesAsync(cancellationToken),
+ destination: response.Body,
+ itemFormatter: (sseItem, bufferWriter) =>
+ {
+ var jsonTypeInfo = OpenAIResponsesJsonUtilities.DefaultOptions.GetTypeInfo(sseItem.Data.GetType());
+ var json = JsonSerializer.SerializeToUtf8Bytes(sseItem.Data, jsonTypeInfo);
+ bufferWriter.Write(json);
+ },
+ cancellationToken);
+ }
+
+ private async IAsyncEnumerable> GetStreamingResponsesAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ var sequenceNumber = 1;
+ var outputIndex = 1;
+ AgentThread? agentThread = null;
+
+ ResponseItem? lastResponseItem = null;
+ OpenAIResponse? lastOpenAIResponse = null;
+
+ await foreach (var update in agent.RunStreamingAsync(chatMessages, thread: agentThread, cancellationToken: cancellationToken).ConfigureAwait(false))
+ {
+ if (string.IsNullOrEmpty(update.ResponseId)
+ && string.IsNullOrEmpty(update.MessageId)
+ && update.Contents is not { Count: > 0 })
+ {
+ continue;
+ }
+
+ if (sequenceNumber == 1)
+ {
+ lastOpenAIResponse = update.AsChatResponse().AsOpenAIResponse();
+
+ var responseCreated = new StreamingCreatedResponse(sequenceNumber++)
+ {
+ Response = lastOpenAIResponse
+ };
+ yield return new(responseCreated, responseCreated.Type);
+ }
+
+ if (update.Contents is not { Count: > 0 })
+ {
+ continue;
+ }
+
+ // to help convert the AIContent into OpenAI ResponseItem we pack it into the known "chatMessage"
+ // and use existing convertion extension method
+ var chatMessage = new ChatMessage(ChatRole.Assistant, update.Contents)
+ {
+ MessageId = update.MessageId,
+ CreatedAt = update.CreatedAt,
+ RawRepresentation = update.RawRepresentation
+ };
+
+ foreach (var openAIResponseItem in MicrosoftExtensionsAIResponsesExtensions.AsOpenAIResponseItems([chatMessage]))
+ {
+ if (chatMessage.MessageId is not null)
+ {
+ openAIResponseItem.SetId(chatMessage.MessageId);
+ }
+
+ lastResponseItem = openAIResponseItem;
+
+ var responseOutputItemAdded = new StreamingOutputItemAddedResponse(sequenceNumber++)
+ {
+ OutputIndex = outputIndex++,
+ Item = openAIResponseItem
+ };
+ yield return new(responseOutputItemAdded, responseOutputItemAdded.Type);
+ }
+ }
+
+ if (lastResponseItem is not null)
+ {
+ // we were streaming "response.output_item.added" before
+ // so we should complete it now via "response.output_item.done"
+ var responseOutputDoneAdded = new StreamingOutputItemDoneResponse(sequenceNumber++)
+ {
+ OutputIndex = outputIndex++,
+ Item = lastResponseItem
+ };
+ yield return new(responseOutputDoneAdded, responseOutputDoneAdded.Type);
+ }
+
+ if (lastOpenAIResponse is not null)
+ {
+ // complete the whole streaming with the full response model
+ var responseCompleted = new StreamingCompletedResponse(sequenceNumber++)
+ {
+ Response = lastOpenAIResponse
+ };
+ yield return new(responseCompleted, responseCompleted.Type);
+ }
+ }
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Model/ResponseStreamEvent.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Model/ResponseStreamEvent.cs
new file mode 100644
index 0000000000..b9bc0ed51c
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Model/ResponseStreamEvent.cs
@@ -0,0 +1,166 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json.Serialization;
+using OpenAI.Responses;
+
+namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses.Model;
+
+///
+/// Abstract base class for all streaming response events in the OpenAI Responses API.
+/// Provides common properties shared across all streaming event types.
+///
+[JsonPolymorphic(UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization)]
+[JsonDerivedType(typeof(StreamingOutputItemAddedResponse), StreamingOutputItemAddedResponse.EventType)]
+[JsonDerivedType(typeof(StreamingOutputItemDoneResponse), StreamingOutputItemDoneResponse.EventType)]
+[JsonDerivedType(typeof(StreamingCreatedResponse), StreamingCreatedResponse.EventType)]
+[JsonDerivedType(typeof(StreamingCompletedResponse), StreamingCompletedResponse.EventType)]
+internal abstract class StreamingResponseEventBase
+{
+ ///
+ /// Gets or sets the type identifier for the streaming response event.
+ /// This property is used to discriminate between different event types during serialization.
+ ///
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
+
+ ///
+ /// Gets or sets the sequence number of this event in the streaming response.
+ /// Events are numbered sequentially starting from 1 to maintain ordering.
+ ///
+ [JsonPropertyName("sequence_number")]
+ public int SequenceNumber { get; set; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The type identifier for this streaming response event.
+ /// The sequence number of this event in the streaming response.
+ [JsonConstructor]
+ public StreamingResponseEventBase(string type, int sequenceNumber)
+ {
+ this.Type = type;
+ this.SequenceNumber = sequenceNumber;
+ }
+}
+
+///
+/// Represents a streaming response event indicating that a new output item has been added to the response.
+/// This event is sent when the AI agent produces a new piece of content during streaming.
+///
+internal sealed class StreamingOutputItemAddedResponse : StreamingResponseEventBase
+{
+ ///
+ /// The constant event type identifier for output item added events.
+ ///
+ public const string EventType = "response.output_item.added";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The sequence number of this event in the streaming response.
+ public StreamingOutputItemAddedResponse(int sequenceNumber) : base(EventType, sequenceNumber)
+ {
+ }
+
+ ///
+ /// Gets or sets the index of the output in the response where this item was added.
+ /// Multiple outputs can exist in a single response, and this identifies which one.
+ ///
+ [JsonPropertyName("output_index")]
+ public int OutputIndex { get; set; }
+
+ ///
+ /// Gets or sets the response item that was added to the output.
+ /// This contains the actual content or data produced by the AI agent.
+ ///
+ [JsonPropertyName("item")]
+ public ResponseItem? Item { get; set; }
+}
+
+///
+/// Represents a streaming response event indicating that an output item has been completed.
+/// This event is sent when the AI agent finishes producing a particular piece of content.
+///
+internal sealed class StreamingOutputItemDoneResponse : StreamingResponseEventBase
+{
+ ///
+ /// The constant event type identifier for output item done events.
+ ///
+ public const string EventType = "response.output_item.done";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The sequence number of this event in the streaming response.
+ public StreamingOutputItemDoneResponse(int sequenceNumber) : base(EventType, sequenceNumber)
+ {
+ }
+
+ ///
+ /// Gets or sets the index of the output in the response where this item was completed.
+ /// This corresponds to the same output index from the associated .
+ ///
+ [JsonPropertyName("output_index")]
+ public int OutputIndex { get; set; }
+
+ ///
+ /// Gets or sets the completed response item.
+ /// This contains the final version of the content produced by the AI agent.
+ ///
+ [JsonPropertyName("item")]
+ public ResponseItem? Item { get; set; }
+}
+
+///
+/// Represents a streaming response event indicating that a new response has been created and streaming has begun.
+/// This is typically the first event sent in a streaming response sequence.
+///
+internal sealed class StreamingCreatedResponse : StreamingResponseEventBase
+{
+ ///
+ /// The constant event type identifier for response created events.
+ ///
+ public const string EventType = "response.created";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The sequence number of this event in the streaming response.
+ public StreamingCreatedResponse(int sequenceNumber) : base(EventType, sequenceNumber)
+ {
+ }
+
+ ///
+ /// Gets or sets the OpenAI response object that was created.
+ /// This contains metadata about the response including ID, creation timestamp, and other properties.
+ ///
+ [JsonPropertyName("response")]
+ public required OpenAIResponse Response { get; set; }
+}
+
+///
+/// Represents a streaming response event indicating that the response has been completed.
+/// This is typically the last event sent in a streaming response sequence.
+///
+internal sealed class StreamingCompletedResponse : StreamingResponseEventBase
+{
+ ///
+ /// The constant event type identifier for response completed events.
+ ///
+ public const string EventType = "response.completed";
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The sequence number of this event in the streaming response.
+ public StreamingCompletedResponse(int sequenceNumber) : base(EventType, sequenceNumber)
+ {
+ }
+
+ ///
+ /// Gets or sets the completed OpenAI response object.
+ /// This contains the final state of the response including all generated content and metadata.
+ ///
+ [JsonPropertyName("response")]
+ public required OpenAIResponse Response { get; set; }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/OpenAIResponsesRunOptions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/OpenAIResponsesRunOptions.cs
new file mode 100644
index 0000000000..14ec11fe8e
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/OpenAIResponsesRunOptions.cs
@@ -0,0 +1,8 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses;
+
+internal sealed class OpenAIResponsesRunOptions : AgentRunOptions
+{
+ public bool Background { get; init; }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/AgentRunResponseUpdateExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/AgentRunResponseUpdateExtensions.cs
new file mode 100644
index 0000000000..63e66f25cb
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/AgentRunResponseUpdateExtensions.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses.Utils;
+
+internal static class AgentRunResponseUpdateExtensions
+{
+ ///
+ /// Converts an instance to a .
+ ///
+ /// The to convert. Cannot be null.
+ /// The role of agent run response contents. By default is .
+ /// A populated with values from .
+ public static ChatResponse AsChatResponse(this AgentRunResponseUpdate response, ChatRole? role = null) => new()
+ {
+ CreatedAt = response.CreatedAt,
+ ResponseId = response.ResponseId,
+ RawRepresentation = response.RawRepresentation,
+ AdditionalProperties = response.AdditionalProperties,
+ Messages = [new ChatMessage(response.Role ?? role ?? ChatRole.Assistant, response.Contents)]
+ };
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/OpenAIResponseJsonConverter.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/OpenAIResponseJsonConverter.cs
new file mode 100644
index 0000000000..c2ff8131d8
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/OpenAIResponseJsonConverter.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.ClientModel.Primitives;
+using System.Diagnostics;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using OpenAI.Responses;
+
+namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses.Utils;
+
+internal sealed class OpenAIResponseJsonConverter : JsonConverter
+{
+ public override OpenAIResponse? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var item = OpenAIResponsesModelFactory.OpenAIResponse();
+ var jsonModel = item as IJsonModel;
+ Debug.Assert(jsonModel is not null, "OpenAIResponse should implement IJsonModel");
+
+ return jsonModel.Create(ref reader, ModelReaderWriterOptions.Json);
+ }
+
+ public override void Write(Utf8JsonWriter writer, OpenAIResponse value, JsonSerializerOptions options)
+ {
+ var jsonModel = value as IJsonModel;
+ Debug.Assert(jsonModel is not null, "OpenAIResponse should implement IJsonModel");
+
+ jsonModel.Write(writer, ModelReaderWriterOptions.Json);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/OpenAIResponsesJsonUtilities.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/OpenAIResponsesJsonUtilities.cs
new file mode 100644
index 0000000000..d7ee539526
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/OpenAIResponsesJsonUtilities.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Microsoft.Agents.AI.Hosting.OpenAI.Responses.Model;
+
+namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses.Utils;
+
+internal static partial class OpenAIResponsesJsonUtilities
+{
+ ///
+ /// Gets the singleton used as the default in JSON serialization operations.
+ ///
+ ///
+ ///
+ /// For Native AOT or applications disabling , this instance
+ /// includes source generated contracts for all common exchange types contained in this library.
+ ///
+ ///
+ /// It additionally turns on the following settings:
+ ///
+ /// - Enables defaults.
+ /// - Enables as the default ignore condition for properties.
+ /// - Enables as the default number handling for number types.
+ ///
+ ///
+ ///
+ public static JsonSerializerOptions DefaultOptions { get; } = CreateDefaultOptions();
+
+ ///
+ /// Creates default options to use for agents-related serialization.
+ ///
+ /// The configured options.
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL3050:RequiresDynamicCode", Justification = "Converter is guarded by IsReflectionEnabledByDefault check.")]
+ [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access", Justification = "Converter is guarded by IsReflectionEnabledByDefault check.")]
+ private static JsonSerializerOptions CreateDefaultOptions()
+ {
+ JsonSerializerOptions options = new(JsonContext.Default.Options);
+
+ options.Converters.Add(new ResponseItemJsonConverter());
+ options.Converters.Add(new OpenAIResponseJsonConverter());
+
+ options.MakeReadOnly();
+ return options;
+ }
+
+ [JsonSerializable(typeof(StreamingResponseEventBase))]
+
+ [ExcludeFromCodeCoverage]
+ private sealed partial class JsonContext : JsonSerializerContext;
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/ResponseCreationOptionsExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/ResponseCreationOptionsExtensions.cs
new file mode 100644
index 0000000000..a91811c793
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/ResponseCreationOptionsExtensions.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Microsoft.Shared.Diagnostics;
+using OpenAI.Responses;
+
+namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses.Utils;
+
+[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline", Justification = "Specifically for accessing hidden members")]
+[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations", Justification = "Specifically for accessing hidden members")]
+internal static class ResponseCreationOptionsExtensions
+{
+ private static readonly Func _getStreamNullable;
+ private static readonly Func> _getInput;
+
+ static ResponseCreationOptionsExtensions()
+ {
+ // OpenAI SDK does not have a simple way to get the input as a c# object.
+ // However, it does parse most of the interesting fields into internal properties of `ResponseCreationOptions` object.
+
+ // --- Stream (internal bool? Stream { get; set; }) ---
+ const string streamPropName = "Stream";
+ var streamProp = typeof(ResponseCreationOptions).GetProperty(streamPropName, BindingFlags.Instance | BindingFlags.NonPublic)
+ ?? throw new MissingMemberException(typeof(ResponseCreationOptions).FullName!, streamPropName);
+ var streamGetter = streamProp.GetGetMethod(nonPublic: true) ?? throw new MissingMethodException($"{streamPropName} getter not found.");
+
+ _getStreamNullable = streamGetter.CreateDelegate>();
+
+ // --- Input (internal IList Input { get; set; }) ---
+ const string inputPropName = "Input";
+ var inputProp = typeof(ResponseCreationOptions).GetProperty(inputPropName, BindingFlags.Instance | BindingFlags.NonPublic)
+ ?? throw new MissingMemberException(typeof(ResponseCreationOptions).FullName!, inputPropName);
+ var inputGetter = inputProp.GetGetMethod(nonPublic: true)
+ ?? throw new MissingMethodException($"{inputPropName} getter not found.");
+
+ _getInput = inputGetter.CreateDelegate>>();
+ }
+
+ public static bool GetStream(this ResponseCreationOptions options)
+ {
+ Throw.IfNull(options);
+ return _getStreamNullable(options) ?? false;
+ }
+
+ public static IList GetInput(this ResponseCreationOptions options)
+ {
+ Throw.IfNull(options);
+ return _getInput(options);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/ResponseItemExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/ResponseItemExtensions.cs
new file mode 100644
index 0000000000..f3e6dbb98d
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/ResponseItemExtensions.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Reflection;
+using Microsoft.Shared.Diagnostics;
+using OpenAI.Responses;
+
+namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses.Utils;
+
+[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline", Justification = "Specifically for accessing hidden members")]
+[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations", Justification = "Specifically for accessing hidden members")]
+internal static class ResponseItemExtensions
+{
+ private static readonly Action _setId;
+
+ static ResponseItemExtensions()
+ {
+ // OpenAI SDK ResponseItem has an internal setter for Id property.
+ // We need to access it via reflection to set the Id when creating response items.
+
+ // --- Id (public string Id { get; internal set; }) ---
+ const string idPropName = "Id";
+ var idProp = typeof(ResponseItem).GetProperty(idPropName, BindingFlags.Instance | BindingFlags.Public)
+ ?? throw new MissingMemberException(typeof(ResponseItem).FullName!, idPropName);
+ var idSetter = idProp.GetSetMethod(nonPublic: true) ?? throw new MissingMethodException($"{idPropName} setter not found.");
+
+ _setId = idSetter.CreateDelegate>();
+ }
+
+ ///
+ /// Sets the Id property on a ResponseItem using reflection to access the internal setter.
+ ///
+ /// The ResponseItem to set the Id on.
+ /// The Id value to set.
+ public static void SetId(this ResponseItem responseItem, string id)
+ {
+ Throw.IfNull(responseItem);
+ _setId(responseItem, id);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/ResponseItemJsonConverter.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/ResponseItemJsonConverter.cs
new file mode 100644
index 0000000000..6b39029b84
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Utils/ResponseItemJsonConverter.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.ClientModel.Primitives;
+using System.Diagnostics;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using OpenAI.Responses;
+
+namespace Microsoft.Agents.AI.Hosting.OpenAI.Responses.Utils;
+
+internal sealed class ResponseItemJsonConverter : JsonConverter
+{
+ public override ResponseItem? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var item = ResponseItem.CreateUserMessageItem(""); // no other way to instantiate it.
+ var jsonModel = item as IJsonModel;
+ Debug.Assert(jsonModel is not null, "ResponseItem should implement IJsonModel");
+ return jsonModel.Create(ref reader, ModelReaderWriterOptions.Json);
+ }
+
+ public override void Write(Utf8JsonWriter writer, ResponseItem? value, JsonSerializerOptions options)
+ {
+ if (value is null)
+ {
+ writer.WriteNullValue();
+ return;
+ }
+
+ var jsonmodel = value as IJsonModel;
+ Debug.Assert(jsonmodel is not null, "ResponseItem should implement IJsonModel");
+ jsonmodel.Write(writer, ModelReaderWriterOptions.Json);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/TurnToken.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/TurnToken.cs
index 7f65e3e9d3..91a0833cc0 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/TurnToken.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/TurnToken.cs
@@ -1,10 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
+using Microsoft.Extensions.AI;
+
namespace Microsoft.Agents.AI.Workflows;
///
/// Sent to an -based executor to request
-/// a response to accumulated .
+/// a response to accumulated .
///
/// Whether to raise AgentRunEvents for this executor.
public class TurnToken(bool? emitEvents = null)
diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs
index 27436f42d0..edca371f5d 100644
--- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs
+++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs
@@ -601,7 +601,7 @@ public sealed partial class ChatClientAgent : AIAgent
{
throw new InvalidOperationException(
$"""
- The {nameof(chatOptions.ConversationId)} provided via {nameof(Extensions.AI.ChatOptions)} is different to the id of the provided {nameof(AgentThread)}.
+ The {nameof(chatOptions.ConversationId)} provided via {nameof(this.ChatOptions)} is different to the id of the provided {nameof(AgentThread)}.
Only one id can be used for a run.
""");
}