Skip to content

Commit 1c864e0

Browse files
committed
feat(slack): add conversations.create and conversations.invite tools
1 parent 44ceed4 commit 1c864e0

File tree

5 files changed

+354
-0
lines changed

5 files changed

+354
-0
lines changed

apps/sim/tools/registry.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1948,6 +1948,7 @@ import {
19481948
slackAddReactionTool,
19491949
slackCanvasTool,
19501950
slackCreateChannelCanvasTool,
1951+
slackCreateConversationTool,
19511952
slackDeleteMessageTool,
19521953
slackDownloadTool,
19531954
slackEditCanvasTool,
@@ -1957,6 +1958,7 @@ import {
19571958
slackGetThreadTool,
19581959
slackGetUserPresenceTool,
19591960
slackGetUserTool,
1961+
slackInviteToConversationTool,
19601962
slackListChannelsTool,
19611963
slackListMembersTool,
19621964
slackListUsersTool,
@@ -2822,6 +2824,8 @@ export const tools: Record<string, ToolConfig> = {
28222824
slack_publish_view: slackPublishViewTool,
28232825
slack_edit_canvas: slackEditCanvasTool,
28242826
slack_create_channel_canvas: slackCreateChannelCanvasTool,
2827+
slack_create_conversation: slackCreateConversationTool,
2828+
slack_invite_to_conversation: slackInviteToConversationTool,
28252829
github_repo_info: githubRepoInfoTool,
28262830
github_repo_info_v2: githubRepoInfoV2Tool,
28272831
github_latest_commit: githubLatestCommitTool,
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import type { SlackCreateConversationParams } from '@/tools/slack/types'
2+
import { CHANNEL_OUTPUT_PROPERTIES } from '@/tools/slack/types'
3+
import type { ToolConfig } from '@/tools/types'
4+
5+
interface SlackCreateConversationResponse {
6+
success: boolean
7+
output: {
8+
channel: {
9+
id: string
10+
name: string
11+
is_private: boolean
12+
is_archived: boolean
13+
is_member: boolean
14+
topic: string
15+
purpose: string
16+
created: number
17+
creator: string
18+
}
19+
}
20+
}
21+
22+
export const slackCreateConversationTool: ToolConfig<
23+
SlackCreateConversationParams,
24+
SlackCreateConversationResponse
25+
> = {
26+
id: 'slack_create_conversation',
27+
name: 'Slack Create Conversation',
28+
description: 'Create a new public or private channel in a Slack workspace.',
29+
version: '1.0.0',
30+
31+
oauth: {
32+
required: true,
33+
provider: 'slack',
34+
},
35+
36+
params: {
37+
authMethod: {
38+
type: 'string',
39+
required: false,
40+
visibility: 'user-only',
41+
description: 'Authentication method: oauth or bot_token',
42+
},
43+
botToken: {
44+
type: 'string',
45+
required: false,
46+
visibility: 'user-only',
47+
description: 'Bot token for Custom Bot',
48+
},
49+
accessToken: {
50+
type: 'string',
51+
required: false,
52+
visibility: 'hidden',
53+
description: 'OAuth access token or bot token for Slack API',
54+
},
55+
name: {
56+
type: 'string',
57+
required: true,
58+
visibility: 'user-or-llm',
59+
description:
60+
'Name of the channel to create (lowercase, numbers, hyphens, underscores only; max 80 characters)',
61+
},
62+
isPrivate: {
63+
type: 'boolean',
64+
required: false,
65+
visibility: 'user-or-llm',
66+
description: 'Create a private channel instead of a public one (default: false)',
67+
},
68+
teamId: {
69+
type: 'string',
70+
required: false,
71+
visibility: 'user-or-llm',
72+
description: 'Encoded team ID to create the channel in (required if using an org token)',
73+
},
74+
},
75+
76+
request: {
77+
url: 'https://slack.com/api/conversations.create',
78+
method: 'POST',
79+
headers: (params: SlackCreateConversationParams) => ({
80+
'Content-Type': 'application/json',
81+
Authorization: `Bearer ${params.accessToken || params.botToken}`,
82+
}),
83+
body: (params: SlackCreateConversationParams) => {
84+
const body: Record<string, unknown> = {
85+
name: params.name?.trim(),
86+
}
87+
if (params.isPrivate != null) {
88+
body.is_private = params.isPrivate
89+
}
90+
if (params.teamId?.trim()) {
91+
body.team_id = params.teamId.trim()
92+
}
93+
return body
94+
},
95+
},
96+
97+
transformResponse: async (response: Response) => {
98+
const data = await response.json()
99+
100+
if (!data.ok) {
101+
if (data.error === 'name_taken') {
102+
throw new Error('A channel with this name already exists in the workspace.')
103+
}
104+
if (
105+
data.error === 'invalid_name' ||
106+
data.error === 'invalid_name_specials' ||
107+
data.error === 'invalid_name_maxlength'
108+
) {
109+
throw new Error(
110+
'Invalid channel name. Use only lowercase letters, numbers, hyphens, and underscores (max 80 characters).'
111+
)
112+
}
113+
if (data.error === 'missing_scope') {
114+
throw new Error(
115+
'Missing required permissions. Please reconnect your Slack account with the necessary scopes (channels:manage, groups:write).'
116+
)
117+
}
118+
if (data.error === 'invalid_auth') {
119+
throw new Error('Invalid authentication. Please check your Slack credentials.')
120+
}
121+
if (data.error === 'restricted_action') {
122+
throw new Error('Workspace policy prevents channel creation.')
123+
}
124+
throw new Error(data.error || 'Failed to create conversation in Slack')
125+
}
126+
127+
const ch = data.channel || {}
128+
129+
return {
130+
success: true,
131+
output: {
132+
channel: {
133+
id: ch.id,
134+
name: ch.name,
135+
is_private: ch.is_private || false,
136+
is_archived: ch.is_archived || false,
137+
is_member: ch.is_member || false,
138+
topic: ch.topic?.value || '',
139+
purpose: ch.purpose?.value || '',
140+
created: ch.created ?? null,
141+
creator: ch.creator ?? null,
142+
},
143+
},
144+
}
145+
},
146+
147+
outputs: {
148+
channel: {
149+
type: 'object',
150+
description: 'The newly created channel object',
151+
properties: CHANNEL_OUTPUT_PROPERTIES,
152+
},
153+
},
154+
}

apps/sim/tools/slack/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { slackAddReactionTool } from '@/tools/slack/add_reaction'
22
import { slackCanvasTool } from '@/tools/slack/canvas'
33
import { slackCreateChannelCanvasTool } from '@/tools/slack/create_channel_canvas'
4+
import { slackCreateConversationTool } from '@/tools/slack/create_conversation'
45
import { slackDeleteMessageTool } from '@/tools/slack/delete_message'
56
import { slackDownloadTool } from '@/tools/slack/download'
67
import { slackEditCanvasTool } from '@/tools/slack/edit_canvas'
@@ -10,6 +11,7 @@ import { slackGetMessageTool } from '@/tools/slack/get_message'
1011
import { slackGetThreadTool } from '@/tools/slack/get_thread'
1112
import { slackGetUserTool } from '@/tools/slack/get_user'
1213
import { slackGetUserPresenceTool } from '@/tools/slack/get_user_presence'
14+
import { slackInviteToConversationTool } from '@/tools/slack/invite_to_conversation'
1315
import { slackListChannelsTool } from '@/tools/slack/list_channels'
1416
import { slackListMembersTool } from '@/tools/slack/list_members'
1517
import { slackListUsersTool } from '@/tools/slack/list_users'
@@ -25,6 +27,7 @@ import { slackUpdateViewTool } from '@/tools/slack/update_view'
2527
export {
2628
slackMessageTool,
2729
slackCanvasTool,
30+
slackCreateConversationTool,
2831
slackCreateChannelCanvasTool,
2932
slackMessageReaderTool,
3033
slackDownloadTool,
@@ -46,4 +49,5 @@ export {
4649
slackPublishViewTool,
4750
slackGetMessageTool,
4851
slackGetThreadTool,
52+
slackInviteToConversationTool,
4953
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import type { SlackInviteToConversationParams } from '@/tools/slack/types'
2+
import { CHANNEL_OUTPUT_PROPERTIES } from '@/tools/slack/types'
3+
import type { ToolConfig } from '@/tools/types'
4+
5+
interface SlackInviteToConversationResponse {
6+
success: boolean
7+
output: {
8+
channel: {
9+
id: string
10+
name: string
11+
is_private: boolean
12+
is_archived: boolean
13+
is_member: boolean
14+
topic: string
15+
purpose: string
16+
created: number
17+
creator: string
18+
}
19+
}
20+
}
21+
22+
export const slackInviteToConversationTool: ToolConfig<
23+
SlackInviteToConversationParams,
24+
SlackInviteToConversationResponse
25+
> = {
26+
id: 'slack_invite_to_conversation',
27+
name: 'Slack Invite to Conversation',
28+
description: 'Invite one or more users to a Slack channel. Supports up to 100 users at a time.',
29+
version: '1.0.0',
30+
31+
oauth: {
32+
required: true,
33+
provider: 'slack',
34+
},
35+
36+
params: {
37+
authMethod: {
38+
type: 'string',
39+
required: false,
40+
visibility: 'user-only',
41+
description: 'Authentication method: oauth or bot_token',
42+
},
43+
botToken: {
44+
type: 'string',
45+
required: false,
46+
visibility: 'user-only',
47+
description: 'Bot token for Custom Bot',
48+
},
49+
accessToken: {
50+
type: 'string',
51+
required: false,
52+
visibility: 'hidden',
53+
description: 'OAuth access token or bot token for Slack API',
54+
},
55+
channel: {
56+
type: 'string',
57+
required: true,
58+
visibility: 'user-or-llm',
59+
description: 'The ID of the channel to invite users to',
60+
},
61+
users: {
62+
type: 'string',
63+
required: true,
64+
visibility: 'user-or-llm',
65+
description: 'Comma-separated list of user IDs to invite (up to 100)',
66+
},
67+
force: {
68+
type: 'boolean',
69+
required: false,
70+
visibility: 'user-or-llm',
71+
description:
72+
'When true, continues inviting valid users while skipping invalid ones (default: false)',
73+
},
74+
},
75+
76+
request: {
77+
url: 'https://slack.com/api/conversations.invite',
78+
method: 'POST',
79+
headers: (params: SlackInviteToConversationParams) => ({
80+
'Content-Type': 'application/json',
81+
Authorization: `Bearer ${params.accessToken || params.botToken}`,
82+
}),
83+
body: (params: SlackInviteToConversationParams) => {
84+
const body: Record<string, unknown> = {
85+
channel: params.channel?.trim(),
86+
users: params.users?.trim(),
87+
}
88+
if (params.force != null) {
89+
body.force = params.force
90+
}
91+
return body
92+
},
93+
},
94+
95+
transformResponse: async (response: Response) => {
96+
const data = await response.json()
97+
98+
if (!data.ok) {
99+
if (data.error === 'channel_not_found') {
100+
throw new Error('Channel not found. Please verify the channel ID.')
101+
}
102+
if (data.error === 'user_not_found') {
103+
throw new Error('One or more user IDs were not found.')
104+
}
105+
if (data.error === 'cant_invite_self') {
106+
throw new Error('You cannot invite yourself to a channel.')
107+
}
108+
if (data.error === 'already_in_channel') {
109+
throw new Error('One or more users are already in the channel.')
110+
}
111+
if (data.error === 'is_archived') {
112+
throw new Error('The channel is archived and cannot accept new members.')
113+
}
114+
if (data.error === 'not_in_channel') {
115+
throw new Error('The authenticated user is not a member of this channel.')
116+
}
117+
if (data.error === 'cant_invite') {
118+
throw new Error('This user cannot be invited to the channel.')
119+
}
120+
if (data.error === 'no_permission') {
121+
throw new Error('You do not have permission to invite this user to the channel.')
122+
}
123+
if (data.error === 'org_user_not_in_team') {
124+
throw new Error(
125+
'One or more invited members are in the Enterprise org but not this workspace.'
126+
)
127+
}
128+
if (data.error === 'missing_scope') {
129+
throw new Error(
130+
'Missing required permissions. Please reconnect your Slack account with the necessary scopes (channels:manage, groups:write).'
131+
)
132+
}
133+
if (data.error === 'invalid_auth') {
134+
throw new Error('Invalid authentication. Please check your Slack credentials.')
135+
}
136+
throw new Error(data.error || 'Failed to invite users to Slack conversation')
137+
}
138+
139+
const ch = data.channel || {}
140+
141+
return {
142+
success: true,
143+
output: {
144+
channel: {
145+
id: ch.id,
146+
name: ch.name,
147+
is_private: ch.is_private || false,
148+
is_archived: ch.is_archived || false,
149+
is_member: ch.is_member || false,
150+
topic: ch.topic?.value || '',
151+
purpose: ch.purpose?.value || '',
152+
created: ch.created ?? null,
153+
creator: ch.creator ?? null,
154+
},
155+
},
156+
}
157+
},
158+
159+
outputs: {
160+
channel: {
161+
type: 'object',
162+
description: 'The channel object after inviting users',
163+
properties: CHANNEL_OUTPUT_PROPERTIES,
164+
},
165+
},
166+
}

0 commit comments

Comments
 (0)