diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs
index 4ceff757431..642c0da2034 100644
--- a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs
@@ -120,9 +120,24 @@ public static IChatClient AsIChatClientWithStoredOutputDisabled(this ResponsesCl
return Throw.IfNull(responseClient)
.AsIChatClient(model)
.AsBuilder()
- .ConfigureOptions(x => x.RawRepresentationFactory = _ => includeReasoningEncryptedContent
- ? new CreateResponseOptions() { StoredOutputEnabled = false, IncludedProperties = { IncludedResponseProperty.ReasoningEncryptedContent } }
- : new CreateResponseOptions() { StoredOutputEnabled = false })
+ .ConfigureOptions(x =>
+ {
+ var previousFactory = x.RawRepresentationFactory;
+ x.RawRepresentationFactory = state =>
+ {
+ var responseOptions = previousFactory?.Invoke(state) as CreateResponseOptions ?? new CreateResponseOptions();
+
+ responseOptions.StoredOutputEnabled = false;
+
+ if (includeReasoningEncryptedContent &&
+ !responseOptions.IncludedProperties.Contains(IncludedResponseProperty.ReasoningEncryptedContent))
+ {
+ responseOptions.IncludedProperties.Add(IncludedResponseProperty.ReasoningEncryptedContent);
+ }
+
+ return responseOptions;
+ };
+ })
.Build();
}
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/ProjectResponsesClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/ProjectResponsesClientExtensionsTests.cs
index fda9962e124..b1424bb403b 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/ProjectResponsesClientExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/ProjectResponsesClientExtensionsTests.cs
@@ -129,6 +129,75 @@ public void AsIChatClientWithStoredOutputDisabled_WithIncludeReasoningFalse_Conf
Assert.DoesNotContain(IncludedResponseProperty.ReasoningEncryptedContent, createResponseOptions.IncludedProperties);
}
+ ///
+ /// Verify that AsIChatClientWithStoredOutputDisabled preserves an existing RawRepresentationFactory
+ /// set on ChatOptions, augmenting it with StoredOutputEnabled and ReasoningEncryptedContent
+ /// rather than replacing it.
+ ///
+ [Fact]
+ public void AsIChatClientWithStoredOutputDisabled_PreservesExistingRawRepresentationFactory()
+ {
+ // Arrange
+ var responseClient = CreateTestClient();
+ var chatClient = responseClient.AsIChatClientWithStoredOutputDisabled();
+
+ // Simulate a caller setting their own RawRepresentationFactory on ChatOptions
+ // (e.g., to add WebSearchCallActionSources).
+ var options = new ChatOptions
+ {
+ RawRepresentationFactory = _ => new CreateResponseOptions
+ {
+ IncludedProperties = { IncludedResponseProperty.WebSearchCallActionSources },
+ },
+ };
+
+ // Act
+ var createResponseOptions = GetCreateResponseOptionsFromPipeline(chatClient, options);
+
+ // Assert
+ Assert.NotNull(createResponseOptions);
+ Assert.False(createResponseOptions.StoredOutputEnabled);
+ Assert.Contains(IncludedResponseProperty.ReasoningEncryptedContent, createResponseOptions.IncludedProperties);
+ Assert.Contains(IncludedResponseProperty.WebSearchCallActionSources, createResponseOptions.IncludedProperties);
+ }
+
+ ///
+ /// Verify that AsIChatClientWithStoredOutputDisabled does not duplicate ReasoningEncryptedContent
+ /// when the existing factory already includes it.
+ ///
+ [Fact]
+ public void AsIChatClientWithStoredOutputDisabled_DoesNotDuplicateReasoningEncryptedContent()
+ {
+ // Arrange
+ var responseClient = CreateTestClient();
+ var chatClient = responseClient.AsIChatClientWithStoredOutputDisabled();
+
+ // Simulate a caller that already includes ReasoningEncryptedContent
+ var options = new ChatOptions
+ {
+ RawRepresentationFactory = _ => new CreateResponseOptions
+ {
+ IncludedProperties = { IncludedResponseProperty.ReasoningEncryptedContent },
+ },
+ };
+
+ // Act
+ var createResponseOptions = GetCreateResponseOptionsFromPipeline(chatClient, options);
+
+ // Assert - ReasoningEncryptedContent should appear exactly once
+ Assert.NotNull(createResponseOptions);
+ int count = 0;
+ foreach (var prop in createResponseOptions.IncludedProperties)
+ {
+ if (prop == IncludedResponseProperty.ReasoningEncryptedContent)
+ {
+ count++;
+ }
+ }
+
+ Assert.Equal(1, count);
+ }
+
///
/// Verify that AsIChatClientWithStoredOutputDisabled works with an optional deployment name.
///
@@ -153,6 +222,15 @@ public void AsIChatClientWithStoredOutputDisabled_WithDeploymentName_ConfiguresS
/// by using reflection to access the configure action and invoking it on a test .
///
private static CreateResponseOptions? GetCreateResponseOptionsFromPipeline(IChatClient chatClient)
+ {
+ return GetCreateResponseOptionsFromPipeline(chatClient, new ChatOptions());
+ }
+
+ ///
+ /// Overload that runs the configure action on caller-supplied ,
+ /// useful for testing that existing factories are preserved.
+ ///
+ private static CreateResponseOptions? GetCreateResponseOptionsFromPipeline(IChatClient chatClient, ChatOptions options)
{
var configureField = chatClient.GetType().GetField("_configureOptions", BindingFlags.NonPublic | BindingFlags.Instance);
Assert.NotNull(configureField);
@@ -160,7 +238,6 @@ public void AsIChatClientWithStoredOutputDisabled_WithDeploymentName_ConfiguresS
var configureAction = configureField.GetValue(chatClient) as Action;
Assert.NotNull(configureAction);
- var options = new ChatOptions();
configureAction(options);
Assert.NotNull(options.RawRepresentationFactory);
diff --git a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIResponseClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIResponseClientExtensionsTests.cs
index 1205889e199..eede6ec6375 100644
--- a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIResponseClientExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIResponseClientExtensionsTests.cs
@@ -370,6 +370,75 @@ public void AsIChatClientWithStoredOutputDisabled_WithIncludeReasoningFalse_Conf
Assert.DoesNotContain(IncludedResponseProperty.ReasoningEncryptedContent, createResponseOptions.IncludedProperties);
}
+ ///
+ /// Verify that AsIChatClientWithStoredOutputDisabled preserves an existing RawRepresentationFactory
+ /// set on ChatOptions, augmenting it with StoredOutputEnabled and ReasoningEncryptedContent
+ /// rather than replacing it.
+ ///
+ [Fact]
+ public void AsIChatClientWithStoredOutputDisabled_PreservesExistingRawRepresentationFactory()
+ {
+ // Arrange
+ var responseClient = new TestOpenAIResponseClient();
+ var chatClient = responseClient.AsIChatClientWithStoredOutputDisabled();
+
+ // Simulate a caller setting their own RawRepresentationFactory on ChatOptions
+ // (e.g., to add WebSearchCallActionSources).
+ var options = new ChatOptions
+ {
+ RawRepresentationFactory = _ => new CreateResponseOptions
+ {
+ IncludedProperties = { IncludedResponseProperty.WebSearchCallActionSources },
+ },
+ };
+
+ // Act
+ var createResponseOptions = GetCreateResponseOptionsFromPipeline(chatClient, options);
+
+ // Assert
+ Assert.NotNull(createResponseOptions);
+ Assert.False(createResponseOptions.StoredOutputEnabled);
+ Assert.Contains(IncludedResponseProperty.ReasoningEncryptedContent, createResponseOptions.IncludedProperties);
+ Assert.Contains(IncludedResponseProperty.WebSearchCallActionSources, createResponseOptions.IncludedProperties);
+ }
+
+ ///
+ /// Verify that AsIChatClientWithStoredOutputDisabled does not duplicate ReasoningEncryptedContent
+ /// when the existing factory already includes it.
+ ///
+ [Fact]
+ public void AsIChatClientWithStoredOutputDisabled_DoesNotDuplicateReasoningEncryptedContent()
+ {
+ // Arrange
+ var responseClient = new TestOpenAIResponseClient();
+ var chatClient = responseClient.AsIChatClientWithStoredOutputDisabled();
+
+ // Simulate a caller that already includes ReasoningEncryptedContent
+ var options = new ChatOptions
+ {
+ RawRepresentationFactory = _ => new CreateResponseOptions
+ {
+ IncludedProperties = { IncludedResponseProperty.ReasoningEncryptedContent },
+ },
+ };
+
+ // Act
+ var createResponseOptions = GetCreateResponseOptionsFromPipeline(chatClient, options);
+
+ // Assert - ReasoningEncryptedContent should appear exactly once
+ Assert.NotNull(createResponseOptions);
+ int count = 0;
+ foreach (var prop in createResponseOptions.IncludedProperties)
+ {
+ if (prop == IncludedResponseProperty.ReasoningEncryptedContent)
+ {
+ count++;
+ }
+ }
+
+ Assert.Equal(1, count);
+ }
+
///
/// A simple test IServiceProvider implementation for testing.
///
@@ -394,6 +463,15 @@ private sealed class TestServiceProvider : IServiceProvider
/// by using reflection to access the configure action and invoking it on a test .
///
private static CreateResponseOptions? GetCreateResponseOptionsFromPipeline(IChatClient chatClient)
+ {
+ return GetCreateResponseOptionsFromPipeline(chatClient, new ChatOptions());
+ }
+
+ ///
+ /// Overload that runs the configure action on caller-supplied ,
+ /// useful for testing that existing factories are preserved.
+ ///
+ private static CreateResponseOptions? GetCreateResponseOptionsFromPipeline(IChatClient chatClient, ChatOptions options)
{
// The ConfigureOptionsChatClient stores the configure action in a private field.
var configureField = chatClient.GetType().GetField("_configureOptions", BindingFlags.NonPublic | BindingFlags.Instance);
@@ -402,7 +480,6 @@ private sealed class TestServiceProvider : IServiceProvider
var configureAction = configureField.GetValue(chatClient) as Action;
Assert.NotNull(configureAction);
- var options = new ChatOptions();
configureAction(options);
Assert.NotNull(options.RawRepresentationFactory);