Spaces:
Paused
Paused
| """Managed Modal environment backed by tool-gateway.""" | |
| from __future__ import annotations | |
| import json | |
| import logging | |
| import os | |
| import requests | |
| import uuid | |
| from dataclasses import dataclass | |
| from typing import Any, Dict, Optional | |
| from tools.environments.modal_utils import ( | |
| BaseModalExecutionEnvironment, | |
| ModalExecStart, | |
| PreparedModalExec, | |
| ) | |
| from tools.managed_tool_gateway import resolve_managed_tool_gateway | |
| logger = logging.getLogger(__name__) | |
| def _request_timeout_env(name: str, default: float) -> float: | |
| try: | |
| value = float(os.getenv(name, str(default))) | |
| return value if value > 0 else default | |
| except (TypeError, ValueError): | |
| return default | |
| class _ManagedModalExecHandle: | |
| exec_id: str | |
| class ManagedModalEnvironment(BaseModalExecutionEnvironment): | |
| """Gateway-owned Modal sandbox with Hermes-compatible execute/cleanup.""" | |
| _CONNECT_TIMEOUT_SECONDS = _request_timeout_env("TERMINAL_MANAGED_MODAL_CONNECT_TIMEOUT_SECONDS", 1.0) | |
| _POLL_READ_TIMEOUT_SECONDS = _request_timeout_env("TERMINAL_MANAGED_MODAL_POLL_READ_TIMEOUT_SECONDS", 5.0) | |
| _CANCEL_READ_TIMEOUT_SECONDS = _request_timeout_env("TERMINAL_MANAGED_MODAL_CANCEL_READ_TIMEOUT_SECONDS", 5.0) | |
| _client_timeout_grace_seconds = 10.0 | |
| _interrupt_output = "[Command interrupted - Modal sandbox exec cancelled]" | |
| _unexpected_error_prefix = "Managed Modal exec failed" | |
| def __init__( | |
| self, | |
| image: str, | |
| cwd: str = "/root", | |
| timeout: int = 60, | |
| modal_sandbox_kwargs: Optional[Dict[str, Any]] = None, | |
| persistent_filesystem: bool = True, | |
| task_id: str = "default", | |
| ): | |
| super().__init__(cwd=cwd, timeout=timeout) | |
| self._guard_unsupported_credential_passthrough() | |
| gateway = resolve_managed_tool_gateway("modal") | |
| if gateway is None: | |
| raise ValueError("Managed Modal requires a configured tool gateway and Nous user token") | |
| self._gateway_origin = gateway.gateway_origin.rstrip("/") | |
| self._nous_user_token = gateway.nous_user_token | |
| self._task_id = task_id | |
| self._persistent = persistent_filesystem | |
| self._image = image | |
| self._sandbox_kwargs = dict(modal_sandbox_kwargs or {}) | |
| self._create_idempotency_key = str(uuid.uuid4()) | |
| self._sandbox_id = self._create_sandbox() | |
| def _start_modal_exec(self, prepared: PreparedModalExec) -> ModalExecStart: | |
| exec_id = str(uuid.uuid4()) | |
| payload: Dict[str, Any] = { | |
| "execId": exec_id, | |
| "command": prepared.command, | |
| "cwd": prepared.cwd, | |
| "timeoutMs": int(prepared.timeout * 1000), | |
| } | |
| if prepared.stdin_data is not None: | |
| payload["stdinData"] = prepared.stdin_data | |
| try: | |
| response = self._request( | |
| "POST", | |
| f"/v1/sandboxes/{self._sandbox_id}/execs", | |
| json=payload, | |
| timeout=10, | |
| ) | |
| except Exception as exc: | |
| return ModalExecStart( | |
| immediate_result=self._error_result(f"Managed Modal exec failed: {exc}") | |
| ) | |
| if response.status_code >= 400: | |
| return ModalExecStart( | |
| immediate_result=self._error_result( | |
| self._format_error("Managed Modal exec failed", response) | |
| ) | |
| ) | |
| body = response.json() | |
| status = body.get("status") | |
| if status in {"completed", "failed", "cancelled", "timeout"}: | |
| return ModalExecStart( | |
| immediate_result=self._result( | |
| body.get("output", ""), | |
| body.get("returncode", 1), | |
| ) | |
| ) | |
| if body.get("execId") != exec_id: | |
| return ModalExecStart( | |
| immediate_result=self._error_result( | |
| "Managed Modal exec start did not return the expected exec id" | |
| ) | |
| ) | |
| return ModalExecStart(handle=_ManagedModalExecHandle(exec_id=exec_id)) | |
| def _poll_modal_exec(self, handle: _ManagedModalExecHandle) -> dict | None: | |
| try: | |
| status_response = self._request( | |
| "GET", | |
| f"/v1/sandboxes/{self._sandbox_id}/execs/{handle.exec_id}", | |
| timeout=(self._CONNECT_TIMEOUT_SECONDS, self._POLL_READ_TIMEOUT_SECONDS), | |
| ) | |
| except Exception as exc: | |
| return self._error_result(f"Managed Modal exec poll failed: {exc}") | |
| if status_response.status_code == 404: | |
| return self._error_result("Managed Modal exec not found") | |
| if status_response.status_code >= 400: | |
| return self._error_result( | |
| self._format_error("Managed Modal exec poll failed", status_response) | |
| ) | |
| status_body = status_response.json() | |
| status = status_body.get("status") | |
| if status in {"completed", "failed", "cancelled", "timeout"}: | |
| return self._result( | |
| status_body.get("output", ""), | |
| status_body.get("returncode", 1), | |
| ) | |
| return None | |
| def _cancel_modal_exec(self, handle: _ManagedModalExecHandle) -> None: | |
| self._cancel_exec(handle.exec_id) | |
| def _timeout_result_for_modal(self, timeout: int) -> dict: | |
| return self._result(f"Managed Modal exec timed out after {timeout}s", 124) | |
| def cleanup(self): | |
| if not getattr(self, "_sandbox_id", None): | |
| return | |
| try: | |
| self._request( | |
| "POST", | |
| f"/v1/sandboxes/{self._sandbox_id}/terminate", | |
| json={ | |
| "snapshotBeforeTerminate": self._persistent, | |
| }, | |
| timeout=60, | |
| ) | |
| except Exception as exc: | |
| logger.warning("Managed Modal cleanup failed: %s", exc) | |
| finally: | |
| self._sandbox_id = None | |
| def _create_sandbox(self) -> str: | |
| cpu = self._coerce_number(self._sandbox_kwargs.get("cpu"), 1) | |
| memory = self._coerce_number( | |
| self._sandbox_kwargs.get("memoryMiB", self._sandbox_kwargs.get("memory")), | |
| 5120, | |
| ) | |
| disk = self._coerce_number( | |
| self._sandbox_kwargs.get("ephemeral_disk", self._sandbox_kwargs.get("diskMiB")), | |
| None, | |
| ) | |
| create_payload = { | |
| "image": self._image, | |
| "cwd": self.cwd, | |
| "cpu": cpu, | |
| "memoryMiB": memory, | |
| "timeoutMs": 3_600_000, | |
| "idleTimeoutMs": max(300_000, int(self.timeout * 1000)), | |
| "persistentFilesystem": self._persistent, | |
| "logicalKey": self._task_id, | |
| } | |
| if disk is not None: | |
| create_payload["diskMiB"] = disk | |
| response = self._request( | |
| "POST", | |
| "/v1/sandboxes", | |
| json=create_payload, | |
| timeout=60, | |
| extra_headers={ | |
| "x-idempotency-key": self._create_idempotency_key, | |
| }, | |
| ) | |
| if response.status_code >= 400: | |
| raise RuntimeError(self._format_error("Managed Modal create failed", response)) | |
| body = response.json() | |
| sandbox_id = body.get("id") | |
| if not isinstance(sandbox_id, str) or not sandbox_id: | |
| raise RuntimeError("Managed Modal create did not return a sandbox id") | |
| return sandbox_id | |
| def _guard_unsupported_credential_passthrough(self) -> None: | |
| """Managed Modal does not sync or mount host credential files.""" | |
| try: | |
| from tools.credential_files import get_credential_file_mounts | |
| except Exception: | |
| return | |
| mounts = get_credential_file_mounts() | |
| if mounts: | |
| raise ValueError( | |
| "Managed Modal does not support host credential-file passthrough. " | |
| "Use TERMINAL_MODAL_MODE=direct when skills or config require " | |
| "credential files inside the sandbox." | |
| ) | |
| def _request(self, method: str, path: str, *, | |
| json: Dict[str, Any] | None = None, | |
| timeout: int = 30, | |
| extra_headers: Dict[str, str] | None = None) -> requests.Response: | |
| headers = { | |
| "Authorization": f"Bearer {self._nous_user_token}", | |
| "Content-Type": "application/json", | |
| } | |
| if extra_headers: | |
| headers.update(extra_headers) | |
| return requests.request( | |
| method, | |
| f"{self._gateway_origin}{path}", | |
| headers=headers, | |
| json=json, | |
| timeout=timeout, | |
| ) | |
| def _cancel_exec(self, exec_id: str) -> None: | |
| try: | |
| self._request( | |
| "POST", | |
| f"/v1/sandboxes/{self._sandbox_id}/execs/{exec_id}/cancel", | |
| timeout=(self._CONNECT_TIMEOUT_SECONDS, self._CANCEL_READ_TIMEOUT_SECONDS), | |
| ) | |
| except Exception as exc: | |
| logger.warning("Managed Modal exec cancel failed: %s", exc) | |
| def _coerce_number(value: Any, default: float) -> float: | |
| try: | |
| if value is None: | |
| return default | |
| return float(value) | |
| except (TypeError, ValueError): | |
| return default | |
| def _format_error(prefix: str, response: requests.Response) -> str: | |
| try: | |
| payload = response.json() | |
| if isinstance(payload, dict): | |
| message = payload.get("error") or payload.get("message") or payload.get("code") | |
| if isinstance(message, str) and message: | |
| return f"{prefix}: {message}" | |
| return f"{prefix}: {json.dumps(payload, ensure_ascii=False)}" | |
| except Exception: | |
| pass | |
| text = response.text.strip() | |
| if text: | |
| return f"{prefix}: {text}" | |
| return f"{prefix}: HTTP {response.status_code}" | |