OpenAI Cloudflare 沙箱
本指南通过Cloudflare参考Worker,实现 Webhook管理的资源创建。
完整示例见 Cookbook 的应用管理与Webhook管理。
工作方式
- 应用创建Agents API会话并发送输入。
- OpenAI把会话webhook发送到Cloudflare账户中的Worker。
- Worker为此会话启动或重连运行
codex exec-server的Container;执行器向OpenAI建立出站连接,让智能体运行命令和处理文件。
应用调用Agents API,参考Worker管理沙箱资源。连接与恢复行为见沙箱生命周期。
开始之前
需要具备Containers权限的Cloudflare账户。应用请求使用 OPENAI_API_KEY,OPENAI_EXECUTOR_API_KEY 设置为独立环境密钥,只将后者作为 CODEX_API_KEY 传入Container。
创建智能体,把ID保存为 OPENAI_AGENT_ID,应用和参考Worker使用相同ID。
部署参考 Worker
Cloudflare的参考Worker包含webhook处理器、Container镜像、部署配置和清理端点。
生成清理端点秘密,保存为 EXECUTOR_CLIENT_SECRET:
来源示例 1(bash):
## [中文] 示例 1:部署参考 Worker。
## [中文] 用 openssl 生成32字节随机值并以十六进制输出,用作清理端点秘密;不要把实际值写入教程或日志。
openssl rand -hex 32
通过Deploy to Cloudflare(部署到Cloudflare)在Cloudflare账户部署Worker,提示输入时填写:
| 变量 | 值 |
|---|---|
OPENAI_API_KEY |
Worker读取会话状态使用的密钥 |
OPENAI_EXECUTOR_API_KEY |
以CODEX_API_KEY传给执行器的环境密钥 |
OPENAI_AGENT_ID |
此Worker服务的智能体ID |
OPENAI_WEBHOOK_SECRET |
首次部署先填pending-webhook-registration |
EXECUTOR_CLIENT_SECRET |
前面生成的清理秘密 |
部署后的Worker URL保存为 WORKER_URL。
注册 Webhook
按配置指南,在OpenAI项目注册 $WORKER_URL/webhook,启用参考集成列出的事件:
agent.session.createdagent.session.action_requiredagent.session.in_progressagent.session.idleagent.session.failed
把OpenAI返回的签名秘密写入 OPENAI_WEBHOOK_SECRET,部署新Worker版本后检查配置。以下用标准HTTP客户端检查Worker:
来源示例 2(javascript):
// [中文] 示例 2:注册 Webhook。
// [中文] 本段涉及的请求或服务地址:https://worker.example.com;示例占位地址需替换为实际资源。
// Replace the illustrative IDs and URLs below with your own resource values.
const response = await fetch(
"https://worker.example.com".replace(/\/+$/, "") + "/health",
{ method: "GET" }
);
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
console.log(await response.text());
来源示例 3(python):
## [中文] 示例 3:注册 Webhook。
## [中文] 本段涉及的请求或服务地址:https://worker.example.com;示例占位地址需替换为实际资源。
## Replace the illustrative IDs and URLs below with your own resource values.
import urllib.request
url = "https://worker.example.com".rstrip("/") + "/health"
request = urllib.request.Request(url, method="GET")
with urllib.request.urlopen(request) as response:
print(response.read().decode())
来源示例 4(go):
// [中文] 示例 4:注册 Webhook。
// [中文] 本段涉及的请求或服务地址:https://worker.example.com;示例占位地址需替换为实际资源。
// Replace the illustrative IDs and URLs below with your own resource values.
import (
"io"
"net/http"
"os"
"strings"
)
endpoint := strings.TrimRight("https://worker.example.com", "/") + "/health"
request, err := http.NewRequest("GET", endpoint, nil)
if err != nil {
panic(err)
}
response, err := http.DefaultClient.Do(request)
if err != nil {
panic(err)
}
defer response.Body.Close()
if response.StatusCode/100 != 2 {
panic(response.Status)
}
if _, err := io.Copy(os.Stdout, response.Body); err != nil {
panic(err)
}
来源示例 5(java):
// [中文] 示例 5:注册 Webhook。
// [中文] 本段涉及的请求或服务地址:https://worker.example.com;示例占位地址需替换为实际资源。
// Replace the illustrative IDs and URLs below with your own resource values.
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
String endpoint = "https://worker.example.com".replaceAll("/+$", "") + "/health";
var request =
HttpRequest.newBuilder(URI.create(endpoint))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() / 100 != 2)
throw new IllegalStateException("Request failed: " + response.statusCode());
System.out.println(response.body());
来源示例 6(ruby):
## [中文] 示例 6:注册 Webhook。
## [中文] 本段涉及的请求或服务地址:https://worker.example.com;示例占位地址需替换为实际资源。
## Replace the illustrative IDs and URLs below with your own resource values.
require "uri"
require "net/http"
uri = URI("https://worker.example.com".sub(%r{/+\z}, "") + "/health")
request = Net::HTTP::Get.new(uri)
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(request) }
raise "Request failed: #{response.code}" unless response.is_a?(Net::HTTPSuccess)
puts response.body
来源示例 7(bash):
## [中文] 示例 7:注册 Webhook。
## [中文] 检查已部署 Worker 的 health 端点;应核对 configured 与 webhook_configured,而非只看 HTTP 成功。
curl --fail-with-body "$WORKER_URL/health"
响应应同时包含 "configured": true 与 "webhook_configured": true。
重连离线执行器的信号是 environment_connection 所需动作,不是单独的空闲事件,见生命周期行为。
运行会话
使用应用的 OPENAI_API_KEY 和Worker配置中相同的 OPENAI_AGENT_ID,按会话步骤创建自托管会话,让智能体写入并读取 /workspace/hello.txt。
Worker接收会话事件并连接沙箱执行器,应用通过Agents API流式读取输出。
保存会话ID为 SESSION_ID。继续对话前,先打开会话事件流,再发送后续输入。执行器离线时,新输入请求环境连接并等待Worker重连。重连本身不会恢复此前Container的文件。
在 Worker 中运行应用
Cloudflare的基础Worker应用通过 @openai/agents-api TypeScript SDK创建会话、发送初始与后续输入并清理资源,POST /demo 运行完整流程。
此应用仍采用webhook管理资源。在Worker中运行应用,不代表应用必须直接创建沙箱。
清理
不再需要沙箱时,调用参考Worker经过认证的清理端点:
来源示例 8(javascript):
// [中文] 示例 8:清理。
// [中文] 本段涉及的请求或服务地址:https://worker.example.com/executors/sess_123;示例占位地址需替换为实际资源。
// Replace the illustrative IDs and URLs below with your own resource values.
const response = await fetch("https://worker.example.com/executors/sess_123", {
method: "DELETE",
headers: { Authorization: `Bearer ${process.env.EXECUTOR_CLIENT_SECRET}` },
});
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
console.log(await response.text());
来源示例 9(python):
## [中文] 示例 9:清理。
## [中文] 本段涉及的请求或服务地址:https://worker.example.com;示例占位地址需替换为实际资源。
## Replace the illustrative IDs and URLs below with your own resource values.
import os
from urllib.parse import quote
import urllib.request
url = (
"https://worker.example.com".rstrip("/")
+ "/executors/"
+ quote("sess_123", safe="")
)
request = urllib.request.Request(
url,
method="DELETE",
headers={"Authorization": "Bearer " + os.environ["EXECUTOR_CLIENT_SECRET"]},
)
with urllib.request.urlopen(request) as response:
print(response.read().decode())
来源示例 10(go):
// [中文] 示例 10:清理。
// [中文] 本段涉及的请求或服务地址:https://worker.example.com;示例占位地址需替换为实际资源。
// Replace the illustrative IDs and URLs below with your own resource values.
import (
"io"
"net/http"
"net/url"
"os"
"strings"
)
endpoint := strings.TrimRight("https://worker.example.com", "/") + "/executors/" + url.PathEscape("sess_123")
request, err := http.NewRequest("DELETE", endpoint, nil)
if err != nil {
panic(err)
}
request.Header.Set("Authorization", "Bearer "+os.Getenv("EXECUTOR_CLIENT_SECRET"))
response, err := http.DefaultClient.Do(request)
if err != nil {
panic(err)
}
defer response.Body.Close()
if response.StatusCode/100 != 2 {
panic(response.Status)
}
if _, err := io.Copy(os.Stdout, response.Body); err != nil {
panic(err)
}
来源示例 11(java):
// [中文] 示例 11:清理。
// [中文] 本段涉及的请求或服务地址:https://worker.example.com;示例占位地址需替换为实际资源。
// Replace the illustrative IDs and URLs below with your own resource values.
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
String endpoint =
"https://worker.example.com".replaceAll("/+$", "")
+ "/executors/"
+ URLEncoder.encode("sess_123", StandardCharsets.UTF_8).replace("+", "%20");
var request =
HttpRequest.newBuilder(URI.create(endpoint))
.header("Authorization", "Bearer " + System.getenv("EXECUTOR_CLIENT_SECRET"))
.method("DELETE", HttpRequest.BodyPublishers.noBody())
.build();
var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() / 100 != 2)
throw new IllegalStateException("Request failed: " + response.statusCode());
System.out.println(response.body());
来源示例 12(ruby):
## [中文] 示例 12:清理。
## [中文] 本段涉及的请求或服务地址:https://worker.example.com;示例占位地址需替换为实际资源。
## Replace the illustrative IDs and URLs below with your own resource values.
require "uri"
require "net/http"
uri = URI("https://worker.example.com".sub(%r{/+\z}, "") + "/executors/" + URI.encode_www_form_component("sess_123").gsub("+", "%20"))
request = Net::HTTP::Delete.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("EXECUTOR_CLIENT_SECRET")}"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(request) }
raise "Request failed: #{response.code}" unless response.is_a?(Net::HTTPSuccess)
puts response.body
来源示例 13(bash):
## [中文] 示例 13:清理。
## [中文] 本段清理指定资源;会话与提供商沙箱是独立资源,按本节要求分别处理。
curl --fail-with-body \
--request DELETE \
--header "Authorization: Bearer $EXECUTOR_CLIENT_SECRET" \
"$WORKER_URL/executors/$SESSION_ID"
另行删除Agents API会话。删除会话不会发webhook,需要两项操作都完成才能立即清理。释放Container前先取回文件。
进阶:应用管理资源创建
需要直接控制时,使用Cloudflare Sandbox SDK,遵循应用管理生命周期及执行器连接要求。每个会话只设一个资源控制器。
参考
- Cloudflare Containers与OpenAI Agents API,涵盖配置、生命周期、快照和镜像定制。
- Cloudflare沙箱文档。
- Cloudflare沙箱TypeScript SDK。

TRAE-AI编程


更多建议: