import { z } from "zod";
import { AdoClient } from "../../ado-client.js";
import * as GitInterfaces from "azure-devops-node-api/interfaces/GitInterfaces";
export const getPullRequestSchema = z.object({
project: z.string().optional().describe("Project name, defaults to ADO_PROJECT env var"),
repository: z.string().describe("Repository name or ID"),
pullRequestId: z.number().describe("Pull request ID"),
includeCommits: z.boolean().default(true).describe("Include commits, default true"),
includeWorkItems: z.boolean().default(true).describe("Include linked work items, default true"),
});
export const getPullRequestTool = {
name: "get_pull_request",
description: "Get complete details for a pull request by ID. Returns title, description, status, reviewers with vote status, merge status, commits included, and linked work items. Use this to review PR details, check approval status, or see what changes are included.",
inputSchema: {
type: "object" as const,
properties: {
project: {
type: "string",
description: "Project name, defaults to ADO_PROJECT env var",
},
repository: {
type: "string",
description: "Repository name or ID",
},
pullRequestId: {
type: "number",
description: "Pull request ID",
},
includeCommits: {
type: "boolean",
description: "Include commits, default true",
},
includeWorkItems: {
type: "boolean",
description: "Include linked work items, default true",
},
},
required: ["repository", "pullRequestId"],
},
};
export interface PullRequestDetails {
id: number;
title: string;
description?: string;
status: string;
createdBy: string;
creationDate: string;
closedDate?: string;
sourceBranch: string;
targetBranch: string;
isDraft: boolean;
mergeStatus?: string;
autoCompleteSetBy?: string;
reviewers: Array<{
id: string;
displayName: string;
vote: number;
isRequired: boolean;
}>;
commits?: Array<{
commitId: string;
message: string;
author: string;
}>;
workItems?: Array<{
id: number;
title: string;
state: string;
}>;
url: string;
}
export async function getPullRequest(
client: AdoClient,
params: z.infer<typeof getPullRequestSchema>
): Promise<PullRequestDetails> {
const validatedParams = getPullRequestSchema.parse(params);
const project = client.resolveProject(validatedParams.project);
const gitApi = await client.getGitApi();
const pr = await gitApi.getPullRequest(
validatedParams.repository,
validatedParams.pullRequestId,
project
);
if (!pr) {
throw new Error(`Pull request ${validatedParams.pullRequestId} not found`);
}
const result: PullRequestDetails = {
id: pr.pullRequestId || 0,
title: pr.title || "",
description: pr.description,
status: getStatusString(pr.status),
createdBy: pr.createdBy?.displayName || "",
creationDate: pr.creationDate?.toISOString() || "",
closedDate: pr.closedDate?.toISOString(),
sourceBranch: pr.sourceRefName?.replace("refs/heads/", "") || "",
targetBranch: pr.targetRefName?.replace("refs/heads/", "") || "",
isDraft: pr.isDraft || false,
mergeStatus: getMergeStatusString(pr.mergeStatus),
autoCompleteSetBy: pr.autoCompleteSetBy?.displayName,
reviewers: (pr.reviewers || []).map((r) => ({
id: r.id || "",
displayName: r.displayName || "",
vote: r.vote || 0,
isRequired: r.isRequired || false,
})),
url: pr.url || "",
};
// Get commits if requested
if (validatedParams.includeCommits) {
const commits = await gitApi.getPullRequestCommits(
validatedParams.repository,
validatedParams.pullRequestId,
project
);
if (commits) {
result.commits = commits.map((c) => ({
commitId: c.commitId || "",
message: c.comment || "",
author: c.author?.name || "",
}));
}
}
// Get work items if requested
if (validatedParams.includeWorkItems) {
const workItemRefs = await gitApi.getPullRequestWorkItemRefs(
validatedParams.repository,
validatedParams.pullRequestId,
project
);
if (workItemRefs && workItemRefs.length > 0) {
const witApi = await client.getWorkItemTrackingApi();
const ids = workItemRefs
.filter((ref) => ref.id)
.map((ref) => parseInt(ref.id!, 10));
if (ids.length > 0) {
const workItems = await witApi.getWorkItems(ids, [
"System.Title",
"System.State",
]);
if (workItems) {
result.workItems = workItems
.filter((wi) => wi && wi.fields)
.map((wi) => ({
id: wi.id || 0,
title: wi.fields!["System.Title"] || "",
state: wi.fields!["System.State"] || "",
}));
}
}
}
}
return result;
}
function getStatusString(status?: GitInterfaces.PullRequestStatus): string {
switch (status) {
case GitInterfaces.PullRequestStatus.Active:
return "active";
case GitInterfaces.PullRequestStatus.Completed:
return "completed";
case GitInterfaces.PullRequestStatus.Abandoned:
return "abandoned";
default:
return "unknown";
}
}
function getMergeStatusString(status?: GitInterfaces.PullRequestAsyncStatus): string | undefined {
switch (status) {
case GitInterfaces.PullRequestAsyncStatus.Conflicts:
return "conflicts";
case GitInterfaces.PullRequestAsyncStatus.Failure:
return "failure";
case GitInterfaces.PullRequestAsyncStatus.NotSet:
return "notSet";
case GitInterfaces.PullRequestAsyncStatus.Queued:
return "queued";
case GitInterfaces.PullRequestAsyncStatus.RejectedByPolicy:
return "rejectedByPolicy";
case GitInterfaces.PullRequestAsyncStatus.Succeeded:
return "succeeded";
default:
return undefined;
}
}