@gitlab/opencode-gitlab-plugin 1.6.1 → 1.7.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.7.0](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.6.2...v1.7.0) (2026-02-24)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### ✨ Features
|
|
9
|
+
|
|
10
|
+
* **award-emoji:** add tools for managing reactions on resources ([77b8ad5](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/77b8ad5656b681c3a6c9395f2a0025126e4c39bb))
|
|
11
|
+
* **discussions:** use GraphQL for discussion resolution ([b6ed342](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/b6ed342cea8f726b14a2850350d217a937406caa))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### 🐛 Bug Fixes
|
|
15
|
+
|
|
16
|
+
* **todos:** handle 404 gracefully when marking todo as done ([2a26763](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/2a267632012d89884ebc3462ac0adafc5ffd9286))
|
|
17
|
+
|
|
18
|
+
## [1.6.2](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.6.1...v1.6.2) (2026-02-04)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### 🐛 Bug Fixes
|
|
22
|
+
|
|
23
|
+
* resolved filter now correctly filters resolvable discussions only ([59defec](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/commit/59defec6c1c1d1536ea659a536b2bde3e89c95c5))
|
|
24
|
+
|
|
5
25
|
## [1.6.1](https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-plugin/compare/v1.6.0...v1.6.1) (2026-02-04)
|
|
6
26
|
|
|
7
27
|
|
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -277,6 +277,23 @@ var SET_AUTO_MERGE_MUTATION = `
|
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
279
|
`;
|
|
280
|
+
var RESOLVE_DISCUSSION_MUTATION = `
|
|
281
|
+
mutation resolveDiscussion($discussionId: DiscussionID!, $resolve: Boolean!) {
|
|
282
|
+
discussionToggleResolve(input: { id: $discussionId, resolve: $resolve }) {
|
|
283
|
+
discussion {
|
|
284
|
+
id
|
|
285
|
+
resolved
|
|
286
|
+
resolvedAt
|
|
287
|
+
resolvedBy {
|
|
288
|
+
id
|
|
289
|
+
username
|
|
290
|
+
name
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
errors
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
`;
|
|
280
297
|
var MergeRequestsClient = class extends GitLabApiClient {
|
|
281
298
|
async getMergeRequest(projectId, mrIid, includeChanges) {
|
|
282
299
|
const encodedProject = this.encodeProjectId(projectId);
|
|
@@ -368,6 +385,32 @@ var MergeRequestsClient = class extends GitLabApiClient {
|
|
|
368
385
|
{ resolved: false }
|
|
369
386
|
);
|
|
370
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* Resolve or unresolve a discussion using GraphQL API.
|
|
390
|
+
* This works for all discussions including outdated ones (resolved: null)
|
|
391
|
+
* which the REST API cannot handle.
|
|
392
|
+
*
|
|
393
|
+
* @param discussionId - The discussion ID (can be short ID or full gid://gitlab/Discussion/ID format)
|
|
394
|
+
* @param resolve - Whether to resolve (true) or unresolve (false) the discussion
|
|
395
|
+
*/
|
|
396
|
+
async toggleDiscussionResolved(discussionId, resolve2) {
|
|
397
|
+
const gid = discussionId.startsWith("gid://") ? discussionId : `gid://gitlab/Discussion/${discussionId}`;
|
|
398
|
+
const result = await this.fetchGraphQL(RESOLVE_DISCUSSION_MUTATION, {
|
|
399
|
+
discussionId: gid,
|
|
400
|
+
resolve: resolve2
|
|
401
|
+
});
|
|
402
|
+
if (result.discussionToggleResolve.errors.length > 0) {
|
|
403
|
+
throw new Error(
|
|
404
|
+
`Failed to ${resolve2 ? "resolve" : "unresolve"} discussion: ${result.discussionToggleResolve.errors.join(", ")}`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
if (!result.discussionToggleResolve.discussion) {
|
|
408
|
+
throw new Error(
|
|
409
|
+
`Failed to ${resolve2 ? "resolve" : "unresolve"} discussion: No discussion returned`
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
return result.discussionToggleResolve.discussion;
|
|
413
|
+
}
|
|
371
414
|
async createMrDiscussion(projectId, mrIid, body, position) {
|
|
372
415
|
const encodedProject = this.encodeProjectId(projectId);
|
|
373
416
|
const requestBody = { body };
|
|
@@ -425,6 +468,32 @@ var MergeRequestsClient = class extends GitLabApiClient {
|
|
|
425
468
|
`/projects/${encodedProject}/merge_requests/${mrIid}/pipelines`
|
|
426
469
|
);
|
|
427
470
|
}
|
|
471
|
+
/**
|
|
472
|
+
* Add a reviewer to a merge request without affecting existing reviewers
|
|
473
|
+
*/
|
|
474
|
+
async addReviewer(projectId, mrIid, reviewerId) {
|
|
475
|
+
const mr = await this.getMergeRequest(projectId, mrIid);
|
|
476
|
+
const currentReviewers = mr.reviewers || [];
|
|
477
|
+
const currentReviewerIds = currentReviewers.map((r) => r.id);
|
|
478
|
+
if (currentReviewerIds.includes(reviewerId)) {
|
|
479
|
+
return mr;
|
|
480
|
+
}
|
|
481
|
+
const newReviewerIds = [...currentReviewerIds, reviewerId];
|
|
482
|
+
return this.updateMergeRequest(projectId, mrIid, { reviewer_ids: newReviewerIds });
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Remove a reviewer from a merge request without affecting other reviewers
|
|
486
|
+
*/
|
|
487
|
+
async removeReviewer(projectId, mrIid, reviewerId) {
|
|
488
|
+
const mr = await this.getMergeRequest(projectId, mrIid);
|
|
489
|
+
const currentReviewers = mr.reviewers || [];
|
|
490
|
+
const currentReviewerIds = currentReviewers.map((r) => r.id);
|
|
491
|
+
if (!currentReviewerIds.includes(reviewerId)) {
|
|
492
|
+
return mr;
|
|
493
|
+
}
|
|
494
|
+
const newReviewerIds = currentReviewerIds.filter((id) => id !== reviewerId);
|
|
495
|
+
return this.updateMergeRequest(projectId, mrIid, { reviewer_ids: newReviewerIds });
|
|
496
|
+
}
|
|
428
497
|
async listMergeRequestDiffs(projectId, mrIid, options = {}) {
|
|
429
498
|
const encodedProject = this.encodeProjectId(projectId);
|
|
430
499
|
const params = new URLSearchParams();
|
|
@@ -1468,7 +1537,21 @@ var TodosClient = class extends GitLabApiClient {
|
|
|
1468
1537
|
return typeMap[type] || type;
|
|
1469
1538
|
}
|
|
1470
1539
|
async markTodoAsDone(todoId) {
|
|
1471
|
-
|
|
1540
|
+
try {
|
|
1541
|
+
const result = await this.fetch(
|
|
1542
|
+
"POST",
|
|
1543
|
+
`/todos/${todoId}/mark_as_done`
|
|
1544
|
+
);
|
|
1545
|
+
return { success: true, todo: result };
|
|
1546
|
+
} catch (error) {
|
|
1547
|
+
if (error instanceof Error && error.message.includes("404")) {
|
|
1548
|
+
return {
|
|
1549
|
+
success: false,
|
|
1550
|
+
message: `Todo ${todoId} not found. It may have already been marked as done or does not exist.`
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
throw error;
|
|
1554
|
+
}
|
|
1472
1555
|
}
|
|
1473
1556
|
async markAllTodosAsDone() {
|
|
1474
1557
|
return this.fetch("POST", "/todos/mark_as_done");
|
|
@@ -1963,6 +2046,63 @@ var AuditClient = class extends GitLabApiClient {
|
|
|
1963
2046
|
}
|
|
1964
2047
|
};
|
|
1965
2048
|
|
|
2049
|
+
// src/client/award-emoji.ts
|
|
2050
|
+
var AwardEmojiClient = class extends GitLabApiClient {
|
|
2051
|
+
/**
|
|
2052
|
+
* Build the base path for award emoji operations
|
|
2053
|
+
*/
|
|
2054
|
+
buildAwardEmojiPath(resource) {
|
|
2055
|
+
const encodedProject = this.encodeProjectId(resource.projectId);
|
|
2056
|
+
let basePath;
|
|
2057
|
+
switch (resource.resourceType) {
|
|
2058
|
+
case "merge_request":
|
|
2059
|
+
basePath = `/projects/${encodedProject}/merge_requests/${resource.resourceIid}`;
|
|
2060
|
+
break;
|
|
2061
|
+
case "issue":
|
|
2062
|
+
basePath = `/projects/${encodedProject}/issues/${resource.resourceIid}`;
|
|
2063
|
+
break;
|
|
2064
|
+
case "snippet":
|
|
2065
|
+
basePath = `/projects/${encodedProject}/snippets/${resource.resourceIid}`;
|
|
2066
|
+
break;
|
|
2067
|
+
}
|
|
2068
|
+
if (resource.noteId) {
|
|
2069
|
+
return `${basePath}/notes/${resource.noteId}/award_emoji`;
|
|
2070
|
+
}
|
|
2071
|
+
return `${basePath}/award_emoji`;
|
|
2072
|
+
}
|
|
2073
|
+
/**
|
|
2074
|
+
* List all award emoji on a resource or note
|
|
2075
|
+
*/
|
|
2076
|
+
async listAwardEmoji(resource) {
|
|
2077
|
+
const path2 = this.buildAwardEmojiPath(resource);
|
|
2078
|
+
return this.fetch("GET", path2);
|
|
2079
|
+
}
|
|
2080
|
+
/**
|
|
2081
|
+
* Get a single award emoji by ID
|
|
2082
|
+
*/
|
|
2083
|
+
async getAwardEmoji(resource, awardId) {
|
|
2084
|
+
const path2 = `${this.buildAwardEmojiPath(resource)}/${awardId}`;
|
|
2085
|
+
return this.fetch("GET", path2);
|
|
2086
|
+
}
|
|
2087
|
+
/**
|
|
2088
|
+
* Add an award emoji (reaction) to a resource or note
|
|
2089
|
+
*
|
|
2090
|
+
* @param resource - The resource to add the emoji to
|
|
2091
|
+
* @param name - The emoji name without colons (e.g., 'thumbsup', 'rocket', 'eyes')
|
|
2092
|
+
*/
|
|
2093
|
+
async createAwardEmoji(resource, name) {
|
|
2094
|
+
const path2 = this.buildAwardEmojiPath(resource);
|
|
2095
|
+
return this.fetch("POST", path2, { name });
|
|
2096
|
+
}
|
|
2097
|
+
/**
|
|
2098
|
+
* Remove an award emoji from a resource or note
|
|
2099
|
+
*/
|
|
2100
|
+
async deleteAwardEmoji(resource, awardId) {
|
|
2101
|
+
const path2 = `${this.buildAwardEmojiPath(resource)}/${awardId}`;
|
|
2102
|
+
await this.fetch("DELETE", path2);
|
|
2103
|
+
}
|
|
2104
|
+
};
|
|
2105
|
+
|
|
1966
2106
|
// src/client/index.ts
|
|
1967
2107
|
var UnifiedGitLabClient = class extends GitLabApiClient {
|
|
1968
2108
|
};
|
|
@@ -1992,7 +2132,8 @@ applyMixins(UnifiedGitLabClient, [
|
|
|
1992
2132
|
EpicsClient,
|
|
1993
2133
|
SnippetsClient,
|
|
1994
2134
|
DiscussionsClient,
|
|
1995
|
-
AuditClient
|
|
2135
|
+
AuditClient,
|
|
2136
|
+
AwardEmojiClient
|
|
1996
2137
|
]);
|
|
1997
2138
|
|
|
1998
2139
|
// src/utils.ts
|
|
@@ -2203,6 +2344,36 @@ This is useful when you need to process diffs in chunks or when the MR has many
|
|
|
2203
2344
|
return JSON.stringify(diffs, null, 2);
|
|
2204
2345
|
}
|
|
2205
2346
|
}),
|
|
2347
|
+
gitlab_add_mr_reviewer: tool({
|
|
2348
|
+
description: `Add a reviewer to a merge request without affecting existing reviewers.
|
|
2349
|
+
This is safer than gitlab_update_merge_request when you only want to add a single reviewer,
|
|
2350
|
+
as it preserves all existing reviewers.`,
|
|
2351
|
+
args: {
|
|
2352
|
+
project_id: z.string().describe("The project ID or URL-encoded path"),
|
|
2353
|
+
mr_iid: z.number().describe("The internal ID of the merge request"),
|
|
2354
|
+
reviewer_id: z.number().describe("The user ID of the reviewer to add")
|
|
2355
|
+
},
|
|
2356
|
+
execute: async (args, _ctx) => {
|
|
2357
|
+
const client = getGitLabClient();
|
|
2358
|
+
const mr = await client.addReviewer(args.project_id, args.mr_iid, args.reviewer_id);
|
|
2359
|
+
return JSON.stringify(mr, null, 2);
|
|
2360
|
+
}
|
|
2361
|
+
}),
|
|
2362
|
+
gitlab_remove_mr_reviewer: tool({
|
|
2363
|
+
description: `Remove a reviewer from a merge request without affecting other reviewers.
|
|
2364
|
+
This is safer than gitlab_update_merge_request when you only want to remove a single reviewer,
|
|
2365
|
+
as it preserves all other reviewers.`,
|
|
2366
|
+
args: {
|
|
2367
|
+
project_id: z.string().describe("The project ID or URL-encoded path"),
|
|
2368
|
+
mr_iid: z.number().describe("The internal ID of the merge request"),
|
|
2369
|
+
reviewer_id: z.number().describe("The user ID of the reviewer to remove")
|
|
2370
|
+
},
|
|
2371
|
+
execute: async (args, _ctx) => {
|
|
2372
|
+
const client = getGitLabClient();
|
|
2373
|
+
const mr = await client.removeReviewer(args.project_id, args.mr_iid, args.reviewer_id);
|
|
2374
|
+
return JSON.stringify(mr, null, 2);
|
|
2375
|
+
}
|
|
2376
|
+
}),
|
|
2206
2377
|
gitlab_set_mr_auto_merge: tool({
|
|
2207
2378
|
description: `Enable auto-merge (MWPS - Merge When Pipeline Succeeds) on a merge request.
|
|
2208
2379
|
Uses the GitLab GraphQL API mergeRequestAccept mutation with a merge strategy.
|
|
@@ -3227,7 +3398,24 @@ Examples:
|
|
|
3227
3398
|
return JSON.stringify({ error: 'todo_id is required when action is "one"' }, null, 2);
|
|
3228
3399
|
}
|
|
3229
3400
|
const result = await client.markTodoAsDone(args.todo_id);
|
|
3230
|
-
|
|
3401
|
+
if (!result.success) {
|
|
3402
|
+
return JSON.stringify(
|
|
3403
|
+
{
|
|
3404
|
+
success: false,
|
|
3405
|
+
message: result.message
|
|
3406
|
+
},
|
|
3407
|
+
null,
|
|
3408
|
+
2
|
|
3409
|
+
);
|
|
3410
|
+
}
|
|
3411
|
+
return JSON.stringify(
|
|
3412
|
+
{
|
|
3413
|
+
success: true,
|
|
3414
|
+
todo: result.todo
|
|
3415
|
+
},
|
|
3416
|
+
null,
|
|
3417
|
+
2
|
|
3418
|
+
);
|
|
3231
3419
|
}
|
|
3232
3420
|
case "all": {
|
|
3233
3421
|
const result = await client.markAllTodosAsDone();
|
|
@@ -3388,14 +3576,23 @@ Can update title, description, state, labels, and assignees.`,
|
|
|
3388
3576
|
// src/tools/discussions-unified.ts
|
|
3389
3577
|
import { tool as tool12 } from "@opencode-ai/plugin";
|
|
3390
3578
|
var z12 = tool12.schema;
|
|
3579
|
+
function normalizeBoolean(value) {
|
|
3580
|
+
if (typeof value === "boolean") return value;
|
|
3581
|
+
if (value === "true") return true;
|
|
3582
|
+
if (value === "false") return false;
|
|
3583
|
+
return void 0;
|
|
3584
|
+
}
|
|
3391
3585
|
function filterDiscussionsByResolved(result, resolved) {
|
|
3392
|
-
|
|
3586
|
+
const normalizedResolved = normalizeBoolean(resolved);
|
|
3587
|
+
if (normalizedResolved === void 0) {
|
|
3393
3588
|
return result;
|
|
3394
3589
|
}
|
|
3395
3590
|
return {
|
|
3396
3591
|
discussions: {
|
|
3397
3592
|
...result.discussions,
|
|
3398
|
-
nodes: result.discussions.nodes.filter(
|
|
3593
|
+
nodes: result.discussions.nodes.filter(
|
|
3594
|
+
(d) => d.resolvable && d.resolved === normalizedResolved
|
|
3595
|
+
)
|
|
3399
3596
|
}
|
|
3400
3597
|
};
|
|
3401
3598
|
}
|
|
@@ -3458,7 +3655,7 @@ Examples:
|
|
|
3458
3655
|
snippet_id: z12.number().optional().describe("Snippet ID (required for snippet)"),
|
|
3459
3656
|
// Filtering
|
|
3460
3657
|
resolved: z12.boolean().optional().describe(
|
|
3461
|
-
"Filter by resolved status: true for resolved, false for unresolved (
|
|
3658
|
+
"Filter by resolved status: true for resolved, false for unresolved. Only returns resolvable discussions (excludes system notes). Client-side filtering."
|
|
3462
3659
|
),
|
|
3463
3660
|
// Pagination
|
|
3464
3661
|
first: z12.number().optional().describe("Number of items to return (default: 20)"),
|
|
@@ -3669,6 +3866,9 @@ Examples:
|
|
|
3669
3866
|
description: `Mark a discussion thread as resolved or unresolve it.
|
|
3670
3867
|
Only works for resolvable discussions (MRs and issues only).
|
|
3671
3868
|
|
|
3869
|
+
Uses GraphQL API which handles all discussion types including outdated discussions
|
|
3870
|
+
(those with resolved: null) that the REST API cannot handle.
|
|
3871
|
+
|
|
3672
3872
|
Use after addressing feedback to indicate the discussion is complete.`,
|
|
3673
3873
|
args: {
|
|
3674
3874
|
resource_type: z12.enum(["merge_request", "issue"]).describe("Type of resource (only MR and issue discussions can be resolved)"),
|
|
@@ -3679,37 +3879,12 @@ Use after addressing feedback to indicate the discussion is complete.`,
|
|
|
3679
3879
|
},
|
|
3680
3880
|
execute: async (args, _ctx) => {
|
|
3681
3881
|
const client = getGitLabClient();
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
2
|
|
3689
|
-
);
|
|
3690
|
-
case "issue":
|
|
3691
|
-
return JSON.stringify(
|
|
3692
|
-
await client.resolveIssueDiscussion(args.project_id, args.iid, args.discussion_id),
|
|
3693
|
-
null,
|
|
3694
|
-
2
|
|
3695
|
-
);
|
|
3696
|
-
}
|
|
3697
|
-
} else {
|
|
3698
|
-
switch (args.resource_type) {
|
|
3699
|
-
case "merge_request":
|
|
3700
|
-
return JSON.stringify(
|
|
3701
|
-
await client.unresolveMrDiscussion(args.project_id, args.iid, args.discussion_id),
|
|
3702
|
-
null,
|
|
3703
|
-
2
|
|
3704
|
-
);
|
|
3705
|
-
case "issue":
|
|
3706
|
-
return JSON.stringify(
|
|
3707
|
-
await client.unresolveIssueDiscussion(args.project_id, args.iid, args.discussion_id),
|
|
3708
|
-
null,
|
|
3709
|
-
2
|
|
3710
|
-
);
|
|
3711
|
-
}
|
|
3712
|
-
}
|
|
3882
|
+
const resolve2 = args.action === "resolve";
|
|
3883
|
+
return JSON.stringify(
|
|
3884
|
+
await client.toggleDiscussionResolved(args.discussion_id, resolve2),
|
|
3885
|
+
null,
|
|
3886
|
+
2
|
|
3887
|
+
);
|
|
3713
3888
|
}
|
|
3714
3889
|
})
|
|
3715
3890
|
};
|
|
@@ -3717,15 +3892,22 @@ Use after addressing feedback to indicate the discussion is complete.`,
|
|
|
3717
3892
|
// src/tools/notes-unified.ts
|
|
3718
3893
|
import { tool as tool13 } from "@opencode-ai/plugin";
|
|
3719
3894
|
var z13 = tool13.schema;
|
|
3895
|
+
function normalizeBoolean2(value) {
|
|
3896
|
+
if (typeof value === "boolean") return value;
|
|
3897
|
+
if (value === "true") return true;
|
|
3898
|
+
if (value === "false") return false;
|
|
3899
|
+
return void 0;
|
|
3900
|
+
}
|
|
3720
3901
|
function filterNotesByResolved(result, resolved) {
|
|
3721
|
-
|
|
3902
|
+
const normalizedResolved = normalizeBoolean2(resolved);
|
|
3903
|
+
if (normalizedResolved === void 0) {
|
|
3722
3904
|
return result;
|
|
3723
3905
|
}
|
|
3724
3906
|
return {
|
|
3725
3907
|
...result,
|
|
3726
3908
|
notes: {
|
|
3727
3909
|
...result.notes,
|
|
3728
|
-
nodes: result.notes.nodes.filter((n) => n.resolved ===
|
|
3910
|
+
nodes: result.notes.nodes.filter((n) => n.resolvable && n.resolved === normalizedResolved)
|
|
3729
3911
|
}
|
|
3730
3912
|
};
|
|
3731
3913
|
}
|
|
@@ -3802,7 +3984,7 @@ Examples:
|
|
|
3802
3984
|
snippet_id: z13.number().optional().describe("Snippet ID (required for snippet)"),
|
|
3803
3985
|
// Filtering
|
|
3804
3986
|
resolved: z13.boolean().optional().describe(
|
|
3805
|
-
"Filter by resolved status: true for resolved, false for unresolved (
|
|
3987
|
+
"Filter by resolved status: true for resolved, false for unresolved. Only returns resolvable notes (excludes system notes). Client-side filtering."
|
|
3806
3988
|
),
|
|
3807
3989
|
// Pagination
|
|
3808
3990
|
first: z13.number().optional().describe("Number of items to return from the beginning (default: 20, max: 100)"),
|
|
@@ -4277,6 +4459,107 @@ Note: Requires administrator access.`,
|
|
|
4277
4459
|
})
|
|
4278
4460
|
};
|
|
4279
4461
|
|
|
4462
|
+
// src/tools/award-emoji.ts
|
|
4463
|
+
import { tool as tool16 } from "@opencode-ai/plugin";
|
|
4464
|
+
var z16 = tool16.schema;
|
|
4465
|
+
function validateAwardEmojiParams(args) {
|
|
4466
|
+
if (!args.project_id) {
|
|
4467
|
+
throw new Error("project_id is required");
|
|
4468
|
+
}
|
|
4469
|
+
if (args.resource_iid == null) {
|
|
4470
|
+
throw new Error("resource_iid is required");
|
|
4471
|
+
}
|
|
4472
|
+
}
|
|
4473
|
+
function buildResourceId(args) {
|
|
4474
|
+
return {
|
|
4475
|
+
projectId: args.project_id,
|
|
4476
|
+
resourceType: args.resource_type,
|
|
4477
|
+
resourceIid: args.resource_iid,
|
|
4478
|
+
noteId: args.note_id
|
|
4479
|
+
};
|
|
4480
|
+
}
|
|
4481
|
+
var awardEmojiTools = {
|
|
4482
|
+
/**
|
|
4483
|
+
* Add a reaction (award emoji) to a resource or note
|
|
4484
|
+
*/
|
|
4485
|
+
gitlab_create_award_emoji: tool16({
|
|
4486
|
+
description: `Add a reaction (award emoji) to a merge request, issue, snippet, or a note/comment on these resources.
|
|
4487
|
+
|
|
4488
|
+
Common emoji names: thumbsup, thumbsdown, smile, tada, rocket, eyes, heart, +1, -1
|
|
4489
|
+
|
|
4490
|
+
To react to a specific comment/note, provide the note_id parameter.
|
|
4491
|
+
|
|
4492
|
+
Examples:
|
|
4493
|
+
- React to MR: resource_type="merge_request", project_id="group/project", resource_iid=123, name="thumbsup"
|
|
4494
|
+
- React to issue comment: resource_type="issue", project_id="group/project", resource_iid=456, note_id=789, name="rocket"`,
|
|
4495
|
+
args: {
|
|
4496
|
+
resource_type: z16.enum(["merge_request", "issue", "snippet"]).describe("Type of resource to add the reaction to"),
|
|
4497
|
+
project_id: z16.string().describe("Project ID or URL-encoded path"),
|
|
4498
|
+
resource_iid: z16.number().describe("Internal ID of the merge request, issue, or snippet"),
|
|
4499
|
+
name: z16.string().describe(
|
|
4500
|
+
'Emoji name without colons (e.g., "thumbsup", "rocket", "eyes", "heart", "tada")'
|
|
4501
|
+
),
|
|
4502
|
+
note_id: z16.number().optional().describe("Note/comment ID to add the reaction to (if reacting to a specific comment)")
|
|
4503
|
+
},
|
|
4504
|
+
execute: async (args, _ctx) => {
|
|
4505
|
+
validateAwardEmojiParams(args);
|
|
4506
|
+
const client = getGitLabClient();
|
|
4507
|
+
const resource = buildResourceId(args);
|
|
4508
|
+
const result = await client.createAwardEmoji(resource, args.name);
|
|
4509
|
+
return JSON.stringify(result, null, 2);
|
|
4510
|
+
}
|
|
4511
|
+
}),
|
|
4512
|
+
/**
|
|
4513
|
+
* List all reactions on a resource or note
|
|
4514
|
+
*/
|
|
4515
|
+
gitlab_list_award_emoji: tool16({
|
|
4516
|
+
description: `List all reactions (award emoji) on a merge request, issue, snippet, or a specific note/comment.
|
|
4517
|
+
|
|
4518
|
+
Examples:
|
|
4519
|
+
- List reactions on MR: resource_type="merge_request", project_id="group/project", resource_iid=123
|
|
4520
|
+
- List reactions on a comment: resource_type="issue", project_id="group/project", resource_iid=456, note_id=789`,
|
|
4521
|
+
args: {
|
|
4522
|
+
resource_type: z16.enum(["merge_request", "issue", "snippet"]).describe("Type of resource"),
|
|
4523
|
+
project_id: z16.string().describe("Project ID or URL-encoded path"),
|
|
4524
|
+
resource_iid: z16.number().describe("Internal ID of the merge request, issue, or snippet"),
|
|
4525
|
+
note_id: z16.number().optional().describe("Note/comment ID to list reactions for (if listing for a specific comment)")
|
|
4526
|
+
},
|
|
4527
|
+
execute: async (args, _ctx) => {
|
|
4528
|
+
validateAwardEmojiParams(args);
|
|
4529
|
+
const client = getGitLabClient();
|
|
4530
|
+
const resource = buildResourceId(args);
|
|
4531
|
+
const result = await client.listAwardEmoji(resource);
|
|
4532
|
+
return JSON.stringify(result, null, 2);
|
|
4533
|
+
}
|
|
4534
|
+
}),
|
|
4535
|
+
/**
|
|
4536
|
+
* Remove a reaction from a resource or note
|
|
4537
|
+
*/
|
|
4538
|
+
gitlab_delete_award_emoji: tool16({
|
|
4539
|
+
description: `Remove a reaction (award emoji) from a merge request, issue, snippet, or note/comment.
|
|
4540
|
+
|
|
4541
|
+
You need the award_id which can be found using gitlab_list_award_emoji.
|
|
4542
|
+
|
|
4543
|
+
Examples:
|
|
4544
|
+
- Remove reaction from MR: resource_type="merge_request", project_id="group/project", resource_iid=123, award_id=456
|
|
4545
|
+
- Remove from comment: resource_type="issue", ..., note_id=789, award_id=456`,
|
|
4546
|
+
args: {
|
|
4547
|
+
resource_type: z16.enum(["merge_request", "issue", "snippet"]).describe("Type of resource"),
|
|
4548
|
+
project_id: z16.string().describe("Project ID or URL-encoded path"),
|
|
4549
|
+
resource_iid: z16.number().describe("Internal ID of the merge request, issue, or snippet"),
|
|
4550
|
+
award_id: z16.number().describe("ID of the award emoji to remove"),
|
|
4551
|
+
note_id: z16.number().optional().describe("Note/comment ID if removing from a specific comment")
|
|
4552
|
+
},
|
|
4553
|
+
execute: async (args, _ctx) => {
|
|
4554
|
+
validateAwardEmojiParams(args);
|
|
4555
|
+
const client = getGitLabClient();
|
|
4556
|
+
const resource = buildResourceId(args);
|
|
4557
|
+
await client.deleteAwardEmoji(resource, args.award_id);
|
|
4558
|
+
return JSON.stringify({ success: true, message: "Award emoji removed" }, null, 2);
|
|
4559
|
+
}
|
|
4560
|
+
})
|
|
4561
|
+
};
|
|
4562
|
+
|
|
4280
4563
|
// src/index.ts
|
|
4281
4564
|
var gitlabPlugin = async (_input) => {
|
|
4282
4565
|
return {
|
|
@@ -4312,7 +4595,9 @@ var gitlabPlugin = async (_input) => {
|
|
|
4312
4595
|
// Git Tools
|
|
4313
4596
|
...gitTools,
|
|
4314
4597
|
// Audit Tools
|
|
4315
|
-
...auditTools
|
|
4598
|
+
...auditTools,
|
|
4599
|
+
// Award Emoji (Reactions) Tools
|
|
4600
|
+
...awardEmojiTools
|
|
4316
4601
|
}
|
|
4317
4602
|
};
|
|
4318
4603
|
};
|
package/package.json
CHANGED
|
Binary file
|