Skip to content

Commit b0e6ce6

Browse files
Align experimental mode defaults across SDKs
Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com>
1 parent 1e90676 commit b0e6ce6

20 files changed

Lines changed: 357 additions & 80 deletions

File tree

dotnet/src/Client.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,7 @@ private void ApplyConfigDefaultsForMode(SessionConfigBase config)
664664
{
665665
if (_options.Mode == CopilotClientMode.Empty)
666666
{
667+
config.EnableExperimentalMode ??= false;
667668
config.EnableSessionTelemetry ??= false;
668669
config.SkipEmbeddingRetrieval ??= true;
669670
config.EmbeddingCacheStorage ??= EmbeddingCacheStorageMode.InMemory;

dotnet/src/Types.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2641,13 +2641,11 @@ protected SessionConfigBase(SessionConfigBase? other)
26412641
public bool? EnableSessionTelemetry { get; set; }
26422642

26432643
/// <summary>
2644-
/// Overrides the session's experimental feature-flag tier resolution.
2644+
/// Controls whether the session enables experimental features.
26452645
/// </summary>
26462646
/// <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.
2647+
/// Defaults to <see langword="false"/> in <see cref="CopilotClientMode.Empty"/>.
2648+
/// Otherwise, the runtime decides when left <see langword="null"/>.
26512649
/// </remarks>
26522650
public bool? EnableExperimentalMode { get; set; }
26532651

dotnet/test/Unit/SerializationTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,35 @@ public void SessionRequests_CanSerializeEnableExperimentalMode_WithSdkOptions()
333333
Assert.False(resumeOmittedRoot.TryGetProperty("isExperimentalMode", out _));
334334
}
335335

336+
[Fact]
337+
public void ApplyConfigDefaultsForMode_EmptyDefaultsEnableExperimentalModeFalse()
338+
{
339+
var client = new CopilotClient(new CopilotClientOptions
340+
{
341+
Mode = CopilotClientMode.Empty,
342+
BaseDirectory = System.IO.Path.GetTempPath(),
343+
});
344+
var config = new SessionConfig();
345+
346+
InvokeApplyConfigDefaultsForMode(client, config);
347+
348+
Assert.False(config.EnableExperimentalMode);
349+
}
350+
351+
[Fact]
352+
public void ApplyConfigDefaultsForMode_CopilotCliLeavesEnableExperimentalModeNull()
353+
{
354+
var client = new CopilotClient(new CopilotClientOptions
355+
{
356+
Mode = CopilotClientMode.CopilotCli,
357+
});
358+
var config = new ResumeSessionConfig();
359+
360+
InvokeApplyConfigDefaultsForMode(client, config);
361+
362+
Assert.Null(config.EnableExperimentalMode);
363+
}
364+
336365
[Fact]
337366
public void CreateSessionRequest_CanSerializeEnableOnDemandInstructionDiscovery_WithSdkOptions()
338367
{
@@ -602,6 +631,15 @@ private static Type GetNestedType(Type containingType, string name)
602631
return type!;
603632
}
604633

634+
private static void InvokeApplyConfigDefaultsForMode(CopilotClient client, SessionConfigBase config)
635+
{
636+
var method = typeof(CopilotClient).GetMethod(
637+
"ApplyConfigDefaultsForMode",
638+
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
639+
Assert.NotNull(method);
640+
method!.Invoke(client, [config]);
641+
}
642+
605643
[Fact]
606644
public void HooksInvokeResponse_SerializesBoxedJsonElement_AsOutput()
607645
{

go/mode_empty.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ func (c *Client) applyConfigDefaultsForMode(config *SessionConfig) {
122122
if c.options.Mode != ModeEmpty {
123123
return
124124
}
125+
if config.EnableExperimentalMode == nil {
126+
f := false
127+
config.EnableExperimentalMode = &f
128+
}
125129
if config.EnableSessionTelemetry == nil {
126130
f := false
127131
config.EnableSessionTelemetry = &f
@@ -163,6 +167,10 @@ func (c *Client) applyResumeDefaultsForMode(config *ResumeSessionConfig) {
163167
if c.options.Mode != ModeEmpty {
164168
return
165169
}
170+
if config.EnableExperimentalMode == nil {
171+
f := false
172+
config.EnableExperimentalMode = &f
173+
}
166174
if config.EnableSessionTelemetry == nil {
167175
f := false
168176
config.EnableSessionTelemetry = &f

go/toolset_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,24 @@ func TestApplyConfigDefaultsForMode_emptyDefaultsTelemetryFalse(t *testing.T) {
229229
}
230230
}
231231

232+
func TestApplyConfigDefaultsForMode_emptyDefaultsExperimentalModeFalse(t *testing.T) {
233+
c := NewClient(&ClientOptions{Mode: ModeEmpty, BaseDirectory: t.TempDir()})
234+
cfg := &SessionConfig{}
235+
c.applyConfigDefaultsForMode(cfg)
236+
if cfg.EnableExperimentalMode == nil || *cfg.EnableExperimentalMode != false {
237+
t.Errorf("expected experimental mode default false in empty mode, got %v", cfg.EnableExperimentalMode)
238+
}
239+
}
240+
241+
func TestApplyConfigDefaultsForMode_copilotCliLeavesExperimentalModeNil(t *testing.T) {
242+
c := NewClient(&ClientOptions{Mode: ModeCopilotCli})
243+
cfg := &SessionConfig{}
244+
c.applyConfigDefaultsForMode(cfg)
245+
if cfg.EnableExperimentalMode != nil {
246+
t.Errorf("non-empty mode must not default experimental mode")
247+
}
248+
}
249+
232250
func TestApplyConfigDefaultsForMode_emptyHonorsCallerTelemetry(t *testing.T) {
233251
c := NewClient(&ClientOptions{Mode: ModeEmpty, BaseDirectory: t.TempDir()})
234252
trueVal := true
@@ -362,3 +380,21 @@ func TestApplyConfigDefaultsForMode_copilotCliLeavesMCPOAuthTokenStorageEmpty(t
362380
t.Errorf("non-empty mode must not default MCPOAuthTokenStorage, got %q", cfg.MCPOAuthTokenStorage)
363381
}
364382
}
383+
384+
func TestApplyResumeDefaultsForMode_emptyDefaultsExperimentalModeFalse(t *testing.T) {
385+
c := NewClient(&ClientOptions{Mode: ModeEmpty, BaseDirectory: t.TempDir()})
386+
cfg := &ResumeSessionConfig{}
387+
c.applyResumeDefaultsForMode(cfg)
388+
if cfg.EnableExperimentalMode == nil || *cfg.EnableExperimentalMode != false {
389+
t.Errorf("expected experimental mode default false in empty mode, got %v", cfg.EnableExperimentalMode)
390+
}
391+
}
392+
393+
func TestApplyResumeDefaultsForMode_copilotCliLeavesExperimentalModeNil(t *testing.T) {
394+
c := NewClient(&ClientOptions{Mode: ModeCopilotCli})
395+
cfg := &ResumeSessionConfig{}
396+
c.applyResumeDefaultsForMode(cfg)
397+
if cfg.EnableExperimentalMode != nil {
398+
t.Errorf("non-empty mode must not default experimental mode")
399+
}
400+
}

go/types.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -980,11 +980,9 @@ type SessionConfig struct {
980980
// regardless of this setting. This is independent of the OpenTelemetry
981981
// configuration in ClientOptions.Telemetry.
982982
EnableSessionTelemetry *bool
983-
// EnableExperimentalMode, 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.
983+
// EnableExperimentalMode controls whether the session enables experimental
984+
// features. When nil, it defaults to false in [ModeEmpty]; otherwise the
985+
// runtime decides.
988986
EnableExperimentalMode *bool
989987
// SkipCustomInstructions, when non-nil, controls whether the runtime loads
990988
// custom instruction files. See also [ClientOptions.Mode] = [ModeEmpty].
@@ -1301,11 +1299,9 @@ type ResumeSessionConfig struct {
13011299
// regardless of this setting. This is independent of the OpenTelemetry
13021300
// configuration in ClientOptions.Telemetry.
13031301
EnableSessionTelemetry *bool
1304-
// EnableExperimentalMode, 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.
1302+
// EnableExperimentalMode controls whether the session enables experimental
1303+
// features. When nil, it defaults to false in [ModeEmpty]; otherwise the
1304+
// runtime decides.
13091305
EnableExperimentalMode *bool
13101306
// SkipCustomInstructions, when non-nil, controls whether the runtime loads
13111307
// custom instruction files. See also [ClientOptions.Mode] = [ModeEmpty].

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,7 @@ public CompletableFuture<CopilotSession> createSession(SessionConfig config) {
513513
registeredIdHolder[0] = localSessionId;
514514
}
515515

516-
var request = SessionRequestBuilder.buildCreateRequest(config, localSessionId);
516+
var request = SessionRequestBuilder.buildCreateRequest(config, localSessionId, options.getMode());
517517
if (extracted.wireSystemMessage() != config.getSystemMessage()) {
518518
request.setSystemMessage(extracted.wireSystemMessage());
519519
}
@@ -551,6 +551,9 @@ public CompletableFuture<CopilotSession> createSession(SessionConfig config) {
551551
if (request.getEnableSkills() == null) {
552552
request.setEnableSkills(false);
553553
}
554+
if (request.getIsExperimentalMode() == null) {
555+
request.setIsExperimentalMode(false);
556+
}
554557
if (request.getMcpOAuthTokenStorage() == null) {
555558
request.setMcpOAuthTokenStorage("in-memory");
556559
}
@@ -651,7 +654,7 @@ public CompletableFuture<CopilotSession> resumeSession(String sessionId, ResumeS
651654
session.registerTransformCallbacks(extracted.transformCallbacks());
652655
}
653656

654-
var request = SessionRequestBuilder.buildResumeRequest(sessionId, config);
657+
var request = SessionRequestBuilder.buildResumeRequest(sessionId, config, options.getMode());
655658
if (extracted.wireSystemMessage() != config.getSystemMessage()) {
656659
request.setSystemMessage(extracted.wireSystemMessage());
657660
}
@@ -687,6 +690,9 @@ public CompletableFuture<CopilotSession> resumeSession(String sessionId, ResumeS
687690
if (request.getEnableSkills() == null) {
688691
request.setEnableSkills(false);
689692
}
693+
if (request.getIsExperimentalMode() == null) {
694+
request.setIsExperimentalMode(false);
695+
}
690696
if (request.getMcpOAuthTokenStorage() == null) {
691697
request.setMcpOAuthTokenStorage("in-memory");
692698
}

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66

77
import java.util.HashMap;
88
import java.util.Map;
9+
import java.util.Optional;
910
import java.util.concurrent.CompletableFuture;
1011
import java.util.function.Function;
1112

1213
import com.github.copilot.rpc.CreateSessionRequest;
1314
import com.github.copilot.rpc.CommandWireDefinition;
15+
import com.github.copilot.rpc.CopilotClientMode;
1416
import com.github.copilot.rpc.ResumeSessionConfig;
1517
import com.github.copilot.rpc.ResumeSessionRequest;
1618
import com.github.copilot.rpc.SectionOverride;
@@ -93,6 +95,10 @@ static ExtractedTransforms extractTransformCallbacks(SystemMessageConfig systemM
9395
* @return the built request object
9496
*/
9597
static CreateSessionRequest buildCreateRequest(SessionConfig config, String sessionId) {
98+
return buildCreateRequest(config, sessionId, CopilotClientMode.COPILOT_CLI);
99+
}
100+
101+
static CreateSessionRequest buildCreateRequest(SessionConfig config, String sessionId, CopilotClientMode mode) {
96102
var request = new CreateSessionRequest();
97103
// Always request permission callbacks to enable deny-by-default behavior
98104
request.setRequestPermission(true);
@@ -114,7 +120,8 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config, String sess
114120
request.setExcludedTools(config.getExcludedTools());
115121
request.setProvider(config.getProvider());
116122
config.getEnableSessionTelemetry().ifPresent(request::setEnableSessionTelemetry);
117-
config.getEnableExperimentalMode().ifPresent(request::setIsExperimentalMode);
123+
experimentalModeForMode(mode, config.getEnableExperimentalMode().orElse(null))
124+
.ifPresent(request::setIsExperimentalMode);
118125
if (config.getOnUserInputRequest() != null) {
119126
request.setRequestUserInput(true);
120127
}
@@ -204,6 +211,11 @@ static CreateSessionRequest buildCreateRequest(SessionConfig config) {
204211
* @return the built request object
205212
*/
206213
static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionConfig config) {
214+
return buildResumeRequest(sessionId, config, CopilotClientMode.COPILOT_CLI);
215+
}
216+
217+
static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionConfig config,
218+
CopilotClientMode mode) {
207219
var request = new ResumeSessionRequest();
208220
request.setSessionId(sessionId);
209221
// Always request permission callbacks to enable deny-by-default behavior
@@ -226,7 +238,8 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo
226238
request.setExcludedTools(config.getExcludedTools());
227239
request.setProvider(config.getProvider());
228240
config.getEnableSessionTelemetry().ifPresent(request::setEnableSessionTelemetry);
229-
config.getEnableExperimentalMode().ifPresent(request::setIsExperimentalMode);
241+
experimentalModeForMode(mode, config.getEnableExperimentalMode().orElse(null))
242+
.ifPresent(request::setIsExperimentalMode);
230243
if (config.getOnUserInputRequest() != null) {
231244
request.setRequestUserInput(true);
232245
}
@@ -292,6 +305,13 @@ static ResumeSessionRequest buildResumeRequest(String sessionId, ResumeSessionCo
292305
return request;
293306
}
294307

308+
private static Optional<Boolean> experimentalModeForMode(CopilotClientMode mode, Boolean supplied) {
309+
if (mode == CopilotClientMode.EMPTY) {
310+
return Optional.of(supplied != null ? supplied : false);
311+
}
312+
return Optional.ofNullable(supplied);
313+
}
314+
295315
/**
296316
* Configures a session with handlers from the given config.
297317
*

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

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -299,24 +299,22 @@ public ResumeSessionConfig clearEnableSessionTelemetry() {
299299
}
300300

301301
/**
302-
* Overrides the resumed session's experimental feature-flag tier resolution.
302+
* Controls whether the session enables experimental features.
303303
*
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
304+
* @return {@code true} when experimental features are enabled, {@code false}
305+
* when they are disabled, or empty to use the mode-specific default
307306
*/
308307
@JsonIgnore
309308
public Optional<Boolean> getEnableExperimentalMode() {
310309
return Optional.ofNullable(enableExperimentalMode);
311310
}
312311

313312
/**
314-
* Overrides the resumed session's experimental feature-flag tier resolution.
313+
* Controls whether the session enables experimental features.
315314
*
316315
* @param enableExperimentalMode
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
316+
* {@code true} to enable experimental features; {@code false} to
317+
* disable them
320318
* @return this config for method chaining
321319
*/
322320
public ResumeSessionConfig setEnableExperimentalMode(boolean enableExperimentalMode) {
@@ -325,8 +323,8 @@ public ResumeSessionConfig setEnableExperimentalMode(boolean enableExperimentalM
325323
}
326324

327325
/**
328-
* Clears the enableExperimentalMode setting, reverting to the runtime default
329-
* behavior.
326+
* Clears the enableExperimentalMode setting. In {@link CopilotClientMode#EMPTY
327+
* EMPTY} mode this defaults to {@code false}; otherwise the runtime decides.
330328
*
331329
* @return this instance for method chaining
332330
*/

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

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -400,24 +400,22 @@ public SessionConfig clearEnableSessionTelemetry() {
400400
}
401401

402402
/**
403-
* Overrides the session's experimental feature-flag tier resolution.
403+
* Controls whether the session enables experimental features.
404404
*
405-
* @return {@code true} to force-enable the experimental tier, {@code false} to
406-
* resolve feature flags as if experimental were off, or empty to
407-
* inherit the runtime process defaults unchanged
405+
* @return {@code true} when experimental features are enabled, {@code false}
406+
* when they are disabled, or empty to use the mode-specific default
408407
*/
409408
@JsonIgnore
410409
public Optional<Boolean> getEnableExperimentalMode() {
411410
return Optional.ofNullable(enableExperimentalMode);
412411
}
413412

414413
/**
415-
* Overrides the session's experimental feature-flag tier resolution.
414+
* Controls whether the session enables experimental features.
416415
*
417416
* @param enableExperimentalMode
418-
* {@code true} to force-enable the experimental tier for this
419-
* session, {@code false} to resolve feature flags as if experimental
420-
* were off
417+
* {@code true} to enable experimental features; {@code false} to
418+
* disable them
421419
* @return this config instance for method chaining
422420
*/
423421
public SessionConfig setEnableExperimentalMode(boolean enableExperimentalMode) {
@@ -426,8 +424,8 @@ public SessionConfig setEnableExperimentalMode(boolean enableExperimentalMode) {
426424
}
427425

428426
/**
429-
* Clears the enableExperimentalMode setting, reverting to the runtime default
430-
* behavior.
427+
* Clears the enableExperimentalMode setting. In {@link CopilotClientMode#EMPTY
428+
* EMPTY} mode this defaults to {@code false}; otherwise the runtime decides.
431429
*
432430
* @return this instance for method chaining
433431
*/

0 commit comments

Comments
 (0)