diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index b2dd14e9b0..83ac69796b 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -416,6 +416,10 @@
+
+
+
+
diff --git a/dotnet/eng/MSBuild/Shared.props b/dotnet/eng/MSBuild/Shared.props
index 9b4771a64e..94ac5b417b 100644
--- a/dotnet/eng/MSBuild/Shared.props
+++ b/dotnet/eng/MSBuild/Shared.props
@@ -8,6 +8,9 @@
+
+
+
diff --git a/dotnet/nuget/nuget-package.props b/dotnet/nuget/nuget-package.props
index dcfcac4077..ee3b144b06 100644
--- a/dotnet/nuget/nuget-package.props
+++ b/dotnet/nuget/nuget-package.props
@@ -2,11 +2,11 @@
1.0.0
- 2
+ 3
$(VersionPrefix)-rc$(RCNumber)
- $(VersionPrefix)-$(VersionSuffix).260225.1
- $(VersionPrefix)-preview.260225.1
- 1.0.0-rc2
+ $(VersionPrefix)-$(VersionSuffix).260304.1
+ $(VersionPrefix)-preview.260304.1
+ 1.0.0-rc3
Debug;Release;Publish
true
diff --git a/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs b/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs
index c966f591fc..bbebd7a312 100644
--- a/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs
+++ b/dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs
@@ -346,14 +346,14 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
};
}
- private AgentResponseUpdate ConvertToAgentResponseUpdate(AssistantMessageEvent assistantMessage)
+ internal AgentResponseUpdate ConvertToAgentResponseUpdate(AssistantMessageEvent assistantMessage)
{
- TextContent textContent = new(assistantMessage.Data?.Content ?? string.Empty)
+ AIContent content = new()
{
RawRepresentation = assistantMessage
};
- return new AgentResponseUpdate(ChatRole.Assistant, [textContent])
+ return new AgentResponseUpdate(ChatRole.Assistant, [content])
{
AgentId = this.Id,
ResponseId = assistantMessage.Data?.MessageId,
diff --git a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs
index 7db4eff6d8..e4b772160e 100644
--- a/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs
+++ b/dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgent.cs
@@ -747,9 +747,15 @@ public sealed partial class ChatClientAgent : AIAgent
{
// The agent has a ChatHistoryProvider configured, but the service returned a conversation id,
// meaning the service manages chat history server-side. Both cannot be used simultaneously.
- if (this._agentOptions?.WarnOnChatHistoryProviderConflict is true)
+ if (this._agentOptions?.WarnOnChatHistoryProviderConflict is true
+ && this._logger.IsEnabled(LogLevel.Warning))
{
- this._logger.LogAgentChatClientHistoryProviderConflict(nameof(ChatClientAgentSession.ConversationId), nameof(this.ChatHistoryProvider), this.Id, this.GetLoggingAgentName());
+ var loggingAgentName = this.GetLoggingAgentName();
+ this._logger.LogAgentChatClientHistoryProviderConflict(
+ nameof(ChatClientAgentSession.ConversationId),
+ nameof(this.ChatHistoryProvider),
+ this.Id,
+ loggingAgentName);
}
if (this._agentOptions?.ThrowOnChatHistoryProviderConflict is true)
diff --git a/dotnet/src/Shared/IntegrationTestsAzureCredentials/README.md b/dotnet/src/Shared/IntegrationTestsAzureCredentials/README.md
new file mode 100644
index 0000000000..e26295ed7f
--- /dev/null
+++ b/dotnet/src/Shared/IntegrationTestsAzureCredentials/README.md
@@ -0,0 +1,9 @@
+# Integration Tests Azure Credentials
+
+Adds a helper for loading Azure credentials in integration tests.
+
+```xml
+
+ true
+
+```
diff --git a/dotnet/src/Shared/IntegrationTestsAzureCredentials/TestAzureCliCredentials.cs b/dotnet/src/Shared/IntegrationTestsAzureCredentials/TestAzureCliCredentials.cs
new file mode 100644
index 0000000000..f1c83ce1f2
--- /dev/null
+++ b/dotnet/src/Shared/IntegrationTestsAzureCredentials/TestAzureCliCredentials.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+#pragma warning disable IDE0005 // This is required in some projects and not in others.
+using System;
+#pragma warning restore IDE0005
+using Azure.Identity;
+
+namespace Shared.IntegrationTests;
+
+///
+/// Provides credential instances for integration tests with
+/// increased timeouts to avoid CI pipeline authentication failures.
+///
+internal static class TestAzureCliCredentials
+{
+ ///
+ /// The default timeout for Azure CLI credential operations.
+ /// Increased from the default (~13s) to accommodate CI pipeline latency.
+ ///
+ private static readonly TimeSpan s_processTimeout = TimeSpan.FromSeconds(60);
+
+ ///
+ /// Creates a new with an increased process timeout
+ /// suitable for CI environments.
+ ///
+ public static AzureCliCredential CreateAzureCliCredential() =>
+ new(new AzureCliCredentialOptions { ProcessTimeout = s_processTimeout });
+}
diff --git a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientCreateTests.cs b/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientCreateTests.cs
index ec4103f6a8..a6691a41bd 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientCreateTests.cs
+++ b/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientCreateTests.cs
@@ -6,7 +6,6 @@ using System.Threading.Tasks;
using AgentConformance.IntegrationTests.Support;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
-using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI.Files;
@@ -17,7 +16,7 @@ namespace AzureAI.IntegrationTests;
public class AIProjectClientCreateTests
{
- private readonly AIProjectClient _client = new(new Uri(TestConfiguration.GetRequiredValue(TestSettings.AzureAIProjectEndpoint)), new AzureCliCredential());
+ private readonly AIProjectClient _client = new(new Uri(TestConfiguration.GetRequiredValue(TestSettings.AzureAIProjectEndpoint)), TestAzureCliCredentials.CreateAzureCliCredential());
[Theory]
[InlineData("CreateWithChatClientAgentOptionsAsync")]
diff --git a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientFixture.cs b/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientFixture.cs
index 192e8b6c9e..6356bb6e01 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientFixture.cs
+++ b/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientFixture.cs
@@ -8,7 +8,6 @@ using AgentConformance.IntegrationTests;
using AgentConformance.IntegrationTests.Support;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
-using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI.Responses;
@@ -170,13 +169,13 @@ public class AIProjectClientFixture : IChatClientAgentFixture
public virtual async ValueTask InitializeAsync()
{
- this._client = new(new Uri(TestConfiguration.GetRequiredValue(TestSettings.AzureAIProjectEndpoint)), new AzureCliCredential());
+ this._client = new(new Uri(TestConfiguration.GetRequiredValue(TestSettings.AzureAIProjectEndpoint)), TestAzureCliCredentials.CreateAzureCliCredential());
this._agent = await this.CreateChatClientAgentAsync();
}
public async Task InitializeAsync(ChatClientAgentOptions options)
{
- this._client = new(new Uri(TestConfiguration.GetRequiredValue(TestSettings.AzureAIProjectEndpoint)), new AzureCliCredential());
+ this._client = new(new Uri(TestConfiguration.GetRequiredValue(TestSettings.AzureAIProjectEndpoint)), TestAzureCliCredentials.CreateAzureCliCredential());
this._agent = await this.CreateChatClientAgentAsync(options);
}
}
diff --git a/dotnet/tests/AzureAI.IntegrationTests/AzureAI.IntegrationTests.csproj b/dotnet/tests/AzureAI.IntegrationTests/AzureAI.IntegrationTests.csproj
index 7ea633e3d4..2703360cb2 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/AzureAI.IntegrationTests.csproj
+++ b/dotnet/tests/AzureAI.IntegrationTests/AzureAI.IntegrationTests.csproj
@@ -3,6 +3,7 @@
$(NoWarn);CS8793
True
+ True
diff --git a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistent.IntegrationTests.csproj b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistent.IntegrationTests.csproj
index 2b12fe728a..0913d484e5 100644
--- a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistent.IntegrationTests.csproj
+++ b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistent.IntegrationTests.csproj
@@ -3,6 +3,7 @@
$(NoWarn);CS8793
True
+ True
diff --git a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentCreateTests.cs b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentCreateTests.cs
index f750b5a8e7..6b29bb4b08 100644
--- a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentCreateTests.cs
+++ b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentCreateTests.cs
@@ -6,7 +6,6 @@ using System.IO;
using System.Threading.Tasks;
using AgentConformance.IntegrationTests.Support;
using Azure.AI.Agents.Persistent;
-using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Shared.IntegrationTests;
@@ -15,7 +14,7 @@ namespace AzureAIAgentsPersistent.IntegrationTests;
public class AzureAIAgentsPersistentCreateTests
{
- private readonly PersistentAgentsClient _persistentAgentsClient = new(TestConfiguration.GetRequiredValue(TestSettings.AzureAIProjectEndpoint), new AzureCliCredential());
+ private readonly PersistentAgentsClient _persistentAgentsClient = new(TestConfiguration.GetRequiredValue(TestSettings.AzureAIProjectEndpoint), TestAzureCliCredentials.CreateAzureCliCredential());
[Theory]
[InlineData("CreateWithChatClientAgentOptionsAsync")]
diff --git a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentFixture.cs b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentFixture.cs
index 1ebbd2b4f1..e6446be1cf 100644
--- a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentFixture.cs
+++ b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentFixture.cs
@@ -7,7 +7,6 @@ using AgentConformance.IntegrationTests;
using AgentConformance.IntegrationTests.Support;
using Azure;
using Azure.AI.Agents.Persistent;
-using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Shared.IntegrationTests;
@@ -99,7 +98,7 @@ public class AzureAIAgentsPersistentFixture : IChatClientAgentFixture
public async ValueTask InitializeAsync()
{
- this._persistentAgentsClient = new(TestConfiguration.GetRequiredValue(TestSettings.AzureAIProjectEndpoint), new AzureCliCredential());
+ this._persistentAgentsClient = new(TestConfiguration.GetRequiredValue(TestSettings.AzureAIProjectEndpoint), TestAzureCliCredentials.CreateAzureCliCredential());
this._agent = await this.CreateChatClientAgentAsync();
}
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/Microsoft.Agents.AI.DurableTask.IntegrationTests.csproj b/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/Microsoft.Agents.AI.DurableTask.IntegrationTests.csproj
index ac4f52e3eb..adc184e510 100644
--- a/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/Microsoft.Agents.AI.DurableTask.IntegrationTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/Microsoft.Agents.AI.DurableTask.IntegrationTests.csproj
@@ -3,6 +3,7 @@
$(TargetFrameworksCore)
enable
+ True
diff --git a/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/TestHelper.cs b/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/TestHelper.cs
index e092db9e8d..d9350cec59 100644
--- a/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/TestHelper.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/TestHelper.cs
@@ -2,7 +2,6 @@
using Azure;
using Azure.AI.OpenAI;
-using Azure.Identity;
using Microsoft.Agents.AI.DurableTask.IntegrationTests.Logging;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
@@ -14,6 +13,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using OpenAI.Chat;
+using Shared.IntegrationTests;
namespace Microsoft.Agents.AI.DurableTask.IntegrationTests;
@@ -165,7 +165,7 @@ internal sealed class TestHelper : IDisposable
AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
? new AzureOpenAIClient(new Uri(azureOpenAiEndpoint), new AzureKeyCredential(azureOpenAiKey))
- : new AzureOpenAIClient(new Uri(azureOpenAiEndpoint), new AzureCliCredential());
+ : new AzureOpenAIClient(new Uri(azureOpenAiEndpoint), TestAzureCliCredentials.CreateAzureCliCredential());
return client.GetChatClient(azureOpenAiDeploymentName);
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.FoundryMemory.IntegrationTests/FoundryMemoryProviderTests.cs b/dotnet/tests/Microsoft.Agents.AI.FoundryMemory.IntegrationTests/FoundryMemoryProviderTests.cs
index 4b1838335c..9b3c95c5c2 100644
--- a/dotnet/tests/Microsoft.Agents.AI.FoundryMemory.IntegrationTests/FoundryMemoryProviderTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.FoundryMemory.IntegrationTests/FoundryMemoryProviderTests.cs
@@ -3,7 +3,6 @@
using System;
using System.Threading.Tasks;
using Azure.AI.Projects;
-using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Shared.IntegrationTests;
@@ -41,7 +40,7 @@ public sealed class FoundryMemoryProviderTests : IDisposable
if (!string.IsNullOrWhiteSpace(endpoint) &&
!string.IsNullOrWhiteSpace(memoryStoreName))
{
- this._client = new AIProjectClient(new Uri(endpoint), new AzureCliCredential());
+ this._client = new AIProjectClient(new Uri(endpoint), TestAzureCliCredentials.CreateAzureCliCredential());
this._memoryStoreName = memoryStoreName;
this._deploymentName = deploymentName ?? "gpt-4.1-mini";
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.FoundryMemory.IntegrationTests/Microsoft.Agents.AI.FoundryMemory.IntegrationTests.csproj b/dotnet/tests/Microsoft.Agents.AI.FoundryMemory.IntegrationTests/Microsoft.Agents.AI.FoundryMemory.IntegrationTests.csproj
index 4bf96a5b35..af184142ca 100644
--- a/dotnet/tests/Microsoft.Agents.AI.FoundryMemory.IntegrationTests/Microsoft.Agents.AI.FoundryMemory.IntegrationTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.FoundryMemory.IntegrationTests/Microsoft.Agents.AI.FoundryMemory.IntegrationTests.csproj
@@ -2,6 +2,7 @@
True
+ True
diff --git a/dotnet/tests/Microsoft.Agents.AI.GitHub.Copilot.UnitTests/GitHubCopilotAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.GitHub.Copilot.UnitTests/GitHubCopilotAgentTests.cs
index 5806636925..52ea0026dc 100644
--- a/dotnet/tests/Microsoft.Agents.AI.GitHub.Copilot.UnitTests/GitHubCopilotAgentTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.GitHub.Copilot.UnitTests/GitHubCopilotAgentTests.cs
@@ -221,4 +221,26 @@ public sealed class GitHubCopilotAgentTests
Assert.Null(result.ConfigDir);
Assert.True(result.Streaming);
}
+
+ [Fact]
+ public void ConvertToAgentResponseUpdate_AssistantMessageEvent_DoesNotEmitTextContent()
+ {
+ var assistantMessage = new AssistantMessageEvent
+ {
+ Data = new AssistantMessageData
+ {
+ MessageId = "msg-456",
+ Content = "Some streamed content that was already delivered via delta events"
+ }
+ };
+ CopilotClient copilotClient = new(new CopilotClientOptions { AutoStart = false });
+ const string TestId = "agent-id";
+ var agent = new GitHubCopilotAgent(copilotClient, ownsClient: false, id: TestId, tools: null);
+ AgentResponseUpdate result = agent.ConvertToAgentResponseUpdate(assistantMessage);
+
+ // result.Text need to be empty because the content was already delivered via delta events, and we want to avoid emitting duplicate content in the response update.
+ // The content should be delivered through TextContent in the Contents collection instead.
+ Assert.Empty(result.Text);
+ Assert.DoesNotContain(result.Contents, c => c is TextContent);
+ }
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/FunctionToolAgentProvider.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/FunctionToolAgentProvider.cs
index 8198618b65..98243dc4d3 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/FunctionToolAgentProvider.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/FunctionToolAgentProvider.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
-using Azure.Identity;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using OpenAI.Responses;
@@ -25,7 +24,7 @@ internal sealed class FunctionToolAgentProvider(IConfiguration configuration) :
AIFunctionFactory.Create(menuPlugin.GetItemPrice),
];
- AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential());
+ AIProjectClient aiProjectClient = new(foundryEndpoint, TestAzureCliCredentials.CreateAzureCliCredential());
yield return
await aiProjectClient.CreateAgentAsync(
diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/MarketingAgentProvider.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/MarketingAgentProvider.cs
index f84a40ae23..693d99b638 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/MarketingAgentProvider.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/MarketingAgentProvider.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
-using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Shared.Foundry;
using Shared.IntegrationTests;
@@ -15,7 +14,7 @@ internal sealed class MarketingAgentProvider(IConfiguration configuration) : Age
{
protected override async IAsyncEnumerable CreateAgentsAsync(Uri foundryEndpoint)
{
- AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential());
+ AIProjectClient aiProjectClient = new(foundryEndpoint, TestAzureCliCredentials.CreateAzureCliCredential());
yield return
await aiProjectClient.CreateAgentAsync(
diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/MathChatAgentProvider.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/MathChatAgentProvider.cs
index 92cea7d76a..91d63404bd 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/MathChatAgentProvider.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/MathChatAgentProvider.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
-using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Shared.Foundry;
using Shared.IntegrationTests;
@@ -15,7 +14,7 @@ internal sealed class MathChatAgentProvider(IConfiguration configuration) : Agen
{
protected override async IAsyncEnumerable CreateAgentsAsync(Uri foundryEndpoint)
{
- AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential());
+ AIProjectClient aiProjectClient = new(foundryEndpoint, TestAzureCliCredentials.CreateAzureCliCredential());
yield return
await aiProjectClient.CreateAgentAsync(
diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/PoemAgentProvider.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/PoemAgentProvider.cs
index 8882709a03..1b79e4e25e 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/PoemAgentProvider.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/PoemAgentProvider.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
-using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Shared.Foundry;
using Shared.IntegrationTests;
@@ -15,7 +14,7 @@ internal sealed class PoemAgentProvider(IConfiguration configuration) : AgentPro
{
protected override async IAsyncEnumerable CreateAgentsAsync(Uri foundryEndpoint)
{
- AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential());
+ AIProjectClient aiProjectClient = new(foundryEndpoint, TestAzureCliCredentials.CreateAzureCliCredential());
yield return
await aiProjectClient.CreateAgentAsync(
diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/TestAgentProvider.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/TestAgentProvider.cs
index 03b201d440..dcb09a4798 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/TestAgentProvider.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/TestAgentProvider.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
-using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Shared.Foundry;
using Shared.IntegrationTests;
@@ -15,7 +14,7 @@ internal sealed class TestAgentProvider(IConfiguration configuration) : AgentPro
{
protected override async IAsyncEnumerable CreateAgentsAsync(Uri foundryEndpoint)
{
- AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential());
+ AIProjectClient aiProjectClient = new(foundryEndpoint, TestAzureCliCredentials.CreateAzureCliCredential());
yield return
await aiProjectClient.CreateAgentAsync(
diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/VisionAgentProvider.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/VisionAgentProvider.cs
index 1c09ea9247..0d95342264 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/VisionAgentProvider.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Agents/VisionAgentProvider.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using Azure.AI.Projects;
using Azure.AI.Projects.OpenAI;
-using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Shared.Foundry;
using Shared.IntegrationTests;
@@ -15,7 +14,7 @@ internal sealed class VisionAgentProvider(IConfiguration configuration) : AgentP
{
protected override async IAsyncEnumerable CreateAgentsAsync(Uri foundryEndpoint)
{
- AIProjectClient aiProjectClient = new(foundryEndpoint, new AzureCliCredential());
+ AIProjectClient aiProjectClient = new(foundryEndpoint, TestAzureCliCredentials.CreateAzureCliCredential());
yield return
await aiProjectClient.CreateAgentAsync(
diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/AzureAgentProviderTest.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/AzureAgentProviderTest.cs
index 562ce8abda..4749289f5a 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/AzureAgentProviderTest.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/AzureAgentProviderTest.cs
@@ -2,9 +2,9 @@
using System.Linq;
using System.Threading.Tasks;
-using Azure.Identity;
using Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.Framework;
using Microsoft.Extensions.AI;
+using Shared.IntegrationTests;
namespace Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests;
@@ -14,7 +14,7 @@ public sealed class AzureAgentProviderTest(ITestOutputHelper output) : Integrati
public async Task ConversationTestAsync()
{
// Arrange
- AzureAgentProvider provider = new(this.TestEndpoint, new AzureCliCredential());
+ AzureAgentProvider provider = new(this.TestEndpoint, TestAzureCliCredentials.CreateAzureCliCredential());
// Act
string conversationId = await provider.CreateConversationAsync();
// Assert
diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Framework/IntegrationTest.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Framework/IntegrationTest.cs
index 59f2dd51ed..6be840ce48 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Framework/IntegrationTest.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Framework/IntegrationTest.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
-using Azure.Identity;
using Microsoft.Agents.AI.Workflows.Declarative.PowerFx;
using Microsoft.Agents.ObjectModel;
using Microsoft.Extensions.AI;
@@ -67,7 +66,7 @@ public abstract class IntegrationTest : IDisposable
protected async ValueTask CreateOptionsAsync(bool externalConversation, IMcpToolHandler? mcpToolProvider, params IEnumerable functionTools)
{
AzureAgentProvider agentProvider =
- new(this.TestEndpoint, new AzureCliCredential())
+ new(this.TestEndpoint, TestAzureCliCredentials.CreateAzureCliCredential())
{
Functions = functionTools,
};
diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/MediaInputTest.cs b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/MediaInputTest.cs
index 60c4915579..7c3aef758c 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/MediaInputTest.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/MediaInputTest.cs
@@ -4,11 +4,11 @@ using System;
using System.IO;
using System.Threading.Tasks;
using Azure.AI.Projects;
-using Azure.Identity;
using Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.Agents;
using Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.Framework;
using Microsoft.Extensions.AI;
using OpenAI.Files;
+using Shared.IntegrationTests;
namespace Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests;
@@ -76,7 +76,7 @@ public sealed class MediaInputTest(ITestOutputHelper output) : IntegrationTest(o
{
// Arrange
byte[] fileData = ReadLocalFile(fileSource);
- AIProjectClient client = new(this.TestEndpoint, new AzureCliCredential());
+ AIProjectClient client = new(this.TestEndpoint, TestAzureCliCredentials.CreateAzureCliCredential());
using MemoryStream contentStream = new(fileData);
OpenAIFileClient fileClient = client.GetProjectOpenAIClient().GetOpenAIFileClient();
OpenAIFile fileInfo = await fileClient.UploadFileAsync(contentStream, documentName, FileUploadPurpose.Assistants);
diff --git a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.csproj
index 92e09fcebb..d37dd58c8c 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.csproj
@@ -5,6 +5,7 @@
true
true
true
+ True
diff --git a/python/packages/core/agent_framework/_agents.py b/python/packages/core/agent_framework/_agents.py
index cd2dc7bfc7..a0c998757c 100644
--- a/python/packages/core/agent_framework/_agents.py
+++ b/python/packages/core/agent_framework/_agents.py
@@ -454,6 +454,7 @@ class BaseAgent(SerializationMixin):
stream_callback: Callable[[AgentResponseUpdate], None]
| Callable[[AgentResponseUpdate], Awaitable[None]]
| None = None,
+ propagate_session: bool = False,
) -> FunctionTool:
"""Create a FunctionTool that wraps this agent.
@@ -464,6 +465,12 @@ class BaseAgent(SerializationMixin):
arg_description: The description for the function argument.
If None, defaults to "Task for {tool_name}".
stream_callback: Optional callback for streaming responses. If provided, uses run(..., stream=True).
+ propagate_session: If True, the parent agent's ``AgentSession`` is
+ forwarded to this sub-agent's ``run()`` call, so both agents
+ operate within the same logical session (sharing the same
+ ``session_id`` and provider-managed state, such as any stored
+ conversation history or metadata). Defaults to False, meaning
+ the sub-agent runs with a new, independent session.
Returns:
A FunctionTool that can be used as a tool by other agents.
@@ -480,9 +487,12 @@ class BaseAgent(SerializationMixin):
# Create an agent
agent = Agent(client=client, name="research-agent", description="Performs research tasks")
- # Convert the agent to a tool
+ # Convert the agent to a tool (independent session)
research_tool = agent.as_tool()
+ # Convert the agent to a tool (shared session with parent)
+ research_tool = agent.as_tool(propagate_session=True)
+
# Use the tool with another agent
coordinator = Agent(client=client, name="coordinator", tools=research_tool)
"""
@@ -509,16 +519,21 @@ class BaseAgent(SerializationMixin):
# Extract the input from kwargs using the specified arg_name
input_text = kwargs.get(arg_name, "")
- # Forward runtime context kwargs, excluding arg_name and conversation_id.
- forwarded_kwargs = {k: v for k, v in kwargs.items() if k not in (arg_name, "conversation_id", "options")}
+ # Extract parent session when propagate_session is enabled
+ parent_session = kwargs.get("session") if propagate_session else None
+
+ # Forward runtime context kwargs, excluding framework-internal keys.
+ forwarded_kwargs = {
+ k: v for k, v in kwargs.items() if k not in (arg_name, "conversation_id", "options", "session")
+ }
if stream_callback is None:
# Use non-streaming mode
- return (await self.run(input_text, stream=False, **forwarded_kwargs)).text
+ return (await self.run(input_text, stream=False, session=parent_session, **forwarded_kwargs)).text
# Use streaming mode - accumulate updates and create final response
response_updates: list[AgentResponseUpdate] = []
- async for update in self.run(input_text, stream=True, **forwarded_kwargs):
+ async for update in self.run(input_text, stream=True, session=parent_session, **forwarded_kwargs):
response_updates.append(update)
if is_async_callback:
await stream_callback(update) # type: ignore[misc]
@@ -1061,6 +1076,9 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]): # type: ignore[misc]
# in function middleware context and tool invocation.
existing_additional_args = opts.pop("additional_function_arguments", None) or {}
additional_function_arguments = {**kwargs, **existing_additional_args}
+ # Include session so as_tool() wrappers with propagate_session=True can access it.
+ if active_session is not None:
+ additional_function_arguments["session"] = active_session
# Build options dict from run() options merged with provided options
run_opts: dict[str, Any] = {
diff --git a/python/packages/core/tests/core/test_agents.py b/python/packages/core/tests/core/test_agents.py
index c8d2d9bf8b..d41b87b707 100644
--- a/python/packages/core/tests/core/test_agents.py
+++ b/python/packages/core/tests/core/test_agents.py
@@ -707,6 +707,81 @@ async def test_chat_agent_as_tool_name_sanitization(client: SupportsChatGetRespo
assert tool.name == expected_tool_name, f"Expected {expected_tool_name}, got {tool.name} for input {agent_name}"
+async def test_chat_agent_as_tool_propagate_session_true(client: SupportsChatGetResponse) -> None:
+ """Test that propagate_session=True forwards the parent's session to the sub-agent."""
+ agent = Agent(client=client, name="SubAgent", description="Sub agent")
+ tool = agent.as_tool(propagate_session=True)
+
+ parent_session = AgentSession(session_id="parent-session-123")
+ parent_session.state["shared_key"] = "shared_value"
+
+ # Spy on the agent's run method to capture the session argument
+ original_run = agent.run
+ captured_session = None
+
+ def capturing_run(*args: Any, **kwargs: Any) -> Any:
+ nonlocal captured_session
+ captured_session = kwargs.get("session")
+ return original_run(*args, **kwargs)
+
+ agent.run = capturing_run # type: ignore[assignment, method-assign]
+
+ await tool.invoke(arguments=tool.input_model(task="Hello"), session=parent_session)
+
+ assert captured_session is parent_session
+ assert captured_session.session_id == "parent-session-123"
+ assert captured_session.state["shared_key"] == "shared_value"
+
+
+async def test_chat_agent_as_tool_propagate_session_false_by_default(client: SupportsChatGetResponse) -> None:
+ """Test that propagate_session defaults to False and does not forward the session."""
+ agent = Agent(client=client, name="SubAgent", description="Sub agent")
+ tool = agent.as_tool() # default: propagate_session=False
+
+ parent_session = AgentSession(session_id="parent-session-456")
+
+ original_run = agent.run
+ captured_session = None
+
+ def capturing_run(*args: Any, **kwargs: Any) -> Any:
+ nonlocal captured_session
+ captured_session = kwargs.get("session")
+ return original_run(*args, **kwargs)
+
+ agent.run = capturing_run # type: ignore[assignment, method-assign]
+
+ await tool.invoke(arguments=tool.input_model(task="Hello"), session=parent_session)
+
+ assert captured_session is None
+
+
+async def test_chat_agent_as_tool_propagate_session_shares_state(client: SupportsChatGetResponse) -> None:
+ """Test that shared session allows the sub-agent to read and write parent's state."""
+ agent = Agent(client=client, name="SubAgent", description="Sub agent")
+ tool = agent.as_tool(propagate_session=True)
+
+ parent_session = AgentSession(session_id="shared-session")
+ parent_session.state["counter"] = 0
+
+ # The sub-agent receives the same session object, so mutations are shared
+ original_run = agent.run
+ captured_session = None
+
+ def capturing_run(*args: Any, **kwargs: Any) -> Any:
+ nonlocal captured_session
+ captured_session = kwargs.get("session")
+ if captured_session:
+ captured_session.state["counter"] += 1
+ return original_run(*args, **kwargs)
+
+ agent.run = capturing_run # type: ignore[assignment, method-assign]
+
+ await tool.invoke(arguments=tool.input_model(task="Hello"), session=parent_session)
+
+ # The parent's state should reflect the sub-agent's mutation
+ assert parent_session.state["counter"] == 1
+
+
async def test_chat_agent_as_mcp_server_basic(client: SupportsChatGetResponse) -> None:
"""Test basic as_mcp_server functionality."""
agent = Agent(client=client, name="TestAgent", description="Test agent for MCP")
diff --git a/python/samples/02-agents/tools/agent_as_tool_with_session_propagation.py b/python/samples/02-agents/tools/agent_as_tool_with_session_propagation.py
new file mode 100644
index 0000000000..33748437e0
--- /dev/null
+++ b/python/samples/02-agents/tools/agent_as_tool_with_session_propagation.py
@@ -0,0 +1,93 @@
+# Copyright (c) Microsoft. All rights reserved.
+
+import asyncio
+from collections.abc import Awaitable, Callable
+
+from agent_framework import AgentContext, AgentSession
+from agent_framework.openai import OpenAIResponsesClient
+from dotenv import load_dotenv
+
+load_dotenv()
+
+"""
+Agent-as-Tool: Session Propagation Example
+
+Demonstrates how to share an AgentSession between a coordinator agent and a
+sub-agent invoked as a tool using ``propagate_session=True``.
+
+When session propagation is enabled, both agents share the same session object,
+including session_id and the mutable state dict. This allows correlated
+conversation tracking and shared state across the agent hierarchy.
+
+The middleware functions below are purely for observability — they are NOT
+required for session propagation to work.
+"""
+
+
+async def log_session(
+ context: AgentContext,
+ call_next: Callable[[], Awaitable[None]],
+) -> None:
+ """Agent middleware that logs the session received by each agent.
+
+ NOT required for session propagation — only used to observe the flow.
+ If propagation is working, both agents will show the same session_id.
+ """
+ session: AgentSession | None = context.session
+ agent_name = context.agent.name or "unknown"
+ session_id = session.session_id if session else None
+ state = dict(session.state) if session else {}
+ print(f" [{agent_name}] session_id={session_id}, state={state}")
+ await call_next()
+
+
+async def main() -> None:
+ print("=== Agent-as-Tool: Session Propagation ===\n")
+
+ client = OpenAIResponsesClient()
+
+ # --- Sub-agent: a research specialist ---
+ # The sub-agent has the same log_session middleware to prove it receives the session.
+ research_agent = client.as_agent(
+ name="ResearchAgent",
+ instructions="You are a research assistant. Provide concise answers.",
+ middleware=[log_session],
+ )
+
+ # propagate_session=True: the coordinator's session will be forwarded
+ research_tool = research_agent.as_tool(
+ name="research",
+ description="Research a topic and return findings",
+ arg_name="query",
+ arg_description="The research query",
+ propagate_session=True,
+ )
+
+ # --- Coordinator agent ---
+ coordinator = client.as_agent(
+ name="CoordinatorAgent",
+ instructions="You coordinate research. Use the 'research' tool to look up information.",
+ tools=[research_tool],
+ middleware=[log_session],
+ )
+
+ # Create a shared session and put some state in it
+ session = coordinator.create_session()
+ session.state["request_source"] = "demo"
+ print(f"Session ID: {session.session_id}")
+ print(f"Session state before run: {session.state}\n")
+
+ query = "What are the latest developments in quantum computing?"
+ print(f"User: {query}\n")
+
+ result = await coordinator.run(query, session=session)
+
+ print(f"\nCoordinator: {result}\n")
+ print(f"Session state after run: {session.state}")
+ print(
+ "\nIf both agents show the same session_id above, session propagation is working."
+ )
+
+
+if __name__ == "__main__":
+ asyncio.run(main())