Frontend (items 1-10):
- WebSocket streaming integration with useWebSocket hook
- Admin Dashboard UI (status, voices, agents, governance tabs)
- Voice playback UI (TTS/STT integration)
- Settings/Preferences page (conversation style, sliders)
- Responsive/mobile layout (breakpoints at 480px, 768px)
- Dark/light theme with CSS variables and localStorage
- Error handling & loading states (retry, empty state, disabled input)
- Authentication UI (login page, Bearer token, logout)
- Head visualization improvements (active/speaking states, animations)
- Consequence/Ethics dashboard (lessons, consequences, insights tabs)
Backend stubs (items 11-21):
- Tool connectors: DocsConnector (text/md/PDF), DBConnector (SQLite/Postgres), CodeRunnerConnector (Python/JS/Bash/Ruby sandboxed)
- STT adapter: WhisperSTTAdapter, AzureSTTAdapter
- Multi-modal interface adapters: Visual, Haptic, Gesture, Biometric
- SSE streaming endpoint (/v1/sessions/{id}/stream/sse)
- Multi-tenant support (X-Tenant-ID header, tenant CRUD)
- Plugin marketplace/registry (register, install, list)
- Backup/restore endpoints
- Versioned API negotiation (Accept-Version header, deprecation)
Infrastructure (items 22-26):
- docker-compose.yml (API + Postgres + Redis + frontend)
- .env.example with all configurable vars
- gunicorn.conf.py production ASGI config
- Prometheus metrics collector and /metrics endpoint
- Structured JSON logging configuration
Documentation (items 27-29):
- Architecture docs with module layout and subsystem descriptions
- Quickstart guide with setup, API tour, and test instructions
Tests (items 30-32):
- Integration tests: 25 end-to-end API tests
- Frontend tests: 10 Vitest tests for hooks (useTheme, useAuth)
- Load/performance tests: latency and throughput benchmarks
- Connector tests: 16 tests for Docs, DB, CodeRunner
- Multi-modal adapter tests: 9 tests
- Metrics collector tests: 5 tests
- STT adapter tests: 2 tests
511 Python tests passing, 10 frontend tests passing, 0 ruff errors.
Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
109 lines
3.7 KiB
Python
109 lines
3.7 KiB
Python
"""Code runner connector: execute code in a sandboxed subprocess."""
|
|
|
|
import subprocess
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from fusionagi._logger import logger
|
|
from fusionagi.tools.connectors.base import BaseConnector
|
|
|
|
SUPPORTED_LANGUAGES = {
|
|
"python": {"ext": ".py", "cmd": ["python3"]},
|
|
"javascript": {"ext": ".js", "cmd": ["node"]},
|
|
"bash": {"ext": ".sh", "cmd": ["bash"]},
|
|
"ruby": {"ext": ".rb", "cmd": ["ruby"]},
|
|
}
|
|
|
|
|
|
class CodeRunnerConnector(BaseConnector):
|
|
"""Execute code snippets in sandboxed subprocesses.
|
|
|
|
Supports Python, JavaScript (Node), Bash, and Ruby.
|
|
Execution is timeout-bounded (default 30s) and captures stdout/stderr.
|
|
"""
|
|
|
|
name = "code_runner"
|
|
|
|
def __init__(self, timeout: float = 30.0, max_output: int = 10000) -> None:
|
|
self._timeout = timeout
|
|
self._max_output = max_output
|
|
|
|
def invoke(self, action: str, params: dict[str, Any]) -> Any:
|
|
if action == "run":
|
|
return self._run(
|
|
params.get("code", ""),
|
|
params.get("language", "python"),
|
|
params.get("timeout"),
|
|
)
|
|
if action == "languages":
|
|
return {"languages": list(SUPPORTED_LANGUAGES.keys())}
|
|
return {"error": f"Unknown action: {action}"}
|
|
|
|
def _run(self, code: str, language: str, timeout: float | None = None) -> dict[str, Any]:
|
|
if not code.strip():
|
|
return {"stdout": "", "stderr": "", "exit_code": 0, "error": "Empty code"}
|
|
|
|
lang = language.lower()
|
|
if lang not in SUPPORTED_LANGUAGES:
|
|
return {
|
|
"stdout": "",
|
|
"stderr": "",
|
|
"exit_code": 1,
|
|
"error": f"Unsupported language: {lang}. Supported: {list(SUPPORTED_LANGUAGES.keys())}",
|
|
}
|
|
|
|
spec = SUPPORTED_LANGUAGES[lang]
|
|
effective_timeout = timeout or self._timeout
|
|
|
|
try:
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w", suffix=spec["ext"], delete=False, dir="/tmp"
|
|
) as f:
|
|
f.write(code)
|
|
f.flush()
|
|
script_path = f.name
|
|
|
|
result = subprocess.run(
|
|
[*spec["cmd"], script_path],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=effective_timeout,
|
|
cwd="/tmp",
|
|
)
|
|
|
|
Path(script_path).unlink(missing_ok=True)
|
|
|
|
return {
|
|
"stdout": result.stdout[: self._max_output],
|
|
"stderr": result.stderr[: self._max_output],
|
|
"exit_code": result.returncode,
|
|
"error": None,
|
|
}
|
|
|
|
except subprocess.TimeoutExpired:
|
|
logger.warning("CodeRunner timeout", extra={"language": lang, "timeout": effective_timeout})
|
|
return {
|
|
"stdout": "",
|
|
"stderr": f"Execution timed out after {effective_timeout}s",
|
|
"exit_code": -1,
|
|
"error": "timeout",
|
|
}
|
|
except FileNotFoundError:
|
|
return {
|
|
"stdout": "",
|
|
"stderr": f"Runtime not found for {lang}: {spec['cmd'][0]}",
|
|
"exit_code": -1,
|
|
"error": f"Runtime '{spec['cmd'][0]}' not installed",
|
|
}
|
|
except Exception as e:
|
|
logger.warning("CodeRunner failed", extra={"error": str(e)})
|
|
return {"stdout": "", "stderr": str(e), "exit_code": -1, "error": str(e)}
|
|
|
|
def schema(self) -> dict[str, Any]:
|
|
return {
|
|
"name": self.name,
|
|
"actions": ["run", "languages"],
|
|
"parameters": {"code": "string", "language": "string", "timeout": "number"},
|
|
}
|