from __future__ import annotations import os import sys import time from collections.abc import Mapping from pathlib import Path, PureWindowsPath from .snapshot import LocalSnapshotSpec _DEFAULT_LOCAL_SNAPSHOT_TTL_SECONDS = 60 * 60 * 24 * 30 _DEFAULT_LOCAL_SNAPSHOT_SUBDIR = Path("openai-agents-python") / "sandbox" / "snapshots" def _first_absolute_windows_env_path(env: Mapping[str, str], *names: str) -> Path | None: for name in names: value = env.get(name) if not value: continue if PureWindowsPath(value).is_absolute(): return Path(value) return None def default_local_snapshot_base_dir( *, home: Path | None = None, env: Mapping[str, str] | None = None, platform: str | None = None, os_name: str | None = None, ) -> Path: resolved_home = home or Path.home() resolved_env = env or os.environ resolved_platform = platform or sys.platform resolved_os_name = os_name or os.name if resolved_platform == "darwin": base = resolved_home / "Library" / "Application Support" elif resolved_os_name == "nt": env_base = _first_absolute_windows_env_path( resolved_env, "LOCALAPPDATA", "APPDATA", ) base = env_base if env_base is not None else resolved_home / "AppData" / "Local" else: xdg_state_home = resolved_env.get("XDG_STATE_HOME") base = Path(xdg_state_home) if xdg_state_home else resolved_home / ".local" / "state" return base / _DEFAULT_LOCAL_SNAPSHOT_SUBDIR def cleanup_stale_default_local_snapshots( base_path: Path, *, now: float | None = None, max_age_seconds: int = _DEFAULT_LOCAL_SNAPSHOT_TTL_SECONDS, ) -> None: # This is intentionally limited to stale files in the SDK-managed default directory. # We do not delete snapshots during normal session teardown because pause/resume may still # need them. If we add explicit artifact cleanup later, it should be a separate opt-in path # that can also account for backend-specific remote artifacts. if max_age_seconds < 0 or not base_path.exists(): return cutoff = (time.time() if now is None else now) - max_age_seconds try: candidates = list(base_path.glob("*.tar")) except OSError: return for candidate in candidates: try: if not candidate.is_file(): continue if candidate.stat().st_mtime >= cutoff: continue candidate.unlink(missing_ok=True) except OSError: continue def resolve_default_local_snapshot_spec( *, home: Path | None = None, env: Mapping[str, str] | None = None, platform: str | None = None, os_name: str | None = None, now: float | None = None, ) -> LocalSnapshotSpec: base_path = default_local_snapshot_base_dir( home=home, env=env, platform=platform, os_name=os_name, ) base_path.mkdir(parents=True, exist_ok=True, mode=0o700) if (os_name or os.name) != "nt": try: base_path.chmod(0o700) except OSError: pass return LocalSnapshotSpec(base_path=base_path)