1
+ # copied and modified from https://github.com/ServiceNow/BrowserGym
2
+ import playwright .sync_api
3
+ from abc import ABC , abstractmethod
4
+ import ast
5
+ import sys
6
+ import os
7
+ import importlib .util
8
+ import logging
9
+ from typing import Any , Callable , Optional , Tuple
10
+ from pathlib import Path
11
+ import ast
12
+ from typing import Any , Callable , Dict , Optional
13
+ import os
14
+ from datetime import datetime
15
+ import sys
16
+ from typing import Any , Callable , Optional
17
+ import importlib .util
18
+
19
+ logger = logging .getLogger (__name__ )
20
+
21
+ def validate_python_syntax (code : str ) -> Tuple [bool , str ]:
22
+ """
23
+ Validate Python code syntax using AST parser.
24
+
25
+ Args:
26
+ code: String containing Python code
27
+
28
+ Returns:
29
+ Tuple of (is_valid, error_message)
30
+ """
31
+ try :
32
+ ast .parse (code )
33
+ return True , ""
34
+ except SyntaxError as e :
35
+ error_msg = f"Syntax error at line { e .lineno } , column { e .offset } : { e .msg } "
36
+ return False , error_msg
37
+ except Exception as e :
38
+ return False , f"Parsing error: { str (e )} "
39
+
40
+
41
+ class AbstractActionSet (ABC ):
42
+ def __init__ (self , strict : bool = False ):
43
+ self .strict = strict
44
+
45
+ @abstractmethod
46
+ def describe (self , with_long_description : bool = True , with_examples : bool = True ) -> str :
47
+ """
48
+ Returns a textual description of this action space.
49
+ """
50
+
51
+ @abstractmethod
52
+ def example_action (self , abstract : bool ) -> str :
53
+ """
54
+ Returns an example action as a string.
55
+ """
56
+
57
+ @abstractmethod
58
+ def to_python_code (self , action ) -> str :
59
+ """
60
+ Converts the given action to browsergym-compatible python code.
61
+
62
+ Args:
63
+ action: the action to convert.
64
+
65
+ Returns:
66
+ Executable python code that performs the action in a browsergym environment.
67
+ """
68
+
69
+
70
+ def save_code_to_file (code : str , log_folder : str ) -> str :
71
+ """Save code to a file and return the file path."""
72
+ timestamp = datetime .now ().strftime ("%Y%m%d_%H%M%S_%f" )
73
+ code_logs_dir = os .path .join (log_folder , "code_logs" )
74
+ os .makedirs (code_logs_dir , exist_ok = True )
75
+ filename = f"code_{ timestamp } .py"
76
+ file_path = os .path .join (code_logs_dir , filename )
77
+
78
+ header = f"""# Generated Code
79
+ # Timestamp: { datetime .now ().isoformat ()}
80
+ # File: { filename }
81
+ """
82
+
83
+ with open (file_path , 'w' , encoding = 'utf-8' ) as f :
84
+ f .write (header + '\n ' + code )
85
+
86
+ logger .info (f"Saved code to: { file_path } " )
87
+ return file_path
88
+
89
+
90
+ def execute_python_code_safely (
91
+ code : str ,
92
+ page : 'playwright.sync_api.Page' ,
93
+ context : Any ,
94
+ log_folder : str ,
95
+ send_message_to_user : Optional [Callable [[str ], None ]] = None ,
96
+ report_infeasible_instructions : Optional [Callable [[str ], None ]] = None
97
+ ) -> str :
98
+ """Execute Python code from file with provided context."""
99
+
100
+ # Save the code to a file
101
+ file_path = save_code_to_file (code , log_folder )
102
+
103
+ try :
104
+ # Add the code directory to Python path
105
+ sys .path .insert (0 , os .path .dirname (file_path ))
106
+
107
+ # Import the module using importlib
108
+ spec = importlib .util .spec_from_file_location ("generated_code" , file_path )
109
+ if spec is None or spec .loader is None :
110
+ raise ImportError (f"Could not load spec for { file_path } " )
111
+
112
+ module = importlib .util .module_from_spec (spec )
113
+
114
+ # Set the global variables in the module
115
+ module .page = page
116
+ module .context = context
117
+ module .send_message_to_user = send_message_to_user
118
+ module .report_infeasible_instructions = report_infeasible_instructions
119
+
120
+ # Execute the module
121
+ spec .loader .exec_module (module )
122
+
123
+ except Exception as e :
124
+ logger .error (f"Error executing code: { e } " )
125
+ raise
126
+
127
+ finally :
128
+ # Remove the directory from sys.path
129
+ if os .path .dirname (file_path ) in sys .path :
130
+ sys .path .remove (os .path .dirname (file_path ))
131
+
132
+ return file_path
0 commit comments