1- import { ObserveResult , Page } from "@browserbasehq/stagehand" ;
1+ import type {
2+ Action ,
3+ ObserveResult ,
4+ Page ,
5+ Stagehand ,
6+ } from "@browserbasehq/stagehand" ;
27import boxen from "boxen" ;
38import chalk from "chalk" ;
49import fs from "fs/promises" ;
@@ -10,15 +15,10 @@ export function announce(message: string, title?: string) {
1015 padding : 1 ,
1116 margin : 3 ,
1217 title : title || "Stagehand" ,
13- } )
18+ } ) ,
1419 ) ;
1520}
1621
17- /**
18- * Get an environment variable and throw an error if it's not found
19- * @param name - The name of the environment variable
20- * @returns The value of the environment variable
21- */
2222export function getEnvVar ( name : string , required = true ) : string | undefined {
2323 const value = process . env [ name ] ;
2424 if ( ! value && required ) {
@@ -27,12 +27,6 @@ export function getEnvVar(name: string, required = true): string | undefined {
2727 return value ;
2828}
2929
30- /**
31- * Validate a Zod schema against some data
32- * @param schema - The Zod schema to validate against
33- * @param data - The data to validate
34- * @returns Whether the data is valid against the schema
35- */
3630export function validateZodSchema ( schema : z . ZodTypeAny , data : unknown ) {
3731 try {
3832 schema . parse ( data ) ;
@@ -42,11 +36,9 @@ export function validateZodSchema(schema: z.ZodTypeAny, data: unknown) {
4236 }
4337}
4438
45- export async function drawObserveOverlay ( page : Page , results : ObserveResult [ ] ) {
46- // Convert single xpath to array for consistent handling
47- const xpathList = results . map ( ( result ) => result . selector ) ;
39+ export async function drawObserveOverlay ( page : Page , results : ObserveResult ) {
40+ const xpathList = results . map ( ( result : Action ) => result . selector ) ;
4841
49- // Filter out empty xpaths
5042 const validXpaths = xpathList . filter ( ( xpath ) => xpath !== "xpath=" ) ;
5143
5244 await page . evaluate ( ( selectors ) => {
@@ -59,7 +51,7 @@ export async function drawObserveOverlay(page: Page, results: ObserveResult[]) {
5951 document ,
6052 null ,
6153 XPathResult . FIRST_ORDERED_NODE_TYPE ,
62- null
54+ null ,
6355 ) . singleNodeValue ;
6456 } else {
6557 element = document . querySelector ( selector ) ;
@@ -84,7 +76,6 @@ export async function drawObserveOverlay(page: Page, results: ObserveResult[]) {
8476}
8577
8678export async function clearOverlays ( page : Page ) {
87- // remove existing stagehandObserve attributes
8879 await page . evaluate ( ( ) => {
8980 const elements = document . querySelectorAll ( '[stagehandObserve="true"]' ) ;
9081 elements . forEach ( ( el ) => {
@@ -97,39 +88,55 @@ export async function clearOverlays(page: Page) {
9788 } ) ;
9889}
9990
100- export async function simpleCache (
101- instruction : string ,
102- actionToCache : ObserveResult
103- ) {
104- // Save action to cache.json
91+ export async function simpleCache ( instruction : string , actionToCache : Action ) {
10592 try {
106- // Read existing cache if it exists
107- let cache : Record < string , ObserveResult > = { } ;
93+ let cache : Record < string , Action > = { } ;
10894 try {
10995 const existingCache = await fs . readFile ( "cache.json" , "utf-8" ) ;
11096 cache = JSON . parse ( existingCache ) ;
111- } catch ( error ) {
112- // File doesn't exist yet, use empty cache
97+ } catch {
98+ // no file yet
11399 }
114100
115- // Add new action to cache
116101 cache [ instruction ] = actionToCache ;
117102
118- // Write updated cache to file
119103 await fs . writeFile ( "cache.json" , JSON . stringify ( cache , null , 2 ) ) ;
120104 } catch ( error ) {
121105 console . error ( chalk . red ( "Failed to save to cache:" ) , error ) ;
122106 }
123107}
124108
125- export async function readCache (
126- instruction : string
127- ) : Promise < ObserveResult | null > {
109+ export async function readCache ( instruction : string ) : Promise < Action | null > {
128110 try {
129111 const existingCache = await fs . readFile ( "cache.json" , "utf-8" ) ;
130- const cache : Record < string , ObserveResult > = JSON . parse ( existingCache ) ;
112+ const cache : Record < string , Action > = JSON . parse ( existingCache ) ;
131113 return cache [ instruction ] || null ;
132- } catch ( error ) {
114+ } catch {
133115 return null ;
134116 }
135117}
118+
119+ export async function actWithCache (
120+ stagehand : Stagehand ,
121+ page : Page ,
122+ instruction : string ,
123+ ) : Promise < void > {
124+ const cachedAction = await readCache ( instruction ) ;
125+ if ( cachedAction ) {
126+ console . log ( chalk . blue ( "Using cached action for:" ) , instruction ) ;
127+ await stagehand . act ( cachedAction ) ;
128+ return ;
129+ }
130+
131+ const results = await stagehand . observe ( instruction ) ;
132+ console . log ( chalk . blue ( "Got results:" ) , results ) ;
133+
134+ const actionToCache = results [ 0 ] ;
135+ console . log ( chalk . blue ( "Taking cacheable action:" ) , actionToCache ) ;
136+ await simpleCache ( instruction , actionToCache ) ;
137+ await drawObserveOverlay ( page , results ) ;
138+ await page . waitForTimeout ( 1000 ) ;
139+ await clearOverlays ( page ) ;
140+
141+ await stagehand . act ( actionToCache ) ;
142+ }
0 commit comments