diff --git a/dotnet/src/Microsoft.Agents.AI.Foundry/FoundryAgent.cs b/dotnet/src/Microsoft.Agents.AI.Foundry/FoundryAgent.cs
index 816d56302a..30bfc84d9a 100644
--- a/dotnet/src/Microsoft.Agents.AI.Foundry/FoundryAgent.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Foundry/FoundryAgent.cs
@@ -428,18 +428,31 @@ public sealed class FoundryAgent : DelegatingAIAgent
return new AIProjectClient(endpoint, credential, clientOptions);
}
- private static AIProjectClientOptions? CreateProjectClientOptions(ProjectOpenAIClientOptions? clientOptions)
+ internal static AIProjectClientOptions? CreateProjectClientOptions(ProjectOpenAIClientOptions? clientOptions)
{
if (clientOptions is null)
{
return null;
}
- return new AIProjectClientOptions
+ // Copy pipeline behavior the caller configured on the per-agent options bag onto the
+ // project-level options bag so the agent endpoint client honors it. UserAgentApplicationId
+ // is project-level (not derived from the agent endpoint), so it must be carried through too.
+ var projectOptions = new AIProjectClientOptions
{
Transport = clientOptions.Transport,
+ RetryPolicy = clientOptions.RetryPolicy,
+ NetworkTimeout = clientOptions.NetworkTimeout,
+ MessageLoggingPolicy = clientOptions.MessageLoggingPolicy,
UserAgentApplicationId = clientOptions.UserAgentApplicationId,
};
+
+ if (clientOptions.ClientLoggingOptions is not null)
+ {
+ projectOptions.ClientLoggingOptions = clientOptions.ClientLoggingOptions;
+ }
+
+ return projectOptions;
}
#endregion
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/AzureAIProjectChatClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/AzureAIProjectChatClientExtensionsTests.cs
index 7b01f8a79b..2996be725d 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/AzureAIProjectChatClientExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/AzureAIProjectChatClientExtensionsTests.cs
@@ -1459,6 +1459,52 @@ public sealed class AzureAIProjectChatClientExtensionsTests
Assert.Same(testChatClient, retrievedTestClient);
}
+ ///
+ /// Verify that AsAIAgent(Uri agentEndpoint) forwards the supplied tools to the inner
+ /// 's .
+ ///
+ [Fact]
+ public void AsAIAgent_WithAgentEndpoint_ForwardsToolsToInnerChatOptions()
+ {
+ // Arrange
+ AIProjectClient client = this.CreateTestAgentClient();
+ var tool1 = AIFunctionFactory.Create(() => "result-1", "tool_1", "First test tool.");
+ var tool2 = AIFunctionFactory.Create(() => "result-2", "tool_2", "Second test tool.");
+ List tools = [tool1, tool2];
+
+ // Act
+ var agent = client.AsAIAgent(new Uri(TestAgentEndpointUrl), tools: tools);
+
+ // Assert
+ Assert.NotNull(agent);
+ ChatOptions? chatOptions = GetAgentChatOptions(agent);
+ Assert.NotNull(chatOptions);
+ Assert.NotNull(chatOptions!.Tools);
+ Assert.Equal(2, chatOptions.Tools!.Count);
+ Assert.Same(tool1, chatOptions.Tools[0]);
+ Assert.Same(tool2, chatOptions.Tools[1]);
+ }
+
+ ///
+ /// Verify that AsAIAgent(Uri agentEndpoint) accepts a null tools argument without throwing
+ /// and produces an agent whose inner is null.
+ ///
+ [Fact]
+ public void AsAIAgent_WithAgentEndpoint_WithNullTools_DoesNotThrow()
+ {
+ // Arrange
+ AIProjectClient client = this.CreateTestAgentClient();
+
+ // Act
+ var agent = client.AsAIAgent(new Uri(TestAgentEndpointUrl), tools: null);
+
+ // Assert
+ Assert.NotNull(agent);
+ ChatOptions? chatOptions = GetAgentChatOptions(agent);
+ Assert.NotNull(chatOptions);
+ Assert.Null(chatOptions!.Tools);
+ }
+
#endregion
#region Helper Methods
diff --git a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/FoundryAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/FoundryAgentTests.cs
index 4ccd1296c5..11f36c2797 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/FoundryAgentTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Foundry.UnitTests/FoundryAgentTests.cs
@@ -2,6 +2,7 @@
using System;
using System.ClientModel.Primitives;
+using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
@@ -356,7 +357,7 @@ public class FoundryAgentTests
bool userAgentFound = false;
using HttpHandlerAssert httpHandler = new(request =>
{
- if (request.Headers.TryGetValues("User-Agent", out System.Collections.Generic.IEnumerable? values))
+ if (request.Headers.TryGetValues("User-Agent", out IEnumerable? values))
{
foreach (string value in values)
{
@@ -676,6 +677,71 @@ public class FoundryAgentTests
Assert.Equal("my-app-id", opts.UserAgentApplicationId);
}
+ [Fact]
+ public void CreateProjectClientOptions_NullCallerOptions_ReturnsNull()
+ {
+ Assert.Null(FoundryAgent.CreateProjectClientOptions(null));
+ }
+
+ [Fact]
+ public void CreateProjectClientOptions_CarriesPipelineSettingsAndUserAgent()
+ {
+ // Arrange
+ var transport = new FakePipelineTransport();
+ var retryPolicy = new FakeRetryPolicy();
+ var messageLoggingPolicy = new FakeMessageLoggingPolicy();
+ var clientLoggingOptions = new ClientLoggingOptions { EnableLogging = false };
+ var networkTimeout = TimeSpan.FromSeconds(42);
+
+ ProjectOpenAIClientOptions callerOptions = new()
+ {
+ UserAgentApplicationId = "my-app-id",
+ Transport = transport,
+ RetryPolicy = retryPolicy,
+ MessageLoggingPolicy = messageLoggingPolicy,
+ ClientLoggingOptions = clientLoggingOptions,
+ NetworkTimeout = networkTimeout,
+ };
+
+ // Act
+ AIProjectClientOptions? projectOptions = FoundryAgent.CreateProjectClientOptions(callerOptions);
+
+ // Assert: every settable pipeline behavior the caller configured is forwarded
+ // onto the project-level options bag, not silently dropped.
+ Assert.NotNull(projectOptions);
+ Assert.Equal("my-app-id", projectOptions!.UserAgentApplicationId);
+ Assert.Same(transport, projectOptions.Transport);
+ Assert.Same(retryPolicy, projectOptions.RetryPolicy);
+ Assert.Same(messageLoggingPolicy, projectOptions.MessageLoggingPolicy);
+ Assert.Same(clientLoggingOptions, projectOptions.ClientLoggingOptions);
+ Assert.Equal(networkTimeout, projectOptions.NetworkTimeout);
+ }
+
+ private sealed class FakeRetryPolicy : PipelinePolicy
+ {
+ public override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex)
+ => ProcessNext(message, pipeline, currentIndex);
+
+ public override ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex)
+ => ProcessNextAsync(message, pipeline, currentIndex);
+ }
+
+ private sealed class FakeMessageLoggingPolicy : PipelinePolicy
+ {
+ public override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex)
+ => ProcessNext(message, pipeline, currentIndex);
+
+ public override ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex)
+ => ProcessNextAsync(message, pipeline, currentIndex);
+ }
+
+ private sealed class FakePipelineTransport : PipelineTransport
+ {
+ protected override PipelineMessage CreateMessageCore() => throw new NotSupportedException();
+ protected override void ProcessCore(PipelineMessage message) => throw new NotSupportedException();
+ protected override ValueTask ProcessCoreAsync(PipelineMessage message) => throw new NotSupportedException();
+ }
+
#endregion
#region ParseAgentEndpoint tests
@@ -758,13 +824,13 @@ public class FoundryAgentTests
private readonly string _value;
public HeaderStampPolicy(string name, string value) { this._name = name; this._value = value; }
- public override void Process(PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex)
+ public override void Process(PipelineMessage message, IReadOnlyList pipeline, int currentIndex)
{
message.Request.Headers.Set(this._name, this._value);
ProcessNext(message, pipeline, currentIndex);
}
- public override ValueTask ProcessAsync(PipelineMessage message, System.Collections.Generic.IReadOnlyList pipeline, int currentIndex)
+ public override ValueTask ProcessAsync(PipelineMessage message, IReadOnlyList pipeline, int currentIndex)
{
message.Request.Headers.Set(this._name, this._value);
return ProcessNextAsync(message, pipeline, currentIndex);