11import { createLogger } from '@sim/logger'
2+ import { getErrorMessage } from '@sim/utils/errors'
23import { sleep } from '@sim/utils/helpers'
4+ import { z } from 'zod'
35import { getMaxExecutionTimeout } from '@/lib/core/execution-limits'
46import type { InternalToolOperationImplementation } from '@/lib/internal/tool-operations/types'
5- import type { BrowserUseRunTaskParams , BrowserUseRunTaskResponse } from '@/tools/browser_use/types'
7+ import type {
8+ BrowserUseRunTaskParams ,
9+ BrowserUseRunTaskResponse ,
10+ BrowserUseTaskStep ,
11+ } from '@/tools/browser_use/types'
612
713const logger = createLogger ( 'BrowserUseTool' )
814
@@ -11,6 +17,61 @@ const MAX_POLL_TIME_MS = getMaxExecutionTimeout()
1117const MAX_CONSECUTIVE_ERRORS = 3
1218const API_BASE = 'https://api.browser-use.com/api/v2'
1319
20+ const createSessionResponseSchema = z . object ( {
21+ id : z . string ( ) . min ( 1 ) ,
22+ } )
23+
24+ const sessionDetailsResponseSchema = z . object ( {
25+ liveUrl : z . string ( ) . nullable ( ) . optional ( ) ,
26+ publicShareUrl : z . string ( ) . nullable ( ) . optional ( ) ,
27+ } )
28+
29+ const taskStepSchema : z . ZodType < BrowserUseTaskStep > = z
30+ . object ( {
31+ number : z . number ( ) ,
32+ memory : z . string ( ) ,
33+ evaluationPreviousGoal : z . string ( ) ,
34+ nextGoal : z . string ( ) ,
35+ url : z . string ( ) ,
36+ screenshotUrl : z . string ( ) . nullable ( ) . optional ( ) ,
37+ actions : z . array ( z . string ( ) ) ,
38+ duration : z . number ( ) . nullable ( ) . optional ( ) ,
39+ } )
40+ . passthrough ( )
41+
42+ const taskStatusResponseSchema = z . object ( {
43+ status : z . string ( ) ,
44+ sessionId : z . string ( ) . nullable ( ) . optional ( ) ,
45+ output : z . unknown ( ) . optional ( ) ,
46+ steps : z . array ( taskStepSchema ) . optional ( ) ,
47+ } )
48+
49+ const createTaskResponseSchema = z . object ( {
50+ id : z . string ( ) . min ( 1 ) ,
51+ sessionId : z . string ( ) . nullable ( ) . optional ( ) ,
52+ } )
53+
54+ const shareResponseSchema = z . object ( {
55+ shareUrl : z . string ( ) . nullable ( ) . optional ( ) ,
56+ } )
57+
58+ interface BrowserUseTaskRequest {
59+ task : string
60+ sessionId ?: string
61+ llm ?: string
62+ startUrl ?: string
63+ maxSteps ?: number
64+ structuredOutput ?: string
65+ flashMode ?: boolean
66+ thinking ?: boolean
67+ vision ?: boolean | 'auto'
68+ systemPromptExtension ?: string
69+ highlightElements ?: boolean
70+ allowedDomains ?: string [ ]
71+ secrets ?: Record < string , unknown >
72+ metadata ?: Record < string , string >
73+ }
74+
1475async function createSessionWithProfile (
1576 profileId : string ,
1677 apiKey : string
@@ -33,12 +94,17 @@ async function createSessionWithProfile(
3394 return { error : `Failed to create session with profile: ${ response . statusText } ` }
3495 }
3596
36- const data = ( await response . json ( ) ) as { id : string }
97+ const parsed = createSessionResponseSchema . safeParse ( await response . json ( ) )
98+ if ( ! parsed . success ) {
99+ logger . error ( 'BrowserUse returned an invalid create-session response' )
100+ return { error : 'BrowserUse returned an invalid create-session response' }
101+ }
102+ const data = parsed . data
37103 logger . info ( `Created session ${ data . id } with profile ${ profileId } ` )
38104 return { sessionId : data . id }
39- } catch ( error : any ) {
105+ } catch ( error : unknown ) {
40106 logger . error ( 'Error creating session with profile:' , error )
41- return { error : `Error creating session: ${ error . message } ` }
107+ return { error : `Error creating session: ${ getErrorMessage ( error , 'Unknown error' ) } ` }
42108 }
43109}
44110
@@ -58,7 +124,7 @@ async function stopSession(sessionId: string, apiKey: string): Promise<void> {
58124 } else {
59125 logger . warn ( `Failed to stop session ${ sessionId } : ${ response . statusText } ` )
60126 }
61- } catch ( error : any ) {
127+ } catch ( error : unknown ) {
62128 logger . warn ( `Error stopping session ${ sessionId } :` , error )
63129 }
64130}
@@ -75,27 +141,38 @@ async function fetchSessionLiveUrl(
75141 if ( ! response . ok ) {
76142 return { liveUrl : null , publicShareUrl : null }
77143 }
78- const data = ( await response . json ( ) ) as { liveUrl ?: string ; publicShareUrl ?: string }
144+ const parsed = sessionDetailsResponseSchema . safeParse ( await response . json ( ) )
145+ if ( ! parsed . success ) {
146+ logger . warn ( `BrowserUse returned an invalid session response for ${ sessionId } ` )
147+ return { liveUrl : null , publicShareUrl : null }
148+ }
149+ const data = parsed . data
79150 return {
80151 liveUrl : data . liveUrl ?? null ,
81152 publicShareUrl : data . publicShareUrl ?? null ,
82153 }
83- } catch ( error : any ) {
154+ } catch ( error : unknown ) {
84155 logger . warn ( `Error fetching session ${ sessionId } :` , error )
85156 return { liveUrl : null , publicShareUrl : null }
86157 }
87158}
88159
89- function normalizeSecrets ( variables : BrowserUseRunTaskParams [ 'variables' ] ) : Record < string , string > {
90- const secrets : Record < string , string > = { }
160+ function normalizeSecrets (
161+ variables : BrowserUseRunTaskParams [ 'variables' ]
162+ ) : Record < string , unknown > {
163+ const secrets : Record < string , unknown > = { }
91164 if ( ! variables ) return secrets
92165
93166 if ( Array . isArray ( variables ) ) {
94- for ( const row of variables as Array < Record < string , any > > ) {
95- if ( row ?. cells ?. Key && row . cells . Value !== undefined ) {
96- secrets [ row . cells . Key ] = row . cells . Value
97- } else if ( row ?. Key && row . Value !== undefined ) {
98- secrets [ row . Key ] = row . Value
167+ for ( const row of variables ) {
168+ const cells =
169+ typeof row . cells === 'object' && row . cells !== null
170+ ? ( row . cells as Record < string , unknown > )
171+ : undefined
172+ const key = cells ?. Key ?? row . Key
173+ const value = cells ?. Value ?? row . Value
174+ if ( key && value !== undefined ) {
175+ secrets [ String ( key ) ] = value
99176 }
100177 }
101178 } else if ( typeof variables === 'object' ) {
@@ -120,8 +197,8 @@ function parseAllowedDomains(input?: string | string[]): string[] | undefined {
120197function buildRequestBody (
121198 params : BrowserUseRunTaskParams ,
122199 sessionId ?: string
123- ) : Record < string , any > {
124- const body : Record < string , any > = { task : params . task }
200+ ) : BrowserUseTaskRequest {
201+ const body : BrowserUseTaskRequest = { task : params . task }
125202
126203 if ( sessionId ) body . sessionId = sessionId
127204 if ( params . model ) body . llm = params . model
@@ -154,7 +231,9 @@ function buildRequestBody(
154231async function fetchTaskStatus (
155232 taskId : string ,
156233 apiKey : string
157- ) : Promise < { ok : true ; data : any } | { ok : false ; error : string } > {
234+ ) : Promise <
235+ { ok : true ; data : z . infer < typeof taskStatusResponseSchema > } | { ok : false ; error : string }
236+ > {
158237 try {
159238 const response = await fetch ( `${ API_BASE } /tasks/${ taskId } ` , {
160239 method : 'GET' ,
@@ -165,16 +244,20 @@ async function fetchTaskStatus(
165244 return { ok : false , error : `HTTP ${ response . status } : ${ response . statusText } ` }
166245 }
167246
168- return { ok : true , data : await response . json ( ) }
169- } catch ( error : any ) {
170- return { ok : false , error : error . message || 'Network error' }
247+ const parsed = taskStatusResponseSchema . safeParse ( await response . json ( ) )
248+ if ( ! parsed . success ) {
249+ return { ok : false , error : 'BrowserUse returned an invalid task-status response' }
250+ }
251+ return { ok : true , data : parsed . data }
252+ } catch ( error : unknown ) {
253+ return { ok : false , error : getErrorMessage ( error , 'Network error' ) }
171254 }
172255}
173256
174257interface PollResult {
175258 success : boolean
176- output : any
177- steps : any [ ]
259+ output : unknown
260+ steps : BrowserUseTaskStep [ ]
178261 sessionId : string | null
179262 liveUrl : string | null
180263 publicShareUrl : string | null
@@ -233,7 +316,7 @@ async function pollForCompletion(taskId: string, apiKey: string): Promise<PollRe
233316 return {
234317 success : status === 'finished' ,
235318 output : taskData . output ?? null ,
236- steps : taskData . steps || [ ] ,
319+ steps : taskData . steps ?? [ ] ,
237320 sessionId,
238321 liveUrl,
239322 publicShareUrl,
@@ -248,7 +331,7 @@ async function pollForCompletion(taskId: string, apiKey: string): Promise<PollRe
248331 return {
249332 success : finalResult . data . status === 'finished' ,
250333 output : finalResult . data . output ?? null ,
251- steps : finalResult . data . steps || [ ] ,
334+ steps : finalResult . data . steps ?? [ ] ,
252335 sessionId : finalResult . data . sessionId ?? sessionId ,
253336 liveUrl,
254337 publicShareUrl,
@@ -281,9 +364,13 @@ async function createShareUrl(sessionId: string, apiKey: string): Promise<string
281364 return null
282365 }
283366
284- const data = ( await response . json ( ) ) as { shareUrl ?: string ; shareToken ?: string }
285- return data . shareUrl ?? null
286- } catch ( error : any ) {
367+ const parsed = shareResponseSchema . safeParse ( await response . json ( ) )
368+ if ( ! parsed . success ) {
369+ logger . warn ( `BrowserUse returned an invalid share response for session ${ sessionId } ` )
370+ return null
371+ }
372+ return parsed . data . shareUrl ?? null
373+ } catch ( error : unknown ) {
287374 logger . warn ( `Error creating share URL for session ${ sessionId } :` , error )
288375 return null
289376 }
@@ -338,7 +425,16 @@ export const executeRunTaskOperation: InternalToolOperationImplementation<
338425 }
339426 }
340427
341- const data = ( await response . json ( ) ) as { id : string ; sessionId ?: string }
428+ const parsed = createTaskResponseSchema . safeParse ( await response . json ( ) )
429+ if ( ! parsed . success ) {
430+ logger . error ( 'BrowserUse returned an invalid create-task response' )
431+ return {
432+ success : false ,
433+ output : emptyOutput ( ) ,
434+ error : 'BrowserUse returned an invalid create-task response' ,
435+ }
436+ }
437+ const data = parsed . data
342438 const taskId = data . id
343439 const initialSessionId = sessionId ?? data . sessionId ?? null
344440 logger . info ( `Created BrowserUse task ${ taskId } ` , { sessionId : initialSessionId } )
@@ -367,15 +463,15 @@ export const executeRunTaskOperation: InternalToolOperationImplementation<
367463 } ,
368464 error : result . error ,
369465 }
370- } catch ( error : any ) {
466+ } catch ( error : unknown ) {
371467 logger . error ( 'Error creating BrowserUse task:' , error )
372468 if ( sessionId ) {
373469 await stopSession ( sessionId , params . apiKey )
374470 }
375471 return {
376472 success : false ,
377473 output : emptyOutput ( ) ,
378- error : `Error creating task: ${ error . message } ` ,
474+ error : `Error creating task: ${ getErrorMessage ( error , 'Unknown error' ) } ` ,
379475 }
380476 }
381477}
0 commit comments