Arquitectura general
Archivos fuente
Esta página se genera a partir de los siguientes archivos fuente:
- .claude/rules/sim-architecture.md
- apps/sim/executor/index.ts
- apps/sim/blocks/registry.ts
- apps/sim/connectors/registry.ts
- apps/sim/providers/registry.ts
- apps/sim/triggers/registry.ts
- apps/sim/hooks/queries/general-settings.ts
- apps/sim/app/workspace/[workspaceId]/settings/components/general/general.tsx
- apps/sim/app/workspace/[workspaceId]/settings/components/general/general-skeleton.tsx
- apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/general/general.tsx
- packages/db/index.ts
- packages/python-sdk/setup.py
- apps/sim/tools/index.ts
- apps/sim/blocks/index.ts
- apps/sim/socket/index.ts
- apps/sim/stores/index.ts
- packages/cli/src/index.ts
- apps/sim/triggers/index.ts
- apps/sim/providers/index.ts
- apps/sim/connectors/index.ts
Visión General de la Arquitectura del Sistema
Sim Studio AI es una plataforma de automatización de flujos de trabajo basada en una arquitectura modular construida sobre Next.js con App Router. El sistema implementa un motor de ejecución DAG (Directed Acyclic Graph) que permite orquestar bloques, conectores, proveedores de LLM y triggers de manera extensible y tipada.
La arquitectura sigue principios fundamentales de diseño: responsabilidad única para cada componente, composición sobre complejidad, seguridad de tipos con TypeScript en todas las capas, y estado predecible mediante Zustand para estado global y useState para concerns de UI (.claude/rules/sim-architecture.md:9-13).
正在加载图表渲染器...
Puntos clave del diagrama de arquitectura:
-
Separación en capas: El sistema divide claramente la capa cliente (UI/estado), el núcleo de ejecución (lógica de workflows), las integraciones externas y la infraestructura de soporte.
-
Flujo de dependencias: Las dependencias fluyen desde la capa cliente hacia el núcleo, y desde el núcleo hacia las integraciones e infraestructura, nunca en sentido inverso.
-
Registros centralizados: Blocks, Connectors, Providers y Triggers mantienen registros centralizados que permiten extensibilidad sin modificar el núcleo (
apps/sim/blocks/registry.ts:207,apps/sim/connectors/registry.ts:33). -
Estado distribuido: TanStack Query gestiona cache del servidor mientras Zustand maneja estado de UI, evitando duplicación y sincronización manual (
apps/sim/hooks/queries/general-settings.ts:67-76).
Módulos Core del Sistema
Motor de Ejecución (Executor)
El motor de ejecución es el componente central que procesa los flujos de trabajo. Implementa un ejecutor DAG que coordina la ejecución de bloques en orden topológico, manejando dependencias entre nodos y propagando resultados entre ellos.
Responsabilidades delimitadas:
- Ejecutar workflows representados como grafos dirigidos acíclicos
- Coordinar la ejecución paralela de bloques independientes
- Manejar el ciclo de vida de ejecución (inicio, progreso, error, completado)
- Gestionar el contexto de ejecución y variables de flujo
API de entrada:
typescript1// Punto de entrada principal - exporta DAGExecutor como Executor 2export { DAGExecutor as Executor } from '@/executor/execution/executor'
(apps/sim/executor/index.ts:6)
Estructura de datos clave: El executor recibe configuraciones de bloques desde el registry, donde cada bloque define su tipo, herramientas asociadas, parámetros y manejadores:
typescript1// Estructura del registry de bloques 2export const registry: Record<string, BlockConfig> = { 3 agent: AgentBlock, 4 api: ApiBlock, 5 condition: ConditionBlock, 6 // ... más de 150 bloques 7}
(apps/sim/blocks/registry.ts:207-430)
Cadena de llamada crítica:
- El usuario dispara un workflow desde UI o trigger externo
- El executor recibe el estado del workflow y valida el DAG
- Para cada bloque, obtiene su configuración via
getBlock(type)(apps/sim/blocks/registry.ts:432-438) - Ejecuta el bloque con el contexto actual y acumula resultados
- Propaga outputs a bloques dependientes según las conexiones del grafo
Manejo de errores: El sistema de herramientas implementa detección de rate limits y errores de cuota, incluyendo casos especiales donde proveedores retornan 401/403 en lugar de 429:
typescript1function isRateLimitError(error: unknown): boolean { 2 if (error && typeof error === 'object') { 3 const status = (error as { status?: number }).status 4 if (status === 429 || status === 503) return true 5 if (status === 401 || status === 403) { 6 const message = ((error as { message?: string }).message || '').toLowerCase() 7 if (message.includes('quota') || message.includes('rate limit')) { 8 return true 9 } 10 } 11 } 12 return false 13}
(apps/sim/tools/index.ts:145-157)
Sistema de Bloques (Block Registry)
El sistema de bloques define las unidades de procesamiento atómicas que componen los workflows. Cada bloque encapsula una funcionalidad específica: integración con servicio externo, operación de datos, lógica condicional, o generación de contenido con IA.
Responsabilidades delimitadas:
- Registrar y exponer todas las configuraciones de bloques disponibles
- Resolver bloques por tipo, incluyendo versionado (v2, v3)
- Categorizar bloques por función (blocks, tools, triggers)
- Validar tipos de bloque en tiempo de ejecución
API de entrada principales:
typescript1// Obtener bloque por tipo con normalización de guiones 2export const getBlock = (type: string): BlockConfig | undefined => { 3 if (registry[type]) return registry[type] 4 const normalized = type.replace(/-/g, '_') 5 return registry[normalized] 6} 7 8// Obtener última versión de un bloque versionado 9export const getLatestBlock = (baseType: string): BlockConfig | undefined => { 10 const normalized = baseType.replace(/-/g, '_') 11 const versionedKeys = Object.keys(registry).filter((key) => { 12 const match = key.match(new RegExp(`^${normalized}_v(\\d+)$`)) 13 return match !== null 14 }) 15 // Ordena por versión y retorna la más reciente 16 if (versionedKeys.length > 0) { 17 const sorted = versionedKeys.sort((a, b) => { 18 const versionA = Number.parseInt(a.match(/_v(\d+)$/)?.[1] || '0', 10) 19 const versionB = Number.parseInt(b.match(/_v(\d+)$/)?.[1] || '0', 10) 20 return versionB - versionA 21 }) 22 return registry[sorted[0]] 23 } 24 return registry[normalized] 25}
(apps/sim/blocks/registry.ts:432-458)
Estructura de datos del BlockConfig: Cada bloque exporta una configuración que incluye:
category: 'blocks' | 'tools' | 'triggers'tools: definición de herramientas accesiblesinputs: esquema de parámetros de entradaoutputs: esquema de resultadoshandler: función de ejecución
Ejemplos de bloques registrados: El registry incluye más de 150 bloques, desde integraciones (Slack, Salesforce, HubSpot) hasta utilidades (Condition, Router, Response):
typescript1export const registry: Record<string, BlockConfig> = { 2 slack: SlackBlock, 3 salesforce: SalesforceBlock, 4 hubspot: HubSpotBlock, 5 condition: ConditionBlock, 6 router: RouterBlock, 7 response: ResponseBlock, 8 // ... 150+ bloques más 9}
(apps/sim/blocks/registry.ts:207-430)
Sistema de versionado:
Los bloques pueden tener múltiples versiones coexistiendo (e.g., mistral_parse, mistral_parse_v2, mistral_parse_v3), permitiendo migración gradual y backward compatibility (apps/sim/blocks/registry.ts:113-117).
Sistema de Herramientas (Tools)
El sistema de herramientas gestiona la ejecución de operaciones específicas dentro de los bloques, incluyendo manejo de API keys alojadas, rate limiting, y procesamiento de outputs.
Responsabilidades delimitadas:
- Inyectar API keys alojadas de forma segura
- Detectar y manejar rate limits y errores de cuota
- Procesar outputs de archivos antes de retornar al usuario
- Filtrar campos internos de las respuestas
Manejo de API Keys alojadas: Cuando un usuario no configura su propia API key, el sistema puede usar keys alojadas con rate limiting:
typescript1// Inyección de API key alojada 2if (!acquireResult.success) { 3 logger.error(`[${requestId}] No hosted keys configured for ${tool.id}: ${acquireResult.error}`) 4 const error = new Error(acquireResult.error || `No hosted keys configured for ${tool.id}`) 5 ;(error as any).status = 503 6 throw error 7} 8params[apiKeyParam] = acquireResult.key
(apps/sim/tools/index.ts:120-128)
Procesamiento de outputs de archivos: El sistema detecta y procesa outputs que contienen archivos antes de retornar resultados:
typescript1async function processFileOutputs( 2 result: ToolResponse, 3 tool: ToolConfig, 4 executionContext?: ExecutionContext 5): Promise<ToolResponse> { 6 if (!executionContext || !result.success) return result 7 if (typeof window !== 'undefined') return result 8 9 const { FileToolProcessor } = await import('@/executor/utils/file-tool-processor') 10 if (!FileToolProcessor.hasFileOutputs(tool)) return result 11 12 const processedOutput = await FileToolProcessor.processToolOutputs( 13 result.output, tool, executionContext 14 ) 15 return { ...result, output: processedOutput } 16}
(apps/sim/tools/index.ts:569-602)
Filtrado de campos internos:
Los campos con prefijo __ son reservados para datos transitorios y se eliminan antes de retornar:
typescript1function stripInternalFields(output: Record<string, unknown>): Record<string, unknown> { 2 if (typeof output !== 'object' || output === null || Array.isArray(output)) { 3 return output 4 } 5 const result: Record<string, unknown> = {} 6 for (const [key, value] of Object.entries(output)) { 7 if (!key.startsWith('__')) { 8 result[key] = value 9 } 10 } 11 return result 12}
(apps/sim/tools/index.ts:365-376)
Parámetros del sistema MCP: El sistema filtra parámetros internos antes de extraer argumentos de herramientas:
typescript1const MCP_SYSTEM_PARAMETERS = new Set([ 2 'serverId', 'serverUrl', 'toolName', 'serverName', 3 '_context', 'envVars', 'workflowVariables', 4 'blockData', 'blockNameMapping', '_toolSchema', 5])
(apps/sim/tools/index.ts:537-548)
Sistema de Proveedores (Provider Registry)
El sistema de proveedores gestiona las integraciones con diferentes servicios de LLM (OpenAI, Anthropic, etc.), proporcionando una interfaz unificada para la ejecución de modelos.
Responsabilidades delimitadas:
- Registrar configuraciones de proveedores LLM
- Inicializar proveedores al arrancar la aplicación
- Resolver proveedores por ID para ejecución
API de entrada:
typescript1export async function getProviderExecutor( 2 providerId: ProviderId 3): Promise<ProviderConfig | undefined> { 4 const provider = providerRegistry[providerId] 5 if (!provider) { 6 logger.error(`Provider not found: ${providerId}`) 7 return undefined 8 } 9 return provider 10}
(apps/sim/providers/registry.ts:39-48)
Inicialización de proveedores: El sistema inicializa todos los proveedores registrados al arrancar:
typescript1export async function initializeProviders(): Promise<void> { 2 for (const [id, provider] of Object.entries(providerRegistry)) { 3 if (provider.initialize) { 4 try { 5 await provider.initialize() 6 logger.info(`Initialized provider: ${id}`) 7 } catch (error) { 8 logger.error(`Failed to initialize ${id} provider`, { 9 error: error instanceof Error ? error.message : 'Unknown error', 10 }) 11 } 12 } 13 } 14}
(apps/sim/providers/registry.ts:50-63)
Sistema de Triggers (Trigger Registry)
El sistema de triggers maneja los eventos que inician la ejecución de workflows, incluyendo webhooks de servicios externos y polling programado.
Responsabilidades delimitadas:
- Registrar configuraciones de triggers disponibles
- Manejar webhooks entrantes de servicios externos
- Gestionar polling para servicios sin webhook
- Mapear eventos específicos a triggers registrados
Estructura del registry: El registry agrupa triggers por servicio y tipo de evento:
typescript1export const TRIGGER_REGISTRY: TriggerRegistry = { 2 slack_webhook: slackWebhookTrigger, 3 airtable_webhook: airtableWebhookTrigger, 4 attio_record_created: attioRecordCreatedTrigger, 5 attio_record_updated: attioRecordUpdatedTrigger, 6 calcom_booking_created: calcomBookingCreatedTrigger, 7 calendly_invitee_created: calendlyInviteeCreatedTrigger, 8 confluence_page_created: confluencePageCreatedTrigger, 9 // ... más triggers 10}
(apps/sim/triggers/registry.ts:176-229)
Granularidad de eventos: Los triggers pueden ser genéricos (webhook) o específicos para tipos de eventos:
typescript1// Triggers específicos de Attio 2attio_record_created: attioRecordCreatedTrigger, 3attio_record_updated: attioRecordUpdatedTrigger, 4attio_record_deleted: attioRecordDeletedTrigger, 5attio_note_created: attioNoteCreatedTrigger, 6attio_task_created: attioTaskCreatedTrigger, 7// Triggers específicos de Confluence 8confluence_page_created: confluencePageCreatedTrigger, 9confluence_page_updated: confluencePageUpdatedTrigger, 10confluence_page_removed: confluencePageRemovedTrigger,
(apps/sim/triggers/registry.ts:186-227)
Sistema de Conectores (Connector Registry)
El sistema de conectores proporciona integraciones preconfiguradas con servicios externos, manejando autenticación OAuth y operaciones comunes.
Responsabilidades delimitadas:
- Registrar conectores disponibles para servicios externos
- Proporcionar configuración de autenticación OAuth
- Estandarizar operaciones comunes entre servicios
Conectores registrados: El sistema incluye más de 30 conectores para servicios populares:
typescript1export const CONNECTOR_REGISTRY: ConnectorRegistry = { 2 airtable: airtableConnector, 3 asana: asanaConnector, 4 confluence: confluenceConnector, 5 discord: discordConnector, 6 github: githubConnector, 7 gmail: gmailConnector, 8 google_calendar: googleCalendarConnector, 9 google_drive: googleDriveConnector, 10 hubspot: hubspotConnector, 11 jira: jiraConnector, 12 linear: linearConnector, 13 notion: notionConnector, 14 slack: slackConnector, 15 salesforce: salesforceConnector, 16 // ... más conectores 17}
(apps/sim/connectors/registry.ts:33-64)
Flujo de Datos y Cadena de Llamadas
El siguiente diagrama ilustra el flujo de datos end-to-end desde que un usuario dispara un workflow hasta la entrega de resultados:
正在加载图表渲染器...
Puntos clave del flujo de datos:
-
Orquestación por DAGExecutor: El executor coordina toda la ejecución, respetando dependencias del grafo y ejecutando bloques en paralelo cuando es posible (
apps/sim/executor/index.ts:6). -
Resolución dinámica de bloques: Cada bloque se resuelve via registry, permitiendo extensibilidad sin modificar el executor (
apps/sim/blocks/registry.ts:432). -
Inyección de API keys: El sistema de herramientas puede inyectar keys alojadas cuando el usuario no configura las propias (
apps/sim/tools/index.ts:120-128). -
Filtrado de respuesta: Los campos internos (
__) se eliminan antes de retornar resultados al usuario (apps/sim/tools/index.ts:365-376). -
Cache con TanStack Query: Los hooks de TanStack Query cachean respuestas con staleTime de 60 minutos, reduciendo llamadas innecesarias (
apps/sim/hooks/queries/general-settings.ts:75).
Decisiones de Diseño y Trade-offs
1. Registry Pattern para Extensibilidad
Decisión: Implementar registros centralizados para bloques, conectores, proveedores y triggers.
Razón: Permite añadir nuevas integraciones sin modificar el núcleo del sistema. Cada nuevo bloque simplemente se añade al registry y queda disponible automáticamente.
Trade-off: Aumenta el tamaño del bundle inicial. Mitigación: code splitting y lazy loading de bloques menos usados.
Evidencia: El patrón se aplica consistentemente en blocks/registry.ts, connectors/registry.ts, providers/registry.ts, triggers/registry.ts.
2. TanStack Query como Fuente de Verdad
Decisión: Usar TanStack Query para todo el estado del servidor, eliminando sincronización manual.
Razón: Evita el problema de estado derivado y sincronización. TanStack Query maneja cache, revalidación, y optimistic updates automáticamente.
Trade-off: Curva de aprendizaje para desarrolladores acostumbrados a Redux/Zustand para todo el estado.
Evidencia: apps/sim/hooks/queries/general-settings.ts:67-76 muestra el patrón de uso.
3. Versionado de Bloques Coexistente
Decisión: Permitir múltiples versiones de un bloque en el registry (e.g., mistral_parse, mistral_parse_v2, mistral_parse_v3).
Razón: Permite migración gradual de usuarios sin breaking changes. Los workflows existentes continúan funcionando mientras nuevos usan versiones mejoradas.
Trade-off: Aumenta complejidad del registry y requiere lógica de resolución de versiones.
Evidencia: apps/sim/blocks/registry.ts:440-458 implementa getLatestBlock().
4. API Keys Alojadas con Rate Limiting
Decisión: Proporcionar API keys alojadas para usuarios sin configuración propia, con rate limiting por dimensión.
Razón: Reduce fricción de onboarding. Usuarios pueden probar el sistema sin configurar credenciales.
Trade-off: Costo operativo y complejidad de gestión de keys. Requiere monitoreo de uso y billing.
Evidencia: apps/sim/tools/index.ts:120-128 y apps/sim/tools/index.ts:338-356.
5. Normalización de Identificadores
Decisión: Normalizar identificadores reemplazando guiones por underscores (type.replace(/-/g, '_')).
Razón: Compatible con convenciones de URLs (kebab-case) y nombres de variables (snake_case) sin duplicación.
Trade-off: Añade overhead de procesamiento en cada lookup.
Evidencia: apps/sim/blocks/registry.ts:436-437.
6. Filtrado de Campos Internos con Prefijo __
Decisión: Reservar el prefijo __ para campos internos y eliminarlos antes de retornar respuestas.
Razón: Permite pasar metadatos internos (costos, debugging) sin exponerlos al usuario final.
Trade-off: Requiere convención estricta y documentación clara.
Evidencia: apps/sim/tools/index.ts:365-376.
7. Detección Amplia de Rate Limits
Decisión: Detectar rate limits no solo por status 429, sino también por mensajes en 401/403.
Razón: Algunos proveedores (e.g., Perplexity) retornan 401/403 con "insufficient_quota" en lugar de 429 estándar.
Trade-off: Puede detectar falsos positivos si mensajes coinciden accidentalmente.
Evidencia: apps/sim/tools/index.ts:145-157.
Tabla de Selección Tecnológica
| Tecnología | Propósito | Razón de Selección | Alternativas Consideradas |
|---|---|---|---|
| Next.js App Router | Framework web | SSR, API routes, file-based routing | Remix, Pages Router |
| TypeScript | Lenguaje | Type safety, DX, ecosistema | JavaScript, Flow |
| TanStack Query | Estado servidor | Cache, revalidación, optimistic updates | SWR, Apollo Client |
| Zustand | Estado global UI | Simple, sin boilerplate, TypeScript | Redux, Jotai, Recoil |
| Drizzle ORM | Base de datos | Type-safe, lightweight, SQL-like | Prisma, TypeORM |
| Mermaid | Diagramas | Integración Markdown, renderizado client-side | PlantUML, D2 |
| Python SDK | SDK cliente | Ecosistema data/ML, requests library | JavaScript SDK únicamente |
| CLI Package | Herramientas dev | Automatización, scripts de desarrollo | Shell scripts |
| Socket Server | Real-time | Actualizaciones de ejecución en vivo | Server-Sent Events |
| DAG Executor | Motor workflows | Ejecución ordenada, paralelismo | State machine simple |
Diagrama de Dependencias entre Módulos
正在加载图表渲染器...
Puntos clave del diagrama de dependencias:
-
Flujo unidireccional: Las dependencias fluyen de UI hacia motor, y de motor hacia infraestructura, nunca en reversa.
-
Aislamiento de registries: Los registries son hojas del grafo de dependencias, no dependen de otros módulos de la aplicación.
-
Hooks como interfaz: TanStack Query hooks actúan como interfaz entre UI y motor de ejecución, encapsulando lógica de fetch y cache.
-
Executor como hub: El executor es el único componente que interactúa con tools, blocks, database y sockets simultáneamente.
Configuración y Flujo de Inicio
Estructura de Directorios
La aplicación sigue una estructura feature-based bajo apps/sim/:
apps/sim/
├── app/ # Next.js app router (páginas, API routes)
├── blocks/ # Definiciones de bloques y registry
├── components/ # UI compartida (emcn/, ui/)
├── executor/ # Motor de ejecución de workflows
├── hooks/ # Hooks compartidos (queries/, selectors/)
├── lib/ # Utilidades de aplicación
├── providers/ # Integraciones de proveedores LLM
├── stores/ # Zustand stores
├── tools/ # Definiciones de herramientas
└── triggers/ # Definiciones de triggers
(.claude/rules/sim-architecture.md:16-28)
Organización de Features
Las features viven bajo app/workspace/[workspaceId]/:
feature/
├── components/ # Componentes de la feature
├── hooks/ # Hooks específicos de feature
├── utils/ # Utilidades específicas (2+ consumidores)
├── feature.tsx # Componente principal
└── page.tsx # Entry point de página Next.js
(.claude/rules/sim-architecture.md:34-41)
Convenciones de Nomenclatura
| Tipo | Convención | Ejemplo |
|---|---|---|
| Componentes | PascalCase | WorkflowList |
| Hooks | Prefijo use | useWorkflowOperations |
| Archivos | kebab-case | workflow-list.tsx |
| Stores | stores/feature/store.ts | stores/workflow/store.ts |
| Constantes | SCREAMING_SNAKE_CASE | MAX_RETRIES |
| Interfaces | PascalCase con sufijo | WorkflowListProps |
(.claude/rules/sim-architecture.md:43-50) |
Reglas de Utilidades
- Nunca crear
utils.tspara un solo consumidor - inline en el componente - Crear
utils.tscuando 2+ archivos necesitan el mismo helper - Ubicación:
lib/(app-wide) →feature/utils/(feature-scoped) → inline (uso único) (.claude/rules/sim-architecture.md:51-56)
Flujo de Inicialización
- Next.js App Router inicializa la aplicación
- Provider Registry inicializa proveedores LLM via
initializeProviders()(apps/sim/providers/registry.ts:50-63) - TanStack Query carga settings iniciales via hooks (
apps/sim/hooks/queries/general-settings.ts:67-76) - Socket Server establece conexión para actualizaciones real-time
- Block/Connector/Trigger Registries cargan configuraciones bajo demanda
SDK de Python
El proyecto incluye un SDK de Python para ejecutar workflows programáticamente:
python1# setup.py 2setup( 3 name="simstudio-sdk", 4 version="0.1.1", 5 description="Sim SDK - Execute workflows programmatically", 6 python_requires=">=3.8", 7 install_requires=[ 8 "requests>=2.25.0", 9 "typing-extensions>=4.0.0; python_version<'3.10'", 10 ], 11)
(packages/python-sdk/setup.py:6-32)
El SDK soporta Python 3.8-3.12 y proporciona una interfaz para ejecutar workflows sin usar la UI web.
