feat: Add tools to get and search pull requests #69

Open
opened 2026-06-20 16:22:44 +00:00 by llm-bot · 0 comments
Member

Problem

The agent currently has tools to create PRs but lacks the ability to read existing PRs or search/filter PRs. This limits its ability to:

  • Check the status of related PRs when reviewing a new one
  • Find PRs that might conflict with changes
  • Summarize PR activity for triage
  • Cross-reference related PRs in issue responses

Implementation Plan

1. Forgejo API Endpoints (src/forgejo/index.ts)

Add the following functions:

// List/search pull requests — Forgejo API endpoint:
// GET /repos/{owner}/{repo}/pulls?{query_params}
// Supports query params: state, sort, direction, limit

export async function listPullRequests(
  repositoryName: string,
  params: {
    state?: "open" | "closed" | "all";
    sort?: "created" | "updated" | "comments" | "popularity" | "long-running";
    direction?: "asc" | "desc";
    limit?: number;          // max 100, default 30
    head?: string;           // filter by head user/repo:branch
    base?: string;           // filter by base branch
    q?: string;              // search query in title/body
  },
) {
  // Build query string from params
  // GET /repos/{repositoryName}/pulls?{query}
  // Returns Type.Array(pullRequestSchema)
}

// Search PRs by keyword — Forgejo full-text search:
// GET /repos/{owner}/{repo}/pulls/search?q={query}
export async function searchPullRequests(
  repositoryName: string,
  query: string,
  params?: {
    state?: "open" | "closed" | "all";
    sort?: "created" | "updated" | "comments" | "popularity" | "long-running";
    direction?: "asc" | "desc";
  },
) {
  // GET /repos/{repositoryName}/pulls/search?q={query}
  // Returns Type.Array(pullRequestSchema)
}

2. Schema Updates (src/schemas.ts)

Add parameter schemas for the list/search operations:

export const prStateSchema = Type.Union([
  Type.Literal("open"),
  Type.Literal("closed"),
  Type.Literal("all"),
]);

export const prSortSchema = Type.Union([
  Type.Literal("created"),
  Type.Literal("updated"),
  Type.Literal("comments"),
  Type.Literal("popularity"),
  Type.Literal("long-running"),
]);

export const prDirectionSchema = Type.Union([
  Type.Literal("asc"),
  Type.Literal("desc"),
]);

export const listPullRequestsParamsSchema = Type.Object({
  state: Type.Optional(prStateSchema),
  sort: Type.Optional(prSortSchema),
  direction: Type.Optional(prDirectionSchema),
  limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100 })),
  head: Type.Optional(Type.String()),
  base: Type.Optional(Type.String()),
});

export const searchPullRequestsParamsSchema = Type.Object({
  state: Type.Optional(prStateSchema),
  sort: Type.Optional(prSortSchema),
  direction: Type.Optional(prDirectionSchema),
});

3. New Tools (src/tools.ts)

Add two new tools:

list-pull-requests — List PRs with filters:

export const listPullRequestsTool = defineTool({
  label: "list-pull-requests",
  name: "list-pull-requests",
  description: "Lists pull requests in a Forgejo repository with optional filters (state, sort, head, base).",
  parameters: Type.Object({
    repository: repositoryFullNameSchema,
    state: Type.Optional(prStateSchema),
    sort: Type.Optional(prSortSchema),
    direction: Type.Optional(prDirectionSchema),
    limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100, default: 30 })),
    head: Type.Optional(Type.String({ description: "Filter by head branch (user/repo:branch)" })),
    base: Type.Optional(Type.String({ description: "Filter by base branch name" })),
  }),
  execute: async (_toolCallId, params) => {
    const prs = await listPullRequests(params.repository, {
      state: params.state,
      sort: params.sort,
      direction: params.direction,
      limit: params.limit,
      head: params.head,
      base: params.base,
    });
    const text = prs.map(p => `#${p.number} [${p.state}] ${p.title} (${p.head.label}${p.base.label})`).join("\n");
    return { content: [{ type: "text", text: prs.length === 0 ? "No pull requests found." : text }], details: null };
  },
});

search-pull-requests — Search PRs by keyword:

export const searchPullRequestsTool = defineTool({
  label: "search-pull-requests",
  name: "search-pull-requests",
  description: "Searches for pull requests in a Forgejo repository by keyword in title or body.",
  parameters: Type.Object({
    repository: repositoryFullNameSchema,
    query: Type.String({ description: "Search query string (title/body)" }),
    state: Type.Optional(prStateSchema),
    sort: Type.Optional(prSortSchema),
    direction: Type.Optional(prDirectionSchema),
  }),
  execute: async (_toolCallId, params) => {
    const prs = await searchPullRequests(params.repository, params.query, {
      state: params.state,
      sort: params.sort,
      direction: params.direction,
    });
    const text = prs.map(p => `#${p.number} [${p.state}] ${p.title} (${p.head.label}${p.base.label})`).join("\n");
    return { content: [{ type: "text", text: prs.length === 0 ? "No pull requests found." : text }], details: null };
  },
});

4. Register Tools (src/main.ts)

Add to customTools array:

customTools: [
  tools.createIssue,
  tools.closeIssue,
  tools.createIssueComment,
  tools.createPullRequest,
  tools.listIssuesTool,       // from #68
  tools.searchIssuesTool,     // from #68
  tools.listPullRequestsTool,
  tools.searchPullRequestsTool,
],

5. Forgejo API Reference

Endpoint Method Description
/repos/{owner}/{repo}/pulls GET List repo PRs (supports state, sort, direction, limit, head, base)
/repos/{owner}/{repo}/pulls/search?q={query} GET Full-text search of PRs

See: https://forgejo.gitea.io/api/latest/#tag/pull/operation/reposGetPullRequests

Example Agent Workflows

User: "Is there already a PR for this fix?"
Agent calls: search-pull-requests(repository="owner/repo", query="fix memory leak", state="open")
Agent: "Found #23 which appears to address the same issue..."

Scenario 2: Find PRs targeting a specific branch

User: "What PRs are targeting main?"
Agent calls: list-pull-requests(repository="owner/repo", base="main", state="open", sort="updated", direction="desc", limit=20)
Agent: "Found 3 open PRs targeting main..."

Scenario 3: PR triage dashboard

User: "Show me all long-running open PRs"
Agent calls: list-pull-requests(repository="owner/repo", state="open", sort="long-running", direction="asc", limit=10)
Agent: "Here are the 5 longest-running open PRs..."

Scenario 4: Cross-reference in issue response

User: "Any PRs related to issue #42?"
Agent calls: search-pull-requests(repository="owner/repo", query="#42", state="open")
Agent: "Found #55 which references issue #42 in its description..."

Files to Modify

File Changes
src/schemas.ts Add prStateSchema, prSortSchema, prDirectionSchema, listPullRequestsParamsSchema, searchPullRequestsParamsSchema
src/forgejo/index.ts Add listPullRequests(), searchPullRequests() functions
src/tools.ts Add listPullRequestsTool, searchPullRequestsTool exports
src/main.ts Register new tools in customTools array

Priority: Medium

Impact: High — enables PR discovery, conflict detection, and cross-referencing across all event types. Pairs well with the issue search tools (#68).

## Problem The agent currently has tools to create PRs but lacks the ability to **read existing PRs** or **search/filter PRs**. This limits its ability to: - Check the status of related PRs when reviewing a new one - Find PRs that might conflict with changes - Summarize PR activity for triage - Cross-reference related PRs in issue responses ## Implementation Plan ### 1. Forgejo API Endpoints (`src/forgejo/index.ts`) Add the following functions: ```typescript // List/search pull requests — Forgejo API endpoint: // GET /repos/{owner}/{repo}/pulls?{query_params} // Supports query params: state, sort, direction, limit export async function listPullRequests( repositoryName: string, params: { state?: "open" | "closed" | "all"; sort?: "created" | "updated" | "comments" | "popularity" | "long-running"; direction?: "asc" | "desc"; limit?: number; // max 100, default 30 head?: string; // filter by head user/repo:branch base?: string; // filter by base branch q?: string; // search query in title/body }, ) { // Build query string from params // GET /repos/{repositoryName}/pulls?{query} // Returns Type.Array(pullRequestSchema) } // Search PRs by keyword — Forgejo full-text search: // GET /repos/{owner}/{repo}/pulls/search?q={query} export async function searchPullRequests( repositoryName: string, query: string, params?: { state?: "open" | "closed" | "all"; sort?: "created" | "updated" | "comments" | "popularity" | "long-running"; direction?: "asc" | "desc"; }, ) { // GET /repos/{repositoryName}/pulls/search?q={query} // Returns Type.Array(pullRequestSchema) } ``` ### 2. Schema Updates (`src/schemas.ts`) Add parameter schemas for the list/search operations: ```typescript export const prStateSchema = Type.Union([ Type.Literal("open"), Type.Literal("closed"), Type.Literal("all"), ]); export const prSortSchema = Type.Union([ Type.Literal("created"), Type.Literal("updated"), Type.Literal("comments"), Type.Literal("popularity"), Type.Literal("long-running"), ]); export const prDirectionSchema = Type.Union([ Type.Literal("asc"), Type.Literal("desc"), ]); export const listPullRequestsParamsSchema = Type.Object({ state: Type.Optional(prStateSchema), sort: Type.Optional(prSortSchema), direction: Type.Optional(prDirectionSchema), limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100 })), head: Type.Optional(Type.String()), base: Type.Optional(Type.String()), }); export const searchPullRequestsParamsSchema = Type.Object({ state: Type.Optional(prStateSchema), sort: Type.Optional(prSortSchema), direction: Type.Optional(prDirectionSchema), }); ``` ### 3. New Tools (`src/tools.ts`) Add two new tools: **`list-pull-requests`** — List PRs with filters: ```typescript export const listPullRequestsTool = defineTool({ label: "list-pull-requests", name: "list-pull-requests", description: "Lists pull requests in a Forgejo repository with optional filters (state, sort, head, base).", parameters: Type.Object({ repository: repositoryFullNameSchema, state: Type.Optional(prStateSchema), sort: Type.Optional(prSortSchema), direction: Type.Optional(prDirectionSchema), limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100, default: 30 })), head: Type.Optional(Type.String({ description: "Filter by head branch (user/repo:branch)" })), base: Type.Optional(Type.String({ description: "Filter by base branch name" })), }), execute: async (_toolCallId, params) => { const prs = await listPullRequests(params.repository, { state: params.state, sort: params.sort, direction: params.direction, limit: params.limit, head: params.head, base: params.base, }); const text = prs.map(p => `#${p.number} [${p.state}] ${p.title} (${p.head.label} → ${p.base.label})`).join("\n"); return { content: [{ type: "text", text: prs.length === 0 ? "No pull requests found." : text }], details: null }; }, }); ``` **`search-pull-requests`** — Search PRs by keyword: ```typescript export const searchPullRequestsTool = defineTool({ label: "search-pull-requests", name: "search-pull-requests", description: "Searches for pull requests in a Forgejo repository by keyword in title or body.", parameters: Type.Object({ repository: repositoryFullNameSchema, query: Type.String({ description: "Search query string (title/body)" }), state: Type.Optional(prStateSchema), sort: Type.Optional(prSortSchema), direction: Type.Optional(prDirectionSchema), }), execute: async (_toolCallId, params) => { const prs = await searchPullRequests(params.repository, params.query, { state: params.state, sort: params.sort, direction: params.direction, }); const text = prs.map(p => `#${p.number} [${p.state}] ${p.title} (${p.head.label} → ${p.base.label})`).join("\n"); return { content: [{ type: "text", text: prs.length === 0 ? "No pull requests found." : text }], details: null }; }, }); ``` ### 4. Register Tools (`src/main.ts`) Add to `customTools` array: ```typescript customTools: [ tools.createIssue, tools.closeIssue, tools.createIssueComment, tools.createPullRequest, tools.listIssuesTool, // from #68 tools.searchIssuesTool, // from #68 tools.listPullRequestsTool, tools.searchPullRequestsTool, ], ``` ### 5. Forgejo API Reference | Endpoint | Method | Description | |----------|--------|-------------| | `/repos/{owner}/{repo}/pulls` | GET | List repo PRs (supports `state`, `sort`, `direction`, `limit`, `head`, `base`) | | `/repos/{owner}/{repo}/pulls/search?q={query}` | GET | Full-text search of PRs | See: https://forgejo.gitea.io/api/latest/#tag/pull/operation/reposGetPullRequests ## Example Agent Workflows ### Scenario 1: Check related PRs during review ``` User: "Is there already a PR for this fix?" Agent calls: search-pull-requests(repository="owner/repo", query="fix memory leak", state="open") Agent: "Found #23 which appears to address the same issue..." ``` ### Scenario 2: Find PRs targeting a specific branch ``` User: "What PRs are targeting main?" Agent calls: list-pull-requests(repository="owner/repo", base="main", state="open", sort="updated", direction="desc", limit=20) Agent: "Found 3 open PRs targeting main..." ``` ### Scenario 3: PR triage dashboard ``` User: "Show me all long-running open PRs" Agent calls: list-pull-requests(repository="owner/repo", state="open", sort="long-running", direction="asc", limit=10) Agent: "Here are the 5 longest-running open PRs..." ``` ### Scenario 4: Cross-reference in issue response ``` User: "Any PRs related to issue #42?" Agent calls: search-pull-requests(repository="owner/repo", query="#42", state="open") Agent: "Found #55 which references issue #42 in its description..." ``` ## Files to Modify | File | Changes | |------|---------| | `src/schemas.ts` | Add `prStateSchema`, `prSortSchema`, `prDirectionSchema`, `listPullRequestsParamsSchema`, `searchPullRequestsParamsSchema` | | `src/forgejo/index.ts` | Add `listPullRequests()`, `searchPullRequests()` functions | | `src/tools.ts` | Add `listPullRequestsTool`, `searchPullRequestsTool` exports | | `src/main.ts` | Register new tools in `customTools` array | ## Priority: Medium **Impact:** High — enables PR discovery, conflict detection, and cross-referencing across all event types. Pairs well with the issue search tools (#68).
Sign in to join this conversation.
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
projects/forgejo-agent#69
No description provided.