# Copyright (c) Meta Platforms, Inc. and affiliates. # All rights reserved. # # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. """Build a pre-baked E2B template with opencode + proxy deps already installed. Run-time per rollout drops from ~3 min (cold install) to ~30s once the template is built, because we skip: - ``curl https://opencode.ai/install | bash`` (~30-90s) - ``pip install fastapi uvicorn httpx`` (~30-60s) - directory layout setup - copying the proxy source The template ships: - opencode CLI at ``/home/user/.opencode/bin/opencode`` - Python deps for the in-sandbox proxy - The proxy source at ``/home/user/proxy/interception.py`` - Pre-created dirs: ``~/.config/opencode``, ``~/logs/{agent,verifier}``, ``~/task``, ``~/workdir``, ``~/proxy`` - Default workdir: ``/home/user/workdir`` Usage:: .venv/bin/python envs/opencode_env/tests/build_e2b_template.py # → builds (or rebuilds) ``opencode-rl`` template, prints template id Then ``test_five_sorts_e2e.py`` will use it via ``--template opencode-rl``. Requires ``E2B_API_KEY`` in the environment. First build is ~3-8 min; subsequent builds reuse the cache and can finish in <60s. """ from __future__ import annotations import argparse import os import sys from pathlib import Path from e2b import Template, default_build_logger _ENV_DIR = Path(__file__).resolve().parent _PROXY_SOURCE = _ENV_DIR / "interception.py" def _load_env(path: Path) -> None: if not path.exists(): return for raw in path.read_text().splitlines(): line = raw.strip() if not line or line.startswith("#") or "=" not in line: continue k, _, v = line.partition("=") k = k.strip() v = v.strip().strip('"').strip("'") if k and k not in os.environ: os.environ[k] = v def build_template(name: str, *, skip_cache: bool = False) -> str: if not _PROXY_SOURCE.exists(): raise RuntimeError(f"proxy source missing at {_PROXY_SOURCE}") # Template.copy() resolves relative paths against the caller's source # file directory. This script lives next to ``interception.py`` so the # bare filename works. # Stage 1 (root): system-wide pip deps for the proxy. # Stage 2 (user): opencode install + dir layout + proxy copy. template = ( Template() .from_python_image("3.12") .pip_install( [ "fastapi>=0.104", "uvicorn[standard]>=0.24", "httpx>=0.27", ] ) .set_user("user") .run_cmd("curl -fsSL https://opencode.ai/install | bash") .run_cmd("/home/user/.opencode/bin/opencode --version") .make_dir("/home/user/.config/opencode") .make_dir("/home/user/logs/agent") .make_dir("/home/user/logs/verifier") .make_dir("/home/user/task") .make_dir("/home/user/workdir") .make_dir("/home/user/proxy") .copy("interception.py", "/home/user/proxy/interception.py") .set_workdir("/home/user/workdir") ) if skip_cache: template = template.skip_cache() info = Template.build( template, name, cpu_count=2, memory_mb=2048, on_build_logs=default_build_logger(), ) return info.template_id if hasattr(info, "template_id") else str(info) def main(argv: list[str] | None = None) -> int: p = argparse.ArgumentParser(prog="build_e2b_template") p.add_argument( "--name", default="opencode-rl", help="Template name (default: opencode-rl).", ) p.add_argument( "--skip-cache", action="store_true", help="Force a clean rebuild, ignoring cache.", ) args = p.parse_args(argv) _load_env(_ENV_DIR / ".env") if not os.environ.get("E2B_API_KEY"): print("ERROR: E2B_API_KEY required.", file=sys.stderr) return 2 print(f"Building template '{args.name}' " f"(proxy source: {_PROXY_SOURCE})") print(f"Skip cache: {args.skip_cache}") print() template_id = build_template(args.name, skip_cache=args.skip_cache) print() print(f"Built. Template id/name: {template_id}") print(f"Use in code: Sandbox.create(template='{args.name}')") return 0 if __name__ == "__main__": sys.exit(main())