Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance
toolFilter.ExcludedTools,
config.Provider,
config.EnableSessionTelemetry,
config.IsExperimentalMode,
config.OnPermissionRequest != null ? true : null,
config.OnUserInputRequest != null ? true : null,
config.OnExitPlanModeRequest != null ? true : null,
Expand Down Expand Up @@ -1091,6 +1092,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
toolFilter.ExcludedTools,
config.Provider,
config.EnableSessionTelemetry,
config.IsExperimentalMode,
config.OnPermissionRequest != null ? true : null,
config.OnUserInputRequest != null ? true : null,
config.OnExitPlanModeRequest != null ? true : null,
Expand Down Expand Up @@ -2283,6 +2285,7 @@ internal record CreateSessionRequest(
IList<string>? ExcludedTools,
ProviderConfig? Provider,
bool? EnableSessionTelemetry,
bool? IsExperimentalMode,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

bool? RequestPermission,
bool? RequestUserInput,
bool? RequestExitPlanMode,
Expand Down Expand Up @@ -2369,6 +2372,7 @@ internal record ResumeSessionRequest(
IList<string>? ExcludedTools,
ProviderConfig? Provider,
bool? EnableSessionTelemetry,
bool? IsExperimentalMode,
bool? RequestPermission,
bool? RequestUserInput,
bool? RequestExitPlanMode,
Expand Down
12 changes: 12 additions & 0 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2474,6 +2474,7 @@ protected SessionConfigBase(SessionConfigBase? other)
OnUserInputRequest = other.OnUserInputRequest;
Provider = other.Provider;
EnableSessionTelemetry = other.EnableSessionTelemetry;
IsExperimentalMode = other.IsExperimentalMode;
SkipCustomInstructions = other.SkipCustomInstructions;
CustomAgentsLocalOnly = other.CustomAgentsLocalOnly;
CoauthorEnabled = other.CoauthorEnabled;
Expand Down Expand Up @@ -2639,6 +2640,17 @@ protected SessionConfigBase(SessionConfigBase? other)
/// </summary>
public bool? EnableSessionTelemetry { get; set; }

/// <summary>
/// Overrides the session's experimental feature-flag tier resolution.

@SteveSandersonMS SteveSandersonMS Jun 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 null so 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 false unless the developer has explicitly opted in by setting this to true

Do this consistently across all languages.

@SteveSandersonMS SteveSandersonMS Jun 12, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @copilot

(sorry @jmoseley if this sounded like I was giving you orders!)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

/// </summary>
/// <remarks>
/// Set to <see langword="true"/> to force-enable the experimental tier for this
/// session, or <see langword="false"/> to resolve feature flags as if
/// experimental were off. Leave <see langword="null"/> to inherit the runtime
/// process defaults unchanged.
/// </remarks>
public bool? IsExperimentalMode { get; set; }

/// <summary>
/// When <see langword="true"/>, suppresses loading of custom instruction files
/// (e.g. <c>.github/copilot-instructions.md</c>, <c>AGENTS.md</c>) from the working directory.
Expand Down
35 changes: 35 additions & 0 deletions dotnet/test/Unit/CloneTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
WorkingDirectory = "/workspace",
Streaming = true,
EnableSessionTelemetry = false,
IsExperimentalMode = true,
EnableOnDemandInstructionDiscovery = true,
IncludeSubAgentStreamingEvents = false,
McpServers = new Dictionary<string, McpServerConfig> { ["server1"] = new McpStdioServerConfig { Command = "echo" } },
Expand Down Expand Up @@ -115,6 +116,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
Assert.Equal(original.WorkingDirectory, clone.WorkingDirectory);
Assert.Equal(original.Streaming, clone.Streaming);
Assert.Equal(original.EnableSessionTelemetry, clone.EnableSessionTelemetry);
Assert.Equal(original.IsExperimentalMode, clone.IsExperimentalMode);
Assert.Equal(original.EnableOnDemandInstructionDiscovery, clone.EnableOnDemandInstructionDiscovery);
Assert.Equal(original.IncludeSubAgentStreamingEvents, clone.IncludeSubAgentStreamingEvents);
Assert.Equal(original.McpServers.Count, clone.McpServers!.Count);
Expand Down Expand Up @@ -355,6 +357,19 @@ public void ResumeSessionConfig_Clone_CopiesEnableSessionTelemetry()
Assert.False(clone.EnableSessionTelemetry);
}

[Fact]
public void ResumeSessionConfig_Clone_CopiesIsExperimentalMode()
{
var original = new ResumeSessionConfig
{
IsExperimentalMode = true,
};

var clone = original.Clone();

Assert.True(clone.IsExperimentalMode);
}

[Fact]
public void ResumeSessionConfig_Clone_CopiesContinuePendingWork()
{
Expand Down Expand Up @@ -440,6 +455,26 @@ public void ResumeSessionConfig_Clone_PreservesEnableSessionTelemetryDefault()
Assert.Null(clone.EnableSessionTelemetry);
}

[Fact]
public void SessionConfig_Clone_PreservesIsExperimentalModeDefault()
{
var original = new SessionConfig();

var clone = original.Clone();

Assert.Null(clone.IsExperimentalMode);
}

[Fact]
public void ResumeSessionConfig_Clone_PreservesIsExperimentalModeDefault()
{
var original = new ResumeSessionConfig();

var clone = original.Clone();

Assert.Null(clone.IsExperimentalMode);
}

[Fact]
public void SessionConfig_Clone_CopiesEnableOnDemandInstructionDiscovery()
{
Expand Down
34 changes: 34 additions & 0 deletions dotnet/test/Unit/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,40 @@ public void ResumeSessionRequest_CanSerializeEnableSessionTelemetry_WithSdkOptio
Assert.False(root.GetProperty("enableSessionTelemetry").GetBoolean());
}

[Fact]
public void SessionRequests_CanSerializeIsExperimentalMode_WithSdkOptions()
{
var options = GetSerializerOptions();

var createRequestType = GetNestedType(typeof(CopilotClient), "CreateSessionRequest");
var createRequest = CreateInternalRequest(
createRequestType,
("SessionId", "session-id"),
("IsExperimentalMode", false));
var createRoot = JsonDocument.Parse(JsonSerializer.Serialize(createRequest, createRequestType, options)).RootElement;
Assert.False(createRoot.GetProperty("isExperimentalMode").GetBoolean());

var createRequestOmitted = CreateInternalRequest(
createRequestType,
("SessionId", "session-id"));
var createOmittedRoot = JsonDocument.Parse(JsonSerializer.Serialize(createRequestOmitted, createRequestType, options)).RootElement;
Assert.False(createOmittedRoot.TryGetProperty("isExperimentalMode", out _));

var resumeRequestType = GetNestedType(typeof(CopilotClient), "ResumeSessionRequest");
var resumeRequest = CreateInternalRequest(
resumeRequestType,
("SessionId", "session-id"),
("IsExperimentalMode", true));
var resumeRoot = JsonDocument.Parse(JsonSerializer.Serialize(resumeRequest, resumeRequestType, options)).RootElement;
Assert.True(resumeRoot.GetProperty("isExperimentalMode").GetBoolean());

var resumeRequestOmitted = CreateInternalRequest(
resumeRequestType,
("SessionId", "session-id"));
var resumeOmittedRoot = JsonDocument.Parse(JsonSerializer.Serialize(resumeRequestOmitted, resumeRequestType, options)).RootElement;
Assert.False(resumeOmittedRoot.TryGetProperty("isExperimentalMode", out _));
}

[Fact]
public void CreateSessionRequest_CanSerializeEnableOnDemandInstructionDiscovery_WithSdkOptions()
{
Expand Down
2 changes: 2 additions & 0 deletions go/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
req.ToolFilterPrecedence = precedence
req.Provider = config.Provider
req.EnableSessionTelemetry = config.EnableSessionTelemetry
req.IsExperimentalMode = config.IsExperimentalMode
req.SkipCustomInstructions = config.SkipCustomInstructions
req.CustomAgentsLocalOnly = config.CustomAgentsLocalOnly
req.CoauthorEnabled = config.CoauthorEnabled
Expand Down Expand Up @@ -923,6 +924,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
req.Tools = config.Tools
req.Provider = config.Provider
req.EnableSessionTelemetry = config.EnableSessionTelemetry
req.IsExperimentalMode = config.IsExperimentalMode
req.SkipCustomInstructions = config.SkipCustomInstructions
req.CustomAgentsLocalOnly = config.CustomAgentsLocalOnly
req.CoauthorEnabled = config.CoauthorEnabled
Expand Down
57 changes: 57 additions & 0 deletions go/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,63 @@ func TestCreateSessionRequest_RequestMCPApps(t *testing.T) {
})
}

