diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIAssistantsExtensions.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIAssistantsExtensions.cs
new file mode 100644
index 00000000000..900883c6d43
--- /dev/null
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIAssistantsExtensions.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace OpenAI.Assistants;
+
+/// Provides extension methods for working with content associated with OpenAI.Assistants.
+public static class MicrosoftExtensionsAIAssistantsExtensions
+{
+ /// Creates an OpenAI from an .
+ /// The function to convert.
+ /// An OpenAI representing .
+ /// is .
+ public static FunctionToolDefinition AsOpenAIAssistantsFunctionToolDefinition(this AIFunction function) =>
+ OpenAIAssistantsChatClient.ToOpenAIAssistantsFunctionToolDefinition(Throw.IfNull(function));
+}
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIChatExtensions.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIChatExtensions.cs
new file mode 100644
index 00000000000..c7eb3b2e03e
--- /dev/null
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIChatExtensions.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace OpenAI.Chat;
+
+/// Provides extension methods for working with content associated with OpenAI.Chat.
+public static class MicrosoftExtensionsAIChatExtensions
+{
+ /// Creates an OpenAI from an .
+ /// The function to convert.
+ /// An OpenAI representing .
+ /// is .
+ public static ChatTool AsOpenAIChatTool(this AIFunction function) =>
+ OpenAIChatClient.ToOpenAIChatTool(Throw.IfNull(function));
+
+ /// Creates a sequence of OpenAI instances from the specified input messages.
+ /// The input messages to convert.
+ /// A sequence of OpenAI chat messages.
+ public static IEnumerable AsOpenAIChatMessages(this IEnumerable messages) =>
+ OpenAIChatClient.ToOpenAIChatMessages(Throw.IfNull(messages), chatOptions: null);
+
+ /// Creates a Microsoft.Extensions.AI from a .
+ /// The to convert to a .
+ /// The options employed in the creation of the response.
+ /// A converted .
+ public static ChatResponse AsChatResponse(this ChatCompletion chatCompletion, ChatCompletionOptions? options = null) =>
+ OpenAIChatClient.FromOpenAIChatCompletion(Throw.IfNull(chatCompletion), options);
+}
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIRealtimeExtensions.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIRealtimeExtensions.cs
new file mode 100644
index 00000000000..ad180e96b1e
--- /dev/null
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIRealtimeExtensions.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace OpenAI.Realtime;
+
+/// Provides extension methods for working with content associated with OpenAI.Realtime.
+public static class MicrosoftExtensionsAIRealtimeExtensions
+{
+ /// Creates an OpenAI from an .
+ /// The function to convert.
+ /// An OpenAI representing .
+ /// is .
+ public static ConversationFunctionTool AsOpenAIConversationFunctionTool(this AIFunction function) =>
+ OpenAIRealtimeConversationClient.ToOpenAIConversationFunctionTool(Throw.IfNull(function));
+}
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
new file mode 100644
index 00000000000..e54b3092c5a
--- /dev/null
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/MicrosoftExtensionsAIResponsesExtensions.cs
@@ -0,0 +1,33 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace OpenAI.Responses;
+
+/// Provides extension methods for working with content associated with OpenAI.Responses.
+public static class MicrosoftExtensionsAIResponsesExtensions
+{
+ /// Creates an OpenAI from an .
+ /// The function to convert.
+ /// An OpenAI representing .
+ /// is .
+ public static ResponseTool AsOpenAIResponseTool(this AIFunction function) =>
+ OpenAIResponsesChatClient.ToResponseTool(Throw.IfNull(function));
+
+ /// Creates a sequence of OpenAI instances from the specified input messages.
+ /// The input messages to convert.
+ /// A sequence of OpenAI response items.
+ public static IEnumerable AsOpenAIResponseItems(this IEnumerable messages) =>
+ OpenAIResponsesChatClient.ToOpenAIResponseItems(Throw.IfNull(messages));
+
+ /// Creates a Microsoft.Extensions.AI from an .
+ /// The to convert to a .
+ /// The options employed in the creation of the response.
+ /// A converted .
+ public static ChatResponse AsChatResponse(this OpenAIResponse response, ResponseCreationOptions? options = null) =>
+ OpenAIResponsesChatClient.FromOpenAIResponse(Throw.IfNull(response), options);
+}
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantsChatClient.cs
similarity index 96%
rename from src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantChatClient.cs
rename to src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantsChatClient.cs
index c1d59e30a68..46bc39ee278 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIAssistantsChatClient.cs
@@ -26,8 +26,8 @@
namespace Microsoft.Extensions.AI;
-/// Represents an for an Azure.AI.Agents.Persistent .
-internal sealed class OpenAIAssistantChatClient : IChatClient
+/// Represents an for an OpenAI .
+internal sealed class OpenAIAssistantsChatClient : IChatClient
{
/// The underlying .
private readonly AssistantClient _client;
@@ -44,8 +44,8 @@ internal sealed class OpenAIAssistantChatClient : IChatClient
/// List of tools associated with the assistant.
private IReadOnlyList? _assistantTools;
- /// Initializes a new instance of the class for the specified .
- public OpenAIAssistantChatClient(AssistantClient assistantClient, string assistantId, string? defaultThreadId)
+ /// Initializes a new instance of the class for the specified .
+ public OpenAIAssistantsChatClient(AssistantClient assistantClient, string assistantId, string? defaultThreadId)
{
_client = Throw.IfNull(assistantClient);
_assistantId = Throw.IfNullOrWhitespace(assistantId);
@@ -62,6 +62,13 @@ public OpenAIAssistantChatClient(AssistantClient assistantClient, string assista
_metadata = new("openai", providerUrl);
}
+ /// Initializes a new instance of the class for the specified .
+ public OpenAIAssistantsChatClient(AssistantClient assistantClient, Assistant assistant, string? defaultThreadId)
+ : this(assistantClient, Throw.IfNull(assistant).Id, defaultThreadId)
+ {
+ _assistantTools = assistant.Tools;
+ }
+
///
public object? GetService(Type serviceType, object? serviceKey = null) =>
serviceType is null ? throw new ArgumentNullException(nameof(serviceType)) :
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs
index 52dfc3dc8af..9fc2f3cbeef 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs
@@ -17,6 +17,7 @@
#pragma warning disable EA0011 // Consider removing unnecessary conditional access operator (?)
#pragma warning disable S1067 // Expressions should not be too complex
#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
+#pragma warning disable SA1202 // Elements should be ordered by access
#pragma warning disable SA1204 // Static elements should appear before instance elements
namespace Microsoft.Extensions.AI;
@@ -76,7 +77,7 @@ public async Task GetResponseAsync(
// Make the call to OpenAI.
var response = await _chatClient.CompleteChatAsync(openAIChatMessages, openAIOptions, cancellationToken).ConfigureAwait(false);
- return FromOpenAIChatCompletion(response.Value, options, openAIOptions);
+ return FromOpenAIChatCompletion(response.Value, openAIOptions);
}
///
@@ -430,7 +431,7 @@ private static string GetOutputAudioMimeType(ChatCompletionOptions? options) =>
"mp3" or _ => "audio/mpeg",
};
- private static ChatResponse FromOpenAIChatCompletion(ChatCompletion openAICompletion, ChatOptions? options, ChatCompletionOptions chatCompletionOptions)
+ internal static ChatResponse FromOpenAIChatCompletion(ChatCompletion openAICompletion, ChatCompletionOptions? chatCompletionOptions)
{
_ = Throw.IfNull(openAICompletion);
@@ -461,17 +462,14 @@ private static ChatResponse FromOpenAIChatCompletion(ChatCompletion openAIComple
}
// Also manufacture function calling content items from any tool calls in the response.
- if (options?.Tools is { Count: > 0 })
+ foreach (ChatToolCall toolCall in openAICompletion.ToolCalls)
{
- foreach (ChatToolCall toolCall in openAICompletion.ToolCalls)
+ if (!string.IsNullOrWhiteSpace(toolCall.FunctionName))
{
- if (!string.IsNullOrWhiteSpace(toolCall.FunctionName))
- {
- var callContent = ParseCallContentFromBinaryData(toolCall.FunctionArguments, toolCall.Id, toolCall.FunctionName);
- callContent.RawRepresentation = toolCall;
+ var callContent = ParseCallContentFromBinaryData(toolCall.FunctionArguments, toolCall.Id, toolCall.FunctionName);
+ callContent.RawRepresentation = toolCall;
- returnMessage.Contents.Add(callContent);
- }
+ returnMessage.Contents.Add(callContent);
}
}
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIClientExtensions.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIClientExtensions.cs
index 9f42fa88773..889998cc933 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIClientExtensions.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIClientExtensions.cs
@@ -8,20 +8,15 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
-using Microsoft.Shared.Diagnostics;
using OpenAI;
using OpenAI.Assistants;
using OpenAI.Audio;
using OpenAI.Chat;
using OpenAI.Embeddings;
-using OpenAI.Realtime;
using OpenAI.Responses;
#pragma warning disable S103 // Lines should not be too long
-#pragma warning disable S1067 // Expressions should not be too complex
#pragma warning disable SA1515 // Single-line comment should be preceded by blank line
-#pragma warning disable CA1305 // Specify IFormatProvider
-#pragma warning disable S1135 // Track uses of "TODO" tags
namespace Microsoft.Extensions.AI;
@@ -120,7 +115,7 @@ public static IChatClient AsIChatClient(this ChatClient chatClient) =>
/// An that can be used to converse via the .
/// is .
public static IChatClient AsIChatClient(this OpenAIResponseClient responseClient) =>
- new OpenAIResponseChatClient(responseClient);
+ new OpenAIResponsesChatClient(responseClient);
/// Gets an for use with this .
/// The instance to be accessed as an .
@@ -135,7 +130,21 @@ public static IChatClient AsIChatClient(this OpenAIResponseClient responseClient
/// is .
/// is empty or composed entirely of whitespace.
public static IChatClient AsIChatClient(this AssistantClient assistantClient, string assistantId, string? threadId = null) =>
- new OpenAIAssistantChatClient(assistantClient, assistantId, threadId);
+ new OpenAIAssistantsChatClient(assistantClient, assistantId, threadId);
+
+ /// Gets an for use with this .
+ /// The instance to be accessed as an .
+ /// The with which to interact.
+ ///
+ /// An optional existing thread identifier for the chat session. This serves as a default, and may be overridden per call to
+ /// or via the
+ /// property. If no thread ID is provided via either mechanism, a new thread will be created for the request.
+ ///
+ /// An instance configured to interact with the specified agent and thread.
+ /// is .
+ /// is .
+ public static IChatClient AsIChatClient(this AssistantClient assistantClient, Assistant assistant, string? threadId = null) =>
+ new OpenAIAssistantsChatClient(assistantClient, assistant, threadId);
/// Gets an for use with this .
/// The client.
@@ -153,48 +162,6 @@ public static ISpeechToTextClient AsISpeechToTextClient(this AudioClient audioCl
public static IEmbeddingGenerator> AsIEmbeddingGenerator(this EmbeddingClient embeddingClient, int? defaultModelDimensions = null) =>
new OpenAIEmbeddingGenerator(embeddingClient, defaultModelDimensions);
- /// Creates an OpenAI from an .
- /// The function to convert.
- /// An OpenAI representing .
- /// is .
- public static ChatTool AsOpenAIChatTool(this AIFunction function) =>
- OpenAIChatClient.ToOpenAIChatTool(Throw.IfNull(function));
-
- /// Creates an OpenAI from an .
- /// The function to convert.
- /// An OpenAI representing .
- /// is .
- public static FunctionToolDefinition AsOpenAIAssistantsFunctionToolDefinition(this AIFunction function) =>
- OpenAIAssistantChatClient.ToOpenAIAssistantsFunctionToolDefinition(Throw.IfNull(function));
-
- /// Creates an OpenAI from an .
- /// The function to convert.
- /// An OpenAI representing .
- /// is .
- public static ResponseTool AsOpenAIResponseTool(this AIFunction function) =>
- OpenAIResponseChatClient.ToResponseTool(Throw.IfNull(function));
-
- /// Creates an OpenAI from an .
- /// The function to convert.
- /// An OpenAI representing .
- /// is .
- public static ConversationFunctionTool AsOpenAIConversationFunctionTool(this AIFunction function) =>
- OpenAIRealtimeConversationClient.ToOpenAIConversationFunctionTool(Throw.IfNull(function));
-
- /// Creates a sequence of OpenAI instances from the specified input messages.
- /// The input messages to convert.
- /// A sequence of OpenAI chat messages.
- public static IEnumerable AsOpenAIChatMessages(this IEnumerable messages) =>
- OpenAIChatClient.ToOpenAIChatMessages(Throw.IfNull(messages), chatOptions: null);
-
- /// Creates a sequence of OpenAI instances from the specified input messages.
- /// The input messages to convert.
- /// A sequence of OpenAI response items.
- public static IEnumerable AsOpenAIResponseItems(this IEnumerable messages) =>
- OpenAIResponseChatClient.ToOpenAIResponseItems(Throw.IfNull(messages));
-
- // TODO: Once we're ready to rely on C# 14 features, add an extension property ChatOptions.Strict.
-
/// Gets whether the properties specify that strict schema handling is desired.
internal static bool? HasStrict(IReadOnlyDictionary? additionalProperties) =>
additionalProperties?.TryGetValue(StrictKey, out object? strictObj) is true &&
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIEmbeddingGenerator.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIEmbeddingGenerator.cs
index 004f6274265..a6c4af831ba 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIEmbeddingGenerator.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIEmbeddingGenerator.cs
@@ -8,7 +8,6 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Shared.Diagnostics;
-using OpenAI;
using OpenAI.Embeddings;
#pragma warning disable S1067 // Expressions should not be too complex
diff --git a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
similarity index 98%
rename from src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs
rename to src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
index b5ba36b5e1a..60d0e10a159 100644
--- a/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponseChatClient.cs
+++ b/src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs
@@ -23,7 +23,7 @@
namespace Microsoft.Extensions.AI;
/// Represents an for an .
-internal sealed class OpenAIResponseChatClient : IChatClient
+internal sealed class OpenAIResponsesChatClient : IChatClient
{
/// Metadata about the client.
private readonly ChatClientMetadata _metadata;
@@ -31,10 +31,10 @@ internal sealed class OpenAIResponseChatClient : IChatClient
/// The underlying .
private readonly OpenAIResponseClient _responseClient;
- /// Initializes a new instance of the class for the specified .
+ /// Initializes a new instance of the class for the specified .
/// The underlying client.
/// is .
- public OpenAIResponseChatClient(OpenAIResponseClient responseClient)
+ public OpenAIResponsesChatClient(OpenAIResponseClient responseClient)
{
_ = Throw.IfNull(responseClient);
@@ -78,10 +78,16 @@ public async Task GetResponseAsync(
// Make the call to the OpenAIResponseClient.
var openAIResponse = (await _responseClient.CreateResponseAsync(openAIResponseItems, openAIOptions, cancellationToken).ConfigureAwait(false)).Value;
+ // Convert the response to a ChatResponse.
+ return FromOpenAIResponse(openAIResponse, openAIOptions);
+ }
+
+ internal static ChatResponse FromOpenAIResponse(OpenAIResponse openAIResponse, ResponseCreationOptions? openAIOptions)
+ {
// Convert and return the results.
ChatResponse response = new()
{
- ConversationId = openAIOptions.StoredOutputEnabled is false ? null : openAIResponse.Id,
+ ConversationId = openAIOptions?.StoredOutputEnabled is false ? null : openAIResponse.Id,
CreatedAt = openAIResponse.CreatedAt,
FinishReason = ToFinishReason(openAIResponse.IncompleteStatusDetails?.Reason),
Messages = [new(ChatRole.Assistant, [])],
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIAssistantChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIAssistantChatClientTests.cs
index 3b084b5ec8a..7779e4cf18d 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIAssistantChatClientTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIAssistantChatClientTests.cs
@@ -19,7 +19,8 @@ public class OpenAIAssistantChatClientTests
public void AsIChatClient_InvalidArgs_Throws()
{
Assert.Throws("assistantClient", () => ((AssistantClient)null!).AsIChatClient("assistantId"));
- Assert.Throws("assistantId", () => new AssistantClient("ignored").AsIChatClient(null!));
+ Assert.Throws("assistantId", () => new AssistantClient("ignored").AsIChatClient((string)null!));
+ Assert.Throws("assistant", () => new AssistantClient("ignored").AsIChatClient((Assistant)null!));
}
[Fact]
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs
index d06d8f520be..640dab54f8e 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs
@@ -398,7 +398,7 @@ public async Task ChatOptions_DoNotOverwrite_NotNullPropertiesInRawRepresentatio
ResponseFormat = OpenAI.Chat.ChatResponseFormat.CreateTextFormat()
};
openAIOptions.StopSequences.Add("hello");
- openAIOptions.Tools.Add(OpenAIClientExtensions.AsOpenAIChatTool(tool));
+ openAIOptions.Tools.Add(tool.AsOpenAIChatTool());
return openAIOptions;
},
ModelId = null,
@@ -475,7 +475,7 @@ public async Task ChatOptions_DoNotOverwrite_NotNullPropertiesInRawRepresentatio
ResponseFormat = OpenAI.Chat.ChatResponseFormat.CreateTextFormat()
};
openAIOptions.StopSequences.Add("hello");
- openAIOptions.Tools.Add(OpenAIClientExtensions.AsOpenAIChatTool(tool));
+ openAIOptions.Tools.Add(tool.AsOpenAIChatTool());
return openAIOptions;
},
ModelId = null, // has no effect, you cannot change the model of an OpenAI's ChatClient.
diff --git a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
index 951554eda75..73f3ae65548 100644
--- a/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
+++ b/test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIConversionTests.cs
@@ -183,4 +183,37 @@ public void AsOpenAIResponseItems_ProducesExpectedOutput()
Assert.Equal(OpenAI.Responses.MessageRole.Assistant, m5.Role);
Assert.Equal("The answer is 42.", Assert.Single(m5.Content).Text);
}
+
+ [Fact]
+ public void AsChatResponse_ConvertsOpenAIChatCompletion()
+ {
+ Assert.Throws("chatCompletion", () => ((ChatCompletion)null!).AsChatResponse());
+
+ ChatCompletion cc = OpenAIChatModelFactory.ChatCompletion(
+ "id", OpenAI.Chat.ChatFinishReason.Length, null, null,
+ [ChatToolCall.CreateFunctionToolCall("id", "functionName", BinaryData.FromString("test"))],
+ ChatMessageRole.User, null, null, null, new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero),
+ "model123", null, OpenAIChatModelFactory.ChatTokenUsage(2, 1, 3));
+ cc.Content.Add(ChatMessageContentPart.CreateTextPart("Hello, world!"));
+ cc.Content.Add(ChatMessageContentPart.CreateImagePart(new Uri("http://example.com/image.png")));
+
+ ChatResponse response = cc.AsChatResponse();
+
+ Assert.Equal("id", response.ResponseId);
+ Assert.Equal(ChatFinishReason.Length, response.FinishReason);
+ Assert.Equal("model123", response.ModelId);
+ Assert.Equal(new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero), response.CreatedAt);
+ Assert.NotNull(response.Usage);
+ Assert.Equal(1, response.Usage.InputTokenCount);
+ Assert.Equal(2, response.Usage.OutputTokenCount);
+ Assert.Equal(3, response.Usage.TotalTokenCount);
+
+ ChatMessage message = Assert.Single(response.Messages);
+ Assert.Equal(ChatRole.User, message.Role);
+
+ Assert.Equal(3, message.Contents.Count);
+ Assert.Equal("Hello, world!", Assert.IsType(message.Contents[0]).Text);
+ Assert.Equal("http://example.com/image.png", Assert.IsType(message.Contents[1]).Uri.ToString());
+ Assert.Equal("functionName", Assert.IsType(message.Contents[2]).Name);
+ }
}