diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 2bfb78486c..05979b090f 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -592,6 +592,7 @@
+
diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/HostedAgentUserAgentPolicy.cs b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/HostedAgentUserAgentPolicy.cs
index 8d5d330471..c4130599a9 100644
--- a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/HostedAgentUserAgentPolicy.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/HostedAgentUserAgentPolicy.cs
@@ -19,7 +19,7 @@ namespace Microsoft.Agents.AI.Foundry.Hosting;
///
///
/// This policy is added at request time (per-call )
-/// by when invoking the wrapped
+/// by when invoking the wrapped
/// . It is only registered when an agent is
/// resolved by the Foundry hosting layer.
///
diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/Microsoft.Agents.AI.Foundry.Hosting.csproj b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/Microsoft.Agents.AI.Foundry.Hosting.csproj
index af02c44aad..209edd9a82 100644
--- a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/Microsoft.Agents.AI.Foundry.Hosting.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/Microsoft.Agents.AI.Foundry.Hosting.csproj
@@ -44,7 +44,7 @@
-
+
diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/ServiceCollectionExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/ServiceCollectionExtensions.cs
index 49eb745b6b..a84bbd3a6b 100644
--- a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/ServiceCollectionExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/ServiceCollectionExtensions.cs
@@ -210,7 +210,7 @@ internal static AIAgent ApplyOpenTelemetry(AIAgent agent)
///
/// Attempts to wrap the agent's underlying
- /// with a so every outgoing Responses-API request
+ /// with a so every outgoing Responses-API request
/// carries the hosted-agent User-Agent segment.
///
///
@@ -219,7 +219,7 @@ internal static AIAgent ApplyOpenTelemetry(AIAgent agent)
///
/// - exposes no ;
/// - the chat client is not backed by MEAI's internal OpenAIResponsesChatClient (e.g., a non-OpenAI provider or a custom impl);
- /// - the inner is already a .
+ /// - the inner is already a .
///
///
///
@@ -261,12 +261,12 @@ internal static AIAgent TryApplyUserAgent(AIAgent agent)
}
var current = field.GetValue(meaiInstance) as ResponsesClient;
- if (current is null or DelegatingResponsesClient)
+ if (current is null or UserAgentResponsesClient)
{
return agent;
}
- field.SetValue(meaiInstance, new DelegatingResponsesClient(current));
+ field.SetValue(meaiInstance, new UserAgentResponsesClient(current));
return agent;
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/DelegatingResponsesClient.cs b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/UserAgentResponsesClient.cs
similarity index 94%
rename from dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/DelegatingResponsesClient.cs
rename to dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/UserAgentResponsesClient.cs
index ebf878b49a..aaddfd89da 100644
--- a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/DelegatingResponsesClient.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/UserAgentResponsesClient.cs
@@ -33,11 +33,11 @@ namespace Microsoft.Agents.AI.Foundry.Hosting;
/// never expected to run; the throwing transport surfaces any unexpected escape route loudly.
///
///
-internal sealed class DelegatingResponsesClient : ResponsesClient
+internal sealed class UserAgentResponsesClient : ResponsesClient
{
private readonly ResponsesClient _inner;
- public DelegatingResponsesClient(ResponsesClient inner)
+ public UserAgentResponsesClient(ResponsesClient inner)
: base(BuildDummyPipeline(), new OpenAIClientOptions { Endpoint = inner?.Endpoint })
{
this._inner = inner ?? throw new ArgumentNullException(nameof(inner));
@@ -104,7 +104,7 @@ private static ClientPipeline BuildDummyPipeline()
private sealed class ThrowingTransport : PipelineTransport
{
private const string Message =
- "DelegatingResponsesClient transport invoked bypassed the override-and-delegate design. This exception should be unreachable and should never be thrown following the correct usage of DelegatingResponsesClient.";
+ "UserAgentResponsesClient transport invoked bypassed the override-and-delegate design. This exception should be unreachable and should never be thrown following the correct usage of UserAgentResponsesClient.";
protected override PipelineMessage CreateMessageCore() => throw new InvalidOperationException(Message);
protected override void ProcessCore(PipelineMessage message) => throw new InvalidOperationException(Message);
diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry/Microsoft.Agents.AI.Foundry.csproj b/dotnet/src/Microsoft.Agents.AI.Foundry/Microsoft.Agents.AI.Foundry.csproj
index cd4200df75..71e7df373f 100644
--- a/dotnet/src/Microsoft.Agents.AI.Foundry/Microsoft.Agents.AI.Foundry.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/Microsoft.Agents.AI.Foundry.csproj
@@ -53,6 +53,7 @@
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/AgentFrameworkResponseHandlerTelemetryTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTelemetryTests.cs
similarity index 99%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/AgentFrameworkResponseHandlerTelemetryTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTelemetryTests.cs
index 48daf490ee..dcb4e7e212 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/AgentFrameworkResponseHandlerTelemetryTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTelemetryTests.cs
@@ -10,7 +10,6 @@
using System.Threading.Tasks;
using Azure.AI.AgentServer.Responses;
using Azure.AI.AgentServer.Responses.Models;
-using Microsoft.Agents.AI.Foundry.Hosting;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
@@ -19,7 +18,7 @@
using OpenTelemetry.Trace;
using MeaiTextContent = Microsoft.Extensions.AI.TextContent;
-namespace Microsoft.Agents.AI.Foundry.UnitTests.Hosting;
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
///
/// Tests that verify OTel spans are actually emitted and captured through the
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/AgentFrameworkResponseHandlerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTests.cs
similarity index 99%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/AgentFrameworkResponseHandlerTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTests.cs
index d7fc8fc884..eaf396eccc 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/AgentFrameworkResponseHandlerTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTests.cs
@@ -9,7 +9,6 @@
using System.Threading.Tasks;
using Azure.AI.AgentServer.Responses;
using Azure.AI.AgentServer.Responses.Models;
-using Microsoft.Agents.AI.Foundry.Hosting;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -17,7 +16,7 @@
using Moq;
using MeaiTextContent = Microsoft.Extensions.AI.TextContent;
-namespace Microsoft.Agents.AI.Foundry.UnitTests.Hosting;
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
public class AgentFrameworkResponseHandlerTests
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerWorkflowTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerWorkflowTests.cs
new file mode 100644
index 0000000000..5954ddafc5
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerWorkflowTests.cs
@@ -0,0 +1,212 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Azure.AI.AgentServer.Responses;
+using Azure.AI.AgentServer.Responses.Models;
+using Microsoft.Agents.AI.Workflows;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
+
+///
+/// Unit tests for that verify behavior
+/// when the registered agent is a workflow-backed . These exercise
+/// real workflow builders and the in-process execution environment to drive the handler
+/// through realistic streaming event patterns.
+///
+public class AgentFrameworkResponseHandlerWorkflowTests
+{
+ [Fact]
+ public async Task SequentialWorkflow_SingleAgent_ProducesTextOutputAsync()
+ {
+ // Arrange: single-agent sequential workflow
+ var echoAgent = new StreamingTextAgent("echo", "Hello from the workflow!");
+ var workflow = AgentWorkflowBuilder.BuildSequential("test-sequential", echoAgent);
+ var workflowAgent = workflow.AsAIAgent(
+ id: "workflow-agent",
+ name: "Test Workflow",
+ executionEnvironment: InProcessExecution.OffThread,
+ includeExceptionDetails: true);
+
+ var (handler, request, context) = CreateHandlerWithAgent(workflowAgent, "Hello");
+
+ // Act
+ var events = await CollectEventsAsync(handler, request, context);
+
+ // Assert: should have lifecycle events + at least one text output + terminal
+ Assert.IsType(events[0]);
+ Assert.IsType(events[1]);
+ Assert.True(events.Count >= 4, $"Expected at least 4 events, got {events.Count}");
+
+ var lastEvent = events[^1];
+ Assert.True(
+ lastEvent is ResponseCompletedEvent || lastEvent is ResponseFailedEvent,
+ $"Expected terminal event, got {lastEvent.GetType().Name}");
+ }
+
+ [Fact]
+ public async Task SequentialWorkflow_TwoAgents_ProducesOutputFromBothAsync()
+ {
+ // Arrange: two agents in sequence
+ var agent1 = new StreamingTextAgent("agent1", "First agent says hello");
+ var agent2 = new StreamingTextAgent("agent2", "Second agent says goodbye");
+ var workflow = AgentWorkflowBuilder.BuildSequential("test-sequential-2", agent1, agent2);
+ var workflowAgent = workflow.AsAIAgent(
+ id: "seq-workflow",
+ name: "Sequential Workflow",
+ executionEnvironment: InProcessExecution.OffThread,
+ includeExceptionDetails: true);
+
+ var (handler, request, context) = CreateHandlerWithAgent(workflowAgent, "Process this");
+
+ // Act
+ var events = await CollectEventsAsync(handler, request, context);
+
+ // Assert: should have workflow action events for executor lifecycle
+ var lastEvent = events[^1];
+ Assert.True(
+ lastEvent is ResponseCompletedEvent || lastEvent is ResponseFailedEvent,
+ $"Expected terminal event, got {lastEvent.GetType().Name}");
+
+ // Should have output item events (either text messages or workflow actions)
+ Assert.True(events.OfType().Any(),
+ "Expected at least one output item from the workflow");
+ }
+
+ [Fact]
+ public async Task Workflow_AgentThrowsException_ProducesErrorOutputAsync()
+ {
+ // Arrange: workflow with an agent that throws
+ var throwingAgent = new ThrowingStreamingAgent("thrower", new InvalidOperationException("Agent crashed"));
+ var workflow = AgentWorkflowBuilder.BuildSequential("test-error", throwingAgent);
+ var workflowAgent = workflow.AsAIAgent(
+ id: "error-workflow",
+ name: "Error Workflow",
+ executionEnvironment: InProcessExecution.OffThread,
+ includeExceptionDetails: true);
+
+ var (handler, request, context) = CreateHandlerWithAgent(workflowAgent, "Trigger error");
+
+ // Act
+ var events = await CollectEventsAsync(handler, request, context);
+
+ // Assert: should have lifecycle events + error/failure indicator
+ Assert.IsType(events[0]);
+ Assert.IsType(events[1]);
+
+ var lastEvent = events[^1];
+ // Workflow errors surface as either Failed or Completed (depending on error handling)
+ Assert.True(
+ lastEvent is ResponseCompletedEvent || lastEvent is ResponseFailedEvent,
+ $"Expected terminal event, got {lastEvent.GetType().Name}");
+ }
+
+ [Fact]
+ public async Task Workflow_ExecutorEvents_ProduceWorkflowActionItemsAsync()
+ {
+ // Arrange
+ var agent = new StreamingTextAgent("test-agent", "Result");
+ var workflow = AgentWorkflowBuilder.BuildSequential("test-actions", agent);
+ var workflowAgent = workflow.AsAIAgent(
+ id: "actions-workflow",
+ name: "Actions Workflow",
+ executionEnvironment: InProcessExecution.OffThread);
+
+ var (handler, request, context) = CreateHandlerWithAgent(workflowAgent, "Hello");
+
+ // Act
+ var events = await CollectEventsAsync(handler, request, context);
+
+ // Assert: workflow should produce OutputItemAdded events for executor lifecycle
+ var addedEvents = events.OfType().ToList();
+ Assert.True(addedEvents.Count >= 1,
+ $"Expected at least 1 output item added event, got {addedEvents.Count}");
+ }
+
+ [Fact]
+ public async Task WorkflowAgent_RegisteredWithKey_ResolvesCorrectlyAsync()
+ {
+ // Arrange: workflow agent registered with a keyed service name
+ var agent = new StreamingTextAgent("inner", "Keyed workflow response");
+ var workflow = AgentWorkflowBuilder.BuildSequential("keyed-wf", agent);
+ var workflowAgent = workflow.AsAIAgent(
+ id: "keyed-workflow",
+ name: "Keyed Workflow",
+ executionEnvironment: InProcessExecution.OffThread);
+
+ var services = new ServiceCollection();
+ services.AddSingleton(new InMemoryAgentSessionStore());
+ services.AddKeyedSingleton("my-workflow", workflowAgent);
+ var sp = services.BuildServiceProvider();
+
+ var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
+ var request = new CreateResponse { Model = "test", AgentReference = new AgentReference("my-workflow") };
+ request.Input = CreateUserInput("Test keyed workflow");
+ var mockContext = CreateMockContext();
+
+ // Act
+ var events = await CollectEventsAsync(handler, request, mockContext.Object);
+
+ // Assert
+ Assert.IsType(events[0]);
+ Assert.True(events.Count >= 3, $"Expected at least 3 events, got {events.Count}");
+ }
+
+ private static (AgentFrameworkResponseHandler handler, CreateResponse request, ResponseContext context)
+ CreateHandlerWithAgent(AIAgent agent, string userMessage)
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton(new InMemoryAgentSessionStore());
+ services.AddSingleton(agent);
+ services.AddSingleton>(NullLogger.Instance);
+ var sp = services.BuildServiceProvider();
+
+ var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
+ var request = new CreateResponse { Model = "test" };
+ request.Input = CreateUserInput(userMessage);
+ var mockContext = CreateMockContext();
+
+ return (handler, request, mockContext.Object);
+ }
+
+ private static BinaryData CreateUserInput(string text)
+ {
+ return BinaryData.FromObjectAsJson(new[]
+ {
+ new { type = "message", id = "msg_in_1", status = "completed", role = "user",
+ content = new[] { new { type = "input_text", text } }
+ }
+ });
+ }
+
+ private static Mock CreateMockContext()
+ {
+ var mock = new Mock("resp_" + new string('0', 46)) { CallBase = true };
+ mock.Setup(x => x.GetHistoryAsync(It.IsAny()))
+ .ReturnsAsync(Array.Empty());
+ mock.Setup(x => x.GetInputItemsAsync(It.IsAny(), It.IsAny()))
+ .ReturnsAsync(Array.Empty- ());
+ return mock;
+ }
+
+ private static async Task
> CollectEventsAsync(
+ AgentFrameworkResponseHandler handler,
+ CreateResponse request,
+ ResponseContext context)
+ {
+ var events = new List();
+ await foreach (var evt in handler.CreateAsync(request, context, CancellationToken.None))
+ {
+ events.Add(evt);
+ }
+
+ return events;
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FakeAuthenticationTokenProvider.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FakeAuthenticationTokenProvider.cs
new file mode 100644
index 0000000000..96d542379a
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FakeAuthenticationTokenProvider.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.ClientModel;
+using System.ClientModel.Primitives;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
+
+internal sealed class FakeAuthenticationTokenProvider : AuthenticationTokenProvider
+{
+ public override GetTokenOptions? CreateTokenOptions(IReadOnlyDictionary properties)
+ {
+ return new GetTokenOptions(new Dictionary());
+ }
+
+ public override AuthenticationToken GetToken(GetTokenOptions options, CancellationToken cancellationToken)
+ {
+ return new AuthenticationToken("token-value", "token-type", DateTimeOffset.UtcNow.AddHours(1));
+ }
+
+ public override ValueTask GetTokenAsync(GetTokenOptions options, CancellationToken cancellationToken)
+ {
+ return new ValueTask(this.GetToken(options, cancellationToken));
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryAIToolExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryAIToolExtensionsTests.cs
similarity index 96%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryAIToolExtensionsTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryAIToolExtensionsTests.cs
index f083a9946e..022fbf0bfe 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryAIToolExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryAIToolExtensionsTests.cs
@@ -1,9 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
-using Microsoft.Agents.AI.Foundry.Hosting;
-namespace Microsoft.Agents.AI.Foundry.UnitTests.Hosting;
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
public class FoundryAIToolExtensionsTests
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryToolboxBearerTokenHandlerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxBearerTokenHandlerTests.cs
similarity index 98%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryToolboxBearerTokenHandlerTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxBearerTokenHandlerTests.cs
index cea48d8eb0..e619445481 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryToolboxBearerTokenHandlerTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxBearerTokenHandlerTests.cs
@@ -6,10 +6,9 @@
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
-using Microsoft.Agents.AI.Foundry.Hosting;
using Moq;
-namespace Microsoft.Agents.AI.Foundry.UnitTests.Hosting;
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
public class FoundryToolboxBearerTokenHandlerTests
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryToolboxServiceTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxServiceTests.cs
similarity index 96%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryToolboxServiceTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxServiceTests.cs
index 24f7433c4e..cdcdf5ee8e 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryToolboxServiceTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxServiceTests.cs
@@ -4,11 +4,10 @@
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
-using Microsoft.Agents.AI.Foundry.Hosting;
using Microsoft.Extensions.Options;
using Moq;
-namespace Microsoft.Agents.AI.Foundry.UnitTests.Hosting;
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
public class FoundryToolboxServiceTests
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryToolboxTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxTests.cs
similarity index 99%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryToolboxTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxTests.cs
index 3f0a8a54dd..6c4be484a7 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryToolboxTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxTests.cs
@@ -9,13 +9,12 @@
using System.Threading.Tasks;
using Azure.AI.Projects;
using Azure.AI.Projects.Agents;
-using Microsoft.Agents.AI.Foundry.Hosting;
using Microsoft.Extensions.AI;
#pragma warning disable OPENAI001
#pragma warning disable AAIP001
-namespace Microsoft.Agents.AI.Foundry.UnitTests;
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
///
/// Unit tests for the class.
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/HostedOutboundUserAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HostedOutboundUserAgentTests.cs
similarity index 98%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/HostedOutboundUserAgentTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HostedOutboundUserAgentTests.cs
index 30c4bef9de..31981509e4 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/HostedOutboundUserAgentTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HostedOutboundUserAgentTests.cs
@@ -9,7 +9,6 @@
using System.Threading;
using System.Threading.Tasks;
using Azure.AI.Extensions.OpenAI;
-using Microsoft.Agents.AI.Foundry.Hosting;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.TestHost;
@@ -18,7 +17,7 @@
#pragma warning disable OPENAI001, SCME0001, SCME0002, MEAI001
-namespace Microsoft.Agents.AI.Foundry.UnitTests.Hosting;
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
///
/// End-to-end tests that exercise the FULL hosted ASP.NET Core pipeline:
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HttpHandlerAssert.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HttpHandlerAssert.cs
new file mode 100644
index 0000000000..f8b3f5ba30
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HttpHandlerAssert.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
+
+internal sealed class HttpHandlerAssert : HttpClientHandler
+{
+ private readonly Func? _assertion;
+ private readonly Func>? _assertionAsync;
+
+ public HttpHandlerAssert(Func assertion)
+ {
+ this._assertion = assertion;
+ }
+ public HttpHandlerAssert(Func> assertionAsync)
+ {
+ this._assertionAsync = assertionAsync;
+ }
+
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ if (this._assertionAsync is not null)
+ {
+ return await this._assertionAsync.Invoke(request);
+ }
+
+ return this._assertion!.Invoke(request);
+ }
+
+#if NET
+ protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ return this._assertion!(request);
+ }
+#endif
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/InputConverterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/InputConverterTests.cs
similarity index 99%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/InputConverterTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/InputConverterTests.cs
index f10a015c4b..f4a46940e9 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/InputConverterTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/InputConverterTests.cs
@@ -3,11 +3,10 @@
using System;
using System.Linq;
using Azure.AI.AgentServer.Responses.Models;
-using Microsoft.Agents.AI.Foundry.Hosting;
using Microsoft.Extensions.AI;
using MeaiTextContent = Microsoft.Extensions.AI.TextContent;
-namespace Microsoft.Agents.AI.Foundry.UnitTests.Hosting;
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
public class InputConverterTests
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests.csproj
new file mode 100644
index 0000000000..fa28a32494
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests.csproj
@@ -0,0 +1,35 @@
+
+
+
+ $(TargetFrameworksCore)
+ false
+ $(NoWarn);NU1605;NU1903
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/OutputConverterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterTests.cs
similarity index 99%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/OutputConverterTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterTests.cs
index 2b8abfefcb..5fa9a435a1 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/OutputConverterTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterTests.cs
@@ -7,13 +7,12 @@
using System.Threading.Tasks;
using Azure.AI.AgentServer.Responses;
using Azure.AI.AgentServer.Responses.Models;
-using Microsoft.Agents.AI.Foundry.Hosting;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
using Moq;
using MeaiTextContent = Microsoft.Extensions.AI.TextContent;
-namespace Microsoft.Agents.AI.Foundry.UnitTests.Hosting;
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
public class OutputConverterTests
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterWorkflowTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterWorkflowTests.cs
new file mode 100644
index 0000000000..f991744292
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterWorkflowTests.cs
@@ -0,0 +1,213 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Azure.AI.AgentServer.Responses;
+using Azure.AI.AgentServer.Responses.Models;
+using Microsoft.Agents.AI.Workflows;
+using Microsoft.Extensions.AI;
+using Moq;
+using MeaiTextContent = Microsoft.Extensions.AI.TextContent;
+
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
+
+///
+/// Unit tests for driven directly by hand-crafted update
+/// sequences that mirror the patterns produced by real workflow executions
+/// (sequential, group chat, code executor, sub-workflow, mixed content types).
+///
+public class OutputConverterWorkflowTests
+{
+ [Fact]
+ public async Task SequentialWorkflowPattern_ProducesCorrectEventsAsync()
+ {
+ // Simulate what WorkflowSession produces for a 2-agent sequential workflow
+ var (stream, _) = CreateTestStream();
+ var updates = new[]
+ {
+ // Superstep 1: Agent 1
+ new AgentResponseUpdate { RawRepresentation = new SuperStepStartedEvent(1) },
+ new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("agent_1", "start") },
+ new AgentResponseUpdate { MessageId = "msg_a1", Contents = [new MeaiTextContent("Agent 1 output")] },
+ new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("agent_1", null) },
+ new AgentResponseUpdate { RawRepresentation = new SuperStepCompletedEvent(1) },
+ // Superstep 2: Agent 2
+ new AgentResponseUpdate { RawRepresentation = new SuperStepStartedEvent(2) },
+ new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("agent_2", "start") },
+ new AgentResponseUpdate { MessageId = "msg_a2", Contents = [new MeaiTextContent("Agent 2 output")] },
+ new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("agent_2", null) },
+ new AgentResponseUpdate { RawRepresentation = new SuperStepCompletedEvent(2) },
+ };
+
+ var events = new List();
+ await foreach (var evt in OutputConverter.ConvertUpdatesToEventsAsync(ToAsync(updates), stream))
+ {
+ events.Add(evt);
+ }
+
+ // 4 workflow action items + 2 text messages = 6 output items
+ Assert.Equal(6, events.OfType().Count());
+ Assert.Equal(2, events.OfType().Count());
+ Assert.IsType(events[^1]);
+ }
+
+ [Fact]
+ public async Task GroupChatPattern_ProducesCorrectEventsAsync()
+ {
+ // Simulate round-robin group chat: agent1 → agent2 → agent1 → terminate
+ var (stream, _) = CreateTestStream();
+ var updates = new[]
+ {
+ new AgentResponseUpdate { RawRepresentation = new SuperStepStartedEvent(1) },
+ new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("chat_agent_1", "turn") },
+ new AgentResponseUpdate { MessageId = "msg_gc_1", Contents = [new MeaiTextContent("Agent 1 turn 1")] },
+ new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("chat_agent_1", null) },
+ new AgentResponseUpdate { RawRepresentation = new SuperStepCompletedEvent(1) },
+ new AgentResponseUpdate { RawRepresentation = new SuperStepStartedEvent(2) },
+ new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("chat_agent_2", "turn") },
+ new AgentResponseUpdate { MessageId = "msg_gc_2", Contents = [new MeaiTextContent("Agent 2 turn 1")] },
+ new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("chat_agent_2", null) },
+ new AgentResponseUpdate { RawRepresentation = new SuperStepCompletedEvent(2) },
+ new AgentResponseUpdate { RawRepresentation = new SuperStepStartedEvent(3) },
+ new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("chat_agent_1", "turn") },
+ new AgentResponseUpdate { MessageId = "msg_gc_3", Contents = [new MeaiTextContent("Agent 1 turn 2")] },
+ new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("chat_agent_1", null) },
+ new AgentResponseUpdate { RawRepresentation = new SuperStepCompletedEvent(3) },
+ };
+
+ var events = new List();
+ await foreach (var evt in OutputConverter.ConvertUpdatesToEventsAsync(ToAsync(updates), stream))
+ {
+ events.Add(evt);
+ }
+
+ // 6 workflow actions + 3 text messages = 9 output items
+ Assert.Equal(9, events.OfType().Count());
+ Assert.Equal(3, events.OfType().Count());
+ Assert.IsType(events[^1]);
+ }
+
+ [Fact]
+ public async Task CodeExecutorPattern_ProducesCorrectEventsAsync()
+ {
+ // Simulate a code-based FunctionExecutor: invoked → completed, no text content
+ // (code executors don't produce AgentResponseUpdateEvent, just executor lifecycle)
+ var (stream, _) = CreateTestStream();
+ var updates = new[]
+ {
+ new AgentResponseUpdate { RawRepresentation = new SuperStepStartedEvent(1) },
+ new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("uppercase_fn", "hello") },
+ new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("uppercase_fn", "HELLO") },
+ new AgentResponseUpdate { RawRepresentation = new SuperStepCompletedEvent(1) },
+ // Second executor uses the output
+ new AgentResponseUpdate { RawRepresentation = new SuperStepStartedEvent(2) },
+ new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("format_agent", "start") },
+ new AgentResponseUpdate { MessageId = "msg_fmt", Contents = [new MeaiTextContent("Formatted: HELLO")] },
+ new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("format_agent", null) },
+ new AgentResponseUpdate { RawRepresentation = new SuperStepCompletedEvent(2) },
+ };
+
+ var events = new List();
+ await foreach (var evt in OutputConverter.ConvertUpdatesToEventsAsync(ToAsync(updates), stream))
+ {
+ events.Add(evt);
+ }
+
+ // 4 workflow actions + 1 text message = 5 output items
+ Assert.Equal(5, events.OfType().Count());
+ Assert.Single(events.OfType());
+ Assert.IsType(events[^1]);
+ }
+
+ [Fact]
+ public async Task SubworkflowPattern_ProducesCorrectEventsAsync()
+ {
+ // Simulate a parent workflow that invokes a sub-workflow executor
+ var (stream, _) = CreateTestStream();
+ var updates = new[]
+ {
+ new AgentResponseUpdate { RawRepresentation = new WorkflowStartedEvent("parent") },
+ new AgentResponseUpdate { RawRepresentation = new SuperStepStartedEvent(1) },
+ // Sub-workflow executor invoked
+ new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("sub_workflow_host", "start") },
+ // Inner agent within sub-workflow produces text (unwrapped by WorkflowSession)
+ new AgentResponseUpdate { MessageId = "msg_sub_1", Contents = [new MeaiTextContent("Sub-workflow agent output")] },
+ // Sub-workflow executor completed
+ new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("sub_workflow_host", null) },
+ new AgentResponseUpdate { RawRepresentation = new SuperStepCompletedEvent(1) },
+ };
+
+ var events = new List();
+ await foreach (var evt in OutputConverter.ConvertUpdatesToEventsAsync(ToAsync(updates), stream))
+ {
+ events.Add(evt);
+ }
+
+ // 2 workflow actions + 1 text message = 3 output items
+ Assert.Equal(3, events.OfType().Count());
+ Assert.Single(events.OfType());
+ Assert.IsType(events[^1]);
+ }
+
+ [Fact]
+ public async Task WorkflowWithMultipleContentTypes_HandlesAllCorrectlyAsync()
+ {
+ // Simulate a workflow producing reasoning, text, function calls, and usage
+ var (stream, _) = CreateTestStream();
+ var updates = new[]
+ {
+ new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("planner", "start") },
+ // Reasoning
+ new AgentResponseUpdate { Contents = [new TextReasoningContent("Let me think about this...")] },
+ // Function call (tool use)
+ new AgentResponseUpdate
+ {
+ Contents = [new FunctionCallContent("call_search", "web_search",
+ new Dictionary { ["query"] = "latest news" })]
+ },
+ new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("planner", null) },
+ // Next executor uses tool result
+ new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("writer", "start") },
+ new AgentResponseUpdate { MessageId = "msg_w1", Contents = [new MeaiTextContent("Based on my research, ")] },
+ new AgentResponseUpdate { MessageId = "msg_w1", Contents = [new MeaiTextContent("here are the findings.")] },
+ new AgentResponseUpdate
+ {
+ Contents = [new UsageContent(new UsageDetails { InputTokenCount = 500, OutputTokenCount = 200, TotalTokenCount = 700 })]
+ },
+ new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("writer", null) },
+ };
+
+ var events = new List();
+ await foreach (var evt in OutputConverter.ConvertUpdatesToEventsAsync(ToAsync(updates), stream))
+ {
+ events.Add(evt);
+ }
+
+ // Workflow actions: 4 (2 invoked + 2 completed)
+ // Content: 1 reasoning + 1 function call + 1 text message = 3
+ // Total: 7 output items
+ Assert.Equal(7, events.OfType().Count());
+ Assert.Contains(events, e => e is ResponseFunctionCallArgumentsDoneEvent);
+ Assert.Equal(2, events.OfType().Count());
+ Assert.IsType(events[^1]);
+ }
+
+ private static (ResponseEventStream stream, Mock mockContext) CreateTestStream()
+ {
+ var mockContext = new Mock("resp_" + new string('0', 46)) { CallBase = true };
+ var request = new CreateResponse { Model = "test-model" };
+ var stream = new ResponseEventStream(mockContext.Object, request);
+ return (stream, mockContext);
+ }
+
+ private static async IAsyncEnumerable ToAsync(IEnumerable source)
+ {
+ foreach (var item in source)
+ {
+ yield return item;
+ }
+
+ await Task.CompletedTask;
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/ServiceCollectionExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/ServiceCollectionExtensionsTests.cs
similarity index 97%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/ServiceCollectionExtensionsTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/ServiceCollectionExtensionsTests.cs
index c5b9bf1701..bcab777af0 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/ServiceCollectionExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/ServiceCollectionExtensionsTests.cs
@@ -3,13 +3,12 @@
using System;
using System.Linq;
using Azure.AI.AgentServer.Responses;
-using Microsoft.Agents.AI.Foundry.Hosting;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using OpenAI.Responses;
-namespace Microsoft.Agents.AI.Foundry.UnitTests.Hosting;
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
public class ServiceCollectionExtensionsTests
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxRecordResponse.json b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxRecordResponse.json
similarity index 100%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxRecordResponse.json
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxRecordResponse.json
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionResponse.json b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxVersionResponse.json
similarity index 100%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionResponse.json
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxVersionResponse.json
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionWithDecorationFields.json b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxVersionWithDecorationFields.json
similarity index 100%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionWithDecorationFields.json
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxVersionWithDecorationFields.json
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestDataUtil.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestDataUtil.cs
new file mode 100644
index 0000000000..c62f2aedc2
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestDataUtil.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.IO;
+
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
+
+///
+/// Utility class for loading toolbox-related test data files.
+///
+internal static class TestDataUtil
+{
+ private static readonly string s_toolboxRecordResponseJson = File.ReadAllText("TestData/ToolboxRecordResponse.json");
+ private static readonly string s_toolboxVersionResponseJson = File.ReadAllText("TestData/ToolboxVersionResponse.json");
+ private static readonly string s_toolboxVersionWithDecorationFieldsJson = File.ReadAllText("TestData/ToolboxVersionWithDecorationFields.json");
+
+ ///
+ /// Gets the toolbox record response JSON.
+ ///
+ public static string GetToolboxRecordResponseJson() => s_toolboxRecordResponseJson;
+
+ ///
+ /// Gets the toolbox version response JSON.
+ ///
+ public static string GetToolboxVersionResponseJson() => s_toolboxVersionResponseJson;
+
+ ///
+ /// Gets the toolbox version response JSON with decoration fields on tools.
+ ///
+ public static string GetToolboxVersionWithDecorationFieldsJson() => s_toolboxVersionWithDecorationFieldsJson;
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/DelegatingResponsesClientTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/UserAgentResponsesClientTests.cs
similarity index 97%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/DelegatingResponsesClientTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/UserAgentResponsesClientTests.cs
index 8ec8a7f570..c57bf6802e 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/DelegatingResponsesClientTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/UserAgentResponsesClientTests.cs
@@ -11,23 +11,22 @@
using System.Threading;
using System.Threading.Tasks;
using Azure.AI.Extensions.OpenAI;
-using Microsoft.Agents.AI.Foundry.Hosting;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Responses;
#pragma warning disable OPENAI001, SCME0001, SCME0002, MEAI001
-namespace Microsoft.Agents.AI.Foundry.UnitTests.Hosting;
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
///
-/// Verifies that preserves user-supplied client options
+/// Verifies that preserves user-supplied client options
/// (Transport, RetryPolicy, UserAgentApplicationId, OrganizationId, ProjectId) and adds the
/// hosted-agent User-Agent supplement on every outgoing request, including streaming.
/// Covers both the Azure-flavored and the native OpenAI
/// .
///
-public sealed partial class DelegatingResponsesClientTests
+public sealed partial class UserAgentResponsesClientTests
{
private const string TestEndpoint = "https://fake-foundry.example.com/api/projects/fake-prj";
private const string OpenAIEndpoint = "https://fake-openai.example.com/v1";
@@ -215,7 +214,7 @@ public async Task Polyfill_AncillaryProtocolMethod_AddsSupplementAsync(string me
using var httpClient = new HttpClient(handler);
#pragma warning restore CA5399
var inner = BuildOpenAIInner(httpClient, userAgentApplicationId: "MY_APP_ID");
- var wrapper = new DelegatingResponsesClient(inner);
+ var wrapper = new UserAgentResponsesClient(inner);
// Act
switch (method)
@@ -326,7 +325,7 @@ private static IChatClient MakeWithDelegating(ResponsesClient inner)
IChatClient meai = inner.AsIChatClient(Deployment);
var meaiType = meai.GetType();
var field = meaiType.GetField("_responseClient", BindingFlags.NonPublic | BindingFlags.Instance)!;
- field.SetValue(meai, new DelegatingResponsesClient(inner));
+ field.SetValue(meai, new UserAgentResponsesClient(inner));
return meai;
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/WorkflowTestAgents.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/WorkflowTestAgents.cs
new file mode 100644
index 0000000000..7e10abc9eb
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/WorkflowTestAgents.cs
@@ -0,0 +1,96 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using MeaiTextContent = Microsoft.Extensions.AI.TextContent;
+
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
+
+///
+/// A test agent that streams a single text update.
+///
+internal sealed class StreamingTextAgent(string id, string responseText) : AIAgent
+{
+ public new string Id => id;
+
+ protected override async IAsyncEnumerable RunCoreStreamingAsync(
+ IEnumerable messages,
+ AgentSession? session,
+ AgentRunOptions? options,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ yield return new AgentResponseUpdate
+ {
+ MessageId = $"msg_{id}",
+ Contents = [new MeaiTextContent(responseText)]
+ };
+
+ await Task.CompletedTask;
+ }
+
+ protected override Task RunCoreAsync(
+ IEnumerable messages,
+ AgentSession? session,
+ AgentRunOptions? options,
+ CancellationToken cancellationToken = default) =>
+ throw new NotImplementedException();
+
+ protected override ValueTask CreateSessionCoreAsync(
+ CancellationToken cancellationToken = default) =>
+ throw new NotImplementedException();
+
+ protected override ValueTask SerializeSessionCoreAsync(
+ AgentSession session,
+ JsonSerializerOptions? jsonSerializerOptions,
+ CancellationToken cancellationToken = default) =>
+ throw new NotImplementedException();
+
+ protected override ValueTask DeserializeSessionCoreAsync(
+ JsonElement serializedState,
+ JsonSerializerOptions? jsonSerializerOptions,
+ CancellationToken cancellationToken = default) =>
+ throw new NotImplementedException();
+}
+
+///
+/// A test agent that always throws an exception during streaming.
+///
+internal sealed class ThrowingStreamingAgent(string id, Exception exception) : AIAgent
+{
+ public new string Id => id;
+
+ protected override IAsyncEnumerable RunCoreStreamingAsync(
+ IEnumerable messages,
+ AgentSession? session,
+ AgentRunOptions? options,
+ CancellationToken cancellationToken = default) =>
+ throw exception;
+
+ protected override Task RunCoreAsync(
+ IEnumerable messages,
+ AgentSession? session,
+ AgentRunOptions? options,
+ CancellationToken cancellationToken = default) =>
+ throw new NotImplementedException();
+
+ protected override ValueTask CreateSessionCoreAsync(
+ CancellationToken cancellationToken = default) =>
+ throw new NotImplementedException();
+
+ protected override ValueTask SerializeSessionCoreAsync(
+ AgentSession session,
+ JsonSerializerOptions? jsonSerializerOptions,
+ CancellationToken cancellationToken = default) =>
+ throw new NotImplementedException();
+
+ protected override ValueTask DeserializeSessionCoreAsync(
+ JsonElement serializedState,
+ JsonSerializerOptions? jsonSerializerOptions,
+ CancellationToken cancellationToken = default) =>
+ throw new NotImplementedException();
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/WorkflowIntegrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/WorkflowIntegrationTests.cs
deleted file mode 100644
index d87406f815..0000000000
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/WorkflowIntegrationTests.cs
+++ /dev/null
@@ -1,508 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
-using Azure.AI.AgentServer.Responses;
-using Azure.AI.AgentServer.Responses.Models;
-using Microsoft.Agents.AI.Foundry.Hosting;
-using Microsoft.Agents.AI.Workflows;
-using Microsoft.Extensions.AI;
-using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.Logging;
-using Microsoft.Extensions.Logging.Abstractions;
-using Moq;
-using MeaiTextContent = Microsoft.Extensions.AI.TextContent;
-
-namespace Microsoft.Agents.AI.Foundry.UnitTests.Hosting;
-
-///
-/// Integration tests that verify workflow execution through the
-/// → pipeline.
-/// These use real workflow builders and the InProcessExecution environment
-/// to produce authentic streaming event patterns.
-///
-public class WorkflowIntegrationTests
-{
- // ===== Sequential Workflow Tests =====
-
- [Fact]
- public async Task SequentialWorkflow_SingleAgent_ProducesTextOutputAsync()
- {
- // Arrange: single-agent sequential workflow
- var echoAgent = new StreamingTextAgent("echo", "Hello from the workflow!");
- var workflow = AgentWorkflowBuilder.BuildSequential("test-sequential", echoAgent);
- var workflowAgent = workflow.AsAIAgent(
- id: "workflow-agent",
- name: "Test Workflow",
- executionEnvironment: InProcessExecution.OffThread,
- includeExceptionDetails: true);
-
- var (handler, request, context) = CreateHandlerWithAgent(workflowAgent, "Hello");
-
- // Act
- var events = await CollectEventsAsync(handler, request, context);
-
- // Assert: should have lifecycle events + at least one text output + terminal
- Assert.IsType(events[0]);
- Assert.IsType(events[1]);
- Assert.True(events.Count >= 4, $"Expected at least 4 events, got {events.Count}");
-
- var lastEvent = events[^1];
- Assert.True(
- lastEvent is ResponseCompletedEvent || lastEvent is ResponseFailedEvent,
- $"Expected terminal event, got {lastEvent.GetType().Name}");
- }
-
- [Fact]
- public async Task SequentialWorkflow_TwoAgents_ProducesOutputFromBothAsync()
- {
- // Arrange: two agents in sequence
- var agent1 = new StreamingTextAgent("agent1", "First agent says hello");
- var agent2 = new StreamingTextAgent("agent2", "Second agent says goodbye");
- var workflow = AgentWorkflowBuilder.BuildSequential("test-sequential-2", agent1, agent2);
- var workflowAgent = workflow.AsAIAgent(
- id: "seq-workflow",
- name: "Sequential Workflow",
- executionEnvironment: InProcessExecution.OffThread,
- includeExceptionDetails: true);
-
- var (handler, request, context) = CreateHandlerWithAgent(workflowAgent, "Process this");
-
- // Act
- var events = await CollectEventsAsync(handler, request, context);
-
- // Assert: should have workflow action events for executor lifecycle
- var lastEvent = events[^1];
- Assert.True(
- lastEvent is ResponseCompletedEvent || lastEvent is ResponseFailedEvent,
- $"Expected terminal event, got {lastEvent.GetType().Name}");
-
- // Should have output item events (either text messages or workflow actions)
- Assert.True(events.OfType().Any(),
- "Expected at least one output item from the workflow");
- }
-
- // ===== Workflow Error Propagation =====
-
- [Fact]
- public async Task Workflow_AgentThrowsException_ProducesErrorOutputAsync()
- {
- // Arrange: workflow with an agent that throws
- var throwingAgent = new ThrowingStreamingAgent("thrower", new InvalidOperationException("Agent crashed"));
- var workflow = AgentWorkflowBuilder.BuildSequential("test-error", throwingAgent);
- var workflowAgent = workflow.AsAIAgent(
- id: "error-workflow",
- name: "Error Workflow",
- executionEnvironment: InProcessExecution.OffThread,
- includeExceptionDetails: true);
-
- var (handler, request, context) = CreateHandlerWithAgent(workflowAgent, "Trigger error");
-
- // Act
- var events = await CollectEventsAsync(handler, request, context);
-
- // Assert: should have lifecycle events + error/failure indicator
- Assert.IsType(events[0]);
- Assert.IsType(events[1]);
-
- var lastEvent = events[^1];
- // Workflow errors surface as either Failed or Completed (depending on error handling)
- Assert.True(
- lastEvent is ResponseCompletedEvent || lastEvent is ResponseFailedEvent,
- $"Expected terminal event, got {lastEvent.GetType().Name}");
- }
-
- // ===== Workflow Action Lifecycle Events =====
-
- [Fact]
- public async Task Workflow_ExecutorEvents_ProduceWorkflowActionItemsAsync()
- {
- // Arrange
- var agent = new StreamingTextAgent("test-agent", "Result");
- var workflow = AgentWorkflowBuilder.BuildSequential("test-actions", agent);
- var workflowAgent = workflow.AsAIAgent(
- id: "actions-workflow",
- name: "Actions Workflow",
- executionEnvironment: InProcessExecution.OffThread);
-
- var (handler, request, context) = CreateHandlerWithAgent(workflowAgent, "Hello");
-
- // Act
- var events = await CollectEventsAsync(handler, request, context);
-
- // Assert: workflow should produce OutputItemAdded events for executor lifecycle
- var addedEvents = events.OfType().ToList();
- Assert.True(addedEvents.Count >= 1,
- $"Expected at least 1 output item added event, got {addedEvents.Count}");
- }
-
- // ===== Keyed Workflow Registration =====
-
- [Fact]
- public async Task WorkflowAgent_RegisteredWithKey_ResolvesCorrectlyAsync()
- {
- // Arrange: workflow agent registered with a keyed service name
- var agent = new StreamingTextAgent("inner", "Keyed workflow response");
- var workflow = AgentWorkflowBuilder.BuildSequential("keyed-wf", agent);
- var workflowAgent = workflow.AsAIAgent(
- id: "keyed-workflow",
- name: "Keyed Workflow",
- executionEnvironment: InProcessExecution.OffThread);
-
- var services = new ServiceCollection();
- services.AddSingleton(new InMemoryAgentSessionStore());
- services.AddKeyedSingleton("my-workflow", workflowAgent);
- var sp = services.BuildServiceProvider();
-
- var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = new CreateResponse { Model = "test", AgentReference = new AgentReference("my-workflow") };
- request.Input = CreateUserInput("Test keyed workflow");
- var mockContext = CreateMockContext();
-
- // Act
- var events = await CollectEventsAsync(handler, request, mockContext.Object);
-
- // Assert
- Assert.IsType(events[0]);
- Assert.True(events.Count >= 3, $"Expected at least 3 events, got {events.Count}");
- }
-
- // ===== OutputConverter Direct Workflow Pattern Tests =====
- // These test the OutputConverter directly with update patterns that mirror real workflows.
-
- [Fact]
- public async Task OutputConverter_SequentialWorkflowPattern_ProducesCorrectEventsAsync()
- {
- // Simulate what WorkflowSession produces for a 2-agent sequential workflow
- var (stream, _) = CreateTestStream();
- var updates = new[]
- {
- // Superstep 1: Agent 1
- new AgentResponseUpdate { RawRepresentation = new SuperStepStartedEvent(1) },
- new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("agent_1", "start") },
- new AgentResponseUpdate { MessageId = "msg_a1", Contents = [new MeaiTextContent("Agent 1 output")] },
- new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("agent_1", null) },
- new AgentResponseUpdate { RawRepresentation = new SuperStepCompletedEvent(1) },
- // Superstep 2: Agent 2
- new AgentResponseUpdate { RawRepresentation = new SuperStepStartedEvent(2) },
- new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("agent_2", "start") },
- new AgentResponseUpdate { MessageId = "msg_a2", Contents = [new MeaiTextContent("Agent 2 output")] },
- new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("agent_2", null) },
- new AgentResponseUpdate { RawRepresentation = new SuperStepCompletedEvent(2) },
- };
-
- var events = new List();
- await foreach (var evt in OutputConverter.ConvertUpdatesToEventsAsync(ToAsync(updates), stream))
- {
- events.Add(evt);
- }
-
- // 4 workflow action items + 2 text messages = 6 output items
- Assert.Equal(6, events.OfType().Count());
- Assert.Equal(2, events.OfType().Count());
- Assert.IsType(events[^1]);
- }
-
- [Fact]
- public async Task OutputConverter_GroupChatPattern_ProducesCorrectEventsAsync()
- {
- // Simulate round-robin group chat: agent1 → agent2 → agent1 → terminate
- var (stream, _) = CreateTestStream();
- var updates = new[]
- {
- new AgentResponseUpdate { RawRepresentation = new SuperStepStartedEvent(1) },
- new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("chat_agent_1", "turn") },
- new AgentResponseUpdate { MessageId = "msg_gc_1", Contents = [new MeaiTextContent("Agent 1 turn 1")] },
- new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("chat_agent_1", null) },
- new AgentResponseUpdate { RawRepresentation = new SuperStepCompletedEvent(1) },
- new AgentResponseUpdate { RawRepresentation = new SuperStepStartedEvent(2) },
- new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("chat_agent_2", "turn") },
- new AgentResponseUpdate { MessageId = "msg_gc_2", Contents = [new MeaiTextContent("Agent 2 turn 1")] },
- new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("chat_agent_2", null) },
- new AgentResponseUpdate { RawRepresentation = new SuperStepCompletedEvent(2) },
- new AgentResponseUpdate { RawRepresentation = new SuperStepStartedEvent(3) },
- new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("chat_agent_1", "turn") },
- new AgentResponseUpdate { MessageId = "msg_gc_3", Contents = [new MeaiTextContent("Agent 1 turn 2")] },
- new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("chat_agent_1", null) },
- new AgentResponseUpdate { RawRepresentation = new SuperStepCompletedEvent(3) },
- };
-
- var events = new List();
- await foreach (var evt in OutputConverter.ConvertUpdatesToEventsAsync(ToAsync(updates), stream))
- {
- events.Add(evt);
- }
-
- // 6 workflow actions + 3 text messages = 9 output items
- Assert.Equal(9, events.OfType().Count());
- Assert.Equal(3, events.OfType().Count());
- Assert.IsType(events[^1]);
- }
-
- [Fact]
- public async Task OutputConverter_CodeExecutorPattern_ProducesCorrectEventsAsync()
- {
- // Simulate a code-based FunctionExecutor: invoked → completed, no text content
- // (code executors don't produce AgentResponseUpdateEvent, just executor lifecycle)
- var (stream, _) = CreateTestStream();
- var updates = new[]
- {
- new AgentResponseUpdate { RawRepresentation = new SuperStepStartedEvent(1) },
- new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("uppercase_fn", "hello") },
- new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("uppercase_fn", "HELLO") },
- new AgentResponseUpdate { RawRepresentation = new SuperStepCompletedEvent(1) },
- // Second executor uses the output
- new AgentResponseUpdate { RawRepresentation = new SuperStepStartedEvent(2) },
- new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("format_agent", "start") },
- new AgentResponseUpdate { MessageId = "msg_fmt", Contents = [new MeaiTextContent("Formatted: HELLO")] },
- new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("format_agent", null) },
- new AgentResponseUpdate { RawRepresentation = new SuperStepCompletedEvent(2) },
- };
-
- var events = new List();
- await foreach (var evt in OutputConverter.ConvertUpdatesToEventsAsync(ToAsync(updates), stream))
- {
- events.Add(evt);
- }
-
- // 4 workflow actions + 1 text message = 5 output items
- Assert.Equal(5, events.OfType().Count());
- Assert.Single(events.OfType());
- Assert.IsType(events[^1]);
- }
-
- [Fact]
- public async Task OutputConverter_SubworkflowPattern_ProducesCorrectEventsAsync()
- {
- // Simulate a parent workflow that invokes a sub-workflow executor
- var (stream, _) = CreateTestStream();
- var updates = new[]
- {
- new AgentResponseUpdate { RawRepresentation = new WorkflowStartedEvent("parent") },
- new AgentResponseUpdate { RawRepresentation = new SuperStepStartedEvent(1) },
- // Sub-workflow executor invoked
- new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("sub_workflow_host", "start") },
- // Inner agent within sub-workflow produces text (unwrapped by WorkflowSession)
- new AgentResponseUpdate { MessageId = "msg_sub_1", Contents = [new MeaiTextContent("Sub-workflow agent output")] },
- // Sub-workflow executor completed
- new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("sub_workflow_host", null) },
- new AgentResponseUpdate { RawRepresentation = new SuperStepCompletedEvent(1) },
- };
-
- var events = new List();
- await foreach (var evt in OutputConverter.ConvertUpdatesToEventsAsync(ToAsync(updates), stream))
- {
- events.Add(evt);
- }
-
- // 2 workflow actions + 1 text message = 3 output items
- Assert.Equal(3, events.OfType().Count());
- Assert.Single(events.OfType());
- Assert.IsType(events[^1]);
- }
-
- [Fact]
- public async Task OutputConverter_WorkflowWithMultipleContentTypes_HandlesAllCorrectlyAsync()
- {
- // Simulate a workflow producing reasoning, text, function calls, and usage
- var (stream, _) = CreateTestStream();
- var updates = new[]
- {
- new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("planner", "start") },
- // Reasoning
- new AgentResponseUpdate { Contents = [new TextReasoningContent("Let me think about this...")] },
- // Function call (tool use)
- new AgentResponseUpdate
- {
- Contents = [new FunctionCallContent("call_search", "web_search",
- new Dictionary { ["query"] = "latest news" })]
- },
- new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("planner", null) },
- // Next executor uses tool result
- new AgentResponseUpdate { RawRepresentation = new ExecutorInvokedEvent("writer", "start") },
- new AgentResponseUpdate { MessageId = "msg_w1", Contents = [new MeaiTextContent("Based on my research, ")] },
- new AgentResponseUpdate { MessageId = "msg_w1", Contents = [new MeaiTextContent("here are the findings.")] },
- new AgentResponseUpdate
- {
- Contents = [new UsageContent(new UsageDetails { InputTokenCount = 500, OutputTokenCount = 200, TotalTokenCount = 700 })]
- },
- new AgentResponseUpdate { RawRepresentation = new ExecutorCompletedEvent("writer", null) },
- };
-
- var events = new List();
- await foreach (var evt in OutputConverter.ConvertUpdatesToEventsAsync(ToAsync(updates), stream))
- {
- events.Add(evt);
- }
-
- // Workflow actions: 4 (2 invoked + 2 completed)
- // Content: 1 reasoning + 1 function call + 1 text message = 3
- // Total: 7 output items
- Assert.Equal(7, events.OfType().Count());
- Assert.Contains(events, e => e is ResponseFunctionCallArgumentsDoneEvent);
- Assert.Equal(2, events.OfType().Count());
- Assert.IsType(events[^1]);
- }
-
- // ===== Helpers =====
-
- private static (AgentFrameworkResponseHandler handler, CreateResponse request, ResponseContext context)
- CreateHandlerWithAgent(AIAgent agent, string userMessage)
- {
- var services = new ServiceCollection();
- services.AddSingleton(new InMemoryAgentSessionStore());
- services.AddSingleton(agent);
- services.AddSingleton>(NullLogger.Instance);
- var sp = services.BuildServiceProvider();
-
- var handler = new AgentFrameworkResponseHandler(sp, NullLogger.Instance);
- var request = new CreateResponse { Model = "test" };
- request.Input = CreateUserInput(userMessage);
- var mockContext = CreateMockContext();
-
- return (handler, request, mockContext.Object);
- }
-
- private static BinaryData CreateUserInput(string text)
- {
- return BinaryData.FromObjectAsJson(new[]
- {
- new { type = "message", id = "msg_in_1", status = "completed", role = "user",
- content = new[] { new { type = "input_text", text } }
- }
- });
- }
-
- private static Mock CreateMockContext()
- {
- var mock = new Mock("resp_" + new string('0', 46)) { CallBase = true };
- mock.Setup(x => x.GetHistoryAsync(It.IsAny()))
- .ReturnsAsync(Array.Empty());
- mock.Setup(x => x.GetInputItemsAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(Array.Empty- ());
- return mock;
- }
-
- private static (ResponseEventStream stream, Mock mockContext) CreateTestStream()
- {
- var mockContext = new Mock("resp_" + new string('0', 46)) { CallBase = true };
- var request = new CreateResponse { Model = "test-model" };
- var stream = new ResponseEventStream(mockContext.Object, request);
- return (stream, mockContext);
- }
-
- private static async Task
> CollectEventsAsync(
- AgentFrameworkResponseHandler handler,
- CreateResponse request,
- ResponseContext context)
- {
- var events = new List();
- await foreach (var evt in handler.CreateAsync(request, context, CancellationToken.None))
- {
- events.Add(evt);
- }
-
- return events;
- }
-
- private static async IAsyncEnumerable ToAsync(IEnumerable source)
- {
- foreach (var item in source)
- {
- yield return item;
- }
-
- await Task.CompletedTask;
- }
-
- // ===== Test Agent Types =====
-
- ///
- /// A test agent that streams a single text update.
- ///
- private sealed class StreamingTextAgent(string id, string responseText) : AIAgent
- {
- public new string Id => id;
-
- protected override async IAsyncEnumerable RunCoreStreamingAsync(
- IEnumerable messages,
- AgentSession? session,
- AgentRunOptions? options,
- [EnumeratorCancellation] CancellationToken cancellationToken = default)
- {
- yield return new AgentResponseUpdate
- {
- MessageId = $"msg_{id}",
- Contents = [new MeaiTextContent(responseText)]
- };
-
- await Task.CompletedTask;
- }
-
- protected override Task RunCoreAsync(
- IEnumerable messages,
- AgentSession? session,
- AgentRunOptions? options,
- CancellationToken cancellationToken = default) =>
- throw new NotImplementedException();
-
- protected override ValueTask CreateSessionCoreAsync(
- CancellationToken cancellationToken = default) =>
- throw new NotImplementedException();
-
- protected override ValueTask SerializeSessionCoreAsync(
- AgentSession session,
- JsonSerializerOptions? jsonSerializerOptions,
- CancellationToken cancellationToken = default) =>
- throw new NotImplementedException();
-
- protected override ValueTask DeserializeSessionCoreAsync(
- JsonElement serializedState,
- JsonSerializerOptions? jsonSerializerOptions,
- CancellationToken cancellationToken = default) =>
- throw new NotImplementedException();
- }
-
- ///
- /// A test agent that always throws an exception during streaming.
- ///
- private sealed class ThrowingStreamingAgent(string id, Exception exception) : AIAgent
- {
- public new string Id => id;
-
- protected override IAsyncEnumerable RunCoreStreamingAsync(
- IEnumerable messages,
- AgentSession? session,
- AgentRunOptions? options,
- CancellationToken cancellationToken = default) =>
- throw exception;
-
- protected override Task RunCoreAsync(
- IEnumerable messages,
- AgentSession? session,
- AgentRunOptions? options,
- CancellationToken cancellationToken = default) =>
- throw new NotImplementedException();
-
- protected override ValueTask CreateSessionCoreAsync(
- CancellationToken cancellationToken = default) =>
- throw new NotImplementedException();
-
- protected override ValueTask SerializeSessionCoreAsync(
- AgentSession session,
- JsonSerializerOptions? jsonSerializerOptions,
- CancellationToken cancellationToken = default) =>
- throw new NotImplementedException();
-
- protected override ValueTask DeserializeSessionCoreAsync(
- JsonElement serializedState,
- JsonSerializerOptions? jsonSerializerOptions,
- CancellationToken cancellationToken = default) =>
- throw new NotImplementedException();
- }
-}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Microsoft.Agents.AI.Foundry.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Microsoft.Agents.AI.Foundry.UnitTests.csproj
index 2265719711..cfa5e7a11f 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Microsoft.Agents.AI.Foundry.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Microsoft.Agents.AI.Foundry.UnitTests.csproj
@@ -7,33 +7,13 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -50,15 +30,6 @@
Always
-
- Always
-
-
- Always
-
-
- Always
-
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/RequestOptionsExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/RequestOptionsExtensionsTests.cs
index 18824c4875..df5dd8ebae 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/RequestOptionsExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/RequestOptionsExtensionsTests.cs
@@ -66,7 +66,7 @@ public async Task MeaiUserAgentPolicy_DoesNotAddFoundryHostingSegmentAsync()
await pipeline.SendAsync(message);
// Assert: the policy is MEAI-only; the foundry-hosting supplement is added elsewhere
- // (by the polyfill DelegatingResponsesClient → HostedAgentUserAgentPolicy).
+ // (by the polyfill UserAgentResponsesClient → HostedAgentUserAgentPolicy).
Assert.NotNull(handler.LastUserAgent);
Assert.DoesNotContain("foundry-hosting/agent-framework-dotnet", handler.LastUserAgent);
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestDataUtil.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestDataUtil.cs
index 898e0293c7..3460362efd 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestDataUtil.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestDataUtil.cs
@@ -14,9 +14,6 @@ internal static class TestDataUtil
private static readonly string s_agentResponseJson = File.ReadAllText("TestData/AgentResponse.json");
private static readonly string s_agentVersionResponseJson = File.ReadAllText("TestData/AgentVersionResponse.json");
private static readonly string s_openAIDefaultResponseJson = File.ReadAllText("TestData/OpenAIDefaultResponse.json");
- private static readonly string s_toolboxRecordResponseJson = File.ReadAllText("TestData/ToolboxRecordResponse.json");
- private static readonly string s_toolboxVersionResponseJson = File.ReadAllText("TestData/ToolboxVersionResponse.json");
- private static readonly string s_toolboxVersionWithDecorationFieldsJson = File.ReadAllText("TestData/ToolboxVersionWithDecorationFields.json");
private const string AgentDefinitionPlaceholder = "\"agent-definition-placeholder\"";
@@ -165,19 +162,4 @@ private static string ApplyDescription(string json, string? description)
}
return json;
}
-
- ///
- /// Gets the toolbox record response JSON.
- ///
- public static string GetToolboxRecordResponseJson() => s_toolboxRecordResponseJson;
-
- ///
- /// Gets the toolbox version response JSON.
- ///
- public static string GetToolboxVersionResponseJson() => s_toolboxVersionResponseJson;
-
- ///
- /// Gets the toolbox version response JSON with decoration fields on tools.
- ///
- public static string GetToolboxVersionWithDecorationFieldsJson() => s_toolboxVersionWithDecorationFieldsJson;
}