opencode-gitlab-plugin 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
4
4
 
5
+ ## [2.6.0](https://gitlab.com/vglafirov/opencode-gitlab-plugin/compare/v2.5.0...v2.6.0) (2026-05-20)
6
+
7
+
8
+ ### ✨ Features
9
+
10
+ * **pagination:** expose pagination info in list MR and issue responses ([1c1722e](https://gitlab.com/vglafirov/opencode-gitlab-plugin/commit/1c1722e62e0e41f00ab5409057bea1e76ec17a8d))
11
+
12
+
13
+ ### ♻️ Code Refactoring
14
+
15
+ * **client:** address review feedback on !23 ([2e94e46](https://gitlab.com/vglafirov/opencode-gitlab-plugin/commit/2e94e46706807bc8797f10884f71fc56ecf967b9)), closes [23#note_3135645102](https://gitlab.com/vglafirov/23/issues/note_3135645102) [23#note_3135645423](https://gitlab.com/vglafirov/23/issues/note_3135645423) [23#note_3135644923](https://gitlab.com/vglafirov/23/issues/note_3135644923)
16
+
5
17
  ## [2.5.0](https://gitlab.com/vglafirov/opencode-gitlab-plugin/compare/v2.4.0...v2.5.0) (2026-05-20)
6
18
 
7
19
 
package/dist/index.js CHANGED
@@ -26,7 +26,13 @@ var GitLabApiClient = class {
26
26
  }
27
27
  return projectId;
28
28
  }
29
- async fetch(method, path2, body) {
29
+ /**
30
+ * Core HTTP request used by all REST helpers.
31
+ *
32
+ * Returns the raw `Response` after validating the status code so callers can
33
+ * inspect headers (e.g. for pagination) before parsing the body.
34
+ */
35
+ async rawFetch(method, path2, body) {
30
36
  const url = `${this.instanceUrl}/api/v4${path2}`;
31
37
  const response = await fetch(url, {
32
38
  method,
@@ -37,12 +43,53 @@ var GitLabApiClient = class {
37
43
  const errorText = await response.text();
38
44
  throw new Error(`GitLab API error ${response.status}: ${errorText}`);
39
45
  }
46
+ return response;
47
+ }
48
+ async fetch(method, path2, body) {
49
+ const response = await this.rawFetch(method, path2, body);
40
50
  const text = await response.text();
41
51
  if (!text) {
42
52
  return {};
43
53
  }
44
54
  return JSON.parse(text);
45
55
  }
56
+ /**
57
+ * Parse a numeric pagination header, guarding against empty strings.
58
+ *
59
+ * GitLab returns `""` (rather than omitting the header) for `X-Next-Page` and
60
+ * `X-Prev-Page` when there is no next/prev page, and bare `parseInt` without
61
+ * a radix on an empty string yields `NaN`. Treat empty/missing as `undefined`.
62
+ */
63
+ parsePaginationHeader(response, name) {
64
+ const raw = response.headers.get(name);
65
+ if (raw == null || raw === "") return void 0;
66
+ const value = parseInt(raw, 10);
67
+ return Number.isNaN(value) ? void 0 : value;
68
+ }
69
+ /**
70
+ * Pagination info extracted from GitLab API response headers
71
+ */
72
+ extractPaginationInfo(response) {
73
+ return {
74
+ total: this.parsePaginationHeader(response, "X-Total"),
75
+ totalPages: this.parsePaginationHeader(response, "X-Total-Pages"),
76
+ page: this.parsePaginationHeader(response, "X-Page"),
77
+ perPage: this.parsePaginationHeader(response, "X-Per-Page"),
78
+ nextPage: this.parsePaginationHeader(response, "X-Next-Page"),
79
+ prevPage: this.parsePaginationHeader(response, "X-Prev-Page")
80
+ };
81
+ }
82
+ /**
83
+ * Fetch with pagination info from response headers.
84
+ * Use this for list endpoints where knowing if more data exists is important.
85
+ */
86
+ async fetchWithPagination(method, path2, body) {
87
+ const response = await this.rawFetch(method, path2, body);
88
+ const text = await response.text();
89
+ const data = text ? JSON.parse(text) : [];
90
+ const pagination = this.extractPaginationInfo(response);
91
+ return { data, pagination };
92
+ }
46
93
  async fetchText(method, path2) {
47
94
  const url = `${this.instanceUrl}/api/v4${path2}`;
48
95
  const response = await fetch(url, {
@@ -306,6 +353,7 @@ var MergeRequestsClient = class extends GitLabApiClient {
306
353
  async listMergeRequests(options) {
307
354
  const params = new URLSearchParams();
308
355
  params.set("per_page", String(options.limit || 20));
356
+ if (options.page) params.set("page", String(options.page));
309
357
  if (options.state) params.set("state", options.state);
310
358
  if (options.scope) params.set("scope", options.scope);
311
359
  if (options.search) params.set("search", options.search);
@@ -317,7 +365,7 @@ var MergeRequestsClient = class extends GitLabApiClient {
317
365
  } else {
318
366
  path2 = `/merge_requests?${params}`;
319
367
  }
320
- return this.fetch("GET", path2);
368
+ return this.fetchWithPagination("GET", path2);
321
369
  }
322
370
  async getMrChanges(projectId, mrIid) {
323
371
  const encodedProject = this.encodeProjectId(projectId);
@@ -729,6 +777,7 @@ var IssuesClient = class extends GitLabApiClient {
729
777
  async listIssues(options) {
730
778
  const params = new URLSearchParams();
731
779
  params.set("per_page", String(options.limit || 20));
780
+ if (options.page) params.set("page", String(options.page));
732
781
  if (options.state) params.set("state", options.state);
733
782
  if (options.scope) params.set("scope", options.scope);
734
783
  if (options.search) params.set("search", options.search);
@@ -741,7 +790,7 @@ var IssuesClient = class extends GitLabApiClient {
741
790
  } else {
742
791
  path2 = `/issues?${params}`;
743
792
  }
744
- return this.fetch("GET", path2);
793
+ return this.fetchWithPagination("GET", path2);
745
794
  }
746
795
  /**
747
796
  * List notes on an issue using GraphQL API with pagination support
@@ -2647,26 +2696,31 @@ Returns: title, description, state, author, assignees, reviewers, labels, diff s
2647
2696
  }),
2648
2697
  gitlab_list_merge_requests: tool({
2649
2698
  description: `List merge requests for a project or search globally.
2650
- Can filter by state (opened, closed, merged, all), scope (assigned_to_me, created_by_me), and labels.`,
2699
+ Can filter by state (opened, closed, merged, all), scope (assigned_to_me, created_by_me), and labels.
2700
+
2701
+ IMPORTANT: Response includes pagination info. If nextPage exists, there are more results.
2702
+ Always paginate until you get an empty response or no nextPage to ensure you have all data.`,
2651
2703
  args: {
2652
2704
  project_id: z.string().optional().describe("The project ID or path. If not provided, searches globally."),
2653
2705
  state: z.enum(["opened", "closed", "merged", "all"]).optional().describe("Filter by MR state (default: opened)"),
2654
2706
  scope: z.enum(["assigned_to_me", "created_by_me", "all"]).optional().describe("Filter by scope"),
2655
2707
  search: z.string().optional().describe("Search MRs by title or description"),
2656
2708
  labels: z.string().optional().describe("Comma-separated list of labels to filter by"),
2657
- limit: z.number().optional().describe("Maximum number of results (default: 20)")
2709
+ limit: z.number().optional().describe("Maximum number of results per page (default: 20)"),
2710
+ page: z.number().optional().describe("Page number for pagination (default: 1)")
2658
2711
  },
2659
2712
  execute: async (args, _ctx) => {
2660
2713
  const client = getGitLabClient();
2661
- const mrs = await client.listMergeRequests({
2714
+ const result = await client.listMergeRequests({
2662
2715
  projectId: args.project_id,
2663
2716
  state: args.state,
2664
2717
  scope: args.scope,
2665
2718
  search: args.search,
2666
2719
  labels: args.labels,
2667
- limit: args.limit
2720
+ limit: args.limit,
2721
+ page: args.page
2668
2722
  });
2669
- return JSON.stringify(mrs, null, 2);
2723
+ return JSON.stringify(result, null, 2);
2670
2724
  }
2671
2725
  }),
2672
2726
  gitlab_get_mr_changes: tool({
@@ -2998,7 +3052,10 @@ Returns: title, description, state, author, assignees, labels, milestone, weight
2998
3052
  }),
2999
3053
  gitlab_list_issues: tool2({
3000
3054
  description: `List issues for a project or search globally.
3001
- Can filter by state, labels, assignee, milestone.`,
3055
+ Can filter by state, labels, assignee, milestone.
3056
+
3057
+ IMPORTANT: Response includes pagination info. If nextPage exists, there are more results.
3058
+ Always paginate until you get an empty response or no nextPage to ensure you have all data.`,
3002
3059
  args: {
3003
3060
  project_id: z2.string().optional().describe("The project ID or path. If not provided, searches globally."),
3004
3061
  state: z2.enum(["opened", "closed", "all"]).optional().describe("Filter by issue state (default: opened)"),
@@ -3006,20 +3063,22 @@ Can filter by state, labels, assignee, milestone.`,
3006
3063
  search: z2.string().optional().describe("Search issues by title or description"),
3007
3064
  labels: z2.string().optional().describe("Comma-separated list of labels to filter by"),
3008
3065
  milestone: z2.string().optional().describe("Filter by milestone title"),
3009
- limit: z2.number().optional().describe("Maximum number of results (default: 20)")
3066
+ limit: z2.number().optional().describe("Maximum number of results per page (default: 20)"),
3067
+ page: z2.number().optional().describe("Page number for pagination (default: 1)")
3010
3068
  },
3011
3069
  execute: async (args, _ctx) => {
3012
3070
  const client = getGitLabClient();
3013
- const issues = await client.listIssues({
3071
+ const result = await client.listIssues({
3014
3072
  projectId: args.project_id,
3015
3073
  state: args.state,
3016
3074
  scope: args.scope,
3017
3075
  search: args.search,
3018
3076
  labels: args.labels,
3019
3077
  milestone: args.milestone,
3020
- limit: args.limit
3078
+ limit: args.limit,
3079
+ page: args.page
3021
3080
  });
3022
- return JSON.stringify(issues, null, 2);
3081
+ return JSON.stringify(result, null, 2);
3023
3082
  }
3024
3083
  })
3025
3084
  };