fix(slack): respect message limit, remove duplicate canonical representations (#2469)

* fix(slack): respect message limit, remove duplicate canonical representations

* removed comment

* updated docs script

---------

Co-authored-by: aadamgough <adam@sim.ai>
This commit is contained in:
Adam Gough
2025-12-18 20:37:14 -08:00
committed by GitHub
parent a2f14cab54
commit 38be2b76c4
8 changed files with 141 additions and 171 deletions

View File

@@ -120,117 +120,117 @@ import {
type IconComponent = ComponentType<SVGProps<SVGSVGElement>>
export const blockTypeToIconMap: Record<string, IconComponent> = {
zoom: ZoomIcon,
zep: ZepIcon,
zendesk: ZendeskIcon,
youtube: YouTubeIcon,
x: xIcon,
wordpress: WordpressIcon,
wikipedia: WikipediaIcon,
whatsapp: WhatsAppIcon,
webflow: WebflowIcon,
wealthbox: WealthboxIcon,
vision: EyeIcon,
video_generator: VideoIcon,
typeform: TypeformIcon,
twilio_voice: TwilioIcon,
twilio_sms: TwilioIcon,
tts: TTSIcon,
trello: TrelloIcon,
translate: TranslateIcon,
thinking: BrainIcon,
telegram: TelegramIcon,
tavily: TavilyIcon,
supabase: SupabaseIcon,
stt: STTIcon,
stripe: StripeIcon,
stagehand: StagehandIcon,
ssh: SshIcon,
sqs: SQSIcon,
spotify: SpotifyIcon,
smtp: SmtpIcon,
slack: SlackIcon,
shopify: ShopifyIcon,
sharepoint: MicrosoftSharepointIcon,
sftp: SftpIcon,
servicenow: ServiceNowIcon,
serper: SerperIcon,
sentry: SentryIcon,
sendgrid: SendgridIcon,
search: SearchIcon,
salesforce: SalesforceIcon,
s3: S3Icon,
resend: ResendIcon,
reddit: RedditIcon,
rds: RDSIcon,
qdrant: QdrantIcon,
posthog: PosthogIcon,
postgresql: PostgresIcon,
polymarket: PolymarketIcon,
pipedrive: PipedriveIcon,
pinecone: PineconeIcon,
perplexity: PerplexityIcon,
parallel_ai: ParallelIcon,
outlook: OutlookIcon,
openai: OpenAIIcon,
onedrive: MicrosoftOneDriveIcon,
notion: NotionIcon,
neo4j: Neo4jIcon,
mysql: MySQLIcon,
mongodb: MongoDBIcon,
mistral_parse: MistralIcon,
microsoft_teams: MicrosoftTeamsIcon,
microsoft_planner: MicrosoftPlannerIcon,
microsoft_excel: MicrosoftExcelIcon,
memory: BrainIcon,
mem0: Mem0Icon,
mailgun: MailgunIcon,
mailchimp: MailchimpIcon,
linkup: LinkupIcon,
linkedin: LinkedInIcon,
linear: LinearIcon,
knowledge: PackageSearchIcon,
kalshi: KalshiIcon,
jira: JiraIcon,
jina: JinaAIIcon,
intercom: IntercomIcon,
incidentio: IncidentioIcon,
image_generator: ImageIcon,
hunter: HunterIOIcon,
huggingface: HuggingFaceIcon,
hubspot: HubspotIcon,
grafana: GrafanaIcon,
google_vault: GoogleVaultIcon,
google_slides: GoogleSlidesIcon,
google_sheets: GoogleSheetsIcon,
google_groups: GoogleGroupsIcon,
google_forms: GoogleFormsIcon,
google_drive: GoogleDriveIcon,
google_docs: GoogleDocsIcon,
google_calendar: GoogleCalendarIcon,
google_search: GoogleIcon,
gmail: GmailIcon,
gitlab: GitLabIcon,
github: GithubIcon,
firecrawl: FirecrawlIcon,
file: DocumentIcon,
exa: ExaAIIcon,
elevenlabs: ElevenLabsIcon,
elasticsearch: ElasticsearchIcon,
dynamodb: DynamoDBIcon,
duckduckgo: DuckDuckGoIcon,
dropbox: DropboxIcon,
discord: DiscordIcon,
datadog: DatadogIcon,
cursor: CursorIcon,
confluence: ConfluenceIcon,
clay: ClayIcon,
calendly: CalendlyIcon,
browser_use: BrowserUseIcon,
asana: AsanaIcon,
arxiv: ArxivIcon,
apollo: ApolloIcon,
apify: ApifyIcon,
airtable: AirtableIcon,
ahrefs: AhrefsIcon,
airtable: AirtableIcon,
apify: ApifyIcon,
apollo: ApolloIcon,
arxiv: ArxivIcon,
asana: AsanaIcon,
browser_use: BrowserUseIcon,
calendly: CalendlyIcon,
clay: ClayIcon,
confluence: ConfluenceIcon,
cursor: CursorIcon,
datadog: DatadogIcon,
discord: DiscordIcon,
dropbox: DropboxIcon,
duckduckgo: DuckDuckGoIcon,
dynamodb: DynamoDBIcon,
elasticsearch: ElasticsearchIcon,
elevenlabs: ElevenLabsIcon,
exa: ExaAIIcon,
file: DocumentIcon,
firecrawl: FirecrawlIcon,
github: GithubIcon,
gitlab: GitLabIcon,
gmail: GmailIcon,
google_calendar: GoogleCalendarIcon,
google_docs: GoogleDocsIcon,
google_drive: GoogleDriveIcon,
google_forms: GoogleFormsIcon,
google_groups: GoogleGroupsIcon,
google_search: GoogleIcon,
google_sheets: GoogleSheetsIcon,
google_slides: GoogleSlidesIcon,
google_vault: GoogleVaultIcon,
grafana: GrafanaIcon,
hubspot: HubspotIcon,
huggingface: HuggingFaceIcon,
hunter: HunterIOIcon,
image_generator: ImageIcon,
incidentio: IncidentioIcon,
intercom: IntercomIcon,
jina: JinaAIIcon,
jira: JiraIcon,
kalshi: KalshiIcon,
knowledge: PackageSearchIcon,
linear: LinearIcon,
linkedin: LinkedInIcon,
linkup: LinkupIcon,
mailchimp: MailchimpIcon,
mailgun: MailgunIcon,
mem0: Mem0Icon,
memory: BrainIcon,
microsoft_excel: MicrosoftExcelIcon,
microsoft_planner: MicrosoftPlannerIcon,
microsoft_teams: MicrosoftTeamsIcon,
mistral_parse: MistralIcon,
mongodb: MongoDBIcon,
mysql: MySQLIcon,
neo4j: Neo4jIcon,
notion: NotionIcon,
onedrive: MicrosoftOneDriveIcon,
openai: OpenAIIcon,
outlook: OutlookIcon,
parallel_ai: ParallelIcon,
perplexity: PerplexityIcon,
pinecone: PineconeIcon,
pipedrive: PipedriveIcon,
polymarket: PolymarketIcon,
postgresql: PostgresIcon,
posthog: PosthogIcon,
qdrant: QdrantIcon,
rds: RDSIcon,
reddit: RedditIcon,
resend: ResendIcon,
s3: S3Icon,
salesforce: SalesforceIcon,
search: SearchIcon,
sendgrid: SendgridIcon,
sentry: SentryIcon,
serper: SerperIcon,
servicenow: ServiceNowIcon,
sftp: SftpIcon,
sharepoint: MicrosoftSharepointIcon,
shopify: ShopifyIcon,
slack: SlackIcon,
smtp: SmtpIcon,
spotify: SpotifyIcon,
sqs: SQSIcon,
ssh: SshIcon,
stagehand: StagehandIcon,
stripe: StripeIcon,
stt: STTIcon,
supabase: SupabaseIcon,
tavily: TavilyIcon,
telegram: TelegramIcon,
thinking: BrainIcon,
translate: TranslateIcon,
trello: TrelloIcon,
tts: TTSIcon,
twilio_sms: TwilioIcon,
twilio_voice: TwilioIcon,
typeform: TypeformIcon,
video_generator: VideoIcon,
vision: EyeIcon,
wealthbox: WealthboxIcon,
webflow: WebflowIcon,
whatsapp: WhatsAppIcon,
wikipedia: WikipediaIcon,
wordpress: WordpressIcon,
x: xIcon,
youtube: YouTubeIcon,
zendesk: ZendeskIcon,
zep: ZepIcon,
zoom: ZoomIcon,
}

View File

@@ -114,7 +114,7 @@ Read the latest messages from Slack channels. Retrieve conversation history with
| `botToken` | string | No | Bot token for Custom Bot |
| `channel` | string | No | Slack channel to read messages from \(e.g., #general\) |
| `userId` | string | No | User ID for DM conversation \(e.g., U1234567890\) |
| `limit` | number | No | Number of messages to retrieve \(default: 10, max: 100\) |
| `limit` | number | No | Number of messages to retrieve \(default: 10, max: 15\) |
| `oldest` | string | No | Start of time range \(timestamp\) |
| `latest` | string | No | End of time range \(timestamp\) |

View File

@@ -14,7 +14,12 @@ const SlackReadMessagesSchema = z
accessToken: z.string().min(1, 'Access token is required'),
channel: z.string().optional().nullable(),
userId: z.string().optional().nullable(),
limit: z.number().optional().nullable(),
limit: z.coerce
.number()
.min(1, 'Limit must be at least 1')
.max(15, 'Limit cannot exceed 15')
.optional()
.nullable(),
oldest: z.string().optional().nullable(),
latest: z.string().optional().nullable(),
})
@@ -62,8 +67,8 @@ export async function POST(request: NextRequest) {
const url = new URL('https://slack.com/api/conversations.history')
url.searchParams.append('channel', channel!)
const limit = validatedData.limit ? Number(validatedData.limit) : 10
url.searchParams.append('limit', String(Math.min(limit, 15)))
const limit = validatedData.limit ?? 10
url.searchParams.append('limit', String(limit))
if (validatedData.oldest) {
url.searchParams.append('oldest', validatedData.oldest)

View File

@@ -134,7 +134,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
placeholder: 'Enter the bucket ID',
condition: { field: 'operation', value: ['read_bucket', 'update_bucket', 'delete_bucket'] },
dependsOn: ['credential'],
canonicalParamId: 'bucketId',
},
// ETag for update/delete operations

View File

@@ -181,7 +181,6 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
id: 'threadTs',
title: 'Thread Timestamp',
type: 'short-input',
canonicalParamId: 'thread_ts',
placeholder: 'Reply to thread (e.g., 1405894322.002768)',
condition: {
field: 'operation',
@@ -263,7 +262,6 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
id: 'channelLimit',
title: 'Channel Limit',
type: 'short-input',
canonicalParamId: 'limit',
placeholder: '100',
condition: {
field: 'operation',
@@ -275,7 +273,6 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
id: 'memberLimit',
title: 'Member Limit',
type: 'short-input',
canonicalParamId: 'limit',
placeholder: '100',
condition: {
field: 'operation',
@@ -301,7 +298,6 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
id: 'userLimit',
title: 'User Limit',
type: 'short-input',
canonicalParamId: 'limit',
placeholder: '100',
condition: {
field: 'operation',
@@ -358,7 +354,6 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
id: 'updateTimestamp',
title: 'Message Timestamp',
type: 'short-input',
canonicalParamId: 'timestamp',
placeholder: 'Message timestamp (e.g., 1405894322.002768)',
condition: {
field: 'operation',
@@ -382,7 +377,6 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
id: 'deleteTimestamp',
title: 'Message Timestamp',
type: 'short-input',
canonicalParamId: 'timestamp',
placeholder: 'Message timestamp (e.g., 1405894322.002768)',
condition: {
field: 'operation',
@@ -395,7 +389,6 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
id: 'reactionTimestamp',
title: 'Message Timestamp',
type: 'short-input',
canonicalParamId: 'timestamp',
placeholder: 'Message timestamp (e.g., 1405894322.002768)',
condition: {
field: 'operation',
@@ -407,7 +400,6 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
id: 'emojiName',
title: 'Emoji Name',
type: 'short-input',
canonicalParamId: 'name',
placeholder: 'Emoji name without colons (e.g., thumbsup, heart, eyes)',
condition: {
field: 'operation',
@@ -554,47 +546,35 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
baseParams.content = content
break
case 'read':
if (limit) {
const parsedLimit = Number.parseInt(limit, 10)
baseParams.limit = !Number.isNaN(parsedLimit) ? parsedLimit : 10
} else {
baseParams.limit = 10
case 'read': {
const parsedLimit = limit ? Number.parseInt(limit, 10) : 10
if (Number.isNaN(parsedLimit) || parsedLimit < 1 || parsedLimit > 15) {
throw new Error('Message limit must be between 1 and 15')
}
baseParams.limit = parsedLimit
if (oldest) {
baseParams.oldest = oldest
}
break
}
case 'list_channels':
case 'list_channels': {
baseParams.includePrivate = includePrivate !== 'false'
baseParams.excludeArchived = true
if (channelLimit) {
const parsedLimit = Number.parseInt(channelLimit, 10)
baseParams.limit = !Number.isNaN(parsedLimit) ? parsedLimit : 100
} else {
baseParams.limit = 100
}
baseParams.limit = channelLimit ? Number.parseInt(channelLimit, 10) : 100
break
}
case 'list_members':
if (memberLimit) {
const parsedLimit = Number.parseInt(memberLimit, 10)
baseParams.limit = !Number.isNaN(parsedLimit) ? parsedLimit : 100
} else {
baseParams.limit = 100
}
case 'list_members': {
baseParams.limit = memberLimit ? Number.parseInt(memberLimit, 10) : 100
break
}
case 'list_users':
case 'list_users': {
baseParams.includeDeleted = includeDeleted === 'true'
if (userLimit) {
const parsedLimit = Number.parseInt(userLimit, 10)
baseParams.limit = !Number.isNaN(parsedLimit) ? parsedLimit : 100
} else {
baseParams.limit = 100
}
baseParams.limit = userLimit ? Number.parseInt(userLimit, 10) : 100
break
}
case 'get_user':
if (!userId) {

View File

@@ -70,17 +70,6 @@ export const WealthboxBlock: BlockConfig<WealthboxResponse> = {
title: 'Task ID',
type: 'short-input',
placeholder: 'Enter Task ID',
mode: 'basic',
canonicalParamId: 'taskId',
condition: { field: 'operation', value: ['read_task'] },
},
{
id: 'manualTaskId',
title: 'Task ID',
type: 'short-input',
canonicalParamId: 'taskId',
placeholder: 'Enter Task ID',
mode: 'advanced',
condition: { field: 'operation', value: ['read_task'] },
},
{
@@ -167,12 +156,9 @@ export const WealthboxBlock: BlockConfig<WealthboxResponse> = {
}
},
params: (params) => {
const { credential, operation, contactId, manualContactId, taskId, manualTaskId, ...rest } =
params
const { credential, operation, contactId, manualContactId, taskId, ...rest } = params
// Handle both selector and manual inputs
const effectiveContactId = (contactId || manualContactId || '').trim()
const effectiveTaskId = (taskId || manualTaskId || '').trim()
const baseParams = {
...rest,
@@ -225,7 +211,6 @@ export const WealthboxBlock: BlockConfig<WealthboxResponse> = {
contactId: { type: 'string', description: 'Contact identifier' },
manualContactId: { type: 'string', description: 'Manual contact identifier' },
taskId: { type: 'string', description: 'Task identifier' },
manualTaskId: { type: 'string', description: 'Manual task identifier' },
content: { type: 'string', description: 'Content text' },
firstName: { type: 'string', description: 'First name' },
lastName: { type: 'string', description: 'Last name' },

View File

@@ -51,7 +51,7 @@ export const slackMessageReaderTool: ToolConfig<
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of messages to retrieve (default: 10, max: 100)',
description: 'Number of messages to retrieve (default: 10, max: 15)',
},
oldest: {
type: 'string',

View File

@@ -71,7 +71,7 @@ async function generateIconMapping(): Promise<Record<string, string>> {
console.log('Generating icon mapping from block definitions...')
const iconMapping: Record<string, string> = {}
const blockFiles = await glob(`${BLOCKS_PATH}/*.ts`)
const blockFiles = (await glob(`${BLOCKS_PATH}/*.ts`)).sort()
for (const blockFile of blockFiles) {
const fileContent = fs.readFileSync(blockFile, 'utf-8')
@@ -132,6 +132,7 @@ function writeIconMapping(iconMapping: Record<string, string>): void {
// Generate mapping with direct references (no dynamic access for tree shaking)
const mappingEntries = Object.entries(iconMapping)
.sort(([a], [b]) => a.localeCompare(b))
.map(([blockType, iconName]) => ` ${blockType}: ${iconName},`)
.join('\n')
@@ -1165,7 +1166,7 @@ async function generateAllBlockDocs() {
const iconMapping = await generateIconMapping()
writeIconMapping(iconMapping)
const blockFiles = await glob(`${BLOCKS_PATH}/*.ts`)
const blockFiles = (await glob(`${BLOCKS_PATH}/*.ts`)).sort()
for (const blockFile of blockFiles) {
await generateBlockDoc(blockFile)