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 提供,启动时拉取并应用。