Skip to content
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,7 @@ For negotiated supported protocol versions `2026-07-28` or newer, the consolidat
- **pull_request_read** - Get details for a single pull request
- **OAuth Challenge Scopes**: `repo`
- `after`: Cursor for pagination, used only by the get_review_comments method. Pass the endCursor from the previous page's PageInfo to fetch the next page. (string, optional)
- `fields`: Subset of fields to return for pull_request_read results. Supported for get_reviews and get_check_runs. Valid fields depend on the selected method. If omitted or empty, all fields are returned. (string[], optional)
- `method`: Action to specify what pull request data needs to be retrieved from GitHub.
Possible options:
1. get - Get details of a specific pull request.
Expand Down
108 changes: 83 additions & 25 deletions pkg/github/__toolsnaps__/pull_request_read.snap
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,29 @@
"description": "Cursor for pagination, used only by the get_review_comments method. Pass the endCursor from the previous page's PageInfo to fetch the next page.",
"type": "string"
},
"fields": {
"description": "Subset of fields to return for pull_request_read results. Supported for get_reviews and get_check_runs. Valid fields depend on the selected method. If omitted or empty, all fields are returned.",
"items": {
"enum": [
"id",
"state",
"body",
"html_url",
"user",
"commit_id",
"submitted_at",
"author_association",
"name",
"status",
"conclusion",
"details_url",
"started_at",
"completed_at"
],
"type": "string"
},
"type": "array"
},
"method": {
"description": "Action to specify what pull request data needs to be retrieved from GitHub. \nPossible options: \n 1. get - Get details of a specific pull request.\n 2. get_diff - Get the diff of a pull request.\n 3. get_status - Get combined commit status of a head commit in a pull request.\n 4. get_files - Get the list of files changed in a pull request. Use with pagination parameters to control the number of results returned.\n 5. get_commits - Get the list of commits on a pull request. Use with pagination parameters to control the number of results returned.\n 6. get_review_comments - Get review threads on a pull request. Each thread contains logically grouped review comments made on the same code location during pull request reviews. Returns thread metadata and comments with nullable current and original line-range coordinates (line, start_line, original_line, original_start_line). Current coordinates are omitted when unavailable, such as for outdated comments. Use cursor-based pagination (perPage, after) to control results.\n 7. get_reviews - Get the reviews on a pull request. When asked for review comments, use get_review_comments method. Use with pagination parameters to control the number of results returned.\n 8. get_comments - Get comments on a pull request. Use this if user doesn't specifically want review comments. Use with pagination parameters to control the number of results returned.\n 9. get_check_runs - Get check runs for the head commit of a pull request. Check runs are the individual CI/CD jobs and checks that run on the PR.\n",
"enum": [
Expand Down Expand Up @@ -581,7 +604,10 @@
"properties": {
"completed_at": {
"description": "RFC 3339 timestamp.",
"type": "string"
"type": [
"null",
"string"
]
},
"conclusion": {
"description": "Completed check outcome.",
Expand All @@ -596,24 +622,42 @@
"stale",
"startup_failure"
],
"type": "string"
"type": [
"null",
"string"
]
},
"details_url": {
"type": "string"
"type": [
"null",
"string"
]
},
"html_url": {
"description": "Browser URL.",
"type": "string"
"type": [
"null",
"string"
]
},
"id": {
"type": "integer"
"type": [
"null",
"integer"
]
},
"name": {
"type": "string"
"type": [
"null",
"string"
]
},
"started_at": {
"description": "RFC 3339 timestamp.",
"type": "string"
"type": [
"null",
"string"
]
},
"status": {
"description": "Check execution status; empty for a sparse response.",
Expand All @@ -626,14 +670,12 @@
"pending",
""
],
"type": "string"
"type": [
"null",
"string"
]
}
},
"required": [
"id",
"name",
"status"
],
"type": "object"
},
"type": [
Expand Down Expand Up @@ -752,21 +794,36 @@
"additionalProperties": false,
"properties": {
"author_association": {
"type": "string"
"type": [
"null",
"string"
]
},
"body": {
"type": "string"
"type": [
"null",
"string"
]
},
"commit_id": {
"description": "Git commit SHA.",
"type": "string"
"type": [
"null",
"string"
]
},
"html_url": {
"description": "Browser URL.",
"type": "string"
"type": [
"null",
"string"
]
},
"id": {
"type": "integer"
"type": [
"null",
"integer"
]
},
"state": {
"description": "Review state; empty for a sparse response.",
Expand All @@ -778,11 +835,17 @@
"PENDING",
""
],
"type": "string"
"type": [
"null",
"string"
]
},
"submitted_at": {
"description": "RFC 3339 timestamp.",
"type": "string"
"type": [
"null",
"string"
]
},
"user": {
"additionalProperties": false,
Expand Down Expand Up @@ -879,11 +942,6 @@
]
}
},
"required": [
"id",
"state",
"html_url"
],
"type": "object"
},
"type": [
Expand Down
142 changes: 131 additions & 11 deletions pkg/github/consolidated_pull_request_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ import (
)

type PullRequestReadInput struct {
Method string `json:"method"`
Owner string `json:"owner"`
Repo string `json:"repo"`
PullNumber int `json:"pullNumber"`
Page *int `json:"page,omitempty"`
PerPage *int `json:"perPage,omitempty"`
After *string `json:"after,omitempty"`
Method string `json:"method"`
Owner string `json:"owner"`
Repo string `json:"repo"`
PullNumber int `json:"pullNumber"`
Fields []string `json:"fields,omitempty"`
Page *int `json:"page,omitempty"`
PerPage *int `json:"perPage,omitempty"`
After *string `json:"after,omitempty"`
}

type CreatePullRequestInput struct {
Expand Down Expand Up @@ -114,9 +115,9 @@ type PullRequestReadOutput struct {
Files *[]MinimalPRFile
Commits *[]MinimalPullRequestCommit
ReviewThreads *MinimalReviewThreadsResponse
Reviews *[]MinimalPullRequestReview
Reviews *[]PullRequestReviewOutput
Comments *[]IssueReadCommentOutput
CheckRuns *MinimalCheckRunsResult
CheckRuns *PullRequestCheckRunsOutput
}

// PullRequestDiffOutput wraps the raw diff, which the text content returns verbatim.
Expand Down Expand Up @@ -151,6 +152,125 @@ func (out PullRequestReadOutput) MarshalJSON() ([]byte, error) {
}
}

// fieldSelector reports whether a field should be emitted for the given
// selection. An empty selection keeps every field, matching the behaviour of
// omitting the `fields` parameter entirely.
func fieldSelector(fields []string) func(string) bool {
return func(field string) bool {
return len(fields) == 0 || slices.Contains(fields, field)
}
}

// PullRequestReviewOutput is the field-projected shape of a single review. Every
// field is optional so a `fields` selection can omit anything the caller did not
// ask for while still satisfying the typed output schema. The unfiltered case
// populates every populated field and so matches the historical JSON exactly.
type PullRequestReviewOutput struct {
ID *int64 `json:"id,omitempty"`
State *string `json:"state,omitempty"`
Body *string `json:"body,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
User *MinimalUser `json:"user,omitempty"`
CommitID *string `json:"commit_id,omitempty"`
SubmittedAt *string `json:"submitted_at,omitempty"`
AuthorAssociation *string `json:"author_association,omitempty"`
}

// structuredPullRequestReviewsOutput projects converted reviews onto the selected
// fields so structuredContent matches the filtered text response.
func structuredPullRequestReviewsOutput(reviews []MinimalPullRequestReview, fields []string) []PullRequestReviewOutput {
selected := fieldSelector(fields)
output := make([]PullRequestReviewOutput, 0, len(reviews))
for _, review := range reviews {
item := PullRequestReviewOutput{}
if selected("id") {
item.ID = new(review.ID)
}
if selected("state") {
item.State = new(review.State)
}
if selected("body") && review.Body != "" {
item.Body = new(review.Body)
}
if selected("html_url") {
item.HTMLURL = new(review.HTMLURL)
}
if selected("user") && review.User != nil {
item.User = review.User
}
if selected("commit_id") && review.CommitID != "" {
item.CommitID = new(review.CommitID)
}
if selected("submitted_at") && review.SubmittedAt != "" {
item.SubmittedAt = new(review.SubmittedAt)
}
if selected("author_association") && review.AuthorAssociation != "" {
item.AuthorAssociation = new(review.AuthorAssociation)
}
output = append(output, item)
}
return output
}

// PullRequestCheckRunOutput is the field-projected shape of a single check run.
type PullRequestCheckRunOutput struct {
ID *int64 `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
Status *string `json:"status,omitempty"`
Conclusion *string `json:"conclusion,omitempty"`
HTMLURL *string `json:"html_url,omitempty"`
DetailsURL *string `json:"details_url,omitempty"`
StartedAt *string `json:"started_at,omitempty"`
CompletedAt *string `json:"completed_at,omitempty"`
}

// PullRequestCheckRunsOutput keeps the total_count wrapper. total_count is
// response metadata rather than an item field, so a `fields` selection never
// removes it.
type PullRequestCheckRunsOutput struct {
TotalCount int `json:"total_count"`
CheckRuns []PullRequestCheckRunOutput `json:"check_runs"`
}

// structuredPullRequestCheckRunsOutput projects check runs onto the selected
// fields while always preserving total_count.
func structuredPullRequestCheckRunsOutput(result MinimalCheckRunsResult, fields []string) PullRequestCheckRunsOutput {
selected := fieldSelector(fields)
output := PullRequestCheckRunsOutput{
TotalCount: result.TotalCount,
CheckRuns: make([]PullRequestCheckRunOutput, 0, len(result.CheckRuns)),
}
for _, run := range result.CheckRuns {
item := PullRequestCheckRunOutput{}
if selected("id") {
item.ID = new(run.ID)
}
if selected("name") {
item.Name = new(run.Name)
}
if selected("status") {
item.Status = new(run.Status)
}
if selected("conclusion") && run.Conclusion != "" {
item.Conclusion = new(run.Conclusion)
}
if selected("html_url") && run.HTMLURL != "" {
item.HTMLURL = new(run.HTMLURL)
}
if selected("details_url") && run.DetailsURL != "" {
item.DetailsURL = new(run.DetailsURL)
}
if selected("started_at") && run.StartedAt != "" {
item.StartedAt = new(run.StartedAt)
}
if selected("completed_at") && run.CompletedAt != "" {
item.CompletedAt = new(run.CompletedAt)
}
output.CheckRuns = append(output.CheckRuns, item)
}
return output
}

func pullRequestCommentsOutput(output *IssueReadOutput) *PullRequestReadOutput {
if output == nil || output.Comments == nil {
return nil
Expand All @@ -162,7 +282,7 @@ func pullRequestReadOutputSchema() *jsonschema.Schema {
arrays := []*jsonschema.Schema{
pullRequestOutputSchema[[]MinimalPRFile](),
pullRequestOutputSchema[[]MinimalPullRequestCommit](),
pullRequestOutputSchema[[]MinimalPullRequestReview](),
pullRequestOutputSchema[[]PullRequestReviewOutput](),
pullRequestOutputSchema[[]IssueReadCommentOutput](),
}
for _, array := range arrays {
Expand All @@ -176,7 +296,7 @@ func pullRequestReadOutputSchema() *jsonschema.Schema {
pullRequestOutputSchema[PullRequestDiffOutput](),
pullRequestOutputSchema[MinimalCombinedStatus](),
pullRequestOutputSchema[MinimalReviewThreadsResponse](),
pullRequestOutputSchema[MinimalCheckRunsResult](),
pullRequestOutputSchema[PullRequestCheckRunsOutput](),
&jsonschema.Schema{Type: "array", AnyOf: arrays},
)
}
Expand Down
Loading