.NET: Fix FunctionInvocationDelegatingAgent to preserve all AgentRunOptions properties (#4179)

When converting base AgentRunOptions to ChatClientAgentRunOptions, the middleware
now preserves AllowBackgroundResponses, ContinuationToken, and AdditionalProperties
in addition to ResponseFormat.

Added unit test verifying all properties are preserved during the conversion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
SergeyMenshykh
2026-02-23 16:37:43 +00:00
committed by GitHub
Unverified
parent ba454552c5
commit 892c177e93
2 changed files with 61 additions and 1 deletions
@@ -32,7 +32,13 @@ internal sealed class FunctionInvocationDelegatingAgent : DelegatingAIAgent
{
if (options is null || options.GetType() == typeof(AgentRunOptions))
{
options = new ChatClientAgentRunOptions();
options = new ChatClientAgentRunOptions()
{
ResponseFormat = options?.ResponseFormat,
AllowBackgroundResponses = options?.AllowBackgroundResponses,
ContinuationToken = options?.ContinuationToken,
AdditionalProperties = options?.AdditionalProperties,
};
}
if (options is not ChatClientAgentRunOptions aco)
@@ -935,6 +935,60 @@ public sealed class FunctionInvocationDelegatingAgentTests
#endregion
#region Options Preservation Tests
/// <summary>
/// Tests that FunctionInvocationDelegatingAgent preserves all original AgentRunOptions properties
/// when converting base AgentRunOptions to ChatClientAgentRunOptions.
/// </summary>
[Fact]
public async Task RunAsync_WithBaseAgentRunOptions_PreservesAllOriginalOptionsAsync()
{
// Arrange
AgentRunOptions? capturedOptions = null;
var responseFormat = ChatResponseFormat.Json;
var additionalProperties = new AdditionalPropertiesDictionary { ["key1"] = "value1" };
Mock<IChatClient> mockChatClient = new();
var chatClientAgent = new ChatClientAgent(mockChatClient.Object);
// Wrap the inner agent in a spy that captures the converted options and returns a dummy response
var spyAgent = new AnonymousDelegatingAIAgent(
chatClientAgent,
runFunc: (messages, session, options, innerAgent, ct) =>
{
capturedOptions = options;
return Task.FromResult(new AgentResponse(new ChatResponse(new ChatMessage(ChatRole.Assistant, "test")) { ResponseId = "test" }));
},
runStreamingFunc: null);
static ValueTask<object?> MiddlewareCallbackAsync(AIAgent agent, FunctionInvocationContext context, Func<FunctionInvocationContext, CancellationToken, ValueTask<object?>> next, CancellationToken cancellationToken)
=> next(context, cancellationToken);
var middleware = new FunctionInvocationDelegatingAgent(spyAgent, MiddlewareCallbackAsync);
var originalOptions = new AgentRunOptions
{
ResponseFormat = responseFormat,
AllowBackgroundResponses = true,
ContinuationToken = ResponseContinuationToken.FromBytes(new byte[] { 1, 2, 3 }),
AdditionalProperties = additionalProperties,
};
// Act
await middleware.RunAsync([new(ChatRole.User, "Test")], null, originalOptions, CancellationToken.None);
// Assert - All original properties were preserved on the converted options
Assert.NotNull(capturedOptions);
Assert.IsType<ChatClientAgentRunOptions>(capturedOptions);
Assert.Same(responseFormat, capturedOptions.ResponseFormat);
Assert.True(capturedOptions.AllowBackgroundResponses);
Assert.Same(originalOptions.ContinuationToken, capturedOptions.ContinuationToken);
Assert.Same(additionalProperties, capturedOptions.AdditionalProperties);
}
#endregion
/// <summary>
/// Creates a mock IChatClient with predefined responses for testing.
/// </summary>