AIO Sandbox
AIO Sandbox 把浏览器、Shell、代码执行、文件 API、MCP/Skills、Code Server 放进同一个 Docker 容器,共享 /home/gem 文件系统。下面按我实际会用到的能力来写;安装和背景一笔带过。
安装
$ docker run --security-opt seccomp=unconfined --rm -it -p 8080:8080 ghcr.io/agent-infra/sandbox:latest国内可用火山镜像,见 快速开始。启动后仪表板:
| 服务 | URL |
|---|---|
| 仪表板 | http://localhost:8080/index.html |
| API 文档 | http://localhost:8080/v1/docs |
| VNC | http://localhost:8080/vnc/index.html?autoconnect=true |
| 终端 | http://localhost:8080/terminal |
| Code Server | http://localhost:8080/code-server/ |
| MCP | http://localhost:8080/mcp |
$ pip install agent-sandboxfrom agent_sandbox import Sandbox
client = Sandbox(base_url="http://localhost:8080")浏览器与 VNC
Agent 要「看网页、点按钮、下文件」,沙盒里主要有四条路:
| 方式 | 适合 |
|---|---|
| VNC | 人要盯着完整桌面点;多标签、本地文件 |
| CDP | Playwright/Puppeteer;DOM、网络、注入 JS |
| GUI 操作 | 截图 + 键鼠,DOM 不好碰的风控页 |
MCP browser_* | 给模型接工具,少写 HTTP 胶水 |
VNC
浏览器打开(或 iframe 嵌入产品):
http://localhost:8080/vnc/index.html?autoconnect=true传的是整块桌面像素,带宽和延迟都偏高,但交互最直观。
CDP
$ curl -s http://127.0.0.1:8080/v1/browser/info | jq '.data.cdp_url'
$ curl -s http://localhost:8080/cdp/json/versionfrom playwright.async_api import async_playwright
from agent_sandbox import Sandbox
client = Sandbox(base_url="http://localhost:8080")
async def main():
async with async_playwright() as p:
cdp_url = client.browser.get_info().cdp_url
browser = await p.chromium.connect_over_cdp(cdp_url)
page = await browser.new_page()
await page.goto("https://example.com")
await page.screenshot(path="shot.png")
import asyncio
asyncio.run(main())产品里要嵌「只显示当前页、省带宽」,可用 @agent-infra/browser-ui 的 BrowserCanvas,cdpEndpoint 指到 http://localhost:8080/json/version。
GUI 操作
不查 DOM,按坐标点击、输入、滚动:
from agent_sandbox.browser import Action_MoveTo, Action_Click, Action_Typing
client.browser.execute_action(request=Action_MoveTo(x=100, y=100))
client.browser.execute_action(request=Action_Click(x=200, y=200))
client.browser.execute_action(request=Action_Typing(text="hello", use_clipboard=True))VNC 和 CDP 怎么选
| VNC | Canvas + CDP | |
|---|---|---|
| 内容 | 完整浏览器 UI | 主要是当前页 |
| 自动化 | 键鼠模拟 | DOM / 网络 / JS |
| 带宽 | 高 | 低 |
更多见 浏览器与 VNC。
Shell 终端
基于 PTY 的真终端,适合 REPL、交互式安装;REST + WebSocket 两种接法。
Shell vs Bash 管道
Shell /v1/shell | Bash /v1/bash | |
|---|---|---|
| 输出 | 单一 output | stdout / stderr 分开 |
| 读取 | wait + view 快照 | /output + offset 增量 |
| 场景 | Web 终端、交互 | Agent 程序化跑命令 |
一次性执行
$ curl -X POST http://localhost:8080/v1/shell/exec \
-H "Content-Type: application/json" \
-d '{"command": "pwd && ls -la"}'复用会话
第一次返回 session_id,后续带上 id,工作目录和环境变量会保留:
$ SESSION=$(curl -s -X POST http://localhost:8080/v1/shell/exec \
-H "Content-Type: application/json" \
-d '{"command": "cd /tmp && export FOO=bar"}' | jq -r '.data.session_id')
$ curl -X POST http://localhost:8080/v1/shell/exec \
-H "Content-Type: application/json" \
-d "{\"id\": \"$SESSION\", \"command\": \"pwd && echo $FOO\"}"Web 终端
页面:http://localhost:8080/terminal。自研 UI 连 WebSocket:
const ws = new WebSocket('ws://localhost:8080/v1/shell/ws')
ws.send(JSON.stringify({ type: 'input', data: 'ls -la\n' }))
ws.onmessage = (e) => {
const msg = JSON.parse(e.data)
if (msg.type === 'output') terminal.write(msg.data)
}长任务用 async_mode: true,再调 /v1/shell/wait 和 /v1/shell/view。详见 Shell 终端、Bash 管道。
统一代码执行
不想自己管 Jupyter/Node 会话时,用 POST /v1/code/execute,按 language 路由到对应运行时:
| 语言 | 底层 |
|---|---|
python | Jupyter kernel |
javascript | Node.js |
$ curl -X POST http://localhost:8080/v1/code/execute \
-H "Content-Type: application/json" \
-d '{"language": "python", "code": "print(sum([1,2,3]))"}'
$ curl -X POST http://localhost:8080/v1/code/execute \
-H "Content-Type: application/json" \
-d '{"language": "javascript", "code": "console.log([1,2,3].reduce((a,b)=>a+b,0))"}'
$ curl http://localhost:8080/v1/code/info需要会话、内核状态、更长代码块时,再直接用 /v1/jupyter/* 或 /v1/nodejs/*(和统一入口写的是同一份文件系统)。见 统一代码执行。
文件操作
所有能力读写的是同一棵树:浏览器下载在 Downloads/,Shell echo 出的文件,Code Server 里改的,API 都能立刻看到。
常用端点(前缀 /v1/file):
| 端点 | 用途 |
|---|---|
read / write | 读写信,支持行范围、base64 二进制 |
replace / search / grep | 替换与搜索 |
find / glob / list | 列目录、匹配路径 |
upload / download | 上传表单、流式下载 |
watch / watch/wait | 等文件生成(如下载完成) |
$ curl -X POST http://localhost:8080/v1/file/write \
-H "Content-Type: application/json" \
-d '{"file": "/home/gem/workspace/demo.txt", "content": "Hello"}'
$ curl -X POST http://localhost:8080/v1/file/read \
-H "Content-Type: application/json" \
-d '{"file": "/home/gem/workspace/demo.txt"}'注意:很多接口失败时仍是 HTTP 200,要看 success 和 data.error_type(如 not_found)。download 失败则按 HTTP 状态码处理。
和 Shell 串起来:
$ curl -X POST http://localhost:8080/v1/shell/exec \
-H "Content-Type: application/json" \
-d '{"command": "echo ok > /tmp/from-shell.txt"}'
$ curl -X POST http://localhost:8080/v1/file/read \
-H "Content-Type: application/json" \
-d '{"file": "/tmp/from-shell.txt"}'详见 文件操作。
MCP 与 Skills
MCP Hub
一个地址 http://localhost:8080/mcp 聚合浏览器、文件、终端、Markitdown 等工具。Cursor 等客户端配置示例:
{
"mcpServers": {
"aio-sandbox": {
"url": "http://localhost:8080/mcp"
}
}
}| 前缀 | 示例 |
|---|---|
browser_* | browser_navigate、browser_screenshot、browser_extract |
file_* | file_read、file_write、file_grep |
terminal_* | terminal_execute |
| Markitdown | markitdown_convert |
MCP 抽象更高、连接相对稳;要细控 DOM 时再走 CDP。见 MCP 集成。
Skills
Skills 是可注册的指令 + 脚本包,让 Agent 发现「什么时候用、怎么做」。目录结构大致如下:
my-skill/
SKILL.md # frontmatter + Markdown 说明
scripts/
requirements.txtSKILL.md 示例:
---
name: report-writer
description: 需要把材料整理成结构化报告时使用
---
# Report Writer
...注册与查询:
# 注册沙盒内已有目录
$ curl -X POST http://localhost:8080/v1/skills/register \
-F "path=/home/gem/skills/report-writer"
# 上传 zip
$ curl -X POST http://localhost:8080/v1/skills/register \
-F "[email protected]" \
-F "path=/home/gem/skills" \
-F "name=report-writer"
$ curl http://localhost:8080/v1/skills/metadatas
$ curl http://localhost:8080/v1/skills/report-writer/content容器内也可用 aio skills list 查看。实践上:description 写清触发场景,大段逻辑放 scripts/,密钥不要写进 Skill。见 Skills、AIO CLI。
Code Server
浏览器里的完整 VS Code:http://localhost:8080/code-server/。默认本地跑无鉴权,改的是和 API、终端同一套文件。
推荐目录习惯:
/home/gem/
├── projects/ # 项目
├── Downloads/ # 浏览器下载
└── workspace/ # 当前工作区Agent 用文件 API 写出代码 → 人在 Code Server 里打开 /home/gem/projects/... 改 → Shell 里 npm run build,路径不用对齐。预装 Node、Python、Git、npm/pip 等。
进阶(扩展、工作目录、纯 API 部署)见 Code Server、Code Server 配置。
鉴权
本地调试可以不开;对外暴露端口时,用 JWT(环境变量 JWT_PUBLIC_KEY,内容为 base64 后的 RSA 公钥)。
$ openssl genrsa -out private_key.pem 2048
$ openssl rsa -in private_key.pem -pubout -out public_key.pem
$ export JWT_PUBLIC_KEY=$(cat public_key.pem | base64)
# 启动容器时传入 JWT_PUBLIC_KEY ...业务侧用私钥签发 JWT,请求带 Header:
$ curl http://localhost:8080/cdp/json/version \
-H "Authorization: Bearer ${jwt}"不能带 Header 的场景(例如浏览器直接打开 VNC):先用 JWT 换短时 ticket,再拼 URL:
$ curl -X POST http://localhost:8080/tickets \
-H "Authorization: Bearer ${jwt}"
# 响应里的 ticket 用于:
# http://localhost:8080/vnc/index.html?ticket=...&path=websockify%3Fticket%3D...票据默认约 30s,可用 TICKET_TTL_SECONDS 调整。详见 鉴权。
串一次完整流程
浏览器抓页 → Jupyter 转 Markdown → Shell 列文件 → 文件 API 拉回本地(体现统一文件系统):
import asyncio, base64
from playwright.async_api import async_playwright
from agent_sandbox import Sandbox
async def main():
c = Sandbox(base_url="http://localhost:8080")
home = c.sandbox.get_context().home_dir
async with async_playwright() as p:
info = c.browser.get_info().data
page = await (await p.chromium.connect_over_cdp(info.cdp_url)).new_page()
await page.goto("https://sandbox.agent-infra.com/", wait_until="networkidle")
html, shot = await page.content(), base64.b64encode(
await page.screenshot(type="png")
).decode()
c.jupyter.execute_code(code=f"""
from markdownify import markdownify
open('{home}/site.md','w').write(markdownify('''{html}'''))
""")
print(c.shell.exec_command(command=f"ls -lh {home}").data.output)
open("output.md", "w").write(c.file.read_file(file=f"{home}/site.md").data.content)
asyncio.run(main())Agent 跑在沙盒外(HTTP/MCP)还是沙盒内(127.0.0.1:8080 / aio CLI),见 Agent Call Sandbox vs In Sandbox。