OpenAI Cloudflare 沙箱

2026-11-06 14:59 更新

本指南通过Cloudflare参考Worker,实现 Webhook管理的资源创建。

完整示例见 Cookbook 的应用管理与Webhook管理。

工作方式

  1. 应用创建Agents API会话并发送输入。
  2. OpenAI把会话webhook发送到Cloudflare账户中的Worker。
  3. 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.created
  • agent.session.action_required
  • agent.session.in_progress
  • agent.session.idle
  • agent.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,遵循应用管理生命周期及执行器连接要求。每个会话只设一个资源控制器。

参考

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号