Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions apps/sim/app/api/tools/stagehand/agent/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
47 changes: 40 additions & 7 deletions apps/sim/app/api/tools/stagehand/extract/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]),
Comment on lines +21 to +45
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Extract model list to separate config file to improve maintainability

env: z.enum(['browserbase', 'local']),
})

export async function POST(request: NextRequest) {
Expand All @@ -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 },
Expand All @@ -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', {
Expand All @@ -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,
Expand All @@ -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,
Comment on lines +111 to 113
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: apiKey and projectId are always passed to Stagehand even when env is 'local'. Only pass these for browserbase environment

Suggested change
env: env === 'browserbase' ? 'BROWSERBASE' : 'LOCAL',
apiKey: BROWSERBASE_API_KEY,
projectId: BROWSERBASE_PROJECT_ID,
env: env === 'browserbase' ? 'BROWSERBASE' : 'LOCAL',
...(env === 'browserbase' && {
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()')
Expand Down
49 changes: 47 additions & 2 deletions apps/sim/blocks/blocks/stagehand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,55 @@ export const StagehandBlock: BlockConfig<StagehandExtractResponse> = {
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' },
Comment on lines +66 to +71
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Duplicate entry 'gemini-2.0-flash' in model options. Remove one instance to prevent confusion.

Suggested change
{ 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.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.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' }
]
},
Comment on lines +47 to +78
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider moving model options to a separate config file to improve maintainability

{
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,
},
{
Expand All @@ -62,6 +105,8 @@ export const StagehandBlock: BlockConfig<StagehandExtractResponse> = {
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, },
Comment on lines +108 to +109
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Extra comma after required: true in env field definition. Remove for consistency.

Suggested change
model: { type: 'string', required: true },
env: { type: 'string', required: true, },
model: { type: 'string', required: true },
env: { type: 'string', required: true },

},
outputs: {
response: {
Expand Down
1 change: 1 addition & 0 deletions apps/sim/drizzle.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export default {
dbCredentials: {
url: env.DATABASE_URL,
},

} satisfies Config
12 changes: 12 additions & 0 deletions apps/sim/tools/stagehand/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ export const extractTool: ToolConfig<StagehandExtractParams, StagehandExtractRes
required: true,
description: 'JSON schema defining the structure of the data to extract',
},
model: {
type: 'string',
required: true,
description: 'Model to use for extraction (e.g., gpt-4o, claude-3-5-sonnet)',
},
env: {
type: 'string',
required: true,
description: 'Environment for extraction (browserbase or local)',
},
apiKey: {
type: 'string',
required: true,
Expand All @@ -44,6 +54,8 @@ export const extractTool: ToolConfig<StagehandExtractParams, StagehandExtractRes
schema: params.schema,
apiKey: params.apiKey,
url: params.url,
model: params.model,
env: params.env,
}),
},

Expand Down
2 changes: 2 additions & 0 deletions apps/sim/tools/stagehand/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export interface StagehandExtractParams {
schema: Record<string, any>
apiKey: string
url: string
model: string
env: 'browserbase' | 'local'
}

export interface StagehandExtractResponse extends ToolResponse {
Expand Down
2 changes: 1 addition & 1 deletion bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@
},
"packages/cli": {
"name": "simstudio",
"version": "0.1.18",
"version": "0.1.19",
"bin": {
"simstudio": "dist/index.js",
},
Expand Down
16 changes: 8 additions & 8 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ services:
image: ghcr.io/simstudioai/simstudio:latest
restart: unless-stopped
ports:
- '3000:3000'
- "3000:3000"
deploy:
resources:
limits:
Expand All @@ -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
Expand All @@ -40,7 +40,7 @@ services:
image: ghcr.io/simstudioai/realtime:latest
restart: unless-stopped
ports:
- '3002:3002'
- "3002:3002"
deploy:
resources:
limits:
Expand All @@ -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
Expand All @@ -67,22 +67,22 @@ 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}
- POSTGRES_DB=${POSTGRES_DB:-simstudio}
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
Expand Down