126 lines
3.5 KiB
Python
126 lines
3.5 KiB
Python
"""
|
|
JavaScript Runtime
|
|
|
|
Node.js stdio protocol runtime for JavaScript tools.
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import shutil
|
|
from pathlib import Path
|
|
from typing import Any, Dict, Optional
|
|
|
|
from tools.runtime.base import BaseRuntime
|
|
|
|
|
|
class JavaScriptRuntime(BaseRuntime):
|
|
"""JavaScript runtime using Node.js stdio protocol"""
|
|
|
|
def __init__(self, node_path: Optional[str] = None):
|
|
self.node_path = node_path or self._detect_node()
|
|
self._validated: bool = False
|
|
|
|
def get_name(self) -> str:
|
|
return "javascript"
|
|
|
|
def _detect_node(self) -> str:
|
|
"""Detect Node.js executable path"""
|
|
node = shutil.which("node")
|
|
if node:
|
|
return node
|
|
# Fallback for Windows
|
|
return "node"
|
|
|
|
async def validate(self, entry: str) -> bool:
|
|
"""Validate Node.js runtime and entry file"""
|
|
# Check node is available
|
|
try:
|
|
result = await asyncio.create_subprocess_exec(
|
|
self.node_path,
|
|
"--version",
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE,
|
|
)
|
|
await result.wait()
|
|
if result.returncode != 0:
|
|
return False
|
|
except Exception:
|
|
return False
|
|
|
|
# Check entry file exists
|
|
path = Path(entry)
|
|
if not path.exists():
|
|
return False
|
|
|
|
self._validated = True
|
|
return True
|
|
|
|
async def execute(
|
|
self,
|
|
entry: str,
|
|
command: str,
|
|
parameters: Dict[str, Any],
|
|
timeout: int,
|
|
) -> Dict[str, Any]:
|
|
"""Execute a JavaScript tool via stdio protocol"""
|
|
if not self._validated:
|
|
is_valid = await self.validate(entry)
|
|
if not is_valid:
|
|
return {
|
|
"status": "error",
|
|
"error": "JavaScript runtime not available or entry file invalid",
|
|
}
|
|
|
|
# Build input data per stdio protocol
|
|
input_data = {
|
|
"command": command,
|
|
"parameters": parameters,
|
|
}
|
|
|
|
try:
|
|
process = await asyncio.create_subprocess_exec(
|
|
self.node_path,
|
|
entry,
|
|
stdin=asyncio.subprocess.PIPE,
|
|
stdout=asyncio.subprocess.PIPE,
|
|
stderr=asyncio.subprocess.PIPE,
|
|
)
|
|
|
|
stdout, stderr = await asyncio.wait_for(
|
|
process.communicate(input=json.dumps(input_data).encode()),
|
|
timeout=timeout / 1000,
|
|
)
|
|
|
|
if process.returncode != 0:
|
|
return {
|
|
"status": "error",
|
|
"error": stderr.decode() if stderr else "Unknown error",
|
|
}
|
|
|
|
result_text = stdout.decode()
|
|
if not result_text:
|
|
return {
|
|
"status": "error",
|
|
"error": "Empty response from Node.js runtime",
|
|
}
|
|
|
|
try:
|
|
result = json.loads(result_text)
|
|
return result
|
|
except json.JSONDecodeError:
|
|
return {
|
|
"status": "success",
|
|
"result": result_text,
|
|
}
|
|
|
|
except asyncio.TimeoutError:
|
|
return {
|
|
"status": "error",
|
|
"error": f"Execution timed out after {timeout}ms",
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"status": "error",
|
|
"error": str(e),
|
|
}
|