本指南通过 Cloudflare 的 Worker 参考实现,采用 由 Webhook 管理的预配方式 。
请参阅 OpenAI Cookbook 中的由应用管理和由 Webhook 管理的示例。
- 您的应用创建 Agents API 会话并发送输入。
- OpenAI 向您 Cloudflare 账户中的 Worker 发送会话 Webhook。
- 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。
Cloudflare 的 Worker 参考实现包含 Webhook 处理程序、Container 镜像、部署配置和清理端点。
为清理端点生成一个密钥,并将其保存为 EXECUTOR_CLIENT_SECRET:
openssl rand -hex 32
在您的 Cloudflare 账户中部署 Worker:
部署到 Cloudflare
根据提示输入以下值:
| 变量 | 值 |
|---|
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。启用 Cloudflare 参考集成中列出的事件:
agent.session.created
agent.session.action_required
agent.session.in_progress
agent.session.idle
agent.session.failed
将 OPENAI_WEBHOOK_SECRET 替换为 OpenAI 返回的签名密钥,然后部署新版本的 Worker。检查其配置。以下示例使用标准 HTTP 客户端调用 Worker:
1
2
3
4
5
6
7
8// 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());
1
2
3
4
5
6
7# 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())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 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)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 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());
1
2
3
4
5
6
7
8
9
10# 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
1curl --fail-with-body "$WORKER_URL/health"
响应应同时包含 "configured": true 和 "webhook_configured": true。
environment_connection 必需操作是重新连接离线执行器的信号。仅凭空闲事件不足以判断可以安全关闭;请参阅生命周期行为。
使用您应用的 OPENAI_API_KEY,以及与 Worker 中配置相同的 OPENAI_AGENT_ID,按照会话步骤操作。创建自托管会话,并让智能体写入和读取 /workspace/hello.txt。
Worker 接收会话 Webhook 并连接沙盒执行器。您的应用通过 Agents API 以流式方式传输智能体的输出。
将会话 ID 保存为 SESSION_ID。要继续对话,请先打开会话事件流,再发送后续输入。如果执行器处于离线状态,新输入会请求建立环境连接,并等待 Worker 重新连接执行器。重新连接本身不会恢复之前 Container 中的文件。
Cloudflare 的基础 Worker 应用使用 @openai/agents-api TypeScript SDK 创建会话、发送初始和后续输入,以及清理资源。其 POST /demo 端点用于运行该工作流程。
此应用也采用由 Webhook 管理的预配方式。在 Worker 中运行您的应用,并不意味着应用必须直接预配沙盒。
当应用不再需要沙盒时,调用 Worker 参考实现中需要身份验证的清理端点:
1
2
3
4
5
6
7
8// 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());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 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())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// 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)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// 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());
1
2
3
4
5
6
7
8
9
10
11# 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
1
2
3
4curl --fail-with-body \
--request DELETE \
--header "Authorization: Bearer $EXECUTOR_CLIENT_SECRET" \
"$WORKER_URL/executors/$SESSION_ID"
另行删除 Agents API 会话。删除会话不会触发 Webhook,因此要立即完成清理,请执行这两项操作。在释放 Container 之前,请取回您需要的文件。
要直接控制沙盒预配,请使用 Cloudflare Sandbox SDK,并遵循由应用管理的生命周期和执行器连接说明。每个会话使用一个预配控制器。