File: README.md
AIO Sandbox - All-in-One Agent Sandbox Environment
🚀 Quick Start
Get up and running in 30 seconds:
# Recommended: Enable API Key authentication (protects all services: API, JupyterLab, VNC)
# - Supports three methods: X-AIO-API-Key header, Authorization: Bearer header, ?api_key= query parameter
# - Without SANDBOX_API_KEY, services remain open (backward compatible)
docker run --security-opt seccomp=unconfined --rm -it \
-e SANDBOX_API_KEY=your-secret-key \
-p 127.0.0.1:8080:8080 ghcr.io/agent-infra/sandbox:latestFor users in mainland China:
docker run --security-opt seccomp=unconfined --rm -it \
-e SANDBOX_API_KEY=your-secret-key \
-p 127.0.0.1:8080:8080 enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:1.11.0For reproducible deployments, pin a release tag. Replace 1.11.0 with the release you want:
docker run --security-opt seccomp=unconfined --rm -it \
-p 127.0.0.1:8080:8080 ghcr.io/agent-infra/sandbox:1.11.0
# or use the pinned mainland China mirror
docker run --security-opt seccomp=unconfined --rm -it \
-p 127.0.0.1:8080:8080 enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:1.11.0These examples intentionally bind the host side to 127.0.0.1 because the sandbox listens on 0.0.0.0 inside the container. For cloud deployment, keep port 8080 private and publish it through a reverse proxy or Ingress: Cloud Deployment Guide.
Once running, access the environment at:
- 📖 Documentation: http://localhost:8080/v1/docs
- 🌐 VNC Browser: http://localhost:8080/vnc/index.html?autoconnect=true
- 💻 VSCode Server: http://localhost:8080/code-server/
- 🤖 MCP Services: http://localhost:8080/mcp
🎯 What is AIO Sandbox?
AIO Sandbox is an all-in-one agent sandbox environment that combines Browser, Shell, File, MCP operations, and VSCode Server in a single Docker container. Built on cloud-native lightweight sandbox technology, it provides a unified, secure execution environment for AI agents and developers.
Why Choose AIO Sandbox?
Traditional sandboxes are single-purpose (browser, code, or shell), making file sharing and functional coordination extremely challenging. AIO Sandbox solves this by providing:
- ✅ Unified File System - Files downloaded in browser are instantly available in Shell/File operations
- ✅ Multiple Interfaces - VNC, VSCode, Jupyter, and Terminal in one unified environment
- ✅ Secure Execution - Sandboxed Python and Node.js execution with safety guarantees
- ✅ Zero Configuration - Pre-configured MCP servers and development tools ready to use
- ✅ Agent-Ready - MCP-compatible APIs for seamless AI agent integration
📦 Installation
SDK Installation
| Python
pip install agent-sandbox
``` | **TypeScript/JavaScript**
```bash
npm install @agent-infra/sandbox
``` | **Golang**
```bash
go get github.com/agent-infra/sandbox-sdk-go
``` |
| --- | --- | --- |
### Basic Usage
| **Python Example**
```python
from agent_sandbox import Sandbox
# Initialize client
client = Sandbox(base_url="http://localhost:8080")
home_dir = client.sandbox.get_context().home_dir
# Execute shell commands
result = client.shell.exec_command(command="ls -la")
print(result.data.output)
# File operations
content = client.file.read_file(file=f"{home_dir}/.bashrc")
print(content.data.content)
# Browser automation
screenshot = client.browser.screenshot()
``` | **TypeScript Example**
```typescript
import { Sandbox } from '@agent-infra/sandbox';
// Initialize client
const sandbox = new Sandbox({ baseURL: 'http://localhost:8080' });
// Execute shell commands
const result = await sandbox.shell.exec({ command: 'ls -la' });
console.log(result.output);
// File operations
const content = await sandbox.file.read({ path: '/home/gem/.bashrc' });
console.log(content);
// Browser automation
const screenshot = await sandbox.browser.screenshot();
``` |
| --- | --- |
## 🌟 Key Features
### 🔗 Unified Environment
All components run in the same container with a shared filesystem, enabling seamless workflows:
### 🌐 Browser Automation
Full browser control through multiple interfaces:
- **VNC** - Visual browser interaction through remote desktop
- **CDP** - Chrome DevTools Protocol for programmatic control
- **MCP** - High-level browser automation tools
### 💻 Development Tools
Integrated development environment with:
- **VSCode Server** - Full IDE experience in browser
- **Jupyter Notebook** - Interactive Python environment
- **Terminal** - WebSocket-based terminal access
- **Port Forwarding** - Smart preview for web applications
### 🤖 MCP Integration
Pre-configured Model Context Protocol servers:
- **Browser** - Web automation and scraping
- **File** - File system operations
- **Shell** - Command execution
- **Markitdown** - Document processing
## 📚 Complete Example
Convert a webpage to Markdown with embedded screenshot:
```python
import asyncio
import base64
from playwright.async_api import async_playwright
from agent_sandbox import Sandbox
async def site_to_markdown():
# Initialize sandbox client
c = Sandbox(base_url="http://localhost:8080")
home_dir = c.sandbox.get_context().home_dir
# Browser: Automation to download HTML
async with async_playwright() as p:
browser_info = c.browser.get_info().data
page = await (await p.chromium.connect_over_cdp(browser_info.cdp_url)).new_page()
await page.goto("https://example.com", wait_until="networkidle")
html = await page.content()
screenshot_b64 = base64.b64encode(await page.screenshot()).decode('utf-8')
# Jupyter: Convert HTML to markdown in sandbox
c.jupyter.execute_code(code=f"""
from markdownify import markdownify
html = '''{html}'''
screenshot_b64 = "{screenshot_b64}"
md = f"{{markdownify(html)}}\\n\\n"
with open('{home_dir}/site.md', 'w') as f:
f.write(md)
print("Done!")
""")
# Shell: List files in sandbox
list_result = c.shell.exec_command(command=f"ls -lh {home_dir}")
print(f"Files in sandbox: {list_result.data.output}")
# File: Read the generated markdown
return c.file.read_file(file=f"{home_dir}/site.md").data.content
if __name__ == "__main__":
result = asyncio.run(site_to_markdown())
print(f"Markdown saved successfully!")🏗️ Architecture
┌─────────────────────────────────────────────────────────────┐
│ 🌐 Browser + VNC │
├─────────────────────────────────────────────────────────────┤
│ 💻 VSCode Server │ 🐚 Shell Terminal │ 📁 File Ops │
├─────────────────────────────────────────────────────────────┤
│ 🔗 MCP Hub + 🔒 Sandbox Fusion │
├─────────────────────────────────────────────────────────────┤
│ 🚀 Preview Proxy + 📊 Service Monitoring │
└─────────────────────────────────────────────────────────────┘🛠️ API Reference
Core APIs
| Endpoint | Description |
|---|---|
/v1/sandbox |
Get sandbox environment information |
/v1/shell/exec |
Execute shell commands |
/v1/file/read |
Read file contents |
/v1/file/write |
Write file contents |
/v1/browser/screenshot |
Take browser screenshot |
/v1/jupyter/execute |
Execute Jupyter code |
MCP Servers
| Server | Tools Available |
|---|---|
browser |
navigate, screenshot, click, type, scroll |
file |
read, write, list, search, replace |
shell |
exec, create_session, kill |
markitdown |
convert, extract_text, extract_images |
🚢 Deployment
Docker Compose
services:
sandbox:
container_name: aio-sandbox
image: ghcr.io/agent-infra/sandbox:latest
security_opt:
- seccomp:unconfined
ports:
- "127.0.0.1:${HOST_PORT:-8080}:8080"
volumes:
- sandbox_data:/home/gem/workspace
extra_hosts:
- "host.docker.internal:host-gateway"
restart: "unless-stopped"
shm_size: "2gb"
environment:
SANDBOX_API_KEY: ${SANDBOX_API_KEY:-}
PROXY_SERVER: ${PROXY_SERVER:-}
WORKSPACE: ${WORKSPACE:-/home/gem/workspace}
TZ: ${TZ:-Asia/Singapore}
volumes:
sandbox_data:Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: aio-sandbox
spec:
replicas: 2
selector:
matchLabels:
app: aio-sandbox
template:
metadata:
labels:
app: aio-sandbox
spec:
containers:
- name: aio-sandbox
image: ghcr.io/agent-infra/sandbox:latest
ports:
- containerPort: 8080
resources:
limits:
memory: "2Gi"
cpu: "1000m"🤝 Integration Examples
Browser Use Integration
import asyncio
from agent_sandbox import Sandbox
from browser_use import Agent, Tools
from browser_use.browser import BrowserProfile, BrowserSession
from browser_use.llm import ChatOpenAI
sandbox = Sandbox(base_url="http://localhost:8080")
print("sandbox", sandbox.browser)
cdp_url = sandbox.browser.get_info().data.cdp_url
browser_session = BrowserSession(
browser_profile=BrowserProfile(cdp_url=cdp_url, is_local=True)
)
tools = Tools()
async def main():
agent = Agent(
task='Visit https://duckduckgo.com and search for "browser-use founders"',
llm=ChatOpenAI(model="gcp-claude4.1-opus"),
tools=tools,
browser_session=browser_session,
)
await agent.run()
await browser_session.kill()
input("Press Enter to close...")
if __name__ == "__main__":
asyncio.run(main())LangChain Integration
from langchain.tools import BaseTool
from agent_sandbox import Sandbox
class SandboxTool(BaseTool):
name = "sandbox_execute"
description = "Execute commands in AIO Sandbox"
def _run(self, command: str) -> str:
client = Sandbox(base_url="http://localhost:8080")
result = client.shell.exec_command(command=command)
return result.data.outputOpenAI Assistant Integration
from openai import OpenAI
from agent_sandbox import Sandbox
import json
client = OpenAI(
api_key="your_api_key",
)
sandbox = Sandbox(base_url="http://localhost:8080")
# define a tool to run code in the sandbox
def run_code(code, lang="python"):
if lang == "python":
return sandbox.jupyter.execute_code(code=code).data
return sandbox.nodejs.execute_nodejs_code(code=code).data
# Use OpenAI
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "calculate 1+1"}],
tools=[
{
"type": "function",
"function": {
"name": "run_code",
"parameters": {
"type": "object",
"properties": {
"code": {"type": "string"},
"lang": {"type": "string"},
},
},
},
}
],
)
if response.choices[0].message.tool_calls:
args = json.loads(response.choices[0].message.tool_calls[0].function.arguments)
print("args", args)
result = run_code(**args)
print(result['outputs'][0]['text'])MiniMax Integration
MiniMax provides an OpenAI-compatible API, so you can use the same openai SDK with a different base_url:
from openai import OpenAI
from agent_sandbox import Sandbox
import json
client = OpenAI(
api_key="your_minimax_api_key",
base_url="https://api.minimax.io/v1",
)
sandbox = Sandbox(base_url="http://localhost:8080")
def run_code(code, lang="python"):
if lang == "python":
return sandbox.jupyter.execute_code(code=code).data
return sandbox.nodejs.execute_code(code=code).data
response = client.chat.completions.create(
model="MiniMax-M2.7",
messages=[{"role": "user", "content": "calculate 1+1"}],
tools=[
{
"type": "function",
"function": {
"name": "run_code",
"parameters": {
"type": "object",
"properties": {
"code": {"type": "string"},
"lang": {"type": "string"},
},
},
},
}
],
temperature=0.01, # MiniMax requires temperature > 0
)
if response.choices[0].message.tool_calls:
args = json.loads(response.choices[0].message.tool_calls[0].function.arguments)
result = run_code(**args)
print(result.outputs[0].text)See the full minimax-integration example for more details.
🤝 Contributing
We welcome contributions! Please see our Contributing Guide for details.
📄 License
AIO Sandbox is released under the Apache License 2.0.
🙏 Acknowledgments
Built with ❤️ by the Agent Infra team. Special thanks to all contributors and the open-source community.
📞 Support
File: biome.json
{
"$schema": "https://biomejs.dev/schemas/1.8.0/schema.json",
"organizeImports": {
"enabled": true
},
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"formatter": {
"indentStyle": "space"
},
"javascript": {
"formatter": {
"quoteStyle": "single"
}
},
"css": {
"parser": {
"cssModules": true
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}
File: CONTRIBUTING.md
Contributing to Agent Infrastructure SDK
Thank you for your interest in contributing to the Agent Infrastructure SDK! This document provides guidelines and instructions for contributing to the project.
Table of Contents
- Code of Conduct
- Getting Started
- Development Setup
- How to Contribute
- Pull Request Process
- Development Guidelines
- Testing
- Documentation
- Release Process
Code of Conduct
We are committed to providing a welcoming and inclusive environment for all contributors. Please be respectful and constructive in all interactions.
Expected Behavior
- Use welcoming and inclusive language
- Be respectful of differing viewpoints and experiences
- Gracefully accept constructive criticism
- Focus on what is best for the community
- Show empathy towards other community members
Getting Started
Prerequisites
- Node.js 18+ and npm/yarn for JavaScript SDK
- Python 3.8+ for Python SDK
- Git for version control
- Docker (optional, for containerized development)
Repository Structure
sdk/
├── javascript/ # JavaScript/TypeScript SDK
│ ├── src/ # Source code
│ ├── tests/ # Test files
│ └── examples/ # Usage examples
├── python/ # Python SDK
│ ├── src/ # Source code
│ ├── tests/ # Test files
│ └── examples/ # Usage examples
└── website/ # Documentation websiteDevelopment Setup
JavaScript SDK
cd javascript
npm install
npm run build
npm testPython SDK
cd python
pip install -e .
pip install -r requirements-dev.txt
pytestHow to Contribute
Reporting Issues
Before creating an issue, please check if a similar issue already exists. When creating a new issue:
- Use a clear and descriptive title
- Provide a detailed description of the problem
- Include steps to reproduce the issue
- Specify your environment (OS, SDK version, runtime version)
- Include relevant code snippets or error messages
Suggesting Enhancements
Enhancement suggestions are tracked as GitHub issues. When suggesting an enhancement:
- Use a clear and descriptive title
- Provide a detailed description of the proposed enhancement
- Explain why this enhancement would be useful
- Include code examples if applicable
Contributing Code
- Fork the repository
- Create a feature branch from
main:bashgit checkout -b feature/your-feature-name - Make your changes following our coding standards
- Write or update tests as needed
- Ensure all tests pass
- Commit your changes with a descriptive message
- Push to your fork and create a pull request
Pull Request Process
Before Submitting
- Test your changes: Run the full test suite
- Update documentation: Update relevant documentation and examples
- Follow code style: Run linters and formatters
- Write clear commits: Use conventional commit format
Pull Request Guidelines
Title: Use a clear, descriptive title following conventional commit format
feat:for new featuresfix:for bug fixesdocs:for documentation changesrefactor:for code refactoringtest:for test additions/changeschore:for maintenance tasks
Description: Include:
- Summary of changes
- Related issue numbers (fixes #123)
- Breaking changes (if any)
- Testing performed
Size: Keep PRs focused and reasonably sized
- Split large changes into multiple PRs if possible
- One feature/fix per PR
Review Process
- At least one maintainer review is required
- All CI checks must pass
- Resolve all review comments
- Maintainers will merge using squash and merge
Development Guidelines
Code Style
JavaScript/TypeScript
- Use ESLint and Prettier configurations
- Follow TypeScript strict mode
- Use meaningful variable and function names
- Add JSDoc comments for public APIs
/**
* Creates a new sandbox instance
* @param config - Sandbox configuration options
* @returns Promise<Sandbox> - The initialized sandbox
*/
async function createSandbox(config: SandboxConfig): Promise<Sandbox> {
// Implementation
}Python
- Follow PEP 8 style guide
- Use type hints for function signatures
- Use meaningful variable and function names
- Add docstrings for all public functions
def create_sandbox(config: SandboxConfig) -> Sandbox:
"""
Creates a new sandbox instance.
Args:
config: Sandbox configuration options
Returns:
The initialized sandbox instance
Raises:
ValueError: If configuration is invalid
"""
# ImplementationCommit Messages
Follow conventional commit format:
<type>(<scope>): <subject>
<body>
<footer>Examples:
feat(python): add sandbox lifecycle management
fix(javascript): resolve connection timeout issue
docs: update API reference for v2.0Testing
- Write unit tests for all new functionality
- Maintain or improve code coverage
- Include integration tests for API changes
- Test edge cases and error conditions
JavaScript Testing
npm test # Run all tests
npm run test:unit # Run unit tests only
npm run test:integration # Run integration tests
npm run test:coverage # Generate coverage reportPython Testing
pytest # Run all tests
pytest tests/unit # Run unit tests only
pytest tests/integration # Run integration tests
pytest --cov=src # Generate coverage reportDocumentation
Code Documentation
- Document all public APIs
- Include examples in docstrings
- Explain complex algorithms or business logic
- Keep documentation up-to-date with code changes
User Documentation
- Update README files as needed
- Add examples for new features
- Update API reference documentation
- Consider adding tutorials for complex features
Documentation Website
The documentation website is built with Docusaurus:
cd website
npm install
npm start # Start development server
npm build # Build for productionRelease Process
Version Numbering
We follow Semantic Versioning (SemVer):
- MAJOR version for incompatible API changes
- MINOR version for backwards-compatible functionality additions
- PATCH version for backwards-compatible bug fixes
Release Checklist
- Update version numbers in package files
- Update CHANGELOG.md
- Run full test suite
- Build and verify packages
- Create git tag
- Publish to package repositories
- Update documentation website
- Create GitHub release with notes
Publishing
JavaScript SDK
npm version <major|minor|patch>
npm run build
npm publishPython SDK
cd python
./publish.sh # Builds, publishes to PyPI, and creates git tagQuestions and Support
If you have questions or need help:
- Check the documentation
- Search existing issues
- Join our community discussions
- Create a new issue if needed
License
By contributing to this project, you agree that your contributions will be licensed under the same license as the project (see LICENSE file).
Acknowledgments
Thank you to all contributors who help make this project better!
File: docker-compose.yaml
services:
sandbox:
container_name: aio-sandbox
image: ghcr.io/agent-infra/sandbox:latest
security_opt:
- seccomp:unconfined
extra_hosts:
- "host.docker.internal:host-gateway"
restart: "unless-stopped"
shm_size: "2gb"
mem_limit: "8g"
cpus: "4"
ports:
- "127.0.0.1:${HOST_PORT:-8080}:8080"
environment:
PROXY_SERVER: ${PROXY_SERVER:-}
JWT_PUBLIC_KEY: ${JWT_PUBLIC_KEY:-}
DNS_OVER_HTTPS_TEMPLATES: ${DNS_OVER_HTTPS_TEMPLATES:-"https://cloudflare-dns.com/dns-query{?dns}"}
WORKSPACE: ${WORKSPACE:-"/home/gem"}
HOMEPAGE: ${HOMEPAGE:-}
BROWSER_EXTRA_ARGS: ${BROWSER_EXTRA_ARGS:-}
TZ: ${TZ:-Asia/Singapore}
DISPLAY_WIDTH: ${DISPLAY_WIDTH:-1280}
DISPLAY_HEIGHT: ${DISPLAY_HEIGHT:-1024}
WAIT_PORTS: "8079,8091"
VNC_SERVER_PORT: 5900
PUBLIC_PORT: 8080
AUTH_BACKEND_PORT: 8081
WEBSOCKET_PROXY_PORT: 6080
GEM_SERVER_PORT: 8088
MCP_HUB_PORT: 8079
SANDBOX_SRV_PORT: 8091
JUPYTER_LAB_PORT: 8888
CODE_SERVER_PORT: 8200
MCP_SERVER_BROWSER_PORT: 8100
TINYPROXY_PORT: 8118
MCP_SERVER_MARKITDOWN_PORT: 8101
MCP_SERVER_CHROME_DEVTOOLS_PORT: 8102
BROWSER_REMOTE_DEBUGGING_PORT: 9222
PIP_INDEX_URL: ${PIP_INDEX_URL:-https://pypi.org/simple}
UV_DEFAULT_INDEX: ${UV_DEFAULT_INDEX:-https://pypi.org/simple}
NPM_CONFIG_REGISTRY: ${NPM_CONFIG_REGISTRY:-https://registry.npmjs.org}
# for gh cli
GITHUB_TOKEN: ""
# Claude Skills
AIO_SKILLS_PATH: ${AIO_SKILLS_PATH:-}
# Features
DISABLE_JUPYTER: ${DISABLE_JUPYTER:-false}
DISABLE_CODE_SERVER: ${DISABLE_CODE_SERVER:-false}
File: package.json
{
"name": "sandbox",
"version": "1.0.0",
"private": true,
"license": "Apache-2.0",
"scripts": {},
"dependencies": {},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@types/node": "^18.11.17",
"prettier": "^3.4.2"
}
}
File: pnpm-workspace.yaml
packages:
- 'website'
- 'sdk/js'
File: evaluation/README.md
AIO Sandbox Tool Evaluation
English | 简体中文
A comprehensive evaluation framework for MCP (Model Context Protocol) tools and AI agent capabilities based on aio-sandbox.
Inspired by Anthropic's "Writing Tools for Agents"
Prerequisites
- Python >= 3.13
- uv package manager
Installation
Clone the repository:
bashgit clone <repository-url> cd evaluationInstall dependencies:
bashuv syncConfigure environment:
bashcp .env.example .envEdit
.envwith your credentials:env# Azure OpenAI Configuration OPENAI_BASE_URL=https://your-endpoint.openai.azure.com OPENAI_API_KEY=your-api-key AZURE_OPENAI_DEPLOYMENT=gpt-4 AZURE_OPENAI_API_VERSION=2024-02-15-preview # MCP Server Configuration MCP_SERVER_URL=http://localhost:8080/mcp # Optional: Concurrency MAX_CONCURRENT_TASKS=5
Usage
Run All Evaluations
uv run main.pyRun Specific Evaluation
uv run main.py --eval basic
uv run main.py --eval browser
uv run main.py --eval collaboration
uv run main.py --eval workflowUse OpenAI-Compatible Providers
The evaluation framework also supports standard OpenAI and compatible APIs
(e.g. MiniMax) via the --agent openai flag:
# Standard OpenAI
uv run main.py --agent openai --openai-model gpt-4
# MiniMax (OpenAI-compatible)
export OPENAI_API_KEY="your_minimax_api_key"
uv run main.py --agent openai \
--openai-base-url https://api.minimax.io/v1 \
--openai-model MiniMax-M2.7Available Categories
| Category | Description |
|---|---|
ping |
Basic connectivity test |
basic |
Single tool capabilities (file ops, code execution, shell) |
browser |
Browser automation basics (navigation, DOM, forms) |
browser_advanced |
Advanced browser interactions (click, hover, keyboard) |
code_advanced |
Advanced code execution (async, error handling, data structures) |
collaboration |
Multi-tool workflows (file+code, browser+file) |
editor |
Text editor operations (view, create, replace, insert, undo) |
packages |
Package management (Python, Node.js) |
util |
Utilities (Markdown conversion) |
error |
Error handling tests |
workflow |
Real-world scenarios (code review, data pipeline, web scraping) |
nextjs |
Next.js project startup |
Project Structure
evaluation/
├── main.py # Main entry point and orchestration
├── agent_loop.py # Agent loop implementations (Azure OpenAI, OpenAI-compatible, etc.)
├── dataset_parser.py # XML dataset parser
├── .env.example # Environment configuration template
├── pyproject.toml # Project dependencies
├── dataset/ # Evaluation datasets
│ ├── evaluation_basic.xml
│ ├── evaluation_browser.xml
│ ├── evaluation_collaboration.xml
│ └── ...
└── result/ # Evaluation reports (auto-generated)
└── YYYYMMDD/ # Date-based output directory
└── evaluation_*.mdExtending the Framework
Add New Evaluation Category
Create XML file in
dataset/:textdataset/evaluation_mycategory.xmlRun:
bashuv run main.py --eval mycategory
Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new features
- Ensure all tests pass
- Submit a Pull Request
License
See LICENSE in the repository root.
References
- Model Context Protocol (MCP)
- Anthropic: Writing Tools for Agents
- Azure OpenAI Documentation
- MiniMax API Documentation
File: evaluation/README_zh.md
AIO Sandbox 工具评测
English | 简体中文
一个基于 aio-sandbox 的 MCP(模型上下文协议)工具和 AI 智能体能力的综合评测框架。
前置要求
- Python >= 3.13
- uv 包管理器
安装
克隆仓库:
bashgit clone <repository-url> cd evaluation安装依赖:
bashuv sync配置环境:
bashcp .env.example .env编辑
.env文件,填入你的凭据:env# Azure OpenAI 配置 OPENAI_BASE_URL=https://your-endpoint.openai.azure.com OPENAI_API_KEY=your-api-key AZURE_OPENAI_DEPLOYMENT=gpt-4 AZURE_OPENAI_API_VERSION=2024-02-15-preview # MCP 服务器配置 MCP_SERVER_URL=http://localhost:8080/mcp # 可选:并发配置 MAX_CONCURRENT_TASKS=5
使用方法
运行所有评测
顺序运行所有评测文件:
uv run main.py运行特定评测
运行特定评测类别:
uv run main.py --eval basic
uv run main.py --eval browser
uv run main.py --eval collaboration
uv run main.py --eval workflow可用的评测类别
| 类别 | 描述 |
|---|---|
ping |
基础连通性测试 |
basic |
单工具能力测试(文件操作、代码执行、Shell) |
browser |
浏览器自动化基础(导航、DOM、表单) |
browser_advanced |
高级浏览器交互(点击、悬停、键盘操作) |
code_advanced |
高级代码执行(异步、错误处理、数据结构) |
collaboration |
多工具协作流程(文件+代码、浏览器+文件) |
editor |
文本编辑器操作(查看、创建、替换、插入、撤销) |
packages |
包管理(Python、Node.js) |
util |
实用工具(Markdown 转换) |
error |
错误处理测试 |
workflow |
真实场景(代码审查、数据管道、爬虫html展示) |
nextjs |
Next.js 项目启动 |
项目结构
evaluation/
├── main.py # 主入口和评测编排
├── agent_loop.py # 智能体循环实现(Azure OpenAI 等)
├── dataset_parser.py # XML 数据集解析器
├── .env.example # 环境配置模板
├── pyproject.toml # 项目依赖
├── dataset/ # 评测数据集
│ ├── evaluation_basic.xml
│ ├── evaluation_browser.xml
│ ├── evaluation_collaboration.xml
│ └── ...
└── result/ # 评测报告(自动生成)
└── YYYYMMDD/ # 基于日期的输出目录
└── evaluation_*.md扩展框架
添加新的评测类别
在
dataset/目录创建 XML 文件:textdataset/evaluation_mycategory.xml运行:
bashuv run main.py --eval mycategory
贡献
欢迎贡献!请:
- Fork 仓库
- 创建功能分支
- 为新功能添加测试
- 确保所有测试通过
- 提交 Pull Request
许可证
查看仓库根目录的 LICENSE 文件。
参考资料
File: evaluation/dataset/README.md
Dataset 评测用例总览
- evaluation_ping.xml - 基础连通性测试
- 测试 aio 系统版本获取:回答 aio 系统的版本号
- evaluation_basic.xml - 单工具基础能力测试
A. 环境探测类
- 检查 Python 包 'fastapi' 是否已安装
- 统计已安装的 Python 包数量
B. 文件操作类
- 创建文件并写入内容 "Hello MCP",然后读取
- 列出 /tmp 目录中以 'test_eval' 开头的文件数量
- 搜索文件中 'Hello MCP' 模式的匹配次数
C. 代码执行类
- 执行 Python 代码计算 2^10
- 计算 1-100 内所有偶数的和
- 统计字符串 "Model Context Protocol" 中的元音字母数量
D. Shell 操作类
- 执行 bash echo 命令输出 "MCP_TEST"
- 使用 bash 统计 /tmp 目录中的文件夹数量
- evaluation_browser.xml - 浏览器基础测试
- 导航到 bytedance.com,检查是否包含 'ByteDance' 或 '字节跳动'
- 导航到 bytedance.com,统计页面链接数量
- 导航到 baidu.com,用 JavaScript 获取链接数量
- 导航到 baidu.com,用 JavaScript 获取页面标题
- 导航到 baidu.com,统计 DOM 中的 anchor 元素数量
- 测试表单填充:在 baidu.com 搜索框填入 'test_search'
- 测试表单填充工作流:填入 'MCP test' 并验证
- 测试完整浏览器工作流:导航、读取链接、获取 markdown、执行 JS
- 测试标签页管理:创建新标签页并统计数量
- 测试标签页切换:切换标签页并获取标题
- 搜索 "aio sandbox",导航到 GitHub 页面,输出主要贡献者
- 获取浏览器下载列表并统计数量
- 触发下载并检查下载列表是否包含文件
- 检查下载项是否包含 'filename' 字段
- evaluation_browser_advanced.xml - 浏览器高级交互测试
A. 元素点击操作
- 点击按钮并验证文本变化
- 点击多个按钮中的特定按钮(使用索引)
B. 表单填充与提交
- 填充文本输入框和邮箱输入框
C. 键盘操作
- 测试按键事件(Enter 键)
- 测试特殊按键(Tab 键)
- 测试组合键(Delete 键)
D. 鼠标移动与悬停
- 测试鼠标悬停事件
- 测试悬停菜单显示
- 测试悬停 tooltip 显示
E. 滚动操作
- 测试页面滚动
- 测试负向滚动(向上滚动)
- 测试滚动到底部
F. 导航历史
- 测试后退功能
- 测试前进功能
G. 标签页管理
- 关闭标签页并统计剩余数量
- 切换标签页后关闭
- 测试关闭最后一个标签页的行为
H. 下拉选择
- 测试下拉菜单选择
- 测试通过索引选择下拉选项
I. 复杂交互场景
- 测试计算器交互:填充数字并执行加法运算
J. 可点击元素获取
- 过滤特定类型的可点击元素
- 获取复杂页面中的所有可交互元素
K. 浏览器信息获取
- 获取浏览器视口信息
- 获取 CDP URL
- 计算视口面积
- evaluation_code_advanced.xml - 代码执行高级测试
A. JavaScript 基础执行
- 基础算术运算
- 字符串反转操作
B. JavaScript 对象和数组
- 数组求和操作
- JSON 解析
C. Python 高级特性
- 列表推导式计算平方和
- 字典值求和
- lambda 和 map 函数使用
D. 文件操作 + 代码执行
- Python 文件 I/O 操作
E. JavaScript 异步代码
- 同步循环求和
F. 错误处理
- Python 异常捕获(ZeroDivisionError)
- JavaScript 错误处理(JSON 解析错误)
G. 数学运算
- Python math 库使用
- JavaScript Math 操作
H. 字符串处理
- Python 正则表达式匹配邮箱
- JavaScript 正则表达式匹配数字
I. 数据结构
- Python 集合交集操作
J. 综合场景
- Python 数据处理管道
- JavaScript 函数式编程
- evaluation_collaboration.xml - 多工具协作测试
E. 文件+代码执行
- 读取文件并统计 'async' 出现次数
- 创建并执行斐波那契数列脚本
F. Shell+文件操作
- 创建目录和文件,然后读取
- 获取当前工作目录并写入文件
G. 环境探测+代码分析
- 获取包列表并统计包含 'fast' 的包数量
H. Browser + 文件操作
- 创建 HTML 文件并用浏览器导航验证
I. 编辑器 + 代码执行
- 使用编辑器创建 Python 文件并执行
- 修改文件后重新执行
J. 复杂多步协作
- bash + 文件 + 代码执行管道
- 获取上下文并写入文件验证版本号
K. 浏览器 + 代码分析
- 浏览器 evaluate + Python 处理 DOM 数据
L. 文件查找 + 处理
- 批量处理文件
- 文件搜索和聚合统计
- evaluation_editor.xml - str_replace_editor 工具测试
A. 文件查看 (view)
- 查看完整文件内容
- 查看指定行范围
B. 文件创建 (create)
- 创建新的 Python 文件
C. 字符串替换 (str_replace)
- 单次字符串替换
- 多行字符串替换
D. 插入 (insert)
- 在指定行插入内容
E. 复杂编辑场景
- Python 代码重构
F. 边界情况
- 创建空文件
- 处理特殊字符
G. 撤销操作 (undo_edit)
- 基础撤销操作
- 多次撤销操作
- 撤销新文件创建
- evaluation_error.xml - 错误处理测试
- 读取不存在的文件
- 执行产生 ZeroDivisionError 的 Python 代码
- evaluation_packages.xml - 包管理工具测试
A. Node.js 包查询
- 检查 'express' 是否已安装
- 统计 Node.js 包数量
B. 混合包查询
- 同时获取 Python 和 Node.js 包
C. 不同语言别名
- 使用 'node' 别名
- 使用 'py' 别名
D. 包搜索
- 搜索包含特定字符串的包
E. 版本信息
- 提取包版本号
F. 包统计
- 统计所有包的总数量
- evaluation_util.xml - 工具测试
- 转换网页到 markdown 格式
- 统计 markdown 中特定词汇出现次数
- evaluation_workflow.xml - 真实工作场景
H. 代码审查工作流
- 搜索包含特定关键字的 Python 文件
- 使用 AST 解析统计函数定义数量
I. 数据处理管道
- 解析 XML 并统计元素数量
File: evaluation/result/20251112/improvement_suggestions.md
AIO Sandbox MCP工具改进建议
"理论和实践有时会冲突。每次都是理论输。" - Linus Torvalds
基于对评测反馈的深度分析,以下是核心问题诊断和改进方案。
【核心判断】
✅ 值得做:这些问题不是理论上的完美主义,而是实际用户每天都在碰到的真实痛点。
【关键洞察】
数据结构问题
- 工具返回格式不统一:部分工具返回纯文本,部分返回JSON,增加了解析复杂度
- sandbox_execute_code执行结果不可预测:表达式返回值有时在stdout,有时需要显式print
复杂度问题
- str_replace_editor与file_operations功能重叠:两个工具都能做文件创建,用户困惑
- browser_evaluate的JavaScript格式要求模糊:需要箭头函数包装但错误提示不明确
风险点
- undo_edit逻辑缺陷:对新创建的文件执行undo应该删除文件,但实际保留了文件
- get_packages别名支持不一致:'node'不支持但'nodejs'支持,容易出错
【致命缺陷排序】
🔴 Level 1 - 必须立即修复(影响正确性)
1.1 undo_edit对新创建文件的处理错误
问题描述:
创建文件 → 执行undo_edit → 文件仍然存在 ❌
预期:创建文件 → 执行undo_edit → 文件被删除 ✓数据流分析:
- 创建操作被记录为"编辑历史"
- undo应该回退到"文件不存在"状态
- 当前实现只回退了文件内容,没有删除文件本身
修复方案:
# 伪代码
class EditorHistory:
def undo_create(self, path):
# 简单粗暴:删除文件
if self.operation_type == 'create':
os.remove(path) # 不要留垃圾
else:
# 正常的内容回退
self.restore_previous_content()Linus评价:"这是个bug,不是feature。新文件的undo应该删除文件,这是常识。"
1.2 sandbox_execute_code的返回行为不一致
问题描述:
# 场景1:直接表达式 - 没有输出
execute_code("2**10") # stdout: "" ❌
# 场景2:需要print才有输出
execute_code("print(2**10)") # stdout: "1024" ✓
# 场景3:但JavaScript却不同
execute_code("10 + 20", language="javascript") # stdout: "30" ✓根本原因:Python REPL和脚本执行的区别
- REPL会自动打印表达式结果
- exec()不会打印返回值
修复方案(Good Taste):
def execute_code(code, language='python'):
if language == 'python':
# 包装代码,自动打印最后一个表达式
wrapped = f"""
import sys
_result = None
try:
_result = ({code})
if _result is not None:
print(_result)
except SyntaxError:
# 是语句不是表达式,直接执行
exec({repr(code)})
"""
# 执行包装后的代码
return run(wrapped)
# JavaScript保持原样
return run(code, 'js')Linus评价:"用户不应该关心'表达式'和'语句'的区别。自动处理这种边界情况,这是好品味。"
🟡 Level 2 - 应该尽快优化(影响易用性)
2.1 str_replace_editor的create命令在文件存在时失败
问题描述:
评测中多次遇到:创建文件失败 → 切换到file_operations → 成功覆盖特殊情况分析:
IF 文件不存在: create成功
IF 文件已存在: create失败,提示"File already exists"消除特殊情况的方案:
def str_replace_editor(command, path, content, overwrite=False):
if command == 'create':
if os.path.exists(path) and not overwrite:
# 不要失败,提供更好的默认行为
return {
"suggest": "use overwrite=True or use str_replace command",
"file_exists": True,
"current_size": os.path.getsize(path)
}
# 直接写入,不管文件是否存在
write_file(path, content)
return {"success": True}Linus评价:"失败是因为设计不好。'create'和'overwrite'不应该是两个命令,应该用一个参数控制。"
2.2 browser_evaluate的JavaScript格式要求不明确
问题描述:
// 失败:直接声明
const x = 5; console.log(x); // ❌ "Unexpected token 'const'"
// 成功:箭头函数包装
() => { const x = 5; console.log(x); return x; } // ✓当前错误信息无用:
Error: "Unexpected token 'const'"
// 用户不知道怎么修正改进方案(实用主义):
def browser_evaluate(script):
# 尝试1:直接执行
result = try_eval(script)
if result.success:
return result
# 尝试2:自动包装
wrapped = f"(() => {{ {script} }})()"
result = try_eval(wrapped)
if result.success:
return result
# 尝试3:更智能的包装(捕获最后一个表达式)
wrapped = f"""
(() => {{
let _last_value;
{transform_to_capture_last_value(script)}
return _last_value;
}})()
"""
return try_eval(wrapped)Linus评价:"不要让用户学习你的内部实现细节。自动处理格式,失败了再fallback,这是务实的做法。"
2.3 get_packages返回纯文本而非结构化数据
问题描述:
# 当前返回
"""
- fastapi==0.121.0
- numpy==2.2.6
- pandas==2.3.3
"""
# 用户需要自己解析,容易出错
packages = output.split('\n')
for line in packages:
name = line.strip()[2:].split('==')[0] # 丑陋的字符串处理好的数据结构 > 坏的代码:
# 改进后返回
{
"language": "python",
"count": 169,
"packages": [
{"name": "fastapi", "version": "0.121.0"},
{"name": "numpy", "version": "2.2.6"},
{"name": "pandas", "version": "2.3.3"}
]
}
# 用户代码变得简单
count = result['count']
fastapi = next(p for p in result['packages'] if p['name'] == 'fastapi')Linus评价:"糟糕的程序员担心代码,优秀的程序员担心数据结构。返回结构化数据,让用户代码变简单。"
🟢 Level 3 - 可以改进(提升体验)
3.1 file_operations和str_replace_editor功能重叠
当前状态:
创建文件:
- file_operations(action='write') ✓
- str_replace_editor(command='create') ✓
两个工具都能做,用户困惑该用哪个Linus的建议:
"一个功能一个工具。如果有重叠,就是设计错了。"
改进方案:
- file_operations: 专注于简单的CRUD操作(create/read/update/delete/list/search)
- str_replace_editor: 专注于编辑操作(view/str_replace/insert/undo)
明确的职责划分:
file_operations: 我是文件管理器
str_replace_editor: 我是编辑器
你要创建新文件? → file_operations
你要编辑已有文件? → str_replace_editor3.2 get_packages不支持常见别名
问题描述:
get_packages(language='py') # ❌ 失败:"should be 'python' or 'nodejs'"
get_packages(language='node') # ❌ 失败:"should be 'python' or 'nodejs'"
get_packages(language='js') # ❌ 失败用户体验差:用户需要记住精确的参数值,不符合直觉
修复方案(简单有效):
LANGUAGE_ALIASES = {
'py': 'python',
'python': 'python',
'python3': 'python',
'node': 'nodejs',
'nodejs': 'nodejs',
'js': 'nodejs',
'javascript': 'nodejs'
}
def get_packages(language=None):
if language:
language = LANGUAGE_ALIASES.get(language.lower())
if not language:
return error("Unsupported language. Try: py/python, node/nodejs/js")
# 继续执行Linus评价:"别让用户猜。支持常见别名,这是基本的用户友好性。"
3.3 文件操作工具缺少批量操作支持
问题描述:
# 场景:搜索7个文件中是否包含'def'
# 当前:需要调用7次file_operations
for file in py_files:
file_operations(action='search', path=file, content='def')
# 效率低,工具调用次数多改进方案:
# 新增batch参数
file_operations(
action='search',
paths=['/tmp/main.py', '/tmp/calc.py', ...], # 批量路径
content='def'
)
# 返回
{
"results": [
{"path": "/tmp/main.py", "matches": 16},
{"path": "/tmp/calc.py", "matches": 1},
...
],
"total_matches": 22
}【实施优先级】
Week 1 (立即修复):
├── undo_edit bug修复
├── execute_code返回值统一化
└── browser_evaluate自动包装
Week 2 (易用性改进):
├── get_packages返回JSON
├── str_replace_editor支持overwrite
└── 别名支持
Week 3 (体验优化):
├── 工具职责明确化文档
├── 批量操作支持
└── 错误提示改进【Linus式总结】
Good Taste原则
- 自动处理边界情况:不要让用户区分"表达式"和"语句"
- 消除特殊情况:文件存在与否不应该影响create的行为
- 数据结构优先:返回JSON而不是文本,让用户代码更简单
Never Break Userspace原则
- 添加
overwrite参数时,默认值应该保持向后兼容 - 返回格式改JSON时,提供
format='legacy'选项过渡
Pragmatism原则
- 自动包装JavaScript代码,不要强制用户学习内部细节
- 支持常见别名(py/node/js),因为这是实际使用中会遇到的
🔵 Level 4 - Next.js 全栈场景优化(实战验证)
4.1 长时间npm操作需要更好的进度反馈
问题描述:
# Next.js评测中的耗时分析
npx create-next-app: 134.50秒 (占总时间201.36秒的67%)
等待服务启动: 10秒
其他操作: 56.86秒用户痛点:
- 长达2分钟的创建过程没有任何进度反馈
- 用户不知道是"正在执行"还是"卡住了"
- 超时参数(timeout: 120)被迫使用,但仍不够
Linus评价:"用户等待超过30秒就会焦虑。提供实时输出,让用户看到进度。"
改进方案:
def execute_bash(cmd, stream_output=False):
if stream_output:
# 流式返回输出,每1秒返回一次增量
for line in process.stdout:
yield {"type": "stdout", "content": line}
else:
# 传统方式:等待完成后返回
return process.communicate()优先级:中等(影响用户体验,但不影响功能正确性)
4.2 后台进程管理需要更优雅的方案
问题描述:
# 当前做法:手动管理后台进程
cd /tmp/my-nextjs-app && nohup npm run dev -- -p 3500 > dev.log 2>&1 &
sleep 10 # 硬编码等待时间
cat dev.log # 手动检查日志特殊情况太多:
- IF 服务启动很快:浪费了等待时间
- IF 服务启动很慢:10秒不够,测试会失败
- IF 端口被占用:没有错误检测机制
- IF 进程启动失败:用户不知道
Linus的"Good Taste"方案:
def start_dev_server(cmd, port, ready_pattern=r"Ready|Listening"):
"""
启动开发服务器并等待就绪
- 消除硬编码的sleep时间
- 自动检测服务是否启动成功
- 提供清晰的错误信息
"""
process = subprocess.Popen(cmd, stdout=PIPE, stderr=STDOUT)
start_time = time.time()
timeout = 60 # 最多等60秒
while time.time() - start_time < timeout:
# 检查进程是否还活着
if process.poll() is not None:
return error(f"Process exited with code {process.returncode}")
# 检查端口是否可用
if is_port_listening(port):
return success("Server is ready")
time.sleep(0.5)
return error(f"Timeout waiting for server on port {port}")消除的特殊情况:
- ❌
sleep 10- 硬编码等待 - ❌
cat dev.log- 手动检查日志 - ❌ 不知道服务是否真的启动了
- ✅ 自动检测,服务就绪立即返回
4.3 browser工具与bash工具的协同需要优化
问题描述:
# Next.js评测中的工具切换
sandbox_browser_navigate # 访问首页
sandbox_browser_evaluate # 获取title
sandbox_browser_navigate # 访问API
sandbox_browser_evaluate # 获取JSON
sandbox_execute_bash # 用curl再验证一次
sandbox_execute_code # 用Python解析JSON
# 为什么需要这么多步骤?根本原因:
browser_evaluate返回的JSON需要手动解析- 浏览器工具无法直接提取JSON响应
- 需要切换到bash/python来处理数据
实用主义改进:
# 新增便捷方法
def browser_fetch_json(url):
"""
直接获取JSON API响应
当前需要3步:
1. navigate(url)
2. evaluate("document.body.innerText")
3. execute_code("json.loads(...)")
改进后1步:
browser_fetch_json(url) → {"status": "success"}
"""
navigate(url)
text = evaluate("document.body.innerText")
try:
return json.loads(text)
except:
return {"error": "Not valid JSON", "content": text}【附录:评测数据摘要】
- 总任务数:76个任务(新增 Next.js 全栈场景)
- 成功率:100% (但过程中有很多tool retry)
- 工具调用统计:
- 平均每任务2.9次工具调用
- 最高:19次(复杂的browser任务)
- 最低:1次(简单的execute任务)
关键发现:
str_replace_editor的create命令失败率:约30%(因为文件已存在)browser_evaluate的JavaScript语法错误率:约40%(需要重试包装)execute_code需要添加print才能得到结果:约25%- [新增] Next.js长时间npm操作缺少进度反馈,用户体验差
- [新增] 后台进程管理依赖硬编码等待时间,不够可靠
这不是理论问题,这是实际用户体验的数据支撑。
"Talk is cheap. Show me the code." - Linus Torvalds
以上建议都是基于实际评测数据,而不是猜测。每个改进都能解决真实存在的用户痛点。
File: evaluation/result/20251112/Latest.md
Eval Report
Datetime: 2025-11-12 13:27:23
Latest Update: 2025-11-12 (Added Next.js evaluation)
basic
| # | Prompt | 操作时间 | 是否成功 | 工具调用数量 | 操作步骤 | 失败原因 |
|---|---|---|---|---|---|---|
| 1 | Check if the Python package 'fastapi' is installed in the sandbox. Answer with 'YES' or 'NO'. | 6.98s | ✅ | 1 | 1. sandbox_get_packages | - |
| 2 | Get the list of installed Python packages and count how many packages are installed. Return just the number. | 43.12s | ✅ | 2 | 1. sandbox_get_packages2. sandbox_execute_code | - |
| 3 | Create a file at /tmp/test_eval.txt with content "Hello MCP". Then read it back and return the content. | 11.83s | ✅ | 2 | 1. sandbox_file_operations2. sandbox_file_operations | - |
| 4 | List all files in /tmp directory find the file name start with 'test_eval' and return the count. | 16.72s | ✅ | 2 | 1. sandbox_file_operations2. sandbox_execute_code | - |
| 5 | Search for the pattern 'Hello MCP' in the file /tmp/test_eval.txt and return the count of matches found. | 5.35s | ✅ | 1 | 1. sandbox_file_operations | - |
| 6 | Execute Python code to calculate 2**10 and return the result as a number. | 24.26s | ✅ | 3 | 1. sandbox_execute_code2. sandbox_execute_code3. sandbox_execute_code | - |
| 7 | Execute Python code to find the sum of all even numbers from 1 to 100 (inclusive). Return just the number. | 37.33s | ✅ | 2 | 1. sandbox_execute_code2. sandbox_execute_code | - |
| 8 | Execute Python code to count the number of vowels (a, e, i, o, u, case-insensitive) in the string "Model Context Protocol". Return just the number. | 17.14s | ✅ | 1 | 1. sandbox_execute_code | - |
| 9 | Execute bash command 'echo "MCP_TEST"' and return the output (without quotes or extra whitespace). | 4.56s | ✅ | 1 | 1. sandbox_execute_bash | - |
| 10 | Use bash to count the number of folders in /tmp directory. Use the command: ls -l /tmp | grep ^d | wc -l. Return just the number. | 10.94s | ✅ | 1 |
browser
| # | Prompt | 操作时间 | 是否成功 | 工具调用数量 | 操作步骤 | 失败原因 |
|---|---|---|---|---|---|---|
| 1 | Navigate to https://bytedance.com, get the page text content, and check if it contains the word 'ByteDance' or '字节跳动'. Return 'YES' if found, 'NO' otherwise. | 21.07s | ✅ | 2 | 1. sandbox_browser_navigate2. sandbox_browser_get_text | - |
| 2 | Navigate to https://bytedance.com, read all links on the page, and count how many links have an href attribute. Return just the number. | 7.33s | ✅ | 2 | 1. sandbox_browser_navigate2. sandbox_browser_read_links | - |
| 3 | Navigate to http://baidu.com, then use evaluate to execute JavaScript: 'document.querySelectorAll("a").length'. This should return the number of links on the page. Return just the number. | 9.24s | ✅ | 2 | 1. sandbox_browser_navigate2. sandbox_browser_evaluate | - |
| 4 | Navigate to http://baidu.com, then use evaluate to execute JavaScript: 'document.title'. Return the title. | 24.86s | ✅ | 2 | 1. sandbox_browser_navigate2. sandbox_browser_evaluate | - |
| 5 | Navigate to http://baidu.com, then use evaluate to execute: 'document.querySelectorAll("a").length'. Return the count of anchor elements. | 15.09s | ✅ | 2 | 1. sandbox_browser_navigate2. sandbox_browser_evaluate | - |
| 6 | Navigate to http://baidu.com, find the search input field (selector: '#kw' or 'input[name="wd"]'), use form_input_fill to fill it with value 'test_search'. Then use evaluate to execute: 'document.querySelector("#kw, input[name=\"wd\"]").value' to verify. Return the value. | 10.64s | ✅ | 3 | 1. sandbox_browser_navigate2. sandbox_browser_form_input_fill3. sandbox_browser_evaluate | - |
| 7 | Navigate to http://baidu.com. Find the search input field (selector: '#kw' or 'input[name="wd"]'), fill it with 'MCP test' using form_input_fill. Then use evaluate to get the value back and return it. | 9.14s | ✅ | 3 | 1. sandbox_browser_navigate2. sandbox_browser_form_input_fill3. sandbox_browser_evaluate | - |
| 8 | Navigate to http://baidu.com. Read all links using read_links, get the markdown content using get_markdown, and execute JavaScript using evaluate to get 'document.title'. Return the title. | 14.57s | ✅ | 4 | 1. sandbox_browser_navigate2. sandbox_browser_read_links3. sandbox_browser_get_markdown4. sandbox_browser_evaluate | - |
| 9 | Create a new tab using new_tab with URL http://baidu.com. Then use tab_list to list all tabs and count them. Return just the number of tabs. | 16.52s | ✅ | 2 | 1. sandbox_browser_new_tab2. sandbox_browser_tab_list | - |
| 10 | Use tab_list to get all tabs, then use switch_tab to switch to the first tab in the list. Use evaluate to execute: 'document.title' and return the title. | 7.37s | ✅ | 3 | 1. sandbox_browser_tab_list2. sandbox_browser_switch_tab3. sandbox_browser_evaluate | - |
| 11 | Use Google to search for "aio sandbox", navigate to the aio sandbox GitHub page, and output the main contributor of aio sandbox. | 66.69s | ✅ | 29 | 1. sandbox_browser_navigate2. sandbox_browser_get_clickable_elements3. sandbox_browser_form_input_fill4. sandbox_browser_form_input_fill5. sandbox_browser_press_key6. sandbox_browser_get_clickable_elements7. sandbox_browser_get_text8. sandbox_browser_get_clickable_elements9. sandbox_browser_navigate10. sandbox_browser_element_click11. sandbox_browser_get_text12. sandbox_browser_element_click13. sandbox_browser_get_clickable_elements14. sandbox_browser_navigate15. sandbox_browser_element_click16. sandbox_browser_get_text17. sandbox_browser_element_click18. sandbox_browser_get_clickable_elements19. sandbox_browser_element_click20. sandbox_browser_get_text21. sandbox_browser_evaluate22. sandbox_browser_evaluate23. sandbox_browser_evaluate24. sandbox_browser_navigate25. sandbox_browser_evaluate26. sandbox_browser_evaluate27. sandbox_browser_navigate28. sandbox_browser_navigate29. sandbox_browser_navigate | - |
| 12 | 使用 browser_get_download_list 获取下载列表。统计列表中的项目数量。返回数量(数字)。 | 19.26s | ✅ | 1 | 1. sandbox_browser_get_download_list | - |
| 13 | 创建 /tmp/download_test.html: Download 导航到 file:///tmp/download_test.html。使用 browser_get_clickable_elements 获取元素列表,找到下载链接并点击。等待 2 秒(使用 browser_execute_action WAIT)。使用 browser_get_download_list 获取下载列表。检查列表是否包含文件名 'test.txt'。如果包含返回 'DOWNLOADED',否则返回 'NOT_FOUND'。 | 19.39s | ✅ | 6 | 1. sandbox_file_operations2. sandbox_browser_navigate3. sandbox_browser_get_clickable_elements4. sandbox_browser_element_click5. sandbox_execute_bash6. sandbox_browser_get_download_list | - |
| 14 | 触发一个下载操作(创建包含下载链接的 HTML 页面并点击)。使用 browser_get_download_list 获取下载列表。检查返回的下载项是否包含 'filename' 字段。如果包含返回 'HAS_FILENAME',否则返回 'NO_FILENAME'。 | 90.57s | ✅ | 25 | 1. sandbox_file_operations2. sandbox_browser_navigate3. sandbox_browser_get_clickable_elements4. sandbox_browser_element_click5. sandbox_browser_get_download_list6. sandbox_browser_element_click7. sandbox_browser_get_download_list8. sandbox_file_operations9. sandbox_browser_navigate10. sandbox_browser_element_click11. sandbox_browser_get_download_list12. sandbox_browser_get_info13. sandbox_browser_get_text14. sandbox_browser_evaluate15. sandbox_browser_evaluate16. sandbox_browser_evaluate17. sandbox_browser_evaluate18. sandbox_browser_get_download_list19. sandbox_browser_navigate20. sandbox_browser_navigate21. sandbox_browser_get_download_list22. sandbox_browser_navigate23. sandbox_browser_get_download_list24. sandbox_browser_get_download_list25. sandbox_execute_bash | - |
browser_advanced
| # | Prompt | 操作时间 | 是否成功 | 工具调用数量 | 操作步骤 | 失败原因 |
|---|---|---|---|---|---|---|
| 1 | First, create a simple HTML file at /tmp/test_click.html with content: Click Me Then navigate to file:///tmp/test_click.html. Use browser_element_click to click the button with selector "#myBtn". Then use evaluate to get the button text: document.querySelector('#myBtn').innerText. Return the text. | 13.04s | ✅ | 6 | 1. sandbox_str_replace_editor2. sandbox_str_replace_editor3. sandbox_browser_navigate4. sandbox_browser_get_clickable_elements5. sandbox_browser_element_click6. sandbox_browser_evaluate | - |
| 2 | Create /tmp/multi_buttons.html with 3 buttons: Button 1 Button 2 Button 3 Navigate to it, get clickable elements, find the index of "Button 3", click it using browser_element_click with that index. Return 'SUCCESS' if clicked. | 17.58s | ✅ | 6 | 1. sandbox_str_replace_editor2. sandbox_str_replace_editor3. sandbox_file_operations4. sandbox_browser_navigate5. sandbox_browser_get_clickable_elements6. sandbox_browser_element_click | - |
| 3 | Create /tmp/form.html: Navigate to file:///tmp/form.html. Use browser_form_input_fill to fill "#username" with "testuser" and "#email" with "[email protected]". Then use evaluate to get both values and return them as "username:email". | 11.98s | ✅ | 7 | 1. sandbox_str_replace_editor2. sandbox_file_operations3. sandbox_browser_navigate4. sandbox_browser_form_input_fill5. sandbox_browser_form_input_fill6. sandbox_browser_evaluate7. sandbox_browser_evaluate | - |
| 4 | Create /tmp/keypress.html: Navigate to it, click on the input field, use browser_press_key to press "Enter". Use evaluate to get output div text. Return the key name. | 19.46s | ✅ | 7 | 1. sandbox_str_replace_editor2. sandbox_file_operations3. sandbox_browser_navigate4. sandbox_browser_get_clickable_elements5. sandbox_browser_element_click6. sandbox_browser_press_key7. sandbox_browser_evaluate | - |
| 5 | Create /tmp/hover.html: Hover Me Not Hovered Navigate to file:///tmp/hover.html, use browser_element_hover on "#hoverTarget". Use evaluate to get status div text. Return it. | 12.17s | ✅ | 5 | 1. sandbox_str_replace_editor2. sandbox_file_operations3. sandbox_browser_navigate4. sandbox_browser_element_hover5. sandbox_browser_evaluate | - |
| 6 | Create /tmp/scroll.html with a long page: Top Bottom Navigate to it. Use browser_scroll with y=2500. Use evaluate to get window.scrollY. Return 'SCROLLED' if value > 2000, else 'NOT_SCROLLED'. | 27.48s | ✅ | 6 | 1. sandbox_str_replace_editor2. sandbox_str_replace_editor3. sandbox_file_operations4. sandbox_browser_navigate5. sandbox_browser_scroll6. sandbox_browser_evaluate | - |
| 7 | Create /tmp/page1.html with content "Page 1" and /tmp/page2.html with content "Page 2". Navigate to file:///tmp/page1.html, then navigate to file:///tmp/page2.html. Use browser_go_back. Use evaluate to get document.body.innerText. Return the text. | 26.01s | ✅ | 7 | 1. sandbox_str_replace_editor2. sandbox_file_operations3. sandbox_file_operations4. sandbox_browser_navigate5. sandbox_browser_navigate6. sandbox_browser_go_back7. sandbox_browser_evaluate | - |
| 8 | Create /tmp/nav1.html ("Nav 1") and /tmp/nav2.html ("Nav 2"). Navigate to nav1, then nav2, then go back, then use browser_go_forward. Use evaluate to get document.body.innerText. Return it. | 10.90s | ✅ | 8 | 1. sandbox_str_replace_editor2. sandbox_str_replace_editor3. sandbox_str_replace_editor4. sandbox_browser_navigate5. sandbox_browser_navigate6. sandbox_browser_go_back7. sandbox_browser_go_forward8. sandbox_browser_evaluate | - |
| 9 | Create 3 new tabs with browser_new_tab (no URL). Use browser_tab_list to count tabs. Close one tab with browser_close_tab using index 1. Use tab_list again to count remaining tabs. Return the count difference (should be 1). | 10.93s | ✅ | 7 | 1. sandbox_browser_new_tab2. sandbox_browser_new_tab3. sandbox_browser_new_tab4. sandbox_browser_tab_list5. sandbox_browser_switch_tab6. sandbox_browser_close_tab7. sandbox_browser_tab_list | - |
| 10 | Create /tmp/select.html: Navigate to file:///tmp/select.html. Use browser_element_select to select option with value "2" from "#mySelect". Use evaluate to get the selected value. Return it. | 12.13s | ✅ | 5 | 1. sandbox_str_replace_editor2. sandbox_file_operations3. sandbox_browser_navigate4. sandbox_browser_element_select5. sandbox_browser_evaluate | - |
| 11 | Create /tmp/calculator.html: Add 0 Navigate to it. Fill num1 with "15", num2 with "27", click add button, get result. Return the result number. | 35.76s | ✅ | 9 | 1. sandbox_str_replace_editor2. sandbox_str_replace_editor3. sandbox_str_replace_editor4. sandbox_browser_navigate5. sandbox_browser_get_clickable_elements6. sandbox_browser_form_input_fill7. sandbox_browser_form_input_fill8. sandbox_browser_element_click9. sandbox_browser_evaluate | - |
| 12 | 创建 /tmp/multi_select.html: 导航到它。使用 browser_element_select 选择 value="banana",使用 selector="#fruits"。使用 evaluate 获取选中的值。返回该值。 | 13.07s | ✅ | 5 | 1. sandbox_str_replace_editor2. sandbox_file_operations3. sandbox_browser_navigate4. sandbox_browser_element_select5. sandbox_browser_evaluate | - |
| 13 | 创建包含下拉菜单的页面。使用 browser_get_clickable_elements 获取元素列表,找到 select 元素的索引。使用 browser_element_select 通过 index 参数选择 value="cherry"。验证选择是否成功。返回选中的值。 | 29.55s | ✅ | 11 | 1. sandbox_str_replace_editor2. sandbox_browser_new_tab3. sandbox_browser_get_clickable_elements4. sandbox_browser_element_select5. sandbox_browser_evaluate6. sandbox_browser_evaluate7. sandbox_browser_evaluate8. sandbox_browser_evaluate9. sandbox_browser_evaluate10. sandbox_browser_evaluate11. sandbox_browser_evaluate | - |
| 14 | 创建 /tmp/hover_selector.html,包含悬停菜单。使用 browser_element_hover 通过 selector="#menu" 悬停在菜单上。使用 evaluate 检查菜单是否显示。返回 'VISIBLE' 或 'HIDDEN'。 | 17.23s | ✅ | 9 | 1. sandbox_str_replace_editor2. sandbox_browser_new_tab3. sandbox_browser_element_hover4. sandbox_browser_evaluate5. sandbox_browser_evaluate6. sandbox_browser_evaluate7. sandbox_browser_evaluate8. sandbox_browser_evaluate9. sandbox_browser_evaluate | - |
| 15 | 创建 /tmp/tooltip.html: Hover Me Tooltip Text 导航到它。使用 browser_element_hover 悬停在按钮上(selector="#btn")。使用 evaluate 获取 tooltip 的 display 样式。如果是 'block' 返回 'SHOWN',否则返回 'HIDDEN'。 | 20.44s | ✅ | 8 | 1. sandbox_str_replace_editor2. sandbox_str_replace_editor3. sandbox_str_replace_editor4. sandbox_browser_navigate5. sandbox_browser_element_hover6. sandbox_browser_evaluate7. sandbox_browser_evaluate8. sandbox_browser_evaluate | - |
| 16 | 创建长页面 /tmp/scroll_up.html(高度 3000px)。导航到它。使用 browser_scroll 滚动 amount=2000(向下)。然后使用 browser_scroll amount=-1000(向上)。使用 evaluate 获取 window.scrollY。如果 scrollY 在 900-1100 之间返回 'CORRECT',否则返回 'INCORRECT'。 | 19.47s | ✅ | 6 | 1. sandbox_str_replace_editor2. sandbox_browser_navigate3. sandbox_browser_scroll4. sandbox_browser_scroll5. sandbox_browser_evaluate6. sandbox_browser_evaluate | - |
| 17 | 创建长页面。导航到它。使用 browser_scroll 不带 amount 参数(应该滚动到底部)。使用 evaluate 执行: window.scrollY + window.innerHeight >= document.body.scrollHeight。如果返回 true 返回 'AT_BOTTOM',否则返回 'NOT_AT_BOTTOM'。 | 32.05s | ✅ | 9 | 1. sandbox_str_replace_editor2. sandbox_browser_navigate3. sandbox_browser_scroll4. sandbox_browser_evaluate5. sandbox_browser_evaluate6. sandbox_browser_scroll7. sandbox_browser_evaluate8. sandbox_browser_scroll9. sandbox_browser_evaluate | - |
| 18 | 创建 /tmp/special_keys.html,包含一个输入框和键盘事件监听器。点击输入框。使用 browser_press_key 按 'Tab' 键。使用 evaluate 检查焦点是否移动。返回 'TAB_PRESSED' 或 'NO_EFFECT'。 | 30.05s | ✅ | 8 | 1. sandbox_str_replace_editor2. sandbox_str_replace_editor3. sandbox_file_operations4. sandbox_browser_navigate5. sandbox_browser_element_click6. sandbox_browser_evaluate7. sandbox_browser_press_key8. sandbox_browser_evaluate | - |
| 19 | 创建 /tmp/key_combo.html: 导航到它。点击输入框。使用 browser_press_key 按 'Delete'。使用 evaluate 获取 #result 的文本。返回该文本。 | 11.31s | ✅ | 6 | 1. sandbox_str_replace_editor2. sandbox_browser_navigate3. sandbox_browser_get_clickable_elements4. sandbox_browser_element_click5. sandbox_browser_press_key6. sandbox_browser_evaluate | - |
| 20 | 创建 3 个新标签页。使用 browser_tab_list 获取所有标签页。使用 browser_switch_tab 切换到索引 1 的标签页。使用 browser_close_tab 关闭该标签页。再次使用 tab_list 获取标签页列表。统计剩余标签页数量并返回。 | 16.18s | ✅ | 7 | 1. sandbox_browser_new_tab2. sandbox_browser_new_tab3. sandbox_browser_new_tab4. sandbox_browser_tab_list5. sandbox_browser_switch_tab6. sandbox_browser_close_tab7. sandbox_browser_tab_list | - |
| 21 | 使用 browser_tab_list 获取当前标签页数量。如果只有 1 个标签页,记录为 'SINGLE_TAB'。使用 browser_close_tab 关闭它。使用 tab_list 检查是否自动创建了新标签页。如果有标签页存在返回 'AUTO_CREATED',否则返回 'NO_TABS'。 | 13.86s | ✅ | 3 | 1. sandbox_browser_tab_list2. sandbox_browser_close_tab3. sandbox_browser_tab_list | - |
| 22 | 创建 /tmp/elements.html: Button 1 Link 1 Button 2 导航到它。使用 browser_get_clickable_elements 获取元素列表。使用 Python 代码解析返回的元素,统计 button 类型的元素数量。返回数量。 | 9.90s | ✅ | 4 | 1. sandbox_str_replace_editor2. sandbox_browser_navigate3. sandbox_browser_get_clickable_elements4. sandbox_execute_code | - |
| 23 | 创建复杂页面包含多种可交互元素(按钮、链接、输入框、选择框、图片链接等)。使用 browser_get_clickable_elements 获取所有元素。统计总元素数量。如果数量 >= 5 返回 'MANY_ELEMENTS',否则返回 'FEW_ELEMENTS'。 | 41.79s | ✅ | 4 | 1. sandbox_browser_new_tab2. sandbox_browser_get_clickable_elements3. sandbox_browser_new_tab4. sandbox_browser_get_clickable_elements | - |
| 24 | 使用 sandbox_browser_get_info 获取浏览器信息,检查返回结果是否包含 viewport 信息。如果包含 width 和 height 字段返回 'YES',否则返回 'NO'。 | 4.90s | ✅ | 1 | 1. sandbox_browser_get_info | - |
| 25 | 使用 sandbox_browser_get_info 获取浏览器信息,提取 cdp_url 字段。如果 cdp_url 以 'ws://' 或 'wss://' 开头返回 'VALID',否则返回 'INVALID'。 | 6.68s | ✅ | 2 | 1. sandbox_browser_get_info2. sandbox_execute_code | - |
| 26 | 使用 sandbox_browser_get_info 获取浏览器视口信息。计算视口面积(width * height),如果面积大于 500000 返回 'LARGE',否则返回 'SMALL'。 | 8.07s | ✅ | 2 | 1. sandbox_browser_get_info2. sandbox_execute_code | - |
code_advanced
| # | Prompt | 操作时间 | 是否成功 | 工具调用数量 | 操作步骤 | 失败原因 |
|---|---|---|---|---|---|---|
| 1 | Execute JavaScript code: "console.log(10 + 20); console.log(5 * 6);" and return the output. The output should contain both numbers. | 10.57s | ✅ | 4 | 1. sandbox_browser_evaluate2. sandbox_browser_evaluate3. sandbox_browser_evaluate4. sandbox_execute_code | - |
| 2 | Execute JavaScript code to reverse a string: "const str = 'Hello MCP'; const reversed = str.split('').reverse().join(''); console.log(reversed);". Return the output. | 9.10s | ✅ | 2 | 1. sandbox_browser_evaluate2. sandbox_browser_evaluate | - |
| 3 | Execute JavaScript: "const arr = [1, 2, 3, 4, 5]; const sum = arr.reduce((a, b) => a + b, 0); console.log(sum);". Return just the number. | 10.81s | ✅ | 2 | 1. sandbox_browser_evaluate2. sandbox_execute_code | - |
| 4 | Execute JavaScript: "const json = '{"name":"test","value":42}'; const obj = JSON.parse(json); console.log(obj.value);". Return the value. | 5.20s | ✅ | 2 | 1. sandbox_browser_evaluate2. sandbox_browser_evaluate | - |
| 5 | Execute Python code: "squares = [x**2 for x in range(1, 6)]; print(sum(squares))". Return the sum. | 10.06s | ✅ | 1 | 1. sandbox_execute_code | - |
| 6 | Execute Python: "data = {'a': 1, 'b': 2, 'c': 3}; result = sum(data.values()); print(result)". Return the number. | 6.94s | ✅ | 1 | 1. sandbox_execute_code | - |
| 7 | Execute Python: "nums = [1, 2, 3, 4]; doubled = list(map(lambda x: x * 2, nums)); print(sum(doubled))". Return the sum. | 5.56s | ✅ | 1 | 1. sandbox_execute_code | - |
| 8 | Execute Python code that creates a file, writes to it, and reads it back: python with open('/tmp/test_code_io.txt', 'w') as f: f.write('Data from code execution') with open('/tmp/test_code_io.txt', 'r') as f: content = f.read() print(len(content)) Return the length. |
13.50s | ✅ | 1 | 1. sandbox_execute_code | - |
| 9 | Execute JavaScript: "let result = 0; for(let i = 1; i <= 10; i++) { result += i; } console.log(result);". Return the sum. | 7.26s | ✅ | 2 | 1. sandbox_browser_evaluate2. sandbox_browser_evaluate | - |
| 10 | Execute Python code: "try:\n x = 10 / 0\nexcept ZeroDivisionError:\n x = -1\nprint(x)". Return the value. | 4.73s | ✅ | 1 | 1. sandbox_execute_code | - |
| 11 | Execute JavaScript: "let result; try { result = JSON.parse('invalid json'); } catch(e) { result = 'ERROR'; } console.log(result);". Return the output. | 8.95s | ✅ | 2 | 1. sandbox_browser_evaluate2. sandbox_browser_evaluate | - |
| 12 | Execute Python: "import math; result = math.sqrt(144) + math.pow(2, 3); print(int(result))". Return the number. | 6.62s | ✅ | 1 | 1. sandbox_execute_code | - |
| 13 | Execute JavaScript: "const result = Math.sqrt(64) + Math.pow(3, 2); console.log(result);". Return the number. | 6.33s | ✅ | 3 | 1. sandbox_browser_evaluate2. sandbox_browser_evaluate3. sandbox_browser_evaluate | - |
| 14 | Execute Python: "import re; text = '[email protected] and [email protected]'; emails = re.findall(r'[\w.-]+@[\w.-]+', text); print(len(emails))". Return the count. | 6.06s | ✅ | 1 | 1. sandbox_execute_code | - |
| 15 | Execute JavaScript: "const text = 'abc123def456'; const numbers = text.match(/\d+/g); console.log(numbers.length);". Return the count. | 6.69s | ✅ | 2 | 1. sandbox_browser_evaluate2. sandbox_browser_evaluate | - |
| 16 | Execute Python: "a = {1, 2, 3, 4}; b = {3, 4, 5, 6}; intersection = a & b; print(len(intersection))". Return the count. | 6.20s | ✅ | 1 | 1. sandbox_execute_code | - |
| 17 | Execute Python: "data = [10, 25, 30, 15, 40]; filtered = [x for x in data if x > 20]; avg = sum(filtered) / len(filtered); print(int(avg))". Return the average. | 8.25s | ✅ | 1 | 1. sandbox_execute_code | - |
| 18 | Execute JavaScript: "const data = [5, 10, 15, 20]; const result = data.filter(x => x > 10).map(x => x * 2).reduce((a, b) => a + b, 0); console.log(result);". Return the result. | 6.00s | ✅ | 2 | 1. sandbox_browser_evaluate2. sandbox_browser_evaluate | - |
collaboration
| # | Prompt | 操作时间 | 是否成功 | 工具调用数量 | 操作步骤 | 失败原因 |
|---|---|---|---|---|---|---|
| 1 | Create a file at /tmp/main.py with some async function. Read the file /tmp/main.py and use Python code to count how many times the word 'async' appears in it. Return just the number. | 15.01s | ✅ | 4 | 1. sandbox_str_replace_editor2. sandbox_file_operations3. sandbox_file_operations4. sandbox_execute_code | - |
| 2 | Create a Python script at /tmp/fibonacci.py with this code: 'def fib(n): return n if n <= 1 else fib(n-1) + fib(n-2)\nprint(fib(10))'. Then execute it and return the output number. | 13.88s | ✅ | 3 | 1. sandbox_str_replace_editor2. sandbox_file_operations3. sandbox_execute_code | - |
| 3 | Use bash to create directory /tmp/eval_test (if it doesn't exist), then create a file /tmp/eval_test/data.txt with content "Sandbox Test" using file operations. Read the file back and return its content. | 7.38s | ✅ | 3 | 1. sandbox_execute_bash2. sandbox_file_operations3. sandbox_file_operations | - |
| 4 | Use bash to get the current working directory (use: pwd). Write the result to /tmp/current_dir.txt using file operations. Then read the file back and return the content. | 11.79s | ✅ | 3 | 1. sandbox_execute_bash2. sandbox_file_operations3. sandbox_file_operations | - |
| 5 | Get the list of installed Python packages, then use Python code to parse the output and count how many package names contain the substring 'fast' (case-insensitive). Return just the number. | 59.92s | ✅ | 2 | 1. sandbox_get_packages2. sandbox_execute_code | - |
| 6 | Create an HTML file at /tmp/test_page.html with content "Test PageMCP Testing". Navigate to file:///tmp/test_page.html with browser. Get the page text and check if it contains "MCP Testing". Return 'YES' if found, 'NO' otherwise. |
8.53s | ✅ | 3 | 1. sandbox_file_operations2. sandbox_browser_navigate3. sandbox_browser_get_text | - |
| 7 | Use str_replace_editor to create /tmp/calc.py with content "def add(a, b):\n return a + b\nprint(add(15, 27))". Then execute the Python file and return the output number. | 9.25s | ✅ | 3 | 1. sandbox_str_replace_editor2. sandbox_str_replace_editor3. sandbox_execute_code | - |
| 8 | Create /tmp/counter.py with "counter = 0\nprint(counter)". Execute it (should print 0). Then use str_replace_editor to replace "counter = 0" with "counter = 100". Execute again and return the new output. | 14.25s | ✅ | 6 | 1. sandbox_str_replace_editor2. sandbox_str_replace_editor3. sandbox_str_replace_editor4. sandbox_execute_bash5. sandbox_str_replace_editor6. sandbox_execute_bash | - |
| 9 | Use bash to create /tmp/data_dir directory. Create /tmp/data_dir/numbers.txt with numbers "1\n2\n3\n4\n5" using file operations. Use Python code to read the file, sum the numbers, and write the result to /tmp/data_dir/sum.txt. Read sum.txt and return its content. | 16.71s | ✅ | 6 | 1. sandbox_execute_bash2. sandbox_str_replace_editor3. sandbox_execute_bash4. sandbox_str_replace_editor5. sandbox_execute_code6. sandbox_file_operations | - |
| 10 | Use get_context to get sandbox version info. Extract the version string (like v1.0.0.xxx). Write it to /tmp/version.txt using file operations. Read the file back and verify it starts with 'v'. Return 'VALID_VERSION' if it matches pattern v*...*, else 'INVALID'. | 21.71s | ✅ | 5 | 1. sandbox_get_context2. sandbox_str_replace_editor3. sandbox_file_operations4. sandbox_file_operations5. sandbox_execute_code | - |
| 11 | Create /tmp/dom_test.html with "ABC". Navigate to it. Use browser_evaluate to get all div texts: "Array.from(document.querySelectorAll('.item')).map(d => d.innerText).join(',')". Use Python to split by comma and count items. Return the count. | 15.82s | ✅ | 5 | 1. sandbox_str_replace_editor2. sandbox_file_operations3. sandbox_browser_navigate4. sandbox_browser_evaluate5. sandbox_execute_code | - |
| 12 | Create 3 files: /tmp/test1.txt (content: "hello"), /tmp/test2.txt (content: "world"), /tmp/test_concat.txt (content: ""). Use file operations to concatenate the content of the 3 files into /tmp/test_concat.txt. for "hello world" content, the content of /tmp/test_concat.txt should be "hello world". Return the content of /tmp/test_concat.txt. | 10.18s | ✅ | 7 | 1. sandbox_file_operations2. sandbox_file_operations3. sandbox_file_operations4. sandbox_file_operations5. sandbox_file_operations6. sandbox_file_operations7. sandbox_file_operations | - |
| 13 | Create /tmp/log.txt with content "ERROR: failed\nINFO: success\nERROR: timeout\nINFO: done". Use file operations to search for "ERROR" pattern. Count the matches and return just the number. | 8.50s | ✅ | 2 | 1. sandbox_file_operations2. sandbox_file_operations | - |
editor
| # | Prompt | 操作时间 | 是否成功 | 工具调用数量 | 操作步骤 | 失败原因 |
|---|---|---|---|---|---|---|
| 1 | Use str_replace_editor to view the file /tmp/test_editor.txt. First create it with content "Line 1\nLine 2\nLine 3\nLine 4\nLine 5" using file_operations. Then view it and return the content. | 6.22s | ✅ | 2 | 1. sandbox_file_operations2. sandbox_str_replace_editor | - |
| 2 | Create /tmp/test_range.txt with 10 lines (Line 1 to Line 10) using file_operations. Then use str_replace_editor with view_range [2, 5] to view a subset of lines. Return the viewed content. | 7.45s | ✅ | 2 | 1. sandbox_file_operations2. sandbox_str_replace_editor | - |
| 3 | Use str_replace_editor to create a new file /tmp/editor_created.py with content "def hello():\n return 'Hello World'". Then read it back with file_operations and return the content. | 8.30s | ✅ | 2 | 1. sandbox_str_replace_editor2. sandbox_file_operations | - |
| 4 | Create /tmp/replace_test.txt with content "Hello World\nHello Python\nHello MCP" using file_operations. Then use str_replace_editor to replace "Hello" with "Hi" in the first occurrence. Read the file back and return its content. | 10.67s | ✅ | 3 | 1. sandbox_file_operations2. sandbox_str_replace_editor3. sandbox_file_operations | - |
| 5 | Create /tmp/multiline.py with content "def old_func():\n pass\n\ndef other():\n return 1". Use str_replace_editor to replace "def old_func():\n pass" with "def new_func():\n return 42". Return the new content. | 11.81s | ✅ | 4 | 1. sandbox_str_replace_editor2. sandbox_file_operations3. sandbox_str_replace_editor4. sandbox_str_replace_editor | - |
| 6 | Create /tmp/insert_test.txt with 3 lines "Line 1\nLine 2\nLine 3". Use str_replace_editor to insert "Inserted Line" at line 1 (after Line 1). Return the content. | 15.14s | ✅ | 4 | 1. sandbox_str_replace_editor2. sandbox_file_operations3. sandbox_str_replace_editor4. sandbox_file_operations | - |
| 7 | Create /tmp/refactor.py with content "def calculate(x, y):\n result = x + y\n return result". Use str_replace_editor to replace the function body to use one line: "return x + y". Then execute the file and call calculate(5, 3). Return the result. | 69.79s | ✅ | 3 | 1. sandbox_str_replace_editor2. sandbox_str_replace_editor3. sandbox_execute_bash | - |
| 8 | Use str_replace_editor to create /tmp/empty.txt with empty content "". Then use file_operations to check if file exists and is empty. Return 'EMPTY' if file exists and has 0 bytes, otherwise return 'NOT_EMPTY'. | 14.50s | ✅ | 4 | 1. sandbox_str_replace_editor2. sandbox_file_operations3. sandbox_file_operations4. sandbox_file_operations | - |
| 9 | Use str_replace_editor to create /tmp/special.txt with content containing special chars: "Hello\tWorld\n$PATH=/usr/bin\n"quoted"". Read it back and count the number of lines. Return just the number. | 14.19s | ✅ | 4 | 1. sandbox_str_replace_editor2. sandbox_str_replace_editor3. sandbox_str_replace_editor4. sandbox_execute_code | - |
| 10 | 使用 str_replace_editor 创建 /tmp/undo_test.txt,内容为 "Original Content"。然后使用 str_replace 将内容替换为 "Modified Content"。使用 undo_edit 命令撤销上一次编辑。使用 file_operations read 读取文件内容。如果内容是 "Original Content" 返回 'UNDONE',如果是 "Modified Content" 返回 'NOT_UNDONE',否则返回 'ERROR'。 | 12.97s | ✅ | 5 | 1. sandbox_str_replace_editor2. sandbox_file_operations3. sandbox_str_replace_editor4. sandbox_str_replace_editor5. sandbox_file_operations | - |
| 11 | 使用 str_replace_editor 创建 /tmp/multi_undo.txt,内容为 "Version 1"。执行 3 次替换:将内容改为 "Version 2",然后 "Version 3",然后 "Version 4"。执行 2 次 undo_edit。读取文件内容,应该回到 "Version 2"。返回当前版本号(如 "Version 2")。 | 11.36s | ✅ | 8 | 1. sandbox_str_replace_editor2. sandbox_file_operations3. sandbox_str_replace_editor4. sandbox_str_replace_editor5. sandbox_str_replace_editor6. sandbox_str_replace_editor7. sandbox_str_replace_editor8. sandbox_str_replace_editor | - |
| 12 | 使用 str_replace_editor 创建 /tmp/new_undo.txt,内容为 "New File"。立即使用 undo_edit 尝试撤销创建操作。使用 file_operations 检查文件是否仍然存在。如果文件不存在返回 'FILE_REMOVED',如果存在返回 'FILE_EXISTS'。 | 56.30s | ✅ | 42 | 1. sandbox_str_replace_editor2. sandbox_file_operations3. sandbox_file_operations4. sandbox_str_replace_editor5. sandbox_file_operations6. sandbox_file_operations7. sandbox_str_replace_editor8. sandbox_file_operations9. sandbox_file_operations10. sandbox_str_replace_editor11. sandbox_str_replace_editor12. sandbox_file_operations13. sandbox_file_operations14. sandbox_str_replace_editor15. sandbox_str_replace_editor16. sandbox_file_operations17. sandbox_str_replace_editor18. sandbox_str_replace_editor19. sandbox_str_replace_editor20. sandbox_file_operations21. sandbox_file_operations22. sandbox_str_replace_editor23. sandbox_str_replace_editor24. sandbox_str_replace_editor25. sandbox_file_operations26. sandbox_file_operations27. sandbox_str_replace_editor28. sandbox_str_replace_editor29. sandbox_file_operations30. sandbox_file_operations31. sandbox_str_replace_editor32. sandbox_str_replace_editor33. sandbox_str_replace_editor34. sandbox_str_replace_editor35. sandbox_str_replace_editor36. sandbox_file_operations37. sandbox_file_operations38. sandbox_str_replace_editor39. sandbox_str_replace_editor40. sandbox_file_operations41. sandbox_str_replace_editor42. sandbox_file_operations | - |
error
| # | Prompt | 操作时间 | 是否成功 | 工具调用数量 | 操作步骤 | 失败原因 |
|---|---|---|---|---|---|---|
| 1 | Try to read a non-existent file at /tmp/this_file_absolutely_does_not_exist_xyz123.txt. If you get an error, return the string 'FILE_NOT_FOUND'. Otherwise return 'FILE_EXISTS'. | 6.22s | ✅ | 1 | 1. sandbox_file_operations | - |
| 2 | Execute Python code that raises a ZeroDivisionError: '1/0'. If an error occurs, return 'ERROR_CAUGHT'. If no error, return 'NO_ERROR'. | 21.10s | ✅ | 2 | 1. sandbox_execute_code2. sandbox_execute_code | - |
fix
| # | Prompt | 操作时间 | 是否成功 | 工具调用数量 | 操作步骤 | 失败原因 |
|---|---|---|---|---|---|---|
| 1 | Create a file at /tmp/main.py with some async function. Read the file /tmp/main.py and use Python code to count how many times the word 'async' appears in it. Return just the number. | 15.67s | ✅ | 3 | 1. sandbox_str_replace_editor2. sandbox_str_replace_editor3. sandbox_execute_code | - |
| 2 | Create 3 files: /tmp/test1.txt (content: "hello"), /tmp/test2.txt (content: "world"), /tmp/test_concat.txt (content: ""). Use file operations to concatenate the content of the 3 files into /tmp/test_concat.txt. for "hello world" content, the content of /tmp/test_concat.txt should be "hello world". Return the content of /tmp/test_concat.txt. | 12.01s | ✅ | 7 | 1. sandbox_file_operations2. sandbox_file_operations3. sandbox_file_operations4. sandbox_file_operations5. sandbox_file_operations6. sandbox_file_operations7. sandbox_file_operations | - |
packages
| # | Prompt | 操作时间 | 是否成功 | 工具调用数量 | 操作步骤 | 失败原因 |
|---|---|---|---|---|---|---|
| 1 | Use get_packages with language='nodejs' to get all installed Node.js packages. Check if 'express' is in the list. Return 'YES' if found, 'NO' otherwise. | 17.66s | ✅ | 1 | 1. sandbox_get_packages | - |
| 2 | Use get_packages with language='nodejs' to get Node.js packages. Use Python code to parse the output and count how many packages are installed. Return just the number. | 11.95s | ✅ | 2 | 1. sandbox_get_packages2. sandbox_execute_code | - |
| 3 | Use get_packages without specifying language parameter. This should return both Python and Node.js packages. Check if the output contains both "Python Packages:" and "Node.js Packages:" sections. Return 'BOTH' if both sections exist, 'PARTIAL' if only one exists, 'NONE' if neither. | 5.95s | ✅ | 1 | 1. sandbox_get_packages | - |
| 4 | Use get_packages with language='node' (alias for nodejs). Check if it returns Node.js packages. Return 'SUCCESS' if packages are listed, 'FAILED' otherwise. | 7.85s | ✅ | 2 | 1. sandbox_get_packages2. sandbox_get_packages | - |
| 5 | Use get_packages with language='py' (alias for python). Count the number of packages. Return just the number. | 10.79s | ✅ | 2 | 1. sandbox_get_packages2. sandbox_get_packages | - |
| 6 | Use get_packages for Python packages. Use Python code to check if packages containing 'test' in their names exist. Return the count of such packages. | 41.74s | ✅ | 2 | 1. sandbox_get_packages2. sandbox_execute_code | - |
| 7 | Use get_packages for Python. Use Python code to extract the version number of 'pip' package (format: pip==X.Y.Z). Return just the version number without 'pip=='. | 24.06s | ✅ | 2 | 1. sandbox_get_packages2. sandbox_execute_code | - |
| 8 | Use get_packages without language parameter to get all packages. Count total number of packages (Python + Node.js). Return just the total number. | 5.21s | ✅ | 1 | 1. sandbox_get_packages | - |
ping
| # | Prompt | 操作时间 | 是否成功 | 工具调用数量 | 操作步骤 | 失败原因 |
|---|---|---|---|---|---|---|
| 1 | answer the aio system's version. like v1.0.0.1xx | 20.22s | ✅ | 1 | 1. sandbox_get_context | - |
util
| # | Prompt | 操作时间 | 是否成功 | 工具调用数量 | 操作步骤 | 失败原因 |
|---|---|---|---|---|---|---|
| 1 | Use convert_to_markdown to convert the page at http://baidu.com to markdown. Check if the output contains the text '百度' or 'Baidu'. Return 'YES' if found, 'NO' otherwise. | 26.82s | ✅ | 2 | 1. sandbox_convert_to_markdown2. sandbox_convert_to_markdown | - |
| 2 | Use convert_to_markdown to convert http://baidu.com to markdown. Count how many times the word '百度' appears in the markdown output. Return just the number. | 35.36s | ✅ | 4 | 1. sandbox_convert_to_markdown2. sandbox_execute_bash3. sandbox_convert_to_markdown4. sandbox_execute_code | - |
workflow
| # | Prompt | 操作时间 | 是否成功 | 工具调用数量 | 操作步骤 | 失败原因 |
|---|---|---|---|---|---|---|
| 1 | Find all .py files in /tmp directory that contain the word 'def'. Use file operations to search. Return the count of files found. | 18.75s | ✅ | 8 | 1. sandbox_file_operations2. sandbox_file_operations3. sandbox_file_operations4. sandbox_file_operations5. sandbox_file_operations6. sandbox_file_operations7. sandbox_file_operations8. sandbox_file_operations | - |
| 2 | Read the file /tmp/main.py and use Python code with AST parsing to count the number of function definitions (async and sync). Return just the number. | 9.49s | ✅ | 2 | 1. sandbox_file_operations2. sandbox_execute_code | - |
| 3 | Read /tmp/evaluation.xml, use Python code to parse it as XML and count the total number of |
9.77s | ✅ | 2 | 1. sandbox_file_operations2. sandbox_execute_code | - |
| 4 | Search "@agent-infra/sandbox", find the contributors of this repo. Output the contributors of aio sandbox. Write the contributors to /tmp/contributors.html use beautiful style's html to write the contributors. Show the contributors in a table. Open browser to /tmp/contributors.html and show the contributors. Return the contributors' names of aio sandbox. | 184.83s | ✅ | 19 | 1. sandbox_browser_new_tab2. sandbox_browser_navigate3. sandbox_browser_get_text4. sandbox_browser_navigate5. sandbox_browser_element_click6. sandbox_browser_get_text7. sandbox_browser_get_clickable_elements8. sandbox_browser_element_click9. sandbox_browser_get_text10. sandbox_browser_navigate11. sandbox_browser_get_markdown12. sandbox_browser_evaluate13. sandbox_browser_evaluate14. sandbox_browser_evaluate15. sandbox_str_replace_editor16. sandbox_str_replace_editor17. sandbox_file_operations18. sandbox_file_operations19. sandbox_browser_navigate | - |
nextjs
| # | Prompt | 操作时间 | 是否成功 | 工具调用数量 | 操作步骤 | 失败原因 |
|---|---|---|---|---|---|---|
| 1 | 完成以下完整的 Next.js 全栈工作流: 1. 清理环境 (防止磁盘空间不足): - 运行: rm -rf /tmp/my-nextjs-app /tmp/test-nextjs-app - 运行: npm cache clean --force 2. 创建项目: - 在 /tmp 目录下运行: npx --yes create-next-app@latest my-nextjs-app --yes - 等待项目创建完成 3. 添加 API 路由: - 进入项目目录 /tmp/my-nextjs-app - 创建 app/api/hello/route.ts 文件,内容: typescript export async function GET() { return Response.json({ message: "Hello from API", status: "success" }) } 4. 启动开发服务器: - 在项目目录中,后台运行: npm run dev -- -p 3500 - 等待 10 秒确保服务完全启动 5. 验证网站访问: - 使用 sandbox_browser_navigate 打开 http://localhost:3500 - 等待 2 秒页面加载 - 使用 sandbox_browser_evaluate 执行 document.title,确认页面已加载 6. 验证 API 请求: - 使用 sandbox_browser_navigate 打开 http://localhost:3500/api/hello - 使用 sandbox_browser_evaluate 执行: document.body.innerText - 解析返回的 JSON,提取 status 字段的值并返回 返回最终的 status 字段值。 注意: - 使用 run_in_background 参数运行 npm run dev - 确保等待足够时间让 npm 安装依赖和启动服务 |
201.36s | ✅ | 14 | 1. sandbox_execute_bash2. sandbox_execute_bash3. sandbox_execute_bash4. sandbox_file_operations5. sandbox_execute_bash6. sandbox_execute_bash7. sandbox_execute_bash8. sandbox_browser_navigate9. sandbox_execute_bash10. sandbox_browser_evaluate11. sandbox_browser_navigate12. sandbox_browser_evaluate13. sandbox_execute_bash14. sandbox_execute_code | - |
File: examples/README.md
Agent Sandbox Examples
This directory contains examples demonstrating various use cases and integrations with agent-sandbox.
Table of Contents
- basic-file-operations - Core file operations (upload, read, list, download)
- oss-upload - Stream sandbox files to cloud storage (Volcengine TOS, AWS S3)
- volcengine-provider - Volcengine cloud provider integration
- site-to-markdown - Website to markdown conversion with browser automation
- browser-use-integration - AI-driven browser automation
- playwright-integration - Browser automation with Playwright
- openai-integration - OpenAI function calling with code execution
- ag2-integration - AG2 multi-agent workflow with sandbox-backed code execution
- minimax-integration - MiniMax function calling with code execution (OpenAI-compatible)
- langgraph-deepagents-integration - LangGraph deep agent with MCP tools integration
- code-execute - Execute code in sandbox (Jupyter and Node.js)
Prerequisites
All examples require:
- Python 3.12+
- A running sandbox instance (most examples use
http://localhost:8080) - uv package manager
Contributing
We welcome contributions of new examples!
Quick Start (Easiest Way)
Copy the _template directory and start coding:
# Copy template to your new example
cp -r _template my-example-name
cd my-example-name
# Set up environment
cp .env.example .env
# Start coding in main.py
# Run it: uv run main.pyThe template includes all necessary files - just modify main.py with your code!
Before Submitting
Test your example:
bashuv run main.pyUpdate the main
examples/README.mdTable of ContentsKeep it focused - one clear purpose per example
Example Naming
- Use kebab-case:
browser-automation,file-operations - For integrations:
openai-integration,langgraph-deepagents-integration
Need Help?
- Check existing examples for reference
- Review the Agent Sandbox Documentation
- Open an issue for questions or suggestions
File: examples/_template/README.md
Your Example Name
Brief description of what this example demonstrates.
Quick Start
- Start the sandbox
# Recommended: Enable API Key authentication (protects all services: API, JupyterLab, VNC)
# - Supports three methods: X-AIO-API-Key header, Authorization: Bearer header, ?api_key= query parameter
# - Without SANDBOX_API_KEY, services remain open (backward compatible)
docker run --security-opt seccomp=unconfined --rm -it \
-e SANDBOX_API_KEY=your-secret-key \
-p 127.0.0.1:8080:8080 ghcr.io/agent-infra/sandbox:latest- Configure the environment
# 1. Copy .env.example to .env
cp .env.example .env
# 2. Run the example
uv run main.pyWhat This Does
Explain what happens when you run this example.
Customize
Modify main.py to add your own logic.
File: examples/ag2-integration/README.md
AG2 Integration Example
This example demonstrates how to run an AG2 multi-agent workflow against Agent Sandbox.
Prerequisites
- A running sandbox instance at
http://localhost:8080 - Python 3.12+
- OpenAI API key
Quick Start
# Start sandbox
docker run --security-opt seccomp=unconfined --rm -it -p 127.0.0.1:8080:8080 ghcr.io/agent-infra/sandbox:latest
# Configure env
cp .env.example .env
# Install dependencies and run
uv sync
uv run main.pyWhat This Example Does
- Creates persistent Jupyter and shell sessions in the sandbox
- Registers sandbox-backed AG2 tools for Python, shell, and file operations
- Runs a Coder + Reviewer group chat against the Iris dataset
- Verifies generated artifacts in
/home/gem - Cleans up sandbox sessions before exit
Optional Smoke Test
smoke_test.ipynb is a step-by-step notebook for validating the SDK calls used by main.py before running the full multi-agent flow.
File: examples/basic-file-operations/README.md
Basic File Operations Example
This example demonstrates basic file operations with agent-sandbox, including:
- Uploading files to the sandbox
- Listing files in the sandbox
- Reading files from the sandbox
- Downloading files from the sandbox
Prerequisites
- A running sandbox instance at
http://localhost:8080 - Python 3.11+
Running the Example
uv run main.pyWhat This Example Does
- Reads a local image file (
foo.png) - Uploads it to the sandbox at
/home/sandbox/test.png - Lists files in the sandbox directory
- Reads the file back from the sandbox
- Downloads the file from the sandbox to verify the operation
File: examples/browser-use-cua/README.md
Browser-Use Integration Example
This example demonstrates how to integrate browser-use with agent-sandbox, allowing you to use browser automation agents with a sandboxed browser.
Prerequisites
- A running sandbox instance at
http://localhost:8080 - Python 3.11+
- OpenAI API key or compatible LLM endpoint
Running the Example
# Set your LLM API credentials
export OPENAI_API_KEY="your_api_key"
# Run the example
uv run main.pyWhat This Example Does
- Connects to the sandbox browser via CDP
- Creates a browser-use Agent with the sandboxed browser session
- Executes an AI-driven browser automation task
- The agent will visit DuckDuckGo and search for "browser-use founders"
Configuration
You can modify the task in main.py:
agent = Agent(
task='Visit https://duckduckgo.com and search for "browser-use founders"',
llm=ChatOpenAI(model="gcp-claude4.1-opus"),
tools=tools,
browser_session=browser_session,
)Update the LLM model in the code according to your setup.
File: examples/browser-use-integration/README.md
Browser-Use Integration Example
This example demonstrates how to integrate browser-use with agent-sandbox, allowing you to use browser automation agents with a sandboxed browser.
Prerequisites
- A running sandbox instance at
http://localhost:8080 - Python 3.11+
- OpenAI API key or compatible LLM endpoint
Running the Example
# Set your LLM API credentials
export OPENAI_API_KEY="your_api_key"
# Run the example
uv run main.pyWhat This Example Does
- Connects to the sandbox browser via CDP
- Creates a browser-use Agent with the sandboxed browser session
- Executes an AI-driven browser automation task
- The agent will visit DuckDuckGo and search for "browser-use founders"
Configuration
You can modify the task in main.py:
agent = Agent(
task='Visit https://duckduckgo.com and search for "browser-use founders"',
llm=ChatOpenAI(model="gcp-claude4.1-opus"),
tools=tools,
browser_session=browser_session,
)Update the LLM model in the code according to your setup.
File: examples/code-execute/README.md
Basic File Operations Example
This example demonstrates basic file operations with agent-sandbox, including:
- Uploading files to the sandbox
- Listing files in the sandbox
- Reading files from the sandbox
- Downloading files from the sandbox
Prerequisites
- A running sandbox instance at
http://localhost:8080 - Python 3.11+
Running the Example
uv run main.pyWhat This Example Does
- Reads a local image file (
foo.png) - Uploads it to the sandbox at
/home/sandbox/test.png - Lists files in the sandbox directory
- Reads the file back from the sandbox
- Downloads the file from the sandbox to verify the operation
File: examples/langgraph-deepagents-integration/README.md
aio-deepagents
A LangGraph-based deep agent implementation that uses AIO Sandbox as a native DeepAgents sandbox backend, implementing the BaseSandbox protocol (execute, upload_files, download_files).
Features
- Native Sandbox Backend: Direct integration via DeepAgents sandbox protocol
- OpenAI Compatible: Supports various LLM providers through OpenAI-compatible API
- Streaming Support: Real-time response streaming
Prerequisites
- Python 3.12 or higher
- Docker (for running AIO sandbox)
- An OpenAI-compatible API key (OpenRouter, etc.)
Quick Start
1. Start AIO Docker Sandbox
For International Users:
docker run --security-opt seccomp=unconfined --rm -it -p 127.0.0.1:8080:8080 ghcr.io/agent-infra/sandbox:latestFor Users in Mainland China:
docker run --security-opt seccomp=unconfined --rm -it -p 127.0.0.1:8080:8080 enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latestMore information:
2. Setup Environment
uv venv
uv sync3. Configure Environment Variables
cp .env.example .envEdit .env with your API details:
OPENAI_API_KEY=sk-xxxxx
OPENAI_MODEL_ID=anthropic/claude-sonnet-4
OPENAI_BASEURL=https://openrouter.ai/api/v1
SANDBOX_URL=http://localhost:80804. Run the Agent
uv run main.pyArchitecture
DeepAgents Agent
└── AIOSandboxBackend (sandbox_backend.py)
├── execute(cmd) → client.bash.exec()
├── upload_files() → client.file.write_file()
└── download_files() → client.file.read_file()
└── agent-sandbox SDK → AIO Sandbox HTTP APIThis follows the same pattern as langchain-daytona, langchain-runloop, etc.
Project Structure
aio-deepagents/
├── main.py # Main entry point
├── sandbox_backend.py # AIOSandboxBackend — DeepAgents sandbox protocol impl
├── pyproject.toml # Project configuration and dependencies
├── .env.example # Environment variables template
└── README.md # This fileCustomization
- Change the question: Modify the content in the
astreamcall inmain.py - Update instructions: Change the
system_promptparameter - Switch models: Update the
OPENAI_MODEL_IDenvironment variable
License
This project is licensed under the terms specified in the LICENSE file.
File: examples/minimax-integration/README.md
MiniMax Integration Example
This example demonstrates how to use MiniMax models with agent-sandbox for safe code execution, leveraging MiniMax's OpenAI-compatible API.
Prerequisites
- A running sandbox instance at
http://localhost:8080 - Python 3.11+
- MiniMax API key (get one here)
Running the Example
# Set your MiniMax API key
export MINIMAX_API_KEY="your_api_key"
# Run the example
uv run main.pyYou can customise the model and the task via environment variables:
MINIMAX_MODEL=MiniMax-M2.7-highspeed TASK="sort a list of 100 random numbers" uv run main.pyWhat This Example Does
- Connects to MiniMax via the OpenAI-compatible endpoint (
https://api.minimax.io/v1) - Sends a natural-language task to a MiniMax model (default: MiniMax-M2.7, 204K context)
- The model generates code and calls the
run_codetool - The code is executed inside the sandbox and results are returned
Available MiniMax Models
| Model | Context | Notes |
|---|---|---|
MiniMax-M2.7 |
204K | Latest, recommended |
MiniMax-M2.7-highspeed |
204K | Faster, lower latency |
MiniMax-M2.5 |
204K | Previous generation |
MiniMax-M2.5-highspeed |
204K | Previous generation, faster |
Key Features
- OpenAI-compatible — uses the standard
openaiPython SDK, just with a differentbase_url - Safe Code Execution — all code runs in an isolated sandbox environment
- Multi-language Support — supports both Python (Jupyter) and Node.js execution
- Function Calling — leverages MiniMax's tool-use capabilities
File: examples/openai-integration/README.md
OpenAI Integration Example
This example demonstrates how to integrate OpenAI's function calling with agent-sandbox to enable safe code execution in a sandboxed environment.
Prerequisites
- A running sandbox instance at
http://localhost:8080 - Python 3.11+
- OpenAI API key
Running the Example
# Set your OpenAI API key
export OPENAI_API_KEY="your_api_key"
# Update the API key in main.py, then run
uv run main.pyWhat This Example Does
- Defines a
run_codefunction that executes Python or Node.js code in the sandbox - Registers the function as an OpenAI tool
- Sends a natural language request to GPT-4 (e.g., "calculate 1+1")
- GPT-4 decides to use the
run_codetool with appropriate code - The code is executed in the sandbox and results are returned
Key Features
- Safe Code Execution: All code runs in an isolated sandbox environment
- Multi-language Support: Supports both Python (via Jupyter) and Node.js execution
- OpenAI Function Calling: Leverages GPT's ability to generate and call functions
Example Usage
The example is currently configured to calculate 1+1, but you can modify the task in main.py:
messages=[{"role": "user", "content": "calculate 1+1"}]Try different tasks like:
- "create a fibonacci sequence generator"
- "calculate the factorial of 10"
- "fetch data from an API and parse it"
File: examples/oss-upload/README.md
Basic File Operations Example
This example demonstrates basic file operations with agent-sandbox, including:
- Uploading files to the sandbox
- Listing files in the sandbox
- Reading files from the sandbox
- Downloading files from the sandbox
Prerequisites
- A running sandbox instance at
http://localhost:8080 - Python 3.11+
Running the Example
uv run main.pyWhat This Example Does
- Reads a local image file (
foo.png) - Uploads it to the sandbox at
/home/sandbox/test.png - Lists files in the sandbox directory
- Reads the file back from the sandbox
- Downloads the file from the sandbox to verify the operation
File: examples/playwright-integration/README.md
Playwright Integration Example
This example demonstrates how to integrate Playwright with agent-sandbox, allowing you to use Playwright's powerful browser automation capabilities with a sandboxed browser.
Prerequisites
- A running sandbox instance at
http://localhost:8080 - Python 3.11+
- uv package manager
Running the Example
# Install Playwright browsers (first time only)
uv run playwright install chromium
# Run the example
uv run main.pyWhat This Example Does
- Connects to the sandbox browser via CDP (Chrome DevTools Protocol)
- Creates a new browser context and page using Playwright
- Navigates to DuckDuckGo and performs a search
- Extracts the first search result's title and link
- Takes a screenshot of the search results page
- Saves the screenshot as
search_results.png
Configuration
You can modify the automation script in main.py to perform different tasks:
# Navigate to a website
await page.goto("https://example.com")
# Interact with elements
await page.click('button[type="submit"]')
await page.fill('input[name="email"]', "[email protected]")
# Wait for elements
await page.wait_for_selector('.result')
# Extract data
title = await page.title()
text = await page.locator('.content').inner_text()
# Take screenshots
await page.screenshot(path="screenshot.png")Key Features
- Browser Automation: Full Playwright API for browser control
- Sandboxed Execution: Browser runs in isolated sandbox environment
- CDP Integration: Connect via Chrome DevTools Protocol
- Screenshot Support: Capture visual output
- Element Selection: Powerful selectors and locators
- Async/Await: Modern async Python patterns
Advanced Usage
Multiple Pages
page1 = await context.new_page()
page2 = await context.new_page()
await page1.goto("https://example1.com")
await page2.goto("https://example2.com")Network Interception
await page.route("**/*.{png,jpg,jpeg}", lambda route: route.abort())Emulation
context = await browser.new_context(
viewport={'width': 1280, 'height': 720},
user_agent='Custom User Agent'
)Resources
File: examples/site-to-markdown/README.md
Site to Markdown Example
This example demonstrates how to combine multiple sandbox features:
- Browser automation with Playwright
- Jupyter code execution for HTML processing
- File operations
The example fetches a website, converts it to markdown, and saves it with a screenshot.
Prerequisites
- A running sandbox instance at
http://localhost:8080 - Python 3.11+
- Playwright installed (will be installed automatically with dependencies)
Running the Example
# Install Playwright browsers (first time only)
uv run playwright install
# Run the example
uv run main.pyWhat This Example Does
- Connects to the sandbox browser via CDP
- Navigates to a website (https://sandbox.agent-infra.com/)
- Captures the HTML content and screenshot
- Uses Jupyter in the sandbox to convert HTML to markdown using
markdownify - Saves the markdown file with embedded screenshot
- Downloads the result to local filesystem as
output.md
File: examples/volcengine-provider/README.md
Volcengine Provider Example
This example demonstrates how to use the Volcengine cloud provider to create and manage sandbox instances using the Volcengine VEFAAS API.
Prerequisites
- Volcengine account with VEFAAS access
- Environment variables set:
VOLCENGINE_ACCESS_KEY: Your Volcengine access keyVOLCENGINE_SECRET_KEY: Your Volcengine secret keyVOLCENGINE_REGION: Region (default: cn-beijing)
- Python 3.11+
Running the Example
export VOLCENGINE_ACCESS_KEY="your_access_key"
export VOLCENGINE_SECRET_KEY="your_secret_key"
export VOLCENGINE_REGION="cn-beijing"
uv run main.pyWhat This Example Does
- Creates a Volcengine application
- Waits for the application to be ready
- Creates a sandbox instance
- Lists all sandboxes for the function
- Gets sandbox details and public domain
- Tests the sandbox by listing files
- Deletes the sandbox instance
File: sdk/fern/fern.config.json
{
"organization": "sandbox",
"version": "0.85.1"
}
File: sdk/fern/generators.yml
yaml-language-server: $schema=https://schema.buildwithfern.dev/generators-yml.json
api:
specs:
- openapi: ../../website/docs/public/v1/openapi.json
default-group: python-sdk
groups:
python-sdk:
generators:
- name: fernapi/fern-python-sdk
version: 4.28.0
config:
package_name: "agent_sandbox"
pydantic_config:
skip_validation: false
smart_union: true
client_class_name: "Sandbox"
client:
class_name: "Sandbox"
output:
location: local-file-system
path: ../python/agent_sandbox
js-sdk:
generators:
- name: fernapi/fern-typescript-sdk
version: 3.28.4
config:
namespaceExport: Sandbox
allowCustomFetcher: true
skipResponseValidation: true
includeApiReference: true
noSerdeLayer: true
omitUndefined: true
enableInlineTypes: false
neverThrowErrors: true
smart-casing: true
output:
location: local-file-system
path: ../js/src
sdk-go:
generators:
- name: fernapi/fern-go-sdk
version: 1.12.4
output:
location: local-file-system
path: ../../sandbox-sdk-go
config:
module:
path: github.com/agent-infra/sandbox-sdk-go
File: sdk/go/README.md
Go SDK
Please refer to https://github.com/agent-infra/sandbox-sdk-go.
File: sdk/js/README.md
@agent-infra/sandbox
Node.js/TypeScript SDK for AIO Sandbox integration, providing tools and interfaces for sandbox management and cloud provider integrations.
Installation
npm install @agent-infra/sandboxor with yarn:
yarn add @agent-infra/sandboxor with pnpm:
pnpm add @agent-infra/sandboxQuick Start
Basic Usage
import { SandboxApiClient } from '@agent-infra/sandbox';
// Initialize the client
const client = new SandboxApiClient({
environment: 'https://your-sandbox-api.com',
// Add authentication if required
});
// Use the API
const result = await client.file.read({
path: '/path/to/file',
});
console.log(result);Using Cloud Providers
The SDK includes provider implementations for managing sandboxes on different cloud platforms.
Volcengine Provider
import { providers } from '@agent-infra/sandbox';
// Initialize Volcengine provider
const volcengineProvider = new providers.VolcengineProvider({
accessKey: process.env.VOLCENGINE_ACCESS_KEY,
secretKey: process.env.VOLCENGINE_SECRET_KEY,
region: 'cn-beijing', // Optional, defaults to 'cn-beijing'
});
// Create a sandbox
const sandboxId = await volcengineProvider.createSandbox(
'your-function-id',
30 // timeout in minutes
);
console.log('Created sandbox:', sandboxId);
// Get sandbox details with APIG domains
const sandbox = await volcengineProvider.getSandbox(
'your-function-id',
sandboxId
);
console.log('Sandbox domains:', sandbox.domains);
// List all sandboxes for a function
const sandboxes = await volcengineProvider.listSandboxes('your-function-id');
console.log('Total sandboxes:', sandboxes.length);
// Delete a sandbox
await volcengineProvider.deleteSandbox('your-function-id', sandboxId);
console.log('Sandbox deleted');Application Management
// Create an application
const appId = await volcengineProvider.createApplication(
'my-app',
'my-gateway'
);
// Check application readiness
const [isReady, functionId] = await volcengineProvider.getApplicationReadiness(appId);
if (isReady) {
console.log('Application is ready, function ID:', functionId);
}
// Get APIG domains for a function
const domains = await volcengineProvider.getApigDomains('your-function-id');
console.log('Available domains:', domains);Features
Sandbox API Client
- File Operations: Read, write, search, and manage files
- Shell Execution: Execute shell commands and manage sessions
- Browser Automation: Control browser actions and retrieve information
- Code Execution: Execute code in various languages (Python, Node.js, Jupyter)
- MCP Integration: Execute MCP (Model Context Protocol) tools
Cloud Providers
Volcengine Provider
- ✅ Sandbox lifecycle management (create, delete, get, list)
- ✅ Application deployment and monitoring
- ✅ APIG (API Gateway) domain management
- ✅ Automatic request signing with HMAC-SHA256
- ✅ Support for temporary credentials
Extensible Provider System
Create custom providers by extending the BaseProvider class:
import { providers } from '@agent-infra/sandbox';
class MyCustomProvider extends providers.BaseProvider {
async createSandbox(functionId: string, ...kwargs: any[]): Promise<any> {
// Your implementation
}
async deleteSandbox(functionId: string, sandboxId: string, ...kwargs: any[]): Promise<any> {
// Your implementation
}
async getSandbox(functionId: string, sandboxId: string, ...kwargs: any[]): Promise<any> {
// Your implementation
}
async listSandboxes(functionId: string, ...kwargs: any[]): Promise<any> {
// Your implementation
}
}API Reference
SandboxApiClient
The main client for interacting with the Sandbox API.
const client = new SandboxApiClient({
environment: string, // API base URL
timeout?: number, // Request timeout in milliseconds
headers?: Record<string, string>, // Custom headers
});Available Modules
client.file- File operationsclient.shell- Shell command executionclient.browser- Browser automationclient.code- Code executionclient.jupyter- Jupyter notebook operationsclient.nodejs- Node.js specific operationsclient.mcp- MCP tool execution
Providers
BaseProvider (Abstract)
Base class for all cloud provider implementations.
Methods:
createSandbox(functionId: string, ...kwargs: any[]): Promise<any>deleteSandbox(functionId: string, sandboxId: string, ...kwargs: any[]): Promise<any>getSandbox(functionId: string, sandboxId: string, ...kwargs: any[]): Promise<any>listSandboxes(functionId: string, ...kwargs: any[]): Promise<any>
VolcengineProvider
Volcengine VEFAAS implementation.
Constructor Options:
{
accessKey: string; // Volcengine access key ID
secretKey: string; // Volcengine secret access key
region?: string; // Region (default: 'cn-beijing')
clientSideValidation?: boolean; // Enable validation (default: true)
}Additional Methods:
createApplication(name: string, gatewayName: string): Promise<string | null>getApplicationReadiness(id: string): Promise<[boolean, string | null]>getApigDomains(functionId: string): Promise<DomainInfo[]>
Environment Variables
Configure Volcengine credentials using environment variables:
# Volcengine credentials (option 1)
VOLCENGINE_ACCESS_KEY=your-access-key
VOLCENGINE_SECRET_KEY=your-secret-key
# Volcengine credentials (option 2)
VOLC_ACCESSKEY=your-access-key
VOLC_SECRETKEY=your-secret-keyTypeScript Support
This package is written in TypeScript and includes full type definitions. TypeScript 5.0+ is recommended.
import type {
SandboxApi,
BaseClientOptions,
BaseRequestOptions
} from '@agent-infra/sandbox';Examples
Execute Shell Command
const result = await client.shell.exec({
command: 'ls -la',
timeout: 5000,
});
console.log(result.stdout);Read File
const fileContent = await client.file.read({
path: '/path/to/file.txt',
});
console.log(fileContent.content);Browser Automation
const browserInfo = await client.browser.info();
console.log('Browser:', browserInfo);
await client.browser.config({
action: {
type: 'click',
selector: '#button',
},
});Execute Python Code
const result = await client.code.execute({
code: 'print("Hello from sandbox!")',
language: 'python',
});
console.log(result.output);Error Handling
import { SandboxApiError, SandboxApiTimeoutError } from '@agent-infra/sandbox';
try {
const result = await client.file.read({ path: '/nonexistent' });
} catch (error) {
if (error instanceof SandboxApiTimeoutError) {
console.error('Request timed out');
} else if (error instanceof SandboxApiError) {
console.error('API error:', error.statusCode, error.message);
} else {
console.error('Unexpected error:', error);
}
}Development
Project Structure
sdk/js/
├── src/ # TypeScript source code
│ ├── api/ # Generated API modules
│ ├── core/ # Core utilities
│ ├── errors/ # Error classes
│ ├── providers/ # Cloud provider implementations (custom code)
│ │ ├── base.ts # Base provider interface
│ │ ├── volcengine.ts # Volcengine implementation
│ │ ├── sign.ts # Request signing utilities
│ │ └── README.md # Provider documentation
│ ├── BaseClient.ts # Base client implementation
│ ├── Client.ts # Main API client
│ └── index.ts # Package entry point
├── dist/ # Compiled JavaScript output (generated by build)
├── package.json # Package configuration
└── tsconfig.json # TypeScript configurationBuilding
The SDK uses TypeScript and compiles source code from src/ to dist/:
npm run buildThis will:
- Compile TypeScript files from
src/to JavaScript indist/ - Generate
.d.tstype definition files - Generate source maps for debugging
Testing
npm test
# With coverage
npm run test:coverage
# With UI
npm run test:uiDevelopment Mode
npm run dev # Watch mode with auto-rebuildGenerating SDK
The base SDK code is generated using Fern:
cd sdk/fern
fern generate --group nodejs-sdk --localThis generates TypeScript code from the OpenAPI specification into src/.
Custom providers in src/providers/ are preserved via .fernignore.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Adding Custom Providers
See providers/README.md for detailed information on implementing custom cloud providers.
License
ISC
Links
Support
For questions and support, please open an issue on GitHub.
Version: 1.0.0
Node.js: >=18.0.0
TypeScript: >=5.0.0
File: sdk/js/package.json
{
"name": "@agent-infra/sandbox",
"version": "1.0.17",
"description": "Node.js SDK for AIO Sandbox integration providing tools and interfaces",
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.mjs",
"types": "./dist/types/index.d.ts",
"exports": {
".": {
"types": "./dist/types/index.d.ts",
"import": "./dist/esm/index.mjs",
"require": "./dist/cjs/index.js"
}
},
"scripts": {
"build": "rslib build && tsc",
"prepublishOnly": "npm run build",
"test": "vitest",
"test:coverage": "vitest --coverage",
"test:ui": "vitest --ui",
"dev": "tsc --watch",
"clean": "rimraf dist"
},
"repository": {
"type": "git",
"url": "git+https://github.com/agent-infra/sandbox-sdk.git",
"directory": "js"
},
"files": [
"dist",
"README.md",
"package.json"
],
"keywords": [
"aio",
"sandbox",
"sdk",
"nodejs",
"typescript"
],
"author": "",
"license": "ISC",
"devDependencies": {
"@rslib/core": "^0.17.1",
"@types/node": "^20.19.24",
"@vitest/coverage-v8": "^1.0.0",
"@vitest/ui": "^1.0.0",
"rimraf": "^5.0.0",
"typescript": "^5.9.3",
"vitest": "^1.0.0"
},
"engines": {
"node": ">=18.0.0"
}
}
File: sdk/js/tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020"],
"types": ["node"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"rootDir": "./src",
"removeComments": true,
"strict": false,
"skipLibCheck": true,
"noEmitOnError": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": false,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"exactOptionalPropertyTypes": false,
"emitDeclarationOnly": true,
"outDir": "./dist/types"
},
"include": ["src//*"],
"exclude": ["node_modules", "dist", "/.test.ts", "**/.spec.ts"]
}
File: sdk/js/examples/README.md
Sandbox SDK Examples
TypeScript examples demonstrating usage of the @agent-infra/sandbox SDK.
Examples
1. simple-examples.ts
Basic, straightforward examples covering core SDK functionality:
- Getting sandbox context
- Executing shell commands
- Running Python code
- File operations (write, read, list, find, search)
2. basic-usage.ts
Fundamental sandbox operations:
- Sandbox context retrieval
- Shell command execution
- Python code execution
- File read/write operations
- Directory listing
- Python package information
3. volcengine-provider.ts
Volcengine cloud provider integration:
- Creating sandboxes with configuration
- Getting sandbox information
- Listing sandboxes
- Deleting sandboxes
4. advanced-usage.ts
Comprehensive demonstration of SDK features:
- File operations (read, write, list, search, find)
- Code execution (Python, JavaScript, Node.js)
- Shell commands
- Volcengine provider workflow
5. error-handling.ts
Error handling patterns and resilience strategies:
- Error type detection and handling
- Retry logic with exponential backoff
- Circuit breaker pattern
- Graceful degradation
Running Examples
Install dependencies first:
npm installRun individual examples:
npm run simple # Simple examples
npm run basic # Basic usage
npm run volcengine # Volcengine provider
npm run advanced # Advanced usage
npm run error # Error handlingRun all examples:
npm run allEnvironment Variables
For Volcengine examples:
export VOLCENGINE_ACCESS_KEY=your-access-key
export VOLCENGINE_SECRET_KEY=your-secret-keyFor custom sandbox URL:
export SANDBOX_API_URL=http://your-sandbox:8080Type Checking
Check TypeScript types:
npm run buildKnown SDK Issues
The SDK source has several TypeScript compilation errors that prevent clean builds:
- Missing errors module (
./errors/index.js): TheSandboxErrorandSandboxTimeoutErrorclasses are exported but the module doesn't exist - HeadersIterator type: DOM type
HeadersIteratoris not found - Buffer/Blob compatibility: Type incompatibility between Node.js Buffer and DOM Blob
- APIResponse error property: Many client methods try to access
.erroron wrong response type
These are SDK build issues, not example code issues. All example TypeScript code is syntactically correct and will run properly with tsx even though tsc reports errors from the SDK internals.
To run examples without type checking:
npx tsx simple-examples.ts
npx tsx basic-usage.ts
# etc.Example Code Patterns
API Response Pattern
All SDK methods return APIResponse<T, E> with .ok discriminator:
const response = await client.file.readFile({ file: '/tmp/test.txt' });
if (response.ok) {
console.log('Success:', response.body);
} else {
console.error('Error:', response.error);
}Error Handling
try {
const response = await client.shell.execCommand({
command: 'echo "Hello"'
});
if (!response.ok) {
console.error('Command failed');
}
} catch (error) {
if (error instanceof Error) {
console.error('Error:', error.message);
}
}File Operations
// Write
await client.file.writeFile({
file: '/tmp/test.txt',
content: 'Hello World'
});
// Read
const read = await client.file.readFile({
file: '/tmp/test.txt'
});
// List
const list = await client.file.listPath({
path: '/tmp'
});
// Search
const search = await client.file.searchInFile({
file: '/tmp/test.txt',
regex: 'pattern'
});
// Find
const find = await client.file.findFiles({
path: '/tmp',
glob: '*.txt'
});Code Execution
// Python
await client.code.executeCode({
language: 'python',
code: 'print("Hello from Python!")'
});
// JavaScript
await client.code.executeCode({
language: 'javascript',
code: 'console.log("Hello from JS!")'
});
// Node.js
await client.nodejs.executeCode({
code: 'console.log(process.version)'
});File: sdk/js/examples/package.json
{
"name": "@agent-infra/sandbox-examples",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc",
"simple": "tsx simple-examples.ts",
"basic": "tsx basic-usage.ts",
"volcengine": "tsx volcengine-provider.ts",
"error": "tsx error-handling.ts",
"advanced": "tsx advanced-usage.ts",
"all": "npm run simple && npm run basic && npm run error && npm run advanced"
},
"dependencies": {
"@agent-infra/sandbox": "file:.."
},
"devDependencies": {
"@types/node": "^20.19.24",
"tsx": "^4.7.0",
"typescript": "^5.9.3"
}
}
File: sdk/js/examples/tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ES2020", "DOM"],
"noEmit": true,
"moduleResolution": "bundler",
"esModuleInterop": true,
"skipLibCheck": true,
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
"types": ["node"]
},
"include": ["./*.ts"],
"exclude": ["node_modules", "../src"]
}
File: sdk/js/src/providers/README.md
Sandbox Providers
This directory contains custom cloud provider implementations for sandbox management.
Volcengine Provider
The Volcengine provider integrates with Volcengine VEFAAS (Volcengine Function as a Service) API to manage sandbox instances.
Installation
The provider is included in the @agent-infra/sandbox package. Install it via npm:
npm install @agent-infra/sandboxUsage
import { providers } from '@agent-infra/sandbox';
// Initialize the Volcengine provider
const provider = new providers.VolcengineProvider({
accessKey: 'your-access-key',
secretKey: 'your-secret-key',
region: 'cn-beijing', // Optional, defaults to 'cn-beijing'
clientSideValidation: true, // Optional, defaults to true
});
// Create a sandbox
const sandboxId = await provider.createSandbox('function-id', 30);
// Get sandbox details
const sandbox = await provider.getSandbox('function-id', sandboxId);
// List all sandboxes
const sandboxes = await provider.listSandboxes('function-id');
// Delete a sandbox
await provider.deleteSandbox('function-id', sandboxId);
// Set sandbox timeout
await provider.setSandboxTimeout('function-id', sandboxId, 120);Application Management
The provider also supports application lifecycle management:
// Create an application
const applicationId = await provider.createApplication('app-name', 'gateway-name');
// Check application readiness
const [isReady, functionId] = await provider.getApplicationReadiness(applicationId);
// Get APIG domains for a function
const domains = await provider.getApigDomains('function-id');Custom Providers
To create a custom provider, extend the BaseProvider class:
import { providers } from '@agent-infra/sandbox';
class MyCustomProvider extends providers.BaseProvider {
async createSandbox(functionId: string, ...kwargs: any[]): Promise<any> {
// Implementation
}
async deleteSandbox(functionId: string, sandboxId: string, ...kwargs: any[]): Promise<any> {
// Implementation
}
async getSandbox(functionId: string, sandboxId: string, ...kwargs: any[]): Promise<any> {
// Implementation
}
async listSandboxes(functionId: string, ...kwargs: any[]): Promise<any> {
// Implementation
}
}API Reference
BaseProvider (Abstract)
Base class for all cloud provider implementations.
Methods
createSandbox(functionId: string, ...kwargs: any[]): Promise<any>deleteSandbox(functionId: string, sandboxId: string, ...kwargs: any[]): Promise<any>getSandbox(functionId: string, sandboxId: string, ...kwargs: any[]): Promise<any>listSandboxes(functionId: string, ...kwargs: any[]): Promise<any>
VolcengineProvider
Volcengine VEFAAS implementation of the BaseProvider.
Constructor Options
accessKey(string): Volcengine access key IDsecretKey(string): Volcengine secret access keyregion(string, optional): Volcengine region, defaults to 'cn-beijing'clientSideValidation(boolean, optional): Enable client-side validation, defaults to true
Additional Methods
createApplication(name: string, gatewayName: string, ...kwargs: any[]): Promise<string | null>getApplicationReadiness(id: string, ...kwargs: any[]): Promise<[boolean, string | null]>getApigDomains(functionId: string): Promise<DomainInfo[]>setSandboxTimeout(functionId: string, sandboxId: string, timeout: number): Promise<any>
Environment Variables
For the Volcengine provider, you can also configure credentials using environment variables:
VOLCENGINE_ACCESS_KEYVOLCENGINE_SECRET_KEY
License
Apache License 2.0
File: sdk/python/README.md
Agent Sandbox Python SDK
A Python SDK for the All-in-One Sandbox API, providing access to sandbox, shell, file, jupyter, nodejs, and mcp services.
Installation
pip install agent-sandboxUsage
from agent_sandbox import Sandbox
client = Sandbox(base_url="http://localhost:8091")
ctx = client.sandbox.get_context()
print(ctx)
result = client.shell.exec_command(command="ls -la")
print(result)Async Support
The SDK also provides async support through the AsyncSandbox class:
import asyncio
from agent_sandbox import AsyncSandbox
async def main():
client = AsyncSandbox(base_url="http://localhost:8091")
# Get sandbox context
ctx = await client.sandbox.get_context()
print(ctx)
result = await client.shell.exec_command(command="ls -la")
print(result)
asyncio.run(main())Cloud Providers
Volcengine
Create a sandbox instance using the Volcengine provider. For more details, please refer to examples/provider_volcengine.py.
from __future__ import print_function
import os
import sys
# Add the parent directory to Python path so we can import agent_sandbox
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from agent_sandbox.providers import VolcengineProvider
def main():
"""
Main function demonstrating Volcengine provider usage.
"""
# Configuration - replace with your actual credentials
access_key = os.getenv("VOLC_ACCESSKEY")
secret_key = os.getenv("VOLC_SECRETKEY")
region = os.getenv("VOLCENGINE_REGION", "cn-beijing")
# Initialize the Volcengine provider
provider = VolcengineProvider(
access_key=access_key,
secret_key=secret_key,
region=region
)
print("=== Volcengine Sandbox Provider Example ===\n")
function_id = "yatoczqh"
print("1. Creating a sandbox...")
sandbox_id = provider.create_sandbox(function_id=function_id)
print(f"Create response: {sandbox_id}")
if __name__ == "__main__":
main()Features
- Sandbox: Access sandbox environment information and installed packages
- Shell: Execute shell commands with session management
- File: Read, write, search, and manage files
- Jupyter: Execute Python code in Jupyter kernels
- Node.js: Execute JavaScript code in Node.js environment
- MCP: Interact with Model Context Protocol servers
Requirements
- Python 3.8+
- httpx
- pydantic
- typing_extensions (for Python < 3.10)
File: sdk/python/agent_sandbox/providers/README.md
Cloud Providers
This module contains cloud provider implementations for sandbox management.
Volcengine
The Volcengine provider integrates with Volcengine VEFAAS APIs to manage sandbox instances.
from agent_sandbox.providers import VolcengineProvider
provider = VolcengineProvider(
access_key="your-access-key",
secret_key="your-secret-key",
region="cn-beijing",
)
sandbox_id = provider.create_sandbox("function-id", timeout=30)
sandbox = provider.get_sandbox("function-id", sandbox_id)
sandboxes = provider.list_sandboxes("function-id")
provider.set_sandbox_timeout("function-id", sandbox_id, 120)
provider.delete_sandbox("function-id", sandbox_id)File: website/i18n.json
{
"oneLineCommand": {
"zh": "docker run --rm -it -p 127.0.0.1:8080:8080 enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest",
"en": "docker run --rm -it -p 127.0.0.1:8080:8080 ghcr.io/agent-infra/sandbox:latest"
},
"gettingStarted": {
"zh": "开始",
"en": "Getting Started"
},
"features": {
"zh": "特性",
"en": "Features"
},
"defaultTheme": {
"zh": "默认主题",
"en": "Default Theme"
},
"advanced": {
"zh": "高级能力",
"en": "Advanced"
},
"apiOverview": {
"zh": "API 概览",
"en": "API Overview"
},
"config": {
"zh": "配置",
"en": "Config"
},
"loadingApiReference": {
"zh": "正在加载 API 文档...",
"en": "Loading API Reference..."
},
"searchPlaceholderText": {
"zh": "搜索",
"en": "Search"
},
"searchPanelCancelText": {
"zh": "取消",
"en": "Cancel"
},
"searchNoResultsText": {
"zh": "未找到匹配的结果",
"en": "No matching results"
},
"searchSuggestedQueryText": {
"zh": "尝试搜索其他关键词",
"en": "Try searching for different keywords"
},
"footerMessage": {
"zh": "为 AI Agents 打造 · AIO Sandbox © 2026",
"en": "Built with ❤️ for AI Agents · AIO Sandbox © 2026"
}
}
File: website/package.json
{
"name": "website",
"version": "1.0.0",
"private": true,
"scripts": {
"build": "rspress build",
"check": "biome check --write",
"dev": "rspress dev",
"format": "prettier --write .",
"preview": "rspress preview",
"deploy": "npm run build && npm run preview"
},
"dependencies": {
"@rspress/core": "2.0.21",
"@rspress/plugin-llms": "2.0.21",
"@scalar/api-reference-react": "0.8.6",
"core-js": "^3.45.1"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@rsbuild/plugin-sass": "2.0.1",
"@rsbuild/plugin-svgr": "^2.0.5",
"@rspress/plugin-sitemap": "2.0.21",
"@shikijs/transformers": "^3.13.0",
"@types/node": "^18.11.17",
"@types/react": "^19.2.14",
"prettier": "^3.4.2",
"rsbuild-plugin-google-analytics": "1.0.6",
"rsbuild-plugin-open-graph": "1.1.3",
"rspress-plugin-font-open-sans": "1.0.4"
}
}
File: website/tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"lib": ["DOM", "ES2020"],
"module": "ESNext",
"jsx": "react-jsx",
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"isolatedModules": true,
"resolveJsonModule": true,
"moduleResolution": "bundler",
"useDefineForClassFields": true,
"allowImportingTsExtensions": true
},
"include": ["docs", "theme", "rspress.config.ts"],
"mdx": {
"checkMdx": true
}
}
File: website/diagrams/README.md
Diagram sources
Archify specs (skill tt-a1i/archify). Regenerate withnode bin/archify.mjs deliver <type> <name>.<type>.json <name>.html --quality showcase,
then export SVG from the delivered viewer into docs/public/architecture/<name>.svg:
aiod-overview.architecture.json— the Introduction hero.aiod-permission-model.architecture.json— the permission model (Daemon → Architecture).aiod-browser-cdp.architecture.json— the two browser paths (Daemon → Browser API).aiod-jupyter-kernel.architecture.json— the kernels aiod spawns (Daemon → Jupyter).aiod-human-in-loop.architecture.json— agent and person on the same objects (Daemon → Computer Use).aiod-mcp-flow.architecture.json— one endpoint, one tool list (Daemon → examples → MCP).aio-sandbox-overview.architecture.json— the surfaces inside the container (Guide → Introduction).aio-agent-calls-sandbox.architecture.json— agent outside, capabilities inside (Guide → Agent Call Sandbox vs In Sandbox).aio-agent-in-sandbox.architecture.json— agent process inside the container (Guide → Agent Call Sandbox vs In Sandbox).aiod-existing-environment.architecture.json— aiod added into an existing container, VM or desktop (Daemon → Introduction).aiod-watch-flow.sequence.json— watch, poll, stop (Daemon → Files, Daemon → examples → file ops).aiod-code-execution-flow.sequence.json— dataset in, chart out (Daemon → examples → code execution).aiod-code-backends.sequence.json— which backend runs the code (Daemon → Code).aiod-browser-use-flow.sequence.json— serve a page, then drive it (Daemon → examples → browser use).aiod-terminal-flow.sequence.json— create, attach, run (Daemon → examples → terminal).aiod-terminal-reconnect.sequence.json— reattach with and without durable (Daemon → examples → terminal).aiod-computer-action.sequence.json— one computer action, then the accessibility tree (Daemon → Computer Use).
*.html without a matching spec are hand-drawn SVG sources; the <svg> element is
extracted verbatim into docs/public/architecture/<name>.svg.
aiod-capabilities.gen.py writes the capability status board straight to docs/public/architecture/aiod-capabilities.svg.
File: website/diagrams/aio-agent-calls-sandbox.architecture.json
{
"schema_version": 1,
"diagram_type": "architecture",
"meta": {
"title": "Agent calls the sandbox",
"quality_profile": "showcase",
"visual_preset": "signal-flow",
"locale": "en"
},
"layout": {
"mode": "grid",
"cols": 3,
"cellW": 176,
"cellH": 72,
"gapX": 144,
"gapY": 56
},
"components": [
{
"id": "agent",
"type": "external",
"label": "Agent",
"sublabel": "LLM · orchestration",
"row": 1,
"col": 0,
"size": [
176,
72
]
},
{
"id": "api",
"type": "backend",
"label": "Sandbox API",
"sublabel": "REST · SDK · MCP",
"row": 1,
"col": 1,
"size": [
176,
72
]
},
{
"id": "shell",
"type": "backend",
"label": "Shell",
"sublabel": "commands · PTY",
"row": 0,
"col": 2,
"size": [
176,
72
]
},
{
"id": "files",
"type": "backend",
"label": "Files",
"sublabel": "read · write",
"row": 1,
"col": 2,
"size": [
176,
72
]
},
{
"id": "browser",
"type": "backend",
"label": "Browser",
"sublabel": "CDP",
"row": 2,
"col": 2,
"size": [
176,
72
]
}
],
"boundaries": [
{
"kind": "region",
"label": "AIO Sandbox",
"wraps": [
"api",
"shell",
"files",
"browser"
]
}
],
"connections": [
{
"id": "agent-api",
"from": "agent",
"to": "api",
"label": "HTTP · :8080",
"variant": "emphasis"
},
{
"id": "api-shell",
"from": "api",
"to": "shell"
},
{
"id": "api-files",
"from": "api",
"to": "files"
},
{
"id": "api-browser",
"from": "api",
"to": "browser"
}
]
}
File: website/diagrams/aio-agent-in-sandbox.architecture.json
{
"schema_version": 1,
"diagram_type": "architecture",
"meta": {
"title": "Agent inside the sandbox",
"quality_profile": "showcase",
"visual_preset": "signal-flow",
"locale": "en"
},
"layout": {
"mode": "grid",
"cols": 3,
"cellW": 176,
"cellH": 64,
"gapX": 144,
"gapY": 32
},
"components": [
{
"id": "agent",
"type": "external",
"label": "Agent process",
"sublabel": "local commands · files",
"row": 1,
"col": 0,
"size": [
176,
64
]
},
{
"id": "api",
"type": "backend",
"label": "Sandbox API",
"sublabel": "REST · SDK · MCP",
"row": 1,
"col": 1,
"size": [
176,
64
]
},
{
"id": "shell",
"type": "backend",
"label": "Shell",
"sublabel": "commands · PTY",
"row": 0,
"col": 2,
"size": [
176,
64
]
},
{
"id": "files",
"type": "backend",
"label": "Files",
"sublabel": "read · write",
"row": 1,
"col": 2,
"size": [
176,
64
]
},
{
"id": "browser",
"type": "backend",
"label": "Browser",
"sublabel": "CDP",
"row": 2,
"col": 2,
"size": [
176,
64
]
},
{
"id": "code",
"type": "backend",
"label": "Code",
"sublabel": "Python · Node",
"row": 3,
"col": 2,
"size": [
176,
64
]
}
],
"boundaries": [
{
"kind": "region",
"label": "AIO Sandbox",
"wraps": [
"agent",
"api",
"shell",
"files",
"browser",
"code"
]
}
],
"connections": [
{
"id": "agent-api",
"from": "agent",
"to": "api",
"label": "127.0.0.1:8080",
"variant": "emphasis"
},
{
"id": "api-shell",
"from": "api",
"to": "shell"
},
{
"id": "api-files",
"from": "api",
"to": "files"
},
{
"id": "api-browser",
"from": "api",
"to": "browser"
},
{
"id": "api-code",
"from": "api",
"to": "code"
}
]
}
File: website/diagrams/aio-sandbox-overview.architecture.json
{
"schema_version": 1,
"diagram_type": "architecture",
"meta": {
"title": "AIO Sandbox surfaces",
"quality_profile": "showcase",
"visual_preset": "signal-flow",
"locale": "en"
},
"layout": {
"mode": "grid",
"cols": 5,
"cellW": 176,
"cellH": 72,
"gapX": 72,
"gapY": 48
},
"components": [
{
"id": "agent",
"type": "external",
"label": "Agent",
"sublabel": "tool calls",
"row": 1,
"col": 0,
"size": [
176,
72
]
},
{
"id": "gateway",
"type": "backend",
"label": "nginx",
"row": 1,
"col": 1,
"sublabel": ":8080",
"size": [
176,
72
]
},
{
"id": "browser",
"type": "frontend",
"label": "Browser",
"sublabel": "VNC · CDP",
"row": 0,
"col": 2,
"size": [
176,
72
]
},
{
"id": "shell",
"type": "backend",
"label": "Shell",
"sublabel": "WebSocket",
"row": 0,
"col": 3,
"size": [
176,
72
]
},
{
"id": "files",
"type": "backend",
"label": "Files",
"sublabel": "API",
"row": 1,
"col": 2,
"size": [
176,
72
]
},
{
"id": "code",
"type": "backend",
"label": "Code",
"sublabel": "execute",
"row": 1,
"col": 3,
"size": [
176,
72
]
},
{
"id": "mcp",
"type": "backend",
"label": "MCP Hub",
"sublabel": "tools",
"row": 2,
"col": 2,
"size": [
424,
72
]
},
{
"id": "vscode",
"type": "frontend",
"label": "VSCode Server",
"sublabel": "code-server",
"row": 0,
"col": 4,
"size": [
176,
72
]
},
{
"id": "preview",
"type": "backend",
"label": "Preview proxy",
"sublabel": "ports",
"row": 1,
"col": 4,
"size": [
176,
72
]
},
{
"id": "services",
"type": "backend",
"label": "Services",
"sublabel": "processes",
"row": 2,
"col": 4,
"size": [
176,
72
]
}
],
"boundaries": [
{
"kind": "region",
"label": "AIO Sandbox",
"wraps": [
"gateway",
"browser",
"shell",
"files",
"code",
"mcp",
"vscode",
"preview",
"services"
],
"pad": 44
},
{
"kind": "region",
"label": "agent tools",
"wraps": [
"browser",
"shell",
"files",
"code",
"mcp"
],
"pad": 18
},
{
"kind": "region",
"label": "human surfaces",
"wraps": [
"vscode",
"preview",
"services"
],
"pad": 18
}
],
"connections": [
{
"id": "agent-gateway",
"from": "agent",
"to": "gateway",
"label": "HTTP · WebSocket",
"variant": "emphasis",
"labelDy": -30
}
]
}
File: website/diagrams/aiod-browser-cdp.architecture.json
{
"schema_version": 1,
"diagram_type": "architecture",
"meta": {
"title": "Browser CDP",
"quality_profile": "showcase",
"visual_preset": "signal-flow",
"locale": "en"
},
"layout": {
"mode": "grid",
"cols": 3,
"cellW": 176,
"cellH": 72,
"gapX": 144,
"gapY": 56
},
"components": [
{
"id": "agent",
"type": "external",
"label": "Agent",
"sublabel": "Playwright · REST",
"row": 1,
"col": 0,
"size": [176, 72]
},
{
"id": "aiod",
"type": "backend",
"label": "aiod",
"sublabel": "/v1/browser",
"row": 0,
"col": 1,
"size": [176, 72]
},
{
"id": "nginx",
"type": "backend",
"label": "nginx",
"sublabel": "/cdp/*",
"row": 2,
"col": 1,
"size": [176, 72]
},
{
"id": "chromium",
"type": "external",
"label": "Chromium",
"sublabel": "CDP :9222",
"row": 1,
"col": 2,
"size": [176, 72]
}
],
"boundaries": [
{
"kind": "region",
"label": "container · VM",
"wraps": ["aiod", "nginx", "chromium"]
}
],
"connections": [
{
"id": "agent-aiod",
"from": "agent",
"to": "aiod",
"label": "REST",
"variant": "emphasis"
},
{
"id": "aiod-chromium",
"from": "aiod",
"to": "chromium",
"label": "CDP"
},
{
"id": "agent-nginx",
"from": "agent",
"to": "nginx",
"label": "cdp_url",
"variant": "dashed"
},
{
"id": "nginx-chromium",
"from": "nginx",
"to": "chromium",
"label": "WebSocket",
"variant": "dashed"
}
]
}
File: website/diagrams/aiod-browser-use-flow.sequence.json
{
"schema_version": 1,
"diagram_type": "sequence",
"meta": {
"title": "Drive a page the sandbox serves",
"quality_profile": "showcase",
"visual_preset": "signal-flow",
"column_fit": "spread",
"locale": "en",
"viewBox": [1080, 640]
},
"participants": [
{ "id": "agent", "type": "external", "label": "Agent" },
{ "id": "aiod", "type": "backend", "label": "aiod", "sublabel": "files · browser" },
{ "id": "chromium", "type": "frontend", "label": "Chromium", "sublabel": "CDP :9222" },
{ "id": "server", "type": "backend", "label": "Server", "sublabel": ":18780" }
],
"messages": [
{ "id": "write-pages", "from": "agent", "to": "aiod", "y": 172, "label": "write index.html · thanks.html", "variant": "emphasis" },
{ "id": "store-pages", "from": "aiod", "to": "server", "y": 204, "label": "index.html · thanks.html", "variant": "default" },
{ "id": "run-server", "from": "agent", "to": "aiod", "y": 236, "label": "run server", "variant": "emphasis" },
{ "id": "http-server", "from": "aiod", "to": "server", "y": 268, "label": "http.server 18780", "variant": "default" },
{ "id": "navigate", "from": "agent", "to": "aiod", "y": 300, "label": "navigate", "variant": "emphasis" },
{ "id": "cdp-load", "from": "aiod", "to": "chromium", "y": 332, "label": "CDP · load", "variant": "default" },
{ "id": "get-index", "from": "chromium", "to": "server", "y": 364, "label": "GET /index.html", "variant": "default" },
{ "id": "snapshot", "from": "agent", "to": "aiod", "y": 396, "label": "snapshot", "variant": "emphasis" },
{ "id": "fill", "from": "agent", "to": "aiod", "y": 428, "label": "fill e6", "variant": "emphasis" },
{ "id": "click", "from": "agent", "to": "aiod", "y": 460, "label": "click e14", "variant": "emphasis" },
{ "id": "evaluate", "from": "agent", "to": "aiod", "y": 492, "label": "evaluate #result", "variant": "emphasis" },
{ "id": "result", "from": "chromium", "to": "agent", "y": 524, "label": "hello, Ada", "variant": "return" }
],
"activations": [
{ "participant": "aiod", "from": 164, "to": 536, "type": "backend" },
{ "participant": "chromium", "from": 326, "to": 532, "type": "frontend" }
]
}
File: website/diagrams/aiod-code-backends.sequence.json
{
"schema_version": 1,
"diagram_type": "sequence",
"meta": {
"title": "How a run picks its backend",
"quality_profile": "showcase",
"visual_preset": "signal-flow",
"column_fit": "spread",
"locale": "en",
"viewBox": [1080, 660]
},
"participants": [
{ "id": "agent", "type": "external", "label": "Agent" },
{ "id": "aiod", "type": "backend", "label": "aiod", "sublabel": "code" },
{ "id": "jupyter", "type": "cloud", "label": "Jupyter endpoint", "sublabel": "external" },
{ "id": "kernel", "type": "backend", "label": "ipykernel", "sublabel": "spawned by aiod" },
{ "id": "native", "type": "backend", "label": "Native REPL", "sublabel": "python3 · node" }
],
"segments": [
{ "from": 206, "to": 306, "label": "AIO_JUPYTER_ENDPOINT reachable" },
{ "from": 322, "to": 422, "label": "ipykernel importable" },
{ "from": 438, "to": 538, "label": "otherwise · every javascript request" }
],
"messages": [
{ "id": "execute", "from": "agent", "to": "aiod", "y": 176, "label": "execute code", "variant": "emphasis" },
{ "id": "proxy", "from": "aiod", "to": "jupyter", "y": 232, "label": "execute · proxied", "variant": "default" },
{ "id": "proxy-result", "from": "jupyter", "to": "aiod", "y": 276, "label": "result", "variant": "return" },
{ "id": "zmq", "from": "aiod", "to": "kernel", "y": 348, "label": "execute_request · ZMQ", "variant": "default" },
{ "id": "zmq-result", "from": "kernel", "to": "aiod", "y": 392, "label": "result", "variant": "return" },
{ "id": "harness", "from": "aiod", "to": "native", "y": 464, "label": "{code, timeout_ms}", "variant": "default" },
{ "id": "harness-result", "from": "native", "to": "aiod", "y": 508, "label": "result", "variant": "return" },
{ "id": "response", "from": "aiod", "to": "agent", "y": 568, "label": "status · outputs · stdout", "variant": "return" }
],
"activations": [
{ "participant": "aiod", "from": 168, "to": 580, "type": "backend" },
{ "participant": "jupyter", "from": 226, "to": 282, "type": "cloud" },
{ "participant": "kernel", "from": 342, "to": 398, "type": "backend" },
{ "participant": "native", "from": 458, "to": 514, "type": "backend" }
]
}
File: website/diagrams/aiod-code-execution-flow.sequence.json
{
"schema_version": 1,
"diagram_type": "sequence",
"meta": {
"title": "Analyze a dataset in a kernel session",
"quality_profile": "showcase",
"visual_preset": "signal-flow",
"column_fit": "spread",
"locale": "en",
"viewBox": [1080, 640]
},
"participants": [
{ "id": "agent", "type": "external", "label": "Agent" },
{ "id": "aiod", "type": "backend", "label": "aiod", "sublabel": "files · code" },
{ "id": "session", "type": "backend", "label": "Session", "sublabel": "analysis" },
{ "id": "files", "type": "database", "label": "Files", "sublabel": "/tmp/analysis" }
],
"messages": [
{ "id": "write-csv", "from": "agent", "to": "aiod", "y": 172, "label": "write sales.csv", "variant": "emphasis" },
{ "id": "store-csv", "from": "aiod", "to": "files", "y": 204, "label": "sales.csv", "variant": "default" },
{ "id": "execute-load", "from": "agent", "to": "aiod", "y": 236, "label": "execute", "variant": "emphasis" },
{ "id": "read-csv", "from": "aiod", "to": "session", "y": 268, "label": "df = read_csv()", "variant": "default" },
{ "id": "outputs", "from": "session", "to": "agent", "y": 300, "label": "outputs[0].text", "variant": "return" },
{ "id": "execute-plot", "from": "agent", "to": "aiod", "y": 332, "label": "execute · same session", "variant": "emphasis" },
{ "id": "savefig", "from": "aiod", "to": "session", "y": 364, "label": "Agg · savefig()", "variant": "default" },
{ "id": "store-png", "from": "session", "to": "files", "y": 396, "label": "plot.png", "variant": "default" },
{ "id": "list", "from": "agent", "to": "aiod", "y": 428, "label": "list files", "variant": "emphasis" },
{ "id": "listing", "from": "files", "to": "agent", "y": 460, "label": "files[]", "variant": "return" },
{ "id": "download", "from": "agent", "to": "aiod", "y": 492, "label": "download plot.png", "variant": "emphasis" },
{ "id": "bytes", "from": "files", "to": "agent", "y": 524, "label": "plot.png bytes", "variant": "return" }
],
"activations": [
{ "participant": "aiod", "from": 164, "to": 536, "type": "backend" },
{ "participant": "session", "from": 262, "to": 404, "type": "backend" }
]
}
File: website/diagrams/aiod-computer-action.sequence.json
{
"schema_version": 1,
"diagram_type": "sequence",
"meta": {
"title": "One computer action",
"quality_profile": "showcase",
"visual_preset": "signal-flow",
"column_fit": "spread",
"locale": "en",
"viewBox": [1080, 640]
},
"participants": [
{ "id": "agent", "type": "external", "label": "Agent" },
{ "id": "aiod", "type": "backend", "label": "aiod", "sublabel": "desktop proxy" },
{ "id": "worker", "type": "backend", "label": "computer-use", "sublabel": "worker" },
{ "id": "desktop", "type": "frontend", "label": "Desktop", "sublabel": "X display" }
],
"segments": [
{ "from": 152, "to": 384, "label": "action · screenshot" },
{ "from": 400, "to": 568, "label": "accessibility tree" }
],
"messages": [
{ "id": "action", "from": "agent", "to": "aiod", "y": 172, "label": "action + screenshot", "variant": "emphasis" },
{ "id": "action-proxied", "from": "aiod", "to": "worker", "y": 204, "label": "proxied", "variant": "default" },
{ "id": "inject", "from": "worker", "to": "desktop", "y": 236, "label": "input · xdotool / AT-SPI", "variant": "default" },
{ "id": "capture", "from": "worker", "to": "desktop", "y": 268, "label": "capture frame", "variant": "default" },
{ "id": "frame", "from": "desktop", "to": "worker", "y": 300, "label": "PNG", "variant": "return" },
{ "id": "action-result", "from": "worker", "to": "aiod", "y": 332, "label": "status · screenshot", "variant": "return" },
{ "id": "action-response", "from": "aiod", "to": "agent", "y": 364, "label": "base64 PNG", "variant": "return" },
{ "id": "find", "from": "agent", "to": "aiod", "y": 420, "label": "find nodes · role · name", "variant": "emphasis" },
{ "id": "find-proxied", "from": "aiod", "to": "worker", "y": 452, "label": "proxied", "variant": "default" },
{ "id": "atspi", "from": "worker", "to": "desktop", "y": 484, "label": "AT-SPI query", "variant": "default" },
{ "id": "nodes", "from": "desktop", "to": "worker", "y": 516, "label": "nodes", "variant": "return" },
{ "id": "find-response", "from": "aiod", "to": "agent", "y": 548, "label": "node_id · role · name · bounds", "variant": "return" }
],
"activations": [
{ "participant": "aiod", "from": 164, "to": 556, "type": "backend" },
{ "participant": "worker", "from": 196, "to": 340, "type": "backend" },
{ "participant": "worker", "from": 444, "to": 524, "type": "backend" },
{ "participant": "desktop", "from": 228, "to": 308, "type": "frontend" },
{ "participant": "desktop", "from": 476, "to": 524, "type": "frontend" }
]
}
File: website/diagrams/aiod-existing-environment.architecture.json
{
"schema_version": 1,
"diagram_type": "architecture",
"meta": {
"title": "Add aiod to an existing environment",
"quality_profile": "showcase",
"visual_preset": "signal-flow",
"locale": "en",
"viewBox": [
926,
420
]
},
"components": [
{
"id": "agent",
"type": "external",
"label": "Agent",
"sublabel": "one interface",
"pos": [
110,
127
],
"size": [
176,
72
]
},
{
"id": "aiod",
"type": "backend",
"label": "aiod",
"sublabel": "commands · files · browser · code",
"pos": [
480,
125
],
"size": [
304,
76
]
},
{
"id": "image",
"type": "external",
"label": "Your image",
"sublabel": "toolchain · private SDK",
"pos": [
480,
237
],
"size": [
142,
68
]
},
{
"id": "apps",
"type": "external",
"label": "Your apps",
"sublabel": "enterprise apps",
"pos": [
642,
237
],
"size": [
142,
68
]
}
],
"boundaries": [
{
"kind": "region",
"label": "Existing environment · container · VM · desktop",
"wraps": [
"aiod",
"image",
"apps"
]
}
],
"connections": [
{
"id": "agent-aiod",
"from": "agent",
"to": "aiod",
"label": "HTTP · MCP · SDK",
"variant": "emphasis"
}
]
}
File: website/diagrams/aiod-human-in-loop.architecture.json
{
"schema_version": 1,
"diagram_type": "architecture",
"meta": {
"title": "Agent and person",
"quality_profile": "showcase",
"visual_preset": "signal-flow",
"locale": "en"
},
"layout": {
"mode": "grid",
"cols": 3,
"cellW": 176,
"cellH": 72,
"gapX": 144,
"gapY": 56
},
"components": [
{
"id": "agent",
"type": "external",
"label": "Agent",
"sublabel": "tool calls",
"row": 0,
"col": 0,
"size": [
176,
72
]
},
{
"id": "aiod",
"type": "backend",
"label": "aiod",
"sublabel": "tools API",
"row": 1,
"col": 0,
"size": [
176,
72
]
},
{
"id": "terminal",
"type": "backend",
"label": "Terminal",
"sublabel": "PTY session",
"row": 0,
"col": 1,
"size": [
176,
72
]
},
{
"id": "chromium",
"type": "external",
"label": "Chromium",
"sublabel": "CDP :9222",
"row": 1,
"col": 1,
"size": [
176,
72
]
},
{
"id": "desktop",
"type": "backend",
"label": "Desktop",
"sublabel": "X display",
"row": 2,
"col": 1,
"size": [
176,
72
]
},
{
"id": "person",
"type": "external",
"label": "Person",
"sublabel": "browser tab",
"row": 0,
"col": 2,
"size": [
176,
72
]
},
{
"id": "gateway",
"type": "backend",
"label": "Gateway",
"sublabel": "nginx",
"row": 1,
"col": 2,
"size": [
176,
72
]
}
],
"boundaries": [
{
"kind": "region",
"label": "agent tools",
"wraps": [
"agent",
"aiod"
]
},
{
"kind": "region",
"label": "human in the loop",
"wraps": [
"person",
"gateway"
]
}
],
"connections": [
{
"id": "agent-aiod",
"from": "agent",
"to": "aiod",
"label": "HTTP",
"variant": "emphasis",
"labelDy": 24
},
{
"id": "aiod-terminal",
"from": "aiod",
"to": "terminal"
},
{
"id": "aiod-chromium",
"from": "aiod",
"to": "chromium"
},
{
"id": "aiod-desktop",
"from": "aiod",
"to": "desktop"
},
{
"id": "person-gateway",
"from": "person",
"to": "gateway",
"label": "HTTPS",
"labelDy": 24
},
{
"id": "gateway-terminal",
"from": "gateway",
"to": "terminal",
"label": "/terminal",
"variant": "dashed"
},
{
"id": "gateway-chromium",
"from": "gateway",
"to": "chromium",
"label": "/browser-ui",
"variant": "dashed",
"labelDx": -37
},
{
"id": "gateway-desktop",
"from": "gateway",
"to": "desktop",
"label": "/vnc",
"variant": "dashed"
}
]
}
File: website/diagrams/aiod-jupyter-kernel.architecture.json
{
"schema_version": 1,
"diagram_type": "architecture",
"meta": {
"title": "Jupyter kernels",
"quality_profile": "showcase",
"visual_preset": "signal-flow",
"locale": "en"
},
"layout": {
"mode": "grid",
"cols": 3,
"cellW": 176,
"cellH": 72,
"gapX": 144,
"gapY": 56
},
"components": [
{
"id": "agent",
"type": "external",
"label": "Agent",
"sublabel": "Jupyter plane",
"row": 1,
"col": 0,
"size": [
176,
72
]
},
{
"id": "aiod",
"type": "backend",
"label": "aiod",
"sublabel": "kernel client",
"row": 1,
"col": 1,
"size": [
176,
72
]
},
{
"id": "s1",
"type": "frontend",
"label": "ipykernel",
"sublabel": "session s1",
"row": 0,
"col": 2,
"size": [
176,
72
]
},
{
"id": "s2",
"type": "frontend",
"label": "ipykernel",
"sublabel": "session s2",
"row": 1,
"col": 2,
"size": [
176,
72
]
},
{
"id": "warm",
"type": "external",
"label": "ipykernel",
"sublabel": "warm · one-off",
"row": 2,
"col": 2,
"size": [
176,
72
]
}
],
"boundaries": [
{
"kind": "region",
"label": "spawned by aiod",
"wraps": [
"s1",
"s2",
"warm"
]
}
],
"connections": [
{
"id": "agent-aiod",
"from": "agent",
"to": "aiod",
"label": "HTTP",
"variant": "emphasis"
},
{
"id": "aiod-s1",
"from": "aiod",
"to": "s1",
"label": "ZMQ"
},
{
"id": "aiod-s2",
"from": "aiod",
"to": "s2"
},
{
"id": "aiod-warm",
"from": "aiod",
"to": "warm",
"variant": "dashed"
}
]
}
File: website/diagrams/aiod-mcp-flow.architecture.json
{
"schema_version": 1,
"diagram_type": "architecture",
"meta": {
"title": "MCP endpoint",
"quality_profile": "showcase",
"visual_preset": "signal-flow",
"locale": "en"
},
"layout": {
"mode": "grid",
"cols": 3,
"cellW": 176,
"cellH": 72,
"gapX": 144,
"gapY": 56
},
"components": [
{
"id": "agent",
"type": "external",
"label": "Agent",
"sublabel": "MCP client",
"row": 0,
"col": 0,
"size": [176, 72]
},
{
"id": "aiod",
"type": "backend",
"label": "aiod",
"sublabel": "/mcp · JSON-RPC",
"row": 0,
"col": 1,
"size": [176, 72]
},
{
"id": "builtin",
"type": "backend",
"label": "Built-in tools",
"sublabel": "files · command · code",
"row": 0,
"col": 2,
"size": [176, 72]
},
{
"id": "upstream",
"type": "external",
"label": "mcp-server-browser",
"sublabel": "EXTRA_MCP_SERVERS",
"row": 1,
"col": 2,
"size": [176, 72]
}
],
"boundaries": [
{
"kind": "region",
"label": "one tool list",
"wraps": ["builtin", "upstream"]
}
],
"connections": [
{
"id": "agent-aiod",
"from": "agent",
"to": "aiod",
"label": "tools/call",
"variant": "emphasis"
},
{
"id": "aiod-builtin",
"from": "aiod",
"to": "builtin"
},
{
"id": "aiod-upstream",
"from": "aiod",
"to": "upstream",
"label": "forward",
"variant": "dashed"
}
]
}
File: website/diagrams/aiod-overview.architecture.json
{
"schema_version": 1,
"diagram_type": "architecture",
"meta": {
"title": "aiod",
"quality_profile": "showcase",
"locale": "en"
},
"layout": {
"mode": "grid",
"cols": 3,
"cellW": 180,
"cellH": 72,
"gapX": 72,
"gapY": 40
},
"components": [
{
"id": "agent",
"type": "external",
"label": "Agent",
"sublabel": "SDK · curl · MCP",
"row": 1,
"col": 0
},
{
"id": "aiod",
"type": "backend",
"label": "aiod",
"sublabel": "tools API · /mcp",
"tag": ":18091",
"row": 1,
"col": 1
},
{
"id": "interp",
"type": "backend",
"label": "Interpreters",
"sublabel": "bash · python · node",
"row": 0,
"col": 2
},
{
"id": "chromium",
"type": "external",
"label": "Chromium",
"sublabel": "CDP :9222",
"row": 1,
"col": 2
},
{
"id": "cu",
"type": "backend",
"label": "computer-use",
"sublabel": "desktop · :18100",
"row": 2,
"col": 2
}
],
"boundaries": [
{
"kind": "region",
"label": "Same host or container",
"wraps": [
"interp",
"chromium",
"cu"
]
}
],
"connections": [
{
"id": "agent-aiod",
"from": "agent",
"to": "aiod",
"label": "HTTP · WebSocket",
"variant": "emphasis"
},
{
"id": "aiod-interp",
"from": "aiod",
"to": "interp",
"label": "spawn"
},
{
"id": "aiod-chromium",
"from": "aiod",
"to": "chromium",
"label": "CDP",
"labelDy": 13
},
{
"id": "aiod-cu",
"from": "aiod",
"to": "cu",
"label": "proxy"
}
]
}
File: website/diagrams/aiod-permission-model.architecture.json
{
"schema_version": 1,
"diagram_type": "architecture",
"meta": {
"title": "Permission model",
"quality_profile": "showcase",
"visual_preset": "signal-flow",
"locale": "en"
},
"layout": {
"mode": "grid",
"cols": 3,
"cellW": 176,
"cellH": 72,
"gapX": 144,
"gapY": 76
},
"components": [
{
"id": "agent",
"type": "external",
"label": "Agent",
"sublabel": "SDK · MCP · curl",
"row": 0,
"col": 0,
"size": [
176,
72
]
},
{
"id": "gate",
"type": "security",
"label": "API key",
"sublabel": "AIO_API_KEY",
"row": 0,
"col": 1,
"size": [
176,
72
]
},
{
"id": "aiod",
"type": "backend",
"label": "aiod",
"sublabel": "AIO_DEFAULT_USER",
"row": 1,
"col": 1,
"size": [
176,
72
]
},
{
"id": "exec",
"type": "backend",
"label": "Commands · PTY · Code",
"sublabel": "runs as user",
"row": 0,
"col": 2,
"size": [
176,
72
]
},
{
"id": "files",
"type": "database",
"label": "Files",
"sublabel": "owned by user",
"row": 1,
"col": 2,
"size": [
176,
72
]
}
],
"boundaries": [
{
"kind": "region",
"label": "container · VM",
"wraps": [
"gate",
"aiod",
"exec",
"files"
],
"pad": 40
},
{
"kind": "security-group",
"label": "per-request user",
"wraps": [
"exec",
"files"
]
}
],
"connections": [
{
"id": "agent-gate",
"from": "agent",
"to": "gate",
"label": "x-api-key",
"variant": "emphasis"
},
{
"id": "gate-aiod",
"from": "gate",
"to": "aiod"
},
{
"id": "aiod-exec",
"from": "aiod",
"to": "exec",
"label": "execution identity"
},
{
"id": "aiod-files",
"from": "aiod",
"to": "files",
"label": "ownership identity",
"labelDy": 14
}
]
}
File: website/diagrams/aiod-terminal-flow.sequence.json
{
"schema_version": 1,
"diagram_type": "sequence",
"meta": {
"title": "Attach to a terminal",
"quality_profile": "showcase",
"visual_preset": "signal-flow",
"column_fit": "spread",
"locale": "en",
"viewBox": [1080, 500]
},
"participants": [
{ "id": "agent", "type": "external", "label": "Agent" },
{ "id": "aiod", "type": "backend", "label": "aiod", "sublabel": "terminal" },
{ "id": "shell", "type": "backend", "label": "Shell" }
],
"messages": [
{ "id": "create", "from": "agent", "to": "aiod", "y": 172, "label": "create session demo-1", "variant": "emphasis" },
{ "id": "attach", "from": "agent", "to": "aiod", "y": 212, "label": "attach", "variant": "emphasis" },
{ "id": "ready", "from": "aiod", "to": "agent", "y": 252, "label": "ready", "variant": "return" },
{ "id": "input", "from": "agent", "to": "aiod", "y": 292, "label": "input · uname -s", "variant": "default" },
{ "id": "run", "from": "aiod", "to": "shell", "y": 332, "label": "uname -s", "variant": "default" },
{ "id": "output", "from": "shell", "to": "agent", "y": 372, "label": "output · Darwin", "variant": "return" }
],
"activations": [
{ "participant": "aiod", "from": 164, "to": 384, "type": "backend" },
{ "participant": "shell", "from": 324, "to": 380, "type": "backend" }
]
}
File: website/diagrams/aiod-terminal-reconnect.sequence.json
{
"schema_version": 1,
"diagram_type": "sequence",
"meta": {
"title": "Reattach with and without durable",
"quality_profile": "showcase",
"visual_preset": "signal-flow",
"column_fit": "spread",
"locale": "en",
"viewBox": [1080, 624]
},
"participants": [
{ "id": "agent", "type": "external", "label": "Agent" },
{ "id": "aiod", "type": "backend", "label": "aiod", "sublabel": "terminal" },
{ "id": "shell", "type": "backend", "label": "Shell" }
],
"segments": [
{ "from": 160, "to": 232, "label": "socket drops" },
{ "from": 250, "to": 394, "label": "A · durable=true" },
{ "from": 412, "to": 524, "label": "B · plain attach" }
],
"messages": [
{ "id": "keeps-running", "from": "shell", "to": "aiod", "y": 196, "label": "output continues", "variant": "default" },
{ "id": "attach-durable", "from": "agent", "to": "aiod", "y": 284, "label": "attach durable", "variant": "emphasis" },
{ "id": "ready-resumed", "from": "aiod", "to": "agent", "y": 324, "label": "ready · resumed true", "variant": "return" },
{ "id": "gap-output", "from": "aiod", "to": "agent", "y": 364, "label": "output · the gap", "variant": "return" },
{ "id": "attach-plain", "from": "agent", "to": "aiod", "y": 452, "label": "attach", "variant": "emphasis" },
{ "id": "ready-fresh", "from": "aiod", "to": "agent", "y": 492, "label": "ready · resumed false", "variant": "return" }
],
"activations": [
{ "participant": "aiod", "from": 164, "to": 504, "type": "backend" },
{ "participant": "shell", "from": 164, "to": 504, "type": "backend" }
]
}
File: website/diagrams/aiod-watch-flow.sequence.json
{
"schema_version": 1,
"diagram_type": "sequence",
"meta": {
"title": "Watch a directory",
"quality_profile": "showcase",
"visual_preset": "signal-flow",
"column_fit": "spread",
"locale": "en",
"viewBox": [1080, 600]
},
"participants": [
{ "id": "agent", "type": "external", "label": "Agent" },
{ "id": "aiod", "type": "backend", "label": "aiod", "sublabel": "files · watch" },
{ "id": "files", "type": "database", "label": "Files", "sublabel": "/tmp/demo" },
{ "id": "command", "type": "backend", "label": "Command" }
],
"messages": [
{ "id": "start-watch", "from": "agent", "to": "aiod", "y": 172, "label": "watch /tmp/demo", "variant": "emphasis" },
{ "id": "subtree", "from": "aiod", "to": "files", "y": 204, "label": "watch subtree", "variant": "default" },
{ "id": "run", "from": "agent", "to": "aiod", "y": 236, "label": "run command", "variant": "emphasis" },
{ "id": "spawn", "from": "aiod", "to": "command", "y": 268, "label": "spawn", "variant": "default" },
{ "id": "write", "from": "command", "to": "files", "y": 300, "label": "writes src/util.py", "variant": "default" },
{ "id": "notify", "from": "files", "to": "aiod", "y": 332, "label": "create · write src/util.py", "variant": "dashed" },
{ "id": "poll-0", "from": "agent", "to": "aiod", "y": 364, "label": "poll cursor=0", "variant": "emphasis" },
{ "id": "events-1-2", "from": "aiod", "to": "agent", "y": 396, "label": "events 1–2 · cursor 2", "variant": "return" },
{ "id": "poll-2", "from": "agent", "to": "aiod", "y": 428, "label": "poll cursor=2 · timeout=30", "variant": "emphasis" },
{ "id": "events-after", "from": "aiod", "to": "agent", "y": 460, "label": "events after 2", "variant": "return" },
{ "id": "stop-watch", "from": "agent", "to": "aiod", "y": 492, "label": "stop watch", "variant": "emphasis" }
],
"activations": [
{ "participant": "aiod", "from": 164, "to": 504, "type": "backend" },
{ "participant": "command", "from": 262, "to": 308, "type": "backend" }
]
}
File: website/docs/README.md
AIO Sandbox Documentation
This documentation provides comprehensive guides and API references for the AIO Sandbox environment.
📚 Documentation Structure
docs/
├── index.md # Homepage with overview
├── guide/ # User guides and tutorials
│ ├── quick-start.md
│ └── ...
├── api/ # API reference documentation
│ ├── index.md # API overview
│ ├── swagger.md # Interactive API docs guide
│ ├── file.md # File operations API
│ ├── shell.md # Shell & terminal API
│ ├── browser.md # Browser automation API
│ └── mcp.md # MCP services API
└── examples/ # Practical examples
├── agent.md
├── browser.md
└── terminal.md🚀 Viewing Documentation
Local Development
If you're running the documentation locally:
# Install dependencies
npm install
# Start dev server
npm run docs:dev
# Build for production
npm run docs:buildLive API Documentation
When AIO Sandbox is running, access the interactive Swagger UI at:
- Swagger UI: http://localhost:8080/v1/docs
- OpenAPI Spec: http://localhost:8080/openapi.json
📖 Key Sections
For Users
- Quick Start - Get up and running in minutes
- Features Guide - Explore all capabilities
- Examples - Real-world use cases
For Developers
- API Reference - Complete API documentation with interactive Swagger UI
🛠️ API Integration
Generate Client SDKs
Use the OpenAPI specification to generate client libraries:
# Install OpenAPI Generator
npm install -g @openapitools/openapi-generator-cli
# Generate Python client
openapi-generator-cli generate \
-i http://localhost:8080/openapi.json \
-g python \
-o ./python-client
# Generate TypeScript client
openapi-generator-cli generate \
-i http://localhost:8080/openapi.json \
-g typescript-axios \
-o ./typescript-clientImport to API Tools
- Postman: Import → URL →
http://localhost:8080/openapi.json - Insomnia: Import → From URL →
http://localhost:8080/openapi.json - Bruno: Import → OpenAPI →
http://localhost:8080/openapi.json
📝 Documentation Updates
The documentation is automatically generated from:
- OpenAPI Specification (
/openapi.json) - API endpoints and schemas - Markdown Files - Guides, examples, and additional content
- Code Comments - Inline documentation in source code
To update documentation:
- Modify the relevant markdown files in
/docs - Update OpenAPI spec for API changes
- Run build to generate updated docs
🤝 Contributing
Contributions to documentation are welcome! Please:
- Follow the existing structure and style
- Write from a user perspective
- Include practical examples
- Test all code samples
- Update the OpenAPI spec for API changes
📚 Resources
- GitHub Repository: https://github.com/agent-infra/sandbox
- Issue Tracker: https://github.com/agent-infra/sandbox/issues
- Docker Hub: https://hub.docker.com/r/agent-infra/sandbox
📄 License
This documentation is part of the AIO Sandbox project and follows the same license terms.
File: website/docs/en/404.md
pageType: custom layout: false
██╗ ██╗ ██████╗ ██╗ ██╗
██║ ██║██╔═══██╗██║ ██║
███████║██║ ██║███████║
╚════██║██║ ██║╚════██║
██║╚██████╔╝ ██║
╚═╝ ╚═════╝ ╚═╝
<h1 style="font-size: 2.8rem; margin: 1.5rem 0 0.5rem; color: #2c3e50; font-weight: 700;">
Oops! Page Not Found
</h1>
The page you're looking for seems to have escaped from the sandbox!
Let's get you back on track with these helpful links.
<a href="/sandbox/"
style="display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #3498db, #2980b9); color: white; padding: 1rem 1.5rem; text-decoration: none; border-radius: 10px; font-weight: 600; transition: transform 0.2s; box-shadow: 0 4px 15px rgba(52, 152, 219, 0.3);"
onmouseover="this.style.transform='translateY(-2px)'"
onmouseout="this.style.transform='translateY(0)'">
🏠
Go Home
</a>
<a href="/sandbox/guide/"
style="display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #2ecc71, #27ae60); color: white; padding: 1rem 1.5rem; text-decoration: none; border-radius: 10px; font-weight: 600; transition: transform 0.2s; box-shadow: 0 4px 15px rgba(46, 204, 113, 0.3);"
onmouseover="this.style.transform='translateY(-2px)'"
onmouseout="this.style.transform='translateY(0)'">
📖
Documentation
</a>
<a href="/sandbox/api/"
style="display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #e67e22, #d35400); color: white; padding: 1rem 1.5rem; text-decoration: none; border-radius: 10px; font-weight: 600; transition: transform 0.2s; box-shadow: 0 4px 15px rgba(230, 126, 34, 0.3);"
onmouseover="this.style.transform='translateY(-2px)'"
onmouseout="this.style.transform='translateY(0)'">
🔧
API Reference
</a>
<h3 style="margin-top: 0; margin-bottom: 1.5rem; color: #2c3e50; font-size: 1.3rem; font-weight: 600;">
🔍 Popular Pages
</h3>
<h4 style="color: #3498db; margin: 0 0 0.5rem; font-size: 1rem;">Getting Started</h4>
<li style="margin: 0.3rem 0;">
<a href="/sandbox/guide/quick-start" style="color: #555; text-decoration: none; font-size: 0.9rem; display: flex; align-items: center;">
⚡Quick Start Guide
</a>
</li>
<li style="margin: 0.3rem 0;">
<a href="/sandbox/guide/features" style="color: #555; text-decoration: none; font-size: 0.9rem; display: flex; align-items: center;">
✨Features Overview
</a>
</li>
<h4 style="color: #e67e22; margin: 0 0 0.5rem; font-size: 1rem;">API Documentation</h4>
<li style="margin: 0.3rem 0;">
<a href="/sandbox/api/shell" style="color: #555; text-decoration: none; font-size: 0.9rem; display: flex; align-items: center;">
🐚Shell WebSocket API
</a>
</li>
<li style="margin: 0.3rem 0;">
<a href="/sandbox/api/file" style="color: #555; text-decoration: none; font-size: 0.9rem; display: flex; align-items: center;">
📁File Operations API
</a>
</li>
Try searching for "terminal", "file operations", "browser", or "API"
<h4 style="color: #666; margin-bottom: 1rem; font-size: 1rem;">AIO Sandbox Architecture</h4>┌─────────────────────────────────────────────────────────────┐
│ 🌐 Browser + VNC │
├─────────────────────────────────────────────────────────────┤
│ 💻 VSCode Server │ 🐚 Shell Terminal │ 📁 File Ops │
├─────────────────────────────────────────────────────────────┤
│ 🔗 MCP Hub + 🔒 Sandbox Fusion │
├─────────────────────────────────────────────────────────────┤
│ 🚀 Preview Proxy + 📊 Service Monitoring │
└─────────────────────────────────────────────────────────────┘
Still can't find what you're looking for?
[Visit our GitHub](https://github.com/agent-infra/sandbox)
or check the
[examples section](/sandbox/examples/).File: website/docs/en/_nav.json
[
{
"text": "Guide",
"link": "/guide/start/introduction",
"activeMatch": "/guide/"
},
{
"text": "Daemon",
"link": "/daemon/start/introduction",
"activeMatch": "/daemon/"
},
{
"text": "API",
"link": "/api",
"activeMatch": "/api/"
},
{
"text": "Blog",
"link": "/blog/index",
"activeMatch": "/blog/"
},
{
"text": "Version",
"items": [
{
"text": "Changelog",
"link": "https://github.com/agent-infra/sandbox/releases"
}
]
}
]
File: website/docs/en/index.md
pageType: home
hero:
name: AIO Sandbox
text: |
All-in-One Environment
for AI Agents
tagline: |
🌐 Browser | 💻 Terminal | 📁 File
🔧 VSCode | 📊 Jupyter | MCP actions:
- theme: brand
text: Get Started
link: /guide/start/introduction
- theme: alt
text: GitHub
link: https://github.com/agent-infra/sandbox
image:
src: /aio-icon.png
alt: AIO Sandbox Logo
features:
- title: Unified Environment
details: One Docker container with shared filesystem. Files downloaded in the browser are instantly accessible in Terminal and VSCode.
icon: 🌐 - title: Out of the Box
details: Built‑in VNC browser, VS Code, Jupyter, file manager, and terminal—accessible directly via API/SDK.
icon: ⚡ - title: Secure Execution
details: Isolated Python and Node.js sandboxes. Safe code execution without system risks.
icon: 🔐 - title: Agent-Ready
details: Pre-configured MCP Server with Browser, File, Terminal, Markdown, Ready-to-use for AI agents.
icon: 🤖 - title: Developer Friendly
details: Cloud-based VSCode with persistent terminals, intelligent port forwarding(via${Port}-${domain}/or/proxy), and instant frontend/backend previews.
icon: 🔧 - title: Production Ready
details: Enterprise-grade Docker deployment. Lightweight, scalable.
icon: 🚀
File: website/docs/en/blog/_meta.json
["index.mdx", "announcing-0"]
File: website/docs/en/blog/announcing-0.mdx
date: 2025-10-29 14:00:00 sidebar: false
October 29, 2025
AIO Sandbox: An Integrated and Customizable Sandbox Environment for AI Agents
Background
As LLMs continue to evolve, AI application forms have undergone three generational shifts:
- Chatbot: Conversational interaction, answering questions
- Copilot: Collaborative assistance, improving efficiency
- Agent: Autonomous execution, completing tasks
Agents can autonomously perceive their environment, plan steps, and invoke tools, enabling them to operate computers like humans: automatically browsing web pages to collect information, generating and running code to analyze data, executing system commands to manage files, and even completing complex multi-step operations through visual interfaces. This capability allows Agent deliverables to approach or even exceed human professional standards.
Pain Points
🧩 Environment Fragmentation: Multiple single-function sandboxes (like E2B for code execution, Browserbase for browsers) force Agents to transfer data across sandboxes via NAS/OSS, increasing latency and complexity. For example: a deep research Agent completing 'convert a paper into a PPT' needs to exchange dozens of intermediate files (JSON configs, chart images, preview screenshots, etc.) across multiple sandboxes, adding complexity and overhead to the entire Agent system.
🎁 Difficult Customization: Different types of Agents require different pre-installed tech stacks. Traditional sandboxes provide unified pre-installed environments that cannot meet all Agents' personalized needs.
🔒 Security Isolation Challenges: Need to give Agents real system capabilities (network, files, browser, GPU) while maintaining strong isolation to prevent unauthorized access and data leaks.
🖥️ Difficult Visual Interaction: Complex Agent tasks require human takeover, functional sandboxes need to integrate VNC, Terminal, VSCode to maintain consistent experience. Resolution switching, screenshots, and GUI visual operations.
🌐 Browser Environment Complexity: Anti-automation and fingerprint risk control, CDP instability, inadequate proxy support with username/password, missing GUI operations.
A well-configured computer can significantly improve human work efficiency; similarly, a powerful sandbox environment can also improve Agent task quality and execution speed.
Introduction
One-sentence introduction: AIO Sandbox integrates browser, code execution, terminal, visual takeover, forward and reverse proxy, MCP, authentication and other basic functions in a single sandbox, allowing environment customization based on needs, enabling different Agents to "complete tasks more efficiently in a unified environment container".
Website: sandbox.agent-infra.com
Github: github.com/agent-infra/sandbox
Features
- 📦 Out-of-the-box: Connect directly to sandbox capabilities via
/mcpprotocol, also providing API / SDK for customizing sandbox toolsets. - 🚀 Second-level Startup: Full sandbox service startup completes in seconds, reaching millisecond-level after pre-caching/cold start.
- 🌈 Customizable: Agents in various vertical scenarios need domain-specific tools and dependencies; AIO provides a unified image base, supporting on-demand expansion with convention-based routing and service configuration.
- 🌐 Browser: Integrates Web Infra's RS lightweight kernel, providing CDP, screenshots, pure visual GUI operations, and Proxy configuration.
- 🔄 Human Takeover: Provides browser VNC, Code Server, Terminal, supporting human takeover and debugging mid-task.
- 📡 Proxy and Forwarding: Supports forward proxy with authentication; maps
{port}-{domain}wildcard domains or/proxy|/absproxy/{port}paths to services inside the sandbox (convenient for preview/demo). - 🔒Security Authentication: JWT Bearer access control; provides Short-Lived Tickets for links that cannot carry Headers.
Examples
| Instruction | Replay | Screenshot |
|---|---|---|
| Help me design an interesting website to introduce sauropod dinosaurs from the Jurassic and Cretaceous periods for elementary school children. I want the website to be cartoon-styled. | Replay | |
| Search for news about ByteDance's Seed 1.6 model, then write a modern-styled webpage and deploy it | Replay | |
| Based on this OSWorld image, please search for the latest information on the internet and design a modern website for it. | Replay | |
| Play Poki 2048 game | Replay |
Quick Start
Cloud
One-Click Deploy All-in-One Sandbox Application--Function Service-Volcano Engine
Local
Prerequisites: Install Docker, then start locally with one command:
docker run --rm -it -p 127.0.0.1:8080:8080 ghcr.io/agent-infra/sandbox:latest
# For faster access in China
# docker run --security-opt seccomp=unconfined --rm -it -p 127.0.0.1:8080:8080 enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest**System Architecture**
Overview
AIO Sandbox provides Agents with basic capabilities like Browser, File, Shell, Code, and offers extensibility to support developers in combining and customizing dedicated sandboxes based on Agent needs (such as AIO Sandbox for Mobile/Medical/Legal/Finance/Scientific Research).
Sandbox customization levels increase progressively:
- Standard (Out-of-the-box): Plug-and-play for Agents via
/mcpendpoint, suitable for quick PoC Agent validation. - Custom Toolset (Tool/Skills Extension): Without modifying the image, add or orchestrate tools based on SDK/API (such as adding
web_search); also extend Skills to implement automated handling of specific sandbox tasks. - Custom Image: Based on
FROM aio.sandboxbase image, install specific dependencies (such as multimedia/image processing, etc.), mount custom services (e.g.,/custom_tools/ocrimage recognition).
Core Components
Browser
Browser environment for Agents, core is providing CDP and VNC, mainstream Browser Use frameworks can be used directly;
AIO provides x11-based browser GUI visual operation interface, which can be combined with CDP for more efficient, lower risk-control Browser Use solutions.
CDP
CDP (Chrome Devtools Protocol) is a protocol for communicating with Chrome or Chromium browsers, providing browser control APIs via WebSocket for navigation and loading, DOM manipulation, JS execution/debugging, network interception and simulation, screenshots and rendering, security and permissions, etc.
For a more intuitive understanding, here's an example of using CDP to initiate a page navigation command:
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' \
--disable-gpu \
--user-data-dir=./test \
--remote-debugging-port=9222 \
https://www.chromestatus.comVisit http://localhost:9222/json/version, where webSocketDebuggerUrl is the CDP address:
$ curl http://localhost:9222/json/version
{
"Browser": "Chrome/141.0.7390.66",
"Protocol-Version": "1.3",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
"V8-Version": "14.1.146.11",
"WebKit-Version": "537.36 (@95681a3c3d516c397b75ff45b8980c1088666775)",
"webSocketDebuggerUrl": "ws://localhost:9222/devtools/browser/a6c5f19f-5d24-4bed-ba08-9c15cf5aeedb"
}After establishing a WebSocket connection with CDP, you can execute browser commands:
Note: AIO Sandbox doesn't directly expose the CDP interface
/json/version, but relays CDP through the uvicorn service and adds heartbeat detection to avoid ws disconnection issues.
GUI Visual Operations
Screenshots
Unlike CDP-based screenshots, visual screenshots /v1/browser/screenshot include Tabs (the entire browser window), and operations target the entire browser window.
| GUI Browser Screenshot (Tabs) | CDP-based Page Screenshot (Page) |
|---|---|
Unlike CDP browser operations, visual operations /v1/browser/actions simulate human behavior for clicking, typing, scrolling, etc., which can reduce target website risk control strategies.
Unified Action Space
Abstract GUI operations into composable minimal atomic actions, such as moving mouse, clicking, dragging, scrolling, key press, text input, and additional utility functions like wait, aligning as closely as possible with VLM visual models executing actual actions.
| action_type | Description | Required Parameters | Optional Parameters |
|---|---|---|---|
MOVE_TO |
Move mouse to specified position | x, y |
- |
MOVE_REL |
Move relative to current mouse position | x_offset, y_offset |
- |
CLICK |
Click operation | - | x, y, button, num_clicks |
MOUSE_DOWN |
Press mouse button | - | button |
MOUSE_UP |
Release mouse button | - | button |
RIGHT_CLICK |
Right click | - | x, y |
DOUBLE_CLICK |
Double click | - | x, y |
DRAG_TO |
Drag to specified position | x, y |
- |
DRAG_REL |
Drag relative to current mouse position | x_offset, y_offset |
- |
SCROLL |
Scroll operation | - | dx, dy |
TYPING |
Type text | text |
- |
PRESS |
Press key | key |
- |
KEY_DOWN |
Press keyboard key | key |
- |
KEY_UP |
Release keyboard key | key |
- |
HOTKEY |
Key combination | keys (array), e.g.: ["ctrl", "c"] |
- |
WAIT |
Wait | duration time (seconds s) |
- |
Takeover
When Browser Use encounters login requirements, human takeover is generally needed, requiring an interactive browser interface. Currently there are two approaches:
VNC Takeover: AIO Sandbox provides
/vnc/index.htmlpage for direct user interaction.Frontend connects via CDP, real-time redrawing complete browser interface on Canvas (Playground); we've packaged the frontend part into a component @agent-infra/browser-ui. Below, left is the actual browser, right is browser-ui screen mirroring:
The differences between the two takeover methods are roughly as follows:
| Comparison Dimension | VNC | Canvas + CDP (Chrome DevTools Protocol) |
|---|---|---|
| Technical Principle | Remote desktop protocol, transmits entire screen pixels | Controls browser via CDP, Canvas renders content |
| Transport Protocol | RFB (Remote Framebuffer) | WebSocket + CDP |
| Transport Content | Complete browser view (with Tabs) | Only browser current page content (no Tabs by default, can be implemented separately) |
| Bandwidth Usage | High (10-50 Mbps) | Low (1-5 Mbps) |
| Latency | Higher (50-200ms) | Lower (10-50ms) |
| Stability | Not easily disconnected | Easily disconnected, needs manual heartbeat with CDP to avoid disconnection |
| CPU Usage | High (desktop encoding) | Low (browser rendering only) |
| Memory Usage | High (needs complete desktop environment) | Low (browser process only) |
| Control Range | Entire browser | Browser internal pages only |
| Automation Capability | Basic (mouse keyboard simulation) | Powerful (DOM operations, network interception, JS injection, etc.) |
| Multi-window Support | ✅ Supported | ❌ Single browser window only |
| File Operations | ✅ Can operate local files | ❌ Limited by browser sandbox |
Command Line Interpreter
For Coding Agents, most tasks can be completed through command line execution. When designing the Shell module, using OpenHands' CmdRunAction as the execution engine, combined with tmux, implements multi-session execution capability.
File Operations
File/code editing only requires two tools:
File CRUD: Encapsulates basic I/O for file read/write/list directory/create/upload/download, with path validation and permission control, covering common file operation scenarios.
Text Editor: Implements model-oriented fine-grained editing tool str_replace_editor, supporting:
view(view file or directory, including line range)str_replace(exact string replacement)insert(insert by line, legacy version support)undo_edit(undo)
Code Execution
Balancing language coverage and image size, using Python 3.10/3.11/3.12 and Node.js 22 runtimes from Sandbox Fusion, providing an integrated secure isolation environment for code execution.
MCP Servers Aggregator
Aggregates multiple MCP Servers (e.g., chrome-devtools-mcp) through unified entry point /mcp, supporting parameter-level filtering, and allowing tool name prefixing (namespacing).
Filter MCP Servers by search, future expansion will include tags (tags) and category (category) multi-dimensional filtering to reduce redundant calls and lower model token costs.
Proxy
In Agent sandboxes, there are generally two types of scenarios corresponding to forward and reverse proxies:
Forward Proxy: Browser Use Agent can access private/global networks
Reverse Proxy: Coding Agent services developed inside the sandbox are exposed externally for user-side preview
Forward Proxy
Using TinyProxy proxy server to bypass geographic restrictions, access restricted content, or provide secure access within corporate intranets.
Why introduce TinyProxy when Chrome has --proxy-server to specify proxy?
The Chromium official documentation states that it will not use any username/password embedded in proxy settings (e.g., http://user:pass@host:port), authentication must go through a separate challenge dialog, affecting the entire Browser Use experience (as shown below):
Reverse Proxy
Provides two methods to access service ports inside the Sandbox:
subdomain wildcard forwarding (recommended): Any domain matching
${port}-${domain}format will be forwarded to ports inside the sandbox.subpath forwarding: Encounters many issues: for routing-sensitive services (like frontend projects), the additional
/proxy|absproxy/${port}path causes resource matching 404s.
Authentication
Agent operations in the sandbox generate user data. To implement unified AIO Sandbox authentication without intrusion, without modifying any existing business routing configuration, and without increasing the mental burden of future routing configuration expansion, an "asymmetric encryption + JWT" reverse proxy architecture was designed at the internal Nginx gateway layer:
How to Enable (One-time Configuration)
- Generate key pair
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem
echo "Key pair generation complete!"- Start service (with public key to enable authentication), using environment variable
JWT_PUBLIC_KEY
export JWT_PUBLIC_KEY=$(cat public_key.pem | base64)
JWT_PUBLIC_KEY="${JWT_PUBLIC_KEY}"Issue JWT
Business service uses private key to generate a JWT valid for 1 hour. Below is a simplified script to generate JWT, in practice business backend should use mature JWT libraries:
# This is a simplified script to generate JWT, in practice business backend should use mature JWT libraries
base64url_encode() { openssl base64 -e -A | tr '+/' '-_' | tr -d '='; }
header='{"alg":"RS256","typ":"JWT"}'
exp_time=$(($(date +%s) + 3600))
payload="{\"exp\":${exp_time}}"
to_be_signed="$(echo -n "$header" | base64url_encode).$(echo -n "$payload" | base64url_encode)"
signature=$(echo -n "$to_be_signed" | openssl dgst -sha256 -sign private_key.pem | base64url_encode)
jwt="${to_be_signed}.${signature}"
echo "JWT generated: ${jwt}"Usage
Header Authentication
shellcurl --silent -X GET "http://localhost:8080/v1/sandbox" \ -H "Authorization: Bearer ${jwt}"Short-Lived Ticket Authentication Example (using VNC page access): Direct access cannot authenticate via Header method, can only use
?ticket=ticket as query parameter.- Use JWT to obtain ticket from common endpoint (default validity is 30s, can be configured via
TICKET_TTL_SECONDSenvironment variable)
bashecho "Using JWT to exchange for common one-time ticket..." ticket_response=$(curl --silent -X POST "http://localhost:8080/tickets" \ -H "Authorization: Bearer ${jwt}") ticket=$(echo "$ticket_response" | jq -r .ticket) expires=$(echo "$ticket_response" | jq -r .expires_in) echo "Successfully obtained! Ticket: ${ticket}, Validity: ${expires} seconds"- Client builds and uses VNC URL: Now you can use the obtained
${ticket}variable to build the VNC URL and initiate access.
bash# Bash script simulates client URL concatenation vnc_url="http://localhost:8080/vnc/index.html?ticket=${ticket}&path=websockify%3Fticket%3D${ticket}" echo "Client-built final URL: ${vnc_url}" # Simulate access (should be done in browser) # curl -I "${vnc_url}"- Use JWT to obtain ticket from common endpoint (default validity is 30s, can be configured via
Extension and Ecosystem
Custom Images
In AIO, service processes (supervisord) and service routing (Nginx) are automatically mounted following convention-based directories:
- Service process directory:
/opt/gem/supervisord/*.conf - Routing directory:
/opt/gem/nginx/*.conf
To customize services and routing on top of the AIO image, refer to the following image code:
FROM enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:latest
# ----------------------
# Install additional system dependencies (if any)
# installed path: /usr/bin/*
# ----------------------
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
${your_system_dep} \
--no-install-recommends; \
# clean up
apt-get clean && \
rm -rf /var/lib/apt/lists/*;
# ----------------------
# npm install (if any)
#
# ----------------------
RUN npm i -g ${your_npm_package}
# ----------------------
# python pip install (if any)
# installed path: /usr/local/bin/*
# ----------------------
RUN pip install ${your_python_package}
# Add custom Server service
COPY ./supervisord.agent_server.conf /opt/tiger/run/supervisord/agent_server.conf
# Bind Nginx routing
COPY ./nginx.agent_server.conf /opt/gem/nginx/nginx.agent_server.conf
# # If you don't need services in AIO, you can delete them, e.g., Code Server
# ## Delete Code Server process and routing
# RUN rm -rf /opt/gem/supervisord/supervisord.code_server.conf
# ## Delete Code Server routing
# RUN rm -rf /opt/gem/nginx/code_server.confSDK Integration
Using fern to convert AIO Sandbox API documentation directly into Python / Go / Node.js SDKs. Using Python as an example, a few lines of code connect AIO Sandbox's core functionality:
from agent_sandbox import Sandbox
client = Sandbox(base_url="http://localhost:8080")
# Execute Shell
shell_res = client.shell.exec_command(command="ls -la")
print(shell_res.data.output) # /home/gem
# Browser Screenshot
screenshot = client.browser.screenshot()
print(screenshot)
# Get Browser CDP
browser_info = client.browser.get_browser_info()
cdp_url = browser_info.data.cdp_url # ws://
# Read File
file_res = client.file.read_file(file="/home/gem/.bashrc")
print(file_res.data.content)More usage examples: agent-infra/sandbox#examples
browser-use
Just add 4 lines of code to integrate the community's browser-use:
Complete code: browser-use#main.py
LangGraph-DeepAgents
Complete code: langgraph-deepagents#main.py
Custom Toolsets
You can use API / SDK to compose high-level toolsets needed by Agents, for example link_reader returns page content for a URL:
from openai import OpenAI
from agent_sandbox import Sandbox
import json
client = OpenAI(
api_key="your_api_key",
)
sandbox = Sandbox(base_url="http://localhost:8080")
tools = [{
"type": "function",
"function": {
"name": "link_reader",
"description": "Render and read webpage, return title, body text, and final URL (based on CDP).",
"parameters": {
"type": "object",
"properties": {
"url": {"type": "string", "format": "uri"},
"timeout_ms": {"type": "integer", "default": 30000}
},
"required": ["url"]
}
}
}]
async def link_reader(url: str, timeout_ms: int = 30_000) -> dict:
cdp_url = sandbox.browser.get_browser_info().cdp_url
async with async_playwright() as p:
browser = await p.chromium.connect_over_cdp(cdp_url)
try:
page = await browser.new_page()
await page.goto(url, wait_until="networkidle", timeout=timeout_ms)
title = await page.title()
text = await page.evaluate("document.body.innerText || ''")
return {"final_url": page.url, "title": title, "text": text[:8000]}
finally:
await browser.close()Deployment
Currently the best public cloud deployment form is function computing, based on Sandbox's designated instance access capability: One-Click Deploy All-in-One Sandbox Application--Function Service-Volcano Engine
Summary and Outlook
AIO Sandbox provides an integrated, customizable base environment (Agent Env), enabling Agents to complete diverse tasks including browsing, executing code, running commands, and file operations within the same environment, while supporting customization of domain-specific sandboxes for different Agents. This sandbox system will continue to evolve and expand alongside the rising intelligence ceiling of Agents and the creativity of developers.
Going forward, we will continue to refine stability, observability, and ecosystem integration, continuously improve evaluation systems and best practices, driving robust deployment and efficient operation of AIO Sandbox in more large-scale, high-demand Agent application scenarios.
Appendix
Terminology
| Term | Explanation |
|---|---|
| Agent | In the LLM context, an AI Agent is an intelligent entity that can autonomously understand intent, plan decisions, and execute complex tasks. An Agent is not an upgraded version of ChatGPT; it doesn't just tell you "how to do it," but actually helps you do it. If Copilot is the co-pilot, then Agent is the main driver. Similar to the human process of "doing things," an Agent's core functions can be summarized as a loop of three steps: Perception, Planning, and Action. |
| Copilot | Copilot refers to an AI-based assistance tool, typically integrated with specific software or applications, designed to help users improve work efficiency. Copilot systems analyze user behavior, inputs, data, and history to provide real-time suggestions, automate tasks, or enhance functionality, helping users make decisions or simplify operations. |
| AIO | All-In-One, refers to integrating multiple capabilities (Browser, Code Execution, Shell, File, visual takeover, authentication, proxy, etc.) within a single image/instance, reducing cross-environment switching and data transfer. |
| Sandbox | A controlled, isolated execution environment. Used to run browsers, code, or command lines, controlling resources and permissions, reducing impact and risk to the host system. |
| CDP | CDP (Chrome Devtools Protocol) is a protocol for communicating with Chrome or Chromium browsers. It allows developers to interact with browsers by sending commands and receiving events for debugging, analysis, and automated browser operations. CDP provides a set of APIs (Application Programming Interface) defining browser behavior and functionality. |
| VNC | VNC is a suite of "remote desktop sharing/control" technologies and tools based on the RFB (Remote Framebuffer) protocol. Core idea: encode the remote host's screen framebuffer (pixels) and transmit over network to the client, while replaying client keyboard and mouse events to the remote host, enabling cross-platform remote operation. |
| MCP | Model Context Protocol is an open protocol that standardizes how applications provide context to LLMs. Think of MCP as the USB-C port for AI applications. Just like USB-C provides a standard way for your devices to connect to various peripherals and accessories, MCP provides a standard way for your AI models to connect to different data sources and tools. |
| Browser Use | General term for Agents completing tasks like search, login, clicking, form filling, downloading through browsers, either via CDP commands or GUI visual operations. |
| OpenHands | OpenHands is an open-source AI Software Developer Agent Platform for training, evaluating, and running large language models (LLM) that can "autonomously program" in real development environments. It was initially released as OpenDevin, later renamed to OpenHands, maintained by the All Hands AI community. |
References
- UI-TARS-2 Technical Report: Advancing GUI Agent with Multi-Turn Reinforcement Learning
- AIO Sandbox: All-in-One Sandbox for AI Agents & Developers
- Agentic AI Infrastructure Practice Series (2): Necessity and Practice of Dedicated Sandbox Environment
- Writing effective tools for AI agents—using AI agents
- Unifying the Computer Use Action Space
File: website/docs/en/blog/index.mdx
title: Overview sidebar: false
AIO Sandbox Blog
Check here for the latest articles and release announcements about AIO Sandbox.
[AIO Sandbox: An Integrated and Customizable Sandbox Environment for AI Agents](/blog/announcing-0)
October 29, 2025
AIO Sandbox integrates all capabilities through a Docker image, providing a unified file system and authentication, and supports image customization, thereby enhancing the efficiency of Agent task execution and delivery.
File: website/docs/en/daemon/_meta.json
[
{
"type": "dir",
"label": "Getting Started",
"name": "start"
},
"architecture",
{
"type": "dir",
"label": "Capabilities",
"name": "basic"
},
{
"type": "dir",
"label": "Deploy",
"name": "ops"
},
{
"type": "dir",
"label": "Examples",
"name": "examples"
},
{
"type": "file",
"name": "api",
"label": "API Reference"
}
]
File: website/docs/en/daemon/architecture.md
Architecture
aiod is AIO Sandbox's core daemon process. It serves the sandbox tools API and hosts the MCP endpoint.
The shell, interpreters, browser, and desktop it drives are separate processes. aiod discovers them at runtime but does not start them.
The daemon process
It serves commands, files, terminals (PTY), code execution, and browser control over HTTP and WebSocket, and hosts POST /mcp.
The browser is the Chromium process listening for CDP at BROWSER_REMOTE_DEBUGGING_HOST:PORT (default 127.0.0.1:9222); any process may start it.
The desktop is provided by the computer-use worker at AIO_COMPUTER_USE_URL (default http://127.0.0.1:18100). aiod only forwards /v2/computer/* to it and never touches a display itself. The worker is separate because it needs a desktop session.
On Windows the difference is explicit: aiod can run as a Session 0 service, but computer-use must run in the interactive logon session (see Windows).
Dependencies and capabilities
aiod always starts. At boot and on each capability check, it probes the dependencies and reports the result in capabilities:
| Dependency | Probe | Routes it enables |
|---|---|---|
| Shell | bash on PATH; PowerShell on Windows |
/v2/commands, /v2/pty, and the v1 forms |
| Interpreters | python3, node on PATH, or AIO_JUPYTER_ENDPOINT |
/v2/code, /v1/code, /v1/jupyter, /v1/nodejs |
| Browser | CDP GET /json/version on the debugging port |
/v2/browser, /v1/browser |
| Desktop | the computer-use worker at AIO_COMPUTER_USE_URL |
/v2/computer |
A missing dependency makes only its own routes answer 503; nothing else changes, and the daemon still binds.
GET /v1/capabilities reports the current capability state. The result is cached for 5 s and refreshed in the background; ?refresh=true forces a probe. aiod doctor --json runs the same probes from the command line. The response shape is in Sandbox Info & Capabilities.
State lives in memory
Command sessions, PTY sessions, code sessions, retained output, file watchers, and editor undo history live in process memory. Nothing is written to disk.
A restart or upgrade drops all of this state, so clients must start over. Files already on disk are untouched.
Permission model
Two identities take part, and neither is a sandbox:
| Identity | What it decides |
|---|---|
The daemon account (AIO_DEFAULT_USER on Linux) |
The privileges of every file and command call |
The request user, on commands, terminals, and code |
The account the process runs as |
The request user, on files |
Who owns what the call creates |
A call can reach anything on the host that the daemon account can reach. aiod provides no per-request jail, chroot, or path allowlist; confining a deployment to a directory or tenant is the sandbox's job.
On Windows user is not implemented, and an explicit value answers 400.
Access to the API is one gate: AIO_API_KEY, checked on every non-public route (see Authentication).
Running the daemon
Add the binary to an image by copying one file:
FROM your/base-image
COPY aiod /usr/local/bin/aiod
CMD ["aiod", "start"]aiod start takes no required flags. It binds 0.0.0.0:18091, uses the daemon account's home directory as its working directory, and resolves a runtime directory on its own.
Run it as the container's command or under an existing supervisor such as systemd, s6, or a custom init.
A fuller start line, for an image with an unprivileged account and an API key:
# Every flag has an environment variable of the same meaning:
# AIO_HOST, AIO_PORT, AIO_API_KEY, AIO_DEFAULT_USER, AIO_API_SURFACE, AIO_RUNTIME_DIR.
aiod start \
--host 0.0.0.0 \
--port 18091 \
--api-key "$API_KEY" \
--default-user gem \
--api-surface full \
--runtime-dir /run/aiod--default-user sets the default account for requests, unless a request names another. --api-surface v2 serves only /v2/* and /mcp.
aiod -h lists the subcommands: start, doctor, version, and print-openapi. aiod start --help lists every flag, its variable, and its default.
aiod doctor probes the host without starting the daemon: bash, rg, tmux on PATH, a browser process, and its CDP port. It prints one line per probe.
Two checks matter for orchestration:
curl "$BASE_URL/health" # liveness: the process is up (no key needed)
curl "$BASE_URL/v1/capabilities" # readiness per capability: browser, code, desktop right nowGate restarts on /health. Gate browser, code, and desktop routing on the matching section of /v1/capabilities.
On Kubernetes, one container usually runs aiod, with computer-use added where a desktop exists. A pod that also runs Chromium must allow the seccomp profile Chromium requires (unconfined if nothing stricter is available) and provide a sufficiently large /dev/shm; 4 GiB is a safe default. This is a Chromium requirement, not an extra requirement from aiod.
aiod version prints aio-daemon <version>+<commit>. aiod print-openapi writes the full spec to stdout.
The daemon serves /openapi.json, /v1/openapi.json, and /v2/openapi.json, without a documentation UI. The v2 reference is the API Reference.
Platforms
aiod ships as a static binary for Linux (x86_64, arm64, riscv64) and Windows (x86_64). It needs no shared libraries and no installed runtime.
File: website/docs/en/daemon/basic/_meta.json
[
{
"type": "section-header",
"label": "Core API"
},
"sandbox",
{
"type": "file",
"name": "bash",
"label": "Bash (Commands)"
},
{
"type": "file",
"name": "shell",
"label": "Shell (PTY)"
},
{
"type": "file",
"name": "file",
"label": "File (FS)"
},
"error-handling",
{
"type": "section-header",
"label": "Browser"
},
{
"type": "file",
"name": "browser",
"label": "Browser API"
},
{
"type": "file",
"name": "cdp",
"label": "CDP Access"
},
{
"type": "section-header",
"label": "Desktop"
},
{
"type": "file",
"name": "display",
"label": "Computer Use"
},
{
"type": "section-header",
"label": "Code"
},
{
"type": "file",
"name": "code",
"label": "Code Interpreter"
},
{
"type": "file",
"name": "nodejs",
"label": "Node.js"
},
{
"type": "file",
"name": "jupyter",
"label": "Jupyter"
},
{
"type": "section-header",
"label": "Access & Ops"
},
"authentication",
"observability",
"windows"
]
File: website/docs/en/daemon/basic/authentication.md
Authentication
aiod supports one optional startup key.
When no key is configured, every route is open and aiod is fully exposed. Bind it to 127.0.0.1, or put a gateway with its own authentication in front.
Starting with an API key
The flag and the environment variable set the same key:
aiod start --api-key my-secret-key
# equivalently
AIO_API_KEY=my-secret-key aiod startSending the API key
Three equivalent forms:
curl "$BASE_URL/v1/sandbox" -H "Authorization: Bearer my-secret-key"
curl "$BASE_URL/v1/sandbox" -H "x-api-key: my-secret-key"
curl "$BASE_URL/v1/sandbox?api_key=my-secret-key"| Channel | When to use it | Notes |
|---|---|---|
Authorization: Bearer |
The default for an HTTP client | A standard bearer token |
x-api-key |
A client that already speaks this header | The prebuilt images' gateway also accepts X-AIO-API-Key |
?api_key= |
Only where headers are impossible | Deprecated; percent-decoded before the compare |
Every channel is compared in constant time, so neither the key's length nor how far a guess matched shows up in response timing.
A credential in a URL can leak into access logs, browser history, and the Referer of a later request.
Use the query form only when a request header is not possible, such as with a browser-native WebSocket or a VNC page:
{ws_base_url}/v1/shell/ws?session_id=SESSION_ID&api_key=my-secret-keyA WebSocket is authenticated before the upgrade. Without a valid key, the handshake answers 401 instead of opening and closing.
Except on the public routes below, every request without a valid key answers 401 with the standard envelope:
{
"success": false,
"message": "Unauthorized",
"data": null,
"hint": null
}Public routes
These skip the key check:
//health/v1/ping/v1/openapi.json/v2/openapi.json/internal/auth
Public paths match by prefix. For example, /health also covers /health/details, while / covers only itself and cannot exempt the whole API.
Public does not mean that a route has content: the daemon serves nothing at /, which is normally where a gateway puts its landing page.
Everything else requires the key when one is set, including /metrics — a scrape surface exposes route names and traffic shape, so there is no exemption for ops endpoints.
Behind a gateway
/internal/auth is the subrequest endpoint used by nginx auth_request. It reads the original request path from X-Original-URI and applies the same key check:
- It answers
204when the request is authorized, the target is public, or no key is configured. - It answers
401otherwise. - It never returns a body.
One auth_request can therefore gate everything the gateway fronts, including the VNC stream that never reaches aiod's middleware. When called directly, with no X-Original-URI, it checks the current request's own credentials.
CORS
CORS is open by design. aiod does not enforce an origin policy; the gateway in front of it does. Never expose the CDP port (default 9222) outside a trusted network — it has no authentication of its own.
Identity: execution vs. ownership
user on a request means two different things, depending on the API. AIO_DEFAULT_USER sets the same default:
On commands, PTY, and code,
userdecides which account runs the process; on files, it only decides who owns new objects.
On files, the daemon still reads and writes with its own privileges; user does not change the execution identity.
This only applies on Linux. Windows answers 400 for an explicit user, and 503 when AIO_DEFAULT_USER would apply — see Windows.
Related
- Error Handling — what
401looks like next to the other statuses - Observability — the key is redacted out of the request log
- File (FS) — ownership identity on the file plane
File: website/docs/en/daemon/basic/bash.mdx
import { ApiV1, ApiV2, Tab, Tabs, UseV2 } from '@theme';
Commands (Bash)
A command is a fresh process. Each call spawns one, and tears it down when it exits or times out. No shell is left running afterward.
A command session persists only the working directory, the run-as identity, any environment it was created with, and retained output between calls.
For a live shell that stays attached and open, use Terminals instead. The v1 and v2 routes side by side are in Migration from 1.x.
Requirements
Call GET /v1/capabilities first to see the command capabilities available on the host. The relevant fields are exec and bins:
curl "$BASE_URL/v1/capabilities"Relevant fields look like this:
{
"exec": {
"shell": true,
"bash": true,
"pty": true
},
"bins": {
"bash": "/bin/bash"
}
}Run a command
curl -X POST "$BASE_URL/v2/commands" \
-H "Content-Type: application/json" \
-d '{"command": "pwd && ls -la", "cwd": "/workspace", "timeout": 30}'Request body:
{
"command": "pwd && ls -la",
"cwd": "/workspace",
"timeout": 30
}The body takes these fields:
| Field | Values | Meaning |
|---|---|---|
command |
string, required | The script to run; with shell: "none", the program path |
args |
array | argv for shell: "none"; rejected in shell modes |
shell |
auto, bash, sh, powershell, cmd, none |
Which shell wraps the command; auto is the platform default |
cwd |
string | Working directory |
env |
object | Per-command variables; they override the session's |
user |
string | Account to run as; fixed on the session at first use; Linux only |
session |
string | Session to run in; absent means a generated one |
timeout |
seconds | How long the call waits; absent waits for the command to end |
hard_timeout |
seconds | When the process is killed; absent never kills it |
max_output_length |
characters | What this response carries, 50000 by default; 0 is unlimited |
mode |
sync (default), async |
async returns at once with a command_id |
curl -X POST "$BASE_URL/v1/bash/exec" \
-H "Content-Type: application/json" \
-d '{"command": "pwd && ls -la", "exec_dir": "/workspace", "timeout": 30}'Request body:
{
"command": "pwd && ls -la",
"exec_dir": "/workspace",
"timeout": 30
}The body takes these fields:
| Field | Values | Meaning |
|---|---|---|
command |
string, required | The script to run |
session_id |
string | Session to run in; created on first use |
exec_dir |
string | Working directory |
env |
object | Per-command variables |
user |
string | Account to run as; AIO_DEFAULT_USER, then aiod's own account, when absent |
async_mode |
boolean | Return at once with status: "running" |
timeout |
seconds | How long the call waits; absent waits for the command to end |
hard_timeout |
seconds | When the process is killed; absent never kills it |
max_output_length |
characters | What this response carries, 50000 by default; 0 is unlimited |
data carries the command and its result either way:
session_id,command_id— the session the command ran in and the command's own idstatus—running,completed, ortimed_outstdout,stderr— each stream on its own,nullon v1 while empty;outputisstdoutfollowed bystderrexit_code—nulluntil the command ends;-1after a kill or a hard timeoutoffset,stderr_offset— bytes of each stream produced so far
Past max_output_length the middle of each stream is replaced by \n... output truncated ...\n, while offset still counts everything the command produced. Only this response is shortened; a later read returns the stream whole.
Stream output as it is produced
Send Accept: application/x-ndjson to receive one JSON object per line instead of a final result:
curl -N -X POST "$BASE_URL/v2/commands" \
-H "Content-Type: application/json" \
-H "Accept: application/x-ndjson" \
-d '{"command": "echo one; echo two >&2; exit 3"}'curl -N -X POST "$BASE_URL/v1/bash/exec" \
-H "Content-Type: application/json" \
-H "Accept: application/x-ndjson" \
-d '{"command": "echo one; echo two >&2; exit 3"}'The frames for that command:
{"type":"started","session_id":"SESSION_ID","command_id":"COMMAND_ID","seq":1}
{"type":"output","stream":"stdout","data":"b25lCg==","offset":0,"seq":2}
{"type":"output","stream":"stderr","data":"dHdvCg==","offset":0,"seq":3}
{"type":"exit","status":"completed","exit_code":3,"stdout_offset":4,"stderr_offset":4,"seq":4}data— base64 of the raw bytesoffset— the chunk's position in its streamseq— the server's frame order
A gap frame carries lost_from and resume_offset when retention trimmed output past the reader's position; a ping frame follows 30 s without one. The stream ends with exit or error. Closing the connection kills a command started in sync mode, and leaves an async one running.
Routes
| Route | Purpose | Notes |
|---|---|---|
POST /v2/commands |
Run a command | mode: "async" returns a command_id at once |
GET /v2/commands/{id} |
Read a command's output | offset, stderr_offset, wait, wait_timeout |
POST /v2/commands/{id}/stdin |
Write to the command's stdin | input goes in verbatim; no newline is added |
POST /v2/commands/{id}/kill |
Signal a running command | SIGTERM by default, SIGKILL or SIGINT |
POST /v2/commands/sessions |
Create a session | id, cwd, env, user |
GET /v2/commands/sessions |
List the live sessions | Directory, command count, last use |
DELETE /v2/commands/sessions/{id} |
Close a session | Ends its commands, SIGTERM then SIGKILL |
| Route | Purpose | Notes |
|---|---|---|
POST /v1/bash/exec |
Run a command | async_mode: true returns at once |
POST /v1/bash/output |
Read a session's output | session_id required, command_id optional |
POST /v1/bash/write |
Write to the command's stdin | input goes in verbatim; no newline is added |
POST /v1/bash/kill |
Signal a running command | signal, and command_id when several run |
POST /v1/bash/sessions/create |
Create a session | session_id, exec_dir, user, snapshot_path |
GET /v1/bash/sessions |
List the live sessions | Directory, command count, last use |
POST /v1/bash/sessions/{id}/close |
Close a session | Ends its commands, SIGTERM then SIGKILL |
Reading is incremental, and the fields are the same on both:
offset,stderr_offset— where to start in each stream; the response's ownoffsetandstderr_offsetgo into the next readwait— long-poll: the read returns as soon as new output arrives, the command ends, orwait_timeout(30 s by default) elapses. Without it the read returns whatever is already therestdout_start_offset,stderr_start_offset— the earliest position still retained. Each stream keeps 10 MiB and is cut back to 5 MiB past thatstdout_gap,stderr_gap—truewhen the requested offset is older than what is retained, so the bytes in between are gonecommand—{command_id, command, status, exit_code}
A session addresses its last 100 commands; past that the oldest ids are dropped and answer 404. input is capped by the 2 MiB JSON body limit, and reaches stdin exactly as sent — a line-buffered program waits for the \n.
Timeouts and status
| Field | Absent | Present |
|---|---|---|
timeout |
The call waits for the command to end | The call returns there with status: "running"; the command runs on |
hard_timeout |
The command runs until it exits | The process is killed there and status becomes timed_out |
status is the lifecycle, and exit_code only means something once the command has ended:
status |
The command | exit_code |
|---|---|---|
running |
Still running, addressable by command_id |
null |
completed |
Exited on its own, or was killed | Its own code, -1 after a kill |
timed_out |
Killed at hard_timeout |
-1 |
Check status first, then exit_code. The wire contract also carries pending and killed, which no request produces.
Patterns for an agent
Most commands finish inside the call. A long one gets a timeout and a second call reads the rest; a program that never exits runs in the background and is killed at the end; an interactive one is fed through stdin.
| Scenario | Start | Then |
|---|---|---|
ls -la |
sync | Read the result from the same response |
npm install |
sync with timeout |
On running, read with wait until it completes |
npm run dev |
background | Read the startup log; kill it when done |
python3 -i |
background | Write a line, read both streams |
a script with input() |
background | Answer each prompt, ending with \n |
Sessions
A cd inside a command does not carry over to the next one: set the directory on the request, or once on a session.
- A command with no
sessiongets a generated one. It is listed like any other and counts against the limit. - Session
envis inherited by every command in it, and a per-commandenvwins. useris fixed the first time a session sees it; a different value later is a400.- Closing a session ends whatever it is running:
SIGTERM, thenSIGKILLa moment later. - On v1,
snapshot_pathnames an existing file that every bash command in the session sources throughBASH_ENV; other shells ignore it. - Up to 50 sessions at once, each idle-closed after 3600 s (
AIO_BASH_MAX_SESSIONS/AIO_BASH_SESSION_TIMEOUT_SECS). At the limit the least recently used idle session is closed to make room, and a create with every session busy is a400.
Run in the background
Suppose the agent has generated a static site under /workspace/site and wants to open it in the sandbox browser before handing it over. The server has to keep running while the pages are checked, so it cannot be a normal command that returns when it exits. A dev server, a watcher, or anything else that never exits on its own is started in the background, read as its output arrives, and killed when it is no longer needed:
Aio is the envelope-aware helper from Examples: it unwraps data and raises when success is false.
sb = Aio(BASE_URL)
# 1. A session fixes the directory and the environment for every command in it.
session = sb.post("/v2/commands/sessions", cwd="/workspace/site",
env={"PORT": "3000"})
# 2. Start the server in it; async returns as soon as the process is spawned.
started = sb.post("/v2/commands", command="python3 -u -m http.server $PORT",
session=session["session_id"], mode="async")
command_id = started["command_id"]
# 3. Read what it printed; wait long-polls until output arrives.
log = sb.get(f"/v2/commands/{command_id}", offset=0, stderr_offset=0,
wait=True, wait_timeout=5)
print(log["stdout"].strip(), "|", log["command"]["status"])
# Serving HTTP on :: port 3000 (http://[::]:3000/) ... | running
# 4. Stop the command, then drop the session.
sb.post(f"/v2/commands/{command_id}/kill", signal="SIGTERM")
sb.delete(f"/v2/commands/sessions/{session['session_id']}")const sb = new Aio(BASE_URL);
// 1. A session fixes the directory and the environment for every command in it.
const session = await sb.post("/v2/commands/sessions", {
cwd: "/workspace/site",
env: { PORT: "3000" },
});
// 2. Start the server in it; async returns as soon as the process is spawned.
const started = await sb.post("/v2/commands", {
command: "python3 -u -m http.server $PORT",
session: session.session_id,
mode: "async",
});
// 3. Read what it printed; wait long-polls until output arrives.
const log = await sb.get(
`/v2/commands/${started.command_id}?offset=0&wait=true&wait_timeout=5`,
);
console.log(log.stdout.trim(), "|", log.command.status);
// Serving HTTP on :: port 3000 (http://[::]:3000/) ... | running
// 4. Stop the command, then drop the session.
await sb.post(`/v2/commands/${started.command_id}/kill`, { signal: "SIGTERM" });
await sb.delete(`/v2/commands/sessions/${session.session_id}`);from agent_sandbox import Sandbox
client = Sandbox(base_url=BASE_URL)
# 1. A session fixes the working directory for every command in it.
session = client.bash.create_session(exec_dir="/workspace/site").data
# 2. Start the server in it; async_mode returns as soon as it is spawned.
client.bash.exec(
command="python3 -u -m http.server 3000",
session_id=session.session_id,
async_mode=True,
)
# 3. Read what it printed; wait long-polls until output arrives.
log = client.bash.output(
session_id=session.session_id,
offset=0,
stderr_offset=0,
wait=True,
wait_timeout=5,
).data
print(log.stdout.strip(), "|", log.command.status)
# Serving HTTP on :: port 3000 (http://[::]:3000/) ... | running
# 4. Stop the command, then close the session.
client.bash.kill(session_id=session.session_id, signal="SIGTERM")
client.bash.close_session(session.session_id)import { SandboxClient } from "@agent-infra/sandbox";
const client = new SandboxClient({ environment: BASE_URL });
// 1. A session fixes the working directory for every command in it.
const session = (
await client.bash.createSession({ exec_dir: "/workspace/site" })
).body.data!;
// 2. Start the server in it; async_mode returns as soon as it is spawned.
await client.bash.exec({
command: "python3 -u -m http.server 3000",
session_id: session.session_id,
async_mode: true,
});
// 3. Read what it printed; wait long-polls until output arrives.
const log = (
await client.bash.output({
session_id: session.session_id,
offset: 0,
stderr_offset: 0,
wait: true,
wait_timeout: 5,
})
).body.data!;
console.log(log.stdout!.trim(), "|", log.command?.status);
// Serving HTTP on :: port 3000 (http://[::]:3000/) ... | running
// 4. Stop the command, then close the session.
await client.bash.kill({ session_id: session.session_id, signal: "SIGTERM" });
await client.bash.closeSession(session.session_id);After the kill the command ends as completed, with exit_code: -1. Every signal reaches the whole process tree, not just the shell that was spawned; killing a command that has already finished is a 400. One thing to know about pipes: a program that block-buffers stdout when it is not a terminal shows nothing until it flushes or exits; Python is one, which is why the example runs it with -u.
Interactive programs
Suppose the agent wants to try expressions one at a time and keep the interpreter's state between them, the way a person works in a Python REPL, or it has to answer a script that stops and asks a question. A REPL or a script that asks questions runs in the background and is fed through stdin. Two facts about pipes decide the shape: a line-buffered program reads nothing until the \n arrives, and prompts often go to stderr, so read both streams.
sb = Aio(BASE_URL)
# 1. Start Python in interactive mode (-i: stdin is a pipe, not a terminal).
repl = sb.post("/v2/commands", command="python3 -i", mode="async")
command_id = repl["command_id"]
# 2. The banner and the first prompt arrive on stderr; read them in order.
banner = sb.get(f"/v2/commands/{command_id}", offset=0, stderr_offset=0,
wait=True, wait_timeout=5)
prompt = sb.get(f"/v2/commands/{command_id}", offset=banner["offset"],
stderr_offset=banner["stderr_offset"], wait=True,
wait_timeout=5)
print(repr(prompt["stderr"])) # '>>> '
# 3. Send a line, then read the result.
sb.post(f"/v2/commands/{command_id}/stdin", input="1 + 1\n")
out = sb.get(f"/v2/commands/{command_id}", offset=prompt["offset"],
stderr_offset=prompt["stderr_offset"], wait=True, wait_timeout=5)
print(repr(out["stdout"]), repr(out["stderr"])) # '2\n' '>>> '
# 4. Leave: the process ends with exit_code 0.
sb.post(f"/v2/commands/{command_id}/stdin", input="exit()\n")const sb = new Aio(BASE_URL);
// 1. Start Python in interactive mode (-i: stdin is a pipe, not a terminal).
const repl = await sb.post("/v2/commands", {
command: "python3 -i",
mode: "async",
});
const read = (offset: number, stderrOffset: number) =>
sb.get(
`/v2/commands/${repl.command_id}?offset=${offset}` +
`&stderr_offset=${stderrOffset}&wait=true&wait_timeout=5`,
);
// 2. The banner and the first prompt arrive on stderr; read them in order.
const banner = await read(0, 0);
const prompt = await read(banner.offset, banner.stderr_offset);
console.log(JSON.stringify(prompt.stderr));
// ">>> "
// 3. Send a line, then read the result.
await sb.post(`/v2/commands/${repl.command_id}/stdin`, { input: "1 + 1\n" });
const out = await read(prompt.offset, prompt.stderr_offset);
console.log(JSON.stringify(out.stdout), JSON.stringify(out.stderr));
// "2\n" ">>> "
// 4. Leave: the process ends with exit_code 0.
await sb.post(`/v2/commands/${repl.command_id}/stdin`, { input: "exit()\n" });# 1. Start Python in interactive mode (-i: stdin is a pipe, not a terminal).
started = client.bash.exec(command="python3 -i", async_mode=True).data
session_id = started.session_id
# 2. The banner and the first prompt arrive on stderr; read them in order.
banner = client.bash.output(
session_id=session_id, offset=0, stderr_offset=0, wait=True, wait_timeout=5
).data
prompt = client.bash.output(
session_id=session_id,
offset=banner.offset,
stderr_offset=banner.stderr_offset,
wait=True,
wait_timeout=5,
).data
print(repr(prompt.stderr)) # '>>> '
# 3. Send a line, then read the result.
client.bash.write(session_id=session_id, input="1 + 1\n")
out = client.bash.output(
session_id=session_id,
offset=prompt.offset,
stderr_offset=prompt.stderr_offset,
wait=True,
wait_timeout=5,
).data
print(repr(out.stdout), repr(out.stderr)) # '2\n' '>>> '
# 4. Leave: the process ends with exit_code 0.
client.bash.write(session_id=session_id, input="exit()\n")// 1. Start Python in interactive mode (-i: stdin is a pipe, not a terminal).
const started = (
await client.bash.exec({ command: "python3 -i", async_mode: true })
).body.data!;
const session_id = started.session_id;
// 2. The banner and the first prompt arrive on stderr; read them in order.
const banner = (
await client.bash.output({
session_id,
offset: 0,
stderr_offset: 0,
wait: true,
wait_timeout: 5,
})
).body.data!;
const prompt = (
await client.bash.output({
session_id,
offset: banner.offset,
stderr_offset: banner.stderr_offset,
wait: true,
wait_timeout: 5,
})
).body.data!;
console.log(JSON.stringify(prompt.stderr));
// ">>> "
// 3. Send a line, then read the result.
await client.bash.write({ session_id, input: "1 + 1\n" });
const out = (
await client.bash.output({
session_id,
offset: prompt.offset,
stderr_offset: prompt.stderr_offset,
wait: true,
wait_timeout: 5,
})
).body.data!;
console.log(JSON.stringify(out.stdout), JSON.stringify(out.stderr));
// "2\n" ">>> "
// 4. Leave: the process ends with exit_code 0.
await client.bash.write({ session_id, input: "exit()\n" });A script that calls input("Name: ") is the same loop with one prompt per turn: the read returns stdout: "Name: " while the process blocks, the answer goes in as "Alice\n", and the next read returns Hello Alice. cat and other line-buffered programs want the trailing \n too.
Run as a user
user selects the execution identity: the process actually runs as that account. Files use a different model; see File.
Add user to the /v2/commands request body:
{
"command": "id -un",
"user": "alice"
}v1 also accepts user in the request body at POST /v1/bash/exec:
{
"command": "id -un",
"user": "alice"
}- Omitted: the default identity. With
AIO_DEFAULT_USERunset, that is aiod's own account. Setting it changes the default; any other account still requires an explicituser. - The account is resolved before the process is spawned. A missing one answers
400 no such user: alice, and a non-root aiod answers400 cannot run as root: aiod is running as uid 501 and only root can change identity. It never runs silently under another identity. - An account that maps to uid 0 is refused, even when aiod is root.
- A failure that comes from
AIO_DEFAULT_USERrather than the request is a503: the daemon is misconfigured, the call is not. - Linux only. On Windows, an explicit
useris a400, and a configuredAIO_DEFAULT_USERis a503.
Shells
auto resolves AIO_BASH_BIN first, then /bin/bash, /usr/bin/bash, /bin/sh, /usr/bin/sh on Linux and macOS, and pwsh.exe, powershell.exe on Windows. A named selector never falls back: sh on Windows, cmd off Windows, and a shell that is not installed all answer 503 naming what was missing.
shell: "none" runs a program directly — command is the path, args is its argv, and nothing is parsed or expanded. args in any other mode is a 400.
On Windows, shell: "cmd" runs a batch file under cmd.exe /d /c (UTF-8), and shell: "bash" resolves Git Bash: AIO_BASH_BIN, a non-WSL bash on PATH, or Git's install directories. Exit codes, kill semantics, and stdin on Windows are in Windows.
Error handling
HTTP success only means the request was accepted; the command's own outcome is status and then exit_code. The failures that belong to this plane:
| Status | Meaning |
|---|---|
400 |
Killing or writing to a command that is not running; args without shell: "none"; a user the daemon cannot switch to; a session that is already busy at the limit |
404 |
An unknown command_id or session_id, or one that retention has dropped |
422 |
A malformed body, with an errors list naming the field |
503 |
No shell for the selected shell, or an AIO_DEFAULT_USER that cannot be used |
413 is the shared 2 MiB JSON body limit; large payloads belong in a file. See Error Handling.
File: website/docs/en/daemon/basic/browser.mdx
import { ApiV1, ApiV2 } from '@theme';
Browser API
aiod attaches to an existing Chromium; it does not launch the browser. Two environment variables decide where it attaches:
| Variable | Default | Meaning |
|---|---|---|
BROWSER_REMOTE_DEBUGGING_HOST |
127.0.0.1 |
Host carrying Chromium's debugging port |
BROWSER_REMOTE_DEBUGGING_PORT |
9222 |
Chromium's debugging port |
Use a CDP client such as Playwright or Puppeteer, or call the REST API directly. /v1/browser/* and /v2/browser/* use the same request bodies. The exception is desktop input: v1 uses POST /v1/browser/actions, while v2 uses /v2/computer. See Computer Use.
What it needs
GET /v2/sandbox reports the browser capability in capabilities.browser:
GET /v1/capabilities reports it in browser.status:
| Status | When | What still answers |
|---|---|---|
ready |
CDP answers on the debugging port | Every route |
degraded |
A browser executable was found, CDP is unreachable | info, network/requests |
absent |
Neither is there | info, network/requests |
Below ready, the other tools are unavailable. See Sandbox.
CDP
Get the CDP URL
aiod asks the Chromium at BROWSER_REMOTE_DEBUGGING_HOST:PORT for /json/version, takes its webSocketDebuggerUrl, and rewrites it from the request headers:
| Part | Taken from |
|---|---|
| Host | X-Forwarded-Host, or Host when absent |
| Scheme | wss when X-Forwarded-Proto is https, otherwise ws |
| Path prefix | X-Forwarded-Prefix, empty when absent |
# BASE_URL=https://sandbox.example.com
curl "$BASE_URL/v2/browser/info"# BASE_URL=https://sandbox.example.com
curl "$BASE_URL/v1/browser/info"{
"data": {
"cdp_url": "wss://sandbox.example.com/cdp/devtools/browser/46164812-5f92-4ec5-891a-8c138aeb93a4",
"cdp_ui_url": null
}
}When Chromium does not answer, cdp_url still comes back in the same shape as a placeholder; call again once it recovers.
Playwright and Puppeteer connect to cdp_url directly; see Browser (CDP). Reverse proxies, path prefixes, and key parameters are covered on CDP Access.
Send a raw CDP command
| Route | Purpose | Notes |
|---|---|---|
POST /cdp |
Send one raw CDP command | The result lands in data |
Use it for what REST does not cover. For a sequence of commands, connect over cdp_url instead.
REST tools
For actions that don't need a full CDP client, aiod serves a small REST set. Every route returns the standard {success, message, data} envelope; full schemas are in the API Reference.
The routes sit under /v2/browser:
The routes sit under /v1/browser:
navigate, evaluate, snapshot, click, fill, upload, and cdp accept tab_id and use the first tab without it. screenshot does not accept tab_id and always captures the first tab.
Navigate and evaluate
| Route | Purpose | Notes |
|---|---|---|
POST /navigate |
Load a URL, or move through history | Takes url or history; url wins if both arrive |
POST /evaluate |
Run JavaScript in the page's main world | Returns the value; await_promise waits for a Promise |
{
"url": "https://example.com",
"wait_until": "load"
}wait_untilisload,domcontentloaded,networkidle, orcommit; the default isload.timeoutdefaults to 30 seconds; a timeout answers503, and the tab remains usable.historyacceptsback,forward, orreload.evaluateputs the result indata.valueand JavaScript exceptions indata.exception; the HTTP status stays200.
Take a screenshot
| Route | Purpose | Notes |
|---|---|---|
GET /screenshot |
Capture the current page | The response body is the image |
format is png (default), jpeg, or jpg; quality is 0–100 for jpeg and defaults to 85. full_page=true captures the whole scrollable page. PNG responses carry x-image-width and x-image-height.
Page snapshots
| Route | Purpose | Notes |
|---|---|---|
POST /snapshot |
Return the page structure | interactive_only keeps actionable elements |
POST /click |
Click an element | By ref or CSS selector |
POST /fill |
Fill an input | By ref or CSS selector |
POST /upload |
Upload a file | By ref or CSS selector |
A ref comes from snapshot. Take a new snapshot after each navigation; refs from the old document are no longer valid.
Tabs
| Route | Purpose | Notes |
|---|---|---|
GET /tabs |
List tabs | Each entry has id, title, url, type |
POST /tabs |
Open a tab | url is optional and defaults to about:blank |
POST /tabs/{tab_id}/activate |
Bring a tab to the front | Moves it to the head of the list; an unknown id is a 404 |
DELETE /tabs/{tab_id} |
Close a tab | An unknown id is a 404 |
A new tab goes to the head of the list and becomes the default target.
Cookies
| Route | Purpose | Notes |
|---|---|---|
POST /cookies |
Set cookies | Body is cookies, a list; the reply counts them |
GET /cookies |
Read them back | Narrows by url or domain |
DELETE /cookies |
Delete one, or all of them | name plus url or domain, or all=true |
The shape is CDP's own. A cookie needs a name and either a url or a domain with a path; the other fields are optional.
The request log
| Route | Purpose | Notes |
|---|---|---|
GET /network/requests |
Read-only log of page requests | limit caps the result, clear=true empties the buffer after reading |
Collection starts on the first call and keeps the latest 300 requests. Interception and header rewriting are not supported.
Window size
| Route | Purpose | Notes |
|---|---|---|
POST /config |
Resize the browser window over CDP | resolution must be a supported size; other values answer 422 |
Omitting resolution leaves the window unchanged.
Error semantics
| Status | Meaning |
|---|---|
400 |
A required parameter is missing or the body is not JSON |
404 |
An element or tab does not exist |
422 |
A parameter value is unsupported |
503 |
CDP is unreachable, navigation timed out, or CDP execution failed |
JavaScript exceptions are not HTTP errors: evaluate answers 200 with data.exception.
Desktop actions
Real mouse and keyboard events run on the computer-use worker rather than on the page, and answer 503 when it is not running.
Call POST /v2/computer/actions; see Computer Use.
Call POST /v1/browser/actions; see Computer Use.
MCP tools
An MCP-capable agent calls browser tools through /mcp. The daemon itself carries browser_get_info, plus browser_gui_screenshot and browser_gui_execute_action when computer-use is available.
The AIO image runs @agent-infra/mcp-server-browser on loopback and registers it as an upstream of /mcp, which is where the image's page-level browser_* navigation tools come from. See MCP for how registration works.
CDP, REST tools, or MCP
All three operate on the same Chromium:
| Way in | Gives | Costs |
|---|---|---|
| CDP client | Full page automation | A CDP client and WebSocket |
| REST API | Common browser operations | One operation per request |
| MCP | Browser operations as tools | An MCP client |
Use CDP for full page automation, REST for common operations, and MCP when the agent already speaks MCP.
Human in the loop
A person can watch or take over the Chromium the agent is driving. Both viewers attach to the same browser environment:
| Viewer | Path | Shows |
|---|---|---|
| noVNC | /vnc/index.html?autoconnect=true |
The whole desktop: Chromium, dialogs, other windows |
| DevTools | /browser-ui, /cdp/devtools/* |
The current Chromium page |
noVNC attaches to the desktop, not to one browser window, so the file dialog, the address bar and other application windows are all reachable, and the desktop survives a Chromium restart. The noVNC page embeds in an iframe.
Person and agent share one browser state: after a login completed in noVNC, the agent's next snapshot reads the logged-in page.
The daemon serves the API only; the image's gateway serves the noVNC and DevTools pages. Deployment and access are described on Computer Use.