from __future__ import annotations import httpx, json, re, uuid, asyncio from typing import Any class SandboxClient: def __init__(self, url: str, token: str): self.url = url.rstrip("/") self.token = token self._client = httpx.AsyncClient( timeout=httpx.Timeout(120.0, connect=10.0), headers={"Authorization": f"Bearer {token}"} if token else {}, ) async def health(self) -> bool: try: resp = await self._client.get(f"{self.url}/health", timeout=5.0) return resp.is_success except Exception: return False async def execute(self, code: str, language: str = "python") -> dict: resp = await self._client.post( f"{self.url}/execute", json={"code": code, "language": language}, ) resp.raise_for_status() return resp.json() async def install(self, packages: list[str]) -> dict: resp = await self._client.post( f"{self.url}/install", json={"packages": packages}, timeout=httpx.Timeout(60.0), ) resp.raise_for_status() return resp.json() async def upload_file(self, filename: str, content: str | bytes) -> dict: if isinstance(content, str): content = content.encode() resp = await self._client.post( f"{self.url}/upload", files={"file": (filename, content)}, ) resp.raise_for_status() return resp.json() async def read_file(self, path: str) -> dict: resp = await self._client.get(f"{self.url}/files/{path.lstrip('/')}") resp.raise_for_status() return resp.json() async def restart(self) -> bool: try: resp = await self._client.post(f"{self.url}/restart", timeout=30.0) return resp.is_success except Exception: return False async def close(self): await self._client.aclose() _OUTPUT_PATTERNS: dict[str, re.Pattern] = { "image": re.compile(r"(?:!\[.*?\]\(.*?\)|data:image/\w+;base64,)", re.IGNORECASE), "audio": re.compile(r"data:audio/\w+;base64,", re.IGNORECASE), "dataframe": re.compile(r"]*>.*?", re.DOTALL | re.IGNORECASE), "plot": re.compile(r"(plt\.show|matplotlib|plotly|bokeh|seaborn)", re.IGNORECASE), "error": re.compile(r"(Traceback \(most recent call last\)|Error|Exception)", re.IGNORECASE), } def parse_output(output: str) -> list[dict]: results = [] for kind, pattern in _OUTPUT_PATTERNS.items(): matches = pattern.findall(output) if matches: results.append({"type": kind, "matches": len(matches)}) if not results: results.append({"type": "text", "matches": 1}) return results async def run_code_with_fix_loop( client: SandboxClient, code: str, language: str = "python", max_iterations: int = 3, system_prompt: str = "", ) -> dict: attempt = 0 last_error = "" current_code = code while attempt < max_iterations: try: result = await client.execute(current_code, language=language) except Exception as e: return {"success": False, "error": str(e), "attempts": attempt + 1} stdout = result.get("stdout", "") stderr = result.get("stderr", "") exit_code = result.get("exit_code", result.get("code", 0)) if exit_code == 0: output_types = parse_output(stdout + stderr) return { "success": True, "stdout": stdout, "stderr": stderr, "exit_code": exit_code, "output_types": output_types, "attempts": attempt + 1, } last_error = stderr or stdout attempt += 1 if attempt >= max_iterations: break current_code = _generate_fix_prompt(current_code, last_error, system_prompt) return { "success": False, "stdout": stdout if attempt > 0 else "", "stderr": last_error, "exit_code": exit_code if attempt > 0 else -1, "attempts": attempt, } def _generate_fix_prompt(code: str, error: str, system_prompt: str) -> str: lines = code.split("\n") if system_prompt: comment_char = "#" if not lines or not lines[0].strip().startswith("#") else lines[0].strip()[:1] lines.insert(0, f"{comment_char} Fix attempt for error: {error[:200]}") lines.insert(1, "") return "\n".join(lines)