Skip to content

V2请求分派总表

从 ClientRequest 到各业务 processor,建立 V2 方法、串行化、响应与副作用的源码地图。

基于rust-v0.150.0
CodexRustAppServerRequest

V2请求分派总表 ​

本文承接MessageProcessor读循环、Connection RPC Gate和AppServer V2方法注册机制,面向已经理解 typed request、connection state 和 serialization scope 的读者。本文回答一个 V2 request 从校验到具体 processor 的路径、方法如何按资源分组,以及 response/notification/副作用在哪里产生;不逐个展开每个业务 handler 的内部算法。

1. 分派入口 ​

所有已初始化 request 都进入 dispatch_initialized_client_request。这里完成未初始化检查、experimental capability 检查、request context 记录和 serialization scope 选择,然后才进入大型 match。

源码位置:codex-rs/app-server/src/message_processor.rs :: dispatch_initialized_client_request

rust
if !session.initialized() {
    return Err(invalid_request("Not initialized"));
}
if let Some(reason) = codex_request.experimental_reason()
    && !session.experimental_api_enabled()
{
    return Err(invalid_request(experimental_required_message(reason)));
}
let serialization_scope = codex_request.serialization_scope();

这段顺序意味着 handler 不会看到未初始化请求,实验请求也不会在 capability 缺失时产生业务副作用。

2. Thread与Turn ​

Thread processor 负责 thread start/resume/fork/read/list、section、name、goal、rollback、background terminals 等 Thread 资源;Turn processor 负责 turn start/steer/interrupt、realtime 和 review。二者都可能触发 Core Op 和异步 notification,但资源 owner 不同。

源码位置:codex-rs/app-server/src/message_processor.rs :: ClientRequest::ThreadStart、ClientRequest::TurnStart

rust
ClientRequest::ThreadStart { params, .. } => {
    self.thread_processor
        .thread_start(
            request_id.clone(),
            params,
            app_server_client_name.clone(),
            client_version.clone(),
            client_mcp_extensions.clone(),
            request_context,
        )
        .await
}
ClientRequest::TurnStart { params, .. } => {
    self.turn_processor
        .turn_start(
            request_id.clone(),
            params,
            app_server_client_name.clone(),
            client_version.clone(),
        )
        .await
}

Thread request 通常返回 Thread view;Turn request 返回接纳结果并通过后续事件报告进度。不要用“都属于会话 API”替代这两个 owner 的区别。

3. 配置与目录 ​

Config、Environment、FS、Model catalog、Plugin、Apps、MCP、Account、Search、Command/Process 各自有独立 processor。大型 match 的价值不在于把所有 variant 写成一张静态清单,而在于揭示每个方法的 owner、serialization key 和返回模式。

方法族Processor典型 scope主要结果
config/*、environment/*Config/Environmentglobal 或 threadtyped response / error
thread/*Threadthread IDThread response / notifications
turn/*、review/startTurnthread IDTurn response + events
fs/*、process/*FS/Processwatch/process handleresponse + stream notifications
plugin/*、apps/*、mcp/*Plugin/Apps/MCPglobal、thread 或 servercatalog/status/tool response
account/*、model/listAccount/Catalogglobalsnapshot / notification
command/exec、fuzzyFileSearchCommand/Searchprocess/session keyresponse + output events

源码位置:codex-rs/app-server-protocol/src/protocol/common.rs :: ClientRequest 生成宏

rust
pub enum ClientRequest {
    $(
        #[serde(rename = $wire)]
        $variant {
            #[serde(rename = "id")]
            request_id: RequestId,
            params: $params,
        },
    )*
}

impl ClientRequest {
    pub fn id(&self) -> &RequestId {
        match self {
            $(Self::$variant { request_id, .. } => request_id,)*
        }
    }

    pub const fn method_name(&self) -> &'static str {
        match self {
            $(Self::$variant { .. } => $wire,)*
        }
    }

    pub fn serialization_scope(&self) -> Option<ClientRequestSerializationScope> {
        match self {
            $(Self::$variant { params, .. } => {
                let _ = params;
                serialization_scope_expr!(params, $serialization $(($($serialization_args)*))?)
            },)*
        }
    }
}

协议宏同时生成 wire method、typed params、request id 访问器和 serialization scope。运行时 match 不是重新解析字符串,而是对这个 typed enum 做穷尽匹配;新增方法必须同时决定 response 类型和资源 scope。

4. 资源串行化 ​

ClientRequest::serialization_scope() 为需要保护共享资源的方法返回 scope;MessageProcessor 将其转换为 RequestSerializationQueueKey。没有 scope 的请求直接 spawn,但仍经过 connection RPC gate。

5. 结果统一出口 ​

源码位置:codex-rs/app-server/src/message_processor.rs :: handle_initialized_client_request 的代表性分支

rust
let result: Result<Option<ClientResponsePayload>, JSONRPCErrorError> = match codex_request {
    ClientRequest::ConfigRead { params, .. } => self
        .config_processor
        .read(params)
        .await
        .map(|response| Some(response.into())),
    ClientRequest::AppsRead { params, .. } => self.apps_processor.apps_read(params).await,
    ClientRequest::AppsInstalled { params, .. } => self
        .apps_processor
        .apps_installed(params)
        .await
        .map(|response| Some(response.into())),
    ClientRequest::McpServerStatusList { params, .. } => self
        .mcp_processor
        .mcp_server_status_list(&request_id, params)
        .await,
    ClientRequest::GetAccount { params, .. } => self.account_processor.get_account(params).await,
    ClientRequest::OneOffCommandExec { params, .. } => self
        .command_exec_processor
        .one_off_command_exec(&request_id, params)
        .await,
    ClientRequest::ProcessSpawn { params, .. } => self
        .process_exec_processor
        .process_spawn(request_id.clone(), params)
        .await
        .map(|()| None),
    ClientRequest::FeedbackUpload { params, .. } => {
        self.feedback_processor.feedback_upload(params).await
    }
};

同一个 match 中既有直接返回 response 的读取方法,也有返回 None、把结果交给后续 notification 或 server request 的异步方法。process/spawn 明确映射为 None,因为它的最终状态走进程通知;config/read 和 account/read 则把 processor response 包成 Some。

源码位置:codex-rs/app-server/src/message_processor.rs :: handle_initialized_client_request

rust
match result {
    Ok(Some(response)) => {
        self.outgoing.send_response_as(request_id.clone(), response).await;
    }
    Ok(None) => {}
    Err(error) => {
        self.outgoing.send_error(request_id.clone(), error).await;
    }
}

processor 返回 Some(response) 才会生成 JSON-RPC response;None 常用于异步事件驱动的方法;错误统一进入 send_error。因此“handler 返回成功”不一定意味着客户端已收到最终业务结果。

6. 反向请求 ​

源码位置:codex-rs/app-server/src/message_processor.rs :: ClientRequest::McpServerEventStreamStart、ClientRequest::ThreadRealtimeStart、ClientRequest::ReviewStart

rust
ClientRequest::McpServerEventStreamStart { params, .. } => {
    let ready = event_stream_ready.ok_or_else(|| {
        internal_error("MCP event subscription was not reserved before startup")
    })?;
    session
        .mcp_event_streams
        .wait_for_activation(&params.subscription_id, ready)
        .await?;
    Ok(Some(
        McpServerEventStreamStartResponse {}.into(),
    ))
}
ClientRequest::ThreadRealtimeStart { params, .. } => self
    .turn_processor
    .thread_realtime_start(&request_id, params)
    .await,
ClientRequest::ReviewStart { params, .. } => self
    .turn_processor
    .review_start(&request_id, params)
    .await,

MCP event stream 在进入 handler 前就由 session 预留 ready receiver;realtime 和 review 则交给 Turn processor。它们都属于 thread 相关能力,但启动握手、scope 和 response 时机并不相同。

Dynamic tool、permissions、MCP elicitation 等 processor 分支会通过 OutgoingMessageSender::send_request 发 server request,等待客户端 response;它们在方法分派表中看似返回 None,实际工作由异步 callback 完成。

7. 失败与未知方法 ​

JSON decode 失败发生在 transport;未初始化和实验 capability 失败发生在 dispatch 前;参数错误、资源不存在和 handler 错误由具体 processor 返回;未知 connection 的消息在主 loop 被丢弃。错误阶段不同,重试位置也不同。

8. 源码验证 ​

协议宏测试验证 ClientRequest 方法名、params 和 serialization scope;MessageProcessor 测试验证代表性 Thread/Turn/FS/Account 分支路由;outgoing 测试验证 Some/None/error 三种结果出口。它们证明分派结构和响应关系,不证明每个 handler 的业务副作用。

源码位置:

  • codex-rs/app-server-protocol/src/protocol/common.rs :: client_request_definitions
  • codex-rs/app-server/src/message_processor.rs :: handle_initialized_client_request
  • codex-rs/app-server/src/outgoing_message.rs :: send_response_clears_registered_request_context
text
cd codex-rs
cargo test -p codex-app-server-protocol client_request
cargo test -p codex-app-server message_processor
cargo test -p codex-app-server send_response_clears_registered_request_context

9. 分派排查 ​

遇到方法找不到,先检查 protocol macro 是否生成 variant;遇到方法不执行,检查 initialized/experimental gate 和 serialization queue;遇到没有 response,判断 handler 是 None 异步路径还是 error;遇到并发顺序异常,检查 scope key/access 而不是只看 match 分支。