feat(pull-requests): expose review resolution reason

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8bf69b12-5131-4382-8c81-8ed1c88fb211
This commit is contained in:
Annie Gesellchen
2026-08-19 11:40:53 -07:00
parent 55f7b721fd
commit bfb759d137
8 changed files with 92 additions and 20 deletions
+1
View File
@@ -1236,6 +1236,7 @@ The following sets of tools are available:
- `owner`: Repository owner (string, required)
- `pullNumber`: Pull request number (number, required)
- `repo`: Repository name (string, required)
- `resolutionReason`: Optional reason for resolving a Copilot code review thread: addressed, wont-fix, or invalid. (string, optional)
- `threadId`: The node ID of the review thread (e.g., PRRT_kwDOxxx). Required for resolve_thread and unresolve_thread methods. Get thread IDs from pull_request_read with method get_review_comments. (string, optional)
- **search_pull_requests** - Search pull requests
+1
View File
@@ -251,6 +251,7 @@ runtime behavior (such as output formatting) won't appear here.
- **resolve_review_thread** - Resolve Review Thread
- **Required OAuth Scopes**: `repo`
- `resolutionReason`: Optional reason for resolving a Copilot code review thread: addressed, wont-fix, or invalid. (string, optional)
- `threadID`: The node ID of the review thread to resolve (e.g., PRRT_kwDOxxx) (string, required)
- **submit_pending_pull_request_review** - Submit Pending Pull Request Review
@@ -47,6 +47,10 @@
"description": "Repository name",
"type": "string"
},
"resolutionReason": {
"description": "Optional reason for resolving a Copilot code review thread: addressed, wont-fix, or invalid.",
"type": "string"
},
"threadId": {
"description": "The node ID of the review thread (e.g., PRRT_kwDOxxx). Required for resolve_thread and unresolve_thread methods. Get thread IDs from pull_request_read with method get_review_comments.",
"type": "string"
@@ -9,6 +9,10 @@
"description": "Resolve a review thread on a pull request. Resolving an already-resolved thread is a no-op.",
"inputSchema": {
"properties": {
"resolutionReason": {
"description": "Optional reason for resolving a Copilot code review thread: addressed, wont-fix, or invalid.",
"type": "string"
},
"threadID": {
"description": "The node ID of the review thread to resolve (e.g., PRRT_kwDOxxx)",
"type": "string"
+5 -3
View File
@@ -1738,8 +1738,9 @@ func TestGranularResolveReviewThread(t *testing.T) {
}
} `graphql:"resolveReviewThread(input: $input)"`
}{},
githubv4.ResolveReviewThreadInput{
ThreadID: githubv4.ID("PRRT_123"),
resolveReviewThreadInput{
ThreadID: githubv4.ID("PRRT_123"),
ResolutionReason: newGQLStringlike[githubv4.String]("addressed"),
},
nil,
githubv4mock.DataResponse(map[string]any{
@@ -1755,7 +1756,8 @@ func TestGranularResolveReviewThread(t *testing.T) {
handler := serverTool.Handler(deps)
request := createMCPRequest(map[string]any{
"threadID": "PRRT_123",
"threadID": "PRRT_123",
"resolutionReason": "addressed",
})
result, err := handler(ContextWithDeps(context.Background(), deps), &request)
require.NoError(t, err)
+24 -13
View File
@@ -1760,14 +1760,15 @@ func UpdatePullRequestBranch(t translations.TranslationHelperFunc) inventory.Ser
}
type PullRequestReviewWriteParams struct {
Method string
Owner string
Repo string
PullNumber int32
Body string
Event string
CommitID *string
ThreadID string
Method string
Owner string
Repo string
PullNumber int32
Body string
Event string
CommitID *string
ThreadID string
ResolutionReason *string
}
func PullRequestReviewWrite(t translations.TranslationHelperFunc) inventory.ServerTool {
@@ -1811,6 +1812,10 @@ func PullRequestReviewWrite(t translations.TranslationHelperFunc) inventory.Serv
Type: "string",
Description: "The node ID of the review thread (e.g., PRRT_kwDOxxx). Required for resolve_thread and unresolve_thread methods. Get thread IDs from pull_request_read with method get_review_comments.",
},
"resolutionReason": {
Type: "string",
Description: "Optional reason for resolving a Copilot code review thread: addressed, wont-fix, or invalid.",
},
},
Required: []string{"method", "owner", "repo", "pullNumber"},
}
@@ -1858,10 +1863,10 @@ Available methods:
result, err := DeletePendingPullRequestReview(ctx, client, params)
return result, nil, err
case "resolve_thread":
result, err := ResolveReviewThread(ctx, client, params.ThreadID, true)
result, err := ResolveReviewThread(ctx, client, params.ThreadID, params.ResolutionReason, true)
return result, nil, err
case "unresolve_thread":
result, err := ResolveReviewThread(ctx, client, params.ThreadID, false)
result, err := ResolveReviewThread(ctx, client, params.ThreadID, nil, false)
return result, nil, err
default:
return utils.NewToolResultError(fmt.Sprintf("unknown method: %s", params.Method)), nil, nil
@@ -2094,8 +2099,13 @@ func DeletePendingPullRequestReview(ctx context.Context, client *githubv4.Client
return utils.NewToolResultText("pending pull request review successfully deleted"), nil
}
type resolveReviewThreadInput struct {
ThreadID githubv4.ID `json:"threadId"`
ResolutionReason *githubv4.String `json:"resolutionReason,omitempty"`
}
// ResolveReviewThread resolves or unresolves a PR review thread using GraphQL mutations.
func ResolveReviewThread(ctx context.Context, client *githubv4.Client, threadID string, resolve bool) (*mcp.CallToolResult, error) {
func ResolveReviewThread(ctx context.Context, client *githubv4.Client, threadID string, resolutionReason *string, resolve bool) (*mcp.CallToolResult, error) {
if threadID == "" {
return utils.NewToolResultError("threadId is required for resolve_thread and unresolve_thread methods"), nil
}
@@ -2110,8 +2120,9 @@ func ResolveReviewThread(ctx context.Context, client *githubv4.Client, threadID
} `graphql:"resolveReviewThread(input: $input)"`
}
input := githubv4.ResolveReviewThreadInput{
ThreadID: githubv4.ID(threadID),
input := resolveReviewThreadInput{
ThreadID: githubv4.ID(threadID),
ResolutionReason: newGQLStringlikePtr[githubv4.String](resolutionReason),
}
if err := client.Mutate(ctx, &mutation, input, nil); err != nil {
+14 -2
View File
@@ -690,6 +690,10 @@ func GranularResolveReviewThread(t translations.TranslationHelperFunc) inventory
Type: "string",
Description: "The node ID of the review thread to resolve (e.g., PRRT_kwDOxxx)",
},
"resolutionReason": {
Type: "string",
Description: "Optional reason for resolving a Copilot code review thread: addressed, wont-fix, or invalid.",
},
},
Required: []string{"threadID"},
},
@@ -700,13 +704,21 @@ func GranularResolveReviewThread(t translations.TranslationHelperFunc) inventory
if err != nil {
return utils.NewToolResultError(err.Error()), nil, nil
}
resolutionReason, hasResolutionReason, err := OptionalParamOK[string](args, "resolutionReason")
if err != nil {
return utils.NewToolResultError(err.Error()), nil, nil
}
var resolutionReasonPtr *string
if hasResolutionReason {
resolutionReasonPtr = &resolutionReason
}
gqlClient, err := deps.GetGQLClient(ctx)
if err != nil {
return utils.NewToolResultErrorFromErr("failed to get GitHub GraphQL client", err), nil, nil
}
result, err := ResolveReviewThread(ctx, gqlClient, threadID, true)
result, err := ResolveReviewThread(ctx, gqlClient, threadID, resolutionReasonPtr, true)
return result, nil, err
},
)
@@ -750,7 +762,7 @@ func GranularUnresolveReviewThread(t translations.TranslationHelperFunc) invento
return utils.NewToolResultErrorFromErr("failed to get GitHub GraphQL client", err), nil, nil
}
result, err := ResolveReviewThread(ctx, gqlClient, threadID, false)
result, err := ResolveReviewThread(ctx, gqlClient, threadID, nil, false)
return result, nil, err
},
)
+39 -2
View File
@@ -4572,7 +4572,7 @@ func TestResolveReviewThread(t *testing.T) {
}
} `graphql:"resolveReviewThread(input: $input)"`
}{},
githubv4.ResolveReviewThreadInput{
resolveReviewThreadInput{
ThreadID: githubv4.ID("PRRT_kwDOTest123"),
},
nil,
@@ -4588,6 +4588,43 @@ func TestResolveReviewThread(t *testing.T) {
),
expectedResult: "review thread resolved successfully",
},
{
name: "successful resolve thread with resolution reason",
requestArgs: map[string]any{
"method": "resolve_thread",
"owner": "owner",
"repo": "repo",
"pullNumber": float64(42),
"threadId": "PRRT_kwDOTest123",
"resolutionReason": "wont-fix",
},
mockedClient: githubv4mock.NewMockedHTTPClient(
githubv4mock.NewMutationMatcher(
struct {
ResolveReviewThread struct {
Thread struct {
ID githubv4.ID
IsResolved githubv4.Boolean
}
} `graphql:"resolveReviewThread(input: $input)"`
}{},
resolveReviewThreadInput{
ThreadID: githubv4.ID("PRRT_kwDOTest123"),
ResolutionReason: newGQLStringlike[githubv4.String]("wont-fix"),
},
nil,
githubv4mock.DataResponse(map[string]any{
"resolveReviewThread": map[string]any{
"thread": map[string]any{
"id": "PRRT_kwDOTest123",
"isResolved": true,
},
},
}),
),
),
expectedResult: "review thread resolved successfully",
},
{
name: "successful unresolve thread",
requestArgs: map[string]any{
@@ -4692,7 +4729,7 @@ func TestResolveReviewThread(t *testing.T) {
}
} `graphql:"resolveReviewThread(input: $input)"`
}{},
githubv4.ResolveReviewThreadInput{
resolveReviewThreadInput{
ThreadID: githubv4.ID("PRRT_invalid"),
},
nil,