all SDKs: add EnableExperimentalMode to session create/resume wire with mode-aware defaults#1600
all SDKs: add EnableExperimentalMode to session create/resume wire with mode-aware defaults#1600jmoseley wants to merge 6 commits into
Conversation
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>
There was a problem hiding this comment.
Pull request overview
Adds per-session control of the Copilot CLI experimental feature-flag tier to the Rust SDK by threading an optional is_experimental_mode: Option<bool> through the user-facing config types into the JSON-RPC session.create / session.resume wire payloads (camelCased as isExperimentalMode and omitted when None for backwards compatibility).
Changes:
- Add
is_experimental_mode: Option<bool>toSessionCreateWireandSessionResumeWirewithskip_serializing_if = "Option::is_none". - Add
is_experimental_mode: Option<bool>toSessionConfigandResumeSessionConfig, wire it through defaults/debug/into_wire, and addwith_is_experimental_mode(bool)builders. - Add unit tests asserting
isExperimentalModeserializes when set and is omitted whenNonefor both create and resume paths.
Show a summary per file
| File | Description |
|---|---|
| rust/src/wire.rs | Extends create/resume JSON wire structs to include optional isExperimentalMode when provided. |
| rust/src/types.rs | Exposes is_experimental_mode on public config types, maps it into wire payloads, and adds serialization/omission tests. |
Copilot's findings
- Files reviewed: 2/2 changed files
- Comments generated: 1
| /// Disable (`false`) or force-enable (`true`) the experimental feature-flag | ||
| /// tier for this session only. `None` (default) inherits the CLI process's | ||
| /// flags. Never persists to config. See the field docs for resume caveats. | ||
| pub fn with_is_experimental_mode(mut self, is_experimental_mode: bool) -> Self { |
|
We will want to add this to all 6 SDKs, not just Rust. |
|
@copilot Continue this PR by adding support for the other 5 languages in addition to Rust |
Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com>
Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com>
| IList<string>? ExcludedTools, | ||
| ProviderConfig? Provider, | ||
| bool? EnableSessionTelemetry, | ||
| bool? IsExperimentalMode, |
There was a problem hiding this comment.
@copilot Rename to EnableExperimentalMode and the equivalent across 6 languages. Obviously you can't change the name on the wire - I just mean rename it in the SDK's API surface.
There was a problem hiding this comment.
Addressed in 1e90676. Renamed the public SDK API surface to EnableExperimentalMode (language-appropriate casing) across Rust, Node.js, Python, Go, .NET, and Java while keeping the wire field name as isExperimentalMode.
| public bool? EnableSessionTelemetry { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Overrides the session's experimental feature-flag tier resolution. |
There was a problem hiding this comment.
Phrase as Controls whether the session enables experimental features.
Also make this respect the session's Mode:
- If mode is "copilot-cli", then it's OK to send
nullso that the runtime decides (e.g., based on feature flags or whether it's a staff user) - If the mode is "empty", then the SDK must send
falseunless the developer has explicitly opted in by setting this totrue
Do this consistently across all languages.
There was a problem hiding this comment.
Addressed in b0e6ce6. EnableExperimentalMode now uses mode-aware defaults across all six SDKs: empty mode sends false unless explicitly set to true, while copilot-cli mode still leaves it unset so the runtime can decide. The public docs were also updated to use the requested phrasing.
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Generated by SDK Consistency Review Agent for issue #1600 · sonnet46 1.5M
| commands: list[CommandDefinition] | None = None, | ||
| on_elicitation_request: ElicitationHandler | None = None, | ||
| enable_mcp_apps: bool = False, | ||
| is_experimental_mode: bool | None = None, |
There was a problem hiding this comment.
Minor parameter ordering inconsistency within Python: in create_session, is_experimental_mode is placed immediately after reasoning_summary (before context_tier and enable_mcp_apps). Here in resume_session it appears after both context_tier and enable_mcp_apps.
Since Python callers generally use keyword arguments this doesn't affect functionality, but aligning the order with create_session would make the two method signatures easier to compare side-by-side:
# Suggested: match create_session ordering
reasoning_summary: ReasoningSummary | None = None,
is_experimental_mode: bool | None = None, # ← move here
context_tier: ContextTier | None = None,
...
enable_mcp_apps: bool = False,| /// This never persists anything to the user's shared config; it only | ||
| /// affects the feature-flag resolution for this one session. Serializes | ||
| /// as `isExperimentalMode` and is omitted from the wire when `None`, so | ||
| /// older CLIs that don't understand it are unaffected. |
There was a problem hiding this comment.
@copilot Make all the doc comments consistent across languages. Right now they randomly vary in what details they give.
The doc comments should only state that the flag controls whether experimental features are enabled. You should not give more info about the concept of feature flags or whatever, as those are internal runtime implementation decisions. Don't mention persistence as that's off-topic (nobody ever suggested this flag was going to be persisted, so why raise the matter?). You can state that it defaults to false in empty mode, whereas in copilot-cli mode it defaults to letting the runtime decide (once you've implemented that).
Be consistent across languages.
There was a problem hiding this comment.
Addressed in b0e6ce6. I made the EnableExperimentalMode docs consistent across Rust, Node.js, Python, Go, .NET, and Java: they now just describe controlling whether the session enables experimental features and note the empty-mode vs copilot-cli default behavior.
Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com>
Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com>
|
@copilot Fix all the CI errors |
Cross-SDK Consistency Review ✅This PR adds What's consistent
Minor observation: Java has redundant defaulting logicIn // CopilotClient.java lines 554–556 (createSession) and 693–695 (resumeSession)
if (request.getIsExperimentalMode() == null) {
request.setIsExperimentalMode(false);
}...is dead code. The other null checks in that same block ( This doesn't affect correctness, but it may confuse future maintainers about where the canonical default logic lives. Consider removing the null check from This is a suggestion, not a blocker.
|
There was a problem hiding this comment.
Generated by SDK Consistency Review Agent for issue #1600 · sonnet46 1.8M
| } | ||
| if (request.getIsExperimentalMode() == null) { | ||
| request.setIsExperimentalMode(false); | ||
| } |
There was a problem hiding this comment.
This null-check is dead code. SessionRequestBuilder.buildCreateRequest(config, sessionId, mode) already calls experimentalModeForMode(EMPTY, null) which returns Optional.of(false) and sets isExperimentalMode = false via ifPresent — so getIsExperimentalMode() will never be null when execution reaches this point in empty mode.\n\nConsider removing this block (same applies to the equivalent block in resumeSession at ~line 694) and letting SessionRequestBuilder.experimentalModeForMode be the single source of truth for this field's mode-aware default.
Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com>
Addressed in a2a56cc. I fixed the Node.js and Python CI formatting failures, aligned Python |
Summary
Adds per-session
EnableExperimentalModesupport across all SDKs so a consumer can control whether a session enables experimental features.This pairs with a companion copilot-agent-runtime change that adds an optional
isExperimentalMode?: booleanfield to the classic--serverSessionCreateRequest/SessionResumeRequest.SDK semantics are now mode-aware and consistent across languages:
copilot-climode, the SDK leaves the field unset by default so the runtime decidesemptymode, the SDK sendsfalseby default unless the developer explicitly opts in withtrueThe motivating consumer is the GitHub desktop app, which spawns
copilot --serverand needs per-session control over the experimental tier.Changes
Rust
rust/src/wire.rs: Addis_experimental_mode: Option<bool>(withskip_serializing_if = "Option::is_none") toSessionCreateWireandSessionResumeWire. Serializes asisExperimentalModevia the existingrename_all = "camelCase".rust/src/types.rs:pub enable_experimental_mode: Option<bool>toSessionConfigandResumeSessionConfig.Debugimpls,Default/newconstructors, and bothinto_wiremappings.with_enable_experimental_mode(bool)builders to both.rust/src/mode.rs/rust/src/session.rs: Apply mode-aware defaulting so unset values becomefalseinClientMode::Emptyand remain unset inClientMode::CopilotCli.Node.js / TypeScript
enableExperimentalMode?: booleanto the public session config surface.session.createandsession.resumerequest payloads as wire fieldisExperimentalMode."empty"mode sendsfalsewhen unset, while"copilot-cli"leaves it unset.Python
enable_experimental_mode: bool | Nonetocreate_session/resume_session.isExperimentalModeinsession.createandsession.resume.mode="empty"sendsFalsewhen unset, whilemode="copilot-cli"leaves it unset.Go
EnableExperimentalMode *booltoSessionConfigandResumeSessionConfig.createSessionRequest/resumeSessionRequestwire structs and request building, keeping the wire field nameisExperimentalMode.applyConfigDefaultsForMode/applyResumeDefaultsForModeso unset values becomefalseonly inModeEmpty..NET
EnableExperimentalModetoSessionConfigBase.CreateSessionRequest/ResumeSessionRequestand clone logic, while keeping the wire field name unchanged.ApplyConfigDefaultsForModeso unset values becomefalseonly inCopilotClientMode.Empty.Java
enableExperimentalModesupport toSessionConfigandResumeSessionConfigwith getter/setter/clear methods and clone wiring.SessionRequestBuilderintoCreateSessionRequest/ResumeSessionRequest, keeping the wire field nameisExperimentalMode.SessionRequestBuildermode-aware so empty mode defaults unset values tofalse, while copilot-cli leaves them unset.Tests
Added or updated focused tests in each SDK to verify
EnableExperimentalModeis forwarded when set and that the default behavior is mode-aware while the wire payload still usesisExperimentalMode:None, plus mode-default helper testsSessionRequestBuilderTestcoverage for create/resume set, omitted, and empty-mode default casesValidation
cargo test experimental_modepasses locally withCOPILOT_SKIP_CLI_DOWNLOAD=1enableExperimentalModepassuv run pytest test_client.py -k "enable_experimental_mode"passesSerializationTestscoverage forEnableExperimentalModeand mode defaults passesmvn -Denforcer.skip=true test -Dtest=SessionRequestBuilderTestpasses locallyNo schema codegen needed for this change path.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>