mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-05 12:14:59 -05:00
* improvement(collab): do not refetch active workflow id * progress on files * more integrations * separate server and client logic * consolidate more code * fix integrations * fix types * consolidate more code * fix tests * fix more bugbot comments * fix type check * fix circular impport * address more bugbot comments * fix ocr integrations * fix typing * remove leftover type * address bugbot comment * fix file block adv mode * fix * normalize file input * fix v2 blocmks for ocr * fix for v2 versions * fix more v2 blocks * update single file blocks * make interface simpler * cleanup fireflies * remove file only annotation * accept all types * added wand to ssh block * user files should be passed through * improve docs * fix slack to include successful execs * fix dropbox upload file * fix sendgrid * fix dropbox * fix * fix * update skills * fix uploaded file --------- Co-authored-by: waleed <walif6@gmail.com>
7.4 KiB
7.4 KiB
paths
| paths | |||
|---|---|---|---|
|
Adding Integrations
Overview
Adding a new integration typically requires:
- Tools - API operations (
tools/{service}/) - Block - UI component (
blocks/blocks/{service}.ts) - Icon - SVG icon (
components/icons.tsx) - Trigger (optional) - Webhooks/polling (
triggers/{service}/)
Always look up the service's API docs first.
1. Tools (tools/{service}/)
tools/{service}/
├── index.ts # Export all tools
├── types.ts # Params/response types
├── {action}.ts # Individual tool (e.g., send_message.ts)
└── ...
Tool file structure:
// tools/{service}/{action}.ts
import type { {Service}Params, {Service}Response } from '@/tools/{service}/types'
import type { ToolConfig } from '@/tools/types'
export const {service}{Action}Tool: ToolConfig<{Service}Params, {Service}Response> = {
id: '{service}_{action}',
name: '{Service} {Action}',
description: 'What this tool does',
version: '1.0.0',
oauth: { required: true, provider: '{service}' }, // if OAuth
params: { /* param definitions */ },
request: {
url: '/api/tools/{service}/{action}',
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => ({ ...params }),
},
transformResponse: async (response) => {
const data = await response.json()
if (!data.success) throw new Error(data.error)
return { success: true, output: data.output }
},
outputs: { /* output definitions */ },
}
Register in tools/registry.ts:
import { {service}{Action}Tool } from '@/tools/{service}'
// Add to registry object
{service}_{action}: {service}{Action}Tool,
2. Block (blocks/blocks/{service}.ts)
import { {Service}Icon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import type { {Service}Response } from '@/tools/{service}/types'
export const {Service}Block: BlockConfig<{Service}Response> = {
type: '{service}',
name: '{Service}',
description: 'Short description',
longDescription: 'Detailed description',
category: 'tools',
bgColor: '#hexcolor',
icon: {Service}Icon,
subBlocks: [ /* see SubBlock Properties below */ ],
tools: {
access: ['{service}_{action}', ...],
config: {
tool: (params) => `{service}_${params.operation}`,
params: (params) => ({ ...params }),
},
},
inputs: { /* input definitions */ },
outputs: { /* output definitions */ },
}
SubBlock Properties
{
id: 'fieldName', // Unique identifier
title: 'Field Label', // UI label
type: 'short-input', // See SubBlock Types below
placeholder: 'Hint text',
required: true, // See Required below
condition: { ... }, // See Condition below
dependsOn: ['otherField'], // See DependsOn below
mode: 'basic', // 'basic' | 'advanced' | 'both' | 'trigger'
}
SubBlock Types: short-input, long-input, dropdown, code, switch, slider, oauth-input, channel-selector, user-selector, file-upload, etc.
condition - Show/hide based on another field
// Show when operation === 'send'
condition: { field: 'operation', value: 'send' }
// Show when operation is 'send' OR 'read'
condition: { field: 'operation', value: ['send', 'read'] }
// Show when operation !== 'send'
condition: { field: 'operation', value: 'send', not: true }
// Complex: NOT in list AND another condition
condition: {
field: 'operation',
value: ['list_channels', 'list_users'],
not: true,
and: { field: 'destinationType', value: 'dm', not: true }
}
required - Field validation
// Always required
required: true
// Conditionally required (same syntax as condition)
required: { field: 'operation', value: 'send' }
dependsOn - Clear field when dependencies change
// Clear when credential changes
dependsOn: ['credential']
// Clear when authMethod changes AND (credential OR botToken) changes
dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }
mode - When to show field
'basic'- Only in basic mode (default UI)'advanced'- Only in advanced mode (manual input)'both'- Show in both modes (default)'trigger'- Only when block is used as trigger
Register in blocks/registry.ts:
import { {Service}Block } from '@/blocks/blocks/{service}'
// Add to registry object (alphabetically)
{service}: {Service}Block,
3. Icon (components/icons.tsx)
export function {Service}Icon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
{/* SVG path from service's brand assets */}
</svg>
)
}
4. Trigger (triggers/{service}/) - Optional
triggers/{service}/
├── index.ts # Export all triggers
├── webhook.ts # Webhook handler
├── utils.ts # Shared utilities
└── {event}.ts # Specific event handlers
Register in triggers/registry.ts:
import { {service}WebhookTrigger } from '@/triggers/{service}'
// Add to TRIGGER_REGISTRY
{service}_webhook: {service}WebhookTrigger,
File Handling
When integrations handle file uploads/downloads, use UserFile objects consistently.
File Input (Uploads)
- Block subBlocks: Use basic/advanced mode pattern with
canonicalParamId - Normalize in block config: Use
normalizeFileInputfrom@/blocks/utils - Internal API route: Create route that uses
downloadFileFromStorageto get file content - Tool routes to internal API: Don't call external APIs directly with files
// In block tools.config:
import { normalizeFileInput } from '@/blocks/utils'
const normalizedFile = normalizeFileInput(
params.uploadFile || params.fileRef || params.fileContent,
{ single: true }
)
if (normalizedFile) params.file = normalizedFile
File Output (Downloads)
Use FileToolProcessor in tool transformResponse to store files:
import { FileToolProcessor } from '@/executor/utils/file-tool-processor'
const processor = new FileToolProcessor(context)
const file = await processor.processFileData({
data: base64Content,
mimeType: 'application/pdf',
filename: 'doc.pdf',
})
Key Helpers
| Helper | Location | Purpose |
|---|---|---|
normalizeFileInput |
@/blocks/utils |
Normalize file params in block config |
processFilesToUserFiles |
@/lib/uploads/utils/file-utils |
Convert raw inputs to UserFile[] |
downloadFileFromStorage |
@/lib/uploads/utils/file-utils.server |
Get Buffer from UserFile |
FileToolProcessor |
@/executor/utils/file-tool-processor |
Process tool output files |
Checklist
- Look up API docs for the service
- Create
tools/{service}/types.tswith proper types - Create tool files for each operation
- Create
tools/{service}/index.tsbarrel export - Register tools in
tools/registry.ts - Add icon to
components/icons.tsx - Create block in
blocks/blocks/{service}.ts - Register block in
blocks/registry.ts - (Optional) Create triggers in
triggers/{service}/ - (Optional) Register triggers in
triggers/registry.ts - (If file uploads) Create internal API route with
downloadFileFromStorage - (If file uploads) Use
normalizeFileInputin block config