feat: experimental menu (#8071)

This will automatically render any `Stage::Beta` features.

The change only gets applied to the *next session*. This started as a
bug but actually this is a good thing to prevent out of distribution
push

<img width="986" height="288" alt="Screenshot 2025-12-15 at 15 38 35"
src="https://github.com/user-attachments/assets/78b7a71d-0e43-4828-a118-91c5237909c7"
/>


<img width="509" height="109" alt="Screenshot 2025-12-15 at 17 35 44"
src="https://github.com/user-attachments/assets/6933de52-9b66-4abf-b58b-a5f26d5747e2"
/>
This commit is contained in:
jif-oai
2025-12-17 17:08:03 +00:00
committed by GitHub
Unverified
parent 9352c6b235
commit ac6ba286aa
15 changed files with 577 additions and 38 deletions
+23
View File
@@ -45,6 +45,7 @@ use crate::config::Config;
use crate::default_client::build_reqwest_client;
use crate::error::CodexErr;
use crate::error::Result;
use crate::features::FEATURES;
use crate::flags::CODEX_RS_SSE_FIXTURE;
use crate::model_provider_info::ModelProviderInfo;
use crate::model_provider_info::WireApi;
@@ -261,6 +262,7 @@ impl ModelClient {
store_override: None,
conversation_id: Some(conversation_id.clone()),
session_source: Some(session_source.clone()),
extra_headers: beta_feature_headers(&self.config),
};
let stream_result = client
@@ -396,6 +398,27 @@ fn build_api_prompt(prompt: &Prompt, instructions: String, tools_json: Vec<Value
}
}
fn beta_feature_headers(config: &Config) -> ApiHeaderMap {
let enabled = FEATURES
.iter()
.filter_map(|spec| {
if spec.stage.beta_menu_description().is_some() && config.features.enabled(spec.id) {
Some(spec.key)
} else {
None
}
})
.collect::<Vec<_>>();
let value = enabled.join(",");
let mut headers = ApiHeaderMap::new();
if !value.is_empty()
&& let Ok(header_value) = HeaderValue::from_str(value.as_str())
{
headers.insert("x-codex-beta-features", header_value);
}
headers
}
fn map_response_stream<S>(api_stream: S, otel_manager: OtelManager) -> ResponseStream
where
S: futures::Stream<Item = std::result::Result<ResponseEvent, ApiError>>
+44 -16
View File
@@ -18,12 +18,33 @@ pub(crate) use legacy::LegacyFeatureToggles;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Stage {
Experimental,
Beta,
Beta {
menu_description: &'static str,
announcement: &'static str,
},
Stable,
Deprecated,
Removed,
}
impl Stage {
pub fn beta_menu_description(self) -> Option<&'static str> {
match self {
Stage::Beta {
menu_description, ..
} => Some(menu_description),
_ => None,
}
}
pub fn beta_announcement(self) -> Option<&'static str> {
match self {
Stage::Beta { announcement, .. } => Some(announcement),
_ => None,
}
}
}
/// Unique features toggled via configuration.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Feature {
@@ -292,13 +313,32 @@ pub const FEATURES: &[FeatureSpec] = &[
stage: Stage::Stable,
default_enabled: true,
},
// Unstable features.
FeatureSpec {
id: Feature::WebSearchRequest,
key: "web_search_request",
stage: Stage::Stable,
default_enabled: false,
},
// Beta program. Rendered in the `/experimental` menu for users.
FeatureSpec {
id: Feature::Skills,
key: "skills",
stage: Stage::Beta {
menu_description: "Define new `skills` for the model",
announcement: "NEW! Try the new `skills` features. Enable in /experimental!",
},
default_enabled: false,
},
FeatureSpec {
id: Feature::UnifiedExec,
key: "unified_exec",
stage: Stage::Experimental,
stage: Stage::Beta {
menu_description: "Run long-running terminal commands in the background.",
announcement: "NEW! Try Background terminals for long running processes. Enable in /experimental!",
},
default_enabled: false,
},
// Unstable features.
FeatureSpec {
id: Feature::RmcpClient,
key: "rmcp_client",
@@ -308,13 +348,7 @@ pub const FEATURES: &[FeatureSpec] = &[
FeatureSpec {
id: Feature::ApplyPatchFreeform,
key: "apply_patch_freeform",
stage: Stage::Beta,
default_enabled: false,
},
FeatureSpec {
id: Feature::WebSearchRequest,
key: "web_search_request",
stage: Stage::Stable,
stage: Stage::Experimental,
default_enabled: false,
},
FeatureSpec {
@@ -347,12 +381,6 @@ pub const FEATURES: &[FeatureSpec] = &[
stage: Stage::Experimental,
default_enabled: false,
},
FeatureSpec {
id: Feature::Skills,
key: "skills",
stage: Stage::Experimental,
default_enabled: false,
},
FeatureSpec {
id: Feature::ShellSnapshot,
key: "shell_snapshot",