Skip to content

Commit d2b8848

Browse files
Add isExperimentalMode support across remaining SDKs
Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com>
1 parent 982ddfc commit d2b8848

18 files changed

Lines changed: 395 additions & 0 deletions

File tree

dotnet/src/Client.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance
896896
toolFilter.ExcludedTools,
897897
config.Provider,
898898
config.EnableSessionTelemetry,
899+
config.IsExperimentalMode,
899900
config.OnPermissionRequest != null ? true : null,
900901
config.OnUserInputRequest != null ? true : null,
901902
config.OnExitPlanModeRequest != null ? true : null,
@@ -1091,6 +1092,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
10911092
toolFilter.ExcludedTools,
10921093
config.Provider,
10931094
config.EnableSessionTelemetry,
1095+
config.IsExperimentalMode,
10941096
config.OnPermissionRequest != null ? true : null,
10951097
config.OnUserInputRequest != null ? true : null,
10961098
config.OnExitPlanModeRequest != null ? true : null,
@@ -2283,6 +2285,7 @@ internal record CreateSessionRequest(
22832285
IList<string>? ExcludedTools,
22842286
ProviderConfig? Provider,
22852287
bool? EnableSessionTelemetry,
2288+
bool? IsExperimentalMode,
22862289
bool? RequestPermission,
22872290
bool? RequestUserInput,
22882291
bool? RequestExitPlanMode,
@@ -2369,6 +2372,7 @@ internal record ResumeSessionRequest(
23692372
IList<string>? ExcludedTools,
23702373
ProviderConfig? Provider,
23712374
bool? EnableSessionTelemetry,
2375+
bool? IsExperimentalMode,
23722376
bool? RequestPermission,
23732377
bool? RequestUserInput,
23742378
bool? RequestExitPlanMode,

dotnet/src/Types.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2474,6 +2474,7 @@ protected SessionConfigBase(SessionConfigBase? other)
24742474
OnUserInputRequest = other.OnUserInputRequest;
24752475
Provider = other.Provider;
24762476
EnableSessionTelemetry = other.EnableSessionTelemetry;
2477+
IsExperimentalMode = other.IsExperimentalMode;
24772478
SkipCustomInstructions = other.SkipCustomInstructions;
24782479
CustomAgentsLocalOnly = other.CustomAgentsLocalOnly;
24792480
CoauthorEnabled = other.CoauthorEnabled;
@@ -2639,6 +2640,17 @@ protected SessionConfigBase(SessionConfigBase? other)
26392640
/// </summary>
26402641
public bool? EnableSessionTelemetry { get; set; }
26412642

2643+
/// <summary>
2644+
/// Overrides the session's experimental feature-flag tier resolution.
2645+
/// </summary>
2646+
/// <remarks>
2647+
/// Set to <see langword="true"/> to force-enable the experimental tier for this
2648+
/// session, or <see langword="false"/> to resolve feature flags as if
2649+
/// experimental were off. Leave <see langword="null"/> to inherit the runtime
2650+
/// process defaults unchanged.
2651+
/// </remarks>
2652+
public bool? IsExperimentalMode { get; set; }
2653+
26422654
/// <summary>
26432655
/// When <see langword="true"/>, suppresses loading of custom instruction files
26442656
/// (e.g. <c>.github/copilot-instructions.md</c>, <c>AGENTS.md</c>) from the working directory.

dotnet/test/Unit/CloneTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
7676
WorkingDirectory = "/workspace",
7777
Streaming = true,
7878
EnableSessionTelemetry = false,
79+
IsExperimentalMode = true,
7980
EnableOnDemandInstructionDiscovery = true,
8081
IncludeSubAgentStreamingEvents = false,
8182
McpServers = new Dictionary<string, McpServerConfig> { ["server1"] = new McpStdioServerConfig { Command = "echo" } },
@@ -115,6 +116,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
115116
Assert.Equal(original.WorkingDirectory, clone.WorkingDirectory);
116117
Assert.Equal(original.Streaming, clone.Streaming);
117118
Assert.Equal(original.EnableSessionTelemetry, clone.EnableSessionTelemetry);
119+
Assert.Equal(original.IsExperimentalMode, clone.IsExperimentalMode);
118120
Assert.Equal(original.EnableOnDemandInstructionDiscovery, clone.EnableOnDemandInstructionDiscovery);
119121
Assert.Equal(original.IncludeSubAgentStreamingEvents, clone.IncludeSubAgentStreamingEvents);
120122
Assert.Equal(original.McpServers.Count, clone.McpServers!.Count);
@@ -355,6 +357,19 @@ public void ResumeSessionConfig_Clone_CopiesEnableSessionTelemetry()
355357
Assert.False(clone.EnableSessionTelemetry);
356358
}
357359

360+
[Fact]
361+
public void ResumeSessionConfig_Clone_CopiesIsExperimentalMode()
362+
{
363+
var original = new ResumeSessionConfig
364+
{
365+
IsExperimentalMode = true,
366+
};
367+
368+
var clone = original.Clone();
369+
370+
Assert.True(clone.IsExperimentalMode);
371+
}
372+
358373
[Fact]
359374
public void ResumeSessionConfig_Clone_CopiesContinuePendingWork()
360375
{
@@ -440,6 +455,26 @@ public void ResumeSessionConfig_Clone_PreservesEnableSessionTelemetryDefault()
440455
Assert.Null(clone.EnableSessionTelemetry);
441456
}
442457

458+
[Fact]
459+
public void SessionConfig_Clone_PreservesIsExperimentalModeDefault()
460+
{
461+
var original = new SessionConfig();
462+
463+
var clone = original.Clone();
464+
465+
Assert.Null(clone.IsExperimentalMode);
466+
}
467+
468+
[Fact]
469+
public void ResumeSessionConfig_Clone_PreservesIsExperimentalModeDefault()
470+
{
471+
var original = new ResumeSessionConfig();
472+
473+
var clone = original.Clone();
474+
475+
Assert.Null(clone.IsExperimentalMode);
476+
}
477+
443478
[Fact]
444479
public void SessionConfig_Clone_CopiesEnableOnDemandInstructionDiscovery()
445480
{

dotnet/test/Unit/SerializationTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,40 @@ public void ResumeSessionRequest_CanSerializeEnableSessionTelemetry_WithSdkOptio
299299
Assert.False(root.GetProperty("enableSessionTelemetry").GetBoolean());
300300
}
301301

302+
[Fact]
303+
public void SessionRequests_CanSerializeIsExperimentalMode_WithSdkOptions()
304+
{
305+
var options = GetSerializerOptions();
306+
307+
var createRequestType = GetNestedType(typeof(CopilotClient), "CreateSessionRequest");
308+
var createRequest = CreateInternalRequest(
309+
createRequestType,
310+
("SessionId", "session-id"),
311+
("IsExperimentalMode", false));
312+
var createRoot = JsonDocument.Parse(JsonSerializer.Serialize(createRequest, createRequestType, options)).RootElement;
313+
Assert.False(createRoot.GetProperty("isExperimentalMode").GetBoolean());
314+
315+
var createRequestOmitted = CreateInternalRequest(
316+
createRequestType,
317+
("SessionId", "session-id"));
318+
var createOmittedRoot = JsonDocument.Parse(JsonSerializer.Serialize(createRequestOmitted, createRequestType, options)).RootElement;
319+
Assert.False(createOmittedRoot.TryGetProperty("isExperimentalMode", out _));
320+
321+
var resumeRequestType = GetNestedType(typeof(CopilotClient), "ResumeSessionRequest");
322+
var resumeRequest = CreateInternalRequest(
323+
resumeRequestType,
324+
("SessionId", "session-id"),
325+
("IsExperimentalMode", true));
326+
var resumeRoot = JsonDocument.Parse(JsonSerializer.Serialize(resumeRequest, resumeRequestType, options)).RootElement;
327+
Assert.True(resumeRoot.GetProperty("isExperimentalMode").GetBoolean());
328+
329+
var resumeRequestOmitted = CreateInternalRequest(
330+
resumeRequestType,
331+
("SessionId", "session-id"));
332+
var resumeOmittedRoot = JsonDocument.Parse(JsonSerializer.Serialize(resumeRequestOmitted, resumeRequestType, options)).RootElement;
333+
Assert.False(resumeOmittedRoot.TryGetProperty("isExperimentalMode", out _));
334+
}
335+
302336
[Fact]
303337
public void CreateSessionRequest_CanSerializeEnableOnDemandInstructionDiscovery_WithSdkOptions()
304338
{

go/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
629629
req.ToolFilterPrecedence = precedence
630630
req.Provider = config.Provider
631631
req.EnableSessionTelemetry = config.EnableSessionTelemetry
632+
req.IsExperimentalMode = config.IsExperimentalMode
632633
req.SkipCustomInstructions = config.SkipCustomInstructions
633634
req.CustomAgentsLocalOnly = config.CustomAgentsLocalOnly
634635
req.CoauthorEnabled = config.CoauthorEnabled
@@ -923,6 +924,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
923924
req.Tools = config.Tools
924925
req.Provider = config.Provider
925926
req.EnableSessionTelemetry = config.EnableSessionTelemetry
927+
req.IsExperimentalMode = config.IsExperimentalMode
926928
req.SkipCustomInstructions = config.SkipCustomInstructions
927929
req.CustomAgentsLocalOnly = config.CustomAgentsLocalOnly
928930
req.CoauthorEnabled = config.CoauthorEnabled

go/client_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,63 @@ func TestCreateSessionRequest_RequestMCPApps(t *testing.T) {
12421242
})
12431243
}
12441244

