From 30ea1cdb2026f5fb7a5b7ee59b5648ceb583b65e Mon Sep 17 00:00:00 2001 From: waleed Date: Wed, 24 Jun 2026 17:25:29 -0700 Subject: [PATCH 1/2] feat(gitlab): add repository, code-review, and CI job tools + validation fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expand the GitLab integration with 12 new tools (all host-aware via getGitLabApiBase, wired through types/index/registry/block): - Repository: list_repository_tree, get_file, create_file, update_file, create_branch, list_branches, list_commits - Code review: get_merge_request_changes, approve_merge_request - CI jobs: list_pipeline_jobs, get_job_log, play_job Validation fixes from /validate-integration: - Correct the block inputs key (credential -> accessToken) so it matches the subBlock id and the params the block reads - Trim projectId before encoding in all tool request URLs (input hygiene) /validate-connector and /validate-trigger passed clean against the GitLab REST API v4 docs — no changes required. --- apps/sim/blocks/blocks/gitlab.ts | 347 +++++++++++++++++- .../sim/tools/gitlab/approve_merge_request.ts | 105 ++++++ apps/sim/tools/gitlab/cancel_pipeline.ts | 2 +- apps/sim/tools/gitlab/create_branch.ts | 102 +++++ apps/sim/tools/gitlab/create_file.ts | 105 ++++++ apps/sim/tools/gitlab/create_issue.ts | 2 +- apps/sim/tools/gitlab/create_issue_note.ts | 2 +- apps/sim/tools/gitlab/create_merge_request.ts | 2 +- .../tools/gitlab/create_merge_request_note.ts | 2 +- apps/sim/tools/gitlab/create_pipeline.ts | 2 +- apps/sim/tools/gitlab/delete_issue.ts | 2 +- apps/sim/tools/gitlab/get_file.ts | 114 ++++++ apps/sim/tools/gitlab/get_issue.ts | 2 +- apps/sim/tools/gitlab/get_job_log.ts | 75 ++++ apps/sim/tools/gitlab/get_merge_request.ts | 2 +- .../tools/gitlab/get_merge_request_changes.ts | 102 +++++ apps/sim/tools/gitlab/get_pipeline.ts | 2 +- apps/sim/tools/gitlab/get_project.ts | 2 +- apps/sim/tools/gitlab/index.ts | 28 ++ apps/sim/tools/gitlab/list_branches.ts | 103 ++++++ apps/sim/tools/gitlab/list_commits.ts | 129 +++++++ apps/sim/tools/gitlab/list_issues.ts | 2 +- apps/sim/tools/gitlab/list_merge_requests.ts | 2 +- apps/sim/tools/gitlab/list_pipeline_jobs.ts | 119 ++++++ apps/sim/tools/gitlab/list_pipelines.ts | 2 +- apps/sim/tools/gitlab/list_repository_tree.ts | 120 ++++++ apps/sim/tools/gitlab/merge_merge_request.ts | 2 +- apps/sim/tools/gitlab/play_job.ts | 90 +++++ apps/sim/tools/gitlab/retry_pipeline.ts | 2 +- apps/sim/tools/gitlab/types.ts | 232 +++++++++++- apps/sim/tools/gitlab/update_file.ts | 117 ++++++ apps/sim/tools/gitlab/update_issue.ts | 2 +- apps/sim/tools/gitlab/update_merge_request.ts | 2 +- apps/sim/tools/registry.ts | 50 ++- 34 files changed, 1935 insertions(+), 39 deletions(-) create mode 100644 apps/sim/tools/gitlab/approve_merge_request.ts create mode 100644 apps/sim/tools/gitlab/create_branch.ts create mode 100644 apps/sim/tools/gitlab/create_file.ts create mode 100644 apps/sim/tools/gitlab/get_file.ts create mode 100644 apps/sim/tools/gitlab/get_job_log.ts create mode 100644 apps/sim/tools/gitlab/get_merge_request_changes.ts create mode 100644 apps/sim/tools/gitlab/list_branches.ts create mode 100644 apps/sim/tools/gitlab/list_commits.ts create mode 100644 apps/sim/tools/gitlab/list_pipeline_jobs.ts create mode 100644 apps/sim/tools/gitlab/list_repository_tree.ts create mode 100644 apps/sim/tools/gitlab/play_job.ts create mode 100644 apps/sim/tools/gitlab/update_file.ts diff --git a/apps/sim/blocks/blocks/gitlab.ts b/apps/sim/blocks/blocks/gitlab.ts index 1cfe24a9e25..078422bedac 100644 --- a/apps/sim/blocks/blocks/gitlab.ts +++ b/apps/sim/blocks/blocks/gitlab.ts @@ -46,6 +46,21 @@ export const GitLabBlock: BlockConfig = { { label: 'Create Pipeline', id: 'gitlab_create_pipeline' }, { label: 'Retry Pipeline', id: 'gitlab_retry_pipeline' }, { label: 'Cancel Pipeline', id: 'gitlab_cancel_pipeline' }, + // Repository Operations + { label: 'List Repository Tree', id: 'gitlab_list_repository_tree' }, + { label: 'Get File', id: 'gitlab_get_file' }, + { label: 'Create File', id: 'gitlab_create_file' }, + { label: 'Update File', id: 'gitlab_update_file' }, + { label: 'List Commits', id: 'gitlab_list_commits' }, + { label: 'List Branches', id: 'gitlab_list_branches' }, + { label: 'Create Branch', id: 'gitlab_create_branch' }, + // Additional Merge Request Operations + { label: 'Get MR Changes', id: 'gitlab_get_merge_request_changes' }, + { label: 'Approve Merge Request', id: 'gitlab_approve_merge_request' }, + // Job Operations + { label: 'List Pipeline Jobs', id: 'gitlab_list_pipeline_jobs' }, + { label: 'Get Job Log', id: 'gitlab_get_job_log' }, + { label: 'Play Job', id: 'gitlab_play_job' }, ], value: () => 'gitlab_list_projects', }, @@ -94,6 +109,18 @@ export const GitLabBlock: BlockConfig = { 'gitlab_create_pipeline', 'gitlab_retry_pipeline', 'gitlab_cancel_pipeline', + 'gitlab_list_repository_tree', + 'gitlab_get_file', + 'gitlab_create_file', + 'gitlab_update_file', + 'gitlab_list_commits', + 'gitlab_list_branches', + 'gitlab_create_branch', + 'gitlab_get_merge_request_changes', + 'gitlab_approve_merge_request', + 'gitlab_list_pipeline_jobs', + 'gitlab_get_job_log', + 'gitlab_play_job', ], }, }, @@ -128,6 +155,8 @@ export const GitLabBlock: BlockConfig = { 'gitlab_update_merge_request', 'gitlab_merge_merge_request', 'gitlab_create_merge_request_note', + 'gitlab_get_merge_request_changes', + 'gitlab_approve_merge_request', ], }, }, @@ -140,7 +169,12 @@ export const GitLabBlock: BlockConfig = { required: true, condition: { field: 'operation', - value: ['gitlab_get_pipeline', 'gitlab_retry_pipeline', 'gitlab_cancel_pipeline'], + value: [ + 'gitlab_get_pipeline', + 'gitlab_retry_pipeline', + 'gitlab_cancel_pipeline', + 'gitlab_list_pipeline_jobs', + ], }, }, // Title (for issue/MR creation) @@ -247,7 +281,135 @@ Return ONLY the comment text - no explanations, no extra formatting.`, required: true, condition: { field: 'operation', - value: ['gitlab_create_pipeline'], + value: ['gitlab_create_pipeline', 'gitlab_get_file', 'gitlab_create_branch'], + }, + }, + // File Path + { + id: 'filePath', + title: 'File Path', + type: 'short-input', + placeholder: 'Path to file (e.g., src/index.ts)', + required: true, + condition: { + field: 'operation', + value: ['gitlab_get_file', 'gitlab_create_file', 'gitlab_update_file'], + }, + }, + // Branch + { + id: 'branch', + title: 'Branch', + type: 'short-input', + placeholder: 'Branch name', + required: true, + condition: { + field: 'operation', + value: ['gitlab_create_file', 'gitlab_update_file', 'gitlab_create_branch'], + }, + }, + // File Content + { + id: 'content', + title: 'File Content', + type: 'long-input', + placeholder: 'File content', + required: true, + condition: { + field: 'operation', + value: ['gitlab_create_file', 'gitlab_update_file'], + }, + }, + // Commit Message + { + id: 'commitMessage', + title: 'Commit Message', + type: 'short-input', + placeholder: 'Commit message', + required: true, + condition: { + field: 'operation', + value: ['gitlab_create_file', 'gitlab_update_file'], + }, + }, + // Job ID + { + id: 'jobId', + title: 'Job ID', + type: 'short-input', + placeholder: 'Enter job ID', + required: true, + condition: { + field: 'operation', + value: ['gitlab_get_job_log', 'gitlab_play_job'], + }, + }, + // Subdirectory path (for repository tree) + { + id: 'path', + title: 'Path', + type: 'short-input', + placeholder: 'Subdirectory path (optional)', + mode: 'advanced', + condition: { + field: 'operation', + value: ['gitlab_list_repository_tree'], + }, + }, + // Recursive tree listing + { + id: 'recursive', + title: 'Recursive', + type: 'switch', + mode: 'advanced', + condition: { + field: 'operation', + value: ['gitlab_list_repository_tree'], + }, + }, + // Ref name filter (for list commits) + { + id: 'refName', + title: 'Ref (branch/tag)', + type: 'short-input', + placeholder: 'Branch or tag (optional)', + mode: 'advanced', + condition: { + field: 'operation', + value: ['gitlab_list_commits'], + }, + }, + // Job scope filter (for list pipeline jobs) + { + id: 'scope', + title: 'Job Scope', + type: 'dropdown', + options: [ + { label: 'All', id: '' }, + { label: 'Failed', id: 'failed' }, + { label: 'Success', id: 'success' }, + { label: 'Running', id: 'running' }, + { label: 'Pending', id: 'pending' }, + { label: 'Canceled', id: 'canceled' }, + { label: 'Manual', id: 'manual' }, + ], + value: () => '', + mode: 'advanced', + condition: { + field: 'operation', + value: ['gitlab_list_pipeline_jobs'], + }, + }, + // Commit SHA (for approve merge request) + { + id: 'sha', + title: 'Commit SHA', + type: 'short-input', + placeholder: 'Optional HEAD SHA to approve', + mode: 'advanced', + condition: { + field: 'operation', + value: ['gitlab_approve_merge_request'], }, }, // Labels @@ -427,6 +589,10 @@ Return ONLY the commit message - no explanations, no extra text.`, 'gitlab_list_issues', 'gitlab_list_merge_requests', 'gitlab_list_pipelines', + 'gitlab_list_repository_tree', + 'gitlab_list_branches', + 'gitlab_list_commits', + 'gitlab_list_pipeline_jobs', ], }, }, @@ -444,6 +610,10 @@ Return ONLY the commit message - no explanations, no extra text.`, 'gitlab_list_issues', 'gitlab_list_merge_requests', 'gitlab_list_pipelines', + 'gitlab_list_repository_tree', + 'gitlab_list_branches', + 'gitlab_list_commits', + 'gitlab_list_pipeline_jobs', ], }, }, @@ -475,6 +645,18 @@ Return ONLY the commit message - no explanations, no extra text.`, 'gitlab_create_pipeline', 'gitlab_retry_pipeline', 'gitlab_cancel_pipeline', + 'gitlab_list_repository_tree', + 'gitlab_get_file', + 'gitlab_create_file', + 'gitlab_update_file', + 'gitlab_create_branch', + 'gitlab_list_branches', + 'gitlab_list_commits', + 'gitlab_get_merge_request_changes', + 'gitlab_approve_merge_request', + 'gitlab_list_pipeline_jobs', + 'gitlab_get_job_log', + 'gitlab_play_job', ], config: { tool: (params) => { @@ -710,6 +892,140 @@ Return ONLY the commit message - no explanations, no extra text.`, pipelineId: Number(params.pipelineId), } + case 'gitlab_list_repository_tree': + if (!params.projectId?.trim()) { + throw new Error('Project ID is required.') + } + return { + ...baseParams, + projectId: params.projectId.trim(), + path: params.path?.trim() || undefined, + recursive: params.recursive || undefined, + perPage: params.perPage ? Number(params.perPage) : undefined, + page: params.page ? Number(params.page) : undefined, + } + + case 'gitlab_get_file': + if (!params.projectId?.trim() || !params.filePath?.trim() || !params.ref?.trim()) { + throw new Error('Project ID, file path, and ref are required.') + } + return { + ...baseParams, + projectId: params.projectId.trim(), + filePath: params.filePath.trim(), + ref: params.ref.trim(), + } + + case 'gitlab_create_file': + case 'gitlab_update_file': + if ( + !params.projectId?.trim() || + !params.filePath?.trim() || + !params.branch?.trim() || + !params.content || + !params.commitMessage?.trim() + ) { + throw new Error( + 'Project ID, file path, branch, content, and commit message are required.' + ) + } + return { + ...baseParams, + projectId: params.projectId.trim(), + filePath: params.filePath.trim(), + branch: params.branch.trim(), + content: params.content, + commitMessage: params.commitMessage.trim(), + } + + case 'gitlab_create_branch': + if (!params.projectId?.trim() || !params.branch?.trim() || !params.ref?.trim()) { + throw new Error('Project ID, branch name, and source ref are required.') + } + return { + ...baseParams, + projectId: params.projectId.trim(), + branch: params.branch.trim(), + ref: params.ref.trim(), + } + + case 'gitlab_list_branches': + if (!params.projectId?.trim()) { + throw new Error('Project ID is required.') + } + return { + ...baseParams, + projectId: params.projectId.trim(), + perPage: params.perPage ? Number(params.perPage) : undefined, + page: params.page ? Number(params.page) : undefined, + } + + case 'gitlab_list_commits': + if (!params.projectId?.trim()) { + throw new Error('Project ID is required.') + } + return { + ...baseParams, + projectId: params.projectId.trim(), + refName: params.refName?.trim() || undefined, + perPage: params.perPage ? Number(params.perPage) : undefined, + page: params.page ? Number(params.page) : undefined, + } + + case 'gitlab_get_merge_request_changes': + if (!params.projectId?.trim() || !params.mergeRequestIid) { + throw new Error('Project ID and Merge Request IID are required.') + } + return { + ...baseParams, + projectId: params.projectId.trim(), + mergeRequestIid: Number(params.mergeRequestIid), + } + + case 'gitlab_approve_merge_request': + if (!params.projectId?.trim() || !params.mergeRequestIid) { + throw new Error('Project ID and Merge Request IID are required.') + } + return { + ...baseParams, + projectId: params.projectId.trim(), + mergeRequestIid: Number(params.mergeRequestIid), + sha: params.sha?.trim() || undefined, + } + + case 'gitlab_list_pipeline_jobs': + if (!params.projectId?.trim() || !params.pipelineId) { + throw new Error('Project ID and Pipeline ID are required.') + } + return { + ...baseParams, + projectId: params.projectId.trim(), + pipelineId: Number(params.pipelineId), + scope: params.scope || undefined, + perPage: params.perPage ? Number(params.perPage) : undefined, + page: params.page ? Number(params.page) : undefined, + } + + case 'gitlab_get_job_log': + if (!params.projectId?.trim() || !params.jobId) { + throw new Error('Project ID and Job ID are required.') + } + return { + ...baseParams, + projectId: params.projectId.trim(), + jobId: Number(params.jobId), + } + + case 'gitlab_play_job': + if (!params.projectId?.trim() || !params.jobId) { + throw new Error('Project ID and Job ID are required.') + } + return { + ...baseParams, + projectId: params.projectId.trim(), + jobId: Number(params.jobId), + } + default: return baseParams } @@ -718,7 +1034,7 @@ Return ONLY the commit message - no explanations, no extra text.`, }, inputs: { operation: { type: 'string', description: 'Operation to perform' }, - credential: { type: 'string', description: 'GitLab access token' }, + accessToken: { type: 'string', description: 'GitLab Personal Access Token' }, host: { type: 'string', description: 'Self-managed GitLab host (defaults to gitlab.com)' }, projectId: { type: 'string', description: 'Project ID or URL-encoded path' }, issueIid: { type: 'number', description: 'Issue internal ID' }, @@ -745,6 +1061,16 @@ Return ONLY the commit message - no explanations, no extra text.`, mergeCommitMessage: { type: 'string', description: 'Custom merge commit message' }, perPage: { type: 'number', description: 'Results per page' }, page: { type: 'number', description: 'Page number' }, + filePath: { type: 'string', description: 'Path to file in the repository' }, + branch: { type: 'string', description: 'Branch name' }, + content: { type: 'string', description: 'File content' }, + commitMessage: { type: 'string', description: 'Commit message' }, + jobId: { type: 'number', description: 'Job ID' }, + path: { type: 'string', description: 'Subdirectory path for repository tree' }, + recursive: { type: 'boolean', description: 'Recursively list repository tree' }, + refName: { type: 'string', description: 'Branch or tag name filter' }, + scope: { type: 'string', description: 'Job scope filter' }, + sha: { type: 'string', description: 'Commit SHA' }, }, outputs: { // Project outputs @@ -761,6 +1087,21 @@ Return ONLY the commit message - no explanations, no extra text.`, pipeline: { type: 'json', description: 'Pipeline details' }, // Note outputs note: { type: 'json', description: 'Comment/note details' }, + // Repository outputs + tree: { type: 'json', description: 'Repository tree entries' }, + content: { type: 'string', description: 'File contents (decoded)' }, + fileName: { type: 'string', description: 'File name' }, + branches: { type: 'json', description: 'List of branches' }, + commits: { type: 'json', description: 'List of commits' }, + name: { type: 'string', description: 'Created branch name' }, + webUrl: { type: 'string', description: 'Web URL' }, + // Merge request change outputs + changes: { type: 'json', description: 'Merge request file changes/diffs' }, + approvalsRequired: { type: 'number', description: 'Approvals required' }, + approvalsLeft: { type: 'number', description: 'Approvals remaining' }, + // Job outputs + jobs: { type: 'json', description: 'Pipeline jobs' }, + log: { type: 'string', description: 'Job log output' }, // Success indicator success: { type: 'boolean', description: 'Operation success status' }, }, diff --git a/apps/sim/tools/gitlab/approve_merge_request.ts b/apps/sim/tools/gitlab/approve_merge_request.ts new file mode 100644 index 00000000000..0db63c53539 --- /dev/null +++ b/apps/sim/tools/gitlab/approve_merge_request.ts @@ -0,0 +1,105 @@ +import type { + GitLabApproveMergeRequestParams, + GitLabApproveMergeRequestResponse, +} from '@/tools/gitlab/types' +import { getGitLabApiBase } from '@/tools/gitlab/utils' +import type { ToolConfig } from '@/tools/types' + +export const gitlabApproveMergeRequestTool: ToolConfig< + GitLabApproveMergeRequestParams, + GitLabApproveMergeRequestResponse +> = { + id: 'gitlab_approve_merge_request', + name: 'GitLab Approve Merge Request', + description: 'Approve a GitLab merge request', + version: '1.0.0', + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitLab Personal Access Token', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or URL-encoded path', + }, + mergeRequestIid: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Merge request internal ID (IID)', + }, + sha: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'HEAD SHA of the merge request to approve', + }, + }, + + request: { + url: (params) => { + const encodedId = encodeURIComponent(String(params.projectId).trim()) + return `${getGitLabApiBase(params.host)}/projects/${encodedId}/merge_requests/${params.mergeRequestIid}/approve` + }, + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/json', + 'PRIVATE-TOKEN': params.accessToken, + }), + body: (params) => { + const body: Record = {} + + if (params.sha) body.sha = params.sha + + return body + }, + }, + + transformResponse: async (response) => { + if (!response.ok) { + const errorText = await response.text() + return { + success: false, + error: `GitLab API error: ${response.status} ${errorText}`, + output: {}, + } + } + + const data = await response.json() + + return { + success: true, + output: { + approvalsRequired: data.approvals_required ?? null, + approvalsLeft: data.approvals_left ?? null, + approvedBy: data.approved_by ?? [], + }, + } + }, + + outputs: { + approvalsRequired: { + type: 'number', + description: 'Number of approvals required', + }, + approvalsLeft: { + type: 'number', + description: 'Number of approvals still needed', + }, + approvedBy: { + type: 'array', + description: 'List of approvers', + }, + }, +} diff --git a/apps/sim/tools/gitlab/cancel_pipeline.ts b/apps/sim/tools/gitlab/cancel_pipeline.ts index 9707f758a0e..b16ef0534e9 100644 --- a/apps/sim/tools/gitlab/cancel_pipeline.ts +++ b/apps/sim/tools/gitlab/cancel_pipeline.ts @@ -40,7 +40,7 @@ export const gitlabCancelPipelineTool: ToolConfig< request: { url: (params) => { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) return `${getGitLabApiBase(params.host)}/projects/${encodedId}/pipelines/${params.pipelineId}/cancel` }, method: 'POST', diff --git a/apps/sim/tools/gitlab/create_branch.ts b/apps/sim/tools/gitlab/create_branch.ts new file mode 100644 index 00000000000..8fe6a8697c2 --- /dev/null +++ b/apps/sim/tools/gitlab/create_branch.ts @@ -0,0 +1,102 @@ +import type { GitLabCreateBranchParams, GitLabCreateBranchResponse } from '@/tools/gitlab/types' +import { getGitLabApiBase } from '@/tools/gitlab/utils' +import type { ToolConfig } from '@/tools/types' + +export const gitlabCreateBranchTool: ToolConfig< + GitLabCreateBranchParams, + GitLabCreateBranchResponse +> = { + id: 'gitlab_create_branch', + name: 'GitLab Create Branch', + description: 'Create a new branch in a GitLab project repository', + version: '1.0.0', + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitLab Personal Access Token', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or URL-encoded path', + }, + branch: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the new branch', + }, + ref: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Source branch/tag/SHA', + }, + }, + + request: { + url: (params) => { + const encodedId = encodeURIComponent(String(params.projectId).trim()) + const queryParams = new URLSearchParams() + queryParams.append('branch', String(params.branch)) + queryParams.append('ref', String(params.ref)) + return `${getGitLabApiBase(params.host)}/projects/${encodedId}/repository/branches?${queryParams.toString()}` + }, + method: 'POST', + headers: (params) => ({ + 'PRIVATE-TOKEN': params.accessToken, + }), + }, + + transformResponse: async (response) => { + if (!response.ok) { + const errorText = await response.text() + return { + success: false, + error: `GitLab API error: ${response.status} ${errorText}`, + output: {}, + } + } + + const data = await response.json() + + return { + success: true, + output: { + name: data.name ?? null, + webUrl: data.web_url ?? null, + protected: data.protected ?? null, + commit: data.commit ?? null, + }, + } + }, + + outputs: { + name: { + type: 'string', + description: 'The created branch name', + }, + webUrl: { + type: 'string', + description: 'The web URL of the branch', + }, + protected: { + type: 'boolean', + description: 'Whether the branch is protected', + }, + commit: { + type: 'object', + description: 'The commit the branch points to', + }, + }, +} diff --git a/apps/sim/tools/gitlab/create_file.ts b/apps/sim/tools/gitlab/create_file.ts new file mode 100644 index 00000000000..513394698ea --- /dev/null +++ b/apps/sim/tools/gitlab/create_file.ts @@ -0,0 +1,105 @@ +import type { GitLabCreateFileParams, GitLabCreateFileResponse } from '@/tools/gitlab/types' +import { getGitLabApiBase } from '@/tools/gitlab/utils' +import type { ToolConfig } from '@/tools/types' + +export const gitlabCreateFileTool: ToolConfig = { + id: 'gitlab_create_file', + name: 'GitLab Create File', + description: 'Create a new file in a GitLab project repository', + version: '1.0.0', + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitLab Personal Access Token', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or URL-encoded path', + }, + filePath: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Path to the file in the repository', + }, + branch: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Branch to commit the new file to', + }, + content: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'File content', + }, + commitMessage: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Commit message', + }, + }, + + request: { + url: (params) => { + const encodedId = encodeURIComponent(String(params.projectId).trim()) + const encodedPath = encodeURIComponent(String(params.filePath)) + return `${getGitLabApiBase(params.host)}/projects/${encodedId}/repository/files/${encodedPath}` + }, + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/json', + 'PRIVATE-TOKEN': params.accessToken, + }), + body: (params) => ({ + branch: params.branch, + content: params.content, + commit_message: params.commitMessage, + }), + }, + + transformResponse: async (response) => { + if (!response.ok) { + const errorText = await response.text() + return { + success: false, + error: `GitLab API error: ${response.status} ${errorText}`, + output: {}, + } + } + + const data = await response.json() + + return { + success: true, + output: { + filePath: data.file_path ?? null, + branch: data.branch ?? null, + }, + } + }, + + outputs: { + filePath: { + type: 'string', + description: 'The created file path', + }, + branch: { + type: 'string', + description: 'The branch the file was committed to', + }, + }, +} diff --git a/apps/sim/tools/gitlab/create_issue.ts b/apps/sim/tools/gitlab/create_issue.ts index cc4475831da..f3830a81f0a 100644 --- a/apps/sim/tools/gitlab/create_issue.ts +++ b/apps/sim/tools/gitlab/create_issue.ts @@ -74,7 +74,7 @@ export const gitlabCreateIssueTool: ToolConfig { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) return `${getGitLabApiBase(params.host)}/projects/${encodedId}/issues` }, method: 'POST', diff --git a/apps/sim/tools/gitlab/create_issue_note.ts b/apps/sim/tools/gitlab/create_issue_note.ts index 0ad5c218bee..c1ef8447405 100644 --- a/apps/sim/tools/gitlab/create_issue_note.ts +++ b/apps/sim/tools/gitlab/create_issue_note.ts @@ -46,7 +46,7 @@ export const gitlabCreateIssueNoteTool: ToolConfig< request: { url: (params) => { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) return `${getGitLabApiBase(params.host)}/projects/${encodedId}/issues/${params.issueIid}/notes` }, method: 'POST', diff --git a/apps/sim/tools/gitlab/create_merge_request.ts b/apps/sim/tools/gitlab/create_merge_request.ts index 2c02c2dd0f4..9c47556553e 100644 --- a/apps/sim/tools/gitlab/create_merge_request.ts +++ b/apps/sim/tools/gitlab/create_merge_request.ts @@ -97,7 +97,7 @@ export const gitlabCreateMergeRequestTool: ToolConfig< request: { url: (params) => { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) return `${getGitLabApiBase(params.host)}/projects/${encodedId}/merge_requests` }, method: 'POST', diff --git a/apps/sim/tools/gitlab/create_merge_request_note.ts b/apps/sim/tools/gitlab/create_merge_request_note.ts index f02f5fa35fb..7f364efba51 100644 --- a/apps/sim/tools/gitlab/create_merge_request_note.ts +++ b/apps/sim/tools/gitlab/create_merge_request_note.ts @@ -49,7 +49,7 @@ export const gitlabCreateMergeRequestNoteTool: ToolConfig< request: { url: (params) => { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) return `${getGitLabApiBase(params.host)}/projects/${encodedId}/merge_requests/${params.mergeRequestIid}/notes` }, method: 'POST', diff --git a/apps/sim/tools/gitlab/create_pipeline.ts b/apps/sim/tools/gitlab/create_pipeline.ts index a27ed7ba372..6a9777ea53b 100644 --- a/apps/sim/tools/gitlab/create_pipeline.ts +++ b/apps/sim/tools/gitlab/create_pipeline.ts @@ -47,7 +47,7 @@ export const gitlabCreatePipelineTool: ToolConfig< request: { url: (params) => { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) return `${getGitLabApiBase(params.host)}/projects/${encodedId}/pipeline` }, method: 'POST', diff --git a/apps/sim/tools/gitlab/delete_issue.ts b/apps/sim/tools/gitlab/delete_issue.ts index 475e52d77a8..7521b826629 100644 --- a/apps/sim/tools/gitlab/delete_issue.ts +++ b/apps/sim/tools/gitlab/delete_issue.ts @@ -38,7 +38,7 @@ export const gitlabDeleteIssueTool: ToolConfig { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) return `${getGitLabApiBase(params.host)}/projects/${encodedId}/issues/${params.issueIid}` }, method: 'DELETE', diff --git a/apps/sim/tools/gitlab/get_file.ts b/apps/sim/tools/gitlab/get_file.ts new file mode 100644 index 00000000000..7c394b2591f --- /dev/null +++ b/apps/sim/tools/gitlab/get_file.ts @@ -0,0 +1,114 @@ +import type { GitLabGetFileParams, GitLabGetFileResponse } from '@/tools/gitlab/types' +import { getGitLabApiBase } from '@/tools/gitlab/utils' +import type { ToolConfig } from '@/tools/types' + +export const gitlabGetFileTool: ToolConfig = { + id: 'gitlab_get_file', + name: 'GitLab Get File', + description: 'Get the contents of a file from a GitLab project repository', + version: '1.0.0', + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitLab Personal Access Token', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or URL-encoded path', + }, + filePath: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Path to the file in the repository', + }, + ref: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Branch, tag, or commit SHA', + }, + }, + + request: { + url: (params) => { + const encodedId = encodeURIComponent(String(params.projectId).trim()) + const encodedPath = encodeURIComponent(String(params.filePath)) + const ref = encodeURIComponent(String(params.ref)) + return `${getGitLabApiBase(params.host)}/projects/${encodedId}/repository/files/${encodedPath}?ref=${ref}` + }, + method: 'GET', + headers: (params) => ({ + 'PRIVATE-TOKEN': params.accessToken, + }), + }, + + transformResponse: async (response) => { + if (!response.ok) { + const errorText = await response.text() + return { + success: false, + error: `GitLab API error: ${response.status} ${errorText}`, + output: {}, + } + } + + const data = await response.json() + const decoded = Buffer.from(data.content ?? '', 'base64').toString('utf-8') + + return { + success: true, + output: { + filePath: data.file_path ?? null, + fileName: data.file_name ?? null, + size: data.size ?? null, + ref: data.ref ?? null, + blobId: data.blob_id ?? null, + lastCommitId: data.last_commit_id ?? null, + content: decoded, + }, + } + }, + + outputs: { + filePath: { + type: 'string', + description: 'The file path', + }, + fileName: { + type: 'string', + description: 'The file name', + }, + size: { + type: 'number', + description: 'The file size in bytes', + }, + ref: { + type: 'string', + description: 'The branch, tag, or commit SHA', + }, + blobId: { + type: 'string', + description: 'The blob ID', + }, + lastCommitId: { + type: 'string', + description: 'The last commit ID that modified the file', + }, + content: { + type: 'string', + description: 'The decoded file content', + }, + }, +} diff --git a/apps/sim/tools/gitlab/get_issue.ts b/apps/sim/tools/gitlab/get_issue.ts index aa87136e552..edd41e86f6c 100644 --- a/apps/sim/tools/gitlab/get_issue.ts +++ b/apps/sim/tools/gitlab/get_issue.ts @@ -37,7 +37,7 @@ export const gitlabGetIssueTool: ToolConfig { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) return `${getGitLabApiBase(params.host)}/projects/${encodedId}/issues/${params.issueIid}` }, method: 'GET', diff --git a/apps/sim/tools/gitlab/get_job_log.ts b/apps/sim/tools/gitlab/get_job_log.ts new file mode 100644 index 00000000000..f3cd4b58314 --- /dev/null +++ b/apps/sim/tools/gitlab/get_job_log.ts @@ -0,0 +1,75 @@ +import type { GitLabGetJobLogParams, GitLabGetJobLogResponse } from '@/tools/gitlab/types' +import { getGitLabApiBase } from '@/tools/gitlab/utils' +import type { ToolConfig } from '@/tools/types' + +export const gitlabGetJobLogTool: ToolConfig = { + id: 'gitlab_get_job_log', + name: 'GitLab Get Job Log', + description: 'Get the log (trace) of a GitLab job', + version: '1.0.0', + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitLab Personal Access Token', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or URL-encoded path', + }, + jobId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Job ID', + }, + }, + + request: { + url: (params) => { + const encodedId = encodeURIComponent(String(params.projectId).trim()) + return `${getGitLabApiBase(params.host)}/projects/${encodedId}/jobs/${params.jobId}/trace` + }, + method: 'GET', + headers: (params) => ({ + 'PRIVATE-TOKEN': params.accessToken, + }), + }, + + transformResponse: async (response) => { + if (!response.ok) { + const errorText = await response.text() + return { + success: false, + error: `GitLab API error: ${response.status} ${errorText}`, + output: {}, + } + } + + const log = await response.text() + + return { + success: true, + output: { + log, + }, + } + }, + + outputs: { + log: { + type: 'string', + description: 'The job log (trace) output', + }, + }, +} diff --git a/apps/sim/tools/gitlab/get_merge_request.ts b/apps/sim/tools/gitlab/get_merge_request.ts index f228cfba2eb..b9b45b0d98d 100644 --- a/apps/sim/tools/gitlab/get_merge_request.ts +++ b/apps/sim/tools/gitlab/get_merge_request.ts @@ -43,7 +43,7 @@ export const gitlabGetMergeRequestTool: ToolConfig< request: { url: (params) => { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) return `${getGitLabApiBase(params.host)}/projects/${encodedId}/merge_requests/${params.mergeRequestIid}` }, method: 'GET', diff --git a/apps/sim/tools/gitlab/get_merge_request_changes.ts b/apps/sim/tools/gitlab/get_merge_request_changes.ts new file mode 100644 index 00000000000..b78e65c6da5 --- /dev/null +++ b/apps/sim/tools/gitlab/get_merge_request_changes.ts @@ -0,0 +1,102 @@ +import type { + GitLabGetMergeRequestChangesParams, + GitLabGetMergeRequestChangesResponse, +} from '@/tools/gitlab/types' +import { getGitLabApiBase } from '@/tools/gitlab/utils' +import type { ToolConfig } from '@/tools/types' + +export const gitlabGetMergeRequestChangesTool: ToolConfig< + GitLabGetMergeRequestChangesParams, + GitLabGetMergeRequestChangesResponse +> = { + id: 'gitlab_get_merge_request_changes', + name: 'GitLab Get Merge Request Changes', + description: 'Get the file changes (diffs) of a GitLab merge request', + version: '1.0.0', + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitLab Personal Access Token', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or URL-encoded path', + }, + mergeRequestIid: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Merge request internal ID (IID)', + }, + }, + + request: { + url: (params) => { + const encodedId = encodeURIComponent(String(params.projectId).trim()) + return `${getGitLabApiBase(params.host)}/projects/${encodedId}/merge_requests/${params.mergeRequestIid}/changes` + }, + method: 'GET', + headers: (params) => ({ + 'PRIVATE-TOKEN': params.accessToken, + }), + }, + + transformResponse: async (response) => { + if (!response.ok) { + const errorText = await response.text() + return { + success: false, + error: `GitLab API error: ${response.status} ${errorText}`, + output: {}, + } + } + + const data = await response.json() + const changes = data.changes ?? [] + + return { + success: true, + output: { + iid: data.iid ?? null, + title: data.title ?? null, + state: data.state ?? null, + changes, + changesCount: changes.length, + }, + } + }, + + outputs: { + iid: { + type: 'number', + description: 'The merge request internal ID', + }, + title: { + type: 'string', + description: 'The merge request title', + }, + state: { + type: 'string', + description: 'The merge request state', + }, + changes: { + type: 'array', + description: 'List of file changes (diffs)', + }, + changesCount: { + type: 'number', + description: 'Number of changed files', + }, + }, +} diff --git a/apps/sim/tools/gitlab/get_pipeline.ts b/apps/sim/tools/gitlab/get_pipeline.ts index 1494e65e4bb..c69cfd61185 100644 --- a/apps/sim/tools/gitlab/get_pipeline.ts +++ b/apps/sim/tools/gitlab/get_pipeline.ts @@ -38,7 +38,7 @@ export const gitlabGetPipelineTool: ToolConfig { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) return `${getGitLabApiBase(params.host)}/projects/${encodedId}/pipelines/${params.pipelineId}` }, method: 'GET', diff --git a/apps/sim/tools/gitlab/get_project.ts b/apps/sim/tools/gitlab/get_project.ts index 5ea42920584..882da20092a 100644 --- a/apps/sim/tools/gitlab/get_project.ts +++ b/apps/sim/tools/gitlab/get_project.ts @@ -31,7 +31,7 @@ export const gitlabGetProjectTool: ToolConfig { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) return `${getGitLabApiBase(params.host)}/projects/${encodedId}` }, method: 'GET', diff --git a/apps/sim/tools/gitlab/index.ts b/apps/sim/tools/gitlab/index.ts index 0133af843ab..25d7ba9e6cd 100644 --- a/apps/sim/tools/gitlab/index.ts +++ b/apps/sim/tools/gitlab/index.ts @@ -1,20 +1,32 @@ +import { gitlabApproveMergeRequestTool } from '@/tools/gitlab/approve_merge_request' import { gitlabCancelPipelineTool } from '@/tools/gitlab/cancel_pipeline' +import { gitlabCreateBranchTool } from '@/tools/gitlab/create_branch' +import { gitlabCreateFileTool } from '@/tools/gitlab/create_file' import { gitlabCreateIssueTool } from '@/tools/gitlab/create_issue' import { gitlabCreateIssueNoteTool } from '@/tools/gitlab/create_issue_note' import { gitlabCreateMergeRequestTool } from '@/tools/gitlab/create_merge_request' import { gitlabCreateMergeRequestNoteTool } from '@/tools/gitlab/create_merge_request_note' import { gitlabCreatePipelineTool } from '@/tools/gitlab/create_pipeline' import { gitlabDeleteIssueTool } from '@/tools/gitlab/delete_issue' +import { gitlabGetFileTool } from '@/tools/gitlab/get_file' import { gitlabGetIssueTool } from '@/tools/gitlab/get_issue' +import { gitlabGetJobLogTool } from '@/tools/gitlab/get_job_log' import { gitlabGetMergeRequestTool } from '@/tools/gitlab/get_merge_request' +import { gitlabGetMergeRequestChangesTool } from '@/tools/gitlab/get_merge_request_changes' import { gitlabGetPipelineTool } from '@/tools/gitlab/get_pipeline' import { gitlabGetProjectTool } from '@/tools/gitlab/get_project' +import { gitlabListBranchesTool } from '@/tools/gitlab/list_branches' +import { gitlabListCommitsTool } from '@/tools/gitlab/list_commits' import { gitlabListIssuesTool } from '@/tools/gitlab/list_issues' import { gitlabListMergeRequestsTool } from '@/tools/gitlab/list_merge_requests' +import { gitlabListPipelineJobsTool } from '@/tools/gitlab/list_pipeline_jobs' import { gitlabListPipelinesTool } from '@/tools/gitlab/list_pipelines' import { gitlabListProjectsTool } from '@/tools/gitlab/list_projects' +import { gitlabListRepositoryTreeTool } from '@/tools/gitlab/list_repository_tree' import { gitlabMergeMergeRequestTool } from '@/tools/gitlab/merge_merge_request' +import { gitlabPlayJobTool } from '@/tools/gitlab/play_job' import { gitlabRetryPipelineTool } from '@/tools/gitlab/retry_pipeline' +import { gitlabUpdateFileTool } from '@/tools/gitlab/update_file' import { gitlabUpdateIssueTool } from '@/tools/gitlab/update_issue' import { gitlabUpdateMergeRequestTool } from '@/tools/gitlab/update_merge_request' @@ -36,10 +48,26 @@ export { gitlabUpdateMergeRequestTool, gitlabMergeMergeRequestTool, gitlabCreateMergeRequestNoteTool, + gitlabGetMergeRequestChangesTool, + gitlabApproveMergeRequestTool, // Pipelines gitlabListPipelinesTool, gitlabGetPipelineTool, gitlabCreatePipelineTool, gitlabRetryPipelineTool, gitlabCancelPipelineTool, + // Jobs + gitlabListPipelineJobsTool, + gitlabGetJobLogTool, + gitlabPlayJobTool, + // Repository Files & Tree + gitlabListRepositoryTreeTool, + gitlabGetFileTool, + gitlabCreateFileTool, + gitlabUpdateFileTool, + // Branches + gitlabListBranchesTool, + gitlabCreateBranchTool, + // Commits + gitlabListCommitsTool, } diff --git a/apps/sim/tools/gitlab/list_branches.ts b/apps/sim/tools/gitlab/list_branches.ts new file mode 100644 index 00000000000..2943c250285 --- /dev/null +++ b/apps/sim/tools/gitlab/list_branches.ts @@ -0,0 +1,103 @@ +import type { GitLabListBranchesParams, GitLabListBranchesResponse } from '@/tools/gitlab/types' +import { getGitLabApiBase } from '@/tools/gitlab/utils' +import type { ToolConfig } from '@/tools/types' + +export const gitlabListBranchesTool: ToolConfig< + GitLabListBranchesParams, + GitLabListBranchesResponse +> = { + id: 'gitlab_list_branches', + name: 'GitLab List Branches', + description: 'List branches in a GitLab project repository', + version: '1.0.0', + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitLab Personal Access Token', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or URL-encoded path', + }, + search: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter branches by name', + }, + perPage: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (default 20, max 100)', + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number for pagination', + }, + }, + + request: { + url: (params) => { + const encodedId = encodeURIComponent(String(params.projectId).trim()) + const queryParams = new URLSearchParams() + + if (params.search) queryParams.append('search', params.search) + if (params.perPage) queryParams.append('per_page', String(params.perPage)) + if (params.page) queryParams.append('page', String(params.page)) + + const query = queryParams.toString() + return `${getGitLabApiBase(params.host)}/projects/${encodedId}/repository/branches${query ? `?${query}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + 'PRIVATE-TOKEN': params.accessToken, + }), + }, + + transformResponse: async (response) => { + if (!response.ok) { + const errorText = await response.text() + return { + success: false, + error: `GitLab API error: ${response.status} ${errorText}`, + output: {}, + } + } + + const branches = await response.json() + const total = response.headers.get('x-total') + + return { + success: true, + output: { + branches: branches ?? [], + total: total ? Number.parseInt(total, 10) : (branches?.length ?? 0), + }, + } + }, + + outputs: { + branches: { + type: 'array', + description: 'List of branches', + }, + total: { + type: 'number', + description: 'Total number of branches', + }, + }, +} diff --git a/apps/sim/tools/gitlab/list_commits.ts b/apps/sim/tools/gitlab/list_commits.ts new file mode 100644 index 00000000000..b3be01049d2 --- /dev/null +++ b/apps/sim/tools/gitlab/list_commits.ts @@ -0,0 +1,129 @@ +import type { GitLabListCommitsParams, GitLabListCommitsResponse } from '@/tools/gitlab/types' +import { getGitLabApiBase } from '@/tools/gitlab/utils' +import type { ToolConfig } from '@/tools/types' + +export const gitlabListCommitsTool: ToolConfig = + { + id: 'gitlab_list_commits', + name: 'GitLab List Commits', + description: 'List commits in a GitLab project repository', + version: '1.0.0', + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitLab Personal Access Token', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or URL-encoded path', + }, + refName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Branch, tag, or revision range to list commits from', + }, + since: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only commits after this ISO 8601 date', + }, + until: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only commits before this ISO 8601 date', + }, + path: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only commits affecting this file path', + }, + author: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter commits by author', + }, + perPage: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (default 20, max 100)', + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number for pagination', + }, + }, + + request: { + url: (params) => { + const encodedId = encodeURIComponent(String(params.projectId).trim()) + const queryParams = new URLSearchParams() + + if (params.refName) queryParams.append('ref_name', params.refName) + if (params.since) queryParams.append('since', params.since) + if (params.until) queryParams.append('until', params.until) + if (params.path) queryParams.append('path', params.path) + if (params.author) queryParams.append('author', params.author) + if (params.perPage) queryParams.append('per_page', String(params.perPage)) + if (params.page) queryParams.append('page', String(params.page)) + + const query = queryParams.toString() + return `${getGitLabApiBase(params.host)}/projects/${encodedId}/repository/commits${query ? `?${query}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + 'PRIVATE-TOKEN': params.accessToken, + }), + }, + + transformResponse: async (response) => { + if (!response.ok) { + const errorText = await response.text() + return { + success: false, + error: `GitLab API error: ${response.status} ${errorText}`, + output: {}, + } + } + + const commits = await response.json() + const total = response.headers.get('x-total') + + return { + success: true, + output: { + commits: commits ?? [], + total: total ? Number.parseInt(total, 10) : (commits?.length ?? 0), + }, + } + }, + + outputs: { + commits: { + type: 'array', + description: 'List of commits', + }, + total: { + type: 'number', + description: 'Total number of commits', + }, + }, + } diff --git a/apps/sim/tools/gitlab/list_issues.ts b/apps/sim/tools/gitlab/list_issues.ts index 40a016f3b34..8a46a915314 100644 --- a/apps/sim/tools/gitlab/list_issues.ts +++ b/apps/sim/tools/gitlab/list_issues.ts @@ -85,7 +85,7 @@ export const gitlabListIssuesTool: ToolConfig { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) const queryParams = new URLSearchParams() if (params.state) queryParams.append('state', params.state) diff --git a/apps/sim/tools/gitlab/list_merge_requests.ts b/apps/sim/tools/gitlab/list_merge_requests.ts index 2cdae3301c4..65ad7efe797 100644 --- a/apps/sim/tools/gitlab/list_merge_requests.ts +++ b/apps/sim/tools/gitlab/list_merge_requests.ts @@ -85,7 +85,7 @@ export const gitlabListMergeRequestsTool: ToolConfig< request: { url: (params) => { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) const queryParams = new URLSearchParams() if (params.state) queryParams.append('state', params.state) diff --git a/apps/sim/tools/gitlab/list_pipeline_jobs.ts b/apps/sim/tools/gitlab/list_pipeline_jobs.ts new file mode 100644 index 00000000000..db63ccb8df6 --- /dev/null +++ b/apps/sim/tools/gitlab/list_pipeline_jobs.ts @@ -0,0 +1,119 @@ +import type { + GitLabListPipelineJobsParams, + GitLabListPipelineJobsResponse, +} from '@/tools/gitlab/types' +import { getGitLabApiBase } from '@/tools/gitlab/utils' +import type { ToolConfig } from '@/tools/types' + +export const gitlabListPipelineJobsTool: ToolConfig< + GitLabListPipelineJobsParams, + GitLabListPipelineJobsResponse +> = { + id: 'gitlab_list_pipeline_jobs', + name: 'GitLab List Pipeline Jobs', + description: 'List jobs for a GitLab pipeline', + version: '1.0.0', + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitLab Personal Access Token', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or URL-encoded path', + }, + pipelineId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Pipeline ID', + }, + scope: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter jobs by scope (e.g. created, running, success, failed)', + }, + includeRetried: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to include retried jobs', + }, + perPage: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (default 20, max 100)', + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number for pagination', + }, + }, + + request: { + url: (params) => { + const encodedId = encodeURIComponent(String(params.projectId).trim()) + const queryParams = new URLSearchParams() + + if (params.scope) queryParams.append('scope', params.scope) + if (params.includeRetried) queryParams.append('include_retried', 'true') + if (params.perPage) queryParams.append('per_page', String(params.perPage)) + if (params.page) queryParams.append('page', String(params.page)) + + const query = queryParams.toString() + return `${getGitLabApiBase(params.host)}/projects/${encodedId}/pipelines/${params.pipelineId}/jobs${query ? `?${query}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + 'PRIVATE-TOKEN': params.accessToken, + }), + }, + + transformResponse: async (response) => { + if (!response.ok) { + const errorText = await response.text() + return { + success: false, + error: `GitLab API error: ${response.status} ${errorText}`, + output: {}, + } + } + + const jobs = await response.json() + const total = response.headers.get('x-total') + + return { + success: true, + output: { + jobs: jobs ?? [], + total: total ? Number.parseInt(total, 10) : (jobs?.length ?? 0), + }, + } + }, + + outputs: { + jobs: { + type: 'array', + description: 'List of pipeline jobs', + }, + total: { + type: 'number', + description: 'Total number of jobs', + }, + }, +} diff --git a/apps/sim/tools/gitlab/list_pipelines.ts b/apps/sim/tools/gitlab/list_pipelines.ts index 80294e85f73..bb21081e2ea 100644 --- a/apps/sim/tools/gitlab/list_pipelines.ts +++ b/apps/sim/tools/gitlab/list_pipelines.ts @@ -71,7 +71,7 @@ export const gitlabListPipelinesTool: ToolConfig< request: { url: (params) => { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) const queryParams = new URLSearchParams() if (params.ref) queryParams.append('ref', params.ref) diff --git a/apps/sim/tools/gitlab/list_repository_tree.ts b/apps/sim/tools/gitlab/list_repository_tree.ts new file mode 100644 index 00000000000..efbc439b852 --- /dev/null +++ b/apps/sim/tools/gitlab/list_repository_tree.ts @@ -0,0 +1,120 @@ +import type { + GitLabListRepositoryTreeParams, + GitLabListRepositoryTreeResponse, +} from '@/tools/gitlab/types' +import { getGitLabApiBase } from '@/tools/gitlab/utils' +import type { ToolConfig } from '@/tools/types' + +export const gitlabListRepositoryTreeTool: ToolConfig< + GitLabListRepositoryTreeParams, + GitLabListRepositoryTreeResponse +> = { + id: 'gitlab_list_repository_tree', + name: 'GitLab List Repository Tree', + description: 'List files and directories in a GitLab project repository', + version: '1.0.0', + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitLab Personal Access Token', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or URL-encoded path', + }, + path: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Path inside the repository to list', + }, + ref: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Branch, tag, or commit SHA to list from', + }, + recursive: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to list files recursively', + }, + perPage: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (default 20, max 100)', + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number for pagination', + }, + }, + + request: { + url: (params) => { + const encodedId = encodeURIComponent(String(params.projectId).trim()) + const queryParams = new URLSearchParams() + + if (params.path) queryParams.append('path', params.path) + if (params.ref) queryParams.append('ref', params.ref) + if (params.recursive) queryParams.append('recursive', 'true') + if (params.perPage) queryParams.append('per_page', String(params.perPage)) + if (params.page) queryParams.append('page', String(params.page)) + + const query = queryParams.toString() + return `${getGitLabApiBase(params.host)}/projects/${encodedId}/repository/tree${query ? `?${query}` : ''}` + }, + method: 'GET', + headers: (params) => ({ + 'PRIVATE-TOKEN': params.accessToken, + }), + }, + + transformResponse: async (response) => { + if (!response.ok) { + const errorText = await response.text() + return { + success: false, + error: `GitLab API error: ${response.status} ${errorText}`, + output: {}, + } + } + + const tree = await response.json() + const total = response.headers.get('x-total') + + return { + success: true, + output: { + tree: tree ?? [], + total: total ? Number.parseInt(total, 10) : (tree?.length ?? 0), + }, + } + }, + + outputs: { + tree: { + type: 'array', + description: 'List of repository tree entries', + }, + total: { + type: 'number', + description: 'Total number of tree entries', + }, + }, +} diff --git a/apps/sim/tools/gitlab/merge_merge_request.ts b/apps/sim/tools/gitlab/merge_merge_request.ts index 500e6ebfd07..4991e67e209 100644 --- a/apps/sim/tools/gitlab/merge_merge_request.ts +++ b/apps/sim/tools/gitlab/merge_merge_request.ts @@ -73,7 +73,7 @@ export const gitlabMergeMergeRequestTool: ToolConfig< request: { url: (params) => { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) return `${getGitLabApiBase(params.host)}/projects/${encodedId}/merge_requests/${params.mergeRequestIid}/merge` }, method: 'PUT', diff --git a/apps/sim/tools/gitlab/play_job.ts b/apps/sim/tools/gitlab/play_job.ts new file mode 100644 index 00000000000..bd8a37effca --- /dev/null +++ b/apps/sim/tools/gitlab/play_job.ts @@ -0,0 +1,90 @@ +import type { GitLabPlayJobParams, GitLabPlayJobResponse } from '@/tools/gitlab/types' +import { getGitLabApiBase } from '@/tools/gitlab/utils' +import type { ToolConfig } from '@/tools/types' + +export const gitlabPlayJobTool: ToolConfig = { + id: 'gitlab_play_job', + name: 'GitLab Play Job', + description: 'Trigger (play) a manual GitLab job', + version: '1.0.0', + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitLab Personal Access Token', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or URL-encoded path', + }, + jobId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Job ID', + }, + }, + + request: { + url: (params) => { + const encodedId = encodeURIComponent(String(params.projectId).trim()) + return `${getGitLabApiBase(params.host)}/projects/${encodedId}/jobs/${params.jobId}/play` + }, + method: 'POST', + headers: (params) => ({ + 'PRIVATE-TOKEN': params.accessToken, + }), + }, + + transformResponse: async (response) => { + if (!response.ok) { + const errorText = await response.text() + return { + success: false, + error: `GitLab API error: ${response.status} ${errorText}`, + output: {}, + } + } + + const data = await response.json() + + return { + success: true, + output: { + id: data.id ?? null, + name: data.name ?? null, + status: data.status ?? null, + webUrl: data.web_url ?? null, + }, + } + }, + + outputs: { + id: { + type: 'number', + description: 'The job ID', + }, + name: { + type: 'string', + description: 'The job name', + }, + status: { + type: 'string', + description: 'The job status', + }, + webUrl: { + type: 'string', + description: 'The web URL of the job', + }, + }, +} diff --git a/apps/sim/tools/gitlab/retry_pipeline.ts b/apps/sim/tools/gitlab/retry_pipeline.ts index 48143109c97..470ade6d9f0 100644 --- a/apps/sim/tools/gitlab/retry_pipeline.ts +++ b/apps/sim/tools/gitlab/retry_pipeline.ts @@ -40,7 +40,7 @@ export const gitlabRetryPipelineTool: ToolConfig< request: { url: (params) => { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) return `${getGitLabApiBase(params.host)}/projects/${encodedId}/pipelines/${params.pipelineId}/retry` }, method: 'POST', diff --git a/apps/sim/tools/gitlab/types.ts b/apps/sim/tools/gitlab/types.ts index af865ed3ef8..882399d3bdd 100644 --- a/apps/sim/tools/gitlab/types.ts +++ b/apps/sim/tools/gitlab/types.ts @@ -124,6 +124,46 @@ interface GitLabPipeline { } } +interface GitLabTreeEntry { + id: string + name: string + type: string + path: string + mode: string +} + +interface GitLabCommit { + id: string + short_id: string + title: string + message: string + author_name: string + authored_date: string + created_at: string + web_url: string +} + +interface GitLabJob { + id: number + name: string + stage: string + status: string + started_at?: string | null + finished_at?: string | null + duration?: number | null + web_url: string + ref?: string +} + +interface GitLabMergeRequestChange { + old_path: string + new_path: string + diff: string + new_file: boolean + deleted_file: boolean + renamed_file: boolean +} + interface GitLabBranch { name: string merged: boolean @@ -371,7 +411,7 @@ export interface GitLabCancelPipelineParams extends GitLabBaseParams { // ===== Branch Parameters ===== -interface GitLabListBranchesParams extends GitLabBaseParams { +export interface GitLabListBranchesParams extends GitLabBaseParams { projectId: string | number search?: string perPage?: number @@ -383,7 +423,7 @@ interface GitLabGetBranchParams extends GitLabBaseParams { branch: string } -interface GitLabCreateBranchParams extends GitLabBaseParams { +export interface GitLabCreateBranchParams extends GitLabBaseParams { projectId: string | number branch: string ref: string @@ -394,6 +434,87 @@ interface GitLabDeleteBranchParams extends GitLabBaseParams { branch: string } +// ===== Repository File & Tree Parameters ===== + +export interface GitLabListRepositoryTreeParams extends GitLabBaseParams { + projectId: string | number + path?: string + ref?: string + recursive?: boolean + perPage?: number + page?: number +} + +export interface GitLabGetFileParams extends GitLabBaseParams { + projectId: string | number + filePath: string + ref: string +} + +export interface GitLabCreateFileParams extends GitLabBaseParams { + projectId: string | number + filePath: string + branch: string + content: string + commitMessage: string +} + +export interface GitLabUpdateFileParams extends GitLabBaseParams { + projectId: string | number + filePath: string + branch: string + content: string + commitMessage: string + lastCommitId?: string +} + +// ===== Commit Parameters ===== + +export interface GitLabListCommitsParams extends GitLabBaseParams { + projectId: string | number + refName?: string + since?: string + until?: string + path?: string + author?: string + perPage?: number + page?: number +} + +// ===== Merge Request Review Parameters ===== + +export interface GitLabGetMergeRequestChangesParams extends GitLabBaseParams { + projectId: string | number + mergeRequestIid: number +} + +export interface GitLabApproveMergeRequestParams extends GitLabBaseParams { + projectId: string | number + mergeRequestIid: number + sha?: string +} + +// ===== Job Parameters ===== + +export interface GitLabListPipelineJobsParams extends GitLabBaseParams { + projectId: string | number + pipelineId: number + scope?: string + includeRetried?: boolean + perPage?: number + page?: number +} + +export interface GitLabGetJobLogParams extends GitLabBaseParams { + projectId: string | number + jobId: number +} + +export interface GitLabPlayJobParams extends GitLabBaseParams { + projectId: string | number + jobId: number +} + // ===== Note/Comment Parameters ===== interface GitLabListIssueNotesParams extends GitLabBaseParams { @@ -560,7 +681,7 @@ export interface GitLabCancelPipelineResponse extends ToolResponse { } } -interface GitLabListBranchesResponse extends ToolResponse { +export interface GitLabListBranchesResponse extends ToolResponse { output: { branches?: GitLabBranch[] total?: number @@ -573,9 +694,12 @@ interface GitLabGetBranchResponse extends ToolResponse { } } -interface GitLabCreateBranchResponse extends ToolResponse { +export interface GitLabCreateBranchResponse extends ToolResponse { output: { - branch?: GitLabBranch + name?: string | null + webUrl?: string | null + protected?: boolean | null + commit?: GitLabBranch['commit'] | null } } @@ -624,6 +748,94 @@ interface GitLabListUsersResponse extends ToolResponse { } } +// ===== Repository File & Tree Responses ===== + +export interface GitLabListRepositoryTreeResponse extends ToolResponse { + output: { + tree?: GitLabTreeEntry[] + total?: number + } +} + +export interface GitLabGetFileResponse extends ToolResponse { + output: { + filePath?: string | null + fileName?: string | null + size?: number | null + ref?: string | null + blobId?: string | null + lastCommitId?: string | null + content?: string + } +} + +export interface GitLabCreateFileResponse extends ToolResponse { + output: { + filePath?: string | null + branch?: string | null + } +} + +export interface GitLabUpdateFileResponse extends ToolResponse { + output: { + filePath?: string | null + branch?: string | null + } +} + +// ===== Commit Responses ===== + +export interface GitLabListCommitsResponse extends ToolResponse { + output: { + commits?: GitLabCommit[] + total?: number + } +} + +// ===== Merge Request Review Responses ===== + +export interface GitLabGetMergeRequestChangesResponse extends ToolResponse { + output: { + iid?: number | null + title?: string | null + state?: string | null + changes?: GitLabMergeRequestChange[] + changesCount?: number + } +} + +export interface GitLabApproveMergeRequestResponse extends ToolResponse { + output: { + approvalsRequired?: number | null + approvalsLeft?: number | null + approvedBy?: unknown[] + } +} + +// ===== Job Responses ===== + +export interface GitLabListPipelineJobsResponse extends ToolResponse { + output: { + jobs?: GitLabJob[] + total?: number + } +} + +export interface GitLabGetJobLogResponse extends ToolResponse { + output: { + log?: string + } +} + +export interface GitLabPlayJobResponse extends ToolResponse { + output: { + id?: number | null + name?: string | null + status?: string | null + webUrl?: string | null + } +} + // ===== Union Response Type ===== export type GitLabResponse = @@ -654,3 +866,13 @@ export type GitLabResponse = | GitLabCreateLabelResponse | GitLabGetCurrentUserResponse | GitLabListUsersResponse + | GitLabListRepositoryTreeResponse + | GitLabGetFileResponse + | GitLabCreateFileResponse + | GitLabUpdateFileResponse + | GitLabListCommitsResponse + | GitLabGetMergeRequestChangesResponse + | GitLabApproveMergeRequestResponse + | GitLabListPipelineJobsResponse + | GitLabGetJobLogResponse + | GitLabPlayJobResponse diff --git a/apps/sim/tools/gitlab/update_file.ts b/apps/sim/tools/gitlab/update_file.ts new file mode 100644 index 00000000000..f6d92f178ca --- /dev/null +++ b/apps/sim/tools/gitlab/update_file.ts @@ -0,0 +1,117 @@ +import type { GitLabUpdateFileParams, GitLabUpdateFileResponse } from '@/tools/gitlab/types' +import { getGitLabApiBase } from '@/tools/gitlab/utils' +import type { ToolConfig } from '@/tools/types' + +export const gitlabUpdateFileTool: ToolConfig = { + id: 'gitlab_update_file', + name: 'GitLab Update File', + description: 'Update an existing file in a GitLab project repository', + version: '1.0.0', + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitLab Personal Access Token', + }, + host: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Self-managed GitLab host (e.g. gitlab.example.com). Defaults to gitlab.com.', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or URL-encoded path', + }, + filePath: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Path to the file in the repository', + }, + branch: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Branch to commit the update to', + }, + content: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'New file content', + }, + commitMessage: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Commit message', + }, + lastCommitId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Last known commit ID for the file (optimistic locking)', + }, + }, + + request: { + url: (params) => { + const encodedId = encodeURIComponent(String(params.projectId).trim()) + const encodedPath = encodeURIComponent(String(params.filePath)) + return `${getGitLabApiBase(params.host)}/projects/${encodedId}/repository/files/${encodedPath}` + }, + method: 'PUT', + headers: (params) => ({ + 'Content-Type': 'application/json', + 'PRIVATE-TOKEN': params.accessToken, + }), + body: (params) => { + const body: Record = { + branch: params.branch, + content: params.content, + commit_message: params.commitMessage, + } + + if (params.lastCommitId) body.last_commit_id = params.lastCommitId + + return body + }, + }, + + transformResponse: async (response) => { + if (!response.ok) { + const errorText = await response.text() + return { + success: false, + error: `GitLab API error: ${response.status} ${errorText}`, + output: {}, + } + } + + const data = await response.json() + + return { + success: true, + output: { + filePath: data.file_path ?? null, + branch: data.branch ?? null, + }, + } + }, + + outputs: { + filePath: { + type: 'string', + description: 'The updated file path', + }, + branch: { + type: 'string', + description: 'The branch the update was committed to', + }, + }, +} diff --git a/apps/sim/tools/gitlab/update_issue.ts b/apps/sim/tools/gitlab/update_issue.ts index acf7ca25402..b2e8b8503aa 100644 --- a/apps/sim/tools/gitlab/update_issue.ts +++ b/apps/sim/tools/gitlab/update_issue.ts @@ -86,7 +86,7 @@ export const gitlabUpdateIssueTool: ToolConfig { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) return `${getGitLabApiBase(params.host)}/projects/${encodedId}/issues/${params.issueIid}` }, method: 'PUT', diff --git a/apps/sim/tools/gitlab/update_merge_request.ts b/apps/sim/tools/gitlab/update_merge_request.ts index 69632a637d5..a0930567db9 100644 --- a/apps/sim/tools/gitlab/update_merge_request.ts +++ b/apps/sim/tools/gitlab/update_merge_request.ts @@ -103,7 +103,7 @@ export const gitlabUpdateMergeRequestTool: ToolConfig< request: { url: (params) => { - const encodedId = encodeURIComponent(String(params.projectId)) + const encodedId = encodeURIComponent(String(params.projectId).trim()) return `${getGitLabApiBase(params.host)}/projects/${encodedId}/merge_requests/${params.mergeRequestIid}` }, method: 'PUT', diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index df8a070ec5c..7f8f537c98d 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1082,23 +1082,35 @@ import { githubUpdateReleaseV2Tool, } from '@/tools/github' import { + gitlabApproveMergeRequestTool, gitlabCancelPipelineTool, + gitlabCreateBranchTool, + gitlabCreateFileTool, gitlabCreateIssueNoteTool, gitlabCreateIssueTool, gitlabCreateMergeRequestNoteTool, gitlabCreateMergeRequestTool, gitlabCreatePipelineTool, gitlabDeleteIssueTool, + gitlabGetFileTool, gitlabGetIssueTool, + gitlabGetJobLogTool, + gitlabGetMergeRequestChangesTool, gitlabGetMergeRequestTool, gitlabGetPipelineTool, gitlabGetProjectTool, + gitlabListBranchesTool, + gitlabListCommitsTool, gitlabListIssuesTool, gitlabListMergeRequestsTool, + gitlabListPipelineJobsTool, gitlabListPipelinesTool, gitlabListProjectsTool, + gitlabListRepositoryTreeTool, gitlabMergeMergeRequestTool, + gitlabPlayJobTool, gitlabRetryPipelineTool, + gitlabUpdateFileTool, gitlabUpdateIssueTool, gitlabUpdateMergeRequestTool, } from '@/tools/gitlab' @@ -5477,25 +5489,37 @@ export const tools: Record = { github_check_star_v2: githubCheckStarV2Tool, github_list_stargazers: githubListStargazersTool, github_list_stargazers_v2: githubListStargazersV2Tool, - gitlab_list_projects: gitlabListProjectsTool, - gitlab_get_project: gitlabGetProjectTool, - gitlab_list_issues: gitlabListIssuesTool, - gitlab_get_issue: gitlabGetIssueTool, + gitlab_approve_merge_request: gitlabApproveMergeRequestTool, + gitlab_cancel_pipeline: gitlabCancelPipelineTool, + gitlab_create_branch: gitlabCreateBranchTool, + gitlab_create_file: gitlabCreateFileTool, gitlab_create_issue: gitlabCreateIssueTool, - gitlab_update_issue: gitlabUpdateIssueTool, - gitlab_delete_issue: gitlabDeleteIssueTool, gitlab_create_issue_note: gitlabCreateIssueNoteTool, - gitlab_list_merge_requests: gitlabListMergeRequestsTool, - gitlab_get_merge_request: gitlabGetMergeRequestTool, gitlab_create_merge_request: gitlabCreateMergeRequestTool, - gitlab_update_merge_request: gitlabUpdateMergeRequestTool, - gitlab_merge_merge_request: gitlabMergeMergeRequestTool, gitlab_create_merge_request_note: gitlabCreateMergeRequestNoteTool, - gitlab_list_pipelines: gitlabListPipelinesTool, - gitlab_get_pipeline: gitlabGetPipelineTool, gitlab_create_pipeline: gitlabCreatePipelineTool, + gitlab_delete_issue: gitlabDeleteIssueTool, + gitlab_get_file: gitlabGetFileTool, + gitlab_get_issue: gitlabGetIssueTool, + gitlab_get_job_log: gitlabGetJobLogTool, + gitlab_get_merge_request: gitlabGetMergeRequestTool, + gitlab_get_merge_request_changes: gitlabGetMergeRequestChangesTool, + gitlab_get_pipeline: gitlabGetPipelineTool, + gitlab_get_project: gitlabGetProjectTool, + gitlab_list_branches: gitlabListBranchesTool, + gitlab_list_commits: gitlabListCommitsTool, + gitlab_list_issues: gitlabListIssuesTool, + gitlab_list_merge_requests: gitlabListMergeRequestsTool, + gitlab_list_pipeline_jobs: gitlabListPipelineJobsTool, + gitlab_list_pipelines: gitlabListPipelinesTool, + gitlab_list_projects: gitlabListProjectsTool, + gitlab_list_repository_tree: gitlabListRepositoryTreeTool, + gitlab_merge_merge_request: gitlabMergeMergeRequestTool, + gitlab_play_job: gitlabPlayJobTool, gitlab_retry_pipeline: gitlabRetryPipelineTool, - gitlab_cancel_pipeline: gitlabCancelPipelineTool, + gitlab_update_file: gitlabUpdateFileTool, + gitlab_update_issue: gitlabUpdateIssueTool, + gitlab_update_merge_request: gitlabUpdateMergeRequestTool, grain_list_recordings: grainListRecordingsTool, grain_get_recording: grainGetRecordingTool, grain_get_transcript: grainGetTranscriptTool, From 1e23b62f0947f190e5486883be1e0b1dd2954bd1 Mon Sep 17 00:00:00 2001 From: waleed Date: Wed, 24 Jun 2026 17:56:04 -0700 Subject: [PATCH 2/2] fix(gitlab): address review feedback + regen docs - get_merge_request_changes: use the /diffs endpoint (/changes was removed in GitLab 18.0); return the diff array + count (drops the MR envelope that /diffs no longer provides), fetch max page size in a single call - create_file/update_file: send explicit `encoding: 'text'` for clarity - Remove `// =====` separator comments from types.ts (repo convention) - Regenerate GitLab integration docs + catalog for the 12 new tools --- .../content/docs/en/integrations/gitlab.mdx | 464 ++++++++++++++++++ apps/sim/lib/integrations/integrations.json | 152 +++++- apps/sim/tools/gitlab/create_file.ts | 1 + .../tools/gitlab/get_merge_request_changes.ts | 30 +- apps/sim/tools/gitlab/types.ts | 44 +- apps/sim/tools/gitlab/update_file.ts | 1 + 6 files changed, 624 insertions(+), 68 deletions(-) diff --git a/apps/docs/content/docs/en/integrations/gitlab.mdx b/apps/docs/content/docs/en/integrations/gitlab.mdx index 6d7340bac8e..eb37db7210b 100644 --- a/apps/docs/content/docs/en/integrations/gitlab.mdx +++ b/apps/docs/content/docs/en/integrations/gitlab.mdx @@ -41,6 +41,7 @@ List GitLab projects accessible to the authenticated user | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `owned` | boolean | No | Limit to projects owned by the current user | | `membership` | boolean | No | Limit to projects the current user is a member of | | `search` | string | No | Search projects by name | @@ -65,6 +66,7 @@ Get details of a specific GitLab project | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path \(e.g., "namespace/project"\) | #### Output @@ -81,6 +83,7 @@ List issues in a GitLab project | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `state` | string | No | Filter by state \(opened, closed, all\) | | `labels` | string | No | Comma-separated list of label names | @@ -107,6 +110,7 @@ Get details of a specific GitLab issue | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `issueIid` | number | Yes | Issue number within the project \(the # shown in GitLab UI\) | @@ -124,6 +128,7 @@ Create a new issue in a GitLab project | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `title` | string | Yes | Issue title | | `description` | string | No | Issue description \(Markdown supported\) | @@ -147,6 +152,7 @@ Update an existing issue in a GitLab project | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `issueIid` | number | Yes | Issue internal ID \(IID\) | | `title` | string | No | New issue title | @@ -172,6 +178,7 @@ Delete an issue from a GitLab project | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `issueIid` | number | Yes | Issue internal ID \(IID\) | @@ -189,6 +196,7 @@ Add a comment to a GitLab issue | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `issueIid` | number | Yes | Issue internal ID \(IID\) | | `body` | string | Yes | Comment body \(Markdown supported\) | @@ -207,6 +215,7 @@ List merge requests in a GitLab project | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `state` | string | No | Filter by state \(opened, closed, merged, all\) | | `labels` | string | No | Comma-separated list of label names | @@ -232,6 +241,7 @@ Get details of a specific GitLab merge request | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `mergeRequestIid` | number | Yes | Merge request internal ID \(IID\) | @@ -249,6 +259,7 @@ Create a new merge request in a GitLab project | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `sourceBranch` | string | Yes | Source branch name | | `targetBranch` | string | Yes | Target branch name | @@ -275,6 +286,7 @@ Update an existing merge request in a GitLab project | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `mergeRequestIid` | number | Yes | Merge request internal ID \(IID\) | | `title` | string | No | New merge request title | @@ -302,6 +314,7 @@ Merge a merge request in a GitLab project | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `mergeRequestIid` | number | Yes | Merge request internal ID \(IID\) | | `mergeCommitMessage` | string | No | Custom merge commit message | @@ -324,6 +337,7 @@ Add a comment to a GitLab merge request | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `mergeRequestIid` | number | Yes | Merge request internal ID \(IID\) | | `body` | string | Yes | Comment body \(Markdown supported\) | @@ -342,6 +356,7 @@ List pipelines in a GitLab project | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `ref` | string | No | Filter by ref \(branch or tag\) | | `status` | string | No | Filter by status \(created, waiting_for_resource, preparing, pending, running, success, failed, canceled, skipped, manual, scheduled\) | @@ -365,6 +380,7 @@ Get details of a specific GitLab pipeline | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `pipelineId` | number | Yes | Pipeline ID | @@ -382,6 +398,7 @@ Trigger a new pipeline in a GitLab project | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `ref` | string | Yes | Branch or tag to run the pipeline on | | `variables` | array | No | Array of variables for the pipeline \(each with key, value, and optional variable_type\) | @@ -400,6 +417,7 @@ Retry a failed GitLab pipeline | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `pipelineId` | number | Yes | Pipeline ID | @@ -417,6 +435,7 @@ Cancel a running GitLab pipeline | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | | `projectId` | string | Yes | Project ID or URL-encoded path | | `pipelineId` | number | Yes | Pipeline ID | @@ -426,4 +445,449 @@ Cancel a running GitLab pipeline | --------- | ---- | ----------- | | `pipeline` | object | The cancelled GitLab pipeline | +### `gitlab_list_repository_tree` + +List files and directories in a GitLab project repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | +| `projectId` | string | Yes | Project ID or URL-encoded path | +| `path` | string | No | Path inside the repository to list | +| `ref` | string | No | Branch, tag, or commit SHA to list from | +| `recursive` | boolean | No | Whether to list files recursively | +| `perPage` | number | No | Number of results per page \(default 20, max 100\) | +| `page` | number | No | Page number for pagination | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `tree` | array | List of repository tree entries | +| `total` | number | Total number of tree entries | + +### `gitlab_get_file` + +Get the contents of a file from a GitLab project repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | +| `projectId` | string | Yes | Project ID or URL-encoded path | +| `filePath` | string | Yes | Path to the file in the repository | +| `ref` | string | Yes | Branch, tag, or commit SHA | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `filePath` | string | The file path | +| `fileName` | string | The file name | +| `size` | number | The file size in bytes | +| `ref` | string | The branch, tag, or commit SHA | +| `blobId` | string | The blob ID | +| `lastCommitId` | string | The last commit ID that modified the file | +| `content` | string | The decoded file content | + +### `gitlab_create_file` + +Create a new file in a GitLab project repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | +| `projectId` | string | Yes | Project ID or URL-encoded path | +| `filePath` | string | Yes | Path to the file in the repository | +| `branch` | string | Yes | Branch to commit the new file to | +| `content` | string | Yes | File content | +| `commitMessage` | string | Yes | Commit message | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `filePath` | string | The created file path | +| `branch` | string | The branch the file was committed to | + +### `gitlab_update_file` + +Update an existing file in a GitLab project repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | +| `projectId` | string | Yes | Project ID or URL-encoded path | +| `filePath` | string | Yes | Path to the file in the repository | +| `branch` | string | Yes | Branch to commit the update to | +| `content` | string | Yes | New file content | +| `commitMessage` | string | Yes | Commit message | +| `lastCommitId` | string | No | Last known commit ID for the file \(optimistic locking\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `filePath` | string | The updated file path | +| `branch` | string | The branch the update was committed to | + +### `gitlab_create_branch` + +Create a new branch in a GitLab project repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | +| `projectId` | string | Yes | Project ID or URL-encoded path | +| `branch` | string | Yes | Name of the new branch | +| `ref` | string | Yes | Source branch/tag/SHA | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `name` | string | The created branch name | +| `webUrl` | string | The web URL of the branch | +| `protected` | boolean | Whether the branch is protected | +| `commit` | object | The commit the branch points to | + +### `gitlab_list_branches` + +List branches in a GitLab project repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | +| `projectId` | string | Yes | Project ID or URL-encoded path | +| `search` | string | No | Filter branches by name | +| `perPage` | number | No | Number of results per page \(default 20, max 100\) | +| `page` | number | No | Page number for pagination | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `branches` | array | List of branches | +| `total` | number | Total number of branches | + +### `gitlab_list_commits` + +List commits in a GitLab project repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | +| `projectId` | string | Yes | Project ID or URL-encoded path | +| `refName` | string | No | Branch, tag, or revision range to list commits from | +| `since` | string | No | Only commits after this ISO 8601 date | +| `until` | string | No | Only commits before this ISO 8601 date | +| `path` | string | No | Only commits affecting this file path | +| `author` | string | No | Filter commits by author | +| `perPage` | number | No | Number of results per page \(default 20, max 100\) | +| `page` | number | No | Page number for pagination | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `commits` | array | List of commits | +| `total` | number | Total number of commits | + +### `gitlab_get_merge_request_changes` + +Get the file changes (diffs) of a GitLab merge request + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | +| `projectId` | string | Yes | Project ID or URL-encoded path | +| `mergeRequestIid` | number | Yes | Merge request internal ID \(IID\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `mergeRequestIid` | number | The merge request internal ID \(IID\) | +| `changes` | array | List of file changes \(diffs\) | +| `changesCount` | number | Number of changed files returned | + +### `gitlab_approve_merge_request` + +Approve a GitLab merge request + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | +| `projectId` | string | Yes | Project ID or URL-encoded path | +| `mergeRequestIid` | number | Yes | Merge request internal ID \(IID\) | +| `sha` | string | No | HEAD SHA of the merge request to approve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `approvalsRequired` | number | Number of approvals required | +| `approvalsLeft` | number | Number of approvals still needed | +| `approvedBy` | array | List of approvers | + +### `gitlab_list_pipeline_jobs` + +List jobs for a GitLab pipeline + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | +| `projectId` | string | Yes | Project ID or URL-encoded path | +| `pipelineId` | number | Yes | Pipeline ID | +| `scope` | string | No | Filter jobs by scope \(e.g. created, running, success, failed\) | +| `includeRetried` | boolean | No | Whether to include retried jobs | +| `perPage` | number | No | Number of results per page \(default 20, max 100\) | +| `page` | number | No | Page number for pagination | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `jobs` | array | List of pipeline jobs | +| `total` | number | Total number of jobs | + +### `gitlab_get_job_log` + +Get the log (trace) of a GitLab job + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | +| `projectId` | string | Yes | Project ID or URL-encoded path | +| `jobId` | number | Yes | Job ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `log` | string | The job log \(trace\) output | + +### `gitlab_play_job` + +Trigger (play) a manual GitLab job + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `host` | string | No | Self-managed GitLab host \(e.g. gitlab.example.com\). Defaults to gitlab.com. | +| `projectId` | string | Yes | Project ID or URL-encoded path | +| `jobId` | number | Yes | Job ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | number | The job ID | +| `name` | string | The job name | +| `status` | string | The job status | +| `webUrl` | string | The web URL of the job | + + + +## Triggers + +A **Trigger** is a block that starts a workflow when an event happens in this service. + +### GitLab Comment + +Trigger workflow when a comment is added on a commit, merge request, or issue + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `accessToken` | string | Yes | Used to create the webhook in your project. Requires the Maintainer or Owner role. | +| `projectId` | string | Yes | The GitLab project to register the webhook on. | +| `host` | string | No | Self-managed GitLab host. Leave blank for gitlab.com. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `object_kind` | string | Event kind \(note\) | +| `event_type` | string | GitLab event type from the X-Gitlab-Event header | +| `object_attributes` | object | object_attributes output from the tool | +| ↳ `id` | number | Comment ID | +| ↳ `note` | string | Comment body | +| ↳ `noteable_type` | string | What the comment is on \(Commit, MergeRequest, Issue, Snippet\) | +| ↳ `action` | string | Action \(create, update\) | +| ↳ `url` | string | Comment URL | + + +--- + +### GitLab Event + +Trigger workflow from any GitLab webhook event + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `accessToken` | string | Yes | Used to create the webhook in your project. Requires the Maintainer or Owner role. | +| `projectId` | string | Yes | The GitLab project to register the webhook on. | +| `host` | string | No | Self-managed GitLab host. Leave blank for gitlab.com. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `object_kind` | string | Event kind \(push, merge_request, issue, etc.\) | +| `event_type` | string | GitLab event type from the X-Gitlab-Event header | +| `user` | json | Actor that triggered the event \(when present\) | +| `object_attributes` | json | Event-specific attributes \(varies by object_kind\) | + + +--- + +### GitLab Issue + +Trigger workflow when an issue is opened, updated, or closed in GitLab + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `accessToken` | string | Yes | Used to create the webhook in your project. Requires the Maintainer or Owner role. | +| `projectId` | string | Yes | The GitLab project to register the webhook on. | +| `host` | string | No | Self-managed GitLab host. Leave blank for gitlab.com. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `object_kind` | string | Event kind \(issue\) | +| `event_type` | string | GitLab event type from the X-Gitlab-Event header | +| `object_attributes` | object | object_attributes output from the tool | +| ↳ `id` | number | Global issue ID | +| ↳ `iid` | number | Project-scoped issue number | +| ↳ `title` | string | Issue title | +| ↳ `state` | string | State \(opened, closed\) | +| ↳ `action` | string | Action \(open, close, reopen, update\) | +| ↳ `description` | string | Issue description | +| ↳ `confidential` | boolean | Whether the issue is confidential | +| ↳ `url` | string | Issue URL | + + +--- + +### GitLab Merge Request + +Trigger workflow when a merge request is opened, updated, or merged in GitLab + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `accessToken` | string | Yes | Used to create the webhook in your project. Requires the Maintainer or Owner role. | +| `projectId` | string | Yes | The GitLab project to register the webhook on. | +| `host` | string | No | Self-managed GitLab host. Leave blank for gitlab.com. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `object_kind` | string | Event kind \(merge_request\) | +| `event_type` | string | GitLab event type from the X-Gitlab-Event header | +| `object_attributes` | object | object_attributes output from the tool | +| ↳ `id` | number | Global merge request ID | +| ↳ `iid` | number | Project-scoped merge request number | +| ↳ `title` | string | Merge request title | +| ↳ `state` | string | State \(opened, closed, merged, locked\) | +| ↳ `action` | string | Action \(open, close, reopen, update, merge, etc.\) | +| ↳ `source_branch` | string | Source branch | +| ↳ `target_branch` | string | Target branch | +| ↳ `merge_status` | string | Merge status | +| ↳ `draft` | boolean | Whether the merge request is a draft | +| ↳ `url` | string | Merge request URL | + + +--- + +### GitLab Pipeline + +Trigger workflow when a pipeline status changes in GitLab + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `accessToken` | string | Yes | Used to create the webhook in your project. Requires the Maintainer or Owner role. | +| `projectId` | string | Yes | The GitLab project to register the webhook on. | +| `host` | string | No | Self-managed GitLab host. Leave blank for gitlab.com. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `object_kind` | string | Event kind \(pipeline\) | +| `event_type` | string | GitLab event type from the X-Gitlab-Event header | +| `object_attributes` | object | object_attributes output from the tool | +| ↳ `id` | number | Pipeline ID | +| ↳ `status` | string | Pipeline status \(success, failed, running, etc.\) | +| ↳ `detailed_status` | string | Detailed pipeline status | +| ↳ `ref` | string | Ref the pipeline ran on | +| ↳ `sha` | string | Commit SHA | +| ↳ `source` | string | Pipeline source \(push, web, schedule, etc.\) | +| ↳ `duration` | number | Pipeline duration in seconds | +| ↳ `url` | string | Pipeline URL | + + +--- + +### GitLab Push + +Trigger workflow when commits are pushed to a GitLab project + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `accessToken` | string | Yes | Used to create the webhook in your project. Requires the Maintainer or Owner role. | +| `projectId` | string | Yes | The GitLab project to register the webhook on. | +| `host` | string | No | Self-managed GitLab host. Leave blank for gitlab.com. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `object_kind` | string | Event kind \(push\) | +| `event_type` | string | GitLab event type from the X-Gitlab-Event header | +| `ref` | string | Git ref that was pushed \(e.g. refs/heads/main\) | +| `branch` | string | Branch name derived from ref | +| `before` | string | SHA before the push | +| `after` | string | SHA after the push | +| `checkout_sha` | string | SHA of the most recent commit | +| `user_username` | string | Username of the pusher | +| `user_name` | string | Display name of the pusher | +| `user_email` | string | Email of the pusher | +| `total_commits_count` | number | Number of commits in the push | +| `commits` | json | Array of commit objects included in this push | diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index cf2683bc099..12543d0f629 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -1,5 +1,5 @@ { - "updatedAt": "2026-06-18", + "updatedAt": "2026-06-25", "integrations": [ { "type": "onepassword", @@ -5628,11 +5628,90 @@ { "name": "Cancel Pipeline", "description": "Cancel a running GitLab pipeline" + }, + { + "name": "List Repository Tree", + "description": "List files and directories in a GitLab project repository" + }, + { + "name": "Get File", + "description": "Get the contents of a file from a GitLab project repository" + }, + { + "name": "Create File", + "description": "Create a new file in a GitLab project repository" + }, + { + "name": "Update File", + "description": "Update an existing file in a GitLab project repository" + }, + { + "name": "List Commits", + "description": "List commits in a GitLab project repository" + }, + { + "name": "List Branches", + "description": "List branches in a GitLab project repository" + }, + { + "name": "Create Branch", + "description": "Create a new branch in a GitLab project repository" + }, + { + "name": "Get MR Changes", + "description": "Get the file changes (diffs) of a GitLab merge request" + }, + { + "name": "Approve Merge Request", + "description": "Approve a GitLab merge request" + }, + { + "name": "List Pipeline Jobs", + "description": "List jobs for a GitLab pipeline" + }, + { + "name": "Get Job Log", + "description": "Get the log (trace) of a GitLab job" + }, + { + "name": "Play Job", + "description": "Trigger (play) a manual GitLab job" } ], - "operationCount": 19, - "triggers": [], - "triggerCount": 0, + "operationCount": 31, + "triggers": [ + { + "id": "gitlab_push", + "name": "GitLab Push", + "description": "Trigger workflow when commits are pushed to a GitLab project" + }, + { + "id": "gitlab_merge_request", + "name": "GitLab Merge Request", + "description": "Trigger workflow when a merge request is opened, updated, or merged in GitLab" + }, + { + "id": "gitlab_issue", + "name": "GitLab Issue", + "description": "Trigger workflow when an issue is opened, updated, or closed in GitLab" + }, + { + "id": "gitlab_pipeline", + "name": "GitLab Pipeline", + "description": "Trigger workflow when a pipeline status changes in GitLab" + }, + { + "id": "gitlab_comment", + "name": "GitLab Comment", + "description": "Trigger workflow when a comment is added on a commit, merge request, or issue" + }, + { + "id": "gitlab_webhook", + "name": "GitLab Event", + "description": "Trigger workflow from any GitLab webhook event" + } + ], + "triggerCount": 6, "authType": "api-key", "category": "tools", "integrationType": "devops", @@ -11233,8 +11312,39 @@ } ], "operationCount": 6, - "triggers": [], - "triggerCount": 0, + "triggers": [ + { + "id": "pagerduty_incident_triggered", + "name": "PagerDuty Incident Triggered", + "description": "Trigger workflow when a new incident is triggered in PagerDuty" + }, + { + "id": "pagerduty_incident_acknowledged", + "name": "PagerDuty Incident Acknowledged", + "description": "Trigger workflow when an incident is acknowledged in PagerDuty" + }, + { + "id": "pagerduty_incident_resolved", + "name": "PagerDuty Incident Resolved", + "description": "Trigger workflow when an incident is resolved in PagerDuty" + }, + { + "id": "pagerduty_incident_escalated", + "name": "PagerDuty Incident Escalated", + "description": "Trigger workflow when an incident is escalated in PagerDuty" + }, + { + "id": "pagerduty_incident_reassigned", + "name": "PagerDuty Incident Reassigned", + "description": "Trigger workflow when an incident is reassigned in PagerDuty" + }, + { + "id": "pagerduty_webhook", + "name": "PagerDuty Incident Event", + "description": "Trigger workflow from any PagerDuty incident event" + } + ], + "triggerCount": 6, "authType": "api-key", "category": "tools", "integrationType": "observability", @@ -18289,8 +18399,34 @@ } ], "operationCount": 26, - "triggers": [], - "triggerCount": 0, + "triggers": [ + { + "id": "zendesk_ticket_created", + "name": "Zendesk Ticket Created", + "description": "Trigger workflow when a new ticket is created in Zendesk" + }, + { + "id": "zendesk_ticket_status_changed", + "name": "Zendesk Ticket Status Changed", + "description": "Trigger workflow when a ticket status changes in Zendesk" + }, + { + "id": "zendesk_ticket_comment_added", + "name": "Zendesk Ticket Comment Added", + "description": "Trigger workflow when a comment is added to a Zendesk ticket" + }, + { + "id": "zendesk_ticket_priority_changed", + "name": "Zendesk Ticket Priority Changed", + "description": "Trigger workflow when a ticket priority changes in Zendesk" + }, + { + "id": "zendesk_webhook", + "name": "Zendesk Ticket Event", + "description": "Trigger workflow from any Zendesk ticket event" + } + ], + "triggerCount": 5, "authType": "api-key", "category": "tools", "integrationType": "support", diff --git a/apps/sim/tools/gitlab/create_file.ts b/apps/sim/tools/gitlab/create_file.ts index 513394698ea..f0468959b4a 100644 --- a/apps/sim/tools/gitlab/create_file.ts +++ b/apps/sim/tools/gitlab/create_file.ts @@ -68,6 +68,7 @@ export const gitlabCreateFileTool: ToolConfig { const encodedId = encodeURIComponent(String(params.projectId).trim()) - return `${getGitLabApiBase(params.host)}/projects/${encodedId}/merge_requests/${params.mergeRequestIid}/changes` + return `${getGitLabApiBase(params.host)}/projects/${encodedId}/merge_requests/${params.mergeRequestIid}/diffs?per_page=100` }, method: 'GET', headers: (params) => ({ @@ -52,7 +58,7 @@ export const gitlabGetMergeRequestChangesTool: ToolConfig< }), }, - transformResponse: async (response) => { + transformResponse: async (response, params) => { if (!response.ok) { const errorText = await response.text() return { @@ -63,14 +69,12 @@ export const gitlabGetMergeRequestChangesTool: ToolConfig< } const data = await response.json() - const changes = data.changes ?? [] + const changes = Array.isArray(data) ? data : [] return { success: true, output: { - iid: data.iid ?? null, - title: data.title ?? null, - state: data.state ?? null, + mergeRequestIid: params?.mergeRequestIid ?? null, changes, changesCount: changes.length, }, @@ -78,17 +82,9 @@ export const gitlabGetMergeRequestChangesTool: ToolConfig< }, outputs: { - iid: { + mergeRequestIid: { type: 'number', - description: 'The merge request internal ID', - }, - title: { - type: 'string', - description: 'The merge request title', - }, - state: { - type: 'string', - description: 'The merge request state', + description: 'The merge request internal ID (IID)', }, changes: { type: 'array', @@ -96,7 +92,7 @@ export const gitlabGetMergeRequestChangesTool: ToolConfig< }, changesCount: { type: 'number', - description: 'Number of changed files', + description: 'Number of changed files returned', }, }, } diff --git a/apps/sim/tools/gitlab/types.ts b/apps/sim/tools/gitlab/types.ts index 882399d3bdd..d87a10d67b6 100644 --- a/apps/sim/tools/gitlab/types.ts +++ b/apps/sim/tools/gitlab/types.ts @@ -1,7 +1,5 @@ import type { ToolResponse } from '@/tools/types' -// ===== Core Types ===== - interface GitLabProject { id: number name: string @@ -230,8 +228,6 @@ interface GitLabMilestone { web_url: string } -// ===== Common Parameters ===== - interface GitLabBaseParams { accessToken: string /** @@ -241,8 +237,6 @@ interface GitLabBaseParams { host?: string } -// ===== Project Parameters ===== - export interface GitLabListProjectsParams extends GitLabBaseParams { owned?: boolean membership?: boolean @@ -258,8 +252,6 @@ export interface GitLabGetProjectParams extends GitLabBaseParams { projectId: string | number } -// ===== Issue Parameters ===== - export interface GitLabListIssuesParams extends GitLabBaseParams { projectId: string | number state?: 'opened' | 'closed' | 'all' @@ -307,8 +299,6 @@ export interface GitLabDeleteIssueParams extends GitLabBaseParams { issueIid: number } -// ===== Merge Request Parameters ===== - export interface GitLabListMergeRequestsParams extends GitLabBaseParams { projectId: string | number state?: 'opened' | 'closed' | 'merged' | 'all' @@ -365,8 +355,6 @@ export interface GitLabMergeMergeRequestParams extends GitLabBaseParams { mergeWhenPipelineSucceeds?: boolean } -// ===== Pipeline Parameters ===== - export interface GitLabListPipelinesParams extends GitLabBaseParams { projectId: string | number ref?: string @@ -409,8 +397,6 @@ export interface GitLabCancelPipelineParams extends GitLabBaseParams { pipelineId: number } -// ===== Branch Parameters ===== - export interface GitLabListBranchesParams extends GitLabBaseParams { projectId: string | number search?: string @@ -434,8 +420,6 @@ interface GitLabDeleteBranchParams extends GitLabBaseParams { branch: string } -// ===== Repository File & Tree Parameters ===== - export interface GitLabListRepositoryTreeParams extends GitLabBaseParams { projectId: string | number path?: string @@ -468,8 +452,6 @@ export interface GitLabUpdateFileParams extends GitLabBaseParams { lastCommitId?: string } -// ===== Commit Parameters ===== - export interface GitLabListCommitsParams extends GitLabBaseParams { projectId: string | number refName?: string @@ -481,8 +463,6 @@ export interface GitLabListCommitsParams extends GitLabBaseParams { page?: number } -// ===== Merge Request Review Parameters ===== - export interface GitLabGetMergeRequestChangesParams extends GitLabBaseParams { projectId: string | number mergeRequestIid: number @@ -494,8 +474,6 @@ export interface GitLabApproveMergeRequestParams extends GitLabBaseParams { sha?: string } -// ===== Job Parameters ===== - export interface GitLabListPipelineJobsParams extends GitLabBaseParams { projectId: string | number pipelineId: number @@ -515,8 +493,6 @@ export interface GitLabPlayJobParams extends GitLabBaseParams { jobId: number } -// ===== Note/Comment Parameters ===== - interface GitLabListIssueNotesParams extends GitLabBaseParams { projectId: string | number issueIid: number @@ -547,8 +523,6 @@ export interface GitLabCreateMergeRequestNoteParams extends GitLabBaseParams { body: string } -// ===== Label Parameters ===== - interface GitLabListLabelsParams extends GitLabBaseParams { projectId: string | number search?: string @@ -563,8 +537,6 @@ interface GitLabCreateLabelParams extends GitLabBaseParams { description?: string } -// ===== User Parameters ===== - interface GitLabGetCurrentUserParams extends GitLabBaseParams {} interface GitLabListUsersParams extends GitLabBaseParams { @@ -573,8 +545,6 @@ interface GitLabListUsersParams extends GitLabBaseParams { page?: number } -// ===== Response Types ===== - export interface GitLabListProjectsResponse extends ToolResponse { output: { projects?: GitLabProject[] @@ -748,8 +718,6 @@ interface GitLabListUsersResponse extends ToolResponse { } } -// ===== Repository File & Tree Responses ===== - export interface GitLabListRepositoryTreeResponse extends ToolResponse { output: { tree?: GitLabTreeEntry[] @@ -783,8 +751,6 @@ export interface GitLabUpdateFileResponse extends ToolResponse { } } -// ===== Commit Responses ===== - export interface GitLabListCommitsResponse extends ToolResponse { output: { commits?: GitLabCommit[] @@ -792,13 +758,9 @@ export interface GitLabListCommitsResponse extends ToolResponse { } } -// ===== Merge Request Review Responses ===== - export interface GitLabGetMergeRequestChangesResponse extends ToolResponse { output: { - iid?: number | null - title?: string | null - state?: string | null + mergeRequestIid?: number | null changes?: GitLabMergeRequestChange[] changesCount?: number } @@ -812,8 +774,6 @@ export interface GitLabApproveMergeRequestResponse extends ToolResponse { } } -// ===== Job Responses ===== - export interface GitLabListPipelineJobsResponse extends ToolResponse { output: { jobs?: GitLabJob[] @@ -836,8 +796,6 @@ export interface GitLabPlayJobResponse extends ToolResponse { } } -// ===== Union Response Type ===== - export type GitLabResponse = | GitLabListProjectsResponse | GitLabGetProjectResponse diff --git a/apps/sim/tools/gitlab/update_file.ts b/apps/sim/tools/gitlab/update_file.ts index f6d92f178ca..38a7beece3d 100644 --- a/apps/sim/tools/gitlab/update_file.ts +++ b/apps/sim/tools/gitlab/update_file.ts @@ -75,6 +75,7 @@ export const gitlabUpdateFileTool: ToolConfig