diff --git a/apps/sim/app/api/tools/stagehand/agent/route.ts b/apps/sim/app/api/tools/stagehand/agent/route.ts index 3ef34a0664..ff5d08f1cc 100644 --- a/apps/sim/app/api/tools/stagehand/agent/route.ts +++ b/apps/sim/app/api/tools/stagehand/agent/route.ts @@ -774,13 +774,12 @@ IMPORTANT: For any form fields that require sensitive information like usernames 1. If you see placeholders like %username% or %password% in the task, DO NOT ask for the actual values. 2. If you need to type in login forms, use the EXACT placeholder text (e.g., "%username%" or "%password%") UNLESS instructed otherwise. 3. The system will automatically substitute the real values when you use these placeholders IF direct login was not attempted. -4. Example correct approach: "type %username% in the username field".${ - variablesObject && Object.keys(variablesObject).length > 0 +4. Example correct approach: "type %username% in the username field".${variablesObject && Object.keys(variablesObject).length > 0 ? `\n5. Available variables: ${Object.keys(variablesObject) - .map((k) => `%${k}%`) - .join(', ')}` + .map((k) => `%${k}%`) + .join(', ')}` : '' - }\n + }\n WEBSITE NAVIGATION GUIDANCE: 1. If you need to log in but don't see a login form, LOOK for login buttons or links (they might say "Login" or "Sign in"). 2. If you're on a login page but don't see a username/password form, try scrolling or looking for "Continue with email" or similar options. diff --git a/apps/sim/app/api/tools/stagehand/extract/route.ts b/apps/sim/app/api/tools/stagehand/extract/route.ts index 0655042199..9f0dabb8d1 100644 --- a/apps/sim/app/api/tools/stagehand/extract/route.ts +++ b/apps/sim/app/api/tools/stagehand/extract/route.ts @@ -18,6 +18,32 @@ const requestSchema = z.object({ selector: z.string().nullable().optional(), apiKey: z.string(), url: z.string().url(), + model: z.enum([ + "gpt-4o", + "gpt-4o-mini", + "gpt-4o-2024-08-06", + "gpt-4.5-preview", + "claude-3-5-sonnet-latest", + "claude-3-5-sonnet-20241022", + "claude-3-5-sonnet-20240620", + "claude-3-7-sonnet-latest", + "claude-3-7-sonnet-20250219", + "o1-mini", + "o1-preview", + "o3-mini", + "gemini-2.0-flash", + "gemini-1.5-flash", + "gemini-1.5-pro", + "gemini-1.5-flash-8b", + "gemini-2.0-flash-lite", + "gemini-2.0-flash", + "gemini-2.5-pro-preview-03-25", + "cerebras-llama-3.3-70b", + "cerebras-llama-3.1-8b", + "groq-llama-3.3-70b-versatile", + "groq-llama-3.3-70b-specdec" + ]), + env: z.enum(['browserbase', 'local']), }) export async function POST(request: NextRequest) { @@ -34,6 +60,7 @@ export async function POST(request: NextRequest) { const validationResult = requestSchema.safeParse(body) if (!validationResult.success) { + logger.error('Invalid request body', { errors: validationResult.error.errors }) return NextResponse.json( { error: 'Invalid request parameters', details: validationResult.error.errors }, @@ -42,7 +69,7 @@ export async function POST(request: NextRequest) { } const params = validationResult.data - const { url: rawUrl, instruction, selector, useTextExtract, apiKey, schema } = params + const { url: rawUrl, instruction, selector, useTextExtract, apiKey, schema, model, env } = params const url = normalizeUrl(rawUrl) logger.info('Starting Stagehand extraction process', { @@ -61,7 +88,7 @@ export async function POST(request: NextRequest) { ) } - if (!BROWSERBASE_API_KEY || !BROWSERBASE_PROJECT_ID) { + if (env === 'browserbase' && (!BROWSERBASE_API_KEY || !BROWSERBASE_PROJECT_ID)) { logger.error('Missing required environment variables', { hasBrowserbaseApiKey: !!BROWSERBASE_API_KEY, hasBrowserbaseProjectId: !!BROWSERBASE_PROJECT_ID, @@ -73,24 +100,30 @@ export async function POST(request: NextRequest) { ) } - if (!apiKey || typeof apiKey !== 'string' || !apiKey.startsWith('sk-')) { - logger.error('Invalid OpenAI API key format') - return NextResponse.json({ error: 'Invalid OpenAI API key format' }, { status: 400 }) + if (!apiKey || typeof apiKey !== 'string') { + logger.error('Invalid API key format') + return NextResponse.json({ error: 'Invalid API key format' }, { status: 400 }) } try { logger.info('Initializing Stagehand with Browserbase') stagehand = new Stagehand({ - env: 'BROWSERBASE', + env: env === 'browserbase' ? 'BROWSERBASE' : 'LOCAL', apiKey: BROWSERBASE_API_KEY, projectId: BROWSERBASE_PROJECT_ID, verbose: 1, logger: (msg) => logger.info(typeof msg === 'string' ? msg : JSON.stringify(msg)), disablePino: true, - modelName: 'gpt-4o', + modelName: model, modelClientOptions: { apiKey: apiKey, }, + ...(env === 'local' && { + localBrowserLaunchOptions: { + headless: true, + executablePath: "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", + } + }) }) logger.info('Starting stagehand.init()') diff --git a/apps/sim/blocks/blocks/stagehand.ts b/apps/sim/blocks/blocks/stagehand.ts index fa08c00b98..c0665ab4b9 100644 --- a/apps/sim/blocks/blocks/stagehand.ts +++ b/apps/sim/blocks/blocks/stagehand.ts @@ -33,12 +33,55 @@ export const StagehandBlock: BlockConfig = { layout: 'full', placeholder: 'Enter detailed instructions for what data to extract from the page...', }, + { + id: "env", + title: 'Environment', + type: 'combobox', + layout: 'full', + placeholder: 'Select the environment for extraction', + options: [ + { id: 'browserbase', label: 'Browserbase' }, + { id: "local", label: 'Local (Chromium)' }, + ], + }, + { + id: "model", + title: 'Model', + type: "combobox", + placeholder: 'Select the model to use for extraction', + layout: 'full', + options: [ + { id: 'gpt-4o', label: 'gpt-4o' }, + { id: 'gpt-4o-mini', label: 'gpt-4o-mini' }, + { id: 'gpt-4o-2024-08-06', label: 'gpt-4o-2024-08-06' }, + { id: 'gpt-4.5-preview', label: 'gpt-4.5-preview' }, + { id: 'claude-3-5-sonnet-latest', label: 'claude-3-5-sonnet-latest' }, + { id: 'claude-3-5-sonnet-20241022', label: 'claude-3-5-sonnet-20241022' }, + { id: 'claude-3-5-sonnet-20240620', label: 'claude-3-5-sonnet-20240620' }, + { id: 'claude-3-7-sonnet-latest', label: 'claude-3-7-sonnet-latest' }, + { id: 'claude-3-7-sonnet-20250219', label: 'claude-3-7-sonnet-20250219' }, + { id: 'o1-mini', label: 'o1-mini' }, + { id: 'o1-preview', label: 'o1-preview' }, + { id: 'o3-mini', label: 'o3-mini' }, + { id: 'gemini-2.0-flash', label: 'gemini-2.0-flash' }, + { id: 'gemini-1.5-flash', label: 'gemini-1.5-flash' }, + { id: 'gemini-1.5-pro', label: 'gemini-1.5-pro' }, + { id: 'gemini-1.5-flash-8b', label: 'gemini-1.5-flash-8b' }, + { id: 'gemini-2.0-flash-lite', label: 'gemini-2.0-flash-lite' }, + { id: 'gemini-2.0-flash', label: 'gemini-2.0-flash' }, + { id: 'gemini-2.5-pro-preview-03-25', label: 'gemini-2.5-pro-preview-03-25' }, + { id: 'cerebras-llama-3.3-70b', label: 'cerebras-llama-3.3-70b' }, + { id: 'cerebras-llama-3.1-8b', label: 'cerebras-llama-3.1-8b' }, + { id: 'groq-llama-3.3-70b-versatile', label: 'groq-llama-3.3-70b-versatile' }, + { id: 'groq-llama-3.3-70b-specdec', label: 'groq-llama-3.3-70b-specdec' } + ] + }, { id: 'apiKey', - title: 'OpenAI API Key', + title: 'API Key', type: 'short-input', layout: 'full', - placeholder: 'Enter your OpenAI API key', + placeholder: 'Enter your API key', password: true, }, { @@ -62,6 +105,8 @@ export const StagehandBlock: BlockConfig = { instruction: { type: 'string', required: true }, schema: { type: 'json', required: true }, apiKey: { type: 'string', required: true }, + model: { type: 'string', required: true }, + env: { type: 'string', required: true, }, }, outputs: { response: { diff --git a/apps/sim/drizzle.config.ts b/apps/sim/drizzle.config.ts index a0088e017f..92c576209f 100644 --- a/apps/sim/drizzle.config.ts +++ b/apps/sim/drizzle.config.ts @@ -8,4 +8,5 @@ export default { dbCredentials: { url: env.DATABASE_URL, }, + } satisfies Config diff --git a/apps/sim/tools/stagehand/extract.ts b/apps/sim/tools/stagehand/extract.ts index ad990fa5a0..bf8bc7fccd 100644 --- a/apps/sim/tools/stagehand/extract.ts +++ b/apps/sim/tools/stagehand/extract.ts @@ -21,6 +21,16 @@ export const extractTool: ToolConfig apiKey: string url: string + model: string + env: 'browserbase' | 'local' } export interface StagehandExtractResponse extends ToolResponse { diff --git a/bun.lock b/bun.lock index 2caa45c2fb..dad9b3d1f5 100644 --- a/bun.lock +++ b/bun.lock @@ -173,7 +173,7 @@ }, "packages/cli": { "name": "simstudio", - "version": "0.1.18", + "version": "0.1.19", "bin": { "simstudio": "dist/index.js", }, diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index c9088ea374..c3626673c6 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -3,7 +3,7 @@ services: image: ghcr.io/simstudioai/simstudio:latest restart: unless-stopped ports: - - '3000:3000' + - "3000:3000" deploy: resources: limits: @@ -30,7 +30,7 @@ services: realtime: condition: service_healthy healthcheck: - test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3000'] + test: ["CMD", "wget", "--spider", "--quiet", "http://127.0.0.1:3000"] interval: 90s timeout: 5s retries: 3 @@ -40,7 +40,7 @@ services: image: ghcr.io/simstudioai/realtime:latest restart: unless-stopped ports: - - '3002:3002' + - "3002:3002" deploy: resources: limits: @@ -54,7 +54,7 @@ services: db: condition: service_healthy healthcheck: - test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3002'] + test: ["CMD", "wget", "--spider", "--quiet", "http://127.0.0.1:3002"] interval: 90s timeout: 5s retries: 3 @@ -67,14 +67,14 @@ services: depends_on: db: condition: service_healthy - command: ['bun', 'run', 'db:migrate'] - restart: 'no' + command: ["bun", "run", "db:migrate"] + restart: "no" db: image: pgvector/pgvector:pg17 restart: unless-stopped ports: - - '5432:5432' + - "5432:5432" environment: - POSTGRES_USER=${POSTGRES_USER:-postgres} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-postgres} @@ -82,7 +82,7 @@ services: volumes: - postgres_data:/var/lib/postgresql/data healthcheck: - test: ['CMD-SHELL', 'pg_isready -U postgres'] + test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5