@gitlab/opencode-gitlab-plugin 1.3.0 → 1.5.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,26 @@
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.5.0](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.4.0...v1.5.0) (2026-02-02)
6
+
7
+
8
+ ### ✨ Features
9
+
10
+ * migrate discussions to GraphQL with cursor-based pagination ([1c61895](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/1c61895a8f0146f40d7a29a303cdc49e91280280))
11
+
12
+ ## [1.4.0](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.3.0...v1.4.0) (2026-02-02)
13
+
14
+
15
+ ### ✨ Features
16
+
17
+ * **todos:** migrate listTodos to GraphQL API with pagination support ([5726c8b](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/5726c8bb3b8fbce6f014c0f3500bf4d913eb4033))
18
+
19
+
20
+ ### 🐛 Bug Fixes
21
+
22
+ * **todos:** add validation for conflicting pagination parameters ([f46d8c2](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/f46d8c2d4c0f4e4b6363013fc3bcee676b78d777))
23
+ * **todos:** address MR review feedback ([87b3afa](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/87b3afae2992e789c59f0c908c7b48a957e4a931))
24
+
5
25
  ## [1.3.0](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.2.0...v1.3.0) (2026-01-30)
6
26
 
7
27
 
package/dist/index.js CHANGED
@@ -132,6 +132,74 @@ var NOTES_CONNECTION_FRAGMENT = `
132
132
  }
133
133
  `;
134
134
 
135
+ // src/client/discussions-types.ts
136
+ function buildDiscussionsPaginationVariables(options) {
137
+ const variables = {};
138
+ if (options?.first !== void 0) {
139
+ variables.first = options.first;
140
+ } else if (options?.last == null) {
141
+ variables.first = 20;
142
+ }
143
+ if (options?.after) variables.after = options.after;
144
+ if (options?.last !== void 0) variables.last = options.last;
145
+ if (options?.before) variables.before = options.before;
146
+ return variables;
147
+ }
148
+ var DISCUSSION_NOTE_FRAGMENT = `
149
+ fragment DiscussionNoteFields on Note {
150
+ id
151
+ body
152
+ bodyHtml
153
+ createdAt
154
+ updatedAt
155
+ system
156
+ resolvable
157
+ resolved
158
+ resolvedAt
159
+ url
160
+ author {
161
+ id
162
+ username
163
+ name
164
+ avatarUrl
165
+ }
166
+ }
167
+ `;
168
+ var DISCUSSION_FRAGMENT = `
169
+ fragment DiscussionFields on Discussion {
170
+ id
171
+ replyId
172
+ createdAt
173
+ resolved
174
+ resolvable
175
+ resolvedAt
176
+ resolvedBy {
177
+ id
178
+ username
179
+ name
180
+ avatarUrl
181
+ }
182
+ notes {
183
+ nodes {
184
+ ...DiscussionNoteFields
185
+ }
186
+ }
187
+ }
188
+ `;
189
+ var DISCUSSIONS_CONNECTION_FRAGMENT = `
190
+ fragment DiscussionsConnectionFields on DiscussionConnection {
191
+ pageInfo {
192
+ hasNextPage
193
+ hasPreviousPage
194
+ startCursor
195
+ endCursor
196
+ }
197
+ nodes {
198
+ ...DiscussionFields
199
+ }
200
+ }
201
+ `;
202
+
135
203
  // src/client/merge-requests.ts
136
204
  var LIST_MR_NOTES_QUERY = `
137
205
  ${NOTES_FRAGMENT}
@@ -160,6 +228,32 @@ var LIST_MR_NOTES_QUERY = `
160
228
  }
161
229
  }
