@gitlab/opencode-gitlab-plugin 1.2.0 → 1.4.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,31 @@
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.4.0](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.3.0...v1.4.0) (2026-02-02)
6
+
7
+
8
+ ### ✨ Features
9
+
10
+ * **todos:** migrate listTodos to GraphQL API with pagination support ([5726c8b](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/5726c8bb3b8fbce6f014c0f3500bf4d913eb4033))
11
+
12
+
13
+ ### 🐛 Bug Fixes
14
+
15
+ * **todos:** add validation for conflicting pagination parameters ([f46d8c2](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/f46d8c2d4c0f4e4b6363013fc3bcee676b78d777))
16
+ * **todos:** address MR review feedback ([87b3afa](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/87b3afae2992e789c59f0c908c7b48a957e4a931))
17
+
18
+ ## [1.3.0](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.2.0...v1.3.0) (2026-01-30)
19
+
20
+
21
+ ### ✨ Features
22
+
23
+ * **notes:** migrate notes listing to GraphQL API with pagination support ([2ec5d65](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/2ec5d6528b27b86a08f5c747f38762f90eb1f297))
24
+
25
+
26
+ ### 🐛 Bug Fixes
27
+
28
+ * address review feedback for notes GraphQL migration ([fcb1a35](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/fcb1a355f3ecbddf2429b0e2fecaeb1f02d29ffe))
29
+
5
30
  ## [1.2.0](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.1.0...v1.2.0) (2026-01-29)
6
31
 
