From be8f44267e321e5e7cc390c7960289c7e0a4b7ba Mon Sep 17 00:00:00 2001 From: Ivan Ma <43893062+ivanma9@users.noreply.github.com> Date: Tue, 1 Apr 2025 17:02:15 -0700 Subject: [PATCH] feat(twilio-block): added twilio block/tools (#201) * Add twilio block base * Edit fields * Add twilio tool * Fix Serialization of body in request. Add check for form-url-encoded. * Remove Test file * Remove execute in types --- sim/blocks/blocks/twilio.ts | 75 +++++++++++++++++++++++ sim/blocks/index.ts | 3 + sim/components/icons.tsx | 20 +++++++ sim/tools/index.ts | 2 + sim/tools/twilio/index.ts | 3 + sim/tools/twilio/sendSMS.ts | 115 ++++++++++++++++++++++++++++++++++++ sim/tools/twilio/types.ts | 18 ++++++ sim/tools/utils.ts | 6 +- 8 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 sim/blocks/blocks/twilio.ts create mode 100644 sim/tools/twilio/index.ts create mode 100644 sim/tools/twilio/sendSMS.ts create mode 100644 sim/tools/twilio/types.ts diff --git a/sim/blocks/blocks/twilio.ts b/sim/blocks/blocks/twilio.ts new file mode 100644 index 0000000000..09c9ce66da --- /dev/null +++ b/sim/blocks/blocks/twilio.ts @@ -0,0 +1,75 @@ +import { TwilioIcon } from '@/components/icons' +import { BlockCategory, BlockConfig, BlockIcon } from '../types' +import { TwilioSMSBlockOutput } from '@/tools/twilio/types' + +export const TwilioSMSBlock: BlockConfig = { + type: 'twilio_sms', + name: 'Twilio SMS', + description: 'Send SMS messages via Twilio', + longDescription: + 'Send text messages to single or multiple recipients using the Twilio API.', + category: 'tools', + bgColor: '#F22F46', // Twilio brand color + icon: TwilioIcon, + subBlocks: [ + { + id: 'phoneNumbers', + title: 'Recipient Phone Numbers', + type: 'long-input', + layout: 'full', + placeholder: 'Enter phone numbers with country code (one per line, e.g., +1234567890)', + }, + { + id: 'message', + title: 'Message', + type: 'long-input', + layout: 'full', + placeholder: 'e.g. "Hello! This is a test message."', + }, + { + id: 'accountSid', + title: 'Twilio Account SID', + type: 'short-input', + layout: 'full', + placeholder: 'Your Twilio Account SID', + }, + { + id: 'authToken', + title: 'Auth Token', + type: 'short-input', + layout: 'full', + placeholder: 'Your Twilio Auth Token', + password: true, + }, + { + id: 'fromNumber', + title: 'From Twilio Phone Number', + type: 'short-input', + layout: 'full', + placeholder: 'e.g. +1234567890', + } + ], + tools: { + access: ['twilio_send_sms'], + config: { + tool: () => 'twilio_send_sms' + }, + }, + inputs: { + phoneNumbers: { type: 'string', required: true }, + message: { type: 'string', required: true }, + accountSid: { type: 'string', required: true }, + authToken: { type: 'string', required: true }, + fromNumber: { type: 'string', required: true } + }, + outputs: { + response: { + type: { + success: 'boolean', + messageId: 'string', + status: 'string', + error: 'string' + }, + }, + }, +} diff --git a/sim/blocks/index.ts b/sim/blocks/index.ts index dfb9920690..ddfd03b3ae 100644 --- a/sim/blocks/index.ts +++ b/sim/blocks/index.ts @@ -35,6 +35,7 @@ import { XBlock } from './blocks/x' import { YouTubeBlock } from './blocks/youtube' import { AirtableBlock } from './blocks/airtable' import { BlockConfig } from './types' +import { TwilioSMSBlock } from './blocks/twilio' // Export blocks for ease of use export { @@ -71,6 +72,7 @@ export { GoogleSheetsBlock, PerplexityBlock, ConfluenceBlock, + TwilioSMSBlock, ImageGeneratorBlock, TypeformBlock, } @@ -107,6 +109,7 @@ const blocks: Record = { supabase: SupabaseBlock, tavily: TavilyBlock, translate: TranslateBlock, + twilio_sms: TwilioSMSBlock, typeform: TypeformBlock, vision: VisionBlock, whatsapp: WhatsAppBlock, diff --git a/sim/components/icons.tsx b/sim/components/icons.tsx index 94fb15e2e4..223bf7804c 100644 --- a/sim/components/icons.tsx +++ b/sim/components/icons.tsx @@ -1734,6 +1734,26 @@ export function ConfluenceIcon(props: SVGProps) { ) } +export function TwilioIcon(props: SVGProps) { + return ( + + ) +} + export function ImageIcon(props: SVGProps) { return ( = { confluence_retrieve: confluenceRetrieveTool, confluence_list: confluenceListTool, confluence_update: confluenceUpdateTool, + twilio_send_sms: sendSMSTool, dalle_generate: dalleTool, airtable_read: airtableReadTool, airtable_write: airtableWriteTool, diff --git a/sim/tools/twilio/index.ts b/sim/tools/twilio/index.ts new file mode 100644 index 0000000000..805225163e --- /dev/null +++ b/sim/tools/twilio/index.ts @@ -0,0 +1,3 @@ +import { sendSMSTool } from './sendSMS' + +export const twilioSendSMSTool = sendSMSTool diff --git a/sim/tools/twilio/sendSMS.ts b/sim/tools/twilio/sendSMS.ts new file mode 100644 index 0000000000..4350c9791f --- /dev/null +++ b/sim/tools/twilio/sendSMS.ts @@ -0,0 +1,115 @@ +import { createLogger } from '@/lib/logs/console-logger' +import { ToolConfig } from '../types' +import { TwilioSMSBlockOutput, TwilioSendSMSParams } from './types' + +const logger = createLogger('Twilio Send SMS Tool') + +export const sendSMSTool: ToolConfig = { + id: 'twilio_send_sms', + name: 'Twilio Send SMS', + description: 'Send text messages to single or multiple recipients using the Twilio API.', + version: '1.0.0', + + params: { + phoneNumbers: { + type: 'string', + required: true, + description: 'Phone numbers to send the message to, separated by newlines' + }, + message: { + type: 'string', + required: true, + description: 'Message to send' + }, + accountSid: { + type: 'string', + required: true, + description: 'Twilio Account SID', + requiredForToolCall: true + }, + authToken: { + type: 'string', + required: true, + description: 'Twilio Auth Token', + requiredForToolCall: true + }, + fromNumber: { + type: 'string', + required: true, + description: 'Twilio phone number to send the message from' + } + }, + + request: { + url: (params) => { + if (!params.accountSid) { + throw new Error('Twilio Account SID is required') + } + const url = `https://api.twilio.com/2010-04-01/Accounts/${params.accountSid}/Messages.json`; + return url + }, + method: 'POST', + headers: (params) => { + if (!params.accountSid || !params.authToken) { + throw new Error('Twilio credentials are required') + } + // Use Buffer instead of btoa for Node.js compatibility + const authToken = Buffer.from(`${params.accountSid}:${params.authToken}`).toString('base64') + const headers = { + Authorization: `Basic ${authToken}`, + 'Content-Type': 'application/x-www-form-urlencoded' + } + return headers + }, + body: (params) => { + if (!params.phoneNumbers) { + throw new Error('Phone numbers are required but not provided') + } + if (!params.message) { + throw new Error('Message content is required but not provided') + } + if (!params.fromNumber) { + throw new Error('From number is required but not provided') + } + + // Get first phone number if multiple are provided + const toNumber = params.phoneNumbers.split('\n')[0].trim() + + // Create a URLSearchParams object and convert to string + const formData = new URLSearchParams() + formData.append('To', toNumber) + formData.append('From', params.fromNumber) + formData.append('Body', params.message) + + const formDataString = formData.toString() + return { body: formDataString } + } + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + const errorMessage = data.error?.message || data.message || `Failed to send SMS (HTTP ${response.status})` + logger.error('Twilio API error:', data) + throw new Error(errorMessage) + } + + logger.info('Twilio Response:', data) + logger.info('Twilio Response type:', typeof data) + return { + success: true, + output: { + success: true, + messageId: data.sid, + status: data.status + }, + error: undefined + } + }, + + transformError: (error) => { + logger.error('Twilio tool error:', { error }) + return `SMS sending failed: ${error.message || 'Unknown error occurred'}` + } +} \ No newline at end of file diff --git a/sim/tools/twilio/types.ts b/sim/tools/twilio/types.ts new file mode 100644 index 0000000000..1176c4aa76 --- /dev/null +++ b/sim/tools/twilio/types.ts @@ -0,0 +1,18 @@ +import { ToolResponse } from '../types' + +export interface TwilioSendSMSParams { + phoneNumbers: string + message: string + accountSid: string + authToken: string + fromNumber: string +} + +export interface TwilioSMSBlockOutput extends ToolResponse { + output: { + success: boolean + messageId?: string + status?: string + error?: string + } +} diff --git a/sim/tools/utils.ts b/sim/tools/utils.ts index bdb2efe8e4..fa4ffd19af 100644 --- a/sim/tools/utils.ts +++ b/sim/tools/utils.ts @@ -47,10 +47,10 @@ export function formatRequestParams(tool: ToolConfig, params: Record