Spaces:
Runtime error
Runtime error
| import esprima | |
| from typing import Any, Callable, Dict, Optional | |
| import types | |
| import ast | |
| import inspect | |
| from smolagents.tools import Tool | |
| class JavaScriptMethodDispatcher(Tool): | |
| name = "javascript_as_python_caller" | |
| description = "turns java script code blocks into callable python functions" | |
| inputs = {'jsContent':{'type':'string', 'description':'content of a regular java script code block of a function definition'}} | |
| output_type = "callable" | |
| def __init__(self): | |
| self._method_cache: Dict[str, Callable] = {} | |
| def register_js_method(self, js_code: str, method_name: Optional[str] = None) -> Callable: | |
| """ | |
| Convert JavaScript code to a Python callable and register it in the dispatcher. | |
| Args: | |
| js_code (str): JavaScript code containing the function | |
| method_name (str, optional): Name to register the method under. | |
| If None, tries to extract from the JS code. | |
| Returns: | |
| Callable: Python function that can be called directly | |
| """ | |
| try: | |
| # Parse JavaScript code | |
| parsed = esprima.parseScript(js_code) | |
| # Find the function declaration | |
| func_decl = None | |
| for node in parsed.body: | |
| if node.type == 'FunctionDeclaration': | |
| func_decl = node | |
| break | |
| if not func_decl: | |
| raise ValueError("No function declaration found in JavaScript code") | |
| # Get the method name | |
| if method_name is None: | |
| method_name = func_decl.id.name | |
| # Convert JavaScript parameters to Python | |
| params = [param.name for param in func_decl.params] | |
| # Convert the function body | |
| body_lines = self._convert_js_body(func_decl.body) | |
| # Create the Python function source | |
| py_source = f"def {method_name}({', '.join(params)}):\n" | |
| py_source += "\n".join(f" {line}" for line in body_lines) | |
| # Create function namespace | |
| namespace = { | |
| 'print': print, # Common built-ins | |
| 'str': str, | |
| 'int': int, | |
| 'float': float, | |
| 'bool': bool, | |
| 'list': list, | |
| 'dict': dict, | |
| 'None': None, | |
| 'True': True, | |
| 'False': False | |
| } | |
| # Compile and execute the Python code | |
| compiled_code = compile(py_source, '<string>', 'exec') | |
| exec(compiled_code, namespace) | |
| # Get the compiled function | |
| py_func = namespace[method_name] | |
| # Store in cache | |
| self._method_cache[method_name] = py_func | |
| return py_func | |
| except Exception as e: | |
| raise ValueError(f"Failed to convert JavaScript method: {str(e)}") | |
| def get_method(self, method_name: str) -> Optional[Callable]: | |
| """ | |
| Get a registered method by name. | |
| Args: | |
| method_name (str): Name of the registered method | |
| Returns: | |
| Optional[Callable]: The registered Python function, or None if not found | |
| """ | |
| return self._method_cache.get(method_name) | |
| def call_method(self, method_name: str, *args, **kwargs) -> Any: | |
| """ | |
| Call a registered method by name with given arguments. | |
| Args: | |
| method_name (str): Name of the registered method | |
| *args: Positional arguments to pass to the method | |
| **kwargs: Keyword arguments to pass to the method | |
| Returns: | |
| Any: Result of the method call | |
| Raises: | |
| ValueError: If method is not found or call fails | |
| """ | |
| method = self.get_method(method_name) | |
| if method is None: | |
| raise ValueError(f"Method '{method_name}' not found") | |
| try: | |
| return method(*args, **kwargs) | |
| except Exception as e: | |
| raise ValueError(f"Failed to call method '{method_name}': {str(e)}") | |
| def _convert_js_body(self, body_node: Any) -> list[str]: | |
| """Convert JavaScript function body to Python code lines.""" | |
| # This is a simplified conversion - you'd want to expand this | |
| # based on your specific needs | |
| lines = [] | |
| # Handle different types of statements | |
| if body_node.type == 'BlockStatement': | |
| for statement in body_node.body: | |
| if statement.type == 'ReturnStatement': | |
| return_value = self._convert_js_expression(statement.argument) | |
| lines.append(f"return {return_value}") | |
| elif statement.type == 'ExpressionStatement': | |
| expr = self._convert_js_expression(statement.expression) | |
| lines.append(expr) | |
| elif statement.type == 'IfStatement': | |
| condition = self._convert_js_expression(statement.test) | |
| lines.append(f"if {condition}:") | |
| then_lines = self._convert_js_body(statement.consequent) | |
| lines.extend(f" {line}" for line in then_lines) | |
| if statement.alternate: | |
| lines.append("else:") | |
| else_lines = self._convert_js_body(statement.alternate) | |
| lines.extend(f" {line}" for line in else_lines) | |
| return lines | |
| def _convert_js_expression(self, node: Any) -> str: | |
| """Convert JavaScript expression to Python code string.""" | |
| if node.type == 'BinaryExpression': | |
| left = self._convert_js_expression(node.left) | |
| right = self._convert_js_expression(node.right) | |
| op = node.operator | |
| # Convert JavaScript operators to Python | |
| op_map = { | |
| '===': '==', | |
| '!==': '!=', | |
| '&&': 'and', | |
| '||': 'or' | |
| } | |
| op = op_map.get(op, op) | |
| return f"({left} {op} {right})" | |
| elif node.type == 'Literal': | |
| if isinstance(node.value, str): | |
| return f"'{node.value}'" | |
| return str(node.value) | |
| elif node.type == 'Identifier': | |
| # Handle JavaScript built-ins | |
| js_to_py = { | |
| 'undefined': 'None', | |
| 'null': 'None', | |
| 'true': 'True', | |
| 'false': 'False' | |
| } | |
| return js_to_py.get(node.name, node.name) | |
| elif node.type == 'CallExpression': | |
| func = self._convert_js_expression(node.callee) | |
| args = [self._convert_js_expression(arg) for arg in node.arguments] | |
| # Handle special cases | |
| if func == 'console.log': | |
| func = 'print' | |
| return f"{func}({', '.join(args)})" | |
| raise ValueError(f"Unsupported expression type: {node.type}") | |
| def forward(self, jsContent: str) -> callable: | |
| return self.register_js_method(jsContent) | |