Code Mode
在 Code Mode 中,Agent 不再一次调用一个 App 工具,而是写一段简短的 JavaScript 程序。程序调用这些工具,处理它们的结果,只把 Agent 需要的内容交回。这样可以节省 token,因为大的结果留在程序里,不会进入模型的上下文。程序也可以同时调用很多工具。
Code Mode 不给 Agent 任何新的权限。程序中的每次 App 调用都经过和普通 App 工具调用相同的能力检查。每次调用也都是一个普通的 App operation。
| 词 | 含义 |
|---|---|
| 程序 | Agent 写的 JavaScript 代码。它是一个 async 函数的函数体。 |
| 绑定 | 程序可以调用的一个 App 能力,比如 apps.work_calendar.list_events。 |
| 沙箱 | 程序运行的封闭环境。里面没有网络、没有文件、没有定时器,也没有 Node.js。 |
| Code run | 一个程序以及它所有 App 调用的记录。用 GET /v1/code-runs/:id 读取。 |
| 重放 | 做出决定后,同一个程序的新一次运行。已经运行过的调用会给出保存下来的结果。 |
打开 Code Mode
Code Mode 默认是关闭的。平台管理员在平台设置中打开它:
curl -X PATCH -H "Authorization: Bearer $OPENBOT_TOKEN" \ -H "Content-Type: application/json" \ -d '{"expectedVersion":3,"codeMode":{"enabled":true,"timeoutMs":30000,"memoryMb":64,"outputBytes":65536}}' \ https://api.example.com/v1/platform/settings| 字段 | 含义 |
|---|---|
enabled |
为 true 时,在有 App 连接的每个 Turn 中,Agent 都会得到 Code Mode 工具。 |
timeoutMs |
程序一次运行可以用多长时间。程序等待 App 调用的时间不算在内。 |
memoryMb |
一个程序可以使用多少内存。 |
outputBytes |
程序返回值的最大大小,以及它最多保留多少日志文本。 |
这些限制保护的是服务器,而不是对 Agent 的限制。OpenBot 的每个实例都使用相同的限制,所以另一个实例再次运行程序时,程序的行为也相同。
Agent 如何使用它
Agent 会多得到两个工具。它们和普通的 App 工具并存,不会取代它们。
| 工具 | 作用 |
|---|---|
openbot_code_api |
显示绑定的 TypeScript 声明。Agent 可以按词语或按连接搜索。 |
openbot_run_code |
运行一个程序,交回它的结果、日志行,以及它的 App 调用列表。 |
每个绑定有两部分:连接和能力。一个名为 “Work calendar” 的连接中有工具 list-events,它会变成 apps.work_calendar.list_events。apps.call("work_calendar.list_events", args) 的作用相同。资源和 prompt 也会变成读取用的绑定。一个程序可以是这样的:
const [today, tomorrow] = await Promise.all([ apps.work_calendar.list_events({ day: "2026-10-23" }), apps.work_calendar.list_events({ day: "2026-10-24" }),])const busy = [today, tomorrow].flatMap( (result) => result.structuredContent.events,)console.log("events", busy.length)return busy.filter((event) => event.attendees > 10).map((event) => event.title)工具调用会把 App 发来的结果交给程序。调用不成功时,程序会得到一个 Error:
| Error 名称 | 原因 |
|---|---|
AppCallDenied |
决定者没有允许这次调用。 |
AppCallFailed |
App 调用失败。details.reason 说明原因。 |
AppInputRequired |
App 需要更多输入。请在代码之外回答 details.operationId 中的 App operation。 |
程序可以捕获这些错误,然后继续运行。
当调用需要决定时
有些 App 调用需要决定,比如会写入数据的工具。这时程序停在这次调用上,Turn 进入等待。请求的 app 中是这次 App 调用,code 中是这个程序:
{ "capability": { "action": "apps.call", "target": "conn-7/send_email" }, "toolCallId": "call-9", "app": { "id": "…", "callId": "call-9:1", "arguments": { "to": "team@example.com" } }, "code": { "run": "8f1c…", "index": 1, "code": "const notes = await …" }}做出决定后,OpenBot 从头再运行一次程序。已经运行过的每次调用都会给出保存下来的结果,OpenBot 不会再把它发给 App。然后程序会越过刚才等待的调用继续运行。程序结束时,Turn 得到结果并继续。
只有程序每次都按相同的顺序做相同的调用,这种方式才行得通。所以程序在 Date.now() 中看到的是固定的时间,Math.random() 每次运行都给出相同的数字。如果程序做了和上次不同的调用,OpenBot 会以 ReplayMismatch 停止它。程序等待期间连接发生变化时,就可能出现这种情况。不会有任何调用被误发。
读取 code run
GET /v1/sessions/:sessionId/code-runs 列出一个 Session 的程序。GET /v1/code-runs/:id 读取一个程序。两者都需要 apps.use。列表还需要对该 Session 的 sessions.read。你只能看到你自己的 Session 中的程序。
{ "id": "8f1c…", "session": "c81d…", "turn": "t-77", "callId": "call-9", "status": "Completed", "code": "const notes = await …", "reason": "Send the notes to the team.", "calls": [ { "index": 0, "binding": "notes.read_note", "source": { "connectionName": "Notes", "name": "read_note", "…": "…" }, "operationId": "3a9e…", "outcome": { "_tag": "Done", "result": { "_tag": "Completed", "…": "…" } } } ], "result": { "_tag": "Completed", "value": "sent", "logs": [] }, "createdAt": 1792486800000, "updatedAt": 1792486801000}status |
含义 |
|---|---|
Running |
程序正在运行。 |
Waiting |
程序的一次调用正在等待决定。 |
Completed |
程序已结束。result.value 是它的返回值。 |
Failed |
程序没有正常结束。result.reason 说明原因。 |
Cancelled |
程序运行时 Turn 被中断了。 |
和 App operation 一样,OpenBot 在数据库中加密保存 code run。
失败原因
reason |
含义 |
|---|---|
Error |
程序抛出了一个它没有捕获的错误。 |
Timeout |
程序用完了它的全部时间。 |
Memory |
程序用完了它的全部内存。 |
OutputTooLarge |
程序返回的数据超过了 outputBytes 允许的大小。 |
ReplayMismatch |
程序做了和上次运行不同的调用。 |
Cancelled |
程序被停止了。 |
Unavailable |
OpenBot 无法运行或保存这个程序,或者 Code Mode 是关闭的。 |