feat: Add tools to get and search issues #68

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

Problem

The agent currently has tools to create, close, and comment on issues, but lacks the ability to read existing issues or search/filter issues. This limits its ability to:

  • Reference existing issues when responding to new ones (e.g., "this is a duplicate of #12")
  • Summarize issue threads for context
  • Triage issues by label, author, or state
  • Cross-reference related issues in PR reviews

Implementation Plan

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

Add the following functions:

// Get a single issue (already partially covered by getIssue, but needs to be exported and usable as a tool)

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

export async function listIssues(
  repositoryName: string,
  params: {
    state?: "open" | "closed" | "all";
    labels?: string[];       // comma-separated or filter by label names
    sort?: "created" | "updated" | "comments";
    direction?: "asc" | "desc";
    limit?: number;          // max 100, default 30
    q?: string;              // search query in title/body
  },
) {
  // Build query string from params
  // GET /repos/{repositoryName}/issues?{query}
  // Returns Type.Array(issueSchema)
}

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

2. Schema Updates (src/schemas.ts)

Add parameter schemas for the list/search operations:

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

export const issueSortSchema = Type.Union([
  Type.Literal("created"),
  Type.Literal("updated"),
  Type.Literal("comments"),
]);

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

export const listIssuesParamsSchema = Type.Object({
  state: Type.Optional(issueStateSchema),
  labels: Type.Optional(Type.Array(Type.String())),
  sort: Type.Optional(issueSortSchema),
  direction: Type.Optional(issueDirectionSchema),
  limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100 })),
});

export const searchIssuesParamsSchema = Type.Object({
  state: Type.Optional(issueStateSchema),
  sort: Type.Optional(issueSortSchema),
  direction: Type.Optional(issueDirectionSchema),
});

3. New Tools (src/tools.ts)

Add two new tools:

list-issues — List issues with filters:

export const listIssuesTool = defineTool({
  label: "list-issues",
  name: "list-issues",
  description: "Lists issues in a Forgejo repository with optional filters (state, labels, sort).",
  parameters: Type.Object({
    repository: repositoryFullNameSchema,
    state: Type.Optional(issueStateSchema),
    labels: Type.Optional(Type.Array(Type.String())),
    sort: Type.Optional(issueSortSchema),
    direction: Type.Optional(issueDirectionSchema),
    limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100, default: 30 })),
  }),
  execute: async (_toolCallId, params) => {
    const issues = await listIssues(params.repository, {
      state: params.state,
      labels: params.labels,
      sort: params.sort,
      direction: params.direction,
      limit: params.limit,
    });
    const text = issues.map(i => `#${i.number} [${i.state}] ${i.title}`).join("\n");
    return { content: [{ type: "text", text: issues.length === 0 ? "No issues found." : text }], details: null };
  },
});

search-issues — Search issues by keyword:

