数据截至 (上游 commit b71439b0797e)
总览:E2B 是什么、两平面全景与阅读地图
30 秒导读: E2B 是一套开源基础设施,让 AI(或你)生成的、不可信的代码在云端安全隔离的沙箱里运行。你几乎不直接碰基础设施——而是用它的 SDK(JS / Python)当客户端:
Sandbox.create()起一个沙箱,然后sandbox.commands.run(...)、sandbox.files.read(...)就像操作一台自己的云端 Linux。核心结论只有一句:SDK 内部分成两条平面,一条 REST「控制面」找 E2B 编排器管沙箱的生老病死,一条直连沙箱内envd守护进程的「数据面」管沙箱里面的活(命令、文件);两条平面的协议全部由spec/目录代码生成,所以 JS、Python、CLI 三份实现长得一模一样。本篇是这套讲解的门厅,只做导航与全景,实现细节留给各章。
1. 这是什么(零基础也能懂)
一句话定义: E2B 是"给 AI agent 的云端一次性电脑"——把一段来历不明的代码放进一个开箱即焚的隔离沙箱里跑,跑完可以随手扔掉,炸了也伤不到你。
官方的自我定位很直白(README.md:19-20,"What is E2B"):
"an open-source infrastructure that allows you to run AI-generated code in secure isolated sandboxes in the cloud."
而 SDK 的角色,packages/js-sdk/package.json 的 description 一句点破:"E2B SDK that give agents cloud environments"——SDK 就是递给 agent 一台云环境的那只手。
解决什么问题 / 给谁用
设想你在做一个 AI 编码助手:模型吐出一段 Python,你总不能直接在自己服务器上 exec 它——它可能 rm -rf、可能装恶意包、可能死循环吃满 CPU。你需要一个用完即弃、跑飞了也不心疼的地方去执行它。E2B 就是这个地方。
典型用户:
- AI 编码 / code-interpreter 类产品——把 LLM 生成的代码丢进沙箱执行,取回 stdout / 图表 / 文件。
- agent 框架——给 agent 一双"手":跑命令、读写文件、开网页服务、做 git 操作。
- 数据 / 自动化流水线——需要一个隔离、可复现、可秒起秒销的 Linux 执行环境。
它能做什么
沙箱内你能做的事(packages/js-sdk/src/sandbox/index.ts:54-65 类注释):
- 访问一台 Linux OS
- 增删查文件与目录
- 运行命令(前台 / 后台 / 流式)
- 跑 git 操作
- 跑隔离的代码
- 访问互联网(可按域名放行/拦截/改写)
用起来什么样
一个最小的真实示例(README.md:47-53)——三行就摸到全部直觉,注意 create() 走的是控制面,commands.run() 走的是数据面(这条分界线是全篇主线):
import Sandbox from 'e2b'
const sandbox = await Sandbox.create() // 控制面:请编排器造一台沙箱
const result = await sandbox.commands.run('echo "Hello from E2B!"') // 数据面:直连沙箱内 envd 跑命令
console.log(result.stdout) // Hello from E2B!
Python 版几乎一模一样(README.md:57-62),并且用 with 语法自动回收沙箱:
from e2b import Sandbox
with Sandbox.create() as sandbox:
result = sandbox.commands.run('echo "Hello from E2B!"')
print(result.stdout) # Hello from E2B!
sandbox 这个对象上挂着四个模块:files(文件)、commands(命令)、pty(伪终端)、git(git 操作)。你对沙箱做的一切,都是调它们(packages/js-sdk/src/sandbox/index.ts:84-96)。
一句话直觉 / 类比
把 E2B 当成「云函数版的 Docker,但带一个可以远程遥控内部的机器人」:控制面像
docker run / stop / commit(管容器本身),数据面像docker exec(钻进容器里干活)——只不过这两件事走的是两条完全不同的网络链路和协议。create()像是"租一台机器并拿到它的门牌号",之后所有操作都是通过网络遥控那台机器。
2. 顶层全景(它大概怎么转)
理解 E2B SDK 的唯一关键,是看懂它把工作切成了两条独立的网络平面。搞混这两条,后面每一章都会读得别扭;分清了,一切豁然开朗。
两条平面
| 平面 | 连到哪 | 用什么协议 | 管什么 | 对应章 |
|---|---|---|---|---|
| 控制面(control plane) | 编排器 https://api.<domain> | REST(OpenAPI 生成的 client) | 沙箱生命周期与编排:create / kill / pause / list / 模板构建 | 01 · 05 |
| 数据面(data plane) | 沙箱内部的 envd 守护进程,直连 49983-<sandboxId>.<domain> | Connect-RPC(命令/PTY/文件元数据)+ HTTP(文件字节流) | 沙箱里面的实际操作:跑命令、读写文件、开 PTY | 02 · 03 · 04 |
一句话记法:控制面管"这台沙箱的生死",数据面管"沙箱里发生什么"。 控制面对着 E2B 的编排器说话;数据面绕过编排器,直接敲沙箱里那个叫 envd("env daemon",沙箱内的执行代理守护进程)的东西。
全景图
怎么读这张图:从上到下是一次典型使用的时间顺序;左边是控制面(对编排器),右边是数据面(对沙箱内的 envd)。
你的代码 / AI agent
│
await Sandbox.create()
│
┌─────────────────┴──────────────────┐
│ ① 控制面:POST api.<domain>/sandboxes │ ← REST,向编排器要一台沙箱
│ 拿回 sandboxId + envdVersion + token │
└─────────────────┬──────────────────┘
│ ② 构造 Sandbox 对象
│ 装配四个数据面模块
▼
┌───────────────────────┐
│ Sandbox 实例 │
│ .files .commands │
│ .pty .git │
└───────────┬───────────┘
│ ③ 数据面:直连沙箱里的 envd
▼
49983-<sandboxId>.<domain> (envd 守护进程)
├─ Connect-RPC ── 跑命令 / PTY / 文件元数据
└─ HTTP /files ── 上传下载文件字节
部件一句话职责
| 部件 | 干什么 | 在哪 |
|---|---|---|
Sandbox(类) | 门面对象:静态 create/connect 起沙箱,实例上 挂四个数据面模块 | packages/js-sdk/src/sandbox/index.ts:76 |
SandboxApi(父类) | 控制面的全部 REST 调用(createSandbox / kill / pause / list…) | packages/js-sdk/src/sandbox/sandboxApi.ts |
ConnectionConfig | 解析域名 / API key / debug,推导所有 URL(api 地址与沙箱主机名) | packages/js-sdk/src/connectionConfig.ts |
envd(沙箱内守护进程) | 数据面的服务端:在沙箱里真正执行命令 / 文件 / PTY | 沙箱镜像内(spec 定义其协议,见 06) |
spec/ | 单一事实源:OpenAPI + protobuf,代码生成出各 SDK 的 client | spec/openapi.yml、spec/envd/ |
主线走一遍(高层,不进代码)
跟着一次 Sandbox.create(),把两条平面串起来:
- 控制面要一台沙箱。
Sandbox.create()走到SandboxApi.createSandbox(),向编排器POST /sandboxes,请求体带上模板 ID、超时、网络策略等(packages/js-sdk/src/sandbox/sandboxApi.ts:1689)。 - 拿回身份与凭证。 响应给出
sandboxId、envdVersion(沙箱里 envd 的版本,用来做能力协商)、以及可选的envdAccessToken/trafficAccessToken(数据面鉴权用)——见sandboxApi.ts:1706-1712。 - 构造 Sandbox 对象、装配数据面。
new this({...sandboxInfo, ...config})进到Sandbox构造函数(packages/js-sdk/src/sandbox/index.ts:131-229):它用ConnectionConfig推导出 envd 的直连 URL,建好 RPC transport,然后new Filesystem/Commands/Pty/Git(...)把四个数据面模块挂上去(index.ts:215-226)。 - 此后全在数据面。 你调
sandbox.commands.run(...)、sandbox.files.write(...),SDK 都是直连沙箱主机49983-<sandboxId>.<domain>上的 envd,编排器不再介入。超时到点或你显式kill(),才又回到控制面DELETE /sandboxes/{id},microVM 被销毁。
这条主线里,
create()是唯一横跨两条平面的地方:它先在控制面拿到沙箱身份,再用这身份布线到数据面。看懂这一跳,就看懂了整个 SDK 的骨架。记住这条分界线:凡是关于「沙箱这台机器本身」的动作走控制面 REST;凡是「机器里面」的动作走数据面 RPC/HTTP。
两个关键常量与 URL 推导(记住即可,细节在 02 章)
- 数据面端口写死在类里:
envdPort = 49983、mcpPort = 50005(packages/js-sdk/src/sandbox/index.ts:113-114)。 - 沙箱 主机名的推导规则是一条简单模板
`${port}-${sandboxId}.${sandboxDomain}`(packages/js-sdk/src/connectionConfig.ts:535,getHost)——所以数据面 URL 长成49983-<sandboxId>.<domain>。 - 控制面 API 地址则是
`https://api.${this.domain}`(packages/js-sdk/src/connectionConfig.ts:418-421)。
这三行就是"两条平面各连去哪"的全部秘密。02 章会讲透 URL 推导、sandbox.<domain> 的稳定主机优化、以及签名鉴权。
3. 仓库全景(monorepo 布局)
E2B 是一个 pnpm monorepo。工作区定义极简(pnpm-workspace.yaml):
packages:
- packages/*
- spec
根 package.json 是私有聚合包("name": "e2b", "private": true),只放跨包脚本(version / publish / test / lint)和 pnpm 版本锁(packageManager: [email protected])——它本身不发布,真正的产物在各子包里。
顶层代码地图
| 目录 | 是什么 | 备注 |
|---|---|---|
packages/js-sdk/ | JavaScript / TypeScript SDK(npm 包 e2b,本讲解主要剖析对象) | 版本 2.31.0,description: "E2B SDK that give agents cloud environments" |
packages/python-sdk/ | Python SDK(PyPI 包 e2b),同 description | 与 JS 保持功能等价,含 sync + async 两套实现 |
packages/cli/ | 命令行工具:"CLI for managing e2b sandbox templates" | 主要面向模板构建,见 05 章 |
packages/connect-python/ | Go 实现的 connect(Python 侧数据面 RPC 桥接相关) | 含 go.mod / cmd/,跨语言一致性的一环 |
spec/ | 单一事实源:OpenAPI(openapi.yml)+ envd protobuf(spec/envd/)+ MCP schema | 代码生成 client 的输入,见 06 章 |
为什么
spec/单独列进 workspace?因为它是所有 SDK 的共同上游:改一次 spec,make codegen就能把 JS / Python 的 client 一起重生成。这保证了多语言 SDK 行为一致——这正是 06 章的主题。
JS SDK 内部结构速览
packages/js-sdk/src/ 的分层与本讲解的章节几乎一一对应:
| 源码位置 | 职责 | 对应章 |
|---|---|---|
sandbox/index.ts | Sandbox 门面类(create/connect/构造装配) | 本篇 + 01/02 |
sandbox/sandboxApi.ts | 控制面 REST 全家桶 | 01 |
connectionConfig.ts | 配置解析 + URL 推导 | 02 |
sandbox/commands/ | 命令执行 + PTY + CommandHandle | 03 |
sandbox/filesystem/ | 文件读写 + 目录 watch | 04 |
sandbox/git/ | git 操作(基于 commands 之上的薄封装,index.ts:226) | 03 |
template/ | 模板 DSL 与 构建 | 05 |
api/、envd/ | 由 spec 生成的 client(REST client + envd RPC/HTTP) | 06 |
packages/js-sdk/src/index.ts 是这些的公共出口:它 re-export Sandbox(默认导出)、四个模块的类型、以及错误类型与 template 全套(packages/js-sdk/src/index.ts:142-182)。想知道"用户能拿到哪些 API",看这个 barrel 文件即可。
4. 阅读地图(各章顺序与精华)
建议按编号顺序读——它就是由浅入深、先控制面再数据面排的。若你带着具体问题来,用下表直接跳章。
| 顺序 | 章 | 一句话讲什么 | 精华(读完带走什么) |
|---|---|---|---|
| 0 | index.md(本篇) | E2B 是什么、两平面全景、仓库地图 | 建立"控制面 vs 数据面"的心智模型 |
| 1 | 01-control-plane.md | 沙箱生命周期与编排器 REST | create/kill/pause/list 如何映射到 POST /sandboxes 等;快照两档语义、分页器、超时、网络策略 |
| 2 | 02-data-plane-connection.md | 连到沙箱里的 envd | URL 推导(49983-<id>.<domain>)、签 名/token 鉴权、握手 vs 空闲双超时、流的生命周期 |
| 3 | 03-commands-pty.md | 命令执行与 PTY | 流式 Connect-RPC、CommandHandle 把流拆成 stdout/stderr/exit、背压与取消、伪终端 |
| 4 | 04-filesystem.md | 文件系统 | 为什么元数据走 RPC、字节走 HTTP /files;目录 watch 事件流;签名直传 URL |
| 5 | 05-templates.md | 模板构建 | Dockerfile 式 DSL → 可秒起镜像;按层内容 hash 去重上传;CLI 的角色 |
| 6 | 06-spec-and-multilang.md | 一份 spec 多个 SDK | OpenAPI/protobuf 代码生成如何保证 JS/Python 一致 |
推荐路线:
- 只想会用 SDK → 本篇 §1–§2 + 03 + 04 足矣。
- 想理解生命周期 / 自建后端 → 本篇 + 01 + 02。
- 想改 SDK / 加语言 → 全读,重点 06(代码生成是改动的起点)。
给 AI agent 的选章提示: 任务涉及「造/停/恢复/快照沙箱」→ 读 01;「连不上沙箱 / URL / 鉴权 / 超时」→ 读 02;「跑命令 / 流输出 / 终端」→ 读 03;「读写文件 / 监听」→ 读 04;「自定义镜像」→ 读 05;「改协议 / 加字段 / 跨语言」→ 读 06。
5. 巧妙之处(值得带走的设计)
这一节把 E2B SDK 里几个「不显然但很聪明」的决定提炼出来,作为读各章前的路标;细节在对应章节展开。
- ① 两平面分离 = 编排器不做数据代理。 命令输出、文件字节这些高频大流量不经过编排器,而是客户端直连沙箱 host。编排器只做「调度 + 生命周期」这种低频控制,天然可横向扩展。分界点就在
Sandbox构造函数:控制面结果一到手,立刻用它建数据面 transport(sandbox/index.ts:168-200)。详见 02。 - ② URL 靠约定推导,不靠查询。 连数据面不需要再问编排器要地址——
getHost()直接把端口和 sandboxID 拼成${port}-${sandboxId}.${domain}(connectionConfig.ts:530)。任意端口都能这样对外暴露(sandbox.getHost(3000)就能拿到沙箱里 3000 端口的公网地址),零额外往返。详见 02。 - ③ 一条 RPC 流承载「启动 + 输出 + 退出码」。
Process.Start是 server-streaming:第一帧是「进程已启动、这是 pid」,中间帧是 stdout/stderr,末帧是 exit code。SDK 只 await 到第一帧就返回句柄,既拿到 pid 又保留后续流——一个连接干完握手和数据两件事。详见 03。 - ④ 把「连接断了」翻译成「沙箱是不是死了」。 长流中途断开,不同 JS 运行时(Node/Bun/Deno)报的错文案都不一样。SDK 匹配这些运行时特有文案识别出「连接被拉断」后主动探一次沙箱健康(
envd/rpc.ts的handleRpcErrorWithHealthCheck),据此把裸网络错翻成语义明确的TimeoutError——告诉你是沙箱寿终正寝,而非临时抖动。详见 02。 - ⑤ 文件字节不塞进 RPC。 文件系统的元数据操作(stat/mkdir/list/move/remove/watch)走 protobuf RPC,但读写字节走独立的
/filesHTTP 端点。大文件因此能流式上传下载、能 gzip、能靠预签名 URL 直传,不必被 protobuf 消息边界束缚。详见 04。 - ⑥ 协议单一事实源,三份 SDK 零漂移。 JS、Python、CLI 不各写各的客户端——控制面从
spec/openapi.yml、数据面从spec/envd/**/*.proto代码生成。改协议只改spec/,make codegen一跑,三端同步。跨语言行为一致是「生成」保证的,不是「纪律」保证的。详见 06。 - ⑦ 快照的两档语义被类型系统钉死。
pause({keepMemory})分「全内存快照」和「仅文件系统快照」;后者 resume 时是冷启动,因此不能配autoResume。SDK 把这个约束做成判别联合类型 + 运行时再校验,非法组合在编译期就报错。详见 01。
6. 代码地图(导航索引)
一张表跳进源码。优先用符号名 grep 定位(行号会随上游漂移,符号名通常还在);file:line as-of commit 2869feb。
| 主题 | 文件路径 | 符号名 |
|---|---|---|
| 门面类 / 四个数据面模块挂载 | packages/js-sdk/src/sandbox/index.ts:76 | class Sandbox(files/commands/pty/git,:82-94) |
| 构造函数:URL 推导 + transport + 装配模块 | packages/js-sdk/src/sandbox/index.ts:131-229 | Sandbox constructor |
| 起沙箱(横跨两平面的入口) | packages/js-sdk/src/sandbox/index.ts:288-348 | Sandbox.create |
| 重连已有沙箱 | packages/js-sdk/src/sandbox/index.ts:370-395 | Sandbox.connect |
| 数据面端口常量 | packages/js-sdk/src/sandbox/index.ts:113-114 | envdPort = 49983、mcpPort = 50005 |
| 控制面 create(REST) | packages/js-sdk/src/sandbox/sandboxApi.ts:1612 | createSandbox(POST /sandboxes @ :1180,返回体 @ :1197-1203) |
| 沙箱主机名推导 | packages/js-sdk/src/connectionConfig.ts:530 | getHost(模板 ${port}-${sandboxId}.${sandboxDomain} @ :456) |
| 控制面 API 地址推导 | packages/js-sdk/src/connectionConfig.ts:418-421 | ConnectionConfig 的 apiUrl |
| 数据面 RPC transport 装配 | packages/js-sdk/src/sandbox/index.ts:168-200 | createConnectTransport(在构造函数内) |
| 公共 API 出口(barrel) | packages/js-sdk/src/index.ts:157-182 | export { Sandbox } / export default Sandbox / export * from './template' |
| 控制面协议源 | spec/openapi.yml | OpenAPI paths / securitySchemes |
| 数据面协议源 | spec/envd/process/process.proto、spec/envd/filesystem/filesystem.proto | service Process、service Filesystem |
| 代码生成入口 | Makefile、packages/js-sdk/package.json | codegen、generate |
| monorepo 工作区 | pnpm-workspace.yaml | packages/* + spec |
| SDK 定位(description) | packages/js-sdk/package.json | "E2B SDK that give agents cloud environments" |
本篇到此为止只做导航与全景:两条平面、一条主线、仓库地图与阅读顺序。每条平面的真实实现——REST 的每个端点、URL 推导与鉴权细节、流式 RPC 的背压、文件双通道、模板 DSL、代码生成——分别在 01–06 各章展开。从 01-control-plane.md 开始下钻。