Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ namespace Microsoft.Extensions.AI;
// [JsonDerivedType(typeof(McpServerToolApprovalResponseContent), typeDiscriminator: "mcpServerToolApprovalResponse")]
// [JsonDerivedType(typeof(CodeInterpreterToolCallContent), typeDiscriminator: "codeInterpreterToolCall")]
// [JsonDerivedType(typeof(CodeInterpreterToolResultContent), typeDiscriminator: "codeInterpreterToolResult")]
// [JsonDerivedType(typeof(AdditionalDetailsRequestContent), typeDiscriminator: "additionalDetailsRequestContent")]
// [JsonDerivedType(typeof(AdditionalDetailsResponseContent), typeDiscriminator: "additionalDetailsResponseContent")]

public class AIContent
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// 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.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.AI;

/// <summary>
/// Represents a request for additional details from the user.
/// </summary>
[Experimental("MEAI001")]
public sealed class AdditionalDetailsRequestContent : UserInputRequestContent
{
/// <summary>
/// Initializes a new instance of the <see cref="AdditionalDetailsRequestContent"/> class.
/// </summary>
/// <param name="id">The ID that uniquely identifies the additional details request/response pair.</param>
/// <param name="request">The additional details request.</param>
/// <exception cref="ArgumentNullException"><paramref name="id"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="id"/> is empty or composed entirely of whitespace.</exception>
/// <exception cref="ArgumentNullException"><paramref name="request"/> is <see langword="null"/>.</exception>
public AdditionalDetailsRequestContent(string id, AIContent request)
: base(id)
{
Request = Throw.IfNull(request);
}

/// <summary>
/// Gets the additional details request.
/// </summary>
public AIContent Request { get; }

/// <summary>
/// Creates a <see cref="AdditionalDetailsResponseContent"/> to provide the requested additional details.
/// </summary>
/// <param name="response">The <see cref="AIContent"/> containing the requested additional details.</param>
/// <returns>The <see cref="AdditionalDetailsResponseContent"/> representing the response.</returns>
/// <exception cref="ArgumentNullException"><paramref name="response"/> is <see langword="null"/>.</exception>
public AdditionalDetailsResponseContent CreateResponse(AIContent response) => new(Id, response);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.AI;

/// <summary>
/// Represents a response for additional details request from the user.
/// </summary>
[Experimental("MEAI001")]
public sealed class AdditionalDetailsResponseContent : UserInputResponseContent
{
/// <summary>
/// Initializes a new instance of the <see cref="AdditionalDetailsResponseContent"/> class.
/// </summary>
/// <param name="id">The ID that uniquely identifies the additional details request/response pair.</param>
/// <param name="response">The additional details response.</param>
/// <exception cref="ArgumentNullException"><paramref name="id"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="id"/> is empty or composed entirely of whitespace.</exception>
/// <exception cref="ArgumentNullException"><paramref name="response"/> is <see langword="null"/>.</exception>
public AdditionalDetailsResponseContent(string id, AIContent response)
: base(id)
{
Response = Throw.IfNull(response);
}

/// <summary>
/// Gets the additional details response.
/// </summary>
public AIContent Response { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace Microsoft.Extensions.AI;
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
[JsonDerivedType(typeof(FunctionApprovalRequestContent), "functionApprovalRequest")]
[JsonDerivedType(typeof(McpServerToolApprovalRequestContent), "mcpServerToolApprovalRequest")]
[JsonDerivedType(typeof(AdditionalDetailsRequestContent), "additionalDetailsRequestContent")]
public class UserInputRequestContent : AIContent
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ namespace Microsoft.Extensions.AI;
[JsonPolymorphic(TypeDiscriminatorPropertyName = "$type")]
[JsonDerivedType(typeof(FunctionApprovalResponseContent), "functionApprovalResponse")]
[JsonDerivedType(typeof(McpServerToolApprovalResponseContent), "mcpServerToolApprovalResponse")]
[JsonDerivedType(typeof(AdditionalDetailsResponseContent), "additionalDetailsResponseContent")]
public class UserInputResponseContent : AIContent
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ private static JsonSerializerOptions CreateDefaultOptions()
AddAIContentType(options, typeof(McpServerToolApprovalResponseContent), typeDiscriminatorId: "mcpServerToolApprovalResponse", checkBuiltIn: false);
AddAIContentType(options, typeof(CodeInterpreterToolCallContent), typeDiscriminatorId: "codeInterpreterToolCall", checkBuiltIn: false);
AddAIContentType(options, typeof(CodeInterpreterToolResultContent), typeDiscriminatorId: "codeInterpreterToolResult", checkBuiltIn: false);
AddAIContentType(options, typeof(AdditionalDetailsRequestContent), typeDiscriminatorId: "additionalDetailsRequestContent", checkBuiltIn: false);
AddAIContentType(options, typeof(AdditionalDetailsResponseContent), typeDiscriminatorId: "additionalDetailsResponseContent", checkBuiltIn: false);

if (JsonSerializer.IsReflectionEnabledByDefault)
{
Expand Down Expand Up @@ -133,6 +135,8 @@ private static JsonSerializerOptions CreateDefaultOptions()
[JsonSerializable(typeof(McpServerToolApprovalResponseContent))]
[JsonSerializable(typeof(CodeInterpreterToolCallContent))]
[JsonSerializable(typeof(CodeInterpreterToolResultContent))]
[JsonSerializable(typeof(AdditionalDetailsRequestContent))]
[JsonSerializable(typeof(AdditionalDetailsResponseContent))]
[JsonSerializable(typeof(ResponseContinuationToken))]

// IEmbeddingGenerator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ public void Serialization_DerivedTypes_Roundtrips()
new McpServerToolCallContent("call123", "myTool", "myServer"),
new McpServerToolResultContent("call123"),
new McpServerToolApprovalRequestContent("request123", new McpServerToolCallContent("call123", "myTool", "myServer")),
new McpServerToolApprovalResponseContent("request123", approved: true)
new McpServerToolApprovalResponseContent("request123", approved: true),
new AdditionalDetailsRequestContent("request123", new TextContent("Please provide the image you mentioned but did not provide.")),
new AdditionalDetailsResponseContent("response123", new DataContent(new byte[]{ 1, 2, 3 }, "image/jpeg"))
]);

var serialized = JsonSerializer.Serialize(message, AIJsonUtilities.DefaultOptions);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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.Text.Json;
using Xunit;

namespace Microsoft.Extensions.AI.Contents;

public class AdditionalDetailsRequestContentTests
{
[Fact]
public void Constructor_InvalidArguments_Throws()
{
Assert.Throws<ArgumentNullException>("id", () => new AdditionalDetailsRequestContent(null!, new TextContent("request")));
Assert.Throws<ArgumentException>("id", () => new AdditionalDetailsRequestContent("", new TextContent("request")));
Assert.Throws<ArgumentException>("id", () => new AdditionalDetailsRequestContent("\r\t\n ", new TextContent("request")));

Assert.Throws<ArgumentNullException>("request", () => new AdditionalDetailsRequestContent("id", null!));
}

[Fact]
public void Constructor_Roundtrips()
{
string id = "abc";
TextContent request = new("What is your name?");
AdditionalDetailsRequestContent content = new(id, request);

Assert.Same(id, content.Id);
Assert.Same(request, content.Request);
}

[Fact]
public void CreateResponse_ReturnsExpectedResponse()
{
string id = "req-1";
string request = "What is your name?";
TextContent response = new TextContent("My name is John");

AdditionalDetailsRequestContent content = new(id, new TextContent(request));

var textResponse = content.CreateResponse(response);

Assert.NotNull(textResponse);
Assert.Same(id, textResponse.Id);
Assert.Same(response, textResponse.Response);
Assert.Throws<ArgumentNullException>("response", () => content.CreateResponse(null!));
}

[Fact]
public void Serialization_Roundtrips()
{
var content = new AdditionalDetailsRequestContent("request123", new TextContent("What is your name?"));

var json = JsonSerializer.Serialize(content, AIJsonUtilities.DefaultOptions);
var deserializedContent = JsonSerializer.Deserialize<AdditionalDetailsRequestContent>(json, AIJsonUtilities.DefaultOptions);

Assert.NotNull(deserializedContent);
Assert.Equal(content.Id, deserializedContent.Id);

TextContent originalRequest = Assert.IsType<TextContent>(content.Request);
TextContent deserializedRequest = Assert.IsType<TextContent>(deserializedContent.Request);

Assert.Equal(originalRequest.Text, deserializedRequest.Text);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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.Text.Json;
using Xunit;

namespace Microsoft.Extensions.AI.Contents;

public class AdditionalDetailsResponseContentTests
{
[Fact]
public void Constructor_InvalidArguments_Throws()
{
Assert.Throws<ArgumentNullException>("id", () => new AdditionalDetailsResponseContent(null!, new TextContent("response")));
Assert.Throws<ArgumentException>("id", () => new AdditionalDetailsResponseContent("", new TextContent("response")));
Assert.Throws<ArgumentException>("id", () => new AdditionalDetailsResponseContent("\r\t\n ", new TextContent("response")));

Assert.Throws<ArgumentNullException>("response", () => new AdditionalDetailsResponseContent("id", null!));
}

[Fact]
public void Constructor_Roundtrips()
{
string id = "test-id";
TextContent response = new("test-response");
AdditionalDetailsResponseContent content = new(id, response);

Assert.Same(id, content.Id);
Assert.Same(response, content.Response);
}

[Fact]
public void Serialization_Roundtrips()
{
var content = new AdditionalDetailsResponseContent("response123", new TextContent("This is my answer"));

var json = JsonSerializer.Serialize(content, AIJsonUtilities.DefaultOptions);
var deserializedContent = JsonSerializer.Deserialize<AdditionalDetailsResponseContent>(json, AIJsonUtilities.DefaultOptions);

Assert.NotNull(deserializedContent);
Assert.Equal(content.Id, deserializedContent.Id);

TextContent originalResponse = Assert.IsType<TextContent>(content.Response);
TextContent deserializedResponse = Assert.IsType<TextContent>(deserializedContent.Response);

Assert.Equal(originalResponse.Text, deserializedResponse.Text);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public void Serialization_DerivedTypes_Roundtrips()
[
new FunctionApprovalRequestContent("request123", new FunctionCallContent("call123", "functionName", new Dictionary<string, object?> { { "param1", 123 } })),
new McpServerToolApprovalRequestContent("request123", new McpServerToolCallContent("call123", "myTool", "myServer")),
new AdditionalDetailsRequestContent("request123", new TextContent("I need more details. Where would you like to fly from and to?")),
];

var serializedContents = JsonSerializer.Serialize(contents, TestJsonSerializerContext.Default.UserInputRequestContentArray);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public void Serialization_DerivedTypes_Roundtrips()
[
new FunctionApprovalResponseContent("request123", true, new FunctionCallContent("call123", "functionName")),
new McpServerToolApprovalResponseContent("request123", true),
new AdditionalDetailsResponseContent("response123", new TextContent("I would like to fly from New York to San Francisco."))
];

var serializedContents = JsonSerializer.Serialize(contents, TestJsonSerializerContext.Default.UserInputResponseContentArray);
Expand Down
Loading