Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,049 changes: 1,049 additions & 0 deletions apps/docs/content/docs/en/tools/github.mdx

Large diffs are not rendered by default.

1,268 changes: 1,264 additions & 4 deletions apps/sim/blocks/blocks/github.ts

Large diffs are not rendered by default.

28 changes: 28 additions & 0 deletions apps/sim/lib/webhooks/processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,34 @@ export async function queueWebhookExecution(
return NextResponse.json({ error: 'Workspace billing account required' }, { status: 402 })
}

// GitHub event filtering for event-specific triggers
if (foundWebhook.provider === 'github') {
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
const triggerId = providerConfig.triggerId as string | undefined

if (triggerId && triggerId !== 'github_webhook') {
const eventType = request.headers.get('x-github-event')
const action = body.action

const { isGitHubEventMatch } = await import('@/triggers/github/utils')

if (!isGitHubEventMatch(triggerId, eventType || '', action, body)) {
logger.debug(
`[${options.requestId}] GitHub event mismatch for trigger ${triggerId}. Event: ${eventType}, Action: ${action}. Skipping execution.`
)

// Return 200 OK to prevent GitHub from retrying
return NextResponse.json({
message: 'Event type does not match trigger configuration. Ignoring.',
})
}

logger.debug(
`[${options.requestId}] GitHub event matched trigger ${triggerId}. Event: ${eventType}, Action: ${action}`
)
}
}

const headers = Object.fromEntries(request.headers.entries())

// For Microsoft Teams Graph notifications, extract unique identifiers for idempotency
Expand Down
108 changes: 108 additions & 0 deletions apps/sim/tools/github/add_assignees.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import type { AddAssigneesParams, IssueResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'

export const addAssigneesTool: ToolConfig<AddAssigneesParams, IssueResponse> = {
id: 'github_add_assignees',
name: 'GitHub Add Assignees',
description: 'Add assignees to an issue in a GitHub repository',
version: '1.0.0',

params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
issue_number: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Issue number',
},
assignees: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated list of usernames to assign to the issue',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},

request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/assignees`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const assigneesArray = params.assignees
.split(',')
.map((a) => a.trim())
.filter((a) => a)
return {
assignees: assigneesArray,
}
},
},

transformResponse: async (response) => {
const issue = await response.json()
const labels = issue.labels?.map((label: any) => label.name) || []
const assignees = issue.assignees?.map((assignee: any) => assignee.login) || []
const content = `Assignees added to issue #${issue.number}: "${issue.title}"
All assignees: ${assignees.join(', ')}
URL: ${issue.html_url}`

return {
success: true,
output: {
content,
metadata: {
number: issue.number,
title: issue.title,
state: issue.state,
html_url: issue.html_url,
labels,
assignees,
created_at: issue.created_at,
updated_at: issue.updated_at,
body: issue.body,
},
},
}
},

outputs: {
content: { type: 'string', description: 'Human-readable assignees confirmation' },
metadata: {
type: 'object',
description: 'Updated issue metadata with assignees',
properties: {
number: { type: 'number', description: 'Issue number' },
title: { type: 'string', description: 'Issue title' },
state: { type: 'string', description: 'Issue state (open/closed)' },
html_url: { type: 'string', description: 'GitHub web URL' },
labels: { type: 'array', description: 'Array of label names' },
assignees: { type: 'array', description: 'All assignees on the issue' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
body: { type: 'string', description: 'Issue body/description' },
},
},
},
}
96 changes: 96 additions & 0 deletions apps/sim/tools/github/add_labels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type { AddLabelsParams, LabelsResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'

export const addLabelsTool: ToolConfig<AddLabelsParams, LabelsResponse> = {
id: 'github_add_labels',
name: 'GitHub Add Labels',
description: 'Add labels to an issue in a GitHub repository',
version: '1.0.0',

params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
issue_number: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Issue number',
},
labels: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated list of label names to add to the issue',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},

request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/labels`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const labelsArray = params.labels
.split(',')
.map((l) => l.trim())
.filter((l) => l)
return {
labels: labelsArray,
}
},
},

transformResponse: async (response) => {
const labelsData = await response.json()

const labels = labelsData.map((label: any) => label.name)

const content = `Labels added to issue successfully!
All labels on issue: ${labels.join(', ')}`

return {
success: true,
output: {
content,
metadata: {
labels,
issue_number: 0, // Will be filled from params in actual implementation
html_url: '', // Will be constructed from params
},
},
}
},

outputs: {
content: { type: 'string', description: 'Human-readable labels confirmation' },
metadata: {
type: 'object',
description: 'Labels metadata',
properties: {
labels: { type: 'array', description: 'All labels currently on the issue' },
issue_number: { type: 'number', description: 'Issue number' },
html_url: { type: 'string', description: 'GitHub issue URL' },
},
},
},
}
124 changes: 124 additions & 0 deletions apps/sim/tools/github/cancel_workflow_run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import type { CancelWorkflowRunParams, CancelWorkflowRunResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'

export const cancelWorkflowRunTool: ToolConfig<CancelWorkflowRunParams, CancelWorkflowRunResponse> =
{
id: 'github_cancel_workflow_run',
name: 'GitHub Cancel Workflow Run',
description:
'Cancel a workflow run. Returns 202 Accepted if cancellation is initiated, or 409 Conflict if the run cannot be cancelled (already completed).',
version: '1.0.0',

params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
run_id: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Workflow run ID to cancel',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},

request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/actions/runs/${params.run_id}/cancel`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},

transformResponse: async (response, params) => {
if (!params) {
return {
success: false,
error: 'Missing parameters',
output: {
content: '',
metadata: {
run_id: 0,
status: 'error',
},
},
}
}

if (response.status === 202) {
const content = `Workflow run #${params.run_id} cancellation initiated successfully.
The run will be cancelled shortly.`

return {
success: true,
output: {
content,
metadata: {
run_id: params.run_id,
status: 'cancellation_initiated',
},
},
}
}
if (response.status === 409) {
const content = `Cannot cancel workflow run #${params.run_id}.
The run may have already completed or been cancelled.`

return {
success: false,
output: {
content,
metadata: {
run_id: params.run_id,
status: 'cannot_cancel',
},
},
}
}

const content = `Workflow run #${params.run_id} cancellation request processed.`

return {
success: true,
output: {
content,
metadata: {
run_id: params.run_id,
status: 'processed',
},
},
}
},

outputs: {
content: { type: 'string', description: 'Cancellation status message' },
metadata: {
type: 'object',
description: 'Cancellation metadata',
properties: {
run_id: { type: 'number', description: 'Workflow run ID' },
status: {
type: 'string',
description: 'Cancellation status (cancellation_initiated, cannot_cancel, processed)',
},
},
},
},
}
Loading