-
Starting State
-
- {Object.keys(startSnapshot.blocks).length} blocks, {startSnapshot.edges.length}{' '}
- edges
+ {/* New Training Session Tab */}
+
+
+ Current workflow:
+
+ {currentWorkflow.getBlockCount()} blocks
+
+ ·
+
+ {currentWorkflow.getEdgeCount()} edges
+
+
+
+
+
+ setLocalTitle(e.target.value)}
+ className='h-9'
+ />
+
+
+
- )}
- >
- )}
-
-
-
- New Session
-
- Datasets ({datasets.length})
- Send Live State
-
+
+
- {/* New Training Session Tab */}
-
-
-
- Current Workflow State
-
-
- {currentWorkflow.getBlockCount()} blocks, {currentWorkflow.getEdgeCount()} edges
-
-
-
-
-
- setLocalTitle(e.target.value)}
- />
-
-
-
-
-
-
-
- {/* Datasets Tab */}
-
- {datasets.length === 0 ? (
-
- No training datasets yet. Start a new session to create one.
-
- ) : (
- <>
-
-
-
0 && selectedDatasets.size === datasets.length}
- onCheckedChange={toggleSelectAll}
- disabled={datasets.length === 0}
- />
-
- {selectedDatasets.size > 0
- ? `${selectedDatasets.size} of ${datasets.length} selected`
- : `${datasets.length} dataset${datasets.length !== 1 ? 's' : ''} recorded`}
-
-
-
- {selectedDatasets.size > 0 && (
-
- )}
-
-
-
-
+ {/* Datasets Tab */}
+
+ {datasets.length === 0 ? (
+
+ No training datasets yet. Start a new session to create one.
-
-
-
- {datasets.map((dataset, index) => (
-
-
-
toggleDatasetSelection(dataset.id)}
- className='mt-0.5 mr-3'
- />
-
-
-
- {expandedDataset === dataset.id && (
-
-
-
Prompt
-
{dataset.prompt}
-
-
-
-
Statistics
-
-
- Duration:{' '}
- {dataset.metadata?.duration
- ? `${(dataset.metadata.duration / 1000).toFixed(1)}s`
- : 'N/A'}
-
-
- Operations:{' '}
- {dataset.editSequence.length}
-
-
- Final blocks:{' '}
- {dataset.metadata?.blockCount || 0}
-
-
- Final edges:{' '}
- {dataset.metadata?.edgeCount || 0}
-
-
-
-
-
-
Edit Sequence
-
-
- {formatEditSequence(dataset.editSequence).map((desc, i) => (
- - {desc}
- ))}
-
-
-
-
-
-
-
-
-
-
- {viewingDataset === dataset.id && (
-
-
- {JSON.stringify(
- {
- prompt: dataset.prompt,
- editSequence: dataset.editSequence,
- metadata: dataset.metadata,
- },
- null,
- 2
- )}
-
-
- )}
-
- )}
-
- ))}
-
-
- >
- )}
-
-
- {/* Send Live State Tab */}
-
-
-
- Current Workflow State
-
-
- {currentWorkflow.getBlockCount()} blocks, {currentWorkflow.getEdgeCount()} edges
-
-
-
-
-
-
setLiveWorkflowTitle(e.target.value)}
- />
-
- A short title identifying this workflow
-
-
-
-
-
-
+
- {liveWorkflowSent && (
-
-
- Workflow state sent successfully!
+ {/* Send Live State Tab */}
+
+
+ Current workflow:
+
+ {currentWorkflow.getBlockCount()} blocks
+
+ ·
+
+ {currentWorkflow.getEdgeCount()} edges
+
+
+
+
+
+
setLiveWorkflowTitle(e.target.value)}
+ className='h-9'
+ />
+
+ A short title identifying this workflow
- )}
- {liveWorkflowFailed && (
-
-
- Failed to send workflow state. Please try again.
+
- )}
-
-
-
-
+
+
+ {sendingLiveWorkflow ? (
+ <>
+
+ Sending...
+ >
+ ) : liveWorkflowSent ? (
+ <>
+
+ Sent Successfully
+ >
+ ) : liveWorkflowFailed ? (
+ <>
+
+ Failed - Try Again
+ >
+ ) : (
+ <>
+
+ Send Live Workflow State
+ >
+ )}
+
+
+ {liveWorkflowSent && (
+
+
+ Workflow state sent successfully!
+
+
+ )}
+
+ {liveWorkflowFailed && (
+
+
+ Failed to send workflow state. Please try again.
+
+
+ )}
+
+
+
+
+
)
}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx
index 19cc2f815..de70d6b67 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx
@@ -24,13 +24,13 @@ import {
Panel,
SubflowNodeComponent,
Terminal,
- TrainingControls,
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components'
import { Chat } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/chat/chat'
import { Cursors } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/cursors/cursors'
import { ErrorBoundary } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/error/index'
import { NoteBlock } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/note-block/note-block'
import { OAuthRequiredModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal'
+import { TrainingModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/training-controls/training-modal'
import { WorkflowBlock } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block'
import { WorkflowEdge } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-edge/workflow-edge'
import {
@@ -45,6 +45,7 @@ import { useWorkspaceEnvironment } from '@/hooks/queries/environment'
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
import { useStreamCleanup } from '@/hooks/use-stream-cleanup'
import { useWorkspacePermissions } from '@/hooks/use-workspace-permissions'
+import { useCopilotTrainingStore } from '@/stores/copilot-training/store'
import { useExecutionStore } from '@/stores/execution/store'
import { useNotificationStore } from '@/stores/notifications/store'
import { useCopilotStore } from '@/stores/panel/copilot/store'
@@ -134,6 +135,9 @@ const WorkflowContent = React.memo(() => {
// Get copilot cleanup function
const copilotCleanup = useCopilotStore((state) => state.cleanup)
+ // Training modal state
+ const showTrainingModal = useCopilotTrainingStore((state) => state.showModal)
+
// Handle copilot stream cleanup on page unload and component unmount
useStreamCleanup(copilotCleanup)
@@ -2244,8 +2248,8 @@ const WorkflowContent = React.memo(() => {
return (
- {/* Training Controls - for recording workflow edits */}
-
+ {/* Training Modal - for recording workflow edits */}
+ {showTrainingModal && }
{
- const equalIndex = line.indexOf('=')
+ const trimmed = line.trim()
+
+ if (!trimmed || trimmed.startsWith('#')) return null
+
+ const withoutExport = trimmed.replace(/^export\s+/, '')
+
+ const equalIndex = withoutExport.indexOf('=')
if (equalIndex === -1 || equalIndex === 0) return null
- const potentialKey = line.substring(0, equalIndex).trim()
+ const potentialKey = withoutExport.substring(0, equalIndex).trim()
if (!ENV_VAR_PATTERN.test(potentialKey)) return null
- const value = line.substring(equalIndex + 1).trim()
+ let value = withoutExport.substring(equalIndex + 1)
+
+ const looksLikeBase64Key = /^[A-Za-z0-9+/]+$/.test(potentialKey) && !potentialKey.includes('_')
+ const valueIsJustPadding = /^=+$/.test(value.trim())
+ if (looksLikeBase64Key && valueIsJustPadding && potentialKey.length > 20) {
+ return null
+ }
+
+ const trimmedValue = value.trim()
+ if (
+ !trimmedValue.startsWith('"') &&
+ !trimmedValue.startsWith("'") &&
+ !trimmedValue.startsWith('`')
+ ) {
+ const commentIndex = value.search(/\s#/)
+ if (commentIndex !== -1) {
+ value = value.substring(0, commentIndex)
+ }
+ }
+
+ value = value.trim()
+
+ if (
+ (value.startsWith('"') && value.endsWith('"')) ||
+ (value.startsWith("'") && value.endsWith("'")) ||
+ (value.startsWith('`') && value.endsWith('`'))
+ ) {
+ value = value.slice(1, -1)
+ }
+
return { key: potentialKey, value, id: generateRowId() }
}, [])
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/mcp/mcp.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/mcp/mcp.tsx
index 5f1cf2471..92a9b2c33 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/mcp/mcp.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/mcp/mcp.tsx
@@ -20,6 +20,7 @@ import {
useDeleteMcpServer,
useMcpServers,
useMcpToolsQuery,
+ useRefreshMcpServer,
} from '@/hooks/queries/mcp'
import { useMcpServerTest } from '@/hooks/use-mcp-server-test'
import type { InputFieldType, McpServerFormData, McpServerTestResult } from './components'
@@ -89,27 +90,24 @@ export function MCP() {
} = useMcpToolsQuery(workspaceId)
const createServerMutation = useCreateMcpServer()
const deleteServerMutation = useDeleteMcpServer()
+ const refreshServerMutation = useRefreshMcpServer()
const { testResult, isTestingConnection, testConnection, clearTestResult } = useMcpServerTest()
const urlInputRef = useRef(null)
- // Form state
const [showAddForm, setShowAddForm] = useState(false)
const [formData, setFormData] = useState(DEFAULT_FORM_DATA)
const [isAddingServer, setIsAddingServer] = useState(false)
- // Search and filtering state
const [searchTerm, setSearchTerm] = useState('')
const [deletingServers, setDeletingServers] = useState>(new Set())
- // Delete confirmation dialog state
const [showDeleteDialog, setShowDeleteDialog] = useState(false)
const [serverToDelete, setServerToDelete] = useState<{ id: string; name: string } | null>(null)
- // Server details view state
const [selectedServerId, setSelectedServerId] = useState(null)
+ const [refreshStatus, setRefreshStatus] = useState<'idle' | 'refreshing' | 'refreshed'>('idle')
- // Environment variable dropdown state
const [showEnvVars, setShowEnvVars] = useState(false)
const [envSearchTerm, setEnvSearchTerm] = useState('')
const [cursorPosition, setCursorPosition] = useState(0)
@@ -255,7 +253,6 @@ export function MCP() {
workspaceId,
}
- // Test connection if not already tested
if (!testResult) {
const result = await testConnection(serverConfig)
if (!result.success) return
@@ -396,6 +393,25 @@ export function MCP() {
setSelectedServerId(null)
}, [])
+ /**
+ * Refreshes a server's tools by re-discovering them from the MCP server.
+ */
+ const handleRefreshServer = useCallback(
+ async (serverId: string) => {
+ try {
+ setRefreshStatus('refreshing')
+ await refreshServerMutation.mutateAsync({ workspaceId, serverId })
+ logger.info(`Refreshed MCP server: ${serverId}`)
+ setRefreshStatus('refreshed')
+ setTimeout(() => setRefreshStatus('idle'), 2000)
+ } catch (error) {
+ logger.error('Failed to refresh MCP server:', error)
+ setRefreshStatus('idle')
+ }
+ },
+ [refreshServerMutation, workspaceId]
+ )
+
/**
* Gets the selected server and its tools for the detail view.
*/
@@ -412,12 +428,10 @@ export function MCP() {
const showEmptyState = !hasServers && !showAddForm
const showNoResults = searchTerm.trim() && filteredServers.length === 0 && servers.length > 0
- // Form validation state
const isFormValid = formData.name.trim() && formData.url?.trim()
const isSubmitDisabled = serversLoading || isAddingServer || !isFormValid
const testButtonLabel = getTestButtonLabel(testResult, isTestingConnection)
- // Show detail view if a server is selected
if (selectedServer) {
const { server, tools } = selectedServer
const transportLabel = formatTransportLabel(server.transport || 'http')
@@ -478,7 +492,18 @@ export function MCP() {
-
+
+
handleRefreshServer(server.id)}
+ variant='default'
+ disabled={refreshStatus !== 'idle'}
+ >
+ {refreshStatus === 'refreshing'
+ ? 'Refreshing...'
+ : refreshStatus === 'refreshed'
+ ? 'Refreshed'
+ : 'Refresh Tools'}
+
= {
},
outputs: {
success: { type: 'boolean', description: 'Operation success status' },
- output: { type: 'string', description: 'Operation result (JSON)' },
+ ts: { type: 'string', description: 'Timestamp of the response' },
+ gid: { type: 'string', description: 'Resource globally unique identifier' },
+ name: { type: 'string', description: 'Resource name' },
+ notes: { type: 'string', description: 'Task notes or description' },
+ completed: { type: 'boolean', description: 'Whether the task is completed' },
+ text: { type: 'string', description: 'Comment text content' },
+ assignee: { type: 'json', description: 'Assignee details (gid, name)' },
+ created_by: { type: 'json', description: 'Creator details (gid, name)' },
+ due_on: { type: 'string', description: 'Due date (YYYY-MM-DD)' },
+ created_at: { type: 'string', description: 'Creation timestamp' },
+ modified_at: { type: 'string', description: 'Last modified timestamp' },
+ permalink_url: { type: 'string', description: 'URL to the resource in Asana' },
+ tasks: { type: 'json', description: 'Array of tasks' },
+ projects: { type: 'json', description: 'Array of projects' },
},
}
diff --git a/apps/sim/blocks/blocks/confluence.ts b/apps/sim/blocks/blocks/confluence.ts
index 67c10a042..6823bb617 100644
--- a/apps/sim/blocks/blocks/confluence.ts
+++ b/apps/sim/blocks/blocks/confluence.ts
@@ -29,6 +29,7 @@ export const ConfluenceBlock: BlockConfig = {
{ label: 'List Comments', id: 'list_comments' },
{ label: 'Update Comment', id: 'update_comment' },
{ label: 'Delete Comment', id: 'delete_comment' },
+ { label: 'Upload Attachment', id: 'upload_attachment' },
{ label: 'List Attachments', id: 'list_attachments' },
{ label: 'Delete Attachment', id: 'delete_attachment' },
{ label: 'List Labels', id: 'list_labels' },
@@ -155,6 +156,28 @@ export const ConfluenceBlock: BlockConfig = {
required: true,
condition: { field: 'operation', value: 'delete_attachment' },
},
+ {
+ id: 'attachmentFile',
+ title: 'File',
+ type: 'file-upload',
+ placeholder: 'Select file to upload',
+ required: true,
+ condition: { field: 'operation', value: 'upload_attachment' },
+ },
+ {
+ id: 'attachmentFileName',
+ title: 'File Name',
+ type: 'short-input',
+ placeholder: 'Optional custom file name',
+ condition: { field: 'operation', value: 'upload_attachment' },
+ },
+ {
+ id: 'attachmentComment',
+ title: 'Comment',
+ type: 'short-input',
+ placeholder: 'Optional comment for the attachment',
+ condition: { field: 'operation', value: 'upload_attachment' },
+ },
{
id: 'labelName',
title: 'Label Name',
@@ -185,6 +208,7 @@ export const ConfluenceBlock: BlockConfig = {
'confluence_list_comments',
'confluence_update_comment',
'confluence_delete_comment',
+ 'confluence_upload_attachment',
'confluence_list_attachments',
'confluence_delete_attachment',
'confluence_list_labels',
@@ -212,6 +236,8 @@ export const ConfluenceBlock: BlockConfig = {
return 'confluence_update_comment'
case 'delete_comment':
return 'confluence_delete_comment'
+ case 'upload_attachment':
+ return 'confluence_upload_attachment'
case 'list_attachments':
return 'confluence_list_attachments'
case 'delete_attachment':
@@ -227,11 +253,19 @@ export const ConfluenceBlock: BlockConfig = {
}
},
params: (params) => {
- const { credential, pageId, manualPageId, operation, ...rest } = params
+ const {
+ credential,
+ pageId,
+ manualPageId,
+ operation,
+ attachmentFile,
+ attachmentFileName,
+ attachmentComment,
+ ...rest
+ } = params
const effectivePageId = (pageId || manualPageId || '').trim()
- // Operations that require pageId
const requiresPageId = [
'read',
'update',
@@ -240,9 +274,9 @@ export const ConfluenceBlock: BlockConfig = {
'list_comments',
'list_attachments',
'list_labels',
+ 'upload_attachment',
]
- // Operations that require spaceId
const requiresSpaceId = ['create', 'get_space']
if (requiresPageId.includes(operation) && !effectivePageId) {
@@ -253,6 +287,18 @@ export const ConfluenceBlock: BlockConfig = {
throw new Error('Space ID is required for this operation.')
}
+ if (operation === 'upload_attachment') {
+ return {
+ credential,
+ pageId: effectivePageId,
+ operation,
+ file: attachmentFile,
+ fileName: attachmentFileName,
+ comment: attachmentComment,
+ ...rest,
+ }
+ }
+
return {
credential,
pageId: effectivePageId || undefined,
@@ -276,6 +322,9 @@ export const ConfluenceBlock: BlockConfig = {
comment: { type: 'string', description: 'Comment text' },
commentId: { type: 'string', description: 'Comment identifier' },
attachmentId: { type: 'string', description: 'Attachment identifier' },
+ attachmentFile: { type: 'json', description: 'File to upload as attachment' },
+ attachmentFileName: { type: 'string', description: 'Custom file name for attachment' },
+ attachmentComment: { type: 'string', description: 'Comment for the attachment' },
labelName: { type: 'string', description: 'Label name' },
limit: { type: 'number', description: 'Maximum number of results' },
},
@@ -297,6 +346,9 @@ export const ConfluenceBlock: BlockConfig = {
spaces: { type: 'array', description: 'List of spaces' },
commentId: { type: 'string', description: 'Comment identifier' },
attachmentId: { type: 'string', description: 'Attachment identifier' },
+ fileSize: { type: 'number', description: 'Attachment file size in bytes' },
+ mediaType: { type: 'string', description: 'Attachment MIME type' },
+ downloadUrl: { type: 'string', description: 'Attachment download URL' },
labelName: { type: 'string', description: 'Label name' },
spaceId: { type: 'string', description: 'Space identifier' },
name: { type: 'string', description: 'Space name' },
diff --git a/apps/sim/blocks/blocks/firecrawl.ts b/apps/sim/blocks/blocks/firecrawl.ts
index dc66c8277..13706951c 100644
--- a/apps/sim/blocks/blocks/firecrawl.ts
+++ b/apps/sim/blocks/blocks/firecrawl.ts
@@ -182,12 +182,20 @@ export const FirecrawlBlock: BlockConfig = {
const result: Record = { apiKey }
- // Handle operation-specific fields
switch (operation) {
case 'scrape':
if (url) result.url = url
if (formats) {
- result.formats = Array.isArray(formats) ? formats : ['markdown']
+ if (Array.isArray(formats)) {
+ result.formats = formats
+ } else if (typeof formats === 'string') {
+ try {
+ const parsed = JSON.parse(formats)
+ result.formats = Array.isArray(parsed) ? parsed : ['markdown']
+ } catch {
+ result.formats = ['markdown']
+ }
+ }
}
if (timeout) result.timeout = Number.parseInt(timeout)
if (waitFor) result.waitFor = Number.parseInt(waitFor)
@@ -214,7 +222,16 @@ export const FirecrawlBlock: BlockConfig = {
case 'extract':
if (urls) {
- result.urls = Array.isArray(urls) ? urls : [urls]
+ if (Array.isArray(urls)) {
+ result.urls = urls
+ } else if (typeof urls === 'string') {
+ try {
+ const parsed = JSON.parse(urls)
+ result.urls = Array.isArray(parsed) ? parsed : [parsed]
+ } catch {
+ result.urls = [urls]
+ }
+ }
}
if (prompt) result.prompt = prompt
break
diff --git a/apps/sim/blocks/blocks/google_groups.ts b/apps/sim/blocks/blocks/google_groups.ts
index 658d66be7..99c3b5672 100644
--- a/apps/sim/blocks/blocks/google_groups.ts
+++ b/apps/sim/blocks/blocks/google_groups.ts
@@ -312,6 +312,12 @@ export const GoogleGroupsBlock: BlockConfig = {
roles: { type: 'string', description: 'Filter by roles for list members' },
},
outputs: {
- output: { type: 'json', description: 'Google Groups API response data' },
+ groups: { type: 'json', description: 'Array of group objects (for list_groups)' },
+ group: { type: 'json', description: 'Single group object (for get/create/update_group)' },
+ members: { type: 'json', description: 'Array of member objects (for list_members)' },
+ member: { type: 'json', description: 'Single member object (for get/add/update_member)' },
+ isMember: { type: 'boolean', description: 'Membership check result (for has_member)' },
+ message: { type: 'string', description: 'Success message (for delete/remove operations)' },
+ nextPageToken: { type: 'string', description: 'Token for fetching next page of results' },
},
}
diff --git a/apps/sim/blocks/blocks/google_vault.ts b/apps/sim/blocks/blocks/google_vault.ts
index 5f3513e05..92806faf9 100644
--- a/apps/sim/blocks/blocks/google_vault.ts
+++ b/apps/sim/blocks/blocks/google_vault.ts
@@ -257,9 +257,16 @@ export const GoogleVaultBlock: BlockConfig = {
description: { type: 'string', description: 'Matter description' },
},
outputs: {
- // Common outputs
- output: { type: 'json', description: 'Vault API response data' },
- // Download export file output
+ matters: { type: 'json', description: 'Array of matter objects (for list_matters)' },
+ exports: { type: 'json', description: 'Array of export objects (for list_matters_export)' },
+ holds: { type: 'json', description: 'Array of hold objects (for list_matters_holds)' },
+ matter: { type: 'json', description: 'Created matter object (for create_matters)' },
+ export: { type: 'json', description: 'Created export object (for create_matters_export)' },
+ hold: { type: 'json', description: 'Created hold object (for create_matters_holds)' },
file: { type: 'json', description: 'Downloaded export file (UserFile) from execution files' },
+ nextPageToken: {
+ type: 'string',
+ description: 'Token for fetching next page of results (for list operations)',
+ },
},
}
diff --git a/apps/sim/blocks/blocks/grafana.ts b/apps/sim/blocks/blocks/grafana.ts
index 9b164169f..5af634fc3 100644
--- a/apps/sim/blocks/blocks/grafana.ts
+++ b/apps/sim/blocks/blocks/grafana.ts
@@ -298,7 +298,8 @@ export const GrafanaBlock: BlockConfig = {
id: 'annotationDashboardUid',
title: 'Dashboard UID',
type: 'short-input',
- placeholder: 'Optional - attach to specific dashboard',
+ placeholder: 'Enter dashboard UID',
+ required: true,
condition: {
field: 'operation',
value: ['grafana_create_annotation', 'grafana_list_annotations'],
diff --git a/apps/sim/blocks/blocks/hubspot.ts b/apps/sim/blocks/blocks/hubspot.ts
index 218284bca..fcd3e6d65 100644
--- a/apps/sim/blocks/blocks/hubspot.ts
+++ b/apps/sim/blocks/blocks/hubspot.ts
@@ -72,21 +72,37 @@ export const HubSpotBlock: BlockConfig = {
id: 'contactId',
title: 'Contact ID or Email',
type: 'short-input',
- placeholder: 'Optional - Leave empty to list all contacts',
- condition: { field: 'operation', value: ['get_contacts', 'update_contact'] },
+ placeholder: 'Leave empty to list all contacts',
+ condition: { field: 'operation', value: 'get_contacts' },
+ },
+ {
+ id: 'contactId',
+ title: 'Contact ID or Email',
+ type: 'short-input',
+ placeholder: 'Numeric ID, or email (requires ID Property below)',
+ condition: { field: 'operation', value: 'update_contact' },
+ required: true,
},
{
id: 'companyId',
title: 'Company ID or Domain',
type: 'short-input',
- placeholder: 'Optional - Leave empty to list all companies',
- condition: { field: 'operation', value: ['get_companies', 'update_company'] },
+ placeholder: 'Leave empty to list all companies',
+ condition: { field: 'operation', value: 'get_companies' },
+ },
+ {
+ id: 'companyId',
+ title: 'Company ID or Domain',
+ type: 'short-input',
+ placeholder: 'Numeric ID, or domain (requires ID Property below)',
+ condition: { field: 'operation', value: 'update_company' },
+ required: true,
},
{
id: 'idProperty',
title: 'ID Property',
type: 'short-input',
- placeholder: 'Optional - e.g., "email" for contacts, "domain" for companies',
+ placeholder: 'Required if using email/domain (e.g., "email" or "domain")',
condition: {
field: 'operation',
value: ['get_contacts', 'update_contact', 'get_companies', 'update_company'],
@@ -822,33 +838,48 @@ Return ONLY the JSON array of property names - no explanations, no markdown, no
credential,
}
- if (propertiesToSet) {
+ const createUpdateOps = [
+ 'create_contact',
+ 'update_contact',
+ 'create_company',
+ 'update_company',
+ ]
+ if (propertiesToSet && createUpdateOps.includes(operation as string)) {
cleanParams.properties = propertiesToSet
}
- if (properties && !searchProperties) {
+ const getListOps = ['get_contacts', 'get_companies', 'get_deals']
+ if (properties && !searchProperties && getListOps.includes(operation as string)) {
cleanParams.properties = properties
}
- if (searchProperties) {
+ const searchOps = ['search_contacts', 'search_companies']
+ if (searchProperties && searchOps.includes(operation as string)) {
cleanParams.properties = searchProperties
}
- if (filterGroups) {
+ if (filterGroups && searchOps.includes(operation as string)) {
cleanParams.filterGroups = filterGroups
}
- if (sorts) {
+ if (sorts && searchOps.includes(operation as string)) {
cleanParams.sorts = sorts
}
- if (associations) {
+ if (associations && ['create_contact', 'create_company'].includes(operation as string)) {
cleanParams.associations = associations
}
- // Add other params
+ const excludeKeys = [
+ 'propertiesToSet',
+ 'properties',
+ 'searchProperties',
+ 'filterGroups',
+ 'sorts',
+ 'associations',
+ ]
Object.entries(rest).forEach(([key, value]) => {
- if (value !== undefined && value !== null && value !== '') {
+ if (value !== undefined && value !== null && value !== '' && !excludeKeys.includes(key)) {
cleanParams[key] = value
}
})
diff --git a/apps/sim/blocks/blocks/pylon.ts b/apps/sim/blocks/blocks/pylon.ts
deleted file mode 100644
index d6418ff3b..000000000
--- a/apps/sim/blocks/blocks/pylon.ts
+++ /dev/null
@@ -1,838 +0,0 @@
-import { PylonIcon } from '@/components/icons'
-import type { BlockConfig } from '@/blocks/types'
-import { AuthMode } from '@/blocks/types'
-
-export const PylonBlock: BlockConfig = {
- type: 'pylon',
- name: 'Pylon',
- description:
- 'Manage customer support issues, accounts, contacts, users, teams, and tags in Pylon',
- longDescription:
- 'Integrate Pylon into the workflow. Manage issues (list, create, get, update, delete, search, snooze, followers, external issues), accounts (list, create, get, update, delete, bulk update, search), contacts (list, create, get, update, delete, search), users (list, get, update, search), teams (list, get, create, update), tags (list, get, create, update, delete), and messages (redact).',
- docsLink: 'https://docs.usepylon.com/pylon-docs/developer/api',
- authMode: AuthMode.ApiKey,
- category: 'tools',
- bgColor: '#E8F4FA',
- icon: PylonIcon,
- subBlocks: [
- {
- id: 'operation',
- title: 'Operation',
- type: 'dropdown',
- options: [
- // Issue operations
- { label: 'List Issues', id: 'list_issues' },
- { label: 'Create Issue', id: 'create_issue' },
- { label: 'Get Issue', id: 'get_issue' },
- { label: 'Update Issue', id: 'update_issue' },
- { label: 'Delete Issue', id: 'delete_issue' },
- { label: 'Search Issues', id: 'search_issues' },
- { label: 'Snooze Issue', id: 'snooze_issue' },
- { label: 'List Issue Followers', id: 'list_issue_followers' },
- { label: 'Manage Issue Followers', id: 'manage_issue_followers' },
- { label: 'Link External Issue', id: 'link_external_issue' },
- // Account operations
- { label: 'List Accounts', id: 'list_accounts' },
- { label: 'Create Account', id: 'create_account' },
- { label: 'Get Account', id: 'get_account' },
- { label: 'Update Account', id: 'update_account' },
- { label: 'Delete Account', id: 'delete_account' },
- { label: 'Bulk Update Accounts', id: 'bulk_update_accounts' },
- { label: 'Search Accounts', id: 'search_accounts' },
- // Contact operations
- { label: 'List Contacts', id: 'list_contacts' },
- { label: 'Create Contact', id: 'create_contact' },
- { label: 'Get Contact', id: 'get_contact' },
- { label: 'Update Contact', id: 'update_contact' },
- { label: 'Delete Contact', id: 'delete_contact' },
- { label: 'Search Contacts', id: 'search_contacts' },
- // User operations
- { label: 'List Users', id: 'list_users' },
- { label: 'Get User', id: 'get_user' },
- { label: 'Update User', id: 'update_user' },
- { label: 'Search Users', id: 'search_users' },
- // Team operations
- { label: 'List Teams', id: 'list_teams' },
- { label: 'Get Team', id: 'get_team' },
- { label: 'Create Team', id: 'create_team' },
- { label: 'Update Team', id: 'update_team' },
- // Tag operations
- { label: 'List Tags', id: 'list_tags' },
- { label: 'Get Tag', id: 'get_tag' },
- { label: 'Create Tag', id: 'create_tag' },
- { label: 'Update Tag', id: 'update_tag' },
- { label: 'Delete Tag', id: 'delete_tag' },
- // Message operations
- { label: 'Redact Message', id: 'redact_message' },
- ],
- value: () => 'list_issues',
- },
- {
- id: 'apiToken',
- title: 'API Token',
- type: 'short-input',
- password: true,
- placeholder: 'Enter your Pylon API token',
- required: true,
- },
- // Issue fields
- {
- id: 'startTime',
- title: 'Start Time',
- type: 'short-input',
- placeholder: 'RFC3339 format (e.g., 2024-01-01T00:00:00Z)',
- required: true,
- condition: {
- field: 'operation',
- value: ['list_issues'],
- },
- },
- {
- id: 'endTime',
- title: 'End Time',
- type: 'short-input',
- placeholder: 'RFC3339 format (e.g., 2024-01-31T23:59:59Z)',
- required: true,
- condition: {
- field: 'operation',
- value: ['list_issues'],
- },
- },
- {
- id: 'issueId',
- title: 'Issue ID',
- type: 'short-input',
- placeholder: 'Issue ID',
- required: true,
- condition: {
- field: 'operation',
- value: [
- 'get_issue',
- 'update_issue',
- 'delete_issue',
- 'snooze_issue',
- 'list_issue_followers',
- 'manage_issue_followers',
- 'link_external_issue',
- 'redact_message',
- ],
- },
- },
- {
- id: 'title',
- title: 'Title',
- type: 'short-input',
- placeholder: 'Issue title',
- required: {
- field: 'operation',
- value: ['create_issue'],
- },
- condition: {
- field: 'operation',
- value: ['create_issue'],
- },
- },
- {
- id: 'bodyHtml',
- title: 'Body HTML',
- type: 'long-input',
- placeholder: 'Issue body in HTML format',
- required: true,
- condition: {
- field: 'operation',
- value: ['create_issue'],
- },
- },
- {
- id: 'accountId',
- title: 'Account ID',
- type: 'short-input',
- placeholder: 'Account ID',
- condition: {
- field: 'operation',
- value: ['create_issue', 'update_issue', 'create_contact', 'update_contact'],
- },
- },
- {
- id: 'assigneeId',
- title: 'Assignee ID',
- type: 'short-input',
- placeholder: 'User ID to assign to',
- condition: {
- field: 'operation',
- value: ['create_issue', 'update_issue'],
- },
- },
- {
- id: 'teamId',
- title: 'Team ID',
- type: 'short-input',
- placeholder: 'Team ID',
- required: {
- field: 'operation',
- value: ['get_team', 'update_team'],
- },
- condition: {
- field: 'operation',
- value: ['create_issue', 'update_issue', 'get_team', 'update_team'],
- },
- },
- {
- id: 'requesterId',
- title: 'Requester ID',
- type: 'short-input',
- placeholder: 'Requester user ID',
- condition: {
- field: 'operation',
- value: ['create_issue', 'update_issue'],
- },
- },
- {
- id: 'requesterEmail',
- title: 'Requester Email',
- type: 'short-input',
- placeholder: 'Requester email address',
- condition: {
- field: 'operation',
- value: ['create_issue'],
- },
- },
- {
- id: 'priority',
- title: 'Priority',
- type: 'short-input',
- placeholder: 'Issue priority',
- condition: {
- field: 'operation',
- value: ['create_issue', 'update_issue'],
- },
- },
- {
- id: 'state',
- title: 'State',
- type: 'short-input',
- placeholder: 'Issue state',
- condition: {
- field: 'operation',
- value: ['update_issue'],
- },
- },
- {
- id: 'tags',
- title: 'Tags',
- type: 'short-input',
- placeholder: 'Comma-separated tag IDs',
- condition: {
- field: 'operation',
- value: [
- 'create_issue',
- 'update_issue',
- 'create_account',
- 'update_account',
- 'bulk_update_accounts',
- ],
- },
- },
- {
- id: 'customFields',
- title: 'Custom Fields',
- type: 'long-input',
- placeholder: 'JSON object with custom fields',
- condition: {
- field: 'operation',
- value: [
- 'create_issue',
- 'update_issue',
- 'create_account',
- 'update_account',
- 'bulk_update_accounts',
- 'create_contact',
- 'update_contact',
- ],
- },
- },
- {
- id: 'attachmentUrls',
- title: 'Attachment URLs',
- type: 'short-input',
- placeholder: 'Comma-separated attachment URLs',
- condition: {
- field: 'operation',
- value: ['create_issue'],
- },
- },
- {
- id: 'customerPortalVisible',
- title: 'Customer Portal Visible',
- type: 'short-input',
- placeholder: 'true or false',
- condition: {
- field: 'operation',
- value: ['update_issue'],
- },
- },
- {
- id: 'snoozeUntil',
- title: 'Snooze Until',
- type: 'short-input',
- placeholder: 'RFC3339 timestamp',
- required: true,
- condition: {
- field: 'operation',
- value: ['snooze_issue'],
- },
- },
- {
- id: 'userIds',
- title: 'User IDs',
- type: 'short-input',
- placeholder: 'Comma-separated user IDs',
- condition: {
- field: 'operation',
- value: ['manage_issue_followers', 'create_team', 'update_team'],
- },
- },
- {
- id: 'contactIds',
- title: 'Contact IDs',
- type: 'short-input',
- placeholder: 'Comma-separated contact IDs',
- condition: {
- field: 'operation',
- value: ['manage_issue_followers'],
- },
- },
- {
- id: 'followerOperation',
- title: 'Follower Operation',
- type: 'dropdown',
- options: [
- { label: 'Add', id: 'add' },
- { label: 'Remove', id: 'remove' },
- ],
- condition: {
- field: 'operation',
- value: ['manage_issue_followers'],
- },
- },
- {
- id: 'externalIssueId',
- title: 'External Issue ID',
- type: 'short-input',
- placeholder: 'External issue identifier',
- required: true,
- condition: {
- field: 'operation',
- value: ['link_external_issue'],
- },
- },
- {
- id: 'source',
- title: 'Source',
- type: 'short-input',
- placeholder: 'Source system (e.g., linear, jira)',
- required: true,
- condition: {
- field: 'operation',
- value: ['link_external_issue'],
- },
- },
- // Account fields
- {
- id: 'name',
- title: 'Name',
- type: 'short-input',
- placeholder: 'Name',
- required: {
- field: 'operation',
- value: ['create_account', 'create_contact'],
- },
- condition: {
- field: 'operation',
- value: [
- 'create_account',
- 'update_account',
- 'create_contact',
- 'update_contact',
- 'create_team',
- 'update_team',
- ],
- },
- },
- {
- id: 'accountIdField',
- title: 'Account ID',
- type: 'short-input',
- placeholder: 'Account ID',
- required: true,
- condition: {
- field: 'operation',
- value: ['get_account', 'update_account', 'delete_account'],
- },
- },
- {
- id: 'accountIds',
- title: 'Account IDs',
- type: 'short-input',
- placeholder: 'Comma-separated account IDs',
- required: true,
- condition: {
- field: 'operation',
- value: ['bulk_update_accounts'],
- },
- },
- {
- id: 'domains',
- title: 'Domains',
- type: 'short-input',
- placeholder: 'Comma-separated domain names',
- condition: {
- field: 'operation',
- value: ['create_account', 'update_account'],
- },
- },
- {
- id: 'primaryDomain',
- title: 'Primary Domain',
- type: 'short-input',
- placeholder: 'Primary domain name',
- condition: {
- field: 'operation',
- value: ['create_account', 'update_account'],
- },
- },
- {
- id: 'channels',
- title: 'Channels',
- type: 'short-input',
- placeholder: 'Channels',
- condition: {
- field: 'operation',
- value: ['create_account', 'update_account'],
- },
- },
- {
- id: 'externalIds',
- title: 'External IDs',
- type: 'short-input',
- placeholder: 'External IDs',
- condition: {
- field: 'operation',
- value: ['create_account', 'update_account'],
- },
- },
- {
- id: 'ownerId',
- title: 'Owner ID',
- type: 'short-input',
- placeholder: 'Owner user ID',
- condition: {
- field: 'operation',
- value: ['create_account', 'update_account', 'bulk_update_accounts'],
- },
- },
- {
- id: 'logoUrl',
- title: 'Logo URL',
- type: 'short-input',
- placeholder: 'Account logo URL',
- condition: {
- field: 'operation',
- value: ['create_account', 'update_account'],
- },
- },
- {
- id: 'subaccountIds',
- title: 'Subaccount IDs',
- type: 'short-input',
- placeholder: 'Comma-separated subaccount IDs',
- condition: {
- field: 'operation',
- value: ['create_account', 'update_account'],
- },
- },
- {
- id: 'tagsApplyMode',
- title: 'Tags Apply Mode',
- type: 'dropdown',
- options: [
- { label: 'Append Only', id: 'append_only' },
- { label: 'Remove Only', id: 'remove_only' },
- { label: 'Replace', id: 'replace' },
- ],
- condition: {
- field: 'operation',
- value: ['bulk_update_accounts'],
- },
- },
- // Contact fields
- {
- id: 'contactId',
- title: 'Contact ID',
- type: 'short-input',
- placeholder: 'Contact ID',
- required: true,
- condition: {
- field: 'operation',
- value: ['get_contact', 'update_contact', 'delete_contact'],
- },
- },
- {
- id: 'email',
- title: 'Email',
- type: 'short-input',
- placeholder: 'Email address',
- condition: {
- field: 'operation',
- value: ['create_contact', 'update_contact'],
- },
- },
- {
- id: 'accountExternalId',
- title: 'Account External ID',
- type: 'short-input',
- placeholder: 'External account ID',
- condition: {
- field: 'operation',
- value: ['create_contact', 'update_contact'],
- },
- },
- {
- id: 'avatarUrl',
- title: 'Avatar URL',
- type: 'short-input',
- placeholder: 'Square PNG/JPG image URL',
- condition: {
- field: 'operation',
- value: ['create_contact', 'update_contact'],
- },
- },
- {
- id: 'portalRole',
- title: 'Portal Role',
- type: 'dropdown',
- options: [
- { label: 'No Access', id: 'no_access' },
- { label: 'Member', id: 'member' },
- { label: 'Admin', id: 'admin' },
- ],
- condition: {
- field: 'operation',
- value: ['create_contact', 'update_contact'],
- },
- },
- // User fields
- {
- id: 'userId',
- title: 'User ID',
- type: 'short-input',
- placeholder: 'User ID',
- required: true,
- condition: {
- field: 'operation',
- value: ['get_user', 'update_user'],
- },
- },
- {
- id: 'roleId',
- title: 'Role ID',
- type: 'short-input',
- placeholder: 'Role ID',
- condition: {
- field: 'operation',
- value: ['update_user'],
- },
- },
- {
- id: 'status',
- title: 'Status',
- type: 'dropdown',
- options: [
- { label: 'Active', id: 'active' },
- { label: 'Away', id: 'away' },
- { label: 'Out of Office', id: 'out_of_office' },
- ],
- condition: {
- field: 'operation',
- value: ['update_user'],
- },
- },
- // Tag fields
- {
- id: 'tagId',
- title: 'Tag ID',
- type: 'short-input',
- placeholder: 'Tag ID',
- required: true,
- condition: {
- field: 'operation',
- value: ['get_tag', 'update_tag', 'delete_tag'],
- },
- },
- {
- id: 'objectType',
- title: 'Object Type',
- type: 'dropdown',
- options: [
- { label: 'Account', id: 'account' },
- { label: 'Issue', id: 'issue' },
- { label: 'Contact', id: 'contact' },
- ],
- required: true,
- condition: {
- field: 'operation',
- value: ['create_tag'],
- },
- },
- {
- id: 'value',
- title: 'Value',
- type: 'short-input',
- placeholder: 'Tag value/name',
- required: {
- field: 'operation',
- value: ['create_tag'],
- },
- condition: {
- field: 'operation',
- value: ['create_tag', 'update_tag'],
- },
- },
- {
- id: 'hexColor',
- title: 'Hex Color',
- type: 'short-input',
- placeholder: 'Hex color code (e.g., #3a89ce)',
- condition: {
- field: 'operation',
- value: ['create_tag', 'update_tag'],
- },
- },
- // Message fields
- {
- id: 'messageId',
- title: 'Message ID',
- type: 'short-input',
- placeholder: 'Message ID',
- required: true,
- condition: {
- field: 'operation',
- value: ['redact_message'],
- },
- },
- // Search and pagination fields
- {
- id: 'filter',
- title: 'Filter',
- type: 'long-input',
- placeholder: 'JSON filter object',
- required: {
- field: 'operation',
- value: ['search_accounts', 'search_contacts', 'search_users'],
- },
- condition: {
- field: 'operation',
- value: ['search_issues', 'search_accounts', 'search_contacts', 'search_users'],
- },
- },
- {
- id: 'limit',
- title: 'Limit',
- type: 'short-input',
- placeholder: 'Results per page (1-1000, default: 100)',
- condition: {
- field: 'operation',
- value: [
- 'list_accounts',
- 'list_contacts',
- 'get_contact',
- 'search_issues',
- 'search_accounts',
- 'search_contacts',
- 'search_users',
- ],
- },
- },
- {
- id: 'cursor',
- title: 'Cursor',
- type: 'short-input',
- placeholder: 'Pagination cursor',
- condition: {
- field: 'operation',
- value: [
- 'list_issues',
- 'list_accounts',
- 'list_contacts',
- 'get_contact',
- 'search_issues',
- 'search_accounts',
- 'search_contacts',
- 'search_users',
- ],
- },
- },
- ],
- tools: {
- access: [
- 'pylon_list_issues',
- 'pylon_create_issue',
- 'pylon_get_issue',
- 'pylon_update_issue',
- 'pylon_delete_issue',
- 'pylon_search_issues',
- 'pylon_snooze_issue',
- 'pylon_list_issue_followers',
- 'pylon_manage_issue_followers',
- 'pylon_link_external_issue',
- 'pylon_list_accounts',
- 'pylon_create_account',
- 'pylon_get_account',
- 'pylon_update_account',
- 'pylon_delete_account',
- 'pylon_bulk_update_accounts',
- 'pylon_search_accounts',
- 'pylon_list_contacts',
- 'pylon_create_contact',
- 'pylon_get_contact',
- 'pylon_update_contact',
- 'pylon_delete_contact',
- 'pylon_search_contacts',
- 'pylon_list_users',
- 'pylon_get_user',
- 'pylon_update_user',
- 'pylon_search_users',
- 'pylon_list_teams',
- 'pylon_get_team',
- 'pylon_create_team',
- 'pylon_update_team',
- 'pylon_list_tags',
- 'pylon_get_tag',
- 'pylon_create_tag',
- 'pylon_update_tag',
- 'pylon_delete_tag',
- 'pylon_redact_message',
- ],
- config: {
- tool: (params) => {
- switch (params.operation) {
- // Issue operations
- case 'list_issues':
- return 'pylon_list_issues'
- case 'create_issue':
- return 'pylon_create_issue'
- case 'get_issue':
- return 'pylon_get_issue'
- case 'update_issue':
- return 'pylon_update_issue'
- case 'delete_issue':
- return 'pylon_delete_issue'
- case 'search_issues':
- return 'pylon_search_issues'
- case 'snooze_issue':
- return 'pylon_snooze_issue'
- case 'list_issue_followers':
- return 'pylon_list_issue_followers'
- case 'manage_issue_followers':
- return 'pylon_manage_issue_followers'
- case 'link_external_issue':
- return 'pylon_link_external_issue'
- // Account operations
- case 'list_accounts':
- return 'pylon_list_accounts'
- case 'create_account':
- return 'pylon_create_account'
- case 'get_account':
- return 'pylon_get_account'
- case 'update_account':
- return 'pylon_update_account'
- case 'delete_account':
- return 'pylon_delete_account'
- case 'bulk_update_accounts':
- return 'pylon_bulk_update_accounts'
- case 'search_accounts':
- return 'pylon_search_accounts'
- // Contact operations
- case 'list_contacts':
- return 'pylon_list_contacts'
- case 'create_contact':
- return 'pylon_create_contact'
- case 'get_contact':
- return 'pylon_get_contact'
- case 'update_contact':
- return 'pylon_update_contact'
- case 'delete_contact':
- return 'pylon_delete_contact'
- case 'search_contacts':
- return 'pylon_search_contacts'
- // User operations
- case 'list_users':
- return 'pylon_list_users'
- case 'get_user':
- return 'pylon_get_user'
- case 'update_user':
- return 'pylon_update_user'
- case 'search_users':
- return 'pylon_search_users'
- // Team operations
- case 'list_teams':
- return 'pylon_list_teams'
- case 'get_team':
- return 'pylon_get_team'
- case 'create_team':
- return 'pylon_create_team'
- case 'update_team':
- return 'pylon_update_team'
- // Tag operations
- case 'list_tags':
- return 'pylon_list_tags'
- case 'get_tag':
- return 'pylon_get_tag'
- case 'create_tag':
- return 'pylon_create_tag'
- case 'update_tag':
- return 'pylon_update_tag'
- case 'delete_tag':
- return 'pylon_delete_tag'
- // Message operations
- case 'redact_message':
- return 'pylon_redact_message'
- default:
- throw new Error(`Unknown operation: ${params.operation}`)
- }
- },
- params: (params) => {
- const { apiToken, operation, ...rest } = params
- const cleanParams: Record = { apiToken }
-
- // Handle parameter mapping
- if (params.accountIdField) {
- cleanParams.accountId = params.accountIdField
- }
- if (params.followerOperation) {
- cleanParams.operation = params.followerOperation
- }
-
- Object.entries(rest).forEach(([key, value]) => {
- if (value !== undefined && value !== null && value !== '') {
- // Skip mapped fields
- if (key === 'accountIdField' || key === 'followerOperation') {
- return
- }
- cleanParams[key] = value
- }
- })
-
- return cleanParams
- },
- },
- },
- inputs: {
- operation: { type: 'string', description: 'Operation to perform' },
- apiToken: { type: 'string', description: 'Pylon API token' },
- },
- outputs: {
- success: { type: 'boolean', description: 'Operation success status' },
- output: { type: 'json', description: 'Operation result data' },
- },
-}
diff --git a/apps/sim/blocks/blocks/spotify.ts b/apps/sim/blocks/blocks/spotify.ts
new file mode 100644
index 000000000..3987dc1b2
--- /dev/null
+++ b/apps/sim/blocks/blocks/spotify.ts
@@ -0,0 +1,1355 @@
+import { SpotifyIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
+import { AuthMode } from '@/blocks/types'
+import type { ToolResponse } from '@/tools/types'
+
+export const SpotifyBlock: BlockConfig = {
+ type: 'spotify',
+ name: 'Spotify',
+ description: 'Search music, manage playlists, control playback, and access your library',
+ authMode: AuthMode.OAuth,
+ longDescription:
+ 'Integrate Spotify into your workflow. Search for tracks, albums, artists, and playlists. Manage playlists, access your library, control playback, browse podcasts and audiobooks.',
+ docsLink: 'https://docs.sim.ai/tools/spotify',
+ category: 'tools',
+ bgColor: '#000000',
+ icon: SpotifyIcon,
+ subBlocks: [
+ {
+ id: 'operation',
+ title: 'Operation',
+ type: 'dropdown',
+ options: [
+ // Search & Discovery
+ { label: 'Search', id: 'spotify_search', group: 'Search & Discovery' },
+ // Tracks
+ { label: 'Get Track', id: 'spotify_get_track', group: 'Tracks' },
+ { label: 'Get Multiple Tracks', id: 'spotify_get_tracks', group: 'Tracks' },
+ // Albums
+ { label: 'Get Album', id: 'spotify_get_album', group: 'Albums' },
+ { label: 'Get Multiple Albums', id: 'spotify_get_albums', group: 'Albums' },
+ { label: 'Get Album Tracks', id: 'spotify_get_album_tracks', group: 'Albums' },
+ { label: 'Get Saved Albums', id: 'spotify_get_saved_albums', group: 'Albums' },
+ { label: 'Save Albums', id: 'spotify_save_albums', group: 'Albums' },
+ { label: 'Remove Saved Albums', id: 'spotify_remove_saved_albums', group: 'Albums' },
+ { label: 'Check Saved Albums', id: 'spotify_check_saved_albums', group: 'Albums' },
+ // Artists
+ { label: 'Get Artist', id: 'spotify_get_artist', group: 'Artists' },
+ { label: 'Get Multiple Artists', id: 'spotify_get_artists', group: 'Artists' },
+ { label: 'Get Artist Albums', id: 'spotify_get_artist_albums', group: 'Artists' },
+ { label: 'Get Artist Top Tracks', id: 'spotify_get_artist_top_tracks', group: 'Artists' },
+ { label: 'Follow Artists', id: 'spotify_follow_artists', group: 'Artists' },
+ { label: 'Unfollow Artists', id: 'spotify_unfollow_artists', group: 'Artists' },
+ { label: 'Get Followed Artists', id: 'spotify_get_followed_artists', group: 'Artists' },
+ { label: 'Check Following', id: 'spotify_check_following', group: 'Artists' },
+ // Shows (Podcasts)
+ { label: 'Get Show', id: 'spotify_get_show', group: 'Podcasts' },
+ { label: 'Get Multiple Shows', id: 'spotify_get_shows', group: 'Podcasts' },
+ { label: 'Get Show Episodes', id: 'spotify_get_show_episodes', group: 'Podcasts' },
+ { label: 'Get Saved Shows', id: 'spotify_get_saved_shows', group: 'Podcasts' },
+ { label: 'Save Shows', id: 'spotify_save_shows', group: 'Podcasts' },
+ { label: 'Remove Saved Shows', id: 'spotify_remove_saved_shows', group: 'Podcasts' },
+ { label: 'Check Saved Shows', id: 'spotify_check_saved_shows', group: 'Podcasts' },
+ // Episodes
+ { label: 'Get Episode', id: 'spotify_get_episode', group: 'Episodes' },
+ { label: 'Get Multiple Episodes', id: 'spotify_get_episodes', group: 'Episodes' },
+ { label: 'Get Saved Episodes', id: 'spotify_get_saved_episodes', group: 'Episodes' },
+ { label: 'Save Episodes', id: 'spotify_save_episodes', group: 'Episodes' },
+ { label: 'Remove Saved Episodes', id: 'spotify_remove_saved_episodes', group: 'Episodes' },
+ { label: 'Check Saved Episodes', id: 'spotify_check_saved_episodes', group: 'Episodes' },
+ // Audiobooks
+ { label: 'Get Audiobook', id: 'spotify_get_audiobook', group: 'Audiobooks' },
+ { label: 'Get Multiple Audiobooks', id: 'spotify_get_audiobooks', group: 'Audiobooks' },
+ {
+ label: 'Get Audiobook Chapters',
+ id: 'spotify_get_audiobook_chapters',
+ group: 'Audiobooks',
+ },
+ { label: 'Get Saved Audiobooks', id: 'spotify_get_saved_audiobooks', group: 'Audiobooks' },
+ { label: 'Save Audiobooks', id: 'spotify_save_audiobooks', group: 'Audiobooks' },
+ {
+ label: 'Remove Saved Audiobooks',
+ id: 'spotify_remove_saved_audiobooks',
+ group: 'Audiobooks',
+ },
+ {
+ label: 'Check Saved Audiobooks',
+ id: 'spotify_check_saved_audiobooks',
+ group: 'Audiobooks',
+ },
+ // Playlists
+ { label: 'Get Playlist', id: 'spotify_get_playlist', group: 'Playlists' },
+ { label: 'Get Playlist Tracks', id: 'spotify_get_playlist_tracks', group: 'Playlists' },
+ { label: 'Get Playlist Cover', id: 'spotify_get_playlist_cover', group: 'Playlists' },
+ { label: 'Get My Playlists', id: 'spotify_get_user_playlists', group: 'Playlists' },
+ { label: 'Create Playlist', id: 'spotify_create_playlist', group: 'Playlists' },
+ { label: 'Update Playlist', id: 'spotify_update_playlist', group: 'Playlists' },
+ { label: 'Add Playlist Cover', id: 'spotify_add_playlist_cover', group: 'Playlists' },
+ {
+ label: 'Add Tracks to Playlist',
+ id: 'spotify_add_tracks_to_playlist',
+ group: 'Playlists',
+ },
+ {
+ label: 'Remove Tracks from Playlist',
+ id: 'spotify_remove_tracks_from_playlist',
+ group: 'Playlists',
+ },
+ {
+ label: 'Reorder Playlist Items',
+ id: 'spotify_reorder_playlist_items',
+ group: 'Playlists',
+ },
+ {
+ label: 'Replace Playlist Items',
+ id: 'spotify_replace_playlist_items',
+ group: 'Playlists',
+ },
+ { label: 'Follow Playlist', id: 'spotify_follow_playlist', group: 'Playlists' },
+ { label: 'Unfollow Playlist', id: 'spotify_unfollow_playlist', group: 'Playlists' },
+ {
+ label: 'Check Playlist Followers',
+ id: 'spotify_check_playlist_followers',
+ group: 'Playlists',
+ },
+ // User Profile & Library
+ { label: 'Get My Profile', id: 'spotify_get_current_user', group: 'User & Library' },
+ { label: 'Get User Profile', id: 'spotify_get_user_profile', group: 'User & Library' },
+ { label: 'Get My Top Tracks', id: 'spotify_get_top_tracks', group: 'User & Library' },
+ { label: 'Get My Top Artists', id: 'spotify_get_top_artists', group: 'User & Library' },
+ { label: 'Get Saved Tracks', id: 'spotify_get_saved_tracks', group: 'User & Library' },
+ { label: 'Save Tracks', id: 'spotify_save_tracks', group: 'User & Library' },
+ {
+ label: 'Remove Saved Tracks',
+ id: 'spotify_remove_saved_tracks',
+ group: 'User & Library',
+ },
+ { label: 'Check Saved Tracks', id: 'spotify_check_saved_tracks', group: 'User & Library' },
+ {
+ label: 'Get Recently Played',
+ id: 'spotify_get_recently_played',
+ group: 'User & Library',
+ },
+ // Browse
+ { label: 'Get New Releases', id: 'spotify_get_new_releases', group: 'Browse' },
+ { label: 'Get Categories', id: 'spotify_get_categories', group: 'Browse' },
+ { label: 'Get Available Markets', id: 'spotify_get_markets', group: 'Browse' },
+ // Player Controls
+ { label: 'Get Playback State', id: 'spotify_get_playback_state', group: 'Player' },
+ { label: 'Get Currently Playing', id: 'spotify_get_currently_playing', group: 'Player' },
+ { label: 'Get Devices', id: 'spotify_get_devices', group: 'Player' },
+ { label: 'Get Queue', id: 'spotify_get_queue', group: 'Player' },
+ { label: 'Play', id: 'spotify_play', group: 'Player' },
+ { label: 'Pause', id: 'spotify_pause', group: 'Player' },
+ { label: 'Skip to Next', id: 'spotify_skip_next', group: 'Player' },
+ { label: 'Skip to Previous', id: 'spotify_skip_previous', group: 'Player' },
+ { label: 'Seek', id: 'spotify_seek', group: 'Player' },
+ { label: 'Add to Queue', id: 'spotify_add_to_queue', group: 'Player' },
+ { label: 'Set Volume', id: 'spotify_set_volume', group: 'Player' },
+ { label: 'Set Repeat', id: 'spotify_set_repeat', group: 'Player' },
+ { label: 'Set Shuffle', id: 'spotify_set_shuffle', group: 'Player' },
+ { label: 'Transfer Playback', id: 'spotify_transfer_playback', group: 'Player' },
+ ],
+ value: () => 'spotify_search',
+ },
+
+ // === SEARCH ===
+ {
+ id: 'query',
+ title: 'Search Query',
+ type: 'short-input',
+ placeholder: 'e.g., "Bohemian Rhapsody", "artist:Queen"',
+ required: true,
+ condition: { field: 'operation', value: 'spotify_search' },
+ },
+ {
+ id: 'type',
+ title: 'Search Type',
+ type: 'dropdown',
+ options: [
+ { label: 'Tracks', id: 'track' },
+ { label: 'Albums', id: 'album' },
+ { label: 'Artists', id: 'artist' },
+ { label: 'Playlists', id: 'playlist' },
+ { label: 'Shows', id: 'show' },
+ { label: 'Episodes', id: 'episode' },
+ { label: 'Audiobooks', id: 'audiobook' },
+ { label: 'All', id: 'track,album,artist,playlist' },
+ ],
+ value: () => 'track',
+ condition: { field: 'operation', value: 'spotify_search' },
+ },
+
+ // === TRACK IDs ===
+ {
+ id: 'trackId',
+ title: 'Track ID',
+ type: 'short-input',
+ placeholder: 'Spotify track ID',
+ required: true,
+ condition: { field: 'operation', value: 'spotify_get_track' },
+ },
+ {
+ id: 'trackIds',
+ title: 'Track IDs',
+ type: 'short-input',
+ placeholder: 'Comma-separated track IDs',
+ required: true,
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_get_tracks',
+ 'spotify_save_tracks',
+ 'spotify_remove_saved_tracks',
+ 'spotify_check_saved_tracks',
+ ],
+ },
+ },
+
+ // === ALBUM ID ===
+ {
+ id: 'albumId',
+ title: 'Album ID',
+ type: 'short-input',
+ placeholder: 'Spotify album ID',
+ required: true,
+ condition: { field: 'operation', value: ['spotify_get_album', 'spotify_get_album_tracks'] },
+ },
+ {
+ id: 'albumIds',
+ title: 'Album IDs',
+ type: 'short-input',
+ placeholder: 'Comma-separated album IDs',
+ required: true,
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_get_albums',
+ 'spotify_save_albums',
+ 'spotify_remove_saved_albums',
+ 'spotify_check_saved_albums',
+ ],
+ },
+ },
+
+ // === ARTIST ID ===
+ {
+ id: 'artistId',
+ title: 'Artist ID',
+ type: 'short-input',
+ placeholder: 'Spotify artist ID',
+ required: true,
+ condition: {
+ field: 'operation',
+ value: ['spotify_get_artist', 'spotify_get_artist_albums', 'spotify_get_artist_top_tracks'],
+ },
+ },
+ {
+ id: 'artistIds',
+ title: 'Artist IDs',
+ type: 'short-input',
+ placeholder: 'Comma-separated artist IDs',
+ required: true,
+ condition: {
+ field: 'operation',
+ value: ['spotify_get_artists', 'spotify_follow_artists', 'spotify_unfollow_artists'],
+ },
+ },
+
+ // === SHOW IDs ===
+ {
+ id: 'showId',
+ title: 'Show ID',
+ type: 'short-input',
+ placeholder: 'Spotify show/podcast ID',
+ required: true,
+ condition: { field: 'operation', value: ['spotify_get_show', 'spotify_get_show_episodes'] },
+ },
+ {
+ id: 'showIds',
+ title: 'Show IDs',
+ type: 'short-input',
+ placeholder: 'Comma-separated show IDs',
+ required: true,
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_get_shows',
+ 'spotify_save_shows',
+ 'spotify_remove_saved_shows',
+ 'spotify_check_saved_shows',
+ ],
+ },
+ },
+
+ // === EPISODE IDs ===
+ {
+ id: 'episodeId',
+ title: 'Episode ID',
+ type: 'short-input',
+ placeholder: 'Spotify episode ID',
+ required: true,
+ condition: { field: 'operation', value: 'spotify_get_episode' },
+ },
+ {
+ id: 'episodeIds',
+ title: 'Episode IDs',
+ type: 'short-input',
+ placeholder: 'Comma-separated episode IDs',
+ required: true,
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_get_episodes',
+ 'spotify_save_episodes',
+ 'spotify_remove_saved_episodes',
+ 'spotify_check_saved_episodes',
+ ],
+ },
+ },
+
+ // === AUDIOBOOK IDs ===
+ {
+ id: 'audiobookId',
+ title: 'Audiobook ID',
+ type: 'short-input',
+ placeholder: 'Spotify audiobook ID',
+ required: true,
+ condition: {
+ field: 'operation',
+ value: ['spotify_get_audiobook', 'spotify_get_audiobook_chapters'],
+ },
+ },
+ {
+ id: 'audiobookIds',
+ title: 'Audiobook IDs',
+ type: 'short-input',
+ placeholder: 'Comma-separated audiobook IDs',
+ required: true,
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_get_audiobooks',
+ 'spotify_save_audiobooks',
+ 'spotify_remove_saved_audiobooks',
+ 'spotify_check_saved_audiobooks',
+ ],
+ },
+ },
+
+ // === CHECK FOLLOWING ===
+ {
+ id: 'followType',
+ title: 'Type',
+ type: 'dropdown',
+ options: [
+ { label: 'Artist', id: 'artist' },
+ { label: 'User', id: 'user' },
+ ],
+ value: () => 'artist',
+ condition: { field: 'operation', value: 'spotify_check_following' },
+ },
+ {
+ id: 'ids',
+ title: 'IDs to Check',
+ type: 'short-input',
+ placeholder: 'Comma-separated artist or user IDs',
+ required: true,
+ condition: { field: 'operation', value: 'spotify_check_following' },
+ },
+
+ // === USER ID ===
+ {
+ id: 'userId',
+ title: 'User ID',
+ type: 'short-input',
+ placeholder: 'Spotify user ID',
+ required: true,
+ condition: { field: 'operation', value: 'spotify_get_user_profile' },
+ },
+
+ // === PLAYLIST OPERATIONS ===
+ {
+ id: 'playlistId',
+ title: 'Playlist ID',
+ type: 'short-input',
+ placeholder: 'Spotify playlist ID',
+ required: true,
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_get_playlist',
+ 'spotify_get_playlist_tracks',
+ 'spotify_get_playlist_cover',
+ 'spotify_update_playlist',
+ 'spotify_add_playlist_cover',
+ 'spotify_add_tracks_to_playlist',
+ 'spotify_remove_tracks_from_playlist',
+ 'spotify_reorder_playlist_items',
+ 'spotify_replace_playlist_items',
+ 'spotify_follow_playlist',
+ 'spotify_unfollow_playlist',
+ 'spotify_check_playlist_followers',
+ ],
+ },
+ },
+ {
+ id: 'name',
+ title: 'Playlist Name',
+ type: 'short-input',
+ placeholder: 'My Awesome Playlist',
+ required: true,
+ condition: { field: 'operation', value: 'spotify_create_playlist' },
+ },
+ {
+ id: 'newName',
+ title: 'New Name',
+ type: 'short-input',
+ placeholder: 'New playlist name (optional)',
+ condition: { field: 'operation', value: 'spotify_update_playlist' },
+ },
+ {
+ id: 'description',
+ title: 'Playlist Description',
+ type: 'long-input',
+ placeholder: 'Optional description for the playlist',
+ condition: {
+ field: 'operation',
+ value: ['spotify_create_playlist', 'spotify_update_playlist'],
+ },
+ },
+ {
+ id: 'public',
+ title: 'Public',
+ type: 'switch',
+ defaultValue: true,
+ condition: {
+ field: 'operation',
+ value: ['spotify_create_playlist', 'spotify_update_playlist', 'spotify_follow_playlist'],
+ },
+ },
+
+ // === CHECK PLAYLIST FOLLOWERS ===
+ {
+ id: 'userIds',
+ title: 'User IDs',
+ type: 'short-input',
+ placeholder: 'Comma-separated user IDs to check (max 5)',
+ required: true,
+ condition: { field: 'operation', value: 'spotify_check_playlist_followers' },
+ },
+
+ // === PLAYLIST COVER ===
+ {
+ id: 'imageBase64',
+ title: 'Image (Base64)',
+ type: 'long-input',
+ placeholder: 'Base64-encoded JPEG image (max 256KB)',
+ required: true,
+ condition: { field: 'operation', value: 'spotify_add_playlist_cover' },
+ },
+
+ // === REORDER PLAYLIST ===
+ {
+ id: 'range_start',
+ title: 'Range Start',
+ type: 'short-input',
+ placeholder: 'Start index of items to move',
+ required: true,
+ condition: { field: 'operation', value: 'spotify_reorder_playlist_items' },
+ },
+ {
+ id: 'insert_before',
+ title: 'Insert Before',
+ type: 'short-input',
+ placeholder: 'Index to insert before',
+ required: true,
+ condition: { field: 'operation', value: 'spotify_reorder_playlist_items' },
+ },
+ {
+ id: 'range_length',
+ title: 'Range Length',
+ type: 'short-input',
+ placeholder: 'Number of items to move (default: 1)',
+ condition: { field: 'operation', value: 'spotify_reorder_playlist_items' },
+ },
+
+ // === ADD/REMOVE/REPLACE TRACKS FROM PLAYLIST ===
+ {
+ id: 'uris',
+ title: 'Track URIs',
+ type: 'short-input',
+ placeholder: 'spotify:track:xxx,spotify:track:yyy',
+ required: true,
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_add_tracks_to_playlist',
+ 'spotify_remove_tracks_from_playlist',
+ 'spotify_replace_playlist_items',
+ ],
+ },
+ },
+
+ // === COUNTRY/LOCALE ===
+ {
+ id: 'country',
+ title: 'Country',
+ type: 'short-input',
+ placeholder: 'ISO country code (e.g., US, GB)',
+ condition: {
+ field: 'operation',
+ value: ['spotify_get_new_releases', 'spotify_get_categories'],
+ },
+ },
+
+ // === TOP ITEMS TIME RANGE ===
+ {
+ id: 'time_range',
+ title: 'Time Range',
+ type: 'dropdown',
+ options: [
+ { label: 'Last 4 Weeks', id: 'short_term' },
+ { label: 'Last 6 Months', id: 'medium_term' },
+ { label: 'All Time', id: 'long_term' },
+ ],
+ value: () => 'medium_term',
+ condition: {
+ field: 'operation',
+ value: ['spotify_get_top_tracks', 'spotify_get_top_artists'],
+ },
+ },
+
+ // === PLAYER CONTROLS ===
+ {
+ id: 'device_id',
+ title: 'Device ID',
+ type: 'short-input',
+ placeholder: 'Optional - uses active device if not specified',
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_play',
+ 'spotify_pause',
+ 'spotify_skip_next',
+ 'spotify_skip_previous',
+ 'spotify_add_to_queue',
+ 'spotify_set_volume',
+ 'spotify_seek',
+ 'spotify_set_repeat',
+ 'spotify_set_shuffle',
+ ],
+ },
+ },
+ {
+ id: 'context_uri',
+ title: 'Context URI',
+ type: 'short-input',
+ placeholder: 'spotify:album:xxx or spotify:playlist:yyy',
+ condition: { field: 'operation', value: 'spotify_play' },
+ },
+ {
+ id: 'playUris',
+ title: 'Track URIs',
+ type: 'short-input',
+ placeholder: 'spotify:track:xxx (comma-separated for multiple)',
+ condition: { field: 'operation', value: 'spotify_play' },
+ },
+ {
+ id: 'uri',
+ title: 'Track URI',
+ type: 'short-input',
+ placeholder: 'spotify:track:xxx',
+ required: true,
+ condition: { field: 'operation', value: 'spotify_add_to_queue' },
+ },
+ {
+ id: 'volume_percent',
+ title: 'Volume',
+ type: 'slider',
+ min: 0,
+ max: 100,
+ step: 1,
+ integer: true,
+ condition: { field: 'operation', value: 'spotify_set_volume' },
+ },
+
+ // === SEEK ===
+ {
+ id: 'position_ms',
+ title: 'Position (ms)',
+ type: 'short-input',
+ placeholder: 'Position in milliseconds',
+ required: true,
+ condition: { field: 'operation', value: 'spotify_seek' },
+ },
+
+ // === REPEAT ===
+ {
+ id: 'state',
+ title: 'Repeat Mode',
+ type: 'dropdown',
+ options: [
+ { label: 'Off', id: 'off' },
+ { label: 'Track', id: 'track' },
+ { label: 'Context (Album/Playlist)', id: 'context' },
+ ],
+ value: () => 'off',
+ condition: { field: 'operation', value: 'spotify_set_repeat' },
+ },
+
+ // === SHUFFLE ===
+ {
+ id: 'shuffle_state',
+ title: 'Shuffle',
+ type: 'switch',
+ defaultValue: false,
+ condition: { field: 'operation', value: 'spotify_set_shuffle' },
+ },
+
+ // === TRANSFER PLAYBACK ===
+ {
+ id: 'target_device_id',
+ title: 'Target Device ID',
+ type: 'short-input',
+ placeholder: 'Device ID to transfer to',
+ required: true,
+ condition: { field: 'operation', value: 'spotify_transfer_playback' },
+ },
+
+ // === COMMON: LIMIT ===
+ {
+ id: 'limit',
+ title: 'Limit',
+ type: 'short-input',
+ placeholder: 'Number of results (1-50, default: 20)',
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_search',
+ 'spotify_get_album_tracks',
+ 'spotify_get_saved_albums',
+ 'spotify_get_artist_albums',
+ 'spotify_get_playlist_tracks',
+ 'spotify_get_user_playlists',
+ 'spotify_get_top_tracks',
+ 'spotify_get_top_artists',
+ 'spotify_get_saved_tracks',
+ 'spotify_get_recently_played',
+ 'spotify_get_new_releases',
+ 'spotify_get_categories',
+ 'spotify_get_followed_artists',
+ 'spotify_get_show_episodes',
+ 'spotify_get_saved_shows',
+ 'spotify_get_saved_episodes',
+ 'spotify_get_audiobook_chapters',
+ 'spotify_get_saved_audiobooks',
+ ],
+ },
+ },
+
+ // === OAUTH CREDENTIAL ===
+ {
+ id: 'credential',
+ title: 'Spotify Account',
+ type: 'oauth-input',
+ serviceId: 'spotify',
+ required: true,
+ },
+ ],
+ tools: {
+ access: [
+ 'spotify_search',
+ 'spotify_get_track',
+ 'spotify_get_tracks',
+ 'spotify_get_album',
+ 'spotify_get_albums',
+ 'spotify_get_album_tracks',
+ 'spotify_get_saved_albums',
+ 'spotify_save_albums',
+ 'spotify_remove_saved_albums',
+ 'spotify_check_saved_albums',
+ 'spotify_get_artist',
+ 'spotify_get_artists',
+ 'spotify_get_artist_albums',
+ 'spotify_get_artist_top_tracks',
+ 'spotify_follow_artists',
+ 'spotify_unfollow_artists',
+ 'spotify_get_followed_artists',
+ 'spotify_check_following',
+ 'spotify_get_show',
+ 'spotify_get_shows',
+ 'spotify_get_show_episodes',
+ 'spotify_get_saved_shows',
+ 'spotify_save_shows',
+ 'spotify_remove_saved_shows',
+ 'spotify_check_saved_shows',
+ 'spotify_get_episode',
+ 'spotify_get_episodes',
+ 'spotify_get_saved_episodes',
+ 'spotify_save_episodes',
+ 'spotify_remove_saved_episodes',
+ 'spotify_check_saved_episodes',
+ 'spotify_get_audiobook',
+ 'spotify_get_audiobooks',
+ 'spotify_get_audiobook_chapters',
+ 'spotify_get_saved_audiobooks',
+ 'spotify_save_audiobooks',
+ 'spotify_remove_saved_audiobooks',
+ 'spotify_check_saved_audiobooks',
+ 'spotify_get_playlist',
+ 'spotify_get_playlist_tracks',
+ 'spotify_get_playlist_cover',
+ 'spotify_get_user_playlists',
+ 'spotify_create_playlist',
+ 'spotify_update_playlist',
+ 'spotify_add_playlist_cover',
+ 'spotify_add_tracks_to_playlist',
+ 'spotify_remove_tracks_from_playlist',
+ 'spotify_reorder_playlist_items',
+ 'spotify_replace_playlist_items',
+ 'spotify_follow_playlist',
+ 'spotify_unfollow_playlist',
+ 'spotify_check_playlist_followers',
+ 'spotify_get_current_user',
+ 'spotify_get_user_profile',
+ 'spotify_get_top_tracks',
+ 'spotify_get_top_artists',
+ 'spotify_get_saved_tracks',
+ 'spotify_save_tracks',
+ 'spotify_remove_saved_tracks',
+ 'spotify_check_saved_tracks',
+ 'spotify_get_recently_played',
+ 'spotify_get_new_releases',
+ 'spotify_get_categories',
+ 'spotify_get_markets',
+ 'spotify_get_playback_state',
+ 'spotify_get_currently_playing',
+ 'spotify_get_devices',
+ 'spotify_get_queue',
+ 'spotify_play',
+ 'spotify_pause',
+ 'spotify_skip_next',
+ 'spotify_skip_previous',
+ 'spotify_seek',
+ 'spotify_add_to_queue',
+ 'spotify_set_volume',
+ 'spotify_set_repeat',
+ 'spotify_set_shuffle',
+ 'spotify_transfer_playback',
+ ],
+ config: {
+ tool: (params) => {
+ // Convert numeric parameters
+ if (params.limit) {
+ params.limit = Number(params.limit)
+ }
+ if (params.volume_percent) {
+ params.volume_percent = Number(params.volume_percent)
+ }
+ if (params.range_start) {
+ params.range_start = Number(params.range_start)
+ }
+ if (params.insert_before) {
+ params.insert_before = Number(params.insert_before)
+ }
+ if (params.range_length) {
+ params.range_length = Number(params.range_length)
+ }
+ if (params.position_ms) {
+ params.position_ms = Number(params.position_ms)
+ }
+ // Map followType to type for check_following
+ if (params.followType) {
+ params.type = params.followType
+ }
+ // Map newName to name for update_playlist
+ if (params.newName) {
+ params.name = params.newName
+ }
+ // Map playUris to uris for play
+ if (params.playUris) {
+ params.uris = params.playUris
+ }
+ return params.operation || 'spotify_search'
+ },
+ },
+ },
+ inputs: {
+ operation: { type: 'string', description: 'Operation to perform' },
+ credential: { type: 'string', description: 'Spotify OAuth credential' },
+ // Search
+ query: { type: 'string', description: 'Search query' },
+ type: { type: 'string', description: 'Search type' },
+ // IDs
+ trackId: { type: 'string', description: 'Spotify track ID' },
+ trackIds: { type: 'string', description: 'Comma-separated track IDs' },
+ albumId: { type: 'string', description: 'Spotify album ID' },
+ albumIds: { type: 'string', description: 'Comma-separated album IDs' },
+ artistId: { type: 'string', description: 'Spotify artist ID' },
+ artistIds: { type: 'string', description: 'Comma-separated artist IDs' },
+ showId: { type: 'string', description: 'Spotify show ID' },
+ showIds: { type: 'string', description: 'Comma-separated show IDs' },
+ episodeId: { type: 'string', description: 'Spotify episode ID' },
+ episodeIds: { type: 'string', description: 'Comma-separated episode IDs' },
+ audiobookId: { type: 'string', description: 'Spotify audiobook ID' },
+ audiobookIds: { type: 'string', description: 'Comma-separated audiobook IDs' },
+ playlistId: { type: 'string', description: 'Spotify playlist ID' },
+ userId: { type: 'string', description: 'Spotify user ID' },
+ userIds: { type: 'string', description: 'Comma-separated user IDs' },
+ ids: { type: 'string', description: 'Comma-separated IDs' },
+ followType: { type: 'string', description: 'Type to check (artist or user)' },
+ // Playlist
+ name: { type: 'string', description: 'Playlist name' },
+ newName: { type: 'string', description: 'New playlist name' },
+ description: { type: 'string', description: 'Playlist description' },
+ public: { type: 'boolean', description: 'Whether playlist is public' },
+ imageBase64: { type: 'string', description: 'Base64-encoded JPEG image' },
+ range_start: { type: 'number', description: 'Start index for reorder' },
+ insert_before: { type: 'number', description: 'Insert before index' },
+ range_length: { type: 'number', description: 'Number of items to move' },
+ // Track URIs
+ uris: { type: 'string', description: 'Comma-separated Spotify URIs' },
+ playUris: { type: 'string', description: 'Track URIs to play' },
+ uri: { type: 'string', description: 'Spotify URI' },
+ // Time range
+ time_range: { type: 'string', description: 'Time range for top items' },
+ // Browse
+ country: { type: 'string', description: 'ISO country code' },
+ // Player
+ device_id: { type: 'string', description: 'Device ID for playback' },
+ context_uri: { type: 'string', description: 'Context URI (album, playlist, artist)' },
+ volume_percent: { type: 'number', description: 'Volume level (0-100)' },
+ position_ms: { type: 'number', description: 'Position in milliseconds' },
+ state: { type: 'string', description: 'Repeat mode (off, track, context)' },
+ shuffle_state: { type: 'boolean', description: 'Shuffle on/off' },
+ target_device_id: { type: 'string', description: 'Target device ID for transfer' },
+ // Common
+ limit: { type: 'number', description: 'Maximum number of results' },
+ },
+ outputs: {
+ // === SEARCH OUTPUTS ===
+ tracks: {
+ type: 'json',
+ description: 'List of tracks',
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_search',
+ 'spotify_get_tracks',
+ 'spotify_get_album_tracks',
+ 'spotify_get_playlist_tracks',
+ 'spotify_get_artist_top_tracks',
+ 'spotify_get_saved_tracks',
+ 'spotify_get_top_tracks',
+ ],
+ },
+ },
+ artists: {
+ type: 'json',
+ description: 'List of artists',
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_search',
+ 'spotify_get_artists',
+ 'spotify_get_top_artists',
+ 'spotify_get_followed_artists',
+ ],
+ },
+ },
+ albums: {
+ type: 'json',
+ description: 'List of albums',
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_search',
+ 'spotify_get_albums',
+ 'spotify_get_artist_albums',
+ 'spotify_get_saved_albums',
+ 'spotify_get_new_releases',
+ ],
+ },
+ },
+ playlists: {
+ type: 'json',
+ description: 'List of playlists',
+ condition: { field: 'operation', value: ['spotify_search', 'spotify_get_user_playlists'] },
+ },
+
+ // === SINGLE ITEM OUTPUTS ===
+ id: {
+ type: 'string',
+ description: 'Spotify ID',
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_get_track',
+ 'spotify_get_album',
+ 'spotify_get_artist',
+ 'spotify_get_playlist',
+ 'spotify_get_show',
+ 'spotify_get_episode',
+ 'spotify_get_audiobook',
+ 'spotify_get_current_user',
+ 'spotify_get_user_profile',
+ 'spotify_create_playlist',
+ ],
+ },
+ },
+ name: {
+ type: 'string',
+ description: 'Name',
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_get_track',
+ 'spotify_get_album',
+ 'spotify_get_artist',
+ 'spotify_get_playlist',
+ 'spotify_get_show',
+ 'spotify_get_episode',
+ 'spotify_get_audiobook',
+ 'spotify_create_playlist',
+ ],
+ },
+ },
+ uri: {
+ type: 'string',
+ description: 'Spotify URI',
+ condition: { field: 'operation', value: 'spotify_get_track' },
+ },
+ external_url: {
+ type: 'string',
+ description: 'Spotify URL',
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_get_track',
+ 'spotify_get_album',
+ 'spotify_get_artist',
+ 'spotify_get_playlist',
+ 'spotify_get_show',
+ 'spotify_get_episode',
+ 'spotify_get_audiobook',
+ 'spotify_get_current_user',
+ 'spotify_get_user_profile',
+ 'spotify_create_playlist',
+ ],
+ },
+ },
+ image_url: {
+ type: 'string',
+ description: 'Cover/profile image URL',
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_get_track',
+ 'spotify_get_album',
+ 'spotify_get_artist',
+ 'spotify_get_playlist',
+ 'spotify_get_show',
+ 'spotify_get_episode',
+ 'spotify_get_audiobook',
+ 'spotify_get_current_user',
+ 'spotify_get_user_profile',
+ 'spotify_get_playlist_cover',
+ ],
+ },
+ },
+ popularity: {
+ type: 'number',
+ description: 'Popularity score (0-100)',
+ condition: {
+ field: 'operation',
+ value: ['spotify_get_track', 'spotify_get_album', 'spotify_get_artist'],
+ },
+ },
+
+ // === TRACK OUTPUTS ===
+ album: {
+ type: 'json',
+ description: 'Album information',
+ condition: { field: 'operation', value: 'spotify_get_track' },
+ },
+ duration_ms: {
+ type: 'number',
+ description: 'Duration in milliseconds',
+ condition: { field: 'operation', value: ['spotify_get_track', 'spotify_get_episode'] },
+ },
+ explicit: {
+ type: 'boolean',
+ description: 'Contains explicit content',
+ condition: {
+ field: 'operation',
+ value: ['spotify_get_track', 'spotify_get_show', 'spotify_get_episode'],
+ },
+ },
+ preview_url: {
+ type: 'string',
+ description: 'URL to 30-second preview',
+ condition: { field: 'operation', value: 'spotify_get_track' },
+ },
+
+ // === ALBUM OUTPUTS ===
+ album_type: {
+ type: 'string',
+ description: 'Album type (album, single, compilation)',
+ condition: { field: 'operation', value: 'spotify_get_album' },
+ },
+ release_date: {
+ type: 'string',
+ description: 'Release date',
+ condition: { field: 'operation', value: ['spotify_get_album', 'spotify_get_episode'] },
+ },
+ label: {
+ type: 'string',
+ description: 'Record label',
+ condition: { field: 'operation', value: 'spotify_get_album' },
+ },
+ total_tracks: {
+ type: 'number',
+ description: 'Total tracks',
+ condition: { field: 'operation', value: ['spotify_get_album', 'spotify_get_playlist'] },
+ },
+ genres: {
+ type: 'json',
+ description: 'List of genres',
+ condition: { field: 'operation', value: ['spotify_get_album', 'spotify_get_artist'] },
+ },
+
+ // === ARTIST OUTPUTS ===
+ followers: {
+ type: 'number',
+ description: 'Number of followers',
+ condition: {
+ field: 'operation',
+ value: ['spotify_get_artist', 'spotify_get_current_user', 'spotify_get_user_profile'],
+ },
+ },
+
+ // === PLAYLIST OUTPUTS ===
+ description: {
+ type: 'string',
+ description: 'Description',
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_get_playlist',
+ 'spotify_get_show',
+ 'spotify_get_episode',
+ 'spotify_get_audiobook',
+ 'spotify_create_playlist',
+ ],
+ },
+ },
+ owner: {
+ type: 'json',
+ description: 'Playlist owner information',
+ condition: { field: 'operation', value: ['spotify_get_playlist', 'spotify_create_playlist'] },
+ },
+ public: {
+ type: 'boolean',
+ description: 'Whether playlist is public',
+ condition: { field: 'operation', value: ['spotify_get_playlist', 'spotify_create_playlist'] },
+ },
+ collaborative: {
+ type: 'boolean',
+ description: 'Whether playlist is collaborative',
+ condition: { field: 'operation', value: ['spotify_get_playlist', 'spotify_create_playlist'] },
+ },
+ snapshot_id: {
+ type: 'string',
+ description: 'Playlist version snapshot ID',
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_get_playlist',
+ 'spotify_create_playlist',
+ 'spotify_add_tracks_to_playlist',
+ 'spotify_remove_tracks_from_playlist',
+ 'spotify_reorder_playlist_items',
+ 'spotify_replace_playlist_items',
+ ],
+ },
+ },
+
+ // === SHOW/PODCAST OUTPUTS ===
+ publisher: {
+ type: 'string',
+ description: 'Publisher name',
+ condition: { field: 'operation', value: ['spotify_get_show', 'spotify_get_audiobook'] },
+ },
+ total_episodes: {
+ type: 'number',
+ description: 'Total episodes in show',
+ condition: { field: 'operation', value: 'spotify_get_show' },
+ },
+ shows: {
+ type: 'json',
+ description: 'List of shows/podcasts',
+ condition: { field: 'operation', value: ['spotify_get_shows', 'spotify_get_saved_shows'] },
+ },
+ languages: {
+ type: 'json',
+ description: 'List of languages',
+ condition: { field: 'operation', value: ['spotify_get_show', 'spotify_get_audiobook'] },
+ },
+
+ // === EPISODE OUTPUTS ===
+ show: {
+ type: 'json',
+ description: 'Parent show information',
+ condition: { field: 'operation', value: 'spotify_get_episode' },
+ },
+ episodes: {
+ type: 'json',
+ description: 'List of episodes',
+ condition: {
+ field: 'operation',
+ value: ['spotify_get_episodes', 'spotify_get_show_episodes', 'spotify_get_saved_episodes'],
+ },
+ },
+
+ // === AUDIOBOOK OUTPUTS ===
+ authors: {
+ type: 'json',
+ description: 'List of authors',
+ condition: { field: 'operation', value: 'spotify_get_audiobook' },
+ },
+ narrators: {
+ type: 'json',
+ description: 'List of narrators',
+ condition: { field: 'operation', value: 'spotify_get_audiobook' },
+ },
+ total_chapters: {
+ type: 'number',
+ description: 'Total chapters',
+ condition: { field: 'operation', value: 'spotify_get_audiobook' },
+ },
+ audiobooks: {
+ type: 'json',
+ description: 'List of audiobooks',
+ condition: {
+ field: 'operation',
+ value: ['spotify_get_audiobooks', 'spotify_get_saved_audiobooks'],
+ },
+ },
+ chapters: {
+ type: 'json',
+ description: 'List of chapters',
+ condition: { field: 'operation', value: 'spotify_get_audiobook_chapters' },
+ },
+
+ // === USER PROFILE OUTPUTS ===
+ display_name: {
+ type: 'string',
+ description: 'User display name',
+ condition: {
+ field: 'operation',
+ value: ['spotify_get_current_user', 'spotify_get_user_profile'],
+ },
+ },
+ email: {
+ type: 'string',
+ description: 'User email address',
+ condition: { field: 'operation', value: 'spotify_get_current_user' },
+ },
+ country: {
+ type: 'string',
+ description: 'User country code',
+ condition: { field: 'operation', value: 'spotify_get_current_user' },
+ },
+ product: {
+ type: 'string',
+ description: 'Subscription level (free, premium)',
+ condition: { field: 'operation', value: 'spotify_get_current_user' },
+ },
+
+ // === PLAYER STATE OUTPUTS ===
+ is_playing: {
+ type: 'boolean',
+ description: 'Whether playback is active',
+ condition: {
+ field: 'operation',
+ value: ['spotify_get_playback_state', 'spotify_get_currently_playing'],
+ },
+ },
+ device: {
+ type: 'json',
+ description: 'Active device information',
+ condition: { field: 'operation', value: 'spotify_get_playback_state' },
+ },
+ devices: {
+ type: 'json',
+ description: 'Available playback devices',
+ condition: { field: 'operation', value: 'spotify_get_devices' },
+ },
+ progress_ms: {
+ type: 'number',
+ description: 'Current playback position in ms',
+ condition: {
+ field: 'operation',
+ value: ['spotify_get_playback_state', 'spotify_get_currently_playing'],
+ },
+ },
+ currently_playing_type: {
+ type: 'string',
+ description: 'Type of content (track, episode, ad)',
+ condition: { field: 'operation', value: 'spotify_get_playback_state' },
+ },
+ shuffle_state: {
+ type: 'boolean',
+ description: 'Whether shuffle is enabled',
+ condition: { field: 'operation', value: 'spotify_get_playback_state' },
+ },
+ repeat_state: {
+ type: 'string',
+ description: 'Repeat mode (off, track, context)',
+ condition: { field: 'operation', value: 'spotify_get_playback_state' },
+ },
+ track: {
+ type: 'json',
+ description: 'Currently playing track',
+ condition: {
+ field: 'operation',
+ value: ['spotify_get_playback_state', 'spotify_get_currently_playing'],
+ },
+ },
+ currently_playing: {
+ type: 'json',
+ description: 'Currently playing item',
+ condition: { field: 'operation', value: 'spotify_get_queue' },
+ },
+ queue: {
+ type: 'json',
+ description: 'Upcoming tracks in queue',
+ condition: { field: 'operation', value: 'spotify_get_queue' },
+ },
+
+ // === RECENTLY PLAYED OUTPUTS ===
+ items: {
+ type: 'json',
+ description: 'List of recently played items',
+ condition: { field: 'operation', value: 'spotify_get_recently_played' },
+ },
+
+ // === BROWSE OUTPUTS ===
+ categories: {
+ type: 'json',
+ description: 'List of browse categories',
+ condition: { field: 'operation', value: 'spotify_get_categories' },
+ },
+ markets: {
+ type: 'json',
+ description: 'List of available market codes',
+ condition: { field: 'operation', value: 'spotify_get_markets' },
+ },
+
+ // === CHECK SAVED OUTPUTS ===
+ results: {
+ type: 'json',
+ description: 'Check operation results (id and saved boolean)',
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_check_saved_tracks',
+ 'spotify_check_saved_albums',
+ 'spotify_check_saved_shows',
+ 'spotify_check_saved_episodes',
+ 'spotify_check_saved_audiobooks',
+ 'spotify_check_following',
+ 'spotify_check_playlist_followers',
+ ],
+ },
+ },
+ all_saved: {
+ type: 'boolean',
+ description: 'Whether all tracks are saved',
+ condition: { field: 'operation', value: 'spotify_check_saved_tracks' },
+ },
+ none_saved: {
+ type: 'boolean',
+ description: 'Whether no tracks are saved',
+ condition: { field: 'operation', value: 'spotify_check_saved_tracks' },
+ },
+
+ // === PAGINATION OUTPUTS ===
+ total: {
+ type: 'number',
+ description: 'Total number of items',
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_get_album_tracks',
+ 'spotify_get_artist_albums',
+ 'spotify_get_playlist_tracks',
+ 'spotify_get_user_playlists',
+ 'spotify_get_saved_tracks',
+ 'spotify_get_saved_albums',
+ 'spotify_get_top_tracks',
+ 'spotify_get_top_artists',
+ 'spotify_get_new_releases',
+ 'spotify_get_categories',
+ ],
+ },
+ },
+ next: {
+ type: 'string',
+ description: 'URL for next page of results',
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_get_album_tracks',
+ 'spotify_get_artist_albums',
+ 'spotify_get_playlist_tracks',
+ 'spotify_get_user_playlists',
+ 'spotify_get_saved_tracks',
+ 'spotify_get_saved_albums',
+ 'spotify_get_top_tracks',
+ 'spotify_get_top_artists',
+ 'spotify_get_recently_played',
+ 'spotify_get_new_releases',
+ ],
+ },
+ },
+
+ // === OPERATION RESULT OUTPUTS ===
+ success: {
+ type: 'boolean',
+ description: 'Whether operation succeeded',
+ condition: {
+ field: 'operation',
+ value: [
+ 'spotify_play',
+ 'spotify_pause',
+ 'spotify_skip_next',
+ 'spotify_skip_previous',
+ 'spotify_seek',
+ 'spotify_set_volume',
+ 'spotify_set_repeat',
+ 'spotify_set_shuffle',
+ 'spotify_transfer_playback',
+ 'spotify_add_to_queue',
+ 'spotify_save_tracks',
+ 'spotify_remove_saved_tracks',
+ 'spotify_save_albums',
+ 'spotify_remove_saved_albums',
+ 'spotify_save_shows',
+ 'spotify_remove_saved_shows',
+ 'spotify_save_episodes',
+ 'spotify_remove_saved_episodes',
+ 'spotify_save_audiobooks',
+ 'spotify_remove_saved_audiobooks',
+ 'spotify_follow_artists',
+ 'spotify_unfollow_artists',
+ 'spotify_follow_playlist',
+ 'spotify_unfollow_playlist',
+ 'spotify_update_playlist',
+ 'spotify_add_playlist_cover',
+ ],
+ },
+ },
+ },
+}
diff --git a/apps/sim/blocks/blocks/sqs.ts b/apps/sim/blocks/blocks/sqs.ts
new file mode 100644
index 000000000..72f6ccacd
--- /dev/null
+++ b/apps/sim/blocks/blocks/sqs.ts
@@ -0,0 +1,155 @@
+import { SQSIcon } from '@/components/icons'
+import type { BlockConfig } from '@/blocks/types'
+import type { SqsResponse } from '@/tools/sqs/types'
+
+export const SQSBlock: BlockConfig = {
+ type: 'sqs',
+ name: 'Amazon SQS',
+ description: 'Connect to Amazon SQS',
+ longDescription: 'Integrate Amazon SQS into the workflow. Can send messages to SQS queues.',
+ docsLink: 'https://docs.sim.ai/tools/sqs',
+ category: 'tools',
+ bgColor: 'linear-gradient(45deg, #2E27AD 0%, #527FFF 100%)',
+ icon: SQSIcon,
+ subBlocks: [
+ {
+ id: 'operation',
+ title: 'Operation',
+ type: 'dropdown',
+ options: [{ label: 'Send Message', id: 'send' }],
+ value: () => 'send',
+ },
+ {
+ id: 'region',
+ title: 'AWS Region',
+ type: 'short-input',
+ placeholder: 'us-east-1',
+ required: true,
+ },
+ {
+ id: 'accessKeyId',
+ title: 'AWS Access Key ID',
+ type: 'short-input',
+ placeholder: 'AKIA...',
+ password: true,
+ required: true,
+ },
+ {
+ id: 'secretAccessKey',
+ title: 'AWS Secret Access Key',
+ type: 'short-input',
+ placeholder: 'Your secret access key',
+ password: true,
+ required: true,
+ },
+ {
+ id: 'queueUrl',
+ title: 'Queue URL',
+ type: 'short-input',
+ placeholder: 'https://sqs.us-east-1.amazonaws.com/123456789012/my-queue',
+ required: true,
+ },
+ // Data field for send message operation
+ {
+ id: 'messageGroupId',
+ title: 'Message Group ID (optional)',
+ type: 'short-input',
+ placeholder: '5FAB0F0B-30C6-4427-9407-5634F4A3984A',
+ condition: { field: 'operation', value: 'send' },
+ required: false,
+ },
+ {
+ id: 'messageDeduplicationId',
+ title: 'Message Deduplication ID (optional)',
+ type: 'short-input',
+ placeholder: '5FAB0F0B-30C6-4427-9407-5634F4A3984A',
+ condition: { field: 'operation', value: 'send' },
+ required: false,
+ },
+ {
+ id: 'data',
+ title: 'Data (JSON)',
+ type: 'code',
+ placeholder: '{\n "name": "John Doe",\n "email": "john@example.com",\n "active": true\n}',
+ condition: { field: 'operation', value: 'send' },
+ required: true,
+ },
+ ],
+ tools: {
+ access: ['sqs_send'],
+ config: {
+ tool: (params) => {
+ switch (params.operation) {
+ case 'send':
+ return 'sqs_send'
+ default:
+ throw new Error(`Invalid SQS operation: ${params.operation}`)
+ }
+ },
+ params: (params) => {
+ const { operation, data, messageGroupId, messageDeduplicationId, ...rest } = params
+
+ // Parse JSON fields
+ const parseJson = (value: unknown, fieldName: string) => {
+ if (!value) return undefined
+ if (typeof value === 'object') return value
+ if (typeof value === 'string' && value.trim()) {
+ try {
+ return JSON.parse(value)
+ } catch (parseError) {
+ const errorMsg =
+ parseError instanceof Error ? parseError.message : 'Unknown JSON error'
+ throw new Error(`Invalid JSON in ${fieldName}: ${errorMsg}`)
+ }
+ }
+ return undefined
+ }
+
+ const parsedData = parseJson(data, 'data')
+
+ // Build connection config
+ const connectionConfig = {
+ region: rest.region,
+ accessKeyId: rest.accessKeyId,
+ secretAccessKey: rest.secretAccessKey,
+ }
+
+ // Build params object
+ const result: Record = { ...connectionConfig }
+
+ if (rest.queueUrl) result.queueUrl = rest.queueUrl
+ if (messageGroupId) result.messageGroupId = messageGroupId
+ if (messageDeduplicationId) result.messageDeduplicationId = messageDeduplicationId
+ if (parsedData !== undefined) result.data = parsedData
+
+ return result
+ },
+ },
+ },
+ inputs: {
+ operation: { type: 'string', description: 'SQS operation to perform' },
+ region: { type: 'string', description: 'AWS region' },
+ accessKeyId: { type: 'string', description: 'AWS access key ID' },
+ secretAccessKey: { type: 'string', description: 'AWS secret access key' },
+ queueUrl: { type: 'string', description: 'SQS queue URL' },
+ messageGroupId: {
+ type: 'string',
+ description: 'Message group ID (optional)',
+ },
+ messageDeduplicationId: {
+ type: 'string',
+ description: 'Message deduplication ID (optional)',
+ },
+ data: { type: 'json', description: 'Data for send message operation' },
+ },
+ outputs: {
+ message: {
+ type: 'string',
+ description: 'Success or error message describing the operation outcome',
+ },
+ id: {
+ type: 'string',
+ description: 'Message ID',
+ },
+ },
+}
diff --git a/apps/sim/blocks/blocks/stagehand.ts b/apps/sim/blocks/blocks/stagehand.ts
index f8d4a5111..c57645336 100644
--- a/apps/sim/blocks/blocks/stagehand.ts
+++ b/apps/sim/blocks/blocks/stagehand.ts
@@ -8,23 +8,65 @@ export interface StagehandExtractResponse extends ToolResponse {
}
}
-export const StagehandBlock: BlockConfig = {
+export interface StagehandAgentResponse extends ToolResponse {
+ output: {
+ agentResult: {
+ success: boolean
+ completed: boolean
+ message: string
+ actions?: Array<{
+ type: string
+ description: string
+ result?: string
+ }>
+ }
+ structuredOutput?: Record
+ }
+}
+
+export type StagehandResponse = StagehandExtractResponse | StagehandAgentResponse
+
+export const StagehandBlock: BlockConfig = {
type: 'stagehand',
- name: 'Stagehand Extract',
- description: 'Extract data from websites',
+ name: 'Stagehand',
+ description: 'Web automation and data extraction',
authMode: AuthMode.ApiKey,
longDescription:
- 'Integrate Stagehand into the workflow. Can extract structured data from webpages.',
+ 'Integrate Stagehand into the workflow. Can extract structured data from webpages or run an autonomous agent to perform tasks.',
docsLink: 'https://docs.sim.ai/tools/stagehand',
category: 'tools',
bgColor: '#FFC83C',
icon: StagehandIcon,
subBlocks: [
+ // Operation selection
+ {
+ id: 'operation',
+ title: 'Operation',
+ type: 'dropdown',
+ options: [
+ { label: 'Extract Data', id: 'extract' },
+ { label: 'Run Agent', id: 'agent' },
+ ],
+ value: () => 'extract',
+ },
+ // Provider selection
+ {
+ id: 'provider',
+ title: 'AI Provider',
+ type: 'dropdown',
+ options: [
+ { label: 'OpenAI', id: 'openai' },
+ { label: 'Anthropic', id: 'anthropic' },
+ ],
+ value: () => 'openai',
+ },
+ // Extract operation fields
{
id: 'url',
title: 'URL',
type: 'short-input',
placeholder: 'Enter the URL of the website to extract data from',
+ condition: { field: 'operation', value: 'extract' },
required: true,
},
{
@@ -32,14 +74,7 @@ export const StagehandBlock: BlockConfig = {
title: 'Instructions',
type: 'long-input',
placeholder: 'Enter detailed instructions for what data to extract from the page...',
- required: true,
- },
- {
- id: 'apiKey',
- title: 'OpenAI API Key',
- type: 'short-input',
- placeholder: 'Enter your OpenAI API key',
- password: true,
+ condition: { field: 'operation', value: 'extract' },
required: true,
},
{
@@ -48,6 +83,7 @@ export const StagehandBlock: BlockConfig = {
type: 'code',
placeholder: 'Enter JSON Schema...',
language: 'json',
+ condition: { field: 'operation', value: 'extract' },
required: true,
wandConfig: {
enabled: true,
@@ -162,20 +198,188 @@ Example 3 (List Extraction):
generationType: 'json-schema',
},
},
+ // Agent operation fields
+ {
+ id: 'startUrl',
+ title: 'Starting URL',
+ type: 'short-input',
+ placeholder: 'Enter the starting URL for the agent',
+ condition: { field: 'operation', value: 'agent' },
+ required: true,
+ },
+ {
+ id: 'task',
+ title: 'Task',
+ type: 'long-input',
+ placeholder:
+ 'Enter the task or goal for the agent to achieve. Reference variables using %key% syntax.',
+ condition: { field: 'operation', value: 'agent' },
+ required: true,
+ },
+ {
+ id: 'variables',
+ title: 'Variables',
+ type: 'table',
+ columns: ['Key', 'Value'],
+ condition: { field: 'operation', value: 'agent' },
+ },
+ {
+ id: 'outputSchema',
+ title: 'Output Schema',
+ type: 'code',
+ placeholder: 'Enter JSON Schema...',
+ language: 'json',
+ condition: { field: 'operation', value: 'agent' },
+ wandConfig: {
+ enabled: true,
+ maintainHistory: true,
+ prompt: `You are an expert programmer specializing in creating JSON schemas for web automation agents.
+Generate ONLY the JSON schema based on the user's request.
+The output MUST be a single, valid JSON object, starting with { and ending with }.
+The JSON object MUST have the following top-level properties: 'name' (string), 'description' (string), 'strict' (boolean, usually true), and 'schema' (object).
+The 'schema' object must define the structure and MUST contain 'type': 'object', 'properties': {...}, 'additionalProperties': false, and 'required': [...].
+Inside 'properties', use standard JSON Schema properties (type, description, enum, items for arrays, etc.).
+
+Current schema: {context}
+
+Do not include any explanations, markdown formatting, or other text outside the JSON object.
+
+Valid Schema Examples:
+
+Example 1 (Login Result):
+{
+ "name": "login_result",
+ "description": "Result of a login task performed by the agent",
+ "strict": true,
+ "schema": {
+ "type": "object",
+ "properties": {
+ "success": {
+ "type": "boolean",
+ "description": "Whether the login was successful"
+ },
+ "username": {
+ "type": "string",
+ "description": "The username that was logged in"
+ },
+ "dashboardUrl": {
+ "type": "string",
+ "description": "The URL of the dashboard after login"
+ }
+ },
+ "additionalProperties": false,
+ "required": ["success"]
+ }
+}
+
+Example 2 (Form Submission):
+{
+ "name": "form_submission_result",
+ "description": "Result of submitting a form",
+ "strict": true,
+ "schema": {
+ "type": "object",
+ "properties": {
+ "submitted": {
+ "type": "boolean",
+ "description": "Whether the form was submitted"
+ },
+ "confirmationNumber": {
+ "type": "string",
+ "description": "Confirmation or reference number if provided"
+ },
+ "errorMessage": {
+ "type": "string",
+ "description": "Error message if submission failed"
+ }
+ },
+ "additionalProperties": false,
+ "required": ["submitted"]
+ }
+}
+
+Example 3 (Data Collection):
+{
+ "name": "collected_data",
+ "description": "Data collected by the agent from multiple pages",
+ "strict": true,
+ "schema": {
+ "type": "object",
+ "properties": {
+ "items": {
+ "type": "array",
+ "description": "List of collected items",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Item name"
+ },
+ "value": {
+ "type": "string",
+ "description": "Item value or content"
+ },
+ "sourceUrl": {
+ "type": "string",
+ "description": "URL where the item was found"
+ }
+ },
+ "additionalProperties": false,
+ "required": ["name"]
+ }
+ },
+ "totalCount": {
+ "type": "number",
+ "description": "Total number of items collected"
+ }
+ },
+ "additionalProperties": false,
+ "required": ["items"]
+ }
+}
+`,
+ placeholder: 'Describe what output format you expect from the agent task...',
+ generationType: 'json-schema',
+ },
+ },
+ // Shared API key field
+ {
+ id: 'apiKey',
+ title: 'API Key',
+ type: 'short-input',
+ placeholder: 'Enter your API key for the selected provider',
+ password: true,
+ required: true,
+ },
],
tools: {
- access: ['stagehand_extract'],
+ access: ['stagehand_extract', 'stagehand_agent'],
config: {
- tool: () => 'stagehand_extract',
+ tool: (params) => {
+ return params.operation === 'agent' ? 'stagehand_agent' : 'stagehand_extract'
+ },
},
},
inputs: {
- url: { type: 'string', description: 'Website URL to extract' },
- instruction: { type: 'string', description: 'Extraction instructions' },
- schema: { type: 'json', description: 'JSON schema definition' },
- apiKey: { type: 'string', description: 'OpenAI API key' },
+ operation: { type: 'string', description: 'Operation: extract or agent' },
+ provider: { type: 'string', description: 'AI provider: openai or anthropic' },
+ apiKey: { type: 'string', description: 'API key for the selected provider' },
+ // Extract inputs
+ url: { type: 'string', description: 'Website URL to extract (extract operation)' },
+ instruction: { type: 'string', description: 'Extraction instructions (extract operation)' },
+ schema: { type: 'json', description: 'JSON schema definition (extract operation)' },
+ // Agent inputs
+ startUrl: { type: 'string', description: 'Starting URL for agent (agent operation)' },
+ task: { type: 'string', description: 'Task description (agent operation)' },
+ variables: { type: 'json', description: 'Task variables (agent operation)' },
+ outputSchema: { type: 'json', description: 'Output schema (agent operation)' },
},
outputs: {
- data: { type: 'json', description: 'Extracted data' },
+ // Extract outputs
+ data: { type: 'json', description: 'Extracted data (extract operation)' },
+ // Agent outputs
+ agentResult: { type: 'json', description: 'Agent execution result (agent operation)' },
+ structuredOutput: { type: 'json', description: 'Structured output data (agent operation)' },
},
}
diff --git a/apps/sim/blocks/blocks/stagehand_agent.ts b/apps/sim/blocks/blocks/stagehand_agent.ts
deleted file mode 100644
index acbbdc830..000000000
--- a/apps/sim/blocks/blocks/stagehand_agent.ts
+++ /dev/null
@@ -1,183 +0,0 @@
-import { StagehandIcon } from '@/components/icons'
-import { AuthMode, type BlockConfig } from '@/blocks/types'
-import type { StagehandAgentResponse } from '@/tools/stagehand/types'
-
-export const StagehandAgentBlock: BlockConfig = {
- type: 'stagehand_agent',
- name: 'Stagehand Agent',
- description: 'Autonomous web browsing agent',
- authMode: AuthMode.ApiKey,
- longDescription:
- 'Integrate Stagehand Agent into the workflow. Can navigate the web and perform tasks.',
- docsLink: 'https://docs.sim.ai/tools/stagehand_agent',
- category: 'tools',
- bgColor: '#FFC83C',
- icon: StagehandIcon,
- subBlocks: [
- {
- id: 'startUrl',
- title: 'Starting URL',
- type: 'short-input',
- placeholder: 'Enter the starting URL for the agent',
- required: true,
- },
- {
- id: 'task',
- title: 'Task',
- type: 'long-input',
- placeholder:
- 'Enter the task or goal for the agent to achieve. Reference variables using %key% syntax.',
- required: true,
- },
- {
- id: 'variables',
- title: 'Variables',
- type: 'table',
- columns: ['Key', 'Value'],
- },
- {
- id: 'apiKey',
- title: 'Anthropic API Key',
- type: 'short-input',
- placeholder: 'Enter your Anthropic API key',
- password: true,
- required: true,
- },
- {
- id: 'outputSchema',
- title: 'Output Schema',
- type: 'code',
- placeholder: 'Enter JSON Schema...',
- language: 'json',
- wandConfig: {
- enabled: true,
- maintainHistory: true,
- prompt: `You are an expert programmer specializing in creating JSON schemas for web automation agents.
-Generate ONLY the JSON schema based on the user's request.
-The output MUST be a single, valid JSON object, starting with { and ending with }.
-The JSON object MUST have the following top-level properties: 'name' (string), 'description' (string), 'strict' (boolean, usually true), and 'schema' (object).
-The 'schema' object must define the structure and MUST contain 'type': 'object', 'properties': {...}, 'additionalProperties': false, and 'required': [...].
-Inside 'properties', use standard JSON Schema properties (type, description, enum, items for arrays, etc.).
-
-Current schema: {context}
-
-Do not include any explanations, markdown formatting, or other text outside the JSON object.
-
-Valid Schema Examples:
-
-Example 1 (Login Result):
-{
- "name": "login_result",
- "description": "Result of a login task performed by the agent",
- "strict": true,
- "schema": {
- "type": "object",
- "properties": {
- "success": {
- "type": "boolean",
- "description": "Whether the login was successful"
- },
- "username": {
- "type": "string",
- "description": "The username that was logged in"
- },
- "dashboardUrl": {
- "type": "string",
- "description": "The URL of the dashboard after login"
- }
- },
- "additionalProperties": false,
- "required": ["success"]
- }
-}
-
-Example 2 (Form Submission):
-{
- "name": "form_submission_result",
- "description": "Result of submitting a form",
- "strict": true,
- "schema": {
- "type": "object",
- "properties": {
- "submitted": {
- "type": "boolean",
- "description": "Whether the form was submitted"
- },
- "confirmationNumber": {
- "type": "string",
- "description": "Confirmation or reference number if provided"
- },
- "errorMessage": {
- "type": "string",
- "description": "Error message if submission failed"
- }
- },
- "additionalProperties": false,
- "required": ["submitted"]
- }
-}
-
-Example 3 (Data Collection):
-{
- "name": "collected_data",
- "description": "Data collected by the agent from multiple pages",
- "strict": true,
- "schema": {
- "type": "object",
- "properties": {
- "items": {
- "type": "array",
- "description": "List of collected items",
- "items": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "description": "Item name"
- },
- "value": {
- "type": "string",
- "description": "Item value or content"
- },
- "sourceUrl": {
- "type": "string",
- "description": "URL where the item was found"
- }
- },
- "additionalProperties": false,
- "required": ["name"]
- }
- },
- "totalCount": {
- "type": "number",
- "description": "Total number of items collected"
- }
- },
- "additionalProperties": false,
- "required": ["items"]
- }
-}
-`,
- placeholder: 'Describe what output format you expect from the agent task...',
- generationType: 'json-schema',
- },
- },
- ],
- tools: {
- access: ['stagehand_agent'],
- config: {
- tool: () => 'stagehand_agent',
- },
- },
- inputs: {
- startUrl: { type: 'string', description: 'Starting URL for agent' },
- task: { type: 'string', description: 'Task description' },
- variables: { type: 'json', description: 'Task variables' },
- apiKey: { type: 'string', description: 'Anthropic API key' },
- outputSchema: { type: 'json', description: 'Output schema' },
- },
- outputs: {
- agentResult: { type: 'json', description: 'Agent execution result' },
- structuredOutput: { type: 'json', description: 'Structured output data' },
- },
-}
diff --git a/apps/sim/blocks/blocks/trello.ts b/apps/sim/blocks/blocks/trello.ts
index d711feeea..635b53337 100644
--- a/apps/sim/blocks/blocks/trello.ts
+++ b/apps/sim/blocks/blocks/trello.ts
@@ -381,7 +381,6 @@ export const TrelloBlock: BlockConfig = {
text: { type: 'string', description: 'Comment text' },
},
outputs: {
- success: { type: 'boolean', description: 'Whether the operation was successful' },
lists: {
type: 'array',
description: 'Array of list objects (for list_lists operation)',
@@ -404,11 +403,7 @@ export const TrelloBlock: BlockConfig = {
},
count: {
type: 'number',
- description: 'Number of items returned (boards, cards, actions)',
- },
- error: {
- type: 'string',
- description: 'Error message if operation failed',
+ description: 'Number of items returned (lists, cards, actions)',
},
},
}
diff --git a/apps/sim/blocks/blocks/typeform.ts b/apps/sim/blocks/blocks/typeform.ts
index 7e5c6d877..e8648d078 100644
--- a/apps/sim/blocks/blocks/typeform.ts
+++ b/apps/sim/blocks/blocks/typeform.ts
@@ -308,29 +308,30 @@ export const TypeformBlock: BlockConfig = {
operations: { type: 'json', description: 'JSON Patch operations array' },
},
outputs: {
- // Common outputs (used by responses, list_forms)
+ // List/responses outputs
total_items: { type: 'number', description: 'Total response/form count' },
page_count: { type: 'number', description: 'Total page count' },
items: { type: 'json', description: 'Response/form items array' },
- // Form details outputs (get_form, create_form, update_form)
+ // Form details outputs
id: { type: 'string', description: 'Form unique identifier' },
title: { type: 'string', description: 'Form title' },
type: { type: 'string', description: 'Form type' },
- created_at: { type: 'string', description: 'ISO timestamp of form creation' },
- last_updated_at: { type: 'string', description: 'ISO timestamp of last update' },
settings: { type: 'json', description: 'Form settings object' },
- theme: { type: 'json', description: 'Theme configuration object' },
- workspace: { type: 'json', description: 'Workspace information' },
- fields: { type: 'json', description: 'Form fields/questions array' },
+ theme: { type: 'json', description: 'Theme reference' },
+ workspace: { type: 'json', description: 'Workspace reference' },
+ fields: { type: 'json', description: 'Form fields array' },
+ welcome_screens: { type: 'json', description: 'Welcome screens array' },
thankyou_screens: { type: 'json', description: 'Thank you screens array' },
_links: { type: 'json', description: 'Related resource links' },
// Delete form outputs
- deleted: { type: 'boolean', description: 'Whether the form was successfully deleted' },
+ deleted: { type: 'boolean', description: 'Whether the form was deleted' },
message: { type: 'string', description: 'Deletion confirmation message' },
// File operation outputs
fileUrl: { type: 'string', description: 'Downloaded file URL' },
contentType: { type: 'string', description: 'File content type' },
filename: { type: 'string', description: 'File name' },
+ // Insights outputs
+ form: { type: 'json', description: 'Form analytics and performance data' },
},
triggers: {
enabled: true,
diff --git a/apps/sim/blocks/blocks/video_generator.ts b/apps/sim/blocks/blocks/video_generator.ts
index 0e6518be5..86e3576c5 100644
--- a/apps/sim/blocks/blocks/video_generator.ts
+++ b/apps/sim/blocks/blocks/video_generator.ts
@@ -169,6 +169,29 @@ export const VideoGeneratorBlock: BlockConfig = {
required: false,
},
+ // Duration selection - Fal.ai (only for Kling and MiniMax models)
+ {
+ id: 'duration',
+ title: 'Duration (seconds)',
+ type: 'dropdown',
+ condition: {
+ field: 'model',
+ value: [
+ 'kling-2.5-turbo-pro',
+ 'kling-2.1-pro',
+ 'minimax-hailuo-2.3-pro',
+ 'minimax-hailuo-2.3-standard',
+ ],
+ },
+ options: [
+ { label: '5', id: '5' },
+ { label: '8', id: '8' },
+ { label: '10', id: '10' },
+ ],
+ value: () => '5',
+ required: false,
+ },
+
// Aspect ratio selection - Veo (only 16:9 and 9:16)
{
id: 'aspectRatio',
@@ -213,6 +236,28 @@ export const VideoGeneratorBlock: BlockConfig = {
required: false,
},
+ // Aspect ratio selection - Fal.ai (only for Kling and MiniMax models)
+ {
+ id: 'aspectRatio',
+ title: 'Aspect Ratio',
+ type: 'dropdown',
+ condition: {
+ field: 'model',
+ value: [
+ 'kling-2.5-turbo-pro',
+ 'kling-2.1-pro',
+ 'minimax-hailuo-2.3-pro',
+ 'minimax-hailuo-2.3-standard',
+ ],
+ },
+ options: [
+ { label: '16:9', id: '16:9' },
+ { label: '9:16', id: '9:16' },
+ ],
+ value: () => '16:9',
+ required: false,
+ },
+
// Note: MiniMax aspect ratio is fixed at 16:9 (not configurable)
// Note: Runway Gen-4 Turbo outputs at 720p natively (no resolution selector needed)
diff --git a/apps/sim/blocks/blocks/zep.ts b/apps/sim/blocks/blocks/zep.ts
index 0798986ba..bd3f43f66 100644
--- a/apps/sim/blocks/blocks/zep.ts
+++ b/apps/sim/blocks/blocks/zep.ts
@@ -276,26 +276,26 @@ export const ZepBlock: BlockConfig = {
metadata: { type: 'json', description: 'User metadata' },
},
outputs: {
+ // Thread operations
threadId: { type: 'string', description: 'Thread identifier' },
- userId: { type: 'string', description: 'User identifier' },
uuid: { type: 'string', description: 'Internal UUID' },
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Update timestamp' },
threads: { type: 'json', description: 'Array of threads' },
deleted: { type: 'boolean', description: 'Deletion status' },
+ // Message operations
messages: { type: 'json', description: 'Message data' },
- messageIds: { type: 'json', description: 'Message identifiers' },
- context: { type: 'string', description: 'User context string' },
- facts: { type: 'json', description: 'Extracted facts' },
- entities: { type: 'json', description: 'Extracted entities' },
- summary: { type: 'string', description: 'Conversation summary' },
- batchId: { type: 'string', description: 'Batch operation ID' },
+ messageIds: { type: 'json', description: 'Array of added message UUIDs' },
+ added: { type: 'boolean', description: 'Whether messages were added successfully' },
+ // Context operations
+ context: { type: 'string', description: 'User context string (summary or basic mode)' },
+ // User operations
+ userId: { type: 'string', description: 'User identifier' },
email: { type: 'string', description: 'User email' },
firstName: { type: 'string', description: 'User first name' },
lastName: { type: 'string', description: 'User last name' },
metadata: { type: 'json', description: 'User metadata' },
- responseCount: { type: 'number', description: 'Number of items in response' },
- totalCount: { type: 'number', description: 'Total number of items available' },
- rowCount: { type: 'number', description: 'Number of rows in response' },
+ // Counts
+ totalCount: { type: 'number', description: 'Total number of items returned' },
},
}
diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts
index b49f58151..ca1f30e84 100644
--- a/apps/sim/blocks/registry.ts
+++ b/apps/sim/blocks/registry.ts
@@ -82,7 +82,6 @@ import { PipedriveBlock } from '@/blocks/blocks/pipedrive'
import { PolymarketBlock } from '@/blocks/blocks/polymarket'
import { PostgreSQLBlock } from '@/blocks/blocks/postgresql'
import { PostHogBlock } from '@/blocks/blocks/posthog'
-import { PylonBlock } from '@/blocks/blocks/pylon'
import { QdrantBlock } from '@/blocks/blocks/qdrant'
import { RDSBlock } from '@/blocks/blocks/rds'
import { RedditBlock } from '@/blocks/blocks/reddit'
@@ -102,9 +101,9 @@ import { SharepointBlock } from '@/blocks/blocks/sharepoint'
import { ShopifyBlock } from '@/blocks/blocks/shopify'
import { SlackBlock } from '@/blocks/blocks/slack'
import { SmtpBlock } from '@/blocks/blocks/smtp'
+import { SpotifyBlock } from '@/blocks/blocks/spotify'
import { SSHBlock } from '@/blocks/blocks/ssh'
import { StagehandBlock } from '@/blocks/blocks/stagehand'
-import { StagehandAgentBlock } from '@/blocks/blocks/stagehand_agent'
import { StartTriggerBlock } from '@/blocks/blocks/start_trigger'
import { StarterBlock } from '@/blocks/blocks/starter'
import { StripeBlock } from '@/blocks/blocks/stripe'
@@ -137,6 +136,7 @@ import { ZendeskBlock } from '@/blocks/blocks/zendesk'
import { ZepBlock } from '@/blocks/blocks/zep'
import { ZoomBlock } from '@/blocks/blocks/zoom'
import type { BlockConfig } from '@/blocks/types'
+import { SQSBlock } from './blocks/sqs'
// Registry of all available blocks, alphabetically sorted
export const registry: Record = {
@@ -223,9 +223,9 @@ export const registry: Record = {
polymarket: PolymarketBlock,
postgresql: PostgreSQLBlock,
posthog: PostHogBlock,
- pylon: PylonBlock,
qdrant: QdrantBlock,
rds: RDSBlock,
+ sqs: SQSBlock,
dynamodb: DynamoDBBlock,
reddit: RedditBlock,
resend: ResendBlock,
@@ -242,11 +242,11 @@ export const registry: Record = {
sharepoint: SharepointBlock,
shopify: ShopifyBlock,
slack: SlackBlock,
+ spotify: SpotifyBlock,
smtp: SmtpBlock,
sftp: SftpBlock,
ssh: SSHBlock,
stagehand: StagehandBlock,
- stagehand_agent: StagehandAgentBlock,
starter: StarterBlock,
start_trigger: StartTriggerBlock,
stt: SttBlock,
diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx
index da739b927..f9e690a72 100644
--- a/apps/sim/components/icons.tsx
+++ b/apps/sim/components/icons.tsx
@@ -3667,31 +3667,6 @@ export function ZoomIcon(props: SVGProps) {
)
}
-export function PylonIcon(props: SVGProps) {
- return (
-
- )
-}
-
export function SendgridIcon(props: SVGProps) {
return (