7
32
 
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(
@@ -1084,20 +1206,163 @@ var SecurityClient = class extends GitLabApiClient {
1084
1206
  };
1085
1207
 
1086
1208
  // src/client/todos.ts
1209
+ var LIST_TODOS_QUERY = `
1210
+ query listTodos(
1211
+ $state: [TodoStateEnum!]
1212
+ $action: [TodoActionEnum!]
1213
+ $type: [TodoTargetEnum!]
1214
+ $projectId: [ID!]
1215
+ $groupId: [ID!]
1216
+ $authorId: [ID!]
1217
+ $first: Int
1218
+ $after: String
1219
+ $last: Int
1220
+ $before: String
1221
+ ) {
1222
+ currentUser {
1223
+ todos(
1224
+ state: $state
1225
+ action: $action
1226
+ type: $type
1227
+ projectId: $projectId
1228
+ groupId: $groupId
1229
+ authorId: $authorId
1230
+ first: $first
1231
+ after: $after
1232
+ last: $last
1233
+ before: $before
1234
+ ) {
1235
+ count
1236
+ pageInfo {
1237
+ hasNextPage
1238
+ hasPreviousPage
1239
+ startCursor
1240
+ endCursor
1241
+ }
1242
+ nodes {
1243
+ id
1244
+ body
1245
+ state
1246
+ action
1247
+ createdAt
1248
+ targetType
1249
+ targetUrl
1250
+ snoozedUntil
1251
+ project {
1252
+ id
1253
+ name
1254
+ fullPath
1255
+ }
1256
+ group {
1257
+ id
1258
+ name
1259
+ fullPath
1260
+ }
1261
+ author {
1262
+ id
1263
+ username
1264
+ name
1265
+ avatarUrl
1266
+ }
1267
+ targetEntity {
1268
+ __typename
1269
+ ... on Issue {
1270
+ id
1271
+ title
1272
+ iid
1273
+ }
1274
+ ... on MergeRequest {
1275
+ id
1276
+ title
1277
+ iid
1278
+ }
1279
+ ... on Epic {
1280
+ id
1281
+ title
1282
+ iid
1283
+ }
1284
+ ... on Commit {
1285
+ id
1286
+ title
1287
+ }
1288
+ ... on DesignManagement__Design {
1289
+ id
1290
+ }
1291
+ ... on AlertManagement__Alert {
1292
+ id
1293
+ title
1294
+ iid
1295
+ }
1296
+ }
1297
+ }
1298
+ }
1299
+ }
1300
+ }
1301
+ `;
1087
1302
  var TodosClient = class extends GitLabApiClient {
1088
1303
  async listTodos(options) {
1089
- const params = new URLSearchParams();
1090
- params.set("per_page", String(options?.limit || 20));
1091
- if (options?.action) params.set("action", options.action);
1092
- if (options?.author_id) params.set("author_id", String(options.author_id));
1304
+ if (options?.first !== void 0 && options?.last !== void 0) {
1305
+ throw new Error(
1306
+ 'Cannot specify both "first" and "last" pagination parameters. Use "first"/"after" for forward pagination or "last"/"before" for backward pagination.'
1307
+ );
1308
+ }
1309
+ const variables = {};
1310
+ if (options?.first !== void 0) {
1311
+ variables.first = options.first;
1312
+ } else if (options?.last === void 0) {
1313
+ variables.first = 20;
1314
+ }
1315
+ if (options?.after) variables.after = options.after;
1316
+ if (options?.last !== void 0) variables.last = options.last;
1317
+ if (options?.before) variables.before = options.before;
1318
+ if (options?.action) {
1319
+ variables.action = [this.mapTodoAction(options.action)];
1320
+ }
1321
+ if (options?.author_id) {
1322
+ variables.authorId = [`gid://gitlab/User/${options.author_id}`];
1323
+ }
1093
1324
  if (options?.project_id) {
1094
- const encodedProject = this.encodeProjectId(options.project_id);
1095
- params.set("project_id", encodedProject);
1325
+ variables.projectId = [options.project_id];
1096
1326
  }
1097
- if (options?.group_id) params.set("group_id", options.group_id);
1098
- if (options?.state) params.set("state", options.state);
1099
- if (options?.type) params.set("type", options.type);
1100
- return this.fetch("GET", `/todos?${params}`);
1327
+ if (options?.group_id) {
1328
+ variables.groupId = [options.group_id];
1329
+ }
1330
+ if (options?.state) {
1331
+ variables.state = [options.state.toLowerCase()];
1332
+ }
1333
+ if (options?.type) {
1334
+ variables.type = [this.mapTodoTargetType(options.type)];
1335
+ }
1336
+ const result = await this.fetchGraphQL(LIST_TODOS_QUERY, variables);
1337
+ const todos = result.currentUser.todos;
1338
+ return {
1339
+ todos
1340
+ };
1341
+ }
1342
+ mapTodoAction(action) {
1343
+ const actionMap = {
1344
+ assigned: "ASSIGNED",
1345
+ mentioned: "MENTIONED",
1346
+ build_failed: "BUILD_FAILED",
1347
+ marked: "MARKED",
1348
+ approval_required: "APPROVAL_REQUIRED",
1349
+ unmergeable: "UNMERGEABLE",
1350
+ directly_addressed: "DIRECTLY_ADDRESSED",
1351
+ merge_train_removed: "MERGE_TRAIN_REMOVED",
1352
+ review_requested: "REVIEW_REQUESTED"
1353
+ };
1354
+ return actionMap[action] || action.toUpperCase();
1355
+ }
1356
+ mapTodoTargetType(type) {
1357
+ const typeMap = {
1358
+ Issue: "ISSUE",
1359
+ MergeRequest: "MERGEREQUEST",
1360
+ "DesignManagement::Design": "DESIGN",
1361
+ Alert: "ALERT",
1362
+ Commit: "COMMIT",
1363
+ Epic: "EPIC"
1364
+ };
1365
+ return typeMap[type] || type;
1101
1366
  }
1102
1367
  async markTodoAsDone(todoId) {
1103
1368
  return this.fetch("POST", `/todos/${todoId}/mark_as_done`);
@@ -1106,11 +1371,37 @@ var TodosClient = class extends GitLabApiClient {
1106
1371
  return this.fetch("POST", "/todos/mark_as_done");
1107
1372
  }
1108
1373
  async getTodoCount() {
1109
- return this.fetch("GET", "/todos/count");
1374
+ const result = await this.fetchGraphQL(`query { currentUser { todos(state: [pending]) { count } } }`);
1375
+ return { count: result.currentUser.todos.count };
1110
1376
  }
1111
1377
  };
1112
1378
 
1113
1379
  // src/client/epics.ts
1380
+ var LIST_EPIC_NOTES_QUERY = `
1381
+ ${NOTES_FRAGMENT}
1382
+ ${NOTES_CONNECTION_FRAGMENT}
1383
+ query listEpicNotes(
1384
+ $groupPath: ID!
1385
+ $epicIid: ID!
1386
+ $first: Int
1387
+ $after: String
1388
+ $last: Int
1389
+ $before: String
1390
+ ) {
1391
+ group(fullPath: $groupPath) {
1392
+ epic(iid: $epicIid) {
1393
+ notes(
1394
+ first: $first
1395
+ after: $after
1396
+ last: $last
1397
+ before: $before
1398
+ ) {
1399
+ ...NotesConnectionFields
1400
+ }
1401
+ }
1402
+ }
1403
+ }
1404
+ `;
1114
1405
  var EpicsClient = class extends GitLabApiClient {
1115
1406
  async getEpic(groupId, epicIid) {
1116
1407
  const encodedGroup = encodeURIComponent(groupId);
@@ -1159,12 +1450,25 @@ var EpicsClient = class extends GitLabApiClient {
1159
1450
  `/groups/${encodedGroup}/epics/${epicIid}/issues/${epicIssueId}`
1160
1451
  );
1161
1452
  }
1162
- async listEpicNotes(groupId, epicIid) {
1163
- const encodedGroup = encodeURIComponent(groupId);
1164
- return this.fetch(
1165
- "GET",
1166
- `/groups/${encodedGroup}/epics/${epicIid}/notes`
1167
- );
1453
+ /**
1454
+ * List notes on an epic using GraphQL API with pagination support
1455
+ */
1456
+ async listEpicNotes(groupId, epicIid, options) {
1457
+ const variables = {
1458
+ groupPath: groupId,
1459
+ epicIid: String(epicIid),
1460
+ ...buildPaginationVariables(options)
1461
+ };
1462
+ const result = await this.fetchGraphQL(LIST_EPIC_NOTES_QUERY, variables);
1463
+ const notes = result.group?.epic?.notes;
1464
+ if (!notes) {
1465
+ throw new Error("Epic not found or access denied");
1466
+ }
1467
+ return {
1468
+ notes,
1469
+ pageInfo: notes.pageInfo,
1470
+ totalCount: notes.count
1471
+ };
1168
1472
  }
1169
1473
  async listEpicDiscussions(groupId, epicIid) {
1170
1474
  const encodedGroup = encodeURIComponent(groupId);
@@ -1195,10 +1499,6 @@ var EpicsClient = class extends GitLabApiClient {
1195
1499
  { body }
1196
1500
  );
1197
1501
  }
1198
- /**
1199
- * Get a single note from an epic
1200
- * API: GET /groups/:id/epics/:epic_iid/notes/:note_id
1201
- */
1202
1502
  async getEpicNote(groupId, epicIid, noteId) {
1203
1503
  const encodedGroup = encodeURIComponent(groupId);
1204
1504
  return this.fetch(
@@ -1209,6 +1509,30 @@ var EpicsClient = class extends GitLabApiClient {
1209
1509
  };
1210
1510
 
1211
1511
  // src/client/snippets.ts
1512
+ var LIST_SNIPPET_NOTES_QUERY = `
1513
+ ${NOTES_FRAGMENT}
1514
+ ${NOTES_CONNECTION_FRAGMENT}
1515
+ query listSnippetNotes(
1516
+ $snippetGid: SnippetID!
1517
+ $first: Int
1518
+ $after: String
1519
+ $last: Int
1520
+ $before: String
1521
+ ) {
1522
+ snippets(ids: [$snippetGid]) {
1523
+ nodes {
1524
+ notes(
1525
+ first: $first
1526
+ after: $after
1527
+ last: $last
1528
+ before: $before
1529
+ ) {
1530
+ ...NotesConnectionFields
1531
+ }
1532
+ }
1533
+ }
1534
+ }
1535
+ `;
1212
1536
  var SnippetsClient = class extends GitLabApiClient {
1213
1537
  async listSnippetDiscussions(projectId, snippetId) {
1214
1538
  const encodedProject = this.encodeProjectId(projectId);
@@ -1224,12 +1548,30 @@ var SnippetsClient = class extends GitLabApiClient {
1224
1548
  `/projects/${encodedProject}/snippets/${snippetId}/discussions/${discussionId}`
1225
1549
  );
1226
1550
  }
1227
- async listSnippetNotes(projectId, snippetId) {
1228
- const encodedProject = this.encodeProjectId(projectId);
1229
- return this.fetch(
1230
- "GET",
1231
- `/projects/${encodedProject}/snippets/${snippetId}/notes`
1232
- );
1551
+ /**
1552
+ * List notes on a snippet using GraphQL API with pagination support
1553
+ */
1554
+ async listSnippetNotes(projectId, snippetId, options) {
1555
+ const variables = {
1556
+ snippetGid: `gid://gitlab/ProjectSnippet/${snippetId}`,
1557
+ ...buildPaginationVariables(options)
1558
+ };
1559
+ const result = await this.fetchGraphQL(LIST_SNIPPET_NOTES_QUERY, variables);
1560
+ const notes = result.snippets.nodes[0]?.notes || {
1561
+ nodes: [],
1562
+ pageInfo: {
1563
+ hasNextPage: false,
1564
+ hasPreviousPage: false,
1565
+ startCursor: null,
1566
+ endCursor: null
1567
+ },
1568
+ count: 0
1569
+ };
1570
+ return {
1571
+ notes,
1572
+ pageInfo: notes.pageInfo,
1573
+ totalCount: notes.count
1574
+ };
1233
1575
  }
1234
1576
  async createSnippetNote(projectId, snippetId, body, discussionId) {
1235
1577
  const encodedProject = this.encodeProjectId(projectId);
@@ -1583,17 +1925,32 @@ Note: For a flattened list of all comments, use gitlab_list_mr_notes instead.`,
1583
1925
  }
1584
1926
  }),
1585
1927
  gitlab_list_mr_notes: tool({
1586
- description: `List all notes/comments on a merge request in a flat structure.
1928
+ description: `List all notes/comments on a merge request using GraphQL API with pagination support.
1587
1929
  Returns all comments including system notes, code review comments, and general discussion.
1588
- This is easier to read than discussions which have nested structure.`,
1930
+ This is easier to read than discussions which have nested structure.
1931
+
1932
+ The response includes pagination information (pageInfo) with cursors for fetching additional pages.
1933
+ Use 'after' with the 'endCursor' from pageInfo to get the next page.
1934
+ Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
1589
1935
  args: {
1590
1936
  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")
1937
+ mr_iid: z.number().describe("The internal ID of the merge request"),
1938
+ first: z.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
1939
+ after: z.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
1940
+ last: z.number().optional().describe("Number of items to return from the end (for backward pagination)"),
1941
+ before: z.string().optional().describe("Cursor for backward pagination - use startCursor from previous response"),
1942
+ filter: z.enum(["ALL_NOTES", "ONLY_COMMENTS", "ONLY_ACTIVITY"]).optional().describe("Filter notes by type: ALL_NOTES (default), ONLY_COMMENTS, or ONLY_ACTIVITY")
1592
1943
  },
1593
1944
  execute: async (args, _ctx) => {
1594
1945
  const client = getGitLabClient();
1595
- const notes = await client.listMrNotes(args.project_id, args.mr_iid);
1596
- return JSON.stringify(notes, null, 2);
1946
+ const result = await client.listMrNotes(args.project_id, args.mr_iid, {
1947
+ first: args.first,
1948
+ after: args.after,
1949
+ last: args.last,
1950
+ before: args.before,
1951
+ filter: args.filter
1952
+ });
1953
+ return JSON.stringify(result, null, 2);
1597
1954
  }
1598
1955
  }),
1599
1956
  gitlab_create_mr_note: tool({
@@ -1926,16 +2283,31 @@ Can filter by state, labels, assignee, milestone.`,
1926
2283
  }
1927
2284
  }),
1928
2285
  gitlab_list_issue_notes: tool2({
1929
- description: `List all notes/comments on an issue.
1930
- Returns all comments including system notes in chronological order.`,
2286
+ description: `List all notes/comments on an issue using GraphQL API with pagination support.
2287
+ Returns all comments including system notes in chronological order.
2288
+
2289
+ The response includes pagination information (pageInfo) with cursors for fetching additional pages.
2290
+ Use 'after' with the 'endCursor' from pageInfo to get the next page.
2291
+ Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
1931
2292
  args: {
1932
2293
  project_id: z2.string().describe("The project ID or URL-encoded path"),
1933
- issue_iid: z2.number().describe("The internal ID of the issue")
2294
+ issue_iid: z2.number().describe("The internal ID of the issue"),
2295
+ first: z2.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
2296
+ after: z2.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
2297
+ last: z2.number().optional().describe("Number of items to return from the end (for backward pagination)"),
2298
+ before: z2.string().optional().describe("Cursor for backward pagination - use startCursor from previous response"),
2299
+ filter: z2.enum(["ALL_NOTES", "ONLY_COMMENTS", "ONLY_ACTIVITY"]).optional().describe("Filter notes by type: ALL_NOTES (default), ONLY_COMMENTS, or ONLY_ACTIVITY")
1934
2300
  },
1935
2301
  execute: async (args, _ctx) => {
1936
2302
  const client = getGitLabClient();
1937
- const notes = await client.listIssueNotes(args.project_id, args.issue_iid);
1938
- return JSON.stringify(notes, null, 2);
2303
+ const result = await client.listIssueNotes(args.project_id, args.issue_iid, {
2304
+ first: args.first,
2305
+ after: args.after,
2306
+ last: args.last,
2307
+ before: args.before,
2308
+ filter: args.filter
2309
+ });
2310
+ return JSON.stringify(result, null, 2);
1939
2311
  }
1940
2312
  }),
1941
2313
  gitlab_list_issue_discussions: tool2({
@@ -2190,16 +2562,29 @@ Removes the association between the issue and the epic.`,
2190
2562
  }
2191
2563
  }),
2192
2564
  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.`,
2565
+ description: `List all comments/notes on an epic using GraphQL API with pagination support.
2566
+ Returns all comments in chronological order.
2567
+
2568
+ The response includes pagination information (pageInfo) with cursors for fetching additional pages.
2569
+ Use 'after' with the 'endCursor' from pageInfo to get the next page.
2570
+ Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
2195
2571
  args: {
2196
2572
  group_id: z3.string().describe("The group ID or URL-encoded path"),
2197
- epic_iid: z3.number().describe("The internal ID of the epic")
2573
+ epic_iid: z3.number().describe("The internal ID of the epic"),
2574
+ first: z3.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
2575
+ after: z3.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
2576
+ last: z3.number().optional().describe("Number of items to return from the end (for backward pagination)"),
2577
+ before: z3.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
2198
2578
  },
2199
2579
  execute: async (args, _ctx) => {
2200
2580
  const client = getGitLabClient();
2201
- const notes = await client.listEpicNotes(args.group_id, args.epic_iid);
2202
- return JSON.stringify(notes, null, 2);
2581
+ const result = await client.listEpicNotes(args.group_id, args.epic_iid, {
2582
+ first: args.first,
2583
+ after: args.after,
2584
+ last: args.last,
2585
+ before: args.before
2586
+ });
2587
+ return JSON.stringify(result, null, 2);
2203
2588
  }
2204
2589
  }),
2205
2590
  gitlab_list_epic_discussions: tool3({
@@ -3210,16 +3595,29 @@ Use this to get the full context of a specific conversation.`,
3210
3595
  }
3211
3596
  }),
3212
3597
  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.`,
3598
+ description: `List all notes/comments on a project snippet using GraphQL API with pagination support.
3599
+ Returns all comments including system notes in chronological order.
3600
+
3601
+ The response includes pagination information (pageInfo) with cursors for fetching additional pages.
3602
+ Use 'after' with the 'endCursor' from pageInfo to get the next page.
3603
+ Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
3215
3604
  args: {
3216
3605
  project_id: z9.string().describe("The project ID or URL-encoded path"),
3217
- snippet_id: z9.number().describe("The ID of the snippet")
3606
+ snippet_id: z9.number().describe("The ID of the snippet"),
3607
+ first: z9.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
3608
+ after: z9.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
3609
+ last: z9.number().optional().describe("Number of items to return from the end (for backward pagination)"),
3610
+ before: z9.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
3218
3611
  },
3219
3612
  execute: async (args, _ctx) => {
3220
3613
  const client = getGitLabClient();
3221
- const notes = await client.listSnippetNotes(args.project_id, args.snippet_id);
3222
- return JSON.stringify(notes, null, 2);
3614
+ const result = await client.listSnippetNotes(args.project_id, args.snippet_id, {
3615
+ first: args.first,
3616
+ after: args.after,
3617
+ last: args.last,
3618
+ before: args.before
3619
+ });
3620
+ return JSON.stringify(result, null, 2);
3223
3621
  }
3224
3622
  }),
3225
3623
  gitlab_create_snippet_note: tool9({
@@ -3270,9 +3668,13 @@ import { tool as tool10 } from "@opencode-ai/plugin";
3270
3668
  var z10 = tool10.schema;
3271
3669
  var todoTools = {
3272
3670
  gitlab_list_todos: tool10({
3273
- description: `List TODO items for the current user.
3671
+ description: `List TODO items for the current user using GraphQL API with pagination support.
3274
3672
  Returns a list of pending or done TODO items assigned to the authenticated user.
3275
- TODOs are created when you are assigned to an issue/MR, mentioned in a comment, or when someone requests your review.`,
3673
+ TODOs are created when you are assigned to an issue/MR, mentioned in a comment, or when someone requests your review.
3674
+
3675
+ The response includes pagination information (pageInfo) with cursors for fetching additional pages.
3676
+ Use 'after' with the 'endCursor' from pageInfo to get the next page.
3677
+ Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
3276
3678
  args: {
3277
3679
  action: z10.enum([
3278
3680
  "assigned",
@@ -3289,21 +3691,27 @@ TODOs are created when you are assigned to an issue/MR, mentioned in a comment,
3289
3691
  project_id: z10.string().optional().describe("Filter by project ID or path"),
3290
3692
  group_id: z10.string().optional().describe("Filter by group ID"),
3291
3693
  state: z10.enum(["pending", "done"]).optional().describe("Filter by state (default: pending)"),
3292
- type: z10.enum(["Issue", "MergeRequest", "DesignManagement::Design", "Alert"]).optional().describe("Filter by target type"),
3293
- limit: z10.number().optional().describe("Maximum number of results (default: 20)")
3694
+ type: z10.enum(["Issue", "MergeRequest", "DesignManagement::Design", "Alert", "Epic", "Commit"]).optional().describe("Filter by target type"),
3695
+ first: z10.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
3696
+ after: z10.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
3697
+ last: z10.number().optional().describe("Number of items to return from the end (for backward pagination)"),
3698
+ before: z10.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
3294
3699
  },
3295
3700
  execute: async (args, _ctx) => {
3296
3701
  const client = getGitLabClient();
3297
- const todos = await client.listTodos({
3702
+ const result = await client.listTodos({
3298
3703
  action: args.action,
3299
3704
  author_id: args.author_id,
3300
3705
  project_id: args.project_id,
3301
3706
  group_id: args.group_id,
3302
3707
  state: args.state,
3303
3708
  type: args.type,
3304
- limit: args.limit
3709
+ first: args.first,
3710
+ after: args.after,
3711
+ last: args.last,
3712
+ before: args.before
3305
3713
  });
3306
- return JSON.stringify(todos, null, 2);
3714
+ return JSON.stringify(result, null, 2);
3307
3715
  }
3308
3716
  }),
3309
3717
  gitlab_mark_todo_done: tool10({
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.4.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",