feat(build): added turbopack in dev & fixed circular dependency (#328)

* fix(tools): fixed x tool

* feat(build): added turbopack in dev & fixed circular dependency

* add turbopack config

* fixed failing test bc of circular import
This commit is contained in:
Waleed Latif
2025-05-04 19:45:27 -07:00
committed by GitHub
parent 4be568e3b4
commit 75c6582f91
13 changed files with 3922 additions and 2166 deletions

View File

@@ -313,22 +313,20 @@ In addition, you will need to update the registries:
```
4. **Register Your Block:**
Import and add your block to the blocks registry (`/sim/blocks/index.ts`) in the appropriate index file so it appears in the workflow builder.
Add your block to the blocks registry (`/sim/blocks/registry.ts`):
```typescript:/sim/blocks/index.ts
```typescript:/sim/blocks/registry.ts
import { PineconeBlock } from './blocks/pinecone'
export const blocks = [
// Registry of all available blocks
export const registry: Record<string, BlockConfig> = {
// ... existing blocks
PineconeBlock,
]
export const blocksByType: Record<string, BlockConfig> = {
// ... existing blocks by type
pinecone: PineconeBlock,
}
```
The block will be automatically available to the application through the registry.
5. **Test Your Block:**
Ensure that the block displays correctly in the UI and that its functionality works as expected.

View File

@@ -2,7 +2,6 @@ import { AgentIcon } from '@/components/icons'
import { createLogger } from '@/lib/logs/console-logger'
import { isHosted } from '@/lib/environment'
import { useOllamaStore } from '@/stores/ollama/store'
import { getAllBlocks } from '@/blocks'
import { MODELS_TEMP_RANGE_0_1, MODELS_TEMP_RANGE_0_2 } from '@/providers/model-capabilities'
import { getAllModelProviders, getBaseModelProviders } from '@/providers/utils'
import { ToolResponse } from '@/tools/types'
@@ -31,9 +30,15 @@ interface AgentResponse extends ToolResponse {
// Helper function to get the tool ID from a block type
const getToolIdFromBlock = (blockType: string): string | undefined => {
const blocks = getAllBlocks()
const block = blocks.find((b) => b.type === blockType)
return block?.tools?.access?.[0]
try {
const { getAllBlocks } = require('@/blocks/registry')
const blocks = getAllBlocks()
const block = blocks.find((b: { type: string; tools?: { access?: string[] } }) => b.type === blockType)
return block?.tools?.access?.[0]
} catch (error) {
logger.error('Error getting tool ID from block', { error })
return undefined
}
}
export const AgentBlock: BlockConfig<AgentResponse> = {

View File

@@ -1,172 +1,19 @@
// Import blocks
import { AgentBlock } from './blocks/agent'
import { AirtableBlock } from './blocks/airtable'
import { ApiBlock } from './blocks/api'
import { BrowserUseBlock } from './blocks/browser_use'
// import { AutoblocksBlock } from './blocks/autoblocks'
import { ConditionBlock } from './blocks/condition'
import { ConfluenceBlock } from './blocks/confluence'
import { GoogleDocsBlock } from './blocks/google_docs'
import { GoogleDriveBlock } from './blocks/drive'
import { ElevenLabsBlock } from './blocks/elevenlabs'
import { EvaluatorBlock } from './blocks/evaluator'
import { ExaBlock } from './blocks/exa'
import { FileBlock } from './blocks/file'
import { FirecrawlBlock } from './blocks/firecrawl'
import { FunctionBlock } from './blocks/function'
import { GitHubBlock } from './blocks/github'
import { GmailBlock } from './blocks/gmail'
import { GoogleSearchBlock } from './blocks/google'
// import { GuestyBlock } from './blocks/guesty'
import { ImageGeneratorBlock } from './blocks/image_generator'
import { JinaBlock } from './blocks/jina'
import { LinkupBlock } from './blocks/linkup'
import { MistralParseBlock } from './blocks/mistral_parse'
import { JiraBlock } from './blocks/jira'
import { NotionBlock } from './blocks/notion'
import { OpenAIBlock } from './blocks/openai'
import { PerplexityBlock } from './blocks/perplexity'
import { PineconeBlock } from './blocks/pinecone'
import { RedditBlock } from './blocks/reddit'
import { RouterBlock } from './blocks/router'
import { SerperBlock } from './blocks/serper'
import { GoogleSheetsBlock } from './blocks/google_sheets'
import { SlackBlock } from './blocks/slack'
import { StagehandBlock } from './blocks/stagehand'
import { StagehandAgentBlock } from './blocks/stagehand_agent'
import { StarterBlock } from './blocks/starter'
import { SupabaseBlock } from './blocks/supabase'
import { TavilyBlock } from './blocks/tavily'
import { ThinkingBlock } from './blocks/thinking'
import { TranslateBlock } from './blocks/translate'
import { TwilioSMSBlock } from './blocks/twilio'
import { TypeformBlock } from './blocks/typeform'
import { VisionBlock } from './blocks/vision'
import { WhatsAppBlock } from './blocks/whatsapp'
import { XBlock } from './blocks/x'
import { YouTubeBlock } from './blocks/youtube'
import { BlockConfig } from './types'
import { Mem0Block } from './blocks/mem0'
import { S3Block } from './blocks/s3'
import { TelegramBlock } from './blocks/telegram'
import { ClayBlock } from './blocks/clay'
import {
registry,
getAllBlocks,
getBlock,
getBlocksByCategory,
getAllBlockTypes,
isValidBlockType
} from './registry'
// Export blocks for ease of use
export {
AgentBlock,
AirtableBlock,
ApiBlock,
BrowserUseBlock,
// AutoblocksBlock,
ElevenLabsBlock,
Mem0Block,
MistralParseBlock,
FunctionBlock,
VisionBlock,
FirecrawlBlock,
// GuestyBlock,
FileBlock,
GoogleSearchBlock,
JinaBlock,
LinkupBlock,
JiraBlock,
TranslateBlock,
SlackBlock,
GitHubBlock,
ConditionBlock,
SerperBlock,
TavilyBlock,
RouterBlock,
EvaluatorBlock,
YouTubeBlock,
NotionBlock,
GmailBlock,
SupabaseBlock,
XBlock,
StarterBlock,
PineconeBlock,
OpenAIBlock,
ExaBlock,
RedditBlock,
GoogleDriveBlock,
GoogleDocsBlock,
WhatsAppBlock,
GoogleSheetsBlock,
PerplexityBlock,
ConfluenceBlock,
TwilioSMSBlock,
ImageGeneratorBlock,
TypeformBlock,
ThinkingBlock,
StagehandBlock,
StagehandAgentBlock,
S3Block,
TelegramBlock,
ClayBlock,
registry,
getBlock,
getBlocksByCategory,
getAllBlockTypes,
isValidBlockType,
getAllBlocks
}
// Registry of all block configurations, alphabetically sorted
const blocks: Record<string, BlockConfig> = {
agent: AgentBlock,
airtable: AirtableBlock,
api: ApiBlock,
browser_use: BrowserUseBlock,
// autoblocks: AutoblocksBlock,
condition: ConditionBlock,
confluence: ConfluenceBlock,
elevenlabs: ElevenLabsBlock,
evaluator: EvaluatorBlock,
exa: ExaBlock,
firecrawl: FirecrawlBlock,
file: FileBlock,
function: FunctionBlock,
github: GitHubBlock,
gmail: GmailBlock,
google_docs: GoogleDocsBlock,
google_drive: GoogleDriveBlock,
google_search: GoogleSearchBlock,
google_sheets: GoogleSheetsBlock,
// guesty: GuestyBlock,
image_generator: ImageGeneratorBlock,
jina: JinaBlock,
linkup: LinkupBlock,
jira: JiraBlock,
mem0: Mem0Block,
mistral_parse: MistralParseBlock,
notion: NotionBlock,
openai: OpenAIBlock,
perplexity: PerplexityBlock,
pinecone: PineconeBlock,
reddit: RedditBlock,
router: RouterBlock,
s3: S3Block,
serper: SerperBlock,
stagehand: StagehandBlock,
stagehand_agent: StagehandAgentBlock,
slack: SlackBlock,
starter: StarterBlock,
supabase: SupabaseBlock,
tavily: TavilyBlock,
thinking: ThinkingBlock,
translate: TranslateBlock,
twilio_sms: TwilioSMSBlock,
typeform: TypeformBlock,
vision: VisionBlock,
whatsapp: WhatsAppBlock,
x: XBlock,
youtube: YouTubeBlock,
telegram: TelegramBlock,
clay: ClayBlock,
}
// Helper functions
export const getBlock = (type: string): BlockConfig | undefined => blocks[type]
export const getBlocksByCategory = (category: 'blocks' | 'tools'): BlockConfig[] =>
Object.values(blocks).filter((block) => block.category === category)
export const getAllBlockTypes = (): string[] => Object.keys(blocks)
export const isValidBlockType = (type: string): type is string => type in blocks
export const getAllBlocks = (): BlockConfig[] => Object.values(blocks)
export type { BlockConfig } from './types'

124
sim/blocks/registry.ts Normal file
View File

@@ -0,0 +1,124 @@
/**
* Blocks Registry
*
*/
import { BlockConfig } from './types'
// Import all blocks directly here
import { AgentBlock } from './blocks/agent'
import { AirtableBlock } from './blocks/airtable'
import { ApiBlock } from './blocks/api'
// import { AutoblocksBlock } from './blocks/autoblocks'
import { BrowserUseBlock } from './blocks/browser_use'
import { ClayBlock } from './blocks/clay'
import { ConditionBlock } from './blocks/condition'
import { ConfluenceBlock } from './blocks/confluence'
import { GoogleDocsBlock } from './blocks/google_docs'
import { GoogleDriveBlock } from './blocks/drive'
import { ElevenLabsBlock } from './blocks/elevenlabs'
import { EvaluatorBlock } from './blocks/evaluator'
import { ExaBlock } from './blocks/exa'
import { FileBlock } from './blocks/file'
import { FirecrawlBlock } from './blocks/firecrawl'
import { FunctionBlock } from './blocks/function'
import { GitHubBlock } from './blocks/github'
import { GmailBlock } from './blocks/gmail'
import { GoogleSearchBlock } from './blocks/google'
// import { GuestyBlock } from './blocks/guesty'
import { ImageGeneratorBlock } from './blocks/image_generator'
import { JinaBlock } from './blocks/jina'
import { LinkupBlock } from './blocks/linkup'
import { MistralParseBlock } from './blocks/mistral_parse'
import { JiraBlock } from './blocks/jira'
import { NotionBlock } from './blocks/notion'
import { OpenAIBlock } from './blocks/openai'
import { PerplexityBlock } from './blocks/perplexity'
import { PineconeBlock } from './blocks/pinecone'
import { RedditBlock } from './blocks/reddit'
import { RouterBlock } from './blocks/router'
import { SerperBlock } from './blocks/serper'
import { GoogleSheetsBlock } from './blocks/google_sheets'
import { SlackBlock } from './blocks/slack'
import { StagehandBlock } from './blocks/stagehand'
import { StagehandAgentBlock } from './blocks/stagehand_agent'
import { StarterBlock } from './blocks/starter'
import { SupabaseBlock } from './blocks/supabase'
import { TavilyBlock } from './blocks/tavily'
import { ThinkingBlock } from './blocks/thinking'
import { TranslateBlock } from './blocks/translate'
import { TwilioSMSBlock } from './blocks/twilio'
import { TypeformBlock } from './blocks/typeform'
import { VisionBlock } from './blocks/vision'
import { WhatsAppBlock } from './blocks/whatsapp'
import { XBlock } from './blocks/x'
import { YouTubeBlock } from './blocks/youtube'
import { Mem0Block } from './blocks/mem0'
import { S3Block } from './blocks/s3'
import { TelegramBlock } from './blocks/telegram'
// Registry of all available blocks, alphabetically sorted
export const registry: Record<string, BlockConfig> = {
agent: AgentBlock,
airtable: AirtableBlock,
api: ApiBlock,
// autoblocks: AutoblocksBlock,
browser_use: BrowserUseBlock,
clay: ClayBlock,
condition: ConditionBlock,
confluence: ConfluenceBlock,
elevenlabs: ElevenLabsBlock,
evaluator: EvaluatorBlock,
exa: ExaBlock,
firecrawl: FirecrawlBlock,
file: FileBlock,
function: FunctionBlock,
github: GitHubBlock,
gmail: GmailBlock,
google_docs: GoogleDocsBlock,
google_drive: GoogleDriveBlock,
google_search: GoogleSearchBlock,
google_sheets: GoogleSheetsBlock,
// guesty: GuestyBlock,
image_generator: ImageGeneratorBlock,
jina: JinaBlock,
linkup: LinkupBlock,
jira: JiraBlock,
mem0: Mem0Block,
mistral_parse: MistralParseBlock,
notion: NotionBlock,
openai: OpenAIBlock,
perplexity: PerplexityBlock,
pinecone: PineconeBlock,
reddit: RedditBlock,
router: RouterBlock,
s3: S3Block,
serper: SerperBlock,
stagehand: StagehandBlock,
stagehand_agent: StagehandAgentBlock,
slack: SlackBlock,
starter: StarterBlock,
supabase: SupabaseBlock,
tavily: TavilyBlock,
thinking: ThinkingBlock,
translate: TranslateBlock,
twilio_sms: TwilioSMSBlock,
typeform: TypeformBlock,
vision: VisionBlock,
whatsapp: WhatsAppBlock,
x: XBlock,
youtube: YouTubeBlock,
telegram: TelegramBlock
}
// Helper functions to access the registry
export const getBlock = (type: string): BlockConfig | undefined => registry[type]
export const getBlocksByCategory = (category: 'blocks' | 'tools'): BlockConfig[] =>
Object.values(registry).filter((block) => block.category === category)
export const getAllBlockTypes = (): string[] => Object.keys(registry)
export const isValidBlockType = (type: string): type is string => type in registry
export const getAllBlocks = (): BlockConfig[] => Object.values(registry)

View File

@@ -12,27 +12,29 @@
import * as Sentry from "@sentry/nextjs"
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN || undefined,
enabled: process.env.NODE_ENV === 'production',
environment: process.env.NODE_ENV || 'development',
integrations: [
Sentry.replayIntegration(),
],
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.2 : 1,
replaysSessionSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 0,
replaysOnErrorSampleRate: process.env.NODE_ENV === 'production' ? 1.0 : 0,
debug: process.env.NODE_ENV === 'development',
beforeSend(event) {
if (process.env.NODE_ENV !== 'production') return null
if (event.request && typeof event.request === 'object') {
(event.request as any).ip = null
}
return event
},
})
if (process.env.NODE_ENV === 'production') {
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN || undefined,
enabled: true,
environment: process.env.NODE_ENV || 'development',
integrations: [
Sentry.replayIntegration(),
],
tracesSampleRate: 0.2,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
beforeSend(event) {
if (event.request && typeof event.request === 'object') {
(event.request as any).ip = null
}
return event
},
})
}
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart
export const onRouterTransitionStart = process.env.NODE_ENV === 'production'
? Sentry.captureRouterTransitionStart
: () => {}
if (typeof window !== 'undefined') {
const TELEMETRY_STATUS_KEY = 'simstudio-telemetry-status'

View File

@@ -13,7 +13,10 @@
// Set experimental.instrumentationHook = true in next.config.ts to enable this
import { createLogger } from '@/lib/logs/console-logger'
import * as Sentry from '@sentry/nextjs'
const Sentry = process.env.NODE_ENV === 'production'
? require('@sentry/nextjs')
: { captureRequestError: () => {} }
const logger = createLogger('OtelInstrumentation')
@@ -31,13 +34,14 @@ const DEFAULT_TELEMETRY_CONFIG = {
}
export async function register() {
// Sentry configuration
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./sentry.server.config')
}
if (process.env.NODE_ENV === 'production') {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./sentry.server.config')
}
if (process.env.NEXT_RUNTIME === 'edge') {
await import('./sentry.edge.config')
if (process.env.NEXT_RUNTIME === 'edge') {
await import('./sentry.edge.config')
}
}
// OpenTelemetry instrumentation
@@ -50,7 +54,8 @@ export async function register() {
let telemetryConfig
try {
telemetryConfig = require('./telemetry.config.js')
// Use dynamic import instead of require for ES modules
telemetryConfig = (await import('./telemetry.config.js')).default
} catch (e) {
telemetryConfig = DEFAULT_TELEMETRY_CONFIG
}

View File

@@ -12,7 +12,15 @@ const nextConfig: NextConfig = {
},
// Always use 'standalone' output to support API routes
output: 'standalone',
webpack: (config, { isServer }) => {
turbopack: {
resolveExtensions: ['.tsx', '.ts', '.jsx', '.js', '.mjs', '.json'],
},
webpack: (config, { isServer, dev }) => {
// Skip webpack configuration in development when using Turbopack
if (dev && process.env.NEXT_RUNTIME === 'turbopack') {
return config;
}
// Configure webpack to use memory cache instead of filesystem cache
// This avoids the serialization of large strings during the build process
if (config.cache) {
@@ -24,6 +32,11 @@ const nextConfig: NextConfig = {
return config
},
transpilePackages: [
'prettier',
'@react-email/components',
'@react-email/render'
],
// Only include headers when not building for standalone export
async headers() {
return [
@@ -93,12 +106,14 @@ const nextConfig: NextConfig = {
},
}
export default withSentryConfig(nextConfig, {
org: 'sim-studio',
project: 'javascript-nextjs',
silent: !process.env.CI,
widenClientFileUpload: true,
tunnelRoute: "/monitoring",
disableLogger: true,
automaticVercelMonitors: true,
})
const sentryConfig = {
silent: true,
org: process.env.SENTRY_ORG || '',
project: process.env.SENTRY_PROJECT || '',
authToken: process.env.SENTRY_AUTH_TOKEN || undefined,
disableSourceMapUpload: process.env.NODE_ENV !== 'production',
}
export default process.env.NODE_ENV === 'development'
? nextConfig
: withSentryConfig(nextConfig, sentryConfig)

5551
sim/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,8 @@
"license": "Apache-2.0",
"type": "module",
"scripts": {
"dev": "next dev",
"dev": "next dev --turbo",
"dev:classic": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",

View File

@@ -5,18 +5,19 @@
import * as Sentry from "@sentry/nextjs"
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN || undefined,
enabled: process.env.NODE_ENV === 'production',
environment: process.env.NODE_ENV || 'development',
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.2 : 1,
debug: process.env.NODE_ENV === 'development',
beforeSend(event) {
if (process.env.NODE_ENV !== 'production') return null
if (event.request && typeof event.request === 'object') {
(event.request as any).ip = null
}
return event
},
})
// Completely skip Sentry initialization in development
if (process.env.NODE_ENV === 'production') {
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN || undefined,
enabled: true,
environment: process.env.NODE_ENV || 'development',
tracesSampleRate: 0.2,
beforeSend(event) {
if (event.request && typeof event.request === 'object') {
(event.request as any).ip = null
}
return event
},
})
}

View File

@@ -4,19 +4,19 @@
import * as Sentry from "@sentry/nextjs"
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN || undefined,
enabled: process.env.NODE_ENV === 'production',
environment: process.env.NODE_ENV || 'development',
tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.2 : 1,
debug: process.env.NODE_ENV === 'development',
beforeSend(event) {
if (process.env.NODE_ENV !== 'production') return null
// Completely skip Sentry initialization in development
if (process.env.NODE_ENV === 'production') {
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN || undefined,
enabled: true,
environment: process.env.NODE_ENV || 'development',
tracesSampleRate: 0.2,
if (event.request && typeof event.request === 'object') {
(event.request as any).ip = null
}
return event
},
})
beforeSend(event) {
if (event.request && typeof event.request === 'object') {
(event.request as any).ip = null
}
return event
},
})
}

