Skip to content

Cloud Tasks

源码版本rust-v0.145.0

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 的本地推理不同——這裡「模型推理」發生在雲端容器裡。

職責

  1. 提交任務: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)。
  2. 列表/詳情/差分:run_list_commandrun_status_commandrun_diff_command 三個 CLI 子命令分別對應 list_tasks / get_task_summary / get_task_diff (codex-rs/cloud-tasks/src/cli.rs:16-27)。
  3. 應用補丁:apply_task 走 dry-run preflight 後再真實 apply,diff_override 讓使用者挑 best-of-N 中的某次嘗試 (codex-rs/cloud-tasks-client/src/http.rs:99-121)。
  4. 抽象 trait:CloudBackend 把所有跟後端互動的方法收口成 trait,debug build 走 MockClient,release 走 HttpClient (codex-rs/cloud-tasks-client/src/api.rs:136-176)。
  5. 初始化後端: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_preflightapply_task,讓 apply 階段不重新拉 diff 而是用使用者選中的那份。

關鍵檔案

codex-rs/cloud-tasks/src/lib.rs:735-744run_main,子命令分發 + TUI 模式啟動入口。codex-rs/cloud-tasks/src/lib.rs:43-107BackendContextinit_backend,根據環境變數決定 mock / http 並裝配 auth。codex-rs/cloud-tasks/src/cli.rs:29-50ExecCommand,定義 --env--attempts--branch 三個核心參數。codex-rs/cloud-tasks-client/src/api.rs:136-176CloudBackend trait,11 個方法覆蓋 list / get / apply / create。codex-rs/cloud-tasks-client/src/http.rs:25-63HttpClient,包裝 backend-client::Client 並實現 CloudBackendcodex-rs/backend-client/src/client.rs:451-482create_task 在 base client 裡的實現,從 JSON 裡摳 task id。codex-rs/cloud-tasks-mock-client/src/mock.rs:163-189MockClient 實現 trait,轉調內部 mock 資料生成函式。

工廠 init_backend 在 debug build 裡檢查環境變數,決定走 mock 還是真 HTTP:

rust
// 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 實現共享同一簽名:

rust
// 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):

rust
// 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}; ..."),
    }
}

資料流

邊界與失敗

小結

Cloud Tasks 把「提交、查詢、應用」三步壓成 CloudBackend trait,HTTP 與 mock 都實現同一份 trait,方便 TUI 在本地無後端時除錯。底層 HTTP 重用 backend-client::Client,跟 Model Provider 共享鑑權與路徑風格判定。雲端任務自身的配置(允許的 environment、enterprise 策略)由 配置系統 裡的 cloud config bundle 提供,啟動時拉取並應用。