162
230
  `;
231
+ var LIST_MR_DISCUSSIONS_QUERY = `
232
+ ${DISCUSSION_NOTE_FRAGMENT}
233
+ ${DISCUSSION_FRAGMENT}
234
+ ${DISCUSSIONS_CONNECTION_FRAGMENT}
235
+ query listMrDiscussions(
236
+ $projectPath: ID!
237
+ $mrIid: String!
238
+ $first: Int
239
+ $after: String
240
+ $last: Int
241
+ $before: String
242
+ ) {
243
+ project(fullPath: $projectPath) {
244
+ mergeRequest(iid: $mrIid) {
245
+ discussions(
246
+ first: $first
247
+ after: $after
248
+ last: $last
249
+ before: $before
250
+ ) {
251
+ ...DiscussionsConnectionFields
252
+ }
253
+ }
254
+ }
255
+ }
256
+ `;
163
257
  var SET_AUTO_MERGE_MUTATION = `
164
258
  mutation setAutoMerge(
165
259
  $projectPath: ID!
@@ -218,12 +312,41 @@ var MergeRequestsClient = class extends GitLabApiClient {
218
312
  `/projects/${encodedProject}/merge_requests/${mrIid}/changes`
219
313
  );
220
314
  }
221
- async listMrDiscussions(projectId, mrIid) {
222
- const encodedProject = this.encodeProjectId(projectId);
223
- return this.fetch(
224
- "GET",
225
- `/projects/${encodedProject}/merge_requests/${mrIid}/discussions`
226
- );
315
+ /**
316
+ * List notes on a merge request using GraphQL API with pagination support
317
+ */
318
+ async listMrNotes(projectId, mrIid, options) {
319
+ const variables = {
320
+ projectPath: projectId,
321
+ mrIid: String(mrIid),
322
+ ...buildPaginationVariables(options)
323
+ };
324
+ const result = await this.fetchGraphQL(LIST_MR_NOTES_QUERY, variables);
325
+ const notes = result.project?.mergeRequest?.notes;
326
+ if (!notes) {
327
+ throw new Error("Merge request not found or access denied");
328
+ }
329
+ return {
330
+ notes,
331
+ pageInfo: notes.pageInfo,
332
+ totalCount: notes.count
333
+ };
334
+ }
335
+ /**
336
+ * List discussions on a merge request using GraphQL API with pagination support
337
+ */
338
+ async listMrDiscussions(projectId, mrIid, options) {
339
+ const variables = {
340
+ projectPath: projectId,
341
+ mrIid: String(mrIid),
342
+ ...buildDiscussionsPaginationVariables(options)
343
+ };
344
+ const result = await this.fetchGraphQL(LIST_MR_DISCUSSIONS_QUERY, variables);
345
+ const discussions = result.project?.mergeRequest?.discussions;
346
+ if (!discussions) {
347
+ throw new Error("Merge request not found or access denied");
348
+ }
349
+ return { discussions };
227
350
  }
228
351
  async getMrDiscussion(projectId, mrIid, discussionId) {
229
352
  const encodedProject = this.encodeProjectId(projectId);
@@ -260,26 +383,6 @@ var MergeRequestsClient = class extends GitLabApiClient {
260
383
  requestBody
261
384
  );
262
385
  }
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
- };
282
- }
283
386
  async createMrNote(projectId, mrIid, body, discussionId) {
284
387
  const encodedProject = this.encodeProjectId(projectId);
285
388
  if (discussionId) {
@@ -383,6 +486,32 @@ var LIST_ISSUE_NOTES_QUERY = `
383
486
  }
384
487
  }
385
488
  `;
489
+ var LIST_ISSUE_DISCUSSIONS_QUERY = `
490
+ ${DISCUSSION_NOTE_FRAGMENT}
491
+ ${DISCUSSION_FRAGMENT}
492
+ ${DISCUSSIONS_CONNECTION_FRAGMENT}
493
+ query listIssueDiscussions(
494
+ $projectPath: ID!
495
+ $issueIid: String!
496
+ $first: Int
497
+ $after: String
498
+ $last: Int
499
+ $before: String
500
+ ) {
501
+ project(fullPath: $projectPath) {
502
+ issue(iid: $issueIid) {
503
+ discussions(
504
+ first: $first
505
+ after: $after
506
+ last: $last
507
+ before: $before
508
+ ) {
509
+ ...DiscussionsConnectionFields
510
+ }
511
+ }
512
+ }
513
+ }
514
+ `;
386
515
  var IssuesClient = class extends GitLabApiClient {
387
516
  async createIssue(projectId, title, options) {
388
517
  const encodedProject = this.encodeProjectId(projectId);
@@ -438,12 +567,21 @@ var IssuesClient = class extends GitLabApiClient {
438
567
  totalCount: notes.count
439
568
  };
440
569
  }
441
- async listIssueDiscussions(projectId, issueIid) {
442
- const encodedProject = this.encodeProjectId(projectId);
443
- return this.fetch(
444
- "GET",
445
- `/projects/${encodedProject}/issues/${issueIid}/discussions`
446
- );
570
+ /**
571
+ * List discussions on an issue using GraphQL API with pagination support
572
+ */
573
+ async listIssueDiscussions(projectId, issueIid, options) {
574
+ const variables = {
575
+ projectPath: projectId,
576
+ issueIid: String(issueIid),
577
+ ...buildDiscussionsPaginationVariables(options)
578
+ };
579
+ const result = await this.fetchGraphQL(LIST_ISSUE_DISCUSSIONS_QUERY, variables);
580
+ const discussions = result.project?.issue?.discussions;
581
+ if (!discussions) {
582
+ throw new Error("Issue not found or access denied");
583
+ }
584
+ return { discussions };
447
585
  }
448
586
  async getIssueDiscussion(projectId, issueIid, discussionId) {
449
587
  const encodedProject = this.encodeProjectId(projectId);
@@ -1206,20 +1344,163 @@ var SecurityClient = class extends GitLabApiClient {
1206
1344
  };
1207
1345
 
1208
1346
  // src/client/todos.ts
1347
+ var LIST_TODOS_QUERY = `
1348
+ query listTodos(
1349
+ $state: [TodoStateEnum!]
1350
+ $action: [TodoActionEnum!]
1351
+ $type: [TodoTargetEnum!]
1352
+ $projectId: [ID!]
1353
+ $groupId: [ID!]
1354
+ $authorId: [ID!]
1355
+ $first: Int
1356
+ $after: String
1357
+ $last: Int
1358
+ $before: String
1359
+ ) {
1360
+ currentUser {
1361
+ todos(
1362
+ state: $state
1363
+ action: $action
1364
+ type: $type
1365
+ projectId: $projectId
1366
+ groupId: $groupId
1367
+ authorId: $authorId
1368
+ first: $first
1369
+ after: $after
1370
+ last: $last
1371
+ before: $before
1372
+ ) {
1373
+ count
1374
+ pageInfo {
1375
+ hasNextPage
1376
+ hasPreviousPage
1377
+ startCursor
1378
+ endCursor
1379
+ }
1380
+ nodes {
1381
+ id
1382
+ body
1383
+ state
1384
+ action
1385
+ createdAt
1386
+ targetType
1387
+ targetUrl
1388
+ snoozedUntil
1389
+ project {
1390
+ id
1391
+ name
1392
+ fullPath
1393
+ }
1394
+ group {
1395
+ id
1396
+ name
1397
+ fullPath
1398
+ }
1399
+ author {
1400
+ id
1401
+ username
1402
+ name
1403
+ avatarUrl
1404
+ }
1405
+ targetEntity {
1406
+ __typename
1407
+ ... on Issue {
1408
+ id
1409
+ title
1410
+ iid
1411
+ }
1412
+ ... on MergeRequest {
1413
+ id
1414
+ title
1415
+ iid
1416
+ }
1417
+ ... on Epic {
1418
+ id
1419
+ title
1420
+ iid
1421
+ }
1422
+ ... on Commit {
1423
+ id
1424
+ title
1425
+ }
1426
+ ... on DesignManagement__Design {
1427
+ id
1428
+ }
1429
+ ... on AlertManagement__Alert {
1430
+ id
1431
+ title
1432
+ iid
1433
+ }
1434
+ }
1435
+ }
1436
+ }
1437
+ }
1438
+ }
1439
+ `;
1209
1440
  var TodosClient = class extends GitLabApiClient {
1210
1441
  async listTodos(options) {
1211
- const params = new URLSearchParams();
1212
- params.set("per_page", String(options?.limit || 20));
1213
- if (options?.action) params.set("action", options.action);
1214
- if (options?.author_id) params.set("author_id", String(options.author_id));
1442
+ if (options?.first !== void 0 && options?.last !== void 0) {
1443
+ throw new Error(
1444
+ 'Cannot specify both "first" and "last" pagination parameters. Use "first"/"after" for forward pagination or "last"/"before" for backward pagination.'
1445
+ );
1446
+ }
1447
+ const variables = {};
1448
+ if (options?.first !== void 0) {
1449
+ variables.first = options.first;
1450
+ } else if (options?.last === void 0) {
1451
+ variables.first = 20;
1452
+ }
1453
+ if (options?.after) variables.after = options.after;
1454
+ if (options?.last !== void 0) variables.last = options.last;
1455
+ if (options?.before) variables.before = options.before;
1456
+ if (options?.action) {
1457
+ variables.action = [this.mapTodoAction(options.action)];
1458
+ }
1459
+ if (options?.author_id) {
1460
+ variables.authorId = [`gid://gitlab/User/${options.author_id}`];
1461
+ }
1215
1462
  if (options?.project_id) {
1216
- const encodedProject = this.encodeProjectId(options.project_id);
1217
- params.set("project_id", encodedProject);
1463
+ variables.projectId = [options.project_id];
1218
1464
  }
1219
- if (options?.group_id) params.set("group_id", options.group_id);
1220
- if (options?.state) params.set("state", options.state);
1221
- if (options?.type) params.set("type", options.type);
1222
- return this.fetch("GET", `/todos?${params}`);
1465
+ if (options?.group_id) {
1466
+ variables.groupId = [options.group_id];
1467
+ }
1468
+ if (options?.state) {
1469
+ variables.state = [options.state.toLowerCase()];
1470
+ }
1471
+ if (options?.type) {
1472
+ variables.type = [this.mapTodoTargetType(options.type)];
1473
+ }
1474
+ const result = await this.fetchGraphQL(LIST_TODOS_QUERY, variables);
1475
+ const todos = result.currentUser.todos;
1476
+ return {
1477
+ todos
1478
+ };
1479
+ }
1480
+ mapTodoAction(action) {
1481
+ const actionMap = {
1482
+ assigned: "ASSIGNED",
1483
+ mentioned: "MENTIONED",
1484
+ build_failed: "BUILD_FAILED",
1485
+ marked: "MARKED",
1486
+ approval_required: "APPROVAL_REQUIRED",
1487
+ unmergeable: "UNMERGEABLE",
1488
+ directly_addressed: "DIRECTLY_ADDRESSED",
1489
+ merge_train_removed: "MERGE_TRAIN_REMOVED",
1490
+ review_requested: "REVIEW_REQUESTED"
1491
+ };
1492
+ return actionMap[action] || action.toUpperCase();
1493
+ }
1494
+ mapTodoTargetType(type) {
1495
+ const typeMap = {
1496
+ Issue: "ISSUE",
1497
+ MergeRequest: "MERGEREQUEST",
1498
+ "DesignManagement::Design": "DESIGN",
1499
+ Alert: "ALERT",
1500
+ Commit: "COMMIT",
1501
+ Epic: "EPIC"
1502
+ };
1503
+ return typeMap[type] || type;
1223
1504
  }
1224
1505
  async markTodoAsDone(todoId) {
1225
1506
  return this.fetch("POST", `/todos/${todoId}/mark_as_done`);
@@ -1228,7 +1509,8 @@ var TodosClient = class extends GitLabApiClient {
1228
1509
  return this.fetch("POST", "/todos/mark_as_done");
1229
1510
  }
1230
1511
  async getTodoCount() {
1231
- return this.fetch("GET", "/todos/count");
1512
+ const result = await this.fetchGraphQL(`query { currentUser { todos(state: [pending]) { count } } }`);
1513
+ return { count: result.currentUser.todos.count };
1232
1514
  }
1233
1515
  };
1234
1516
 
@@ -1238,7 +1520,7 @@ var LIST_EPIC_NOTES_QUERY = `
1238
1520
  ${NOTES_CONNECTION_FRAGMENT}
1239
1521
  query listEpicNotes(
1240
1522
  $groupPath: ID!
1241
- $epicIid: ID!
1523
+ $epicIid: String!
1242
1524
  $first: Int
1243
1525
  $after: String
1244
1526
  $last: Int
@@ -1258,6 +1540,32 @@ var LIST_EPIC_NOTES_QUERY = `
1258
1540
  }
1259
1541
  }
1260
1542
  `;
1543
+ var LIST_EPIC_DISCUSSIONS_QUERY = `
1544
+ ${DISCUSSION_NOTE_FRAGMENT}
1545
+ ${DISCUSSION_FRAGMENT}
1546
+ ${DISCUSSIONS_CONNECTION_FRAGMENT}
1547
+ query listEpicDiscussions(
1548
+ $groupPath: ID!
1549
+ $epicIid: String!
1550
+ $first: Int
1551
+ $after: String
1552
+ $last: Int
1553
+ $before: String
1554
+ ) {
1555
+ group(fullPath: $groupPath) {
1556
+ epic(iid: $epicIid) {
1557
+ discussions(
1558
+ first: $first
1559
+ after: $after
1560
+ last: $last
1561
+ before: $before
1562
+ ) {
1563
+ ...DiscussionsConnectionFields
1564
+ }
1565
+ }
1566
+ }
1567
+ }
1568
+ `;
1261
1569
  var EpicsClient = class extends GitLabApiClient {
1262
1570
  async getEpic(groupId, epicIid) {
1263
1571
  const encodedGroup = encodeURIComponent(groupId);
@@ -1326,12 +1634,21 @@ var EpicsClient = class extends GitLabApiClient {
1326
1634
  totalCount: notes.count
1327
1635
  };
1328
1636
  }
1329
- async listEpicDiscussions(groupId, epicIid) {
1330
- const encodedGroup = encodeURIComponent(groupId);
1331
- return this.fetch(
1332
- "GET",
1333
- `/groups/${encodedGroup}/epics/${epicIid}/discussions`
1334
- );
1637
+ /**
1638
+ * List discussions on an epic using GraphQL API with pagination support
1639
+ */
1640
+ async listEpicDiscussions(groupId, epicIid, options) {
1641
+ const variables = {
1642
+ groupPath: groupId,
1643
+ epicIid: String(epicIid),
1644
+ ...buildDiscussionsPaginationVariables(options)
1645
+ };
1646
+ const result = await this.fetchGraphQL(LIST_EPIC_DISCUSSIONS_QUERY, variables);
1647
+ const discussions = result.group?.epic?.discussions;
1648
+ if (!discussions) {
1649
+ throw new Error("Epic not found or access denied");
1650
+ }
1651
+ return { discussions };
1335
1652
  }
1336
1653
  async getEpicDiscussion(groupId, epicIid, discussionId) {
1337
1654
  const encodedGroup = encodeURIComponent(groupId);
@@ -1389,21 +1706,32 @@ var LIST_SNIPPET_NOTES_QUERY = `
1389
1706
  }
1390
1707
  }
1391
1708
  `;
1392
- var SnippetsClient = class extends GitLabApiClient {
1393
- async listSnippetDiscussions(projectId, snippetId) {
1394
- const encodedProject = this.encodeProjectId(projectId);
1395
- return this.fetch(
1396
- "GET",
1397
- `/projects/${encodedProject}/snippets/${snippetId}/discussions`
1398
- );
1399
- }
1400
- async getSnippetDiscussion(projectId, snippetId, discussionId) {
1401
- const encodedProject = this.encodeProjectId(projectId);
1402
- return this.fetch(
1403
- "GET",
1404
- `/projects/${encodedProject}/snippets/${snippetId}/discussions/${discussionId}`
1405
- );
1709
+ var LIST_SNIPPET_DISCUSSIONS_QUERY = `
1710
+ ${DISCUSSION_NOTE_FRAGMENT}
1711
+ ${DISCUSSION_FRAGMENT}
1712
+ ${DISCUSSIONS_CONNECTION_FRAGMENT}
1713
+ query listSnippetDiscussions(
1714
+ $snippetGid: SnippetID!
1715
+ $first: Int
1716
+ $after: String
1717
+ $last: Int
1718
+ $before: String
1719
+ ) {
1720
+ snippets(ids: [$snippetGid]) {
1721
+ nodes {
1722
+ discussions(
1723
+ first: $first
1724
+ after: $after
1725
+ last: $last
1726
+ before: $before
1727
+ ) {
1728
+ ...DiscussionsConnectionFields
1729
+ }
1730
+ }
1731
+ }
1406
1732
  }
1733
+ `;
1734
+ var SnippetsClient = class extends GitLabApiClient {
1407
1735
  /**
1408
1736
  * List notes on a snippet using GraphQL API with pagination support
1409
1737
  */
@@ -1429,6 +1757,33 @@ var SnippetsClient = class extends GitLabApiClient {
1429
1757
  totalCount: notes.count
1430
1758
  };
1431
1759
  }
1760
+ /**
1761
+ * List discussions on a snippet using GraphQL API with pagination support
1762
+ */
1763
+ async listSnippetDiscussions(projectId, snippetId, options) {
1764
+ const variables = {
1765
+ snippetGid: `gid://gitlab/ProjectSnippet/${snippetId}`,
1766
+ ...buildDiscussionsPaginationVariables(options)
1767
+ };
1768
+ const result = await this.fetchGraphQL(LIST_SNIPPET_DISCUSSIONS_QUERY, variables);
1769
+ const discussions = result.snippets.nodes[0]?.discussions || {
1770
+ nodes: [],
1771
+ pageInfo: {
1772
+ hasNextPage: false,
1773
+ hasPreviousPage: false,
1774
+ startCursor: null,
1775
+ endCursor: null
1776
+ }
1777
+ };
1778
+ return { discussions };
1779
+ }
1780
+ async getSnippetDiscussion(projectId, snippetId, discussionId) {
1781
+ const encodedProject = this.encodeProjectId(projectId);
1782
+ return this.fetch(
1783
+ "GET",
1784
+ `/projects/${encodedProject}/snippets/${snippetId}/discussions/${discussionId}`
1785
+ );
1786
+ }
1432
1787
  async createSnippetNote(projectId, snippetId, body, discussionId) {
1433
1788
  const encodedProject = this.encodeProjectId(projectId);
1434
1789
  if (discussionId) {
@@ -1772,11 +2127,20 @@ Returns all discussion threads with nested notes. Each discussion contains a 'no
1772
2127
  Note: For a flattened list of all comments, use gitlab_list_mr_notes instead.`,
1773
2128
  args: {
1774
2129
  project_id: z.string().describe("The project ID or URL-encoded path"),
1775
- mr_iid: z.number().describe("The internal ID of the merge request")
2130
+ mr_iid: z.number().describe("The internal ID of the merge request"),
2131
+ first: z.number().optional().describe("Number of discussions to fetch from the beginning (default: 20)"),
2132
+ after: z.string().optional().describe("Cursor for forward pagination (from pageInfo.endCursor)"),
2133
+ last: z.number().optional().describe("Number of discussions to fetch from the end"),
2134
+ before: z.string().optional().describe("Cursor for backward pagination (from pageInfo.startCursor)")
1776
2135
  },
1777
2136
  execute: async (args, _ctx) => {
1778
2137
  const client = getGitLabClient();
1779
- const discussions = await client.listMrDiscussions(args.project_id, args.mr_iid);
2138
+ const discussions = await client.listMrDiscussions(args.project_id, args.mr_iid, {
2139
+ first: args.first,
2140
+ after: args.after,
2141
+ last: args.last,
2142
+ before: args.before
2143
+ });
1780
2144
  return JSON.stringify(discussions, null, 2);
1781
2145
  }
1782
2146
  }),
@@ -2172,11 +2536,20 @@ Returns all discussion threads with nested notes. Each discussion contains a 'no
2172
2536
  Use the discussion 'id' field to reply to a specific thread with gitlab_create_issue_note.`,
2173
2537
  args: {
2174
2538
  project_id: z2.string().describe("The project ID or URL-encoded path"),
2175
- issue_iid: z2.number().describe("The internal ID of the issue")
2539
+ issue_iid: z2.number().describe("The internal ID of the issue"),
2540
+ first: z2.number().optional().describe("Number of discussions to fetch from the beginning (default: 20)"),
2541
+ after: z2.string().optional().describe("Cursor for forward pagination (from pageInfo.endCursor)"),
2542
+ last: z2.number().optional().describe("Number of discussions to fetch from the end"),
2543
+ before: z2.string().optional().describe("Cursor for backward pagination (from pageInfo.startCursor)")
2176
2544
  },
2177
2545
  execute: async (args, _ctx) => {
2178
2546
  const client = getGitLabClient();
2179
- const discussions = await client.listIssueDiscussions(args.project_id, args.issue_iid);
2547
+ const discussions = await client.listIssueDiscussions(args.project_id, args.issue_iid, {
2548
+ first: args.first,
2549
+ after: args.after,
2550
+ last: args.last,
2551
+ before: args.before
2552
+ });
2180
2553
  return JSON.stringify(discussions, null, 2);
2181
2554
  }
2182
2555
  }),
