From da67e7e1e38af5a96325358b962567780ea7bb47 Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Thu, 30 Apr 2026 18:02:07 +0100
Subject: [PATCH 1/7] Foundry.Hosting.UnitTests: extract project from
Foundry.UnitTests
Move all Hosting/* tests, three toolbox TestData JSONs, and the FakeAuthenticationTokenProvider/HttpHandlerAssert/TestDataUtil helpers (trimmed to toolbox getters) into a new Microsoft.Agents.AI.Foundry.Hosting.UnitTests project. Add it to the slnx and grant the new assembly InternalsVisibleTo from Microsoft.Agents.AI.Foundry and Microsoft.Agents.AI.Foundry.Hosting.
---
dotnet/agent-framework-dotnet.slnx | 1 +
...Microsoft.Agents.AI.Foundry.Hosting.csproj | 1 +
.../Microsoft.Agents.AI.Foundry.csproj | 1 +
...tFrameworkResponseHandlerTelemetryTests.cs | 0
.../AgentFrameworkResponseHandlerTests.cs | 0
.../DelegatingResponsesClientTests.cs | 0
.../FakeAuthenticationTokenProvider.cs | 28 +++++++++++++
.../FoundryAIToolExtensionsTests.cs | 0
.../FoundryToolboxBearerTokenHandlerTests.cs | 0
.../FoundryToolboxServiceTests.cs | 0
.../FoundryToolboxTests.cs | 0
.../HostedOutboundUserAgentTests.cs | 0
.../HttpHandlerAssert.cs | 40 +++++++++++++++++++
.../InputConverterTests.cs | 0
...Agents.AI.Foundry.Hosting.UnitTests.csproj | 35 ++++++++++++++++
.../OutputConverterTests.cs | 0
.../ServiceCollectionExtensionsTests.cs | 0
.../TestData/ToolboxRecordResponse.json | 5 +++
.../TestData/ToolboxVersionResponse.json | 11 +++++
.../ToolboxVersionWithDecorationFields.json | 11 +++++
.../TestDataUtil.cs | 30 ++++++++++++++
.../WorkflowIntegrationTests.cs | 0
22 files changed, 163 insertions(+)
rename dotnet/tests/{Microsoft.Agents.AI.Foundry.UnitTests/Hosting => Microsoft.Agents.AI.Foundry.Hosting.UnitTests}/AgentFrameworkResponseHandlerTelemetryTests.cs (100%)
rename dotnet/tests/{Microsoft.Agents.AI.Foundry.UnitTests/Hosting => Microsoft.Agents.AI.Foundry.Hosting.UnitTests}/AgentFrameworkResponseHandlerTests.cs (100%)
rename dotnet/tests/{Microsoft.Agents.AI.Foundry.UnitTests/Hosting => Microsoft.Agents.AI.Foundry.Hosting.UnitTests}/DelegatingResponsesClientTests.cs (100%)
create mode 100644 dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FakeAuthenticationTokenProvider.cs
rename dotnet/tests/{Microsoft.Agents.AI.Foundry.UnitTests/Hosting => Microsoft.Agents.AI.Foundry.Hosting.UnitTests}/FoundryAIToolExtensionsTests.cs (100%)
rename dotnet/tests/{Microsoft.Agents.AI.Foundry.UnitTests/Hosting => Microsoft.Agents.AI.Foundry.Hosting.UnitTests}/FoundryToolboxBearerTokenHandlerTests.cs (100%)
rename dotnet/tests/{Microsoft.Agents.AI.Foundry.UnitTests/Hosting => Microsoft.Agents.AI.Foundry.Hosting.UnitTests}/FoundryToolboxServiceTests.cs (100%)
rename dotnet/tests/{Microsoft.Agents.AI.Foundry.UnitTests/Hosting => Microsoft.Agents.AI.Foundry.Hosting.UnitTests}/FoundryToolboxTests.cs (100%)
rename dotnet/tests/{Microsoft.Agents.AI.Foundry.UnitTests/Hosting => Microsoft.Agents.AI.Foundry.Hosting.UnitTests}/HostedOutboundUserAgentTests.cs (100%)
create mode 100644 dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HttpHandlerAssert.cs
rename dotnet/tests/{Microsoft.Agents.AI.Foundry.UnitTests/Hosting => Microsoft.Agents.AI.Foundry.Hosting.UnitTests}/InputConverterTests.cs (100%)
create mode 100644 dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests.csproj
rename dotnet/tests/{Microsoft.Agents.AI.Foundry.UnitTests/Hosting => Microsoft.Agents.AI.Foundry.Hosting.UnitTests}/OutputConverterTests.cs (100%)
rename dotnet/tests/{Microsoft.Agents.AI.Foundry.UnitTests/Hosting => Microsoft.Agents.AI.Foundry.Hosting.UnitTests}/ServiceCollectionExtensionsTests.cs (100%)
create mode 100644 dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxRecordResponse.json
create mode 100644 dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxVersionResponse.json
create mode 100644 dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxVersionWithDecorationFields.json
create mode 100644 dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestDataUtil.cs
rename dotnet/tests/{Microsoft.Agents.AI.Foundry.UnitTests/Hosting => Microsoft.Agents.AI.Foundry.Hosting.UnitTests}/WorkflowIntegrationTests.cs (100%)
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 2bfb78486c6..05979b090fc 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/Microsoft.Agents.AI.Foundry.Hosting.csproj b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/Microsoft.Agents.AI.Foundry.Hosting.csproj
index af02c44aad7..b009c0e62fb 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
@@ -45,6 +45,7 @@
+
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 cd4200df752..71e7df373f5 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 100%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/AgentFrameworkResponseHandlerTelemetryTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTelemetryTests.cs
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 100%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/AgentFrameworkResponseHandlerTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTests.cs
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/DelegatingResponsesClientTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs
similarity index 100%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/DelegatingResponsesClientTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs
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 00000000000..594ed85af31
--- /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.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 100%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryAIToolExtensionsTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryAIToolExtensionsTests.cs
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 100%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryToolboxBearerTokenHandlerTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxBearerTokenHandlerTests.cs
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 100%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryToolboxServiceTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxServiceTests.cs
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 100%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/FoundryToolboxTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxTests.cs
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 100%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/HostedOutboundUserAgentTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HostedOutboundUserAgentTests.cs
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 00000000000..0febf216b49
--- /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.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 100%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/InputConverterTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/InputConverterTests.cs
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 00000000000..fa28a324949
--- /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 100%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/OutputConverterTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterTests.cs
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 100%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/ServiceCollectionExtensionsTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/ServiceCollectionExtensionsTests.cs
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxRecordResponse.json b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxRecordResponse.json
new file mode 100644
index 00000000000..5208a572622
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxRecordResponse.json
@@ -0,0 +1,5 @@
+{
+ "id": "tbx-123",
+ "name": "research_tools",
+ "default_version": "v5"
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxVersionResponse.json b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxVersionResponse.json
new file mode 100644
index 00000000000..13f686ef8da
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxVersionResponse.json
@@ -0,0 +1,11 @@
+{
+ "metadata": {},
+ "id": "tbv-research_tools-v5",
+ "name": "research_tools",
+ "version": "v5",
+ "description": "Example research toolbox",
+ "created_at": 1775779200,
+ "tools": [
+ { "type": "code_interpreter" }
+ ]
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxVersionWithDecorationFields.json b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxVersionWithDecorationFields.json
new file mode 100644
index 00000000000..b78698bfb05
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestData/ToolboxVersionWithDecorationFields.json
@@ -0,0 +1,11 @@
+{
+ "metadata": {},
+ "id": "tbv-dirty-v1",
+ "name": "dirty_toolbox",
+ "version": "v1",
+ "description": "Toolbox with decoration fields on tools",
+ "created_at": 1775779200,
+ "tools": [
+ { "type": "code_interpreter", "name": "code_interpreter", "description": "Execute Python code" }
+ ]
+}
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 00000000000..5ee5af34e56
--- /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.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/WorkflowIntegrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/WorkflowIntegrationTests.cs
similarity index 100%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/Hosting/WorkflowIntegrationTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/WorkflowIntegrationTests.cs
From b8315512ebe79267cbc0bb04a2c1d44d2a39eeeb Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Thu, 30 Apr 2026 18:03:11 +0100
Subject: [PATCH 2/7] Foundry.Hosting.UnitTests: align namespaces to assembly
name
Rename namespaces from Microsoft.Agents.AI.Foundry.UnitTests(.Hosting) to Microsoft.Agents.AI.Foundry.Hosting.UnitTests across all moved tests, the duplicated helpers, and the trimmed TestDataUtil. Also fixes the prior namespace inconsistency in FoundryToolboxTests.
---
.../AgentFrameworkResponseHandlerTelemetryTests.cs | 2 +-
.../AgentFrameworkResponseHandlerTests.cs | 2 +-
.../DelegatingResponsesClientTests.cs | 2 +-
.../FakeAuthenticationTokenProvider.cs | 2 +-
.../FoundryAIToolExtensionsTests.cs | 2 +-
.../FoundryToolboxBearerTokenHandlerTests.cs | 2 +-
.../FoundryToolboxServiceTests.cs | 2 +-
.../FoundryToolboxTests.cs | 2 +-
.../HostedOutboundUserAgentTests.cs | 2 +-
.../HttpHandlerAssert.cs | 2 +-
.../InputConverterTests.cs | 2 +-
.../OutputConverterTests.cs | 2 +-
.../ServiceCollectionExtensionsTests.cs | 2 +-
.../TestDataUtil.cs | 2 +-
.../WorkflowIntegrationTests.cs | 2 +-
15 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTelemetryTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTelemetryTests.cs
index 48daf490ee8..bb2db2eaa6c 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTelemetryTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTelemetryTests.cs
@@ -19,7 +19,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.Hosting.UnitTests/AgentFrameworkResponseHandlerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTests.cs
index d7fc8fc8847..41a44f3ca58 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTests.cs
@@ -17,7 +17,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/DelegatingResponsesClientTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs
index 8ec8a7f5706..db2ada46e03 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs
@@ -18,7 +18,7 @@
#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
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FakeAuthenticationTokenProvider.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FakeAuthenticationTokenProvider.cs
index 594ed85af31..96d542379a0 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FakeAuthenticationTokenProvider.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FakeAuthenticationTokenProvider.cs
@@ -7,7 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.Agents.AI.Foundry.UnitTests;
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
internal sealed class FakeAuthenticationTokenProvider : AuthenticationTokenProvider
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryAIToolExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryAIToolExtensionsTests.cs
index f083a9946ee..62f2e277088 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryAIToolExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryAIToolExtensionsTests.cs
@@ -3,7 +3,7 @@
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.Hosting.UnitTests/FoundryToolboxBearerTokenHandlerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxBearerTokenHandlerTests.cs
index cea48d8eb08..85d94366aa3 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxBearerTokenHandlerTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxBearerTokenHandlerTests.cs
@@ -9,7 +9,7 @@
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.Hosting.UnitTests/FoundryToolboxServiceTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxServiceTests.cs
index 24f7433c4e0..128ba34e26e 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxServiceTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxServiceTests.cs
@@ -8,7 +8,7 @@
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.Hosting.UnitTests/FoundryToolboxTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxTests.cs
index 3f0a8a54dda..fcb17825f74 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxTests.cs
@@ -15,7 +15,7 @@
#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.Hosting.UnitTests/HostedOutboundUserAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HostedOutboundUserAgentTests.cs
index 30c4bef9dec..e77020f273e 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HostedOutboundUserAgentTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HostedOutboundUserAgentTests.cs
@@ -18,7 +18,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
index 0febf216b49..f8b3f5ba30c 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HttpHandlerAssert.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HttpHandlerAssert.cs
@@ -5,7 +5,7 @@
using System.Threading;
using System.Threading.Tasks;
-namespace Microsoft.Agents.AI.Foundry.UnitTests;
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
internal sealed class HttpHandlerAssert : HttpClientHandler
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/InputConverterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/InputConverterTests.cs
index f10a015c4bd..1f6e27ebea0 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/InputConverterTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/InputConverterTests.cs
@@ -7,7 +7,7 @@
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/OutputConverterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterTests.cs
index 2b8abfefcbe..b1c55933aa3 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterTests.cs
@@ -13,7 +13,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 OutputConverterTests
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/ServiceCollectionExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/ServiceCollectionExtensionsTests.cs
index c5b9bf1701f..43befab9691 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/ServiceCollectionExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/ServiceCollectionExtensionsTests.cs
@@ -9,7 +9,7 @@
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.Hosting.UnitTests/TestDataUtil.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestDataUtil.cs
index 5ee5af34e56..c62f2aedc21 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestDataUtil.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/TestDataUtil.cs
@@ -2,7 +2,7 @@
using System.IO;
-namespace Microsoft.Agents.AI.Foundry.UnitTests;
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
///
/// Utility class for loading toolbox-related test data files.
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/WorkflowIntegrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/WorkflowIntegrationTests.cs
index d87406f815c..195020ef924 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/WorkflowIntegrationTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/WorkflowIntegrationTests.cs
@@ -18,7 +18,7 @@
using Moq;
using MeaiTextContent = Microsoft.Extensions.AI.TextContent;
-namespace Microsoft.Agents.AI.Foundry.UnitTests.Hosting;
+namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
///
/// Integration tests that verify workflow execution through the
From e46a38e1174dd57e0cb687dd36712b2c9964ba8e Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Thu, 30 Apr 2026 18:05:50 +0100
Subject: [PATCH 3/7] Foundry.Hosting.UnitTests: split WorkflowIntegrationTests
by SUT
Replace the WorkflowIntegrationTests file (an IT-named file inside a UT project) with two SUT-focused files plus a shared test-doubles file:
- AgentFrameworkResponseHandlerWorkflowTests.cs - the 5 handler-driven tests that exercise AgentFrameworkResponseHandler with a real workflow agent.
- OutputConverterWorkflowTests.cs - the 5 OutputConverter tests driven by hand-crafted update sequences mirroring real workflow patterns.
- WorkflowTestAgents.cs - StreamingTextAgent and ThrowingStreamingAgent extracted as internal types used by both files.
---
...ntFrameworkResponseHandlerWorkflowTests.cs | 213 ++++++++
.../OutputConverterWorkflowTests.cs | 214 ++++++++
.../WorkflowIntegrationTests.cs | 508 ------------------
.../WorkflowTestAgents.cs | 96 ++++
4 files changed, 523 insertions(+), 508 deletions(-)
create mode 100644 dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerWorkflowTests.cs
create mode 100644 dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterWorkflowTests.cs
delete mode 100644 dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/WorkflowIntegrationTests.cs
create mode 100644 dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/WorkflowTestAgents.cs
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 00000000000..1524221ce2d
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerWorkflowTests.cs
@@ -0,0 +1,213 @@
+// 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.Foundry.Hosting;
+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/OutputConverterWorkflowTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterWorkflowTests.cs
new file mode 100644
index 00000000000..7231e610f84
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterWorkflowTests.cs
@@ -0,0 +1,214 @@
+// 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.Foundry.Hosting;
+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.Hosting.UnitTests/WorkflowIntegrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/WorkflowIntegrationTests.cs
deleted file mode 100644
index 195020ef924..00000000000
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/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.Hosting.UnitTests;
-
-///
-/// 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.Hosting.UnitTests/WorkflowTestAgents.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/WorkflowTestAgents.cs
new file mode 100644
index 00000000000..7e10abc9ebd
--- /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();
+}
From 571ca90df3fc7cef858fa3a0a235e82b4b3edd5d Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Thu, 30 Apr 2026 18:07:25 +0100
Subject: [PATCH 4/7] Foundry.UnitTests: trim Hosting-related conditionals and
dead testdata
Now that Hosting tests live in their own project:
- drop the Compile Remove guard for the Hosting subfolder,
- drop the .NETCoreApp-only PackageReferences (Azure.AI.AgentServer.Responses, Microsoft.AspNetCore.TestHost, OpenTelemetry, OpenTelemetry.Exporter.InMemory),
- drop the conditional ProjectReference to Microsoft.Agents.AI.Foundry.Hosting,
- delete the three Toolbox JSON files and the matching Toolbox getters in TestDataUtil.
---
...crosoft.Agents.AI.Foundry.UnitTests.csproj | 29 -------------------
.../TestData/ToolboxRecordResponse.json | 5 ----
.../TestData/ToolboxVersionResponse.json | 11 -------
.../ToolboxVersionWithDecorationFields.json | 11 -------
.../TestDataUtil.cs | 18 ------------
5 files changed, 74 deletions(-)
delete mode 100644 dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxRecordResponse.json
delete mode 100644 dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionResponse.json
delete mode 100644 dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionWithDecorationFields.json
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 22657197114..cfa5e7a11fe 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/TestData/ToolboxRecordResponse.json b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxRecordResponse.json
deleted file mode 100644
index 5208a572622..00000000000
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxRecordResponse.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "id": "tbx-123",
- "name": "research_tools",
- "default_version": "v5"
-}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionResponse.json b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionResponse.json
deleted file mode 100644
index 13f686ef8da..00000000000
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionResponse.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "metadata": {},
- "id": "tbv-research_tools-v5",
- "name": "research_tools",
- "version": "v5",
- "description": "Example research toolbox",
- "created_at": 1775779200,
- "tools": [
- { "type": "code_interpreter" }
- ]
-}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionWithDecorationFields.json b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionWithDecorationFields.json
deleted file mode 100644
index b78698bfb05..00000000000
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestData/ToolboxVersionWithDecorationFields.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "metadata": {},
- "id": "tbv-dirty-v1",
- "name": "dirty_toolbox",
- "version": "v1",
- "description": "Toolbox with decoration fields on tools",
- "created_at": 1775779200,
- "tools": [
- { "type": "code_interpreter", "name": "code_interpreter", "description": "Execute Python code" }
- ]
-}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestDataUtil.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/TestDataUtil.cs
index 898e0293c7f..3460362efd2 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;
}
From cde10f0c23ee484410f0fb072162b015ce242bf8 Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Thu, 30 Apr 2026 18:33:36 +0100
Subject: [PATCH 5/7] Foundry.Hosting.UnitTests: drop redundant 'using
Microsoft.Agents.AI.Foundry.Hosting'
The new project namespace is Microsoft.Agents.AI.Foundry.Hosting.UnitTests, which already brings the parent Microsoft.Agents.AI.Foundry.Hosting namespace into scope. The explicit using statement is therefore redundant (IDE0005). Caught by 'dotnet format --verify-no-changes' running on Linux against the .NET 10 SDK.
---
.../AgentFrameworkResponseHandlerTelemetryTests.cs | 1 -
.../AgentFrameworkResponseHandlerTests.cs | 1 -
.../AgentFrameworkResponseHandlerWorkflowTests.cs | 1 -
.../DelegatingResponsesClientTests.cs | 1 -
.../FoundryAIToolExtensionsTests.cs | 1 -
.../FoundryToolboxBearerTokenHandlerTests.cs | 1 -
.../FoundryToolboxServiceTests.cs | 1 -
.../FoundryToolboxTests.cs | 1 -
.../HostedOutboundUserAgentTests.cs | 1 -
.../InputConverterTests.cs | 1 -
.../OutputConverterTests.cs | 1 -
.../OutputConverterWorkflowTests.cs | 1 -
.../ServiceCollectionExtensionsTests.cs | 1 -
13 files changed, 13 deletions(-)
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTelemetryTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTelemetryTests.cs
index bb2db2eaa6c..dcb4e7e2125 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/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;
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerTests.cs
index 41a44f3ca58..eaf396eccc5 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/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;
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerWorkflowTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerWorkflowTests.cs
index 1524221ce2d..5954ddafc52 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerWorkflowTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/AgentFrameworkResponseHandlerWorkflowTests.cs
@@ -7,7 +7,6 @@
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.DependencyInjection;
using Microsoft.Extensions.Logging;
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs
index db2ada46e03..5dee33288da 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs
@@ -11,7 +11,6 @@
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;
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryAIToolExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryAIToolExtensionsTests.cs
index 62f2e277088..022fbf0bfe1 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryAIToolExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryAIToolExtensionsTests.cs
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
-using Microsoft.Agents.AI.Foundry.Hosting;
namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxBearerTokenHandlerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxBearerTokenHandlerTests.cs
index 85d94366aa3..e619445481a 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxBearerTokenHandlerTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxBearerTokenHandlerTests.cs
@@ -6,7 +6,6 @@
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
-using Microsoft.Agents.AI.Foundry.Hosting;
using Moq;
namespace Microsoft.Agents.AI.Foundry.Hosting.UnitTests;
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxServiceTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxServiceTests.cs
index 128ba34e26e..cdcdf5ee8e3 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxServiceTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxServiceTests.cs
@@ -4,7 +4,6 @@
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
-using Microsoft.Agents.AI.Foundry.Hosting;
using Microsoft.Extensions.Options;
using Moq;
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxTests.cs
index fcb17825f74..6c4be484a73 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/FoundryToolboxTests.cs
@@ -9,7 +9,6 @@
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
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HostedOutboundUserAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/HostedOutboundUserAgentTests.cs
index e77020f273e..31981509e44 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/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;
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/InputConverterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/InputConverterTests.cs
index 1f6e27ebea0..f4a46940e9f 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/InputConverterTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/InputConverterTests.cs
@@ -3,7 +3,6 @@
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;
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterTests.cs
index b1c55933aa3..5fa9a435a14 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterTests.cs
@@ -7,7 +7,6 @@
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;
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterWorkflowTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterWorkflowTests.cs
index 7231e610f84..f9917442926 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterWorkflowTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/OutputConverterWorkflowTests.cs
@@ -5,7 +5,6 @@
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;
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/ServiceCollectionExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/ServiceCollectionExtensionsTests.cs
index 43befab9691..bcab777af07 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/ServiceCollectionExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/ServiceCollectionExtensionsTests.cs
@@ -3,7 +3,6 @@
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;
From 25b5306ab8bf31445533d0b40c5cdcf599ff8e22 Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Thu, 30 Apr 2026 18:47:58 +0100
Subject: [PATCH 6/7] Foundry.Hosting: drop InternalsVisibleTo to
Foundry.UnitTests
The non-hosting Foundry.UnitTests project no longer holds any Hosting tests after the split, so it doesn't need access to internal types in Microsoft.Agents.AI.Foundry.Hosting. Only Microsoft.Agents.AI.Foundry.Hosting.UnitTests needs it.
---
.../Microsoft.Agents.AI.Foundry.Hosting.csproj | 1 -
1 file changed, 1 deletion(-)
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 b009c0e62fb..209edd9a820 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,6 @@
-
From f66997d6ed431e979c59e6096d102f8cc67db950 Mon Sep 17 00:00:00 2001
From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Date: Thu, 30 Apr 2026 19:03:06 +0100
Subject: [PATCH 7/7] Foundry.Hosting: rename DelegatingResponsesClient to
UserAgentResponsesClient
Address westey-m's review feedback on PR #5453: `Delegating*` is conventionally reserved for inheritable base classes (mirroring `DelegatingHandler`) where consumers override one or two members. This polyfill is sealed and only injects the User-Agent supplement, so the new name reflects its actual purpose.
Renamed via `git mv` to preserve history:
* `src/Microsoft.Agents.AI.Foundry.Hosting/DelegatingResponsesClient.cs` to `UserAgentResponsesClient.cs`
* `tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs` to `UserAgentResponsesClientTests.cs`
Class, constructor, and all references updated across:
* `src/.../UserAgentResponsesClient.cs` (class + constructor + internal log message)
* `src/.../ServiceCollectionExtensions.cs` (cref + type check + instantiation)
* `src/.../HostedAgentUserAgentPolicy.cs` (cref)
* `tests/Foundry.UnitTests/RequestOptionsExtensionsTests.cs` (comment)
* `tests/Foundry.Hosting.UnitTests/UserAgentResponsesClientTests.cs` (class + cref + instantiations)
---
.../HostedAgentUserAgentPolicy.cs | 2 +-
.../ServiceCollectionExtensions.cs | 8 ++++----
...tingResponsesClient.cs => UserAgentResponsesClient.cs} | 6 +++---
...sesClientTests.cs => UserAgentResponsesClientTests.cs} | 8 ++++----
.../RequestOptionsExtensionsTests.cs | 2 +-
5 files changed, 13 insertions(+), 13 deletions(-)
rename dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/{DelegatingResponsesClient.cs => UserAgentResponsesClient.cs} (94%)
rename dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/{DelegatingResponsesClientTests.cs => UserAgentResponsesClientTests.cs} (98%)
diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/HostedAgentUserAgentPolicy.cs b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/HostedAgentUserAgentPolicy.cs
index 8d5d3304719..c4130599a93 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/ServiceCollectionExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Foundry.Hosting/ServiceCollectionExtensions.cs
index 49eb745b6be..a84bbd3a6b3 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 ebf878b49ad..aaddfd89daa 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/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/UserAgentResponsesClientTests.cs
similarity index 98%
rename from dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs
rename to dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/UserAgentResponsesClientTests.cs
index 5dee33288da..c57bf6802e3 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/DelegatingResponsesClientTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.Hosting.UnitTests/UserAgentResponsesClientTests.cs
@@ -20,13 +20,13 @@
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";
@@ -214,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)
@@ -325,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.UnitTests/RequestOptionsExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/RequestOptionsExtensionsTests.cs
index 18824c4875a..df5dd8ebaec 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);
}