export const searchIssuesTool = defineTool({
  label: "search-issues",
  name: "search-issues",
  description: "Searches for issues 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(issueStateSchema),
    sort: Type.Optional(issueSortSchema),
    direction: Type.Optional(issueDirectionSchema),
  }),
  execute: async (_toolCallId, params) => {
    const issues = await searchIssues(params.repository, params.query, {
      state: params.state,
      sort: params.sort,
      direction: params.direction,
    });
    const text = issues.map(i => `#${i.number} [${i.state}] ${i.title}`).join("\n");
    return { content: [{ type: "text", text: issues.length === 0 ? "No issues 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,
  tools.searchIssuesTool,
],

5. Forgejo API Reference

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

See: https://forgejo.gitea.io/api/latest/#tag/repository/operation/reposGetIssues

Example Agent Workflows

Scenario 1: Duplicate detection on new issue

User: "This is a duplicate of #42"
Agent calls: search-issues(repository="owner/repo", query="duplicate of #42", state="closed")
Agent: "I found #42 which appears to be related. Here's the summary..."

Scenario 2: Triage by label

User: "What open issues are labeled bug?"
Agent calls: list-issues(repository="owner/repo", state="open", labels=["bug"], sort="created", direction="desc", limit=20)
Agent: "Found 5 open bug issues..."

Scenario 3: Issue summarization

User: "Summarize the last 10 closed issues"
Agent calls: list-issues(repository="owner/repo", state="closed", sort="updated", direction="desc", limit=10)
Agent: "Here's a summary of the 10 most recently closed issues..."

Files to Modify

File Changes
src/schemas.ts Add issueStateSchema, issueSortSchema, issueDirectionSchema, listIssuesParamsSchema, searchIssuesParamsSchema
src/forgejo/index.ts Add listIssues(), searchIssues() functions
src/tools.ts Add listIssuesTool, searchIssuesTool exports
src/main.ts Register new tools in customTools array

Priority: Medium

Impact: High — enables issue triage, duplicate detection, and cross-referencing across all event types.

## Problem The agent currently has tools to create, close, and comment on issues, but lacks the ability to **read existing issues** or **search/filter issues**. This limits its ability to: - Reference existing issues when responding to new ones (e.g., "this is a duplicate of #12") - Summarize issue threads for context - Triage issues by label, author, or state - Cross-reference related issues in PR reviews ## Implementation Plan ### 1. Forgejo API Endpoints (`src/forgejo/index.ts`) Add the following functions: ```typescript // Get a single issue (already partially covered by getIssue, but needs to be exported and usable as a tool) // List/search issues — the Forgejo API endpoint: // GET /repos/{owner}/{repo}/issues?{query_params} // Supports query params: state, labels, sort, direction, limit export async function listIssues( repositoryName: string, params: { state?: "open" | "closed" | "all"; labels?: string[]; // comma-separated or filter by label names sort?: "created" | "updated" | "comments"; direction?: "asc" | "desc"; limit?: number; // max 100, default 30 q?: string; // search query in title/body }, ) { // Build query string from params // GET /repos/{repositoryName}/issues?{query} // Returns Type.Array(issueSchema) } // Search issues by keyword — Forgejo supports full-text search: // GET /repos/{owner}/{repo}/issues/search?q={query} export async function searchIssues( repositoryName: string, query: string, params?: { state?: "open" | "closed" | "all"; sort?: "created" | "updated" | "comments"; direction?: "asc" | "desc"; }, ) { // GET /repos/{repositoryName}/issues/search?q={query} // Returns Type.Array(issueSchema) } ``` ### 2. Schema Updates (`src/schemas.ts`) Add parameter schemas for the list/search operations: ```typescript export const issueStateSchema = Type.Union([ Type.Literal("open"), Type.Literal("closed"), Type.Literal("all"), ]); export const issueSortSchema = Type.Union([ Type.Literal("created"), Type.Literal("updated"), Type.Literal("comments"), ]); export const issueDirectionSchema = Type.Union([ Type.Literal("asc"), Type.Literal("desc"), ]); export const listIssuesParamsSchema = Type.Object({ state: Type.Optional(issueStateSchema), labels: Type.Optional(Type.Array(Type.String())), sort: Type.Optional(issueSortSchema), direction: Type.Optional(issueDirectionSchema), limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100 })), }); export const searchIssuesParamsSchema = Type.Object({ state: Type.Optional(issueStateSchema), sort: Type.Optional(issueSortSchema), direction: Type.Optional(issueDirectionSchema), }); ``` ### 3. New Tools (`src/tools.ts`) Add two new tools: **`list-issues`** — List issues with filters: ```typescript export const listIssuesTool = defineTool({ label: "list-issues", name: "list-issues", description: "Lists issues in a Forgejo repository with optional filters (state, labels, sort).", parameters: Type.Object({ repository: repositoryFullNameSchema, state: Type.Optional(issueStateSchema), labels: Type.Optional(Type.Array(Type.String())), sort: Type.Optional(issueSortSchema), direction: Type.Optional(issueDirectionSchema), limit: Type.Optional(Type.Number({ minimum: 1, maximum: 100, default: 30 })), }), execute: async (_toolCallId, params) => { const issues = await listIssues(params.repository, { state: params.state, labels: params.labels, sort: params.sort, direction: params.direction, limit: params.limit, }); const text = issues.map(i => `#${i.number} [${i.state}] ${i.title}`).join("\n"); return { content: [{ type: "text", text: issues.length === 0 ? "No issues found." : text }], details: null }; }, }); ``` **`search-issues`** — Search issues by keyword: ```typescript export const searchIssuesTool = defineTool({ label: "search-issues", name: "search-issues", description: "Searches for issues 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(issueStateSchema), sort: Type.Optional(issueSortSchema), direction: Type.Optional(issueDirectionSchema), }), execute: async (_toolCallId, params) => { const issues = await searchIssues(params.repository, params.query, { state: params.state, sort: params.sort, direction: params.direction, }); const text = issues.map(i => `#${i.number} [${i.state}] ${i.title}`).join("\n"); return { content: [{ type: "text", text: issues.length === 0 ? "No issues 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, tools.searchIssuesTool, ], ``` ### 5. Forgejo API Reference | Endpoint | Method | Description | |----------|--------|-------------| | `/repos/{owner}/{repo}/issues` | GET | List repo issues (supports `state`, `labels`, `sort`, `direction`, `limit`) | | `/repos/{owner}/{repo}/issues/search?q={query}` | GET | Full-text search of issues | See: https://forgejo.gitea.io/api/latest/#tag/repository/operation/reposGetIssues ## Example Agent Workflows ### Scenario 1: Duplicate detection on new issue ``` User: "This is a duplicate of #42" Agent calls: search-issues(repository="owner/repo", query="duplicate of #42", state="closed") Agent: "I found #42 which appears to be related. Here's the summary..." ``` ### Scenario 2: Triage by label ``` User: "What open issues are labeled bug?" Agent calls: list-issues(repository="owner/repo", state="open", labels=["bug"], sort="created", direction="desc", limit=20) Agent: "Found 5 open bug issues..." ``` ### Scenario 3: Issue summarization ``` User: "Summarize the last 10 closed issues" Agent calls: list-issues(repository="owner/repo", state="closed", sort="updated", direction="desc", limit=10) Agent: "Here's a summary of the 10 most recently closed issues..." ``` ## Files to Modify | File | Changes | |------|---------| | `src/schemas.ts` | Add `issueStateSchema`, `issueSortSchema`, `issueDirectionSchema`, `listIssuesParamsSchema`, `searchIssuesParamsSchema` | | `src/forgejo/index.ts` | Add `listIssues()`, `searchIssues()` functions | | `src/tools.ts` | Add `listIssuesTool`, `searchIssuesTool` exports | | `src/main.ts` | Register new tools in `customTools` array | ## Priority: Medium **Impact:** High — enables issue triage, duplicate detection, and cross-referencing across all event types.
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#68
No description provided.