mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-28 03:00:29 -04:00
feat(slack): canvas related operations (#4306)
* feat(slack): canvas related operations * extract shared code
This commit is contained in:
committed by
GitHub
parent
76ad59fd7d
commit
2a52141d2f
@@ -46,6 +46,10 @@ 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: 'Get Canvas Info', id: 'get_canvas' },
|
||||
{ label: 'List Canvases', id: 'list_canvases' },
|
||||
{ label: 'Lookup Canvas Sections', id: 'lookup_canvas_sections' },
|
||||
{ label: 'Delete Canvas', id: 'delete_canvas' },
|
||||
{ label: 'Create Conversation', id: 'create_conversation' },
|
||||
{ label: 'Invite to Conversation', id: 'invite_to_conversation' },
|
||||
{ label: 'Open View', id: 'open_view' },
|
||||
@@ -146,6 +150,10 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
|
||||
'get_user',
|
||||
'get_user_presence',
|
||||
'edit_canvas',
|
||||
'get_canvas',
|
||||
'list_canvases',
|
||||
'lookup_canvas_sections',
|
||||
'delete_canvas',
|
||||
'create_conversation',
|
||||
'open_view',
|
||||
'update_view',
|
||||
@@ -182,6 +190,10 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
|
||||
'get_user',
|
||||
'get_user_presence',
|
||||
'edit_canvas',
|
||||
'get_canvas',
|
||||
'list_canvases',
|
||||
'lookup_canvas_sections',
|
||||
'delete_canvas',
|
||||
'create_conversation',
|
||||
'open_view',
|
||||
'update_view',
|
||||
@@ -820,6 +832,132 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
value: 'create_channel_canvas',
|
||||
},
|
||||
},
|
||||
// Get Canvas specific fields
|
||||
{
|
||||
id: 'getCanvasId',
|
||||
title: 'Canvas ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter canvas ID (e.g., F1234ABCD)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'get_canvas',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
// List Canvases specific fields
|
||||
{
|
||||
id: 'canvasListChannel',
|
||||
title: 'Channel ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Optional channel filter (e.g., C1234567890)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'list_canvases',
|
||||
},
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'canvasListCount',
|
||||
title: 'Canvas Limit',
|
||||
type: 'short-input',
|
||||
placeholder: '100',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'list_canvases',
|
||||
},
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'canvasListPage',
|
||||
title: 'Page',
|
||||
type: 'short-input',
|
||||
placeholder: '1',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'list_canvases',
|
||||
},
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'canvasListUser',
|
||||
title: 'User ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Optional creator filter (e.g., U1234567890)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'list_canvases',
|
||||
},
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'canvasListTsFrom',
|
||||
title: 'Created After',
|
||||
type: 'short-input',
|
||||
placeholder: 'Unix timestamp (e.g., 123456789)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'list_canvases',
|
||||
},
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'canvasListTsTo',
|
||||
title: 'Created Before',
|
||||
type: 'short-input',
|
||||
placeholder: 'Unix timestamp (e.g., 123456789)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'list_canvases',
|
||||
},
|
||||
mode: 'advanced',
|
||||
},
|
||||
{
|
||||
id: 'canvasListTeamId',
|
||||
title: 'Team ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Encoded team ID (org tokens only)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'list_canvases',
|
||||
},
|
||||
mode: 'advanced',
|
||||
},
|
||||
// Lookup Canvas Sections specific fields
|
||||
{
|
||||
id: 'lookupCanvasId',
|
||||
title: 'Canvas ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter canvas ID (e.g., F1234ABCD)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'lookup_canvas_sections',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: 'sectionCriteria',
|
||||
title: 'Section Criteria',
|
||||
type: 'code',
|
||||
language: 'json',
|
||||
placeholder: '{"section_types":["h1"],"contains_text":"Roadmap"}',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'lookup_canvas_sections',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
// Delete Canvas specific fields
|
||||
{
|
||||
id: 'deleteCanvasId',
|
||||
title: 'Canvas ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter canvas ID (e.g., F1234ABCD)',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: 'delete_canvas',
|
||||
},
|
||||
required: true,
|
||||
},
|
||||
// Create Conversation specific fields
|
||||
{
|
||||
id: 'conversationName',
|
||||
@@ -1058,6 +1196,10 @@ Do not include any explanations, markdown formatting, or other text outside the
|
||||
'slack_get_user_presence',
|
||||
'slack_edit_canvas',
|
||||
'slack_create_channel_canvas',
|
||||
'slack_get_canvas',
|
||||
'slack_list_canvases',
|
||||
'slack_lookup_canvas_sections',
|
||||
'slack_delete_canvas',
|
||||
'slack_create_conversation',
|
||||
'slack_invite_to_conversation',
|
||||
'slack_open_view',
|
||||
@@ -1106,6 +1248,14 @@ 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 'get_canvas':
|
||||
return 'slack_get_canvas'
|
||||
case 'list_canvases':
|
||||
return 'slack_list_canvases'
|
||||
case 'lookup_canvas_sections':
|
||||
return 'slack_lookup_canvas_sections'
|
||||
case 'delete_canvas':
|
||||
return 'slack_delete_canvas'
|
||||
case 'create_conversation':
|
||||
return 'slack_create_conversation'
|
||||
case 'invite_to_conversation':
|
||||
@@ -1164,6 +1314,17 @@ Do not include any explanations, markdown formatting, or other text outside the
|
||||
canvasTitle,
|
||||
channelCanvasTitle,
|
||||
channelCanvasContent,
|
||||
getCanvasId,
|
||||
canvasListChannel,
|
||||
canvasListCount,
|
||||
canvasListPage,
|
||||
canvasListUser,
|
||||
canvasListTsFrom,
|
||||
canvasListTsTo,
|
||||
canvasListTeamId,
|
||||
lookupCanvasId,
|
||||
sectionCriteria,
|
||||
deleteCanvasId,
|
||||
conversationName,
|
||||
isPrivate,
|
||||
teamId,
|
||||
@@ -1343,6 +1504,49 @@ Do not include any explanations, markdown formatting, or other text outside the
|
||||
}
|
||||
break
|
||||
|
||||
case 'get_canvas':
|
||||
baseParams.canvasId = getCanvasId
|
||||
break
|
||||
|
||||
case 'list_canvases':
|
||||
if (canvasListChannel) {
|
||||
baseParams.channel = String(canvasListChannel).trim()
|
||||
}
|
||||
if (canvasListCount) {
|
||||
const parsedCount = Number.parseInt(canvasListCount, 10)
|
||||
if (!Number.isNaN(parsedCount) && parsedCount > 0) {
|
||||
baseParams.count = parsedCount
|
||||
}
|
||||
}
|
||||
if (canvasListPage) {
|
||||
const parsedPage = Number.parseInt(canvasListPage, 10)
|
||||
if (!Number.isNaN(parsedPage) && parsedPage > 0) {
|
||||
baseParams.page = parsedPage
|
||||
}
|
||||
}
|
||||
if (canvasListUser) {
|
||||
baseParams.user = String(canvasListUser).trim()
|
||||
}
|
||||
if (canvasListTsFrom) {
|
||||
baseParams.tsFrom = String(canvasListTsFrom).trim()
|
||||
}
|
||||
if (canvasListTsTo) {
|
||||
baseParams.tsTo = String(canvasListTsTo).trim()
|
||||
}
|
||||
if (canvasListTeamId) {
|
||||
baseParams.teamId = String(canvasListTeamId).trim()
|
||||
}
|
||||
break
|
||||
|
||||
case 'lookup_canvas_sections':
|
||||
baseParams.canvasId = lookupCanvasId
|
||||
baseParams.criteria = sectionCriteria
|
||||
break
|
||||
|
||||
case 'delete_canvas':
|
||||
baseParams.canvasId = deleteCanvasId
|
||||
break
|
||||
|
||||
case 'create_conversation':
|
||||
baseParams.name = conversationName
|
||||
baseParams.isPrivate = isPrivate === 'true'
|
||||
@@ -1461,6 +1665,24 @@ 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' },
|
||||
// Canvas management inputs
|
||||
getCanvasId: { type: 'string', description: 'Canvas ID to retrieve' },
|
||||
canvasListChannel: { type: 'string', description: 'Optional channel filter for canvases' },
|
||||
canvasListCount: { type: 'string', description: 'Maximum number of canvases to return' },
|
||||
canvasListPage: { type: 'string', description: 'Canvas list page number' },
|
||||
canvasListUser: { type: 'string', description: 'Optional canvas creator user filter' },
|
||||
canvasListTsFrom: {
|
||||
type: 'string',
|
||||
description: 'Filter canvases created after this timestamp',
|
||||
},
|
||||
canvasListTsTo: {
|
||||
type: 'string',
|
||||
description: 'Filter canvases created before this timestamp',
|
||||
},
|
||||
canvasListTeamId: { type: 'string', description: 'Encoded team ID for org tokens' },
|
||||
lookupCanvasId: { type: 'string', description: 'Canvas ID to search for sections' },
|
||||
sectionCriteria: { type: 'json', description: 'Canvas section lookup criteria' },
|
||||
deleteCanvasId: { type: 'string', description: 'Canvas ID to delete' },
|
||||
// Create Conversation inputs
|
||||
conversationName: { type: 'string', description: 'Name for the new channel' },
|
||||
isPrivate: { type: 'string', description: 'Create as private channel (true/false)' },
|
||||
@@ -1511,6 +1733,26 @@ Do not include any explanations, markdown formatting, or other text outside the
|
||||
// slack_canvas outputs
|
||||
canvas_id: { type: 'string', description: 'Canvas identifier for created canvases' },
|
||||
title: { type: 'string', description: 'Canvas title' },
|
||||
canvas: {
|
||||
type: 'json',
|
||||
description: 'Canvas file metadata returned by Slack',
|
||||
},
|
||||
canvases: {
|
||||
type: 'json',
|
||||
description: 'Array of canvas file objects returned by Slack',
|
||||
},
|
||||
paging: {
|
||||
type: 'json',
|
||||
description: 'Pagination information for listed canvases',
|
||||
},
|
||||
sections: {
|
||||
type: 'json',
|
||||
description: 'Canvas section IDs returned by Slack section lookup',
|
||||
},
|
||||
ok: {
|
||||
type: 'boolean',
|
||||
description: 'Whether Slack completed the canvas operation successfully',
|
||||
},
|
||||
|
||||
// slack_message_reader outputs (read operation)
|
||||
messages: {
|
||||
|
||||
@@ -704,7 +704,7 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
||||
services: {
|
||||
slack: {
|
||||
name: 'Slack',
|
||||
description: 'Send messages using a bot for Slack.',
|
||||
description: 'Use Slack messaging, files, reactions, views, and canvases.',
|
||||
providerId: 'slack',
|
||||
icon: SlackIcon,
|
||||
baseProviderIcon: SlackIcon,
|
||||
@@ -722,6 +722,7 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
|
||||
// TODO: Add 'users:read.email' once Slack app review is approved
|
||||
'files:write',
|
||||
'files:read',
|
||||
'canvases:read',
|
||||
'canvases:write',
|
||||
'reactions:write',
|
||||
],
|
||||
|
||||
@@ -278,7 +278,8 @@ export const SCOPE_DESCRIPTIONS: Record<string, string> = {
|
||||
'users:read.email': 'View user email addresses',
|
||||
'files:write': 'Upload files',
|
||||
'files:read': 'Download and read files',
|
||||
'canvases:write': 'Create canvas documents',
|
||||
'canvases:read': 'Read canvas sections',
|
||||
'canvases:write': 'Create, edit, and delete canvas documents',
|
||||
'reactions:write': 'Add emoji reactions to messages',
|
||||
|
||||
// Webflow scopes
|
||||
|
||||
@@ -2364,19 +2364,23 @@ import {
|
||||
slackCanvasTool,
|
||||
slackCreateChannelCanvasTool,
|
||||
slackCreateConversationTool,
|
||||
slackDeleteCanvasTool,
|
||||
slackDeleteMessageTool,
|
||||
slackDownloadTool,
|
||||
slackEditCanvasTool,
|
||||
slackEphemeralMessageTool,
|
||||
slackGetCanvasTool,
|
||||
slackGetChannelInfoTool,
|
||||
slackGetMessageTool,
|
||||
slackGetThreadTool,
|
||||
slackGetUserPresenceTool,
|
||||
slackGetUserTool,
|
||||
slackInviteToConversationTool,
|
||||
slackListCanvasesTool,
|
||||
slackListChannelsTool,
|
||||
slackListMembersTool,
|
||||
slackListUsersTool,
|
||||
slackLookupCanvasSectionsTool,
|
||||
slackMessageReaderTool,
|
||||
slackMessageTool,
|
||||
slackOpenViewTool,
|
||||
@@ -3360,6 +3364,10 @@ export const tools: Record<string, ToolConfig> = {
|
||||
slack_publish_view: slackPublishViewTool,
|
||||
slack_edit_canvas: slackEditCanvasTool,
|
||||
slack_create_channel_canvas: slackCreateChannelCanvasTool,
|
||||
slack_get_canvas: slackGetCanvasTool,
|
||||
slack_list_canvases: slackListCanvasesTool,
|
||||
slack_lookup_canvas_sections: slackLookupCanvasSectionsTool,
|
||||
slack_delete_canvas: slackDeleteCanvasTool,
|
||||
slack_create_conversation: slackCreateConversationTool,
|
||||
slack_invite_to_conversation: slackInviteToConversationTool,
|
||||
github_repo_info: githubRepoInfoTool,
|
||||
|
||||
79
apps/sim/tools/slack/delete_canvas.ts
Normal file
79
apps/sim/tools/slack/delete_canvas.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import type { SlackDeleteCanvasParams, SlackDeleteCanvasResponse } from '@/tools/slack/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const slackDeleteCanvasTool: ToolConfig<SlackDeleteCanvasParams, SlackDeleteCanvasResponse> =
|
||||
{
|
||||
id: 'slack_delete_canvas',
|
||||
name: 'Slack Delete Canvas',
|
||||
description: 'Delete a Slack canvas by its canvas ID',
|
||||
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',
|
||||
},
|
||||
canvasId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Canvas ID to delete (e.g., F1234ABCD)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://slack.com/api/canvases.delete',
|
||||
method: 'POST',
|
||||
headers: (params: SlackDeleteCanvasParams) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.accessToken || params.botToken}`,
|
||||
}),
|
||||
body: (params: SlackDeleteCanvasParams) => ({
|
||||
canvas_id: params.canvasId.trim(),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.ok) {
|
||||
if (data.error === 'canvas_not_found') {
|
||||
throw new Error('Canvas not found or not visible to the authenticated Slack user or bot.')
|
||||
}
|
||||
if (data.error === 'canvas_deleting_disabled') {
|
||||
throw new Error('Canvas deletion is disabled for this workspace.')
|
||||
}
|
||||
throw new Error(data.error || 'Failed to delete canvas')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
ok: data.ok,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
ok: { type: 'boolean', description: 'Whether Slack deleted the canvas successfully' },
|
||||
},
|
||||
}
|
||||
85
apps/sim/tools/slack/get_canvas.ts
Normal file
85
apps/sim/tools/slack/get_canvas.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import type { SlackGetCanvasParams, SlackGetCanvasResponse } from '@/tools/slack/types'
|
||||
import { CANVAS_FILE_OUTPUT_PROPERTIES } from '@/tools/slack/types'
|
||||
import { mapCanvasFile } from '@/tools/slack/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const slackGetCanvasTool: ToolConfig<SlackGetCanvasParams, SlackGetCanvasResponse> = {
|
||||
id: 'slack_get_canvas',
|
||||
name: 'Slack Get Canvas Info',
|
||||
description: 'Get Slack canvas file metadata by canvas ID',
|
||||
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',
|
||||
},
|
||||
canvasId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Canvas file ID to retrieve (e.g., F1234ABCD)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: SlackGetCanvasParams) => {
|
||||
const url = new URL('https://slack.com/api/files.info')
|
||||
url.searchParams.append('file', params.canvasId.trim())
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params: SlackGetCanvasParams) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.accessToken || params.botToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.ok) {
|
||||
if (data.error === 'file_not_found') {
|
||||
throw new Error('Canvas not found. Please check the canvas ID and try again.')
|
||||
}
|
||||
if (data.error === 'not_visible') {
|
||||
throw new Error('Canvas is not visible to the authenticated Slack user or bot.')
|
||||
}
|
||||
throw new Error(data.error || 'Failed to get canvas from Slack')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
canvas: mapCanvasFile(data.file),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
canvas: {
|
||||
type: 'object',
|
||||
description: 'Canvas file information returned by Slack',
|
||||
properties: CANVAS_FILE_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -2,19 +2,23 @@ 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 { slackDeleteCanvasTool } from '@/tools/slack/delete_canvas'
|
||||
import { slackDeleteMessageTool } from '@/tools/slack/delete_message'
|
||||
import { slackDownloadTool } from '@/tools/slack/download'
|
||||
import { slackEditCanvasTool } from '@/tools/slack/edit_canvas'
|
||||
import { slackEphemeralMessageTool } from '@/tools/slack/ephemeral_message'
|
||||
import { slackGetCanvasTool } from '@/tools/slack/get_canvas'
|
||||
import { slackGetChannelInfoTool } from '@/tools/slack/get_channel_info'
|
||||
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 { slackListCanvasesTool } from '@/tools/slack/list_canvases'
|
||||
import { slackListChannelsTool } from '@/tools/slack/list_channels'
|
||||
import { slackListMembersTool } from '@/tools/slack/list_members'
|
||||
import { slackListUsersTool } from '@/tools/slack/list_users'
|
||||
import { slackLookupCanvasSectionsTool } from '@/tools/slack/lookup_canvas_sections'
|
||||
import { slackMessageTool } from '@/tools/slack/message'
|
||||
import { slackMessageReaderTool } from '@/tools/slack/message_reader'
|
||||
import { slackOpenViewTool } from '@/tools/slack/open_view'
|
||||
@@ -29,6 +33,10 @@ export {
|
||||
slackCanvasTool,
|
||||
slackCreateConversationTool,
|
||||
slackCreateChannelCanvasTool,
|
||||
slackGetCanvasTool,
|
||||
slackListCanvasesTool,
|
||||
slackLookupCanvasSectionsTool,
|
||||
slackDeleteCanvasTool,
|
||||
slackMessageReaderTool,
|
||||
slackDownloadTool,
|
||||
slackEditCanvasTool,
|
||||
@@ -51,3 +59,5 @@ export {
|
||||
slackGetThreadTool,
|
||||
slackInviteToConversationTool,
|
||||
}
|
||||
|
||||
export * from './types'
|
||||
|
||||
142
apps/sim/tools/slack/list_canvases.ts
Normal file
142
apps/sim/tools/slack/list_canvases.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import type { SlackListCanvasesParams, SlackListCanvasesResponse } from '@/tools/slack/types'
|
||||
import { CANVAS_FILE_OUTPUT_PROPERTIES, CANVAS_PAGING_OUTPUT_PROPERTIES } from '@/tools/slack/types'
|
||||
import { mapCanvasFile } from '@/tools/slack/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const slackListCanvasesTool: ToolConfig<SlackListCanvasesParams, SlackListCanvasesResponse> =
|
||||
{
|
||||
id: 'slack_list_canvases',
|
||||
name: 'Slack List Canvases',
|
||||
description: 'List Slack canvases available to the authenticated user or bot',
|
||||
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: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter canvases appearing in a specific channel ID',
|
||||
},
|
||||
count: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of canvases to return per page',
|
||||
},
|
||||
page: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Page number to return',
|
||||
},
|
||||
user: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter canvases created by a single user ID',
|
||||
},
|
||||
tsFrom: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter canvases created after this Unix timestamp',
|
||||
},
|
||||
tsTo: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter canvases created before this Unix timestamp',
|
||||
},
|
||||
teamId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Encoded team ID, required when using an org-level token',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params: SlackListCanvasesParams) => {
|
||||
const url = new URL('https://slack.com/api/files.list')
|
||||
url.searchParams.append('types', 'canvas')
|
||||
|
||||
if (params.channel) url.searchParams.append('channel', params.channel.trim())
|
||||
if (params.count) url.searchParams.append('count', String(params.count))
|
||||
if (params.page) url.searchParams.append('page', String(params.page))
|
||||
if (params.user) url.searchParams.append('user', params.user.trim())
|
||||
if (params.tsFrom) url.searchParams.append('ts_from', params.tsFrom.trim())
|
||||
if (params.tsTo) url.searchParams.append('ts_to', params.tsTo.trim())
|
||||
if (params.teamId) url.searchParams.append('team_id', params.teamId.trim())
|
||||
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params: SlackListCanvasesParams) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.accessToken || params.botToken}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.ok) {
|
||||
if (data.error === 'unknown_type') {
|
||||
throw new Error('Slack did not recognize the canvas file type filter.')
|
||||
}
|
||||
throw new Error(data.error || 'Failed to list canvases from Slack')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
canvases: (data.files ?? []).map(mapCanvasFile),
|
||||
paging: {
|
||||
count: data.paging?.count ?? 0,
|
||||
total: data.paging?.total ?? 0,
|
||||
page: data.paging?.page ?? 0,
|
||||
pages: data.paging?.pages ?? 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
canvases: {
|
||||
type: 'array',
|
||||
description: 'Canvas file objects returned by Slack',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: CANVAS_FILE_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
paging: {
|
||||
type: 'object',
|
||||
description: 'Pagination information from Slack',
|
||||
properties: CANVAS_PAGING_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
}
|
||||
114
apps/sim/tools/slack/lookup_canvas_sections.ts
Normal file
114
apps/sim/tools/slack/lookup_canvas_sections.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import type {
|
||||
SlackLookupCanvasSectionsParams,
|
||||
SlackLookupCanvasSectionsResponse,
|
||||
} from '@/tools/slack/types'
|
||||
import { CANVAS_SECTION_OUTPUT_PROPERTIES } from '@/tools/slack/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const parseCriteria = (criteria: SlackLookupCanvasSectionsParams['criteria']) => {
|
||||
if (typeof criteria !== 'string') {
|
||||
return criteria
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(criteria)
|
||||
} catch {
|
||||
throw new Error('Canvas section criteria must be a valid JSON object')
|
||||
}
|
||||
}
|
||||
|
||||
export const slackLookupCanvasSectionsTool: ToolConfig<
|
||||
SlackLookupCanvasSectionsParams,
|
||||
SlackLookupCanvasSectionsResponse
|
||||
> = {
|
||||
id: 'slack_lookup_canvas_sections',
|
||||
name: 'Slack Lookup Canvas Sections',
|
||||
description: 'Find Slack canvas section IDs matching criteria for later edits',
|
||||
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',
|
||||
},
|
||||
canvasId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Canvas ID to search (e.g., F1234ABCD)',
|
||||
},
|
||||
criteria: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Section lookup criteria, such as {"section_types":["h1"],"contains_text":"Roadmap"}',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://slack.com/api/canvases.sections.lookup',
|
||||
method: 'POST',
|
||||
headers: (params: SlackLookupCanvasSectionsParams) => ({
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.accessToken || params.botToken}`,
|
||||
}),
|
||||
body: (params: SlackLookupCanvasSectionsParams) => ({
|
||||
canvas_id: params.canvasId.trim(),
|
||||
criteria: parseCriteria(params.criteria),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!data.ok) {
|
||||
if (data.error === 'canvas_not_found') {
|
||||
throw new Error('Canvas not found or not visible to the authenticated Slack user or bot.')
|
||||
}
|
||||
if (data.error === 'missing_scope') {
|
||||
throw new Error(
|
||||
'Missing required permissions. Please reconnect your Slack account with the canvases:read scope.'
|
||||
)
|
||||
}
|
||||
throw new Error(data.error || 'Failed to look up canvas sections')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
sections: data.sections ?? [],
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
sections: {
|
||||
type: 'array',
|
||||
description: 'Canvas sections matching the lookup criteria',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: CANVAS_SECTION_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -488,6 +488,91 @@ export const CANVAS_OUTPUT_PROPERTIES = {
|
||||
title: { type: 'string', description: 'Canvas title' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Canvas file object output properties.
|
||||
* Based on Slack file objects returned by files.info and files.list for canvases.
|
||||
*/
|
||||
export const CANVAS_FILE_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'string', description: 'Unique canvas file identifier' },
|
||||
created: { type: 'number', description: 'Unix timestamp when the canvas was created' },
|
||||
timestamp: { type: 'number', description: 'Unix timestamp associated with the canvas' },
|
||||
name: { type: 'string', description: 'Canvas file name', optional: true },
|
||||
title: { type: 'string', description: 'Canvas title', optional: true },
|
||||
mimetype: { type: 'string', description: 'MIME type of the canvas file', optional: true },
|
||||
filetype: { type: 'string', description: 'Slack file type for the canvas', optional: true },
|
||||
pretty_type: { type: 'string', description: 'Human-readable file type', optional: true },
|
||||
user: { type: 'string', description: 'User ID of the canvas creator', optional: true },
|
||||
editable: { type: 'boolean', description: 'Whether the canvas file is editable', optional: true },
|
||||
size: { type: 'number', description: 'Canvas file size in bytes', optional: true },
|
||||
mode: { type: 'string', description: 'File mode', optional: true },
|
||||
is_external: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the canvas is externally hosted',
|
||||
optional: true,
|
||||
},
|
||||
is_public: { type: 'boolean', description: 'Whether the canvas is public', optional: true },
|
||||
url_private: {
|
||||
type: 'string',
|
||||
description: 'Private URL for the canvas file',
|
||||
optional: true,
|
||||
},
|
||||
url_private_download: {
|
||||
type: 'string',
|
||||
description: 'Private download URL for the canvas file',
|
||||
optional: true,
|
||||
},
|
||||
permalink: { type: 'string', description: 'Permanent URL for the canvas', optional: true },
|
||||
channels: {
|
||||
type: 'array',
|
||||
description: 'Public channel IDs where the canvas appears',
|
||||
items: { type: 'string', description: 'Channel ID' },
|
||||
optional: true,
|
||||
},
|
||||
groups: {
|
||||
type: 'array',
|
||||
description: 'Private channel IDs where the canvas appears',
|
||||
items: { type: 'string', description: 'Channel ID' },
|
||||
optional: true,
|
||||
},
|
||||
ims: {
|
||||
type: 'array',
|
||||
description: 'Direct message IDs where the canvas appears',
|
||||
items: { type: 'string', description: 'Conversation ID' },
|
||||
optional: true,
|
||||
},
|
||||
canvas_readtime: {
|
||||
type: 'number',
|
||||
description: 'Approximate read time for canvas content',
|
||||
optional: true,
|
||||
},
|
||||
is_channel_space: {
|
||||
type: 'boolean',
|
||||
description: 'Whether this canvas is linked to a channel',
|
||||
optional: true,
|
||||
},
|
||||
linked_channel_id: {
|
||||
type: 'string',
|
||||
description: 'Channel ID linked to this canvas',
|
||||
optional: true,
|
||||
},
|
||||
canvas_creator_id: {
|
||||
type: 'string',
|
||||
description: 'User ID of the canvas creator',
|
||||
optional: true,
|
||||
},
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
export const CANVAS_PAGING_OUTPUT_PROPERTIES = {
|
||||
count: { type: 'number', description: 'Number of items requested per page' },
|
||||
total: { type: 'number', description: 'Total number of matching files' },
|
||||
page: { type: 'number', description: 'Current page number' },
|
||||
pages: { type: 'number', description: 'Total number of pages' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
export const CANVAS_SECTION_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'string', description: 'Canvas section identifier' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
/**
|
||||
* Output definition for modal view objects
|
||||
* Based on Slack views.open response structure
|
||||
@@ -735,6 +820,29 @@ export interface SlackCreateChannelCanvasParams extends SlackBaseParams {
|
||||
content?: string
|
||||
}
|
||||
|
||||
export interface SlackGetCanvasParams extends SlackBaseParams {
|
||||
canvasId: string
|
||||
}
|
||||
|
||||
export interface SlackListCanvasesParams extends SlackBaseParams {
|
||||
channel?: string
|
||||
count?: number
|
||||
page?: number
|
||||
user?: string
|
||||
tsFrom?: string
|
||||
tsTo?: string
|
||||
teamId?: string
|
||||
}
|
||||
|
||||
export interface SlackLookupCanvasSectionsParams extends SlackBaseParams {
|
||||
canvasId: string
|
||||
criteria: Record<string, unknown> | string
|
||||
}
|
||||
|
||||
export interface SlackDeleteCanvasParams extends SlackBaseParams {
|
||||
canvasId: string
|
||||
}
|
||||
|
||||
export interface SlackOpenViewParams extends SlackBaseParams {
|
||||
triggerId: string
|
||||
interactivityPointer?: string
|
||||
@@ -1078,6 +1186,69 @@ export interface SlackCreateChannelCanvasResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export interface SlackCanvasFile {
|
||||
id: string
|
||||
created: number | null
|
||||
timestamp: number | null
|
||||
name?: string | null
|
||||
title?: string | null
|
||||
mimetype?: string | null
|
||||
filetype?: string | null
|
||||
pretty_type?: string | null
|
||||
user?: string | null
|
||||
editable?: boolean | null
|
||||
size?: number | null
|
||||
mode?: string | null
|
||||
is_external?: boolean | null
|
||||
is_public?: boolean | null
|
||||
url_private?: string | null
|
||||
url_private_download?: string | null
|
||||
permalink?: string | null
|
||||
channels?: string[]
|
||||
groups?: string[]
|
||||
ims?: string[]
|
||||
canvas_readtime?: number | null
|
||||
is_channel_space?: boolean | null
|
||||
linked_channel_id?: string | null
|
||||
canvas_creator_id?: string | null
|
||||
}
|
||||
|
||||
export interface SlackCanvasPaging {
|
||||
count: number
|
||||
total: number
|
||||
page: number
|
||||
pages: number
|
||||
}
|
||||
|
||||
export interface SlackCanvasSection {
|
||||
id: string
|
||||
}
|
||||
|
||||
export interface SlackGetCanvasResponse extends ToolResponse {
|
||||
output: {
|
||||
canvas: SlackCanvasFile
|
||||
}
|
||||
}
|
||||
|
||||
export interface SlackListCanvasesResponse extends ToolResponse {
|
||||
output: {
|
||||
canvases: SlackCanvasFile[]
|
||||
paging: SlackCanvasPaging
|
||||
}
|
||||
}
|
||||
|
||||
export interface SlackLookupCanvasSectionsResponse extends ToolResponse {
|
||||
output: {
|
||||
sections: SlackCanvasSection[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface SlackDeleteCanvasResponse extends ToolResponse {
|
||||
output: {
|
||||
ok: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface SlackView {
|
||||
id: string
|
||||
team_id?: string | null
|
||||
@@ -1143,6 +1314,10 @@ export type SlackResponse =
|
||||
| SlackGetUserPresenceResponse
|
||||
| SlackEditCanvasResponse
|
||||
| SlackCreateChannelCanvasResponse
|
||||
| SlackGetCanvasResponse
|
||||
| SlackListCanvasesResponse
|
||||
| SlackLookupCanvasSectionsResponse
|
||||
| SlackDeleteCanvasResponse
|
||||
| SlackCreateConversationResponse
|
||||
| SlackInviteToConversationResponse
|
||||
| SlackOpenViewResponse
|
||||
|
||||
28
apps/sim/tools/slack/utils.ts
Normal file
28
apps/sim/tools/slack/utils.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { SlackCanvasFile } from '@/tools/slack/types'
|
||||
|
||||
export const mapCanvasFile = (file: SlackCanvasFile): SlackCanvasFile => ({
|
||||
id: file.id,
|
||||
created: file.created ?? null,
|
||||
timestamp: file.timestamp ?? null,
|
||||
name: file.name ?? null,
|
||||
title: file.title ?? null,
|
||||
mimetype: file.mimetype ?? null,
|
||||
filetype: file.filetype ?? null,
|
||||
pretty_type: file.pretty_type ?? null,
|
||||
user: file.user ?? null,
|
||||
editable: file.editable ?? null,
|
||||
size: file.size ?? null,
|
||||
mode: file.mode ?? null,
|
||||
is_external: file.is_external ?? null,
|
||||
is_public: file.is_public ?? null,
|
||||
url_private: file.url_private ?? null,
|
||||
url_private_download: file.url_private_download ?? null,
|
||||
permalink: file.permalink ?? null,
|
||||
channels: file.channels ?? [],
|
||||
groups: file.groups ?? [],
|
||||
ims: file.ims ?? [],
|
||||
canvas_readtime: file.canvas_readtime ?? null,
|
||||
is_channel_space: file.is_channel_space ?? null,
|
||||
linked_channel_id: file.linked_channel_id ?? null,
|
||||
canvas_creator_id: file.canvas_creator_id ?? null,
|
||||
})
|
||||
Reference in New Issue
Block a user