import { writeFileSync } from "node:fs"; import { join } from "node:path"; import { type } from "arktype"; import { stripExistingFooter } from "../utils/buildShockbotFooter.ts"; import { log } from "../utils/log.ts"; import type { ToolContext } from "./server.ts"; import { execute, tool } from "./shared.ts"; interface GiteaReview { id: number; user?: { login: string }; state?: string; body?: string; submitted_at?: string; commit_id?: string } interface GiteaReviewComment { id: number; body?: string; path?: string; diff_hunk?: string; original_position?: number; position?: number; pull_request_review_id?: number; user?: { login: string } } export const GetReviewComments = type({ pull_number: type.number }); export function GetReviewCommentsTool(ctx: ToolContext) { return tool({ name: "get_review_comments", description: "Get all inline review comments for a pull request. Example: `get_review_comments({ pull_number: 1234 })`.", parameters: GetReviewComments, execute: execute(async ({ pull_number }) => { ctx.toolState.issueNumber = pull_number; const reviewsR = await ctx.gitea.request( "GET /repos/{owner}/{repo}/pulls/{index}/reviews", { owner: ctx.repo.owner, repo: ctx.repo.name, index: pull_number, limit: 50 } ); const reviews = reviewsR.data as GiteaReview[]; const allComments: GiteaReviewComment[] = []; for (const review of reviews) { if (!review.id) continue; try { const cr = await ctx.gitea.request( "GET /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments", { owner: ctx.repo.owner, repo: ctx.repo.name, index: pull_number, id: review.id } ); allComments.push(...(cr.data as GiteaReviewComment[])); } catch { /* best-effort */ } } const tempDir = process.env.SHOCKBOT_TEMP_DIR; let filePath: string | undefined; const rendered = allComments.map((c) => ({ id: c.id, path: c.path, line: c.original_position ?? c.position, side: "RIGHT" as const, body: stripExistingFooter(c.body ?? ""), author: c.user?.login, diffHunk: c.diff_hunk, reviewId: c.pull_request_review_id, })); if (tempDir && rendered.length > 0) { filePath = join(tempDir, `pr-${pull_number}-review-comments.json`); writeFileSync(filePath, JSON.stringify(rendered, null, 2)); log.debug(`wrote review comments to ${filePath}`); } return { pull_number, comments: rendered, count: rendered.length, ...(filePath ? { filePath } : {}) }; }), }); } export const ListPullRequestReviews = type({ pull_number: type.number }); export function ListPullRequestReviewsTool(ctx: ToolContext) { return tool({ name: "list_pull_request_reviews", description: "List all reviews submitted on a pull request. Example: `list_pull_request_reviews({ pull_number: 1234 })`.", parameters: ListPullRequestReviews, execute: execute(async ({ pull_number }) => { const r = await ctx.gitea.request( "GET /repos/{owner}/{repo}/pulls/{index}/reviews", { owner: ctx.repo.owner, repo: ctx.repo.name, index: pull_number, limit: 50 } ); const reviews = r.data as GiteaReview[]; return { pull_number, reviews: reviews.map((r) => ({ id: r.id, user: r.user?.login, state: r.state, body: r.body, submitted_at: r.submitted_at, commit_id: r.commit_id })), count: reviews.length, }; }), }); }