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 提供,啟動時拉取並應用。