Cloud Tasks
codex cloud 是把 prompt 丢到 OpenAI 云端跑的子命令:本地不调模型、不跑 Agent 循环,只负责提交任务、拉状态、拿 diff、应用补丁。cloud-tasks 是 TUI + CLI 入口,cloud-tasks-client 提供 CloudBackend trait + HTTP 实现,cloud-tasks-mock-client 给 debug 模式塞假后端,backend-client 是底层 HTTP(也复用给其他 backend API)。跟 Model Provider 的本地推理不同——这里"模型推理"发生在云端容器里。
职责
- 提交任务:
run_exec_command把 prompt + git ref + environment id 打包POST /wham/tasks(或/api/codex/tasks),返回 task id (codex-rs/cloud-tasks/src/lib.rs:161-184)。 - 列表/详情/差分:
run_list_command、run_status_command、run_diff_command三个 CLI 子命令分别对应list_tasks/get_task_summary/get_task_diff(codex-rs/cloud-tasks/src/cli.rs:16-27)。 - 应用补丁:
apply_task走 dry-run preflight 后再真实 apply,diff_override让用户挑 best-of-N 中的某次尝试 (codex-rs/cloud-tasks-client/src/http.rs:99-121)。 - 抽象 trait:
CloudBackend把所有跟后端交互的方法收口成 trait,debug build 走MockClient,release 走HttpClient(codex-rs/cloud-tasks-client/src/api.rs:136-176)。 - 初始化后端:
init_backend装配 base URL、UA、auth provider、ChatGPT-Account-Id,根据CODEX_CLOUD_TASKS_BASE_URL切 WHAM / Codex API 路径风格 (codex-rs/cloud-tasks/src/lib.rs:43-107)。
设计动机
做成独立 crate 而不是塞进 core,因为这条路径不跑 Agent 主循环。core 里的 client 是对模型发 stream 请求、解析 SSE、维护 turn state;cloud task 是"提交个长任务到云端,异步等结果"。两者并发模型完全不同:前者是长连接 stream,后者是 poll + 拉取。CloudBackend trait 的存在是为了让 TUI 在 debug build 里跑——init_backend 检测 CODEX_CLOUD_TASKS_MODE=mock 时直接换 MockClient,本地不用真连 ChatGPT 后端就能调 UI 状态机。HttpClient 内部复用 backend-client::Client 而不是另起一套 HTTP,因为 backend-api 的鉴权头、Cloudflare cookie store、路径风格(wham vs codex-api)等逻辑跟其他 backend API 调用一致。
best-of-N 是这条路径的特色:create_task 接受 attempts: usize(1-4),云端会跑多个 attempt,list_sibling_attempts 把它们都拉回来,ApplyCommand 让用户指定 --attempt N 选某次结果。diff_override 字段贯穿 apply_task_preflight 和 apply_task,让 apply 阶段不重新拉 diff 而是用用户选中的那份。
关键文件
codex-rs/cloud-tasks/src/lib.rs:735-744 — run_main,子命令分发 + TUI 模式启动入口。codex-rs/cloud-tasks/src/lib.rs:43-107 — BackendContext 与 init_backend,根据环境变量决定 mock / http 并装配 auth。codex-rs/cloud-tasks/src/cli.rs:29-50 — ExecCommand,定义 --env、--attempts、--branch 三个核心参数。codex-rs/cloud-tasks-client/src/api.rs:136-176 — CloudBackend trait,11 个方法覆盖 list / get / apply / create。codex-rs/cloud-tasks-client/src/http.rs:25-63 — HttpClient,包装 backend-client::Client 并实现 CloudBackend。codex-rs/backend-client/src/client.rs:451-482 — create_task 在 base client 里的实现,从 JSON 里抠 task id。codex-rs/cloud-tasks-mock-client/src/mock.rs:163-189 — MockClient 实现 trait,转调内部 mock 数据生成函数。工厂 init_backend 在 debug build 里检查环境变量,决定走 mock 还是真 HTTP:
// cloud-tasks/src/lib.rs:43-60 — 后端选择
async fn init_backend(user_agent_suffix: &str) -> anyhow::Result<BackendContext> {
#[cfg(debug_assertions)]
let use_mock = matches!(
std::env::var("CODEX_CLOUD_TASKS_MODE").ok().as_deref(),
Some("mock") | Some("MOCK")
);
let base_url = std::env::var("CODEX_CLOUD_TASKS_BASE_URL")
.unwrap_or_else(|_| "https://chatgpt.com/backend-api".to_string());
#[cfg(debug_assertions)]
if use_mock {
return Ok(BackendContext {
backend: Arc::new(codex_cloud_tasks_mock_client::MockClient),
base_url,
});
}CloudBackend trait 用 CloudBackendFuture 把返回类型统一成 boxed future,mock 和 http 实现共享同一签名:
// cloud-tasks-client/src/api.rs:136-176 — 后端抽象
pub trait CloudBackend: Send + Sync {
fn list_tasks<'a>(
&'a self,
env: Option<&'a str>,
limit: Option<i64>,
cursor: Option<&'a str>,
) -> CloudBackendFuture<'a, TaskListPage>;
fn get_task_summary(&self, id: TaskId) -> CloudBackendFuture<'_, TaskSummary>;
fn get_task_diff(&self, id: TaskId) -> CloudBackendFuture<'_, Option<String>>;
// ...messages / sibling attempts / apply preflight / apply / create
fn create_task<'a>(
&'a self,
env_id: &'a str,
prompt: &'a str,
git_ref: &'a str,
qa_mode: bool,
best_of_n: usize,
) -> CloudBackendFuture<'a, CreatedTask>;
}create_task 在 base client 里 POST 出去,再从 JSON 里抠 task id(优先 task.id,回退顶层 id):
// backend-client/src/client.rs:451-482 — POST /wham/tasks 并提取 task id
pub async fn create_task(&self, request_body: serde_json::Value) -> Result<String> {
let url = match self.path_style {
PathStyle::CodexApi => format!("{}/api/codex/tasks", self.base_url),
PathStyle::ChatGptApi => format!("{}/wham/tasks", self.base_url),
};
// ...发请求,解析 body
match serde_json::from_str::<serde_json::Value>(&body) {
Ok(v) => {
if let Some(id) = v.get("task").and_then(|t| t.get("id")).and_then(|s| s.as_str()) {
Ok(id.to_string())
} else if let Some(id) = v.get("id").and_then(|s| s.as_str()) {
Ok(id.to_string())
} else {
anyhow::bail!("POST {url} succeeded but no task id found; ...");
}
}
Err(e) => anyhow::bail!("Decode error for {url}: {e}; ..."),
}
}数据流
边界与失败
- 未登录直接退出:
init_backend检测auth为 None 或uses_codex_backend()为 false 时process::exit(1)(codex-rs/cloud-tasks/src/lib.rs:76-95)。 - 路径风格由 base URL 决定:
https://chatgpt.com/backend-api走 WHAM (/wham/tasks),其他走 Codex API (/api/codex/tasks),由PathStyle::from_base_url判定 (codex-rs/backend-client/src/client.rs:151-177)。 - attempts 范围受限:
parse_attempts强制 1-4,跟后端 best-of-N 上限对齐 (codex-rs/cloud-tasks/src/cli.rs:52-61)。 - apply 失败退出码非 0:
run_apply_command检查ApplyStatus,不是Success就process::exit(1),方便脚本链 (codex-rs/cloud-tasks/src/lib.rs:589-608)。 - create_task id 兼容两种 JSON:
task.id和顶层id都接受,不同后端返回结构不同 (codex-rs/backend-client/src/client.rs:464-481)。
小结
Cloud Tasks 把"提交、查询、应用"三步压成 CloudBackend trait,HTTP 与 mock 都实现同一份 trait,方便 TUI 在本地无后端时调试。底层 HTTP 复用 backend-client::Client,跟 Model Provider 共享鉴权与路径风格判定。云端任务自身的配置(允许的 environment、enterprise 策略)由 配置系统 里的 cloud config bundle 提供,启动时拉取并应用。