Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,32 @@
<Right>lib/netstandard2.0/Microsoft.Extensions.AI.Abstractions.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Microsoft.Extensions.AI.FunctionApprovalRequestContent.CreateResponse(System.Boolean)</Target>
<Left>lib/net462/Microsoft.Extensions.AI.Abstractions.dll</Left>
<Right>lib/net462/Microsoft.Extensions.AI.Abstractions.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Microsoft.Extensions.AI.FunctionApprovalRequestContent.CreateResponse(System.Boolean)</Target>
<Left>lib/net8.0/Microsoft.Extensions.AI.Abstractions.dll</Left>
<Right>lib/net8.0/Microsoft.Extensions.AI.Abstractions.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Microsoft.Extensions.AI.FunctionApprovalRequestContent.CreateResponse(System.Boolean)</Target>
<Left>lib/net9.0/Microsoft.Extensions.AI.Abstractions.dll</Left>
<Right>lib/net9.0/Microsoft.Extensions.AI.Abstractions.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Microsoft.Extensions.AI.FunctionApprovalRequestContent.CreateResponse(System.Boolean)</Target>
<Left>lib/netstandard2.0/Microsoft.Extensions.AI.Abstractions.dll</Left>
<Right>lib/netstandard2.0/Microsoft.Extensions.AI.Abstractions.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
</Suppressions>
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public FunctionApprovalRequestContent(string id, FunctionCallContent functionCal
/// Creates a <see cref="FunctionApprovalResponseContent"/> to indicate whether the function call is approved or rejected based on the value of <paramref name="approved"/>.
/// </summary>
/// <param name="approved"><see langword="true"/> if the function call is approved; otherwise, <see langword="false"/>.</param>
/// <param name="reason">An optional reason for the approval or rejection.</param>
/// <returns>The <see cref="FunctionApprovalResponseContent"/> representing the approval response.</returns>
public FunctionApprovalResponseContent CreateResponse(bool approved) => new(Id, approved, FunctionCall);
public FunctionApprovalResponseContent CreateResponse(bool approved, string? reason = null) => new(Id, approved, FunctionCall) { Reason = reason };
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,9 @@ public FunctionApprovalResponseContent(string id, bool approved, FunctionCallCon
/// Gets the function call for which approval was requested.
/// </summary>
public FunctionCallContent FunctionCall { get; }

/// <summary>
/// Gets or sets the optional reason for the approval or rejection.
/// </summary>
public string? Reason { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -1418,7 +1418,16 @@ private static (List<ApprovalResultWithRequestMessage>? approvals, List<Approval
/// <returns>The <see cref="AIContent"/> for the rejected function calls.</returns>
private static List<AIContent>? GenerateRejectedFunctionResults(List<ApprovalResultWithRequestMessage>? rejections) =>
rejections is { Count: > 0 } ?
rejections.ConvertAll(static m => (AIContent)new FunctionResultContent(m.Response.FunctionCall.CallId, "Error: Tool call invocation was rejected by user.")) :
rejections.ConvertAll(m =>
{
string result = "Tool call invocation rejected.";
if (!string.IsNullOrWhiteSpace(m.Response.Reason))
{
result = $"{result} {m.Response.Reason}";
}

return (AIContent)new FunctionResultContent(m.Response.FunctionCall.CallId, result);
}) :
null;

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,28 @@ public void CreateResponse_ReturnsExpectedResponse(bool approved)
Assert.Same(id, response.Id);
Assert.Equal(approved, response.Approved);
Assert.Same(functionCall, response.FunctionCall);
Assert.Null(response.Reason);
}

[Theory]
[InlineData(true, "Approved for testing")]
[InlineData(false, "Rejected due to security concerns")]
[InlineData(true, null)]
[InlineData(false, null)]
public void CreateResponse_WithReason_ReturnsExpectedResponse(bool approved, string? reason)
{
string id = "req-1";
FunctionCallContent functionCall = new("FCC1", "TestFunction");

FunctionApprovalRequestContent content = new(id, functionCall);

var response = content.CreateResponse(approved, reason);

Assert.NotNull(response);
Assert.Same(id, response.Id);
Assert.Equal(approved, response.Approved);
Assert.Same(functionCall, response.FunctionCall);
Assert.Equal(reason, response.Reason);
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,23 @@ public void Constructor_Roundtrips(string id, bool approved)
Assert.Same(functionCall, content.FunctionCall);
}

[Fact]
public void Serialization_Roundtrips()
[Theory]
[InlineData(null)]
[InlineData("Custom rejection reason")]
public void Serialization_Roundtrips(string? reason)
{
var content = new FunctionApprovalResponseContent("request123", true, new FunctionCallContent("call123", "functionName"));
var content = new FunctionApprovalResponseContent("request123", true, new FunctionCallContent("call123", "functionName"))
{
Reason = reason
};

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

Assert.NotNull(deserializedContent);
Assert.Equal(content.Id, deserializedContent.Id);
Assert.Equal(content.Approved, deserializedContent.Approved);
Assert.Equal(content.Reason, deserializedContent.Reason);
Assert.NotNull(deserializedContent.FunctionCall);
Assert.Equal(content.FunctionCall.CallId, deserializedContent.FunctionCall.CallId);
Assert.Equal(content.FunctionCall.Name, deserializedContent.FunctionCall.Name);
Expand Down
Loading
Loading