@@ -2449,11 +2822,20 @@ Returns all discussion threads with nested notes. Each discussion contains a 'no
2449
2822
  Use the discussion 'id' field to reply to a specific thread with gitlab_create_epic_note.`,
2450
2823
  args: {
2451
2824
  group_id: z3.string().describe("The group ID or URL-encoded path"),
2452
- epic_iid: z3.number().describe("The internal ID of the epic")
2825
+ epic_iid: z3.number().describe("The internal ID of the epic"),
2826
+ first: z3.number().optional().describe("Number of discussions to fetch from the beginning (default: 20)"),
2827
+ after: z3.string().optional().describe("Cursor for forward pagination (from pageInfo.endCursor)"),
2828
+ last: z3.number().optional().describe("Number of discussions to fetch from the end"),
2829
+ before: z3.string().optional().describe("Cursor for backward pagination (from pageInfo.startCursor)")
2453
2830
  },
2454
2831
  execute: async (args, _ctx) => {
2455
2832
  const client = getGitLabClient();
2456
- const discussions = await client.listEpicDiscussions(args.group_id, args.epic_iid);
2833
+ const discussions = await client.listEpicDiscussions(args.group_id, args.epic_iid, {
2834
+ first: args.first,
2835
+ after: args.after,
2836
+ last: args.last,
2837
+ before: args.before
2838
+ });
2457
2839
  return JSON.stringify(discussions, null, 2);
2458
2840
  }
2459
2841
  }),
@@ -3423,11 +3805,20 @@ Returns all discussion threads with nested notes. Each discussion contains a 'no
3423
3805
  Use the discussion 'id' field to reply to a specific thread with gitlab_create_snippet_note.`,
3424
3806
  args: {
3425
3807
  project_id: z9.string().describe("The project ID or URL-encoded path"),
3426
- snippet_id: z9.number().describe("The ID of the snippet")
3808
+ snippet_id: z9.number().describe("The ID of the snippet"),
3809
+ first: z9.number().optional().describe("Number of discussions to fetch from the beginning (default: 20)"),
3810
+ after: z9.string().optional().describe("Cursor for forward pagination (from pageInfo.endCursor)"),
3811
+ last: z9.number().optional().describe("Number of discussions to fetch from the end"),
3812
+ before: z9.string().optional().describe("Cursor for backward pagination (from pageInfo.startCursor)")
3427
3813
  },
3428
3814
  execute: async (args, _ctx) => {
3429
3815
  const client = getGitLabClient();
3430
- const discussions = await client.listSnippetDiscussions(args.project_id, args.snippet_id);
3816
+ const discussions = await client.listSnippetDiscussions(args.project_id, args.snippet_id, {
3817
+ first: args.first,
3818
+ after: args.after,
3819
+ last: args.last,
3820
+ before: args.before
3821
+ });
3431
3822
  return JSON.stringify(discussions, null, 2);
3432
3823
  }
3433
3824
  }),
@@ -3524,9 +3915,13 @@ import { tool as tool10 } from "@opencode-ai/plugin";
3524
3915
  var z10 = tool10.schema;
