cmpatino HF Staff commited on
Commit
7ae2f98
·
verified ·
1 Parent(s): bbb9941

Setup sandbox server

Browse files
Files changed (2) hide show
  1. Dockerfile +10 -12
  2. sandbox_server.py +105 -0
Dockerfile CHANGED
@@ -1,29 +1,27 @@
1
  FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
2
 
3
- # Install Dev Mode required packages + useful tools
4
  RUN apt-get update && \
5
  apt-get install -y \
6
- bash \
7
- git git-lfs \
8
- wget curl procps \
9
- htop vim nano \
10
- jq \
11
- tmux \
12
  build-essential && \
13
  rm -rf /var/lib/apt/lists/*
14
 
15
- # Set up user with uid 1000 (required for Dev Mode)
 
16
  RUN useradd -m -u 1000 user
17
  USER user
18
 
19
  ENV HOME=/home/user \
20
- PATH=/home/user/.local/bin:$PATH
21
-
 
 
 
22
 
23
  WORKDIR /app
24
  COPY --chown=user . /app
25
 
26
  EXPOSE 7860
27
 
28
- # Just keep the container alive - agent uses SSH to interact
29
- CMD ["python", "-m", "http.server", "7860"]
 
1
  FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
2
 
 
3
  RUN apt-get update && \
4
  apt-get install -y \
5
+ bash git git-lfs wget curl procps \
6
+ htop vim nano jq tmux \
 
 
 
 
7
  build-essential && \
8
  rm -rf /var/lib/apt/lists/*
9
 
10
+ RUN uv pip install --system fastapi uvicorn python-multipart
11
+
12
  RUN useradd -m -u 1000 user
13
  USER user
14
 
15
  ENV HOME=/home/user \
16
+ PATH=/home/user/.local/bin:$PATH \
17
+ PIP_USER=1 \
18
+ HF_HUB_DISABLE_PROGRESS_BARS=1 \
19
+ TQDM_DISABLE=1 \
20
+ HF_HUB_ENABLE_HF_TRANSFER=1
21
 
22
  WORKDIR /app
23
  COPY --chown=user . /app
24
 
25
  EXPOSE 7860
26
 
27
+ CMD ["python", "sandbox_server.py"]
 
sandbox_server.py ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Minimal FastAPI server for sandbox operations."""
2
+ import os, subprocess, pathlib
3
+ from fastapi import FastAPI
4
+ from pydantic import BaseModel
5
+ from typing import Optional
6
+ import uvicorn
7
+
8
+ app = FastAPI()
9
+
10
+ class BashReq(BaseModel):
11
+ command: str
12
+ work_dir: str = "/app"
13
+ timeout: int = 120
14
+
15
+ class ReadReq(BaseModel):
16
+ path: str
17
+ offset: Optional[int] = None
18
+ limit: Optional[int] = 2000
19
+
20
+ class WriteReq(BaseModel):
21
+ path: str
22
+ content: str
23
+
24
+ class EditReq(BaseModel):
25
+ path: str
26
+ old_str: str
27
+ new_str: str
28
+ replace_all: bool = False
29
+
30
+ class ExistsReq(BaseModel):
31
+ path: str
32
+
33
+ @app.get("/api/health")
34
+ def health():
35
+ return {"status": "ok"}
36
+
37
+ @app.post("/api/bash")
38
+ def bash(req: BashReq):
39
+ try:
40
+ r = subprocess.run(
41
+ req.command, shell=True, capture_output=True, text=True,
42
+ cwd=req.work_dir, timeout=req.timeout,
43
+ )
44
+ output = r.stdout + r.stderr
45
+ if len(output) > 30000:
46
+ output = output[:30000] + "\n... (truncated)"
47
+ return {"success": r.returncode == 0, "output": output, "error": "" if r.returncode == 0 else f"Exit code {r.returncode}"}
48
+ except subprocess.TimeoutExpired:
49
+ return {"success": False, "output": "", "error": f"Timeout after {req.timeout}s"}
50
+ except Exception as e:
51
+ return {"success": False, "output": "", "error": str(e)}
52
+
53
+ @app.post("/api/read")
54
+ def read(req: ReadReq):
55
+ try:
56
+ p = pathlib.Path(req.path)
57
+ if not p.exists():
58
+ return {"success": False, "output": "", "error": f"File not found: {req.path}"}
59
+ if p.is_dir():
60
+ return {"success": False, "output": "", "error": f"Is a directory: {req.path}"}
61
+ lines = p.read_text().splitlines()
62
+ start = (req.offset or 1) - 1
63
+ end = start + (req.limit or len(lines))
64
+ selected = lines[start:end]
65
+ numbered = "\n".join(f"{start + i + 1}\t{line}" for i, line in enumerate(selected))
66
+ return {"success": True, "output": numbered, "error": ""}
67
+ except Exception as e:
68
+ return {"success": False, "output": "", "error": str(e)}
69
+
70
+ @app.post("/api/write")
71
+ def write(req: WriteReq):
72
+ try:
73
+ p = pathlib.Path(req.path)
74
+ p.parent.mkdir(parents=True, exist_ok=True)
75
+ p.write_text(req.content)
76
+ return {"success": True, "output": f"Wrote {len(req.content)} bytes to {req.path}", "error": ""}
77
+ except Exception as e:
78
+ return {"success": False, "output": "", "error": str(e)}
79
+
80
+ @app.post("/api/edit")
81
+ def edit(req: EditReq):
82
+ try:
83
+ p = pathlib.Path(req.path)
84
+ if not p.exists():
85
+ return {"success": False, "output": "", "error": f"File not found: {req.path}"}
86
+ content = p.read_text()
87
+ if req.old_str not in content:
88
+ return {"success": False, "output": "", "error": f"old_str not found in {req.path}"}
89
+ if not req.replace_all and content.count(req.old_str) > 1:
90
+ return {"success": False, "output": "", "error": f"old_str appears {content.count(req.old_str)} times. Use replace_all=true or provide more context."}
91
+ if req.replace_all:
92
+ new_content = content.replace(req.old_str, req.new_str)
93
+ else:
94
+ new_content = content.replace(req.old_str, req.new_str, 1)
95
+ p.write_text(new_content)
96
+ return {"success": True, "output": f"Edited {req.path}", "error": ""}
97
+ except Exception as e:
98
+ return {"success": False, "output": "", "error": str(e)}
99
+
100
+ @app.post("/api/exists")
101
+ def exists(req: ExistsReq):
102
+ return {"success": True, "output": str(pathlib.Path(req.path).exists()).lower(), "error": ""}
103
+
104
+ if __name__ == "__main__":
105
+ uvicorn.run(app, host="0.0.0.0", port=7860)