feat(tools): Phase T.1-T.4 complete - manifest system, registry, implementations, runtime, collaboration, scheduler
This commit is contained in:
125
backend/app/tools/runtime/js_runtime.py
Normal file
125
backend/app/tools/runtime/js_runtime.py
Normal file
@@ -0,0 +1,125 @@
|
||||
"""
|
||||
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),
|
||||
}
|
||||
Reference in New Issue
Block a user