diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj b/dotnet/src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj
index bfcf6e5263..3de68137ba 100644
--- a/dotnet/src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj
@@ -17,6 +17,10 @@
+
+
+
+
Microsoft Agent Framework OpenAI
diff --git a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/ChatClient/AsyncStreamingChatCompletionUpdateCollectionResultTests.cs b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/ChatClient/AsyncStreamingChatCompletionUpdateCollectionResultTests.cs
new file mode 100644
index 0000000000..899d329d5b
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/ChatClient/AsyncStreamingChatCompletionUpdateCollectionResultTests.cs
@@ -0,0 +1,109 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.ClientModel;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using OpenAI.Chat;
+
+namespace Microsoft.Agents.AI.OpenAI.UnitTests.ChatClient;
+
+///
+/// Unit tests for the class.
+///
+public sealed class AsyncStreamingChatCompletionUpdateCollectionResultTests
+{
+ ///
+ /// Verify that GetContinuationToken returns null.
+ ///
+ [Fact]
+ public void GetContinuationToken_ReturnsNull()
+ {
+ // Arrange
+ IAsyncEnumerable updates = CreateTestUpdatesAsync();
+ AsyncCollectionResult collectionResult = new AsyncStreamingChatCompletionUpdateCollectionResult(updates);
+
+ // Act
+ ContinuationToken? token = collectionResult.GetContinuationToken(null!);
+
+ // Assert
+ Assert.Null(token);
+ }
+
+ ///
+ /// Verify that GetRawPagesAsync returns a single page.
+ ///
+ [Fact]
+ public async Task GetRawPagesAsync_ReturnsSinglePageAsync()
+ {
+ // Arrange
+ IAsyncEnumerable updates = CreateTestUpdatesAsync();
+ AsyncCollectionResult collectionResult = new AsyncStreamingChatCompletionUpdateCollectionResult(updates);
+
+ // Act
+ List pages = [];
+ await foreach (ClientResult page in collectionResult.GetRawPagesAsync())
+ {
+ pages.Add(page);
+ }
+
+ // Assert
+ Assert.Single(pages);
+ }
+
+ ///
+ /// Verify that iterating through the collection yields streaming updates.
+ ///
+ [Fact]
+ public async Task IterateCollection_YieldsUpdatesAsync()
+ {
+ // Arrange
+ IAsyncEnumerable updates = CreateTestUpdatesAsync();
+ AsyncCollectionResult collectionResult = new AsyncStreamingChatCompletionUpdateCollectionResult(updates);
+
+ // Act
+ List results = [];
+ await foreach (StreamingChatCompletionUpdate update in collectionResult)
+ {
+ results.Add(update);
+ }
+
+ // Assert
+ Assert.Single(results);
+ }
+
+ ///
+ /// Verify that iterating through the collection with multiple updates yields all updates.
+ ///
+ [Fact]
+ public async Task IterateCollection_WithMultipleUpdates_YieldsAllUpdatesAsync()
+ {
+ // Arrange
+ IAsyncEnumerable updates = CreateMultipleTestUpdatesAsync();
+ AsyncCollectionResult collectionResult = new AsyncStreamingChatCompletionUpdateCollectionResult(updates);
+
+ // Act
+ List results = [];
+ await foreach (StreamingChatCompletionUpdate update in collectionResult)
+ {
+ results.Add(update);
+ }
+
+ // Assert
+ Assert.Equal(3, results.Count);
+ }
+
+ private static async IAsyncEnumerable CreateTestUpdatesAsync()
+ {
+ yield return new AgentResponseUpdate(ChatRole.Assistant, "test");
+ await Task.CompletedTask;
+ }
+
+ private static async IAsyncEnumerable CreateMultipleTestUpdatesAsync()
+ {
+ yield return new AgentResponseUpdate(ChatRole.Assistant, "first");
+ yield return new AgentResponseUpdate(ChatRole.Assistant, "second");
+ yield return new AgentResponseUpdate(ChatRole.Assistant, "third");
+ await Task.CompletedTask;
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/ChatClient/AsyncStreamingResponseUpdateCollectionResultTests.cs b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/ChatClient/AsyncStreamingResponseUpdateCollectionResultTests.cs
new file mode 100644
index 0000000000..d6bb87596c
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/ChatClient/AsyncStreamingResponseUpdateCollectionResultTests.cs
@@ -0,0 +1,190 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.ClientModel;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using OpenAI.Responses;
+
+namespace Microsoft.Agents.AI.OpenAI.UnitTests.ChatClient;
+
+///
+/// Unit tests for the class.
+///
+public sealed class AsyncStreamingResponseUpdateCollectionResultTests
+{
+ ///
+ /// Verify that GetContinuationToken returns null.
+ ///
+ [Fact]
+ public void GetContinuationToken_ReturnsNull()
+ {
+ // Arrange
+ IAsyncEnumerable updates = CreateTestUpdatesAsync();
+ AsyncCollectionResult collectionResult = new AsyncStreamingResponseUpdateCollectionResult(updates);
+
+ // Act
+ ContinuationToken? token = collectionResult.GetContinuationToken(null!);
+
+ // Assert
+ Assert.Null(token);
+ }
+
+ ///
+ /// Verify that GetRawPagesAsync returns a single page.
+ ///
+ [Fact]
+ public async Task GetRawPagesAsync_ReturnsSinglePageAsync()
+ {
+ // Arrange
+ IAsyncEnumerable updates = CreateTestUpdatesAsync();
+ AsyncCollectionResult collectionResult = new AsyncStreamingResponseUpdateCollectionResult(updates);
+
+ // Act
+ List pages = [];
+ await foreach (ClientResult page in collectionResult.GetRawPagesAsync())
+ {
+ pages.Add(page);
+ }
+
+ // Assert
+ Assert.Single(pages);
+ }
+
+ ///
+ /// Verify that iterating through the collection yields streaming updates when RawRepresentation is a StreamingResponseUpdate.
+ ///
+ [Fact]
+ public async Task IterateCollection_WithStreamingResponseUpdateRawRepresentation_YieldsUpdatesAsync()
+ {
+ // Arrange
+ StreamingResponseUpdate rawUpdate = CreateStreamingResponseUpdate();
+ IAsyncEnumerable updates = CreateTestUpdatesWithRawRepresentationAsync(rawUpdate);
+ AsyncCollectionResult collectionResult = new AsyncStreamingResponseUpdateCollectionResult(updates);
+
+ // Act
+ List results = [];
+ await foreach (StreamingResponseUpdate update in collectionResult)
+ {
+ results.Add(update);
+ }
+
+ // Assert
+ Assert.Single(results);
+ Assert.Same(rawUpdate, results[0]);
+ }
+
+ ///
+ /// Verify that iterating through the collection yields updates when RawRepresentation is a ChatResponseUpdate containing a StreamingResponseUpdate.
+ ///
+ [Fact]
+ public async Task IterateCollection_WithChatResponseUpdateContainingStreamingResponseUpdate_YieldsUpdatesAsync()
+ {
+ // Arrange
+ StreamingResponseUpdate rawUpdate = CreateStreamingResponseUpdate();
+ ChatResponseUpdate chatResponseUpdate = new() { RawRepresentation = rawUpdate };
+ IAsyncEnumerable updates = CreateTestUpdatesWithChatResponseUpdateAsync(chatResponseUpdate);
+ AsyncCollectionResult collectionResult = new AsyncStreamingResponseUpdateCollectionResult(updates);
+
+ // Act
+ List results = [];
+ await foreach (StreamingResponseUpdate update in collectionResult)
+ {
+ results.Add(update);
+ }
+
+ // Assert
+ Assert.Single(results);
+ Assert.Same(rawUpdate, results[0]);
+ }
+
+ ///
+ /// Verify that iterating through the collection skips updates when RawRepresentation is not a StreamingResponseUpdate.
+ ///
+ [Fact]
+ public async Task IterateCollection_WithNonStreamingResponseUpdateRawRepresentation_SkipsUpdateAsync()
+ {
+ // Arrange
+ IAsyncEnumerable updates = CreateTestUpdatesAsync();
+ AsyncCollectionResult collectionResult = new AsyncStreamingResponseUpdateCollectionResult(updates);
+
+ // Act
+ List results = [];
+ await foreach (StreamingResponseUpdate update in collectionResult)
+ {
+ results.Add(update);
+ }
+
+ // Assert
+ Assert.Empty(results);
+ }
+
+ ///
+ /// Verify that iterating through the collection skips updates when RawRepresentation is a ChatResponseUpdate without StreamingResponseUpdate.
+ ///
+ [Fact]
+ public async Task IterateCollection_WithChatResponseUpdateWithoutStreamingResponseUpdate_SkipsUpdateAsync()
+ {
+ // Arrange
+ ChatResponseUpdate chatResponseUpdate = new() { RawRepresentation = "not a streaming update" };
+ IAsyncEnumerable updates = CreateTestUpdatesWithChatResponseUpdateAsync(chatResponseUpdate);
+ AsyncCollectionResult collectionResult = new AsyncStreamingResponseUpdateCollectionResult(updates);
+
+ // Act
+ List results = [];
+ await foreach (StreamingResponseUpdate update in collectionResult)
+ {
+ results.Add(update);
+ }
+
+ // Assert
+ Assert.Empty(results);
+ }
+
+ private static async IAsyncEnumerable CreateTestUpdatesAsync()
+ {
+ yield return new AgentResponseUpdate(ChatRole.Assistant, "test");
+ await Task.CompletedTask;
+ }
+
+ private static async IAsyncEnumerable CreateTestUpdatesWithRawRepresentationAsync(object rawRepresentation)
+ {
+ AgentResponseUpdate update = new(ChatRole.Assistant, "test")
+ {
+ RawRepresentation = rawRepresentation
+ };
+ yield return update;
+ await Task.CompletedTask;
+ }
+
+ private static async IAsyncEnumerable CreateTestUpdatesWithChatResponseUpdateAsync(ChatResponseUpdate chatResponseUpdate)
+ {
+ AgentResponseUpdate update = new(ChatRole.Assistant, "test")
+ {
+ RawRepresentation = chatResponseUpdate
+ };
+ yield return update;
+ await Task.CompletedTask;
+ }
+
+ private static StreamingResponseUpdate CreateStreamingResponseUpdate()
+ {
+ const string Json = """
+ {
+ "type": "response.output_item.added",
+ "sequence_number": 1,
+ "output_index": 0,
+ "item": {
+ "id": "item_abc123",
+ "type": "message",
+ "status": "in_progress",
+ "role": "assistant",
+ "content": []
+ }
+ }
+ """;
+
+ return System.ClientModel.Primitives.ModelReaderWriter.Read(BinaryData.FromString(Json))!;
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/ChatClient/StreamingUpdatePipelineResponseTests.cs b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/ChatClient/StreamingUpdatePipelineResponseTests.cs
new file mode 100644
index 0000000000..866bba5700
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/ChatClient/StreamingUpdatePipelineResponseTests.cs
@@ -0,0 +1,154 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.ClientModel.Primitives;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Microsoft.Agents.AI.OpenAI.UnitTests.ChatClient;
+
+///
+/// Unit tests for the class.
+///
+public sealed class StreamingUpdatePipelineResponseTests
+{
+ ///
+ /// Verify that Status property returns 200.
+ ///
+ [Fact]
+ public void Status_ReturnsOkStatus()
+ {
+ // Arrange
+ IAsyncEnumerable updates = CreateTestUpdatesAsync();
+ PipelineResponse response = new StreamingUpdatePipelineResponse(updates);
+
+ // Act
+ int status = response.Status;
+
+ // Assert
+ Assert.Equal(200, status);
+ }
+
+ ///
+ /// Verify that ReasonPhrase property returns "OK".
+ ///
+ [Fact]
+ public void ReasonPhrase_ReturnsOk()
+ {
+ // Arrange
+ IAsyncEnumerable updates = CreateTestUpdatesAsync();
+ PipelineResponse response = new StreamingUpdatePipelineResponse(updates);
+
+ // Act
+ string reasonPhrase = response.ReasonPhrase;
+
+ // Assert
+ Assert.Equal("OK", reasonPhrase);
+ }
+
+ ///
+ /// Verify that ContentStream getter returns null.
+ ///
+ [Fact]
+ public void ContentStream_Get_ReturnsNull()
+ {
+ // Arrange
+ IAsyncEnumerable updates = CreateTestUpdatesAsync();
+ PipelineResponse response = new StreamingUpdatePipelineResponse(updates);
+
+ // Act
+ System.IO.Stream? contentStream = response.ContentStream;
+
+ // Assert
+ Assert.Null(contentStream);
+ }
+
+ ///
+ /// Verify that ContentStream setter is a no-op.
+ ///
+ [Fact]
+ public void ContentStream_Set_IsNoOp()
+ {
+ // Arrange
+ IAsyncEnumerable updates = CreateTestUpdatesAsync();
+ PipelineResponse response = new StreamingUpdatePipelineResponse(updates);
+ var testStream = new System.IO.MemoryStream();
+
+ // Act
+ response.ContentStream = testStream;
+
+ // Assert
+ Assert.Null(response.ContentStream);
+
+ testStream.Dispose();
+ }
+
+ ///
+ /// Verify that Content property returns empty BinaryData.
+ ///
+ [Fact]
+ public void Content_ReturnsEmptyBinaryData()
+ {
+ // Arrange
+ IAsyncEnumerable updates = CreateTestUpdatesAsync();
+ PipelineResponse response = new StreamingUpdatePipelineResponse(updates);
+
+ // Act
+ BinaryData content = response.Content;
+
+ // Assert
+ Assert.NotNull(content);
+ Assert.Equal(string.Empty, content.ToString());
+ }
+
+ ///
+ /// Verify that BufferContent throws NotSupportedException.
+ ///
+ [Fact]
+ public void BufferContent_ThrowsNotSupportedException()
+ {
+ // Arrange
+ IAsyncEnumerable updates = CreateTestUpdatesAsync();
+ PipelineResponse response = new StreamingUpdatePipelineResponse(updates);
+
+ // Act & Assert
+ var exception = Assert.Throws(() => response.BufferContent());
+ Assert.Contains("Buffering content is not supported", exception.Message);
+ }
+
+ ///
+ /// Verify that BufferContentAsync throws NotSupportedException.
+ ///
+ [Fact]
+ public async Task BufferContentAsync_ThrowsNotSupportedExceptionAsync()
+ {
+ // Arrange
+ IAsyncEnumerable updates = CreateTestUpdatesAsync();
+ PipelineResponse response = new StreamingUpdatePipelineResponse(updates);
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(
+ async () => await response.BufferContentAsync());
+ Assert.Contains("Buffering content asynchronously is not supported", exception.Message);
+ }
+
+ ///
+ /// Verify that Dispose does not throw.
+ ///
+ [Fact]
+ public void Dispose_DoesNotThrow()
+ {
+ // Arrange
+ IAsyncEnumerable updates = CreateTestUpdatesAsync();
+ PipelineResponse response = new StreamingUpdatePipelineResponse(updates);
+
+ // Act & Assert
+ response.Dispose();
+ }
+
+ private static async IAsyncEnumerable CreateTestUpdatesAsync()
+ {
+ yield return new AgentResponseUpdate(Microsoft.Extensions.AI.ChatRole.Assistant, "test");
+ await Task.CompletedTask;
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/AIAgentWithOpenAIExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/AIAgentWithOpenAIExtensionsTests.cs
index d29535eddb..db22ab090b 100644
--- a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/AIAgentWithOpenAIExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/AIAgentWithOpenAIExtensionsTests.cs
@@ -7,6 +7,7 @@
using System.Threading.Tasks;
using Moq;
using Moq.Protected;
+using OpenAI.Responses;
using ChatMessage = Microsoft.Extensions.AI.ChatMessage;
using ChatRole = Microsoft.Extensions.AI.ChatRole;
using OpenAIChatMessage = OpenAI.Chat.ChatMessage;
@@ -208,4 +209,167 @@ private static async IAsyncEnumerable ToAsyncEnumerableAsyn
yield return await Task.FromResult(update);
}
}
+
+ #region ResponseItem overload tests
+
+ ///
+ /// Verify that RunAsync with ResponseItem throws ArgumentNullException when agent is null.
+ ///
+ [Fact]
+ public async Task RunAsync_ResponseItem_WithNullAgent_ThrowsArgumentNullExceptionAsync()
+ {
+ // Arrange
+ AIAgent? agent = null;
+ IEnumerable messages = [ResponseItem.CreateUserMessageItem("Test message")];
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(
+ () => agent!.RunAsync(messages));
+
+ Assert.Equal("agent", exception.ParamName);
+ }
+
+ ///
+ /// Verify that RunAsync with ResponseItem throws ArgumentNullException when messages is null.
+ ///
+ [Fact]
+ public async Task RunAsync_ResponseItem_WithNullMessages_ThrowsArgumentNullExceptionAsync()
+ {
+ // Arrange
+ var mockAgent = new Mock();
+ IEnumerable? messages = null;
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(
+ () => mockAgent.Object.RunAsync(messages!));
+
+ Assert.Equal("messages", exception.ParamName);
+ }
+
+ ///
+ /// Verify that the RunAsync with ResponseItem extension method calls the underlying agent's RunAsync with converted messages and parameters.
+ ///
+ [Fact]
+ public async Task RunAsync_ResponseItem_CallsUnderlyingAgentAsync()
+ {
+ // Arrange
+ var mockAgent = new Mock();
+ var mockThread = new Mock();
+ var options = new AgentRunOptions();
+ var cancellationToken = new CancellationToken(false);
+ const string TestMessageText = "Hello, assistant!";
+ const string ResponseText = "This is the assistant's response.";
+ IEnumerable responseItemMessages = [ResponseItem.CreateUserMessageItem(TestMessageText)];
+
+ var responseMessage = new ChatMessage(ChatRole.Assistant, [new TextContent(ResponseText)]);
+
+ mockAgent
+ .Protected()
+ .Setup>("RunCoreAsync",
+ ItExpr.IsAny>(),
+ ItExpr.IsAny(),
+ ItExpr.IsAny(),
+ ItExpr.IsAny())
+ .ReturnsAsync(new AgentResponse([responseMessage]));
+
+ // Act
+ ResponseResult result = await mockAgent.Object.RunAsync(responseItemMessages, mockThread.Object, options, cancellationToken);
+
+ // Assert
+ mockAgent.Protected()
+ .Verify("RunCoreAsync",
+ Times.Once(),
+ ItExpr.IsAny>(),
+ mockThread.Object,
+ options,
+ cancellationToken
+ );
+
+ Assert.NotNull(result);
+ }
+
+ ///
+ /// Verify that RunStreamingAsync with ResponseItem throws ArgumentNullException when agent is null.
+ ///
+ [Fact]
+ public void RunStreamingAsync_ResponseItem_WithNullAgent_ThrowsArgumentNullException()
+ {
+ // Arrange
+ AIAgent? agent = null;
+ IEnumerable messages = [ResponseItem.CreateUserMessageItem("Test message")];
+
+ // Act & Assert
+ Assert.Throws(
+ "agent",
+ () => agent!.RunStreamingAsync(messages));
+ }
+
+ ///
+ /// Verify that RunStreamingAsync with ResponseItem throws ArgumentNullException when messages is null.
+ ///
+ [Fact]
+ public void RunStreamingAsync_ResponseItem_WithNullMessages_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var mockAgent = new Mock();
+ IEnumerable? messages = null;
+
+ // Act & Assert
+ var exception = Assert.Throws(
+ () => mockAgent.Object.RunStreamingAsync(messages!));
+
+ Assert.Equal("messages", exception.ParamName);
+ }
+
+ ///
+ /// Verify that the RunStreamingAsync with ResponseItem extension method calls the underlying agent's RunStreamingAsync with converted messages and parameters.
+ ///
+ [Fact]
+ public async Task RunStreamingAsync_ResponseItem_CallsUnderlyingAgentAsync()
+ {
+ // Arrange
+ var mockAgent = new Mock();
+ var mockThread = new Mock();
+ var options = new AgentRunOptions();
+ var cancellationToken = new CancellationToken(false);
+ const string TestMessageText = "Hello, assistant!";
+ const string ResponseText1 = "This is ";
+ const string ResponseText2 = "the assistant's response.";
+ IEnumerable responseItemMessages = [ResponseItem.CreateUserMessageItem(TestMessageText)];
+
+ var responseUpdates = new List
+ {
+ new(ChatRole.Assistant, ResponseText1),
+ new(ChatRole.Assistant, ResponseText2)
+ };
+
+ mockAgent
+ .Protected()
+ .Setup>("RunCoreStreamingAsync",
+ ItExpr.IsAny>(),
+ ItExpr.IsAny(),
+ ItExpr.IsAny(),
+ ItExpr.IsAny())
+ .Returns(ToAsyncEnumerableAsync(responseUpdates));
+
+ // Act
+ var result = mockAgent.Object.RunStreamingAsync(responseItemMessages, mockThread.Object, options, cancellationToken);
+ var updateCount = 0;
+ await foreach (var update in result)
+ {
+ updateCount++;
+ }
+
+ // Assert
+ mockAgent.Protected()
+ .Verify("RunCoreStreamingAsync",
+ Times.Once(),
+ ItExpr.IsAny>(),
+ mockThread.Object,
+ options,
+ cancellationToken
+ );
+ }
+
+ #endregion
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/AgentResponseExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/AgentResponseExtensionsTests.cs
new file mode 100644
index 0000000000..b2b0a99002
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/AgentResponseExtensionsTests.cs
@@ -0,0 +1,142 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using OpenAI.Chat;
+using ChatMessage = Microsoft.Extensions.AI.ChatMessage;
+using ChatRole = Microsoft.Extensions.AI.ChatRole;
+using TextContent = Microsoft.Extensions.AI.TextContent;
+
+namespace Microsoft.Agents.AI.OpenAI.UnitTests.Extensions;
+
+///
+/// Unit tests for the AgentResponseExtensions class that provides OpenAI extension methods.
+///
+public sealed class AgentResponseExtensionsTests
+{
+ ///
+ /// Verify that AsOpenAIChatCompletion throws ArgumentNullException when response is null.
+ ///
+ [Fact]
+ public void AsOpenAIChatCompletion_WithNullResponse_ThrowsArgumentNullException()
+ {
+ // Arrange
+ AgentResponse? response = null;
+
+ // Act & Assert
+ var exception = Assert.Throws(
+ () => response!.AsOpenAIChatCompletion());
+
+ Assert.Equal("response", exception.ParamName);
+ }
+
+ ///
+ /// Verify that AsOpenAIChatCompletion returns the RawRepresentation when it is a ChatCompletion.
+ ///
+ [Fact]
+ public void AsOpenAIChatCompletion_WithChatCompletionRawRepresentation_ReturnsChatCompletion()
+ {
+ // Arrange
+ ChatCompletion chatCompletion = ModelReaderWriterHelper.CreateChatCompletion("assistant_id", "Hello");
+ var responseMessage = new ChatMessage(ChatRole.Assistant, [new TextContent("Hello")]);
+ var agentResponse = new AgentResponse([responseMessage])
+ {
+ RawRepresentation = chatCompletion
+ };
+
+ // Act
+ ChatCompletion result = agentResponse.AsOpenAIChatCompletion();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Same(chatCompletion, result);
+ }
+
+ ///
+ /// Verify that AsOpenAIChatCompletion converts a ChatResponse when RawRepresentation is not a ChatCompletion.
+ ///
+ [Fact]
+ public void AsOpenAIChatCompletion_WithNonChatCompletionRawRepresentation_ConvertsChatResponse()
+ {
+ // Arrange
+ const string ResponseText = "This is a test response.";
+ var responseMessage = new ChatMessage(ChatRole.Assistant, [new TextContent(ResponseText)]);
+ var agentResponse = new AgentResponse([responseMessage]);
+
+ // Act
+ ChatCompletion result = agentResponse.AsOpenAIChatCompletion();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Single(result.Content);
+ Assert.Equal(ResponseText, result.Content[0].Text);
+ }
+
+ ///
+ /// Verify that AsOpenAIResponse throws ArgumentNullException when response is null.
+ ///
+ [Fact]
+ public void AsOpenAIResponse_WithNullResponse_ThrowsArgumentNullException()
+ {
+ // Arrange
+ AgentResponse? response = null;
+
+ // Act & Assert
+ var exception = Assert.Throws(
+ () => response!.AsOpenAIResponse());
+
+ Assert.Equal("response", exception.ParamName);
+ }
+
+ ///
+ /// Verify that AsOpenAIResponse converts a ChatResponse when RawRepresentation is not a ResponseResult.
+ ///
+ [Fact]
+ public void AsOpenAIResponse_WithNonResponseResultRawRepresentation_ConvertsChatResponse()
+ {
+ // Arrange
+ const string ResponseText = "This is a test response.";
+ var responseMessage = new ChatMessage(ChatRole.Assistant, [new TextContent(ResponseText)]);
+ var agentResponse = new AgentResponse([responseMessage]);
+
+ // Act
+ var result = agentResponse.AsOpenAIResponse();
+
+ // Assert
+ Assert.NotNull(result);
+ }
+}
+
+///
+/// Helper class for creating OpenAI model objects using ModelReaderWriter.
+///
+internal static class ModelReaderWriterHelper
+{
+ public static ChatCompletion CreateChatCompletion(string id, string contentText)
+ {
+ string json = $$"""
+ {
+ "id": "{{id}}",
+ "object": "chat.completion",
+ "created": 1700000000,
+ "model": "gpt-4",
+ "choices": [
+ {
+ "index": 0,
+ "message": {
+ "role": "assistant",
+ "content": "{{contentText}}"
+ },
+ "finish_reason": "stop"
+ }
+ ],
+ "usage": {
+ "prompt_tokens": 10,
+ "completion_tokens": 10,
+ "total_tokens": 20
+ }
+ }
+ """;
+
+ return System.ClientModel.Primitives.ModelReaderWriter.Read(BinaryData.FromString(json))!;
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs
index 2401790dd0..44a4b73b52 100644
--- a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs
@@ -569,6 +569,387 @@ public async Task CreateAIAgentAsync_WithClientFactoryAndServices_AppliesBothCor
return property?.GetValue(client) as IServiceProvider;
}
+ ///
+ /// Verify that CreateAIAgentAsync with HostedCodeInterpreterTool properly adds CodeInterpreter tool definition.
+ ///
+ [Fact]
+ public async Task CreateAIAgentAsync_WithHostedCodeInterpreterTool_CreatesAgentWithToolAsync()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+ const string ModelId = "test-model";
+ var options = new ChatClientAgentOptions
+ {
+ Name = "Test Agent",
+ ChatOptions = new ChatOptions
+ {
+ Instructions = "Test instructions",
+ Tools = [new HostedCodeInterpreterTool()]
+ }
+ };
+
+ // Act
+ var agent = await assistantClient.CreateAIAgentAsync(ModelId, options);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+ }
+
+ ///
+ /// Verify that CreateAIAgentAsync with HostedCodeInterpreterTool with HostedFileContent input properly creates agent.
+ ///
+ [Fact]
+ public async Task CreateAIAgentAsync_WithHostedCodeInterpreterToolAndHostedFileContent_CreatesAgentWithToolResourcesAsync()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+ const string ModelId = "test-model";
+ var codeInterpreterTool = new HostedCodeInterpreterTool
+ {
+ Inputs = [new HostedFileContent("test-file-id")]
+ };
+ var options = new ChatClientAgentOptions
+ {
+ Name = "Test Agent",
+ ChatOptions = new ChatOptions
+ {
+ Instructions = "Test instructions",
+ Tools = [codeInterpreterTool]
+ }
+ };
+
+ // Act
+ var agent = await assistantClient.CreateAIAgentAsync(ModelId, options);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+ }
+
+ ///
+ /// Verify that CreateAIAgentAsync with HostedFileSearchTool properly adds FileSearch tool definition.
+ ///
+ [Fact]
+ public async Task CreateAIAgentAsync_WithHostedFileSearchTool_CreatesAgentWithToolAsync()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+ const string ModelId = "test-model";
+ var options = new ChatClientAgentOptions
+ {
+ Name = "Test Agent",
+ ChatOptions = new ChatOptions
+ {
+ Instructions = "Test instructions",
+ Tools = [new HostedFileSearchTool()]
+ }
+ };
+
+ // Act
+ var agent = await assistantClient.CreateAIAgentAsync(ModelId, options);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+ }
+
+ ///
+ /// Verify that CreateAIAgentAsync with HostedFileSearchTool with HostedVectorStoreContent input properly creates agent.
+ ///
+ [Fact]
+ public async Task CreateAIAgentAsync_WithHostedFileSearchToolAndHostedVectorStoreContent_CreatesAgentWithToolResourcesAsync()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+ const string ModelId = "test-model";
+ var fileSearchTool = new HostedFileSearchTool
+ {
+ MaximumResultCount = 10,
+ Inputs = [new HostedVectorStoreContent("test-vector-store-id")]
+ };
+ var options = new ChatClientAgentOptions
+ {
+ Name = "Test Agent",
+ ChatOptions = new ChatOptions
+ {
+ Instructions = "Test instructions",
+ Tools = [fileSearchTool]
+ }
+ };
+
+ // Act
+ var agent = await assistantClient.CreateAIAgentAsync(ModelId, options);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+ }
+
+ ///
+ /// Verify that CreateAIAgentAsync with multiple tools including functions properly creates agent.
+ ///
+ [Fact]
+ public async Task CreateAIAgentAsync_WithMixedTools_CreatesAgentWithAllToolsAsync()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+ const string ModelId = "test-model";
+ var testFunction = AIFunctionFactory.Create(() => "test", "TestFunction", "A test function");
+ var options = new ChatClientAgentOptions
+ {
+ Name = "Test Agent",
+ ChatOptions = new ChatOptions
+ {
+ Instructions = "Test instructions",
+ Tools = [new HostedCodeInterpreterTool(), new HostedFileSearchTool(), testFunction]
+ }
+ };
+
+ // Act
+ var agent = await assistantClient.CreateAIAgentAsync(ModelId, options);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+ }
+
+ ///
+ /// Verify that CreateAIAgentAsync with function tools properly categorizes them as other tools.
+ ///
+ [Fact]
+ public async Task CreateAIAgentAsync_WithFunctionTools_CategorizesAsOtherToolsAsync()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+ const string ModelId = "test-model";
+ var testFunction = AIFunctionFactory.Create(() => "test", "TestFunction", "A test function");
+ var options = new ChatClientAgentOptions
+ {
+ Name = "Test Agent",
+ ChatOptions = new ChatOptions
+ {
+ Instructions = "Test instructions",
+ Tools = [testFunction]
+ }
+ };
+
+ // Act
+ var agent = await assistantClient.CreateAIAgentAsync(ModelId, options);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+ }
+
+ ///
+ /// Verify that AsAIAgent with legacy overload works correctly when assistant instructions are set.
+ ///
+ [Fact]
+ public void AsAIAgent_LegacyOverload_WithAssistantInstructions_SetsInstructions()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+ var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123", "name": "Test Agent", "instructions": "Original Instructions"}"""))!;
+
+ // Act
+ var agent = assistantClient.AsAIAgent(assistant);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+ Assert.Equal("Original Instructions", agent.Instructions);
+ }
+
+ ///
+ /// Verify that AsAIAgent with legacy overload works correctly when chatOptions with instructions is provided.
+ ///
+ [Fact]
+ public void AsAIAgent_LegacyOverload_WithChatOptionsInstructions_UsesChatOptionsInstructions()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+ var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123", "name": "Test Agent", "instructions": "Original Instructions"}"""))!;
+ var chatOptions = new ChatOptions { Instructions = "Override Instructions" };
+
+ // Act
+ var agent = assistantClient.AsAIAgent(assistant, chatOptions);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+ Assert.Equal("Override Instructions", agent.Instructions);
+ }
+
+ ///
+ /// Verify that AsAIAgent with legacy overload and ClientResult works correctly.
+ ///
+ [Fact]
+ public void AsAIAgent_LegacyOverload_WithClientResult_WorksCorrectly()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+ var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123", "name": "Test Agent", "instructions": "Original Instructions"}"""))!;
+ var clientResult = ClientResult.FromValue(assistant, new FakePipelineResponse());
+
+ // Act
+ var agent = assistantClient.AsAIAgent(clientResult);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+ }
+
+ ///
+ /// Verify that AsAIAgent with legacy overload throws ArgumentNullException when assistant client is null.
+ ///
+ [Fact]
+ public void AsAIAgent_LegacyOverload_WithNullAssistantClient_ThrowsArgumentNullException()
+ {
+ // Arrange
+ AssistantClient? assistantClient = null;
+ var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123"}"""))!;
+
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ assistantClient!.AsAIAgent(assistant));
+
+ Assert.Equal("assistantClient", exception.ParamName);
+ }
+
+ ///
+ /// Verify that AsAIAgent with legacy overload throws ArgumentNullException when assistantMetadata is null.
+ ///
+ [Fact]
+ public void AsAIAgent_LegacyOverload_WithNullAssistantMetadata_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ assistantClient.AsAIAgent((Assistant)null!));
+
+ Assert.Equal("assistantMetadata", exception.ParamName);
+ }
+
+ ///
+ /// Verify that AsAIAgent with legacy overload throws ArgumentNullException when clientResult is null.
+ ///
+ [Fact]
+ public void AsAIAgent_LegacyOverload_WithNullClientResult_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ assistantClient.AsAIAgent(null!, chatOptions: null));
+
+ Assert.Equal("assistantClientResult", exception.ParamName);
+ }
+
+ ///
+ /// Verify that GetAIAgentAsync with legacy overload works correctly.
+ ///
+ [Fact]
+ public async Task GetAIAgentAsync_LegacyOverload_WorksCorrectlyAsync()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+ const string AgentId = "asst_abc123";
+
+ // Act
+ var agent = await assistantClient.GetAIAgentAsync(AgentId);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Original Name", agent.Name);
+ }
+
+ ///
+ /// Verify that GetAIAgentAsync with legacy overload throws ArgumentNullException when assistantClient is null.
+ ///
+ [Fact]
+ public async Task GetAIAgentAsync_LegacyOverload_WithNullAssistantClient_ThrowsArgumentNullExceptionAsync()
+ {
+ // Arrange
+ AssistantClient? assistantClient = null;
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(() =>
+ assistantClient!.GetAIAgentAsync("asst_abc123"));
+
+ Assert.Equal("assistantClient", exception.ParamName);
+ }
+
+ ///
+ /// Verify that GetAIAgentAsync with legacy overload throws ArgumentException when agentId is empty.
+ ///
+ [Fact]
+ public async Task GetAIAgentAsync_LegacyOverload_WithEmptyAgentId_ThrowsArgumentExceptionAsync()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(() =>
+ assistantClient.GetAIAgentAsync(string.Empty));
+
+ Assert.Equal("agentId", exception.ParamName);
+ }
+
+ ///
+ /// Verify that GetAIAgentAsync with options throws ArgumentNullException when assistantClient is null.
+ ///
+ [Fact]
+ public async Task GetAIAgentAsync_WithOptions_WithNullAssistantClient_ThrowsArgumentNullExceptionAsync()
+ {
+ // Arrange
+ AssistantClient? assistantClient = null;
+ var options = new ChatClientAgentOptions();
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(() =>
+ assistantClient!.GetAIAgentAsync("asst_abc123", options));
+
+ Assert.Equal("assistantClient", exception.ParamName);
+ }
+
+ ///
+ /// Verify that GetAIAgentAsync with options throws ArgumentNullException when options is null.
+ ///
+ [Fact]
+ public async Task GetAIAgentAsync_WithOptions_WithNullOptions_ThrowsArgumentNullExceptionAsync()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+
+ // Act & Assert
+ var exception = await Assert.ThrowsAsync(() =>
+ assistantClient.GetAIAgentAsync("asst_abc123", (ChatClientAgentOptions)null!));
+
+ Assert.Equal("options", exception.ParamName);
+ }
+
+ ///
+ /// Verify that AsAIAgent with options throws ArgumentNullException when assistantClient is null.
+ ///
+ [Fact]
+ public void AsAIAgent_WithOptions_WithNullAssistantClient_ThrowsArgumentNullException()
+ {
+ // Arrange
+ AssistantClient? assistantClient = null;
+ var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123"}"""))!;
+ var options = new ChatClientAgentOptions();
+
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ assistantClient!.AsAIAgent(assistant, options));
+
+ Assert.Equal("assistantClient", exception.ParamName);
+ }
+
///
/// Creates a test AssistantClient implementation for testing.
///