本文是 Agent SDK 中沙箱 Sandbox(沙箱)模块的参考。您可以在 IGA Pages 服务端创建或复用受控运行环境,运行代码和命令、读写文件、访问网页,并管理实例生命周期;也可以将沙箱传给 getTools(),让 Agent 调用这些能力(详见工具 Tools)。本文先给出一段可直接运行的最短示例,随后说明沙箱的获取、能力调用、实例管理与错误处理。
Agent SDK 通过核心包 @iga-pages/agent 的 ./sandbox 子路径提供面向 IGA Pages 的沙箱能力,负责凭据换取、实例创建与生命周期管理。您在业务代码中通过 @iga-pages/agent/sandbox 引入对应入口,即可获得经 SDK 封装后的沙箱能力,而无需直接面对底层运行时细节。
沙箱仅用于 IGA Pages 服务端,要求 Node.js 20 或更高版本。实例中的文件、进程和浏览器状态不会永久保存,需要长期保留的数据应写入 Blob 等持久存储。
沙箱运行在 IGA Pages 服务端运行时中,使用当前应用的运行时身份访问对应项目中的资源;项目身份在运行时自动确定,您无需显式传入。
在获取并使用沙箱实例之前,请确认您已完成以下准备:
@iga-pages/agent,并可通过子路径 @iga-pages/agent/sandbox 引入沙箱入口函数。安装步骤请参见安装与环境准备。在已关联项目的 IGA Pages 开发环境或线上服务端使用。安装 SDK:
npm install @iga-pages/agent
在已关联项目的 IGA Pages 开发环境或线上服务端中,创建沙箱并执行代码:
import { createSandbox } from "@iga-pages/agent/sandbox"; export async function runExample() { const sandbox = await createSandbox(); try { const result = await sandbox.code.executeCode({ language: "python", code: "print(1 + 1)", }); if (!result.ok) throw new Error("代码执行请求失败"); const execution = result.body.data; if ( result.body.success === false || !execution || execution.status !== "completed" || (execution.exit_code !== undefined && execution.exit_code !== 0) || (execution.traceback?.length ?? 0) > 0 ) { throw new Error(result.body.message || "代码执行失败"); } console.log(execution.stdout); // 输出 2。 return execution.stdout; } finally { await sandbox.destroy(); } }
该示例在任务结束后释放实例,适合一次性任务。需要跨请求保留沙箱状态时,可按获取沙箱中的说明选择使用方式。
result.ok 表示请求是否成功;代码是否成功执行,还需检查执行状态和退出码等字段,详见请求与错误处理。
SDK 提供两种沙箱管理方式,共有三个入口:
getConversationSandbox()。createSandbox() 创建实例,并用 connectSandbox() 继续使用已保存的实例。| 场景 | 调用方式 | 行为 |
|---|---|---|
| 多轮会话需要复用环境 | getConversationSandbox(conversationId) | 同步返回沙箱对象;首次使用时查找或创建会话实例。 |
| 为一个任务创建独立环境 | await createSandbox() | 每次创建新实例;等待返回后即可读取 sandboxId。 |
| 继续使用已保存的实例 | connectSandbox(sandboxId) | 同步返回沙箱对象;首次使用时检查并连接指定实例。 |
三个入口都从 @iga-pages/agent/sandbox 导入,无需传入客户端配置。conversationId 是应用的非空会话标识,最多 256 UTF-8 字节;sandboxId 是沙箱实例 ID,两者不能互换。应用应校验 ID 与当前用户或任务的归属。
使用 getConversationSandbox(conversationId) 获取指定会话的沙箱。不同会话使用各自的实例,同一会话会复用同一沙箱实例,适合需要在多轮交互中保留文件和执行环境的场景。
import { getConversationSandbox } from "@iga-pages/agent/sandbox"; // conversationId 为非空字符串,最多 256 UTF-8 字节。 const sandbox = getConversationSandbox(conversationId); const result = await sandbox.code.executeCode({ language: "python", code: "print('Hello, sandbox!')", });
getConversationSandbox() 会同步返回沙箱对象,首次调用业务方法、getInfo() 或 extendTimeout() 时才初始化。应用无需自行保存实例 ID,同一会话可跨请求、跨进程复用实例。
SDK 确认实例失效后,会尝试自动恢复,恢复成功后重试原操作一次。新实例不会恢复旧文件、进程和浏览器状态,需要长期保留的数据应提前写入 Blob 等持久存储。
需要下一轮继续使用时,不要在每次请求结束后调用 destroy();确认不再需要该会话的沙箱时,再主动释放。
使用 createSandbox() 创建独立实例,由应用保存实例 ID;后续通过 connectSandbox(sandboxId) 连接同一个实例。适合需要自行决定实例如何分配、复用和释放的场景。
import { createSandbox, connectSandbox, } from "@iga-pages/agent/sandbox"; // 创建独立实例,等待初始化完成。 const sandbox = await createSandbox(); // 保存 ID,供后续请求或其他进程使用。 const sandboxId = sandbox.sandboxId; // 创建后可以直接使用。 const result = await sandbox.code.executeCode({ language: "python", code: "print('Hello, sandbox!')", }); // 后续通过保存的 ID 连接同一个实例。 const connected = connectSandbox(sandboxId); const info = await connected.getInfo(); // 不再需要时释放实例。 await connected.destroy();
实例失效后,需要由应用调用 createSandbox() 创建新实例,并更新保存的实例 ID。主动销毁实例后,请勿继续使用原沙箱对象或通过原 ID 连接。
获取沙箱后,通过以下模块执行操作。按会话管理和自行管理生命周期两种方式提供相同的能力。
调用后先检查 result.ok:请求失败时读取 result.error,请求成功时根据方法读取 result.body。请求成功不代表操作已成功完成,还需检查具体执行结果,详见请求与错误处理。
以下按模块列出全部公开方法及用途。具体参数、参数位置和返回类型,请查看对应方法签名:
| 能力 | 入口 | 用途 |
|---|---|---|
| 代码执行 | sandbox.code | 运行 Python 或 JavaScript 代码。 |
| 命令执行 | sandbox.shell | 执行 Shell 命令、查看输出和管理进程。 |
| 文件操作 | sandbox.file | 读写、查找文件。 |
| 网页操作 | sandbox.browserPage | 打开网页、操作页面和读取内容。 |
通过 sandbox.code 执行 Python 或 JavaScript 代码。
| 方法 | 用途 |
|---|---|
code.executeCode() | 执行 Python 或 JavaScript 代码。 |
例如,执行一段 Python 代码并读取标准输出:
const result = await sandbox.code.executeCode({ language: "python", code: "print(1 + 1)", }); if (!result.ok) { throw new Error("代码执行请求失败"); } if (result.body.success === false || !result.body.data) { throw new Error(result.body.message || "未获取到执行结果"); } console.log(result.body.data.stdout); // 输出:2
executeCode() 的执行数据位于 result.body.data,其中 stdout 为标准输出,stderr 为错误输出。
判断代码是否成功完成时,还需查看执行状态 status、退出码 exit_code 和异常堆栈 traceback,详见请求与错误处理。
通过 sandbox.shell 执行命令、查看输出和控制进程。
| 方法 | 用途 |
|---|---|
shell.execCommand() | 执行 Shell 命令。 |
shell.view() | 查看 Shell 会话输出。 |
shell.waitForProcess() | 等待进程执行。 |
shell.killProcess() | 终止进程。 |
const result = await sandbox.shell.execCommand({ command: "pwd && ls -la", exec_dir: "/tmp", async_mode: false, }); if (!result.ok) throw new Error("命令请求失败"); const command = result.body.data; if (result.body.success === false || !command || command.status !== "completed" || (command.exit_code !== undefined && command.exit_code !== 0)) { throw new Error(result.body.message || "命令未成功完成"); } console.log(command.output);
命令输出通过 result.body.data.output 读取。判断命令是否成功完成,还需检查 body.success、执行状态和退出码。
异步执行的命令可用 waitForProcess() 等待完成,用 killProcess() 终止进程。
通过 sandbox.file 读写、查找文件。
| 方法 | 用途 |
|---|---|
file.readFile() | 读取文件内容。 |
file.writeFile() | 写入或追加文件内容。 |
file.replaceInFile() | 替换文件中的内容。 |
file.searchInFile() | 搜索指定文件中的内容。 |
file.findFiles() | 查找文件。 |
file.grepFiles() | 在多个文件中搜索内容。 |
file.listPath() | 列出指定路径下的文件和目录。 |
文件路径指向沙箱内的文件系统。同一实例中的代码、命令和文件操作可以访问这些文件。下面先写入文件,再读取正文:
const written = await sandbox.file.writeFile({ file: "/tmp/notes.txt", content: "Hello Sandbox\n", append: false, }); if (!written.ok) throw new Error("写文件请求失败"); if (written.body.success === false) { throw new Error(written.body.message || "文件写入失败"); } const read = await sandbox.file.readFile({ file: "/tmp/notes.txt" }); if (!read.ok) throw new Error("读文件请求失败"); if (read.body.success === false) { throw new Error(read.body.message || "文件读取失败"); } const data = read.body.data; if (!data || !("content" in data)) throw new Error("响应中没有文件正文"); console.log(data.content);
读取文件正文前,先检查操作是否成功,再确认 body.data 中存在 content。写入成功后,body.data 包含文件路径和可选的写入字节数。
沙箱中的文件随实例存续。需要长期保留的输入和产物,请写入 Blob 等持久存储。
| 方法 | 用途 |
|---|---|
browserPage.navigate() | 打开指定网页。 |
browserPage.back() | 后退。 |
browserPage.forward() | 前进。 |
browserPage.reload() | 重新加载页面。 |
browserPage.click() | 点击页面元素或指定位置。 |
browserPage.fill() | 填写输入框。 |
browserPage.typeText() | 模拟键盘输入文本。 |
browserPage.pressKey() | 按下指定按键。 |
browserPage.hotKey() | 执行组合键操作。 |
browserPage.hover() | 将鼠标悬停在元素或指定位置。 |
browserPage.selectOption() | 选择下拉框选项。 |
browserPage.check() | 勾选复选框。 |
browserPage.uncheck() | 取消勾选复选框。 |
browserPage.uploadFile() | 向网页的文件上传控件提交文件。 |
browserPage.scroll() | 滚动页面。 |
browserPage.scrollTo() | 滚动到指定位置。 |
browserPage.scrollToElement() | 滚动到指定元素。 |
browserPage.screenshot() | 获取页面截图。 |
browserPage.getHtml() | 读取页面 HTML。 |
browserPage.getText() | 读取页面文本。 |
browserPage.wait() | 等待指定页面条件满足。 |
sandbox.browserPage 操作当前页面。导航、读取内容和截图等有先后依赖的操作,应依次执行,避免并发导航改变操作目标。下面先打开网页,再获取截图:
const navigation = await sandbox.browserPage.navigate({ url: "https://example.com", wait_until: "load", }); if (!navigation.ok) throw new Error("导航请求失败"); if (navigation.body.success === false) { throw new Error(navigation.body.message || "页面导航失败"); } const screenshot = await sandbox.browserPage.screenshot({ full_page: true }); if (!screenshot.ok) throw new Error("截图请求失败"); const image = await screenshot.body.arrayBuffer();
截图使用当前页面,请先通过 navigate() 打开目标网页。截图成功后,body 为二进制内容,可通过 arrayBuffer()、blob() 或 stream() 读取,再用于显示、保存或交给模型处理。
读取 HTML 使用 getHtml(),HTML 字符串位于 result.body.data。
获取到的沙箱实例除上述业务能力模块外,还暴露以下生命周期方法与属性:
| 方法 | 用途 | 返回值 |
|---|---|---|
getInfo(options?) | 查询当前实例信息;必要时触发初始化 | Promise<SandboxInstanceInfo> |
extendTimeout(timeoutInMinutes, options?) | 请求续期,时长单位为分钟 | Promise<SandboxInstanceInfo> |
destroy(options?) | 销毁实例,释放其资源 | Promise<void> |
此外,实例还提供 conversationId(会话 ID,仅绑定会话的实例有)与 sandboxId(实例 ID)两个属性。
const info = await sandbox.getInfo(); console.log(info.sandboxId, info.expireAt); const updated = await sandbox.extendTimeout(30); // 单位为分钟 console.log(updated.expireAt); // 不再需要该实例时释放。 await sandbox.destroy();
getInfo() 和 extendTimeout() 返回以下实例信息 SandboxInstanceInfo:
| 字段 | 类型 | 说明 |
|---|---|---|
| sandboxId | string | 实例 ID。 |
| status | 可选字符串枚举 | Starting(启动中)、Ready(可用)、Failed(失败)、Terminating(终止中)。 |
| createdAt | string | undefined | 创建时间。 |
| expireAt | string | undefined | 预计到期时间。 |
通过 getInfo() 返回的 expireAt 查看实例的预计到期时间。
按会话管理和自行管理生命周期两种方式都支持自动续期。SDK 在使用实例时检查已知到期时间,剩余不超过 5 分钟时尝试续期 30 分钟。常规自动续期失败不会阻断当前业务调用。
自动续期由调用触发,不会在后台持续保活。闲置或长时间执行中的实例仍可能到期。需要确认续期结果时,显式调用 extendTimeout(),并检查返回的 expireAt。
实例续期与代码、命令的执行超时以及单次请求超时相互独立,不能互相替代。
使用 getConversationSandbox() 时,SDK 确认实例失效后会尝试自动恢复,恢复成功后重试原操作一次;恢复或重试仍可能失败,应用需要处理返回结果或异常。
使用 createSandbox()、connectSandbox() 时,SDK 不会自动创建替代实例。应用需要调用 createSandbox() 创建新实例,并更新保存的实例 ID。
注意
connectSandbox() 在懒加载时做前置检查:实例不存在或状态为 Failed/Terminating 时抛出 SandboxError(message 说明实例已不存在);实例已过期则自动续期后接回。
不再需要实例时,调用 destroy():
await sandbox.destroy();
销毁成功后,请停止使用原沙箱对象或通过原 ID 连接。需要继续使用沙箱时,通过获取沙箱中的入口重新获取。
按会话复用沙箱时,如果下一轮交互还需要使用当前实例,请勿在每次请求结束后销毁。
沙箱是内置工具运行的基础。SDK 的内置工具集通过 getTools(sandbox) 基于一个沙箱实例来构建,工具在执行时依托该沙箱所提供的运行环境完成动作。因此,在构建工具之前,您需要先准备好一个可用的沙箱实例,再将其作为参数传入 getTools。
关于内置工具的具体清单与用法,请参见工具 Tools。
实例管理方法与代码、文件等操作方法的返回方式不同:
| 方法 | 成功时 | 失败时 |
|---|---|---|
createSandbox() | 返回沙箱对象 | 抛出异常。 |
getInfo()、extendTimeout() | 返回实例信息 | 抛出异常。 |
destroy() | 无返回数据 | 抛出异常。 |
| 代码、命令、文件和浏览器等业务方法 | 返回 ok: true,结果位于 body | 可能返回 ok: false,也可能在初始化等阶段抛出异常。 |
调用业务方法时,先检查 result.ok:
const result = await sandbox.file.readFile({ file: "/tmp/notes.txt", }); if (!result.ok) { console.error(result.error); throw new Error("读取请求失败"); } // 请求成功后,再检查并读取 result.body。
对于 JSON 响应,还需要判断操作是否成功完成:
| 检查项 | 说明 |
|---|---|
result.ok | 请求是否成功。 |
result.body.success | 为 false 时表示操作失败,说明通常位于 message。 |
result.body.data | 具体结果,按方法检查执行状态、退出码或文件正文等字段。 |
截图返回二进制内容,直接读取 body,无需检查 JSON 的 success、data 字段。
除检查返回结果外,还应使用 try...catch 处理初始化和实例管理等阶段的异常:
import { getConversationSandbox, SandboxError, isAbortError, } from "@iga-pages/agent/sandbox"; export async function inspect(conversationId: string) { try { const sandbox = getConversationSandbox(conversationId); return await sandbox.getInfo(); } catch (error) { if (isAbortError(error)) { return undefined; } if (error instanceof SandboxError) { console.error(error.toJSON()); } throw error; } }
SandboxError 提供以下信息:
| 字段或方法 | 说明 |
|---|---|
code | 由 HTTP 状态名派生的错误码(如 404 → NotFound);无可用状态时回落为 SandboxError,便于程序分支判断。 |
status | 可选,对应的 HTTP 状态码。 |
message | 可读的错误说明。 |
toJSON() | 返回 { code, status?, message } 结构化错误信息,适合记录。 |
SandboxError 以 code 标识出错类型。各模块的错误对象字段结构并不相同(Store 为 StoreError、Blob 为 BlobError),处理时应先用 instanceof 按错误类型分支,再读取该类型自有的字段,不要假设某个字段在所有模块上都存在。
被取消信号中止属于正常控制流,用 isAbortError 单独识别。
代码、文件等业务方法返回的失败仍需检查 result.ok,不能只依赖 catch。
Unauthorized、Forbidden 一类属配置或权限问题,应检查项目登录与授权而非重试。
将取消信号放入本次调用的请求选项:
const result = await sandbox.shell.execCommand( { command: "pwd" }, { abortSignal: request.signal }, );
实例管理方法同样支持取消:
const info = await sandbox.getInfo({ abortSignal: request.signal, });
取消调用不会销毁实例,也不保证终止已经启动的代码或进程。需要停止 Shell 进程时,使用 killProcess()。
请求选项的位置取决于方法签名:
// 有操作参数:请求选项放在第二个参数。 await sandbox.browserPage.getHtml( {}, { abortSignal: request.signal }, ); // 实例管理方法无操作参数:请求选项放在第一个参数。 await sandbox.getInfo({ abortSignal: request.signal, });
获取沙箱的入口函数不接收取消信号。请在具体操作时传入。