346 lines · 12.9 KB
import { z } from "zod";
import { batch, run, stmt } from "../../db";
import { randomId } from "../../crypto";
import { badRequest, conflict, forbidden, HttpError, notFoundError } from "../../errors";
import { createPull, getPull, listPulls, mergePull, updatePullBranch } from "../../api/pulls";
import { headBranchConflicts, requireHeadPush, updateHeadBranch } from "../../pull-merge";
import { pullDiff } from "../../pull-files";
import { loadPull, MergeError } from "../../pulls";
import type { PullRow } from "../../pulls";
import { pullUrl } from "../../pull-access";
import { hasPermission, loadRepoById } from "../../repos";
import { scopedRepoPermission } from "../../token-scope";
import { kickMergeAutomation } from "../../merge-automation";
import { emitNotification, emitWebhook } from "../../events/emit";
import { limitHeavy, limitWrite } from "../../limits";
import { markdownBody, title } from "~/lib/validation";
import type { McpCaller } from "../protocol";
import { apiCtx, apiJson, attributionFooter, defineTool, repoArgs, scopedRepo, truncateText } from "./base";

const number = z.number().int().positive();
const MAX_PATCH_CHARS = 48 * 1024;
const MAX_RESOLVED_CHARS = 1024 * 1024;
const HTTP_CONFLICT = 409;

async function pullFor(
  caller: McpCaller,
  ownerName: string,
  repoName: string,
  pullNumber: number,
  needed: "read" | "write",
) {
  const scoped = await scopedRepo(caller, ownerName, repoName, "pulls", needed);
  const pull = await loadPull(scoped.repo.id, pullNumber);
  if (!pull) throw notFoundError("Pull request not found");
  const headRepo = pull.head_repo_id === scoped.repo.id ? scoped.repo : await loadRepoById(pull.head_repo_id);
  if (!headRepo) throw notFoundError("Head repository was deleted");
  return { ...scoped, pull, headRepo };
}

const listPullsTool = defineTool({