Skip to content

Experimental API边界

解释 experimental derive、capability gate、notification 过滤和 stable schema 导出的完整边界。

基于rust-v0.150.0
CodexRustAppServerExperimental

Experimental API边界 ​

本文承接SchemaFixture与兼容测试和AppServer V2方法注册机制,面向已经理解方法宏、runtime gate 和 schema export 的读者。本文回答实验 method、variant、field 和 nested value 如何被识别、拒绝或过滤,以及稳定化需要同步改变哪些层;不讨论产品层是否应开放某项实验功能。

1. 四层门控 ​

实验接口同时受类型标记、request runtime gate、outbound notification gate 和 stable artifact filter 约束。只修改其中一层会造成客户端契约与运行行为不一致。

2. Reason模型 ​

源码位置:codex-rs/app-server-protocol/src/experimental_api.rs :: ExperimentalApi、ExperimentalField

rust
pub trait ExperimentalApi {
    fn experimental_reason(&self) -> Option<&'static str>;
}

pub struct ExperimentalField {
    pub type_name: &'static str,
    pub field_name: &'static str,
    pub reason: &'static str,
}

pub fn experimental_required_message(reason: &str) -> String {
    format!("{reason} requires experimentalApi capability")
}

pub fn experimental_fields() -> Vec<&'static ExperimentalField> {
    inventory::iter::<ExperimentalField>.into_iter().collect()
}

reason 是稳定的诊断标识,通常为 method 或 method.field。它同时服务 runtime error 和 export 过滤注册。

3. 嵌套传播 ​

源码位置:codex-rs/app-server-protocol/src/experimental_api.rs :: ExperimentalApi for Option/Vec/Map

rust
impl<T: ExperimentalApi> ExperimentalApi for Option<T> {
    fn experimental_reason(&self) -> Option<&'static str> {
        self.as_ref().and_then(ExperimentalApi::experimental_reason)
    }
}

impl<T: ExperimentalApi> ExperimentalApi for Vec<T> {
    fn experimental_reason(&self) -> Option<&'static str> {
        self.iter().find_map(ExperimentalApi::experimental_reason)
    }
}

impl<K, V: ExperimentalApi, S> ExperimentalApi for HashMap<K, V, S> {
    fn experimental_reason(&self) -> Option<&'static str> {
        self.values().find_map(ExperimentalApi::experimental_reason)
    }
}

嵌套 field、collection 和 map 会传播第一个实验 reason。空 collection 没有 reason;带 Some(empty vec) 的直接实验字段仍可被 derive 标记为实验使用。

4. Request拒绝 ​

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

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

request 已成功反序列化,但在业务 handler 前被拒绝,因此不会产生 method 副作用。客户端 capability 是 connection/request context,而不是全局 feature flag。

5. Notification过滤 ​

源码位置:codex-rs/app-server/src/transport.rs :: should_skip_notification_for_connection

rust
fn should_skip_notification_for_connection(
    connection_state: &OutboundConnectionState,
    message: &OutgoingMessage,
) -> bool {
    match message {
        OutgoingMessage::AppServerNotification(envelope) => {
            envelope.notification.experimental_reason().is_some()
                && !connection_state
                    .experimental_api_enabled
                    .load(Ordering::Acquire)
        }
        _ => false,
    }
}

实验 notification 在 transport 层按连接能力抑制;server 内部仍可能产生事件。缺少 notification 不代表 core 状态没有变化。

同一层还会对实验审批字段做出站裁剪:filter_outgoing_message_for_connection 在 capability 缺失时调用 strip_experimental_fields,所以 notification 的“整条消息丢弃”和 request 的“保留主体、移除实验字段”是两种不同策略。

6. Stable导出 ​

源码位置:codex-rs/app-server-protocol/src/export.rs :: filter_experimental_ts、filter_experimental_schema

rust
filter_request_ts(out_dir, "ClientRequest.ts", EXPERIMENTAL_CLIENT_METHODS)?;
filter_experimental_type_fields_ts(out_dir, &registered_fields)?;
remove_generated_type_files(out_dir, &experimental_method_types, "ts")?;

filter_experimental_fields_in_root(bundle, &registered_fields);
prune_experimental_methods(bundle, EXPERIMENTAL_CLIENT_METHODS);

stable artifact 删除 union arm、字段和专属类型文件;experimental artifact 保留全部定义。stable 客户端在类型层根本看不到这些接口。

7. 稳定化路径 ​

一个接口稳定化需要移除 attribute/reason、从实验 method/type 表退出、更新 stable fixture,并确认 runtime 不再要求 capability。仅把 schema 放进 stable exports 而保留 runtime gate,会制造“能编译但调用被拒绝”的接口;这条失败路径必须在稳定化前清理。

8. 源码验证 ​

derive 测试覆盖 enum unit/tuple/named、nested option/vec/map 和 optional collection;common protocol 测试验证 method/field reason;message processor 测试验证 capability 缺失时拒绝;fixture 测试验证 stable/experimental 输出差异。它们证明门控一致性,不证明实验接口本身稳定或适合公开。

源码位置:

  • codex-rs/app-server-protocol/src/experimental_api.rs :: derive_supports_*
  • codex-rs/app-server-protocol/src/protocol/common.rs :: experimental_reason tests
  • codex-rs/app-server/src/message_processor.rs :: experimentalApi gate tests
  • codex-rs/app-server-protocol/src/schema_fixtures_tests.rs :: experimental_precomputed_exports_match_generated
text
cd codex-rs
cargo test -p codex-app-server-protocol experimental_api
cargo test -p codex-app-server experimentalApi
cargo test -p codex-app-server-protocol experimental_precomputed_exports

源码位置:codex-rs/app-server/src/request_processors/initialize_processor.rs :: initialize

rust
let capabilities = params.capabilities.unwrap_or_default();
let experimental_api_enabled = capabilities.experimental_api;
let opt_out_notification_methods = capabilities
    .opt_out_notification_methods
    .unwrap_or_default();

session.initialize(InitializedConnectionSessionState {
    experimental_api_enabled,
    opted_out_notification_methods: opt_out_notification_methods.into_iter().collect(),
    app_server_client_name: name.clone(),
    client_version: version,
    request_attestation,
    client_mcp_extensions,
})?;

能力是在 initialize 时写入连接 session,而不是每个请求临时猜测。这个状态同时被 request gate 和 outbound transport 使用;因此同一条连接初始化后,实验请求和实验通知会遵循同一个 capability 快照。

9. 门控排查 ​

遇到类型里没有接口,检查 stable export 过滤;请求被拒绝时查看 reason 与 connection capability;状态变化但没 notification 时检查 transport gate;稳定化后仍要求 capability,则逐层检查 marker、method 表、runtime 和 fixture 是否同步更新。