View File

@@ -23,7 +23,7 @@
* - IP addresses or geolocation data
*/
module.exports = {
const config = {
/**
* Endpoint URL where telemetry data is sent
* Change this if you want to send telemetry to your own collector
@@ -83,4 +83,6 @@ module.exports = {
serverSide: {
enabled: true,
},
};
};
export default config

View File

@@ -48,7 +48,7 @@ export const xSearchTool: ToolConfig<XSearchParams, XSearchResponse> = {
request: {
url: (params) => {
const query = encodeURIComponent(params.query)
const query = params.query
const expansions = [
'author_id',
'referenced_tweets.id',
@@ -84,6 +84,29 @@ export const xSearchTool: ToolConfig<XSearchParams, XSearchResponse> = {
transformResponse: async (response) => {
const data = await response.json()
// Check if data.data is undefined/null or not an array
if (!data.data || !Array.isArray(data.data)) {
console.error('X Search API Error:', JSON.stringify(data, null, 2))
return {
success: false,
error: data.error?.detail || data.error?.title || 'No results found or invalid response from X API',
output: {
tweets: [],
includes: {
users: [],
media: [],
polls: [],
},
meta: data.meta || {
resultCount: 0,
newestId: null,
oldestId: null,
nextToken: null,
},
},
}
}
const transformTweet = (tweet: any): XTweet => ({
id: tweet.id,
text: tweet.text,
@@ -140,6 +163,12 @@ export const xSearchTool: ToolConfig<XSearchParams, XSearchResponse> = {
if (error.title === 'Invalid Request') {
return 'Invalid search query. Please check your search parameters.'
}
return error.detail || `An error occurred while searching X`
if (error.status === 429) {
return 'Rate limit exceeded. Please try again later.'
}
if (error.detail && typeof error.detail === 'string') {
return `X API error: ${error.detail}`
}
return error.detail || error.message || `An error occurred while searching X`
},
}