mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-21 13:00:04 +08:00
feat(slack): add conversations.create and conversations.invite tools (#3720)
* feat(slack): add conversations.create and conversations.invite tools * fix(slack): address PR review comments on conversation tools * feat(slack): wire create/invite conversation tools into Slack block * lint * fix(slack): rename channel output to channelInfo to avoid type collision The block outputs already declare `channel` as type string (channel ID from send operation). Rename the object output to `channelInfo` to match the pattern used by get_channel_info and avoid [object Object] rendering. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(slack): update output key in docs to match channelInfo rename Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(docs): fix lint errors in auto-generated docs files Sort imports in icon-mapping.ts and add trailing newline to meta.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * lint --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
f02f85fded
commit
4c8395928a
@@ -925,6 +925,82 @@ Create a canvas pinned to a Slack channel as its resource hub
|
||||
| --------- | ---- | ----------- |
|
||||
| `canvas_id` | string | ID of the created channel canvas |
|
||||
|
||||
### `slack_create_conversation`
|
||||
|
||||
Create a new public or private channel in a Slack workspace.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | No | Authentication method: oauth or bot_token |
|
||||
| `botToken` | string | No | Bot token for Custom Bot |
|
||||
| `name` | string | Yes | Name of the channel to create \(lowercase, numbers, hyphens, underscores only; max 80 characters\) |
|
||||
| `isPrivate` | boolean | No | Create a private channel instead of a public one \(default: false\) |
|
||||
| `teamId` | string | No | Encoded team ID to create the channel in \(required if using an org token\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `channelInfo` | object | The newly created channel object |
|
||||
| ↳ `id` | string | Channel ID \(e.g., C1234567890\) |
|
||||
| ↳ `name` | string | Channel name without # prefix |
|
||||
| ↳ `is_channel` | boolean | Whether this is a channel |
|
||||
| ↳ `is_private` | boolean | Whether channel is private |
|
||||
| ↳ `is_archived` | boolean | Whether channel is archived |
|
||||
| ↳ `is_general` | boolean | Whether this is the general channel |
|
||||
| ↳ `is_member` | boolean | Whether the bot/user is a member |
|
||||
| ↳ `is_shared` | boolean | Whether channel is shared across workspaces |
|
||||
| ↳ `is_ext_shared` | boolean | Whether channel is externally shared |
|
||||
| ↳ `is_org_shared` | boolean | Whether channel is org-wide shared |
|
||||
| ↳ `num_members` | number | Number of members in the channel |
|
||||
| ↳ `topic` | string | Channel topic |
|
||||
| ↳ `purpose` | string | Channel purpose/description |
|
||||
| ↳ `created` | number | Unix timestamp when channel was created |
|
||||
| ↳ `creator` | string | User ID of channel creator |
|
||||
| ↳ `updated` | number | Unix timestamp of last update |
|
||||
|
||||
### `slack_invite_to_conversation`
|
||||
|
||||
Invite one or more users to a Slack channel. Supports up to 100 users at a time.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `authMethod` | string | No | Authentication method: oauth or bot_token |
|
||||
| `botToken` | string | No | Bot token for Custom Bot |
|
||||
| `channel` | string | Yes | The ID of the channel to invite users to |
|
||||
| `users` | string | Yes | Comma-separated list of user IDs to invite \(up to 100\) |
|
||||
| `force` | boolean | No | When true, continues inviting valid users while skipping invalid ones \(default: false\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `channelInfo` | object | The channel object after inviting users |
|
||||
| ↳ `id` | string | Channel ID \(e.g., C1234567890\) |
|
||||
| ↳ `name` | string | Channel name without # prefix |
|
||||
| ↳ `is_channel` | boolean | Whether this is a channel |
|
||||
| ↳ `is_private` | boolean | Whether channel is private |
|
||||
| ↳ `is_archived` | boolean | Whether channel is archived |
|
||||
| ↳ `is_general` | boolean | Whether this is the general channel |
|
||||
| ↳ `is_member` | boolean | Whether the bot/user is a member |
|
||||
| ↳ `is_shared` | boolean | Whether channel is shared across workspaces |
|
||||
| ↳ `is_ext_shared` | boolean | Whether channel is externally shared |
|
||||
| ↳ `is_org_shared` | boolean | Whether channel is org-wide shared |
|
||||
| ↳ `num_members` | number | Number of members in the channel |
|
||||
| ↳ `topic` | string | Channel topic |
|
||||
| ↳ `purpose` | string | Channel purpose/description |
|
||||
| ↳ `created` | number | Unix timestamp when channel was created |
|
||||
| ↳ `creator` | string | User ID of channel creator |
|
||||
| ↳ `updated` | number | Unix timestamp of last update |
|
||||
| `errors` | array | Per-user errors when force is true and some invitations failed |
|
||||
| ↳ `user` | string | User ID that failed |
|
||||
| ↳ `ok` | boolean | Always false for error entries |
|
||||
| ↳ `error` | string | Error code for this user |
|
||||
|
||||
### `slack_open_view`
|
||||
|
||||
Open a modal view in Slack using a trigger_id from an interaction payload. Used to display forms, confirmations, and other interactive modals.
|
||||
|
||||
@@ -9581,6 +9581,14 @@
|
||||
"name": "Create Channel Canvas",
|
||||
"description": "Create a canvas pinned to a Slack channel as its resource hub"
|
||||
},
|
||||
{
|
||||
"name": "Create Conversation",
|
||||
"description": "Create a new public or private channel in a Slack workspace."
|
||||
},
|
||||
{
|
||||
"name": "Invite to Conversation",
|
||||
"description": "Invite one or more users to a Slack channel. Supports up to 100 users at a time."
|
||||
},
|
||||
{
|
||||
"name": "Open View",
|
||||
"description": "Open a modal view in Slack using a trigger_id from an interaction payload. Used to display forms, confirmations, and other interactive modals."
|
||||
@@ -9598,7 +9606,7 @@
|
||||
"description": "Publish a static view to a user"
|
||||
}
|
||||
],
|
||||
"operationCount": 23,
|
||||
"operationCount": 25,
|
||||
"triggers": [
|
||||
{
|
||||
"id": "slack_webhook",
|
||||
|
||||
@@ -46,6 +46,8 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
|
||||
{ label: 'Get User Presence', id: 'get_user_presence' },
|
||||
{ label: 'Edit Canvas', id: 'edit_canvas' },
|
||||
{ label: 'Create Channel Canvas', id: 'create_channel_canvas' },
|
||||
{ label: 'Create Conversation', id: 'create_conversation' },
|
||||
{ label: 'Invite to Conversation', id: 'invite_to_conversation' },
|
||||
{ label: 'Open View', id: 'open_view' },
|
||||
{ label: 'Update View', id: 'update_view' },
|
||||
{ label: 'Push View', id: 'push_view' },
|
||||
@@ -144,6 +146,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
|
||||
'get_user',
|
||||
'get_user_presence',
|
||||
'edit_canvas',
|
||||
'create_conversation',
|
||||
'open_view',
|
||||
'update_view',
|
||||
'push_view',
|
||||
@@ -179,6 +182,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
|
||||
'get_user',
|
||||
'get_user_presence',
|
||||
'edit_canvas',
|
||||
'create_conversation',
|
||||
'open_view',
|
||||
'update_view',
|
||||
'push_view',
|
||||
@@ -816,6 +820,70 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
value: 'create_channel_canvas',
|
||||
},
|
||||
},
|
||||
// Create Conversation specific fields
|
||||
{
|
||||
id: 'conversationName',
|
||||
title: 'Channel Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g., project-updates',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'create_conversation',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'isPrivate',
|
||||
title: 'Private Channel',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'No', id: 'false' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'create_conversation',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'teamId',
|
||||
title: 'Team ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Encoded team ID (org tokens only)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'create_conversation',
|
||||
},
|
||||
mode: 'advanced',
|
||||
},
|
||||
// Invite to Conversation specific fields
|
||||
{
|
||||
id: 'inviteUsers',
|
||||
title: 'User IDs',
|
||||
type: 'short-input',
|
||||
placeholder: 'Comma-separated user IDs (e.g., U123,U456)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'invite_to_conversation',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'inviteForce',
|
||||
title: 'Skip Invalid Users',
|
||||
type: 'dropdown',
|
||||
options: [
|
||||
{ label: 'No', id: 'false' },
|
||||
{ label: 'Yes', id: 'true' },
|
||||
],
|
||||
value: () => 'false',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'invite_to_conversation',
|
||||
},
|
||||
mode: 'advanced',
|
||||
},
|
||||
// Open View / Push View specific fields
|
||||
{
|
||||
id: 'viewTriggerId',
|
||||
@@ -990,6 +1058,8 @@ Do not include any explanations, markdown formatting, or other text outside the
|
||||
'slack_get_user_presence',
|
||||
'slack_edit_canvas',
|
||||
'slack_create_channel_canvas',
|
||||
'slack_create_conversation',
|
||||
'slack_invite_to_conversation',
|
||||
'slack_open_view',
|
||||
'slack_update_view',
|
||||
'slack_push_view',
|
||||
@@ -1036,6 +1106,10 @@ Do not include any explanations, markdown formatting, or other text outside the
|
||||
return 'slack_edit_canvas'
|
||||
case 'create_channel_canvas':
|
||||
return 'slack_create_channel_canvas'
|
||||
case 'create_conversation':
|
||||
return 'slack_create_conversation'
|
||||
case 'invite_to_conversation':
|
||||
return 'slack_invite_to_conversation'
|
||||
case 'open_view':
|
||||
return 'slack_open_view'
|
||||
case 'update_view':
|
||||
@@ -1090,6 +1164,11 @@ Do not include any explanations, markdown formatting, or other text outside the
|
||||
canvasTitle,
|
||||
channelCanvasTitle,
|
||||
channelCanvasContent,
|
||||
conversationName,
|
||||
isPrivate,
|
||||
teamId,
|
||||
inviteUsers,
|
||||
inviteForce,
|
||||
viewTriggerId,
|
||||
viewInteractivityPointer,
|
||||
viewId,
|
||||
@@ -1264,6 +1343,21 @@ Do not include any explanations, markdown formatting, or other text outside the
|
||||
}
|
||||
break
|
||||
|
||||
case 'create_conversation':
|
||||
baseParams.name = conversationName
|
||||
baseParams.isPrivate = isPrivate === 'true'
|
||||
if (teamId) {
|
||||
baseParams.teamId = teamId
|
||||
}
|
||||
break
|
||||
|
||||
case 'invite_to_conversation':
|
||||
baseParams.users = inviteUsers
|
||||
if (inviteForce === 'true') {
|
||||
baseParams.force = true
|
||||
}
|
||||
break
|
||||
|
||||
case 'open_view':
|
||||
baseParams.triggerId = viewTriggerId
|
||||
if (viewInteractivityPointer) {
|
||||
@@ -1367,6 +1461,13 @@ Do not include any explanations, markdown formatting, or other text outside the
|
||||
// Create Channel Canvas inputs
|
||||
channelCanvasTitle: { type: 'string', description: 'Title for channel canvas' },
|
||||
channelCanvasContent: { type: 'string', description: 'Content for channel canvas' },
|
||||
// Create Conversation inputs
|
||||
conversationName: { type: 'string', description: 'Name for the new channel' },
|
||||
isPrivate: { type: 'string', description: 'Create as private channel (true/false)' },
|
||||
teamId: { type: 'string', description: 'Encoded team ID for org tokens' },
|
||||
// Invite to Conversation inputs
|
||||
inviteUsers: { type: 'string', description: 'Comma-separated user IDs to invite' },
|
||||
inviteForce: { type: 'string', description: 'Skip invalid users (true/false)' },
|
||||
// View operation inputs
|
||||
viewTriggerId: { type: 'string', description: 'Trigger ID from interaction payload' },
|
||||
viewInteractivityPointer: {
|
||||
@@ -1524,6 +1625,13 @@ Do not include any explanations, markdown formatting, or other text outside the
|
||||
'View object with properties: id, team_id, type, title, submit, close, blocks, private_metadata, callback_id, external_id, state, hash, clear_on_close, notify_on_close, root_view_id, previous_view_id, app_id, bot_id',
|
||||
},
|
||||
|
||||
// slack_invite_to_conversation outputs (invite_to_conversation operation)
|
||||
errors: {
|
||||
type: 'json',
|
||||
description:
|
||||
'Array of per-user error objects when force is true and some invitations failed (user, ok, error)',
|
||||
},
|
||||
|
||||
// Trigger outputs (when used as webhook trigger)
|
||||
event_type: { type: 'string', description: 'Type of Slack event that triggered the workflow' },
|
||||
channel_name: { type: 'string', description: 'Human-readable channel name' },
|
||||
|
||||
@@ -1948,6 +1948,7 @@ import {
|
||||
slackAddReactionTool,
|
||||
slackCanvasTool,
|
||||
slackCreateChannelCanvasTool,
|
||||
slackCreateConversationTool,
|
||||
slackDeleteMessageTool,
|
||||
slackDownloadTool,
|
||||
slackEditCanvasTool,
|
||||
@@ -1957,6 +1958,7 @@ import {
|
||||
slackGetThreadTool,
|
||||
slackGetUserPresenceTool,
|
||||
slackGetUserTool,
|
||||
slackInviteToConversationTool,
|
||||
slackListChannelsTool,
|
||||
slackListMembersTool,
|
||||
slackListUsersTool,
|
||||
@@ -2822,6 +2824,8 @@ export const tools: Record<string, ToolConfig> = {
|
||||
slack_publish_view: slackPublishViewTool,
|
||||
slack_edit_canvas: slackEditCanvasTool,
|
||||
slack_create_channel_canvas: slackCreateChannelCanvasTool,
|
||||
slack_create_conversation: slackCreateConversationTool,
|
||||
slack_invite_to_conversation: slackInviteToConversationTool,
|
||||
github_repo_info: githubRepoInfoTool,
|
||||
github_repo_info_v2: githubRepoInfoV2Tool,
|
||||
github_latest_commit: githubLatestCommitTool,
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import type {
|
||||
SlackCreateConversationParams,
|
||||
SlackCreateConversationResponse,
|
||||
} from '@/tools/slack/types'
|
||||
import { CHANNEL_OUTPUT_PROPERTIES } from '@/tools/slack/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const slackCreateConversationTool: ToolConfig<
|
||||
SlackCreateConversationParams,
|
||||
SlackCreateConversationResponse
|
||||
> = {
|
||||
id: 'slack_create_conversation',
|
||||
name: 'Slack Create Conversation',
|
||||
description: 'Create a new public or private channel in a Slack workspace.',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'slack',
|
||||
},
|
||||
|
||||
params: {
|
||||
authMethod: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Authentication method: oauth or bot_token',
|
||||
},
|
||||
botToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Bot token for Custom Bot',
|
||||
},
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token or bot token for Slack API',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Name of the channel to create (lowercase, numbers, hyphens, underscores only; max 80 characters)',
|
||||
},
|
||||
isPrivate: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Create a private channel instead of a public one (default: false)',
|
||||
},
|
||||
teamId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Encoded team ID to create the channel in (required if using an org token)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://slack.com/api/conversations.create',
|
||||
method: 'POST',
|
||||
headers: (params: SlackCreateConversationParams) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.accessToken || params.botToken}`,
|
||||
}),
|
||||
body: (params: SlackCreateConversationParams) => {
|
||||
const body: Record<string, unknown> = {
|
||||
name: params.name?.trim(),
|
||||
}
|
||||
if (params.isPrivate != null) {
|
||||
body.is_private = params.isPrivate
|
||||
}
|
||||
if (params.teamId?.trim()) {
|
||||
body.team_id = params.teamId.trim()
|
||||
}
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.ok) {
|
||||
if (data.error === 'name_taken') {
|
||||
throw new Error('A channel with this name already exists in the workspace.')
|
||||
}
|
||||
if (
|
||||
data.error === 'invalid_name' ||
|
||||
data.error === 'invalid_name_specials' ||
|
||||
data.error === 'invalid_name_maxlength'
|
||||
) {
|
||||
throw new Error(
|
||||
'Invalid channel name. Use only lowercase letters, numbers, hyphens, and underscores (max 80 characters).'
|
||||
)
|
||||
}
|
||||
if (data.error === 'missing_scope') {
|
||||
throw new Error(
|
||||
'Missing required permissions. Please reconnect your Slack account with the necessary scopes (channels:manage, groups:write).'
|
||||
)
|
||||
}
|
||||
if (data.error === 'invalid_auth') {
|
||||
throw new Error('Invalid authentication. Please check your Slack credentials.')
|
||||
}
|
||||
if (data.error === 'restricted_action') {
|
||||
throw new Error('Workspace policy prevents channel creation.')
|
||||
}
|
||||
throw new Error(data.error || 'Failed to create conversation in Slack')
|
||||
}
|
||||
|
||||
const ch = data.channel || {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
channelInfo: {
|
||||
id: ch.id,
|
||||
name: ch.name,
|
||||
is_private: ch.is_private || false,
|
||||
is_archived: ch.is_archived || false,
|
||||
is_member: ch.is_member || false,
|
||||
topic: ch.topic?.value || '',
|
||||
purpose: ch.purpose?.value || '',
|
||||
created: ch.created,
|
||||
creator: ch.creator,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
channelInfo: {
|
||||
type: 'object',
|
||||
description: 'The newly created channel object',
|
||||
properties: CHANNEL_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { slackAddReactionTool } from '@/tools/slack/add_reaction'
|
||||
import { slackCanvasTool } from '@/tools/slack/canvas'
|
||||
import { slackCreateChannelCanvasTool } from '@/tools/slack/create_channel_canvas'
|
||||
import { slackCreateConversationTool } from '@/tools/slack/create_conversation'
|
||||
import { slackDeleteMessageTool } from '@/tools/slack/delete_message'
|
||||
import { slackDownloadTool } from '@/tools/slack/download'
|
||||
import { slackEditCanvasTool } from '@/tools/slack/edit_canvas'
|
||||
@@ -10,6 +11,7 @@ import { slackGetMessageTool } from '@/tools/slack/get_message'
|
||||
import { slackGetThreadTool } from '@/tools/slack/get_thread'
|
||||
import { slackGetUserTool } from '@/tools/slack/get_user'
|
||||
import { slackGetUserPresenceTool } from '@/tools/slack/get_user_presence'
|
||||
import { slackInviteToConversationTool } from '@/tools/slack/invite_to_conversation'
|
||||
import { slackListChannelsTool } from '@/tools/slack/list_channels'
|
||||
import { slackListMembersTool } from '@/tools/slack/list_members'
|
||||
import { slackListUsersTool } from '@/tools/slack/list_users'
|
||||
@@ -25,6 +27,7 @@ import { slackUpdateViewTool } from '@/tools/slack/update_view'
|
||||
export {
|
||||
slackMessageTool,
|
||||
slackCanvasTool,
|
||||
slackCreateConversationTool,
|
||||
slackCreateChannelCanvasTool,
|
||||
slackMessageReaderTool,
|
||||
slackDownloadTool,
|
||||
@@ -46,4 +49,5 @@ export {
|
||||
slackPublishViewTool,
|
||||
slackGetMessageTool,
|
||||
slackGetThreadTool,
|
||||
slackInviteToConversationTool,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
import type {
|
||||
SlackInviteToConversationParams,
|
||||
SlackInviteToConversationResponse,
|
||||
} from '@/tools/slack/types'
|
||||
import { CHANNEL_OUTPUT_PROPERTIES } from '@/tools/slack/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const slackInviteToConversationTool: ToolConfig<
|
||||
SlackInviteToConversationParams,
|
||||
SlackInviteToConversationResponse
|
||||
> = {
|
||||
id: 'slack_invite_to_conversation',
|
||||
name: 'Slack Invite to Conversation',
|
||||
description: 'Invite one or more users to a Slack channel. Supports up to 100 users at a time.',
|
||||
version: '1.0.0',
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'slack',
|
||||
},
|
||||
|
||||
params: {
|
||||
authMethod: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Authentication method: oauth or bot_token',
|
||||
},
|
||||
botToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Bot token for Custom Bot',
|
||||
},
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'hidden',
|
||||
description: 'OAuth access token or bot token for Slack API',
|
||||
},
|
||||
channel: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the channel to invite users to',
|
||||
},
|
||||
users: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of user IDs to invite (up to 100)',
|
||||
},
|
||||
force: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'When true, continues inviting valid users while skipping invalid ones (default: false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://slack.com/api/conversations.invite',
|
||||
method: 'POST',
|
||||
headers: (params: SlackInviteToConversationParams) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.accessToken || params.botToken}`,
|
||||
}),
|
||||
body: (params: SlackInviteToConversationParams) => {
|
||||
const body: Record<string, unknown> = {
|
||||
channel: params.channel?.trim(),
|
||||
users: params.users?.trim(),
|
||||
}
|
||||
if (params.force != null) {
|
||||
body.force = params.force
|
||||
}
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.ok) {
|
||||
if (data.error === 'channel_not_found') {
|
||||
throw new Error('Channel not found. Please verify the channel ID.')
|
||||
}
|
||||
if (data.error === 'user_not_found') {
|
||||
throw new Error('One or more user IDs were not found.')
|
||||
}
|
||||
if (data.error === 'cant_invite_self') {
|
||||
throw new Error('You cannot invite yourself to a channel.')
|
||||
}
|
||||
if (data.error === 'already_in_channel') {
|
||||
throw new Error('One or more users are already in the channel.')
|
||||
}
|
||||
if (data.error === 'is_archived') {
|
||||
throw new Error('The channel is archived and cannot accept new members.')
|
||||
}
|
||||
if (data.error === 'not_in_channel') {
|
||||
throw new Error('The authenticated user is not a member of this channel.')
|
||||
}
|
||||
if (data.error === 'cant_invite') {
|
||||
throw new Error('This user cannot be invited to the channel.')
|
||||
}
|
||||
if (data.error === 'no_permission') {
|
||||
throw new Error('You do not have permission to invite this user to the channel.')
|
||||
}
|
||||
if (data.error === 'org_user_not_in_team') {
|
||||
throw new Error(
|
||||
'One or more invited members are in the Enterprise org but not this workspace.'
|
||||
)
|
||||
}
|
||||
if (data.error === 'missing_scope') {
|
||||
throw new Error(
|
||||
'Missing required permissions. Please reconnect your Slack account with the necessary scopes (channels:manage, groups:write).'
|
||||
)
|
||||
}
|
||||
if (data.error === 'invalid_auth') {
|
||||
throw new Error('Invalid authentication. Please check your Slack credentials.')
|
||||
}
|
||||
throw new Error(data.error || 'Failed to invite users to Slack conversation')
|
||||
}
|
||||
|
||||
const ch = data.channel || {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
channelInfo: {
|
||||
id: ch.id,
|
||||
name: ch.name,
|
||||
is_private: ch.is_private || false,
|
||||
is_archived: ch.is_archived || false,
|
||||
is_member: ch.is_member || false,
|
||||
topic: ch.topic?.value || '',
|
||||
purpose: ch.purpose?.value || '',
|
||||
created: ch.created,
|
||||
creator: ch.creator,
|
||||
},
|
||||
...(data.errors?.length ? { errors: data.errors } : {}),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
channelInfo: {
|
||||
type: 'object',
|
||||
description: 'The channel object after inviting users',
|
||||
properties: CHANNEL_OUTPUT_PROPERTIES,
|
||||
},
|
||||
errors: {
|
||||
type: 'array',
|
||||
description: 'Per-user errors when force is true and some invitations failed',
|
||||
optional: true,
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
user: { type: 'string', description: 'User ID that failed' },
|
||||
ok: { type: 'boolean', description: 'Always false for error entries' },
|
||||
error: { type: 'string', description: 'Error code for this user' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -709,6 +709,18 @@ export interface SlackGetUserPresenceParams extends SlackBaseParams {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export interface SlackCreateConversationParams extends SlackBaseParams {
|
||||
name: string
|
||||
isPrivate?: boolean
|
||||
teamId?: string
|
||||
}
|
||||
|
||||
export interface SlackInviteToConversationParams extends SlackBaseParams {
|
||||
channel: string
|
||||
users: string
|
||||
force?: boolean
|
||||
}
|
||||
|
||||
export interface SlackEditCanvasParams extends SlackBaseParams {
|
||||
canvasId: string
|
||||
operation: string
|
||||
@@ -1030,6 +1042,19 @@ export interface SlackGetChannelInfoResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export interface SlackCreateConversationResponse extends ToolResponse {
|
||||
output: {
|
||||
channelInfo: SlackChannel
|
||||
}
|
||||
}
|
||||
|
||||
export interface SlackInviteToConversationResponse extends ToolResponse {
|
||||
output: {
|
||||
channelInfo: SlackChannel
|
||||
errors?: Array<{ user: string; ok: boolean; error: string }>
|
||||
}
|
||||
}
|
||||
|
||||
export interface SlackGetUserPresenceResponse extends ToolResponse {
|
||||
output: {
|
||||
presence: string
|
||||
@@ -1118,6 +1143,8 @@ export type SlackResponse =
|
||||
| SlackGetUserPresenceResponse
|
||||
| SlackEditCanvasResponse
|
||||
| SlackCreateChannelCanvasResponse
|
||||
| SlackCreateConversationResponse
|
||||
| SlackInviteToConversationResponse
|
||||
| SlackOpenViewResponse
|
||||
| SlackUpdateViewResponse
|
||||
| SlackPushViewResponse
|
||||
|
||||
Reference in New Issue
Block a user