@gitlab/opencode-gitlab-plugin 1.2.0 → 1.3.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
+ ## [1.3.0](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.2.0...v1.3.0) (2026-01-30)
6
+
7
+
8
+ ### ✨ Features
9
+
10
+ * **notes:** migrate notes listing to GraphQL API with pagination support ([2ec5d65](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/2ec5d6528b27b86a08f5c747f38762f90eb1f297))
11
+
12
+
13
+ ### 🐛 Bug Fixes
14
+
15
+ * address review feedback for notes GraphQL migration ([fcb1a35](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/fcb1a355f3ecbddf2429b0e2fecaeb1f02d29ffe))
16
+
5
17
  ## [1.2.0](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.1.0...v1.2.0) (2026-01-29)
6
18
 
7
19
 
package/dist/index.js CHANGED
@@ -82,7 +82,84 @@ var GitLabApiClient = class {
82
82
  }
83
83
  };
84
84
 
85
+ // src/client/notes-types.ts
86
+ function buildPaginationVariables(options) {
87
+ const variables = {};
88
+ if (options?.first !== void 0) {
89
+ variables.first = options.first;
90
+ } else if (options?.last == null) {
91
+ variables.first = 20;
92
+ }
93
+ if (options?.after) variables.after = options.after;
94
+ if (options?.last !== void 0) variables.last = options.last;
95
+ if (options?.before) variables.before = options.before;
96
+ if (options?.filter) variables.filter = options.filter;
97
+ return variables;
98
+ }
99
+ var NOTES_FRAGMENT = `
100
+ fragment NoteFields on Note {
101
+ id
102
+ body
103
+ bodyHtml
104
+ createdAt
105
+ updatedAt
106
+ system
107
+ internal
108
+ resolvable
109
+ resolved
110
+ resolvedAt
111
+ url
112
+ author {
113
+ id
114
+ username
115
+ name
116
+ avatarUrl
117
+ }
118
+ }
119
+ `;
120
+ var NOTES_CONNECTION_FRAGMENT = `
121
+ fragment NotesConnectionFields on NoteConnection {
122
+ count
123
+ pageInfo {
124
+ hasNextPage
125
+ hasPreviousPage
126
+ startCursor
127
+ endCursor
128
+ }
129
+ nodes {
130
+ ...NoteFields
131
+ }
132
+ }
133
+ `;
134
+
85
135
  // src/client/merge-requests.ts
136
+ var LIST_MR_NOTES_QUERY = `
137
+ ${NOTES_FRAGMENT}
138
+ ${NOTES_CONNECTION_FRAGMENT}
139
+ query listMrNotes(
140
+ $projectPath: ID!
141
+ $mrIid: String!
142
+ $first: Int
143
+ $after: String
144
+ $last: Int
145
+ $before: String
146
+ $filter: WorkItemNotesFilterType
147
+ ) {
148
+ project(fullPath: $projectPath) {
149
+ mergeRequest(iid: $mrIid) {
150
+ notes(
151
+ first: $first
152
+ after: $after
153
+ last: $last
154
+ before: $before
155
+ filter: $filter
156
+ ) {
157
+ ...NotesConnectionFields
158
+ }
159
+ }
160
+ }
161
+ }
162
+ `;
86
163
  var SET_AUTO_MERGE_MUTATION = `
87
164
  mutation setAutoMerge(
88
165
  $projectPath: ID!
@@ -183,12 +260,25 @@ var MergeRequestsClient = class extends GitLabApiClient {
183
260
  requestBody
184
261
  );
185
262
  }
186
- async listMrNotes(projectId, mrIid) {
187
- const encodedProject = this.encodeProjectId(projectId);
188
- return this.fetch(
189
- "GET",
190
- `/projects/${encodedProject}/merge_requests/${mrIid}/notes`
191
- );
263
+ /**
264
+ * List notes on a merge request using GraphQL API with pagination support
265
+ */
266
+ async listMrNotes(projectId, mrIid, options) {
267
+ const variables = {
268
+ projectPath: projectId,
269
+ mrIid: String(mrIid),
270
+ ...buildPaginationVariables(options)
271
+ };
272
+ const result = await this.fetchGraphQL(LIST_MR_NOTES_QUERY, variables);
273
+ const notes = result.project?.mergeRequest?.notes;
274
+ if (!notes) {
275
+ throw new Error("Merge request not found or access denied");
276
+ }
277
+ return {
278
+ notes,
279
+ pageInfo: notes.pageInfo,
280
+ totalCount: notes.count
281
+ };
192
282
  }
193
283
  async createMrNote(projectId, mrIid, body, discussionId) {
194
284
  const encodedProject = this.encodeProjectId(projectId);
@@ -235,10 +325,6 @@ var MergeRequestsClient = class extends GitLabApiClient {
235
325
  `/projects/${encodedProject}/merge_requests/${mrIid}/pipelines`
236
326
  );
237
327
  }
238
- /**
239
- * List merge request diffs with pagination support
240
- * API: GET /projects/:id/merge_requests/:merge_request_iid/diffs
241
- */
242
328
  async listMergeRequestDiffs(projectId, mrIid, options = {}) {
243
329
  const encodedProject = this.encodeProjectId(projectId);
244
330
  const params = new URLSearchParams();
@@ -270,6 +356,33 @@ var MergeRequestsClient = class extends GitLabApiClient {
270
356
  };
271
357
 
272
358
  // src/client/issues.ts
359
+ var LIST_ISSUE_NOTES_QUERY = `
360
+ ${NOTES_FRAGMENT}
361
+ ${NOTES_CONNECTION_FRAGMENT}
362
+ query listIssueNotes(
363
+ $projectPath: ID!
364
+ $issueIid: String!
365
+ $first: Int
366
+ $after: String
367
+ $last: Int
368
+ $before: String
369
+ $filter: WorkItemNotesFilterType
370
+ ) {
371
+ project(fullPath: $projectPath) {
372
+ issue(iid: $issueIid) {
373
+ notes(
374
+ first: $first
375
+ after: $after
376
+ last: $last
377
+ before: $before
378
+ filter: $filter
379
+ ) {
380
+ ...NotesConnectionFields
381
+ }
382
+ }
383
+ }
384
+ }
385
+ `;
273
386
  var IssuesClient = class extends GitLabApiClient {
274
387
  async createIssue(projectId, title, options) {
275
388
  const encodedProject = this.encodeProjectId(projectId);
@@ -305,12 +418,25 @@ var IssuesClient = class extends GitLabApiClient {
305
418
  }
306
419
  return this.fetch("GET", path2);
307
420
  }
308
- async listIssueNotes(projectId, issueIid) {
309
- const encodedProject = this.encodeProjectId(projectId);
310
- return this.fetch(
311
- "GET",
312
- `/projects/${encodedProject}/issues/${issueIid}/notes`
313
- );
421
+ /**
422
+ * List notes on an issue using GraphQL API with pagination support
423
+ */
424
+ async listIssueNotes(projectId, issueIid, options) {
425
+ const variables = {
426
+ projectPath: projectId,
427
+ issueIid: String(issueIid),
428
+ ...buildPaginationVariables(options)
429
+ };
430
+ const result = await this.fetchGraphQL(LIST_ISSUE_NOTES_QUERY, variables);
431
+ const notes = result.project?.issue?.notes;
432
+ if (!notes) {
433
+ throw new Error("Issue not found or access denied");
434
+ }
435
+ return {
436
+ notes,
437
+ pageInfo: notes.pageInfo,
438
+ totalCount: notes.count
439
+ };
314
440
  }
315
441
  async listIssueDiscussions(projectId, issueIid) {
316
442
  const encodedProject = this.encodeProjectId(projectId);
@@ -357,10 +483,6 @@ var IssuesClient = class extends GitLabApiClient {
357
483
  { resolved: false }
358
484
  );
359
485
  }
360
- /**
361
- * Get a single note from an issue
362
- * API: GET /projects/:id/issues/:issue_iid/notes/:note_id
363
- */
364
486
  async getIssueNote(projectId, issueIid, noteId) {
365
487
  const encodedProject = this.encodeProjectId(projectId);
366
488
  return this.fetch(
@@ -1111,6 +1233,31 @@ var TodosClient = class extends GitLabApiClient {
1111
1233
  };
1112
1234
 
1113
1235
  // src/client/epics.ts
1236
+ var LIST_EPIC_NOTES_QUERY = `
1237
+ ${NOTES_FRAGMENT}
1238
+ ${NOTES_CONNECTION_FRAGMENT}
1239
+ query listEpicNotes(
1240
+ $groupPath: ID!
1241
+ $epicIid: ID!
1242
+ $first: Int
1243
+ $after: String
1244
+ $last: Int
1245
+ $before: String
1246
+ ) {
1247
+ group(fullPath: $groupPath) {
1248
+ epic(iid: $epicIid) {
1249
+ notes(
1250
+ first: $first
1251
+ after: $after
1252
+ last: $last
1253
+ before: $before
1254
+ ) {
1255
+ ...NotesConnectionFields
1256
+ }
1257
+ }
1258
+ }
1259
+ }
1260
+ `;
1114
1261
  var EpicsClient = class extends GitLabApiClient {
1115
1262
  async getEpic(groupId, epicIid) {
1116
1263
  const encodedGroup = encodeURIComponent(groupId);
@@ -1159,12 +1306,25 @@ var EpicsClient = class extends GitLabApiClient {
1159
1306
  `/groups/${encodedGroup}/epics/${epicIid}/issues/${epicIssueId}`
1160
1307
  );
1161
1308
  }
1162
- async listEpicNotes(groupId, epicIid) {
1163
- const encodedGroup = encodeURIComponent(groupId);
1164
- return this.fetch(
1165
- "GET",
1166
- `/groups/${encodedGroup}/epics/${epicIid}/notes`
1167
- );
1309
+ /**
1310
+ * List notes on an epic using GraphQL API with pagination support
1311
+ */
1312
+ async listEpicNotes(groupId, epicIid, options) {
1313
+ const variables = {
1314
+ groupPath: groupId,
1315
+ epicIid: String(epicIid),
1316
+ ...buildPaginationVariables(options)
1317
+ };
1318
+ const result = await this.fetchGraphQL(LIST_EPIC_NOTES_QUERY, variables);
1319
+ const notes = result.group?.epic?.notes;
1320
+ if (!notes) {
1321
+ throw new Error("Epic not found or access denied");
1322
+ }
1323
+ return {
1324
+ notes,
1325
+ pageInfo: notes.pageInfo,
1326
+ totalCount: notes.count
1327
+ };
1168
1328
  }
1169
1329
  async listEpicDiscussions(groupId, epicIid) {
1170
1330
  const encodedGroup = encodeURIComponent(groupId);
@@ -1195,10 +1355,6 @@ var EpicsClient = class extends GitLabApiClient {
1195
1355
  { body }
1196
1356
  );
1197
1357
  }
1198
- /**
1199
- * Get a single note from an epic
1200
- * API: GET /groups/:id/epics/:epic_iid/notes/:note_id
1201
- */
1202
1358
  async getEpicNote(groupId, epicIid, noteId) {
1203
1359
  const encodedGroup = encodeURIComponent(groupId);
1204
1360
  return this.fetch(
@@ -1209,6 +1365,30 @@ var EpicsClient = class extends GitLabApiClient {
1209
1365
  };
1210
1366
 
1211
1367
  // src/client/snippets.ts
1368
+ var LIST_SNIPPET_NOTES_QUERY = `
1369
+ ${NOTES_FRAGMENT}
1370
+ ${NOTES_CONNECTION_FRAGMENT}
1371
+ query listSnippetNotes(
1372
+ $snippetGid: SnippetID!
1373
+ $first: Int
1374
+ $after: String
1375
+ $last: Int
1376
+ $before: String
1377
+ ) {
1378
+ snippets(ids: [$snippetGid]) {
1379
+ nodes {
1380
+ notes(
1381
+ first: $first
1382
+ after: $after
1383
+ last: $last
1384
+ before: $before
1385
+ ) {
1386
+ ...NotesConnectionFields
1387
+ }
1388
+ }
1389
+ }
1390
+ }
1391
+ `;
1212
1392
  var SnippetsClient = class extends GitLabApiClient {
1213
1393
  async listSnippetDiscussions(projectId, snippetId) {
1214
1394
  const encodedProject = this.encodeProjectId(projectId);
@@ -1224,12 +1404,30 @@ var SnippetsClient = class extends GitLabApiClient {
1224
1404
  `/projects/${encodedProject}/snippets/${snippetId}/discussions/${discussionId}`
1225
1405
  );
1226
1406
  }
1227
- async listSnippetNotes(projectId, snippetId) {
1228
- const encodedProject = this.encodeProjectId(projectId);
1229
- return this.fetch(
1230
- "GET",
1231
- `/projects/${encodedProject}/snippets/${snippetId}/notes`
1232
- );
1407
+ /**
1408
+ * List notes on a snippet using GraphQL API with pagination support
1409
+ */
1410
+ async listSnippetNotes(projectId, snippetId, options) {
1411
+ const variables = {
1412
+ snippetGid: `gid://gitlab/ProjectSnippet/${snippetId}`,
1413
+ ...buildPaginationVariables(options)
1414
+ };
1415
+ const result = await this.fetchGraphQL(LIST_SNIPPET_NOTES_QUERY, variables);
1416
+ const notes = result.snippets.nodes[0]?.notes || {
1417
+ nodes: [],
1418
+ pageInfo: {
1419
+ hasNextPage: false,
1420
+ hasPreviousPage: false,
1421
+ startCursor: null,
1422
+ endCursor: null
1423
+ },
1424
+ count: 0
1425
+ };
1426
+ return {
1427
+ notes,
1428
+ pageInfo: notes.pageInfo,
1429
+ totalCount: notes.count
1430
+ };
1233
1431
  }
1234
1432
  async createSnippetNote(projectId, snippetId, body, discussionId) {
1235
1433
  const encodedProject = this.encodeProjectId(projectId);
@@ -1583,17 +1781,32 @@ Note: For a flattened list of all comments, use gitlab_list_mr_notes instead.`,
1583
1781
  }
1584
1782
  }),
1585
1783
  gitlab_list_mr_notes: tool({
1586
- description: `List all notes/comments on a merge request in a flat structure.
1784
+ description: `List all notes/comments on a merge request using GraphQL API with pagination support.
1587
1785
  Returns all comments including system notes, code review comments, and general discussion.
1588
- This is easier to read than discussions which have nested structure.`,
1786
+ This is easier to read than discussions which have nested structure.
1787
+
1788
+ The response includes pagination information (pageInfo) with cursors for fetching additional pages.
1789
+ Use 'after' with the 'endCursor' from pageInfo to get the next page.
1790
+ Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
1589
1791
  args: {
1590
1792
  project_id: z.string().describe("The project ID or URL-encoded path"),
1591
- mr_iid: z.number().describe("The internal ID of the merge request")
1793
+ mr_iid: z.number().describe("The internal ID of the merge request"),
1794
+ first: z.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
1795
+ after: z.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
1796
+ last: z.number().optional().describe("Number of items to return from the end (for backward pagination)"),
1797
+ before: z.string().optional().describe("Cursor for backward pagination - use startCursor from previous response"),
1798
+ filter: z.enum(["ALL_NOTES", "ONLY_COMMENTS", "ONLY_ACTIVITY"]).optional().describe("Filter notes by type: ALL_NOTES (default), ONLY_COMMENTS, or ONLY_ACTIVITY")
1592
1799
  },
1593
1800
  execute: async (args, _ctx) => {
1594
1801
  const client = getGitLabClient();
1595
- const notes = await client.listMrNotes(args.project_id, args.mr_iid);
1596
- return JSON.stringify(notes, null, 2);
1802
+ const result = await client.listMrNotes(args.project_id, args.mr_iid, {
1803
+ first: args.first,
1804
+ after: args.after,
1805
+ last: args.last,
1806
+ before: args.before,
1807
+ filter: args.filter
1808
+ });
1809
+ return JSON.stringify(result, null, 2);
1597
1810
  }
1598
1811
  }),
1599
1812
  gitlab_create_mr_note: tool({
@@ -1926,16 +2139,31 @@ Can filter by state, labels, assignee, milestone.`,
1926
2139
  }
1927
2140
  }),
1928
2141
  gitlab_list_issue_notes: tool2({
1929
- description: `List all notes/comments on an issue.
1930
- Returns all comments including system notes in chronological order.`,
2142
+ description: `List all notes/comments on an issue using GraphQL API with pagination support.
2143
+ Returns all comments including system notes in chronological order.
2144
+
2145
+ The response includes pagination information (pageInfo) with cursors for fetching additional pages.
2146
+ Use 'after' with the 'endCursor' from pageInfo to get the next page.
2147
+ Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
1931
2148
  args: {
1932
2149
  project_id: z2.string().describe("The project ID or URL-encoded path"),
1933
- issue_iid: z2.number().describe("The internal ID of the issue")
2150
+ issue_iid: z2.number().describe("The internal ID of the issue"),
2151
+ first: z2.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
2152
+ after: z2.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
2153
+ last: z2.number().optional().describe("Number of items to return from the end (for backward pagination)"),
2154
+ before: z2.string().optional().describe("Cursor for backward pagination - use startCursor from previous response"),
2155
+ filter: z2.enum(["ALL_NOTES", "ONLY_COMMENTS", "ONLY_ACTIVITY"]).optional().describe("Filter notes by type: ALL_NOTES (default), ONLY_COMMENTS, or ONLY_ACTIVITY")
1934
2156
  },
1935
2157
  execute: async (args, _ctx) => {
1936
2158
  const client = getGitLabClient();
1937
- const notes = await client.listIssueNotes(args.project_id, args.issue_iid);
1938
- return JSON.stringify(notes, null, 2);
2159
+ const result = await client.listIssueNotes(args.project_id, args.issue_iid, {
2160
+ first: args.first,
2161
+ after: args.after,
2162
+ last: args.last,
2163
+ before: args.before,
2164
+ filter: args.filter
2165
+ });
2166
+ return JSON.stringify(result, null, 2);
1939
2167
  }
1940
2168
  }),
1941
2169
  gitlab_list_issue_discussions: tool2({
@@ -2190,16 +2418,29 @@ Removes the association between the issue and the epic.`,
2190
2418
  }
2191
2419
  }),
2192
2420
  gitlab_list_epic_notes: tool3({
2193
- description: `List all comments/notes on an epic in a flat structure.
2194
- Returns all comments in chronological order.`,
2421
+ description: `List all comments/notes on an epic using GraphQL API with pagination support.
2422
+ Returns all comments in chronological order.
2423
+
2424
+ The response includes pagination information (pageInfo) with cursors for fetching additional pages.
2425
+ Use 'after' with the 'endCursor' from pageInfo to get the next page.
2426
+ Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
2195
2427
  args: {
2196
2428
  group_id: z3.string().describe("The group ID or URL-encoded path"),
2197
- epic_iid: z3.number().describe("The internal ID of the epic")
2429
+ epic_iid: z3.number().describe("The internal ID of the epic"),
2430
+ first: z3.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
2431
+ after: z3.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
2432
+ last: z3.number().optional().describe("Number of items to return from the end (for backward pagination)"),
2433
+ before: z3.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
2198
2434
  },
2199
2435
  execute: async (args, _ctx) => {
2200
2436
  const client = getGitLabClient();
2201
- const notes = await client.listEpicNotes(args.group_id, args.epic_iid);
2202
- return JSON.stringify(notes, null, 2);
2437
+ const result = await client.listEpicNotes(args.group_id, args.epic_iid, {
2438
+ first: args.first,
2439
+ after: args.after,
2440
+ last: args.last,
2441
+ before: args.before
2442
+ });
2443
+ return JSON.stringify(result, null, 2);
2203
2444
  }
2204
2445
  }),
2205
2446
  gitlab_list_epic_discussions: tool3({
@@ -3210,16 +3451,29 @@ Use this to get the full context of a specific conversation.`,
3210
3451
  }
3211
3452
  }),
3212
3453
  gitlab_list_snippet_notes: tool9({
3213
- description: `List all notes/comments on a project snippet in a flat structure.
3214
- Returns all comments including system notes in chronological order.`,
3454
+ description: `List all notes/comments on a project snippet using GraphQL API with pagination support.
3455
+ Returns all comments including system notes in chronological order.
3456
+
3457
+ The response includes pagination information (pageInfo) with cursors for fetching additional pages.
3458
+ Use 'after' with the 'endCursor' from pageInfo to get the next page.
3459
+ Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
3215
3460
  args: {
3216
3461
  project_id: z9.string().describe("The project ID or URL-encoded path"),
3217
- snippet_id: z9.number().describe("The ID of the snippet")
3462
+ snippet_id: z9.number().describe("The ID of the snippet"),
3463
+ first: z9.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
3464
+ after: z9.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
3465
+ last: z9.number().optional().describe("Number of items to return from the end (for backward pagination)"),
3466
+ before: z9.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
3218
3467
  },
3219
3468
  execute: async (args, _ctx) => {
3220
3469
  const client = getGitLabClient();
3221
- const notes = await client.listSnippetNotes(args.project_id, args.snippet_id);
3222
- return JSON.stringify(notes, null, 2);
3470
+ const result = await client.listSnippetNotes(args.project_id, args.snippet_id, {
3471
+ first: args.first,
3472
+ after: args.after,
3473
+ last: args.last,
3474
+ before: args.before
3475
+ });
3476
+ return JSON.stringify(result, null, 2);
3223
3477
  }
3224
3478
  }),
3225
3479
  gitlab_create_snippet_note: tool9({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gitlab/opencode-gitlab-plugin",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "GitLab tools plugin for OpenCode - provides GitLab API access for merge requests, issues, pipelines, and more",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",