func TestSessionRequests_IsExperimentalMode(t *testing.T) {
t.Run("create forwards isExperimentalMode when explicitly false", func(t *testing.T) {
req := createSessionRequest{
IsExperimentalMode: Bool(false),
}
data, err := json.Marshal(req)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var m map[string]any
if err := json.Unmarshal(data, &m); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
if m["isExperimentalMode"] != false {
t.Errorf("Expected isExperimentalMode to be false, got %v", m["isExperimentalMode"])
}
})

t.Run("create omits isExperimentalMode when unset", func(t *testing.T) {
req := createSessionRequest{}
data, _ := json.Marshal(req)
var m map[string]any
json.Unmarshal(data, &m)
if _, ok := m["isExperimentalMode"]; ok {
t.Error("Expected isExperimentalMode to be omitted when not set")
}
})

t.Run("resume forwards isExperimentalMode when explicitly true", func(t *testing.T) {
req := resumeSessionRequest{
SessionID: "s1",
IsExperimentalMode: Bool(true),
}
data, err := json.Marshal(req)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var m map[string]any
if err := json.Unmarshal(data, &m); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
if m["isExperimentalMode"] != true {
t.Errorf("Expected isExperimentalMode to be true, got %v", m["isExperimentalMode"])
}
})

t.Run("resume omits isExperimentalMode when unset", func(t *testing.T) {
req := resumeSessionRequest{SessionID: "s1"}
data, _ := json.Marshal(req)
var m map[string]any
json.Unmarshal(data, &m)
if _, ok := m["isExperimentalMode"]; ok {
t.Error("Expected isExperimentalMode to be omitted when not set")
}
})
}