1245+
func TestSessionRequests_IsExperimentalMode(t *testing.T) {
1246+
t.Run("create forwards isExperimentalMode when explicitly false", func(t *testing.T) {
1247+
req := createSessionRequest{
1248+
IsExperimentalMode: Bool(false),
1249+
}
1250+
data, err := json.Marshal(req)
1251+
if err != nil {
1252+
t.Fatalf("Failed to marshal: %v", err)
1253+
}
1254+
var m map[string]any
1255+
if err := json.Unmarshal(data, &m); err != nil {
1256+
t.Fatalf("Failed to unmarshal: %v", err)
1257+
}
1258+
if m["isExperimentalMode"] != false {
1259+
t.Errorf("Expected isExperimentalMode to be false, got %v", m["isExperimentalMode"])
1260+
}
1261+
})
1262+
1263+
t.Run("create omits isExperimentalMode when unset", func(t *testing.T) {
1264+
req := createSessionRequest{}
1265+
data, _ := json.Marshal(req)
1266+
var m map[string]any
1267+
json.Unmarshal(data, &m)
1268+
if _, ok := m["isExperimentalMode"]; ok {
1269+
t.Error("Expected isExperimentalMode to be omitted when not set")
1270+
}
1271+
})
1272+
1273+
t.Run("resume forwards isExperimentalMode when explicitly true", func(t *testing.T) {
1274+
req := resumeSessionRequest{
1275+
SessionID: "s1",
1276+
IsExperimentalMode: Bool(true),
1277+
}
1278+
data, err := json.Marshal(req)
1279+
if err != nil {
1280+
t.Fatalf("Failed to marshal: %v", err)
1281+
}
1282+
var m map[string]any
1283+
if err := json.Unmarshal(data, &m); err != nil {
1284+
t.Fatalf("Failed to unmarshal: %v", err)
1285+
}
1286+
if m["isExperimentalMode"] != true {
1287+
t.Errorf("Expected isExperimentalMode to be true, got %v", m["isExperimentalMode"])
1288+
}
1289+
})
1290+
1291+
t.Run("resume omits isExperimentalMode when unset", func(t *testing.T) {
1292+
req := resumeSessionRequest{SessionID: "s1"}
1293+
data, _ := json.Marshal(req)
1294+
var m map[string]any
1295+
json.Unmarshal(data, &m)
1296+
if _, ok := m["isExperimentalMode"]; ok {
1297+
t.Error("Expected isExperimentalMode to be omitted when not set")
1298+
}
1299+
})
1300+
}
1301+
12451302
func TestResumeSessionRequest_RequestMCPApps(t *testing.T) {
12461303
t.Run("sends requestMcpApps flag when EnableMCPApps is set", func(t *testing.T) {
12471304
req := resumeSessionRequest{

go/types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -980,6 +980,12 @@ type SessionConfig struct {
980980
// regardless of this setting. This is independent of the OpenTelemetry
981981
// configuration in ClientOptions.Telemetry.
982982
EnableSessionTelemetry *bool
983+
// IsExperimentalMode, when non-nil, overrides the session's experimental
984+
// feature-flag tier resolution. Use Bool(true) to force-enable the
985+
// experimental tier for this session, Bool(false) to resolve feature flags
986+
// as if experimental were off, or nil to inherit the runtime process
987+
// defaults unchanged.
988+
IsExperimentalMode *bool
983989
// SkipCustomInstructions, when non-nil, controls whether the runtime loads
984990
// custom instruction files. See also [ClientOptions.Mode] = [ModeEmpty].
985991
SkipCustomInstructions *bool
@@ -1295,6 +1301,12 @@ type ResumeSessionConfig struct {
12951301
// regardless of this setting. This is independent of the OpenTelemetry
12961302
// configuration in ClientOptions.Telemetry.
12971303
EnableSessionTelemetry *bool
1304+
// IsExperimentalMode, when non-nil, overrides the resumed session's
1305+
// experimental feature-flag tier resolution. Use Bool(true) to force-enable
1306+
// the experimental tier for this session, Bool(false) to resolve feature
1307+
// flags as if experimental were off, or nil to inherit the runtime process
1308+
// defaults unchanged.
1309+
IsExperimentalMode *bool
12981310
// SkipCustomInstructions, when non-nil, controls whether the runtime loads
12991311
// custom instruction files. See also [ClientOptions.Mode] = [ModeEmpty].
13001312
SkipCustomInstructions *bool
@@ -1690,6 +1702,7 @@ type createSessionRequest struct {
16901702
ToolFilterPrecedence *rpc.OptionsUpdateToolFilterPrecedence `json:"toolFilterPrecedence,omitempty"`
16911703
Provider *ProviderConfig `json:"provider,omitempty"`
16921704
EnableSessionTelemetry *bool `json:"enableSessionTelemetry,omitempty"`
1705+
IsExperimentalMode *bool `json:"isExperimentalMode,omitempty"`
16931706
SkipCustomInstructions *bool `json:"skipCustomInstructions,omitempty"`
16941707
CustomAgentsLocalOnly *bool `json:"customAgentsLocalOnly,omitempty"`
16951708
CoauthorEnabled *bool `json:"coauthorEnabled,omitempty"`
@@ -1768,6 +1781,7 @@ type resumeSessionRequest struct {
17681781
ToolFilterPrecedence *rpc.OptionsUpdateToolFilterPrecedence `json:"toolFilterPrecedence,omitempty"`
17691782
Provider *ProviderConfig `json:"provider,omitempty"`
17701783
EnableSessionTelemetry *bool `json:"enableSessionTelemetry,omitempty"`
1784+
IsExperimentalMode *bool `json:"isExperimentalMode,omitempty"`
17711785
SkipCustomInstructions *bool `json:"skipCustomInstructions,omitempty"`
17721786
CustomAgentsLocalOnly *bool `json:"customAgentsLocalOnly,omitempty"`
17731787
CoauthorEnabled *bool `json:"coauthorEnabled,omitempty"`

java/src/main/java/com/github/copilot/SessionRequestBuilder.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess
114114
request.setExcludedTools(config.getExcludedTools());
115115
request.setProvider(config.getProvider());
116116
config.getEnableSessionTelemetry().ifPresent(request::setEnableSessionTelemetry);
117+
config.getIsExperimentalMode().ifPresent(request::setIsExperimentalMode);
117118
if (config.getOnUserInputRequest() != null) {
118119
request.setRequestUserInput(true);
119120
}
@@ -225,6 +226,7 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo
225226
request.setExcludedTools(config.getExcludedTools());
226227
request.setProvider(config.getProvider());
227228
config.getEnableSessionTelemetry().ifPresent(request::setEnableSessionTelemetry);
229+
config.getIsExperimentalMode().ifPresent(request::setIsExperimentalMode);
228230
if (config.getOnUserInputRequest() != null) {
229231
request.setRequestUserInput(true);
230232
}

java/src/main/java/com/github/copilot/rpc/CreateSessionRequest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ public final class CreateSessionRequest {
164164
@JsonProperty("requestMcpApps")
165165
private Boolean requestMcpApps;
166166

167+
@JsonProperty("isExperimentalMode")
168+
@JsonInclude(JsonInclude.Include.NON_NULL)
169+
private Boolean isExperimentalMode;
170+
167171
@JsonProperty("requestExitPlanMode")
168172
private Boolean requestExitPlanMode;
169173

@@ -759,6 +763,21 @@ public void clearRequestMcpApps() {
759763
this.requestMcpApps = null;
760764
}
761765

766+
/** Gets the isExperimentalMode flag. @return the flag */
767+
public Boolean getIsExperimentalMode() {
768+
return isExperimentalMode;
769+
}
770+
771+
/** Sets the isExperimentalMode flag. @param isExperimentalMode the flag */
772+
public void setIsExperimentalMode(boolean isExperimentalMode) {
773+
this.isExperimentalMode = isExperimentalMode;
774+
}
775+
776+
/** Clears the isExperimentalMode setting, reverting to the default behavior. */
777+
public void clearIsExperimentalMode() {
778+
this.isExperimentalMode = null;
779+
}
780+
762781
/** Gets the requestExitPlanMode flag. @return the flag */
763782
public Boolean getRequestExitPlanMode() {
764783
return requestExitPlanMode;

java/src/main/java/com/github/copilot/rpc/ResumeSessionConfig.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public class ResumeSessionConfig {
4646
private List<String> excludedTools;
4747
private ProviderConfig provider;
4848
private Boolean enableSessionTelemetry;
49+
private Boolean isExperimentalMode;
4950
private Boolean skipCustomInstructions;
5051
private Boolean customAgentsLocalOnly;
5152
private Boolean coauthorEnabled;
@@ -297,6 +298,43 @@ public ResumeSessionConfig clearEnableSessionTelemetry() {
297298
return this;
298299
}
299300

301+
/**
302+
* Overrides the resumed session's experimental feature-flag tier resolution.
303+
*
304+
* @return {@code true} to force-enable the experimental tier, {@code false} to
305+
* resolve feature flags as if experimental were off, or empty to
306+
* inherit the runtime process defaults unchanged
307+
*/
308+
@JsonIgnore
309+
public Optional<Boolean> getIsExperimentalMode() {
310+
return Optional.ofNullable(isExperimentalMode);
311+
}
312+
313+
/**
314+
* Overrides the resumed session's experimental feature-flag tier resolution.
315+
*
316+
* @param isExperimentalMode
317+
* {@code true} to force-enable the experimental tier for this
318+
* session, {@code false} to resolve feature flags as if experimental
319+
* were off
320+
* @return this config for method chaining
321+
*/
322+
public ResumeSessionConfig setIsExperimentalMode(boolean isExperimentalMode) {
323+
this.isExperimentalMode = isExperimentalMode;
324+
return this;
325+
}
326+
327+
/**
328+
* Clears the isExperimentalMode setting, reverting to the runtime default
329+
* behavior.
330+
*
331+
* @return this instance for method chaining
332+
*/
333+
public ResumeSessionConfig clearIsExperimentalMode() {
334+
this.isExperimentalMode = null;
335+
return this;
336+
}
337+
300338
/**
301339
* Gets whether custom instruction file loading is suppressed.
302340
*
@@ -1527,6 +1565,7 @@ public ResumeSessionConfig clone() {
15271565
copy.excludedTools = this.excludedTools != null ? new ArrayList<>(this.excludedTools) : null;
15281566
copy.provider = this.provider;
15291567
copy.enableSessionTelemetry = this.enableSessionTelemetry;
1568+
copy.isExperimentalMode = this.isExperimentalMode;
15301569
copy.reasoningEffort = this.reasoningEffort;
15311570
copy.reasoningSummary = this.reasoningSummary;
15321571
copy.contextTier = this.contextTier;

0 commit comments

Comments
 (0)