3525
3916
  var todoTools = {
3526
3917
  gitlab_list_todos: tool10({
3527
- description: `List TODO items for the current user.
3918
+ description: `List TODO items for the current user using GraphQL API with pagination support.
3528
3919
  Returns a list of pending or done TODO items assigned to the authenticated user.
3529
- TODOs are created when you are assigned to an issue/MR, mentioned in a comment, or when someone requests your review.`,
3920
+ TODOs are created when you are assigned to an issue/MR, mentioned in a comment, or when someone requests your review.
3921
+
3922
+ The response includes pagination information (pageInfo) with cursors for fetching additional pages.
3923
+ Use 'after' with the 'endCursor' from pageInfo to get the next page.
3924
+ Use 'before' with the 'startCursor' from pageInfo to get the previous page.`,
3530
3925
  args: {
3531
3926
  action: z10.enum([
3532
3927
  "assigned",
@@ -3543,21 +3938,27 @@ TODOs are created when you are assigned to an issue/MR, mentioned in a comment,
3543
3938
  project_id: z10.string().optional().describe("Filter by project ID or path"),
3544
3939
  group_id: z10.string().optional().describe("Filter by group ID"),
3545
3940
  state: z10.enum(["pending", "done"]).optional().describe("Filter by state (default: pending)"),
3546
- type: z10.enum(["Issue", "MergeRequest", "DesignManagement::Design", "Alert"]).optional().describe("Filter by target type"),
3547
- limit: z10.number().optional().describe("Maximum number of results (default: 20)")
3941
+ type: z10.enum(["Issue", "MergeRequest", "DesignManagement::Design", "Alert", "Epic", "Commit"]).optional().describe("Filter by target type"),
3942
+ first: z10.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
3943
+ after: z10.string().optional().describe("Cursor for forward pagination - use endCursor from previous response"),
3944
+ last: z10.number().optional().describe("Number of items to return from the end (for backward pagination)"),
3945
+ before: z10.string().optional().describe("Cursor for backward pagination - use startCursor from previous response")
3548
3946
  },
3549
3947
  execute: async (args, _ctx) => {
3550
3948
  const client = getGitLabClient();
3551
- const todos = await client.listTodos({
3949
+ const result = await client.listTodos({
3552
3950
  action: args.action,
3553
3951
  author_id: args.author_id,
3554
3952
  project_id: args.project_id,
3555
3953
  group_id: args.group_id,
3556
3954
  state: args.state,
3557
3955
  type: args.type,
3558
- limit: args.limit
3956
+ first: args.first,
3957
+ after: args.after,
3958
+ last: args.last,
3959
+ before: args.before
3559
3960
  });
3560
- return JSON.stringify(todos, null, 2);
3961
+ return JSON.stringify(result, null, 2);
3561
3962
  }
3562
3963
  }),
3563
3964
  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.3.0",
3
+ "version": "1.5.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",