func TestResumeSessionRequest_RequestMCPApps(t *testing.T) {
t.Run("sends requestMcpApps flag when EnableMCPApps is set", func(t *testing.T) {
req := resumeSessionRequest{
Expand Down
14 changes: 14 additions & 0 deletions go/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,12 @@ type SessionConfig struct {
// regardless of this setting. This is independent of the OpenTelemetry
// configuration in ClientOptions.Telemetry.
EnableSessionTelemetry *bool
// IsExperimentalMode, when non-nil, overrides the session's experimental
// feature-flag tier resolution. Use Bool(true) to force-enable the
// experimental tier for this session, Bool(false) to resolve feature flags
// as if experimental were off, or nil to inherit the runtime process
// defaults unchanged.
IsExperimentalMode *bool
// SkipCustomInstructions, when non-nil, controls whether the runtime loads
// custom instruction files. See also [ClientOptions.Mode] = [ModeEmpty].
SkipCustomInstructions *bool
Expand Down Expand Up @@ -1295,6 +1301,12 @@ type ResumeSessionConfig struct {
// regardless of this setting. This is independent of the OpenTelemetry
// configuration in ClientOptions.Telemetry.
EnableSessionTelemetry *bool
// IsExperimentalMode, when non-nil, overrides the resumed session's
// experimental feature-flag tier resolution. Use Bool(true) to force-enable
// the experimental tier for this session, Bool(false) to resolve feature
// flags as if experimental were off, or nil to inherit the runtime process
// defaults unchanged.
IsExperimentalMode *bool
// SkipCustomInstructions, when non-nil, controls whether the runtime loads
// custom instruction files. See also [ClientOptions.Mode] = [ModeEmpty].
SkipCustomInstructions *bool
Expand Down Expand Up @@ -1690,6 +1702,7 @@ type createSessionRequest struct {
ToolFilterPrecedence *rpc.OptionsUpdateToolFilterPrecedence `json:"toolFilterPrecedence,omitempty"`
Provider *ProviderConfig `json:"provider,omitempty"`
EnableSessionTelemetry *bool `json:"enableSessionTelemetry,omitempty"`
IsExperimentalMode *bool `json:"isExperimentalMode,omitempty"`
SkipCustomInstructions *bool `json:"skipCustomInstructions,omitempty"`
CustomAgentsLocalOnly *bool `json:"customAgentsLocalOnly,omitempty"`
CoauthorEnabled *bool `json:"coauthorEnabled,omitempty"`
Expand Down Expand Up @@ -1768,6 +1781,7 @@ type resumeSessionRequest struct {
ToolFilterPrecedence *rpc.OptionsUpdateToolFilterPrecedence `json:"toolFilterPrecedence,omitempty"`
Provider *ProviderConfig `json:"provider,omitempty"`
EnableSessionTelemetry *bool `json:"enableSessionTelemetry,omitempty"`
IsExperimentalMode *bool `json:"isExperimentalMode,omitempty"`
SkipCustomInstructions *bool `json:"skipCustomInstructions,omitempty"`
CustomAgentsLocalOnly *bool `json:"customAgentsLocalOnly,omitempty"`
CoauthorEnabled *bool `json:"coauthorEnabled,omitempty"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess
request.setExcludedTools(config.getExcludedTools());
request.setProvider(config.getProvider());
config.getEnableSessionTelemetry().ifPresent(request::setEnableSessionTelemetry);
config.getIsExperimentalMode().ifPresent(request::setIsExperimentalMode);
if (config.getOnUserInputRequest() != null) {
request.setRequestUserInput(true);
}
Expand Down Expand Up @@ -225,6 +226,7 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo
request.setExcludedTools(config.getExcludedTools());
request.setProvider(config.getProvider());
config.getEnableSessionTelemetry().ifPresent(request::setEnableSessionTelemetry);
config.getIsExperimentalMode().ifPresent(request::setIsExperimentalMode);
if (config.getOnUserInputRequest() != null) {
request.setRequestUserInput(true);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ public final class CreateSessionRequest {
@JsonProperty("requestMcpApps")
private Boolean requestMcpApps;

@JsonProperty("isExperimentalMode")
@JsonInclude(JsonInclude.Include.NON_NULL)
private Boolean isExperimentalMode;

@JsonProperty("requestExitPlanMode")
private Boolean requestExitPlanMode;

Expand Down Expand Up @@ -759,6 +763,30 @@ public void clearRequestMcpApps() {
this.requestMcpApps = null;
}

/**
* Gets the isExperimentalMode flag.
*
* @return the flag
*/
public Boolean getIsExperimentalMode() {
return isExperimentalMode;
}

/**
* Sets the isExperimentalMode flag.
*
* @param isExperimentalMode
* the flag
*/
public void setIsExperimentalMode(boolean isExperimentalMode) {
this.isExperimentalMode = isExperimentalMode;
}

/** Clears the isExperimentalMode setting, reverting to the default behavior. */
public void clearIsExperimentalMode() {
this.isExperimentalMode = null;
}

/** Gets the requestExitPlanMode flag. @return the flag */
public Boolean getRequestExitPlanMode() {
return requestExitPlanMode;
Expand Down
Loading
Loading