Skip to content

Commit 982ddfc

Browse files
jmoseleyCopilot
andcommitted
rust: add isExperimentalMode to session create/resume wire
Add an optional `is_experimental_mode` field to `SessionConfig` and `ResumeSessionConfig` (plus `with_is_experimental_mode` builders) that serializes as camelCase `isExperimentalMode` and is omitted from the `session.create` / `session.resume` wire when `None`. Lets a consumer disable (`false`) or force-enable (`true`) the experimental feature-flag tier for a single session without persisting to the user's shared config. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 7d6bc92 commit 982ddfc

2 files changed

Lines changed: 111 additions & 0 deletions

File tree

rust/src/types.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,20 @@ pub struct SessionConfig {
12461246
///
12471247
/// Defaults to `None` (treated as `false`).
12481248
pub enable_mcp_apps: Option<bool>,
1249+
/// Disable, force-enable, or inherit the experimental feature-flag tier
1250+
/// for this session only.
1251+
///
1252+
/// - `Some(false)` — the runtime re-resolves this session's feature flags
1253+
/// as if experimental mode were off, stripping experimental-tier flags.
1254+
/// - `Some(true)` — force-enables the experimental tier even if the CLI
1255+
/// process didn't start with it.
1256+
/// - `None` (default) — inherits the CLI process's flags unchanged.
1257+
///
1258+
/// This never persists anything to the user's shared config; it only
1259+
/// affects the feature-flag resolution for this one session. Serializes
1260+
/// as `isExperimentalMode` and is omitted from the wire when `None`, so
1261+
/// older CLIs that don't understand it are unaffected.
1262+
pub is_experimental_mode: Option<bool>,
12491263
/// Skill directory paths passed through to the GitHub Copilot CLI.
12501264
pub skill_directories: Option<Vec<PathBuf>>,
12511265
/// Additional directories to search for custom instruction files.
@@ -1418,6 +1432,7 @@ impl std::fmt::Debug for SessionConfig {
14181432
.field("enable_session_store", &self.enable_session_store)
14191433
.field("enable_skills", &self.enable_skills)
14201434
.field("enable_mcp_apps", &self.enable_mcp_apps)
1435+
.field("is_experimental_mode", &self.is_experimental_mode)
14211436
.field("skill_directories", &self.skill_directories)
14221437
.field("instruction_directories", &self.instruction_directories)
14231438
.field("plugin_directories", &self.plugin_directories)
@@ -1517,6 +1532,7 @@ impl Default for SessionConfig {
15171532
enable_skills: None,
15181533
embedding_cache_storage: None,
15191534
enable_mcp_apps: None,
1535+
is_experimental_mode: None,
15201536
skill_directories: None,
15211537
instruction_directories: None,
15221538
plugin_directories: None,
@@ -1659,6 +1675,7 @@ impl SessionConfig {
16591675
request_auto_mode_switch,
16601676
request_elicitation,
16611677
request_mcp_apps: self.enable_mcp_apps.unwrap_or(false),
1678+
is_experimental_mode: self.is_experimental_mode,
16621679
hooks: hooks_flag,
16631680
skill_directories: self.skill_directories,
16641681
instruction_directories: self.instruction_directories,
@@ -2003,6 +2020,14 @@ impl SessionConfig {
20032020
self
20042021
}
20052022

2023+
/// Disable (`false`) or force-enable (`true`) the experimental feature-flag
2024+
/// tier for this session only. `None` (default) inherits the CLI process's
2025+
/// flags. Never persists to config. See the field docs for resume caveats.
2026+
pub fn with_is_experimental_mode(mut self, is_experimental_mode: bool) -> Self {
2027+
self.is_experimental_mode = Some(is_experimental_mode);
2028+
self
2029+
}
2030+
20062031
/// Set skill directory paths passed through to the CLI.
20072032
pub fn with_skill_directories<I, P>(mut self, paths: I) -> Self
20082033
where
@@ -2252,6 +2277,20 @@ pub struct ResumeSessionConfig {
22522277
/// Enable MCP Apps (SEP-1865) UI passthrough on resume. See
22532278
/// [`SessionConfig::enable_mcp_apps`]. Defaults to `None` (treated as `false`).
22542279
pub enable_mcp_apps: Option<bool>,
2280+
/// Disable, force-enable, or inherit the experimental feature-flag tier
2281+
/// for this resumed session only.
2282+
///
2283+
/// - `Some(false)` — re-resolves this session's feature flags as if
2284+
/// experimental mode were off, stripping experimental-tier flags.
2285+
/// - `Some(true)` — force-enables the experimental tier even if the CLI
2286+
/// process didn't start with it.
2287+
/// - `None` (default) — inherits the CLI process's flags unchanged.
2288+
///
2289+
/// Never persists to config. Note: resume only re-resolves flags on the
2290+
/// cold-load path (the session is not already live in-process); an
2291+
/// already-active session keeps the flags it was created with. Serializes
2292+
/// as `isExperimentalMode` and is omitted from the wire when `None`.
2293+
pub is_experimental_mode: Option<bool>,
22552294
/// Skill directory paths passed through to the GitHub Copilot CLI on resume.
22562295
pub skill_directories: Option<Vec<PathBuf>>,
22572296
/// Additional directories to search for custom instruction files on
@@ -2395,6 +2434,7 @@ impl std::fmt::Debug for ResumeSessionConfig {
23952434
.field("enable_session_store", &self.enable_session_store)
23962435
.field("enable_skills", &self.enable_skills)
23972436
.field("enable_mcp_apps", &self.enable_mcp_apps)
2437+
.field("is_experimental_mode", &self.is_experimental_mode)
23982438
.field("skill_directories", &self.skill_directories)
23992439
.field("instruction_directories", &self.instruction_directories)
24002440
.field("plugin_directories", &self.plugin_directories)
@@ -2538,6 +2578,7 @@ impl ResumeSessionConfig {
25382578
request_auto_mode_switch,
25392579
request_elicitation,
25402580
request_mcp_apps: self.enable_mcp_apps.unwrap_or(false),
2581+
is_experimental_mode: self.is_experimental_mode,
25412582
hooks: hooks_flag,
25422583
skill_directories: self.skill_directories,
25432584
instruction_directories: self.instruction_directories,
@@ -2614,6 +2655,7 @@ impl ResumeSessionConfig {
26142655
enable_skills: None,
26152656
embedding_cache_storage: None,
26162657
enable_mcp_apps: None,
2658+
is_experimental_mode: None,
26172659
skill_directories: None,
26182660
instruction_directories: None,
26192661
plugin_directories: None,
@@ -2933,6 +2975,14 @@ impl ResumeSessionConfig {
29332975
self
29342976
}
29352977

2978+
/// Disable (`false`) or force-enable (`true`) the experimental feature-flag
2979+
/// tier for this session only. `None` (default) inherits the CLI process's
2980+
/// flags. Never persists to config. See the field docs for resume caveats.
2981+
pub fn with_is_experimental_mode(mut self, is_experimental_mode: bool) -> Self {
2982+
self.is_experimental_mode = Some(is_experimental_mode);
2983+
self
2984+
}
2985+
29362986
/// Set skill directory paths passed through to the CLI on resume.
29372987
pub fn with_skill_directories<I, P>(mut self, paths: I) -> Self
29382988
where
@@ -4453,6 +4503,63 @@ mod tests {
44534503
assert_eq!(json["requestMcpApps"], serde_json::Value::Bool(true));
44544504
}
44554505

4506+
#[test]
4507+
fn session_config_is_experimental_mode_serializes_when_set() {
4508+
let cfg = SessionConfig::default().with_is_experimental_mode(false);
4509+
assert_eq!(cfg.is_experimental_mode, Some(false));
4510+
4511+
let (wire, _runtime) = cfg
4512+
.into_wire(Some(SessionId::from("experimental-mode")))
4513+
.expect("is_experimental_mode config has no duplicate handlers");
4514+
assert_eq!(wire.is_experimental_mode, Some(false));
4515+
4516+
let json = serde_json::to_value(&wire).unwrap();
4517+
assert_eq!(json["isExperimentalMode"], serde_json::Value::Bool(false));
4518+
}
4519+
4520+
#[test]
4521+
fn session_config_is_experimental_mode_omitted_when_none() {
4522+
let cfg = SessionConfig::default();
4523+
assert_eq!(cfg.is_experimental_mode, None);
4524+
4525+
let (wire, _runtime) = cfg
4526+
.into_wire(Some(SessionId::from("no-experimental-mode")))
4527+
.expect("default config has no duplicate handlers");
4528+
assert_eq!(wire.is_experimental_mode, None);
4529+
4530+
let json = serde_json::to_value(&wire).unwrap();
4531+
assert!(json.get("isExperimentalMode").is_none());
4532+
}
4533+
4534+
#[test]
4535+
fn resume_session_config_is_experimental_mode_serializes_when_set() {
4536+
let cfg = ResumeSessionConfig::new(SessionId::from("resume-experimental-mode"))
4537+
.with_is_experimental_mode(false);
4538+
assert_eq!(cfg.is_experimental_mode, Some(false));
4539+
4540+
let (wire, _runtime) = cfg
4541+
.into_wire()
4542+
.expect("resume is_experimental_mode config has no duplicate handlers");
4543+
assert_eq!(wire.is_experimental_mode, Some(false));
4544+
4545+
let json = serde_json::to_value(&wire).unwrap();
4546+
assert_eq!(json["isExperimentalMode"], serde_json::Value::Bool(false));
4547+
}
4548+
4549+
#[test]
4550+
fn resume_session_config_is_experimental_mode_omitted_when_none() {
4551+
let cfg = ResumeSessionConfig::new(SessionId::from("resume-no-experimental-mode"));
4552+
assert_eq!(cfg.is_experimental_mode, None);
4553+
4554+
let (wire, _runtime) = cfg
4555+
.into_wire()
4556+
.expect("default resume config has no duplicate handlers");
4557+
assert_eq!(wire.is_experimental_mode, None);
4558+
4559+
let json = serde_json::to_value(&wire).unwrap();
4560+
assert!(json.get("isExperimentalMode").is_none());
4561+
}
4562+
44564563
#[test]
44574564
#[allow(clippy::field_reassign_with_default)]
44584565
fn session_config_into_wire_serializes_bucket_b_fields() {

rust/src/wire.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ pub(crate) struct SessionCreateWire {
147147
pub include_sub_agent_streaming_events: Option<bool>,
148148
#[serde(skip_serializing_if = "Option::is_none")]
149149
pub commands: Option<Vec<CommandWireDefinition>>,
150+
#[serde(skip_serializing_if = "Option::is_none")]
151+
pub is_experimental_mode: Option<bool>,
150152
}
151153

152154
/// The exact JSON shape sent on the `session.resume` JSON-RPC request.
@@ -257,4 +259,6 @@ pub(crate) struct SessionResumeWire {
257259
pub suppress_resume_event: Option<bool>,
258260
#[serde(skip_serializing_if = "Option::is_none")]
259261
pub continue_pending_work: Option<bool>,
262+
#[serde(skip_serializing_if = "Option::is_none")]
263+
pub is_experimental_mode: Option<bool>,
260264
}

0 commit comments

Comments
 (0)