Skip to content

Commit 1e5a3b8

Browse files
Tomas MeszarosCopilot
andcommitted
Add enableOnDemandInstructionDiscovery to all SDK SessionConfig types
Mirrors the existing enableConfigDiscovery and remoteSession precedents (PRs #1044 and #1295). Exposes the SDK option that lets the runtime discover custom instruction files on demand after the agent reads or views files, complementing the existing up-front load of `.github/copilot-instructions.md`, `AGENTS.md`, etc. Wire key (camelCase, identical across all 5 SDKs): enableOnDemandInstructionDiscovery Type shapes: Node enableOnDemandInstructionDiscovery?: boolean Python enable_on_demand_instruction_discovery: bool | None = None Go EnableOnDemandInstructionDiscovery *bool .NET bool? EnableOnDemandInstructionDiscovery Rust Option<bool> with #[serde(skip_serializing_if = "Option::is_none")] Wire semantics: when set, the wire payload carries the literal value (including explicit `false`); when omitted, the key is dropped. Applies to both session.create and session.resume so callers can toggle the setting on a resumed session. Runtime-gated. The runtime honors the option only when custom instructions are enabled and the connected runtime supports on-demand custom instruction discovery; otherwise the option is accepted but no-ops. The SDK does not attempt to detect the runtime gate. Requires @github/copilot ^1.0.49-1 (the runtime change shipped in github/copilot#7759). Security: discovered instruction files are treated as model instructions and may be stored or replayed with session history. Docstrings caution against enabling for untrusted content, CI jobs processing untrusted forks, or directories writable by untrusted users or processes. Go shape note: uses *bool (not bool) so consumers can disable a previously-enabled session on resume. Reuses the precedent already set by EnableSessionTelemetry *bool and IncludeSubAgentStreamingEvents *bool. Does not retrofit the existing EnableConfigDiscovery bool field (that would be a separate breaking source change). Tests: each SDK adds tests for the new field on both create and resume, asserting that explicit `false` is serialized as `false` and that omission drops the key from the payload. Mirrors the test patterns already in place for enable_session_telemetry, include_sub_agent_streaming_events, and enable_config_discovery. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent e846aa6 commit 1e5a3b8

17 files changed

Lines changed: 590 additions & 78 deletions

dotnet/src/Client.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance
629629
config.Agent,
630630
config.ConfigDir,
631631
config.EnableConfigDiscovery,
632+
config.EnableOnDemandInstructionDiscovery,
632633
config.SkillDirectories,
633634
config.DisabledSkills,
634635
config.InfiniteSessions,
@@ -780,6 +781,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
780781
config.WorkingDirectory,
781782
config.ConfigDir,
782783
config.EnableConfigDiscovery,
784+
config.EnableOnDemandInstructionDiscovery,
783785
config.DisableResume is true ? true : null,
784786
config.Streaming is true ? true : null,
785787
config.IncludeSubAgentStreamingEvents,
@@ -2020,6 +2022,7 @@ internal record CreateSessionRequest(
20202022
string? Agent,
20212023
string? ConfigDir,
20222024
bool? EnableConfigDiscovery,
2025+
bool? EnableOnDemandInstructionDiscovery,
20232026
IList<string>? SkillDirectories,
20242027
IList<string>? DisabledSkills,
20252028
InfiniteSessionConfig? InfiniteSessions,
@@ -2074,6 +2077,7 @@ internal record ResumeSessionRequest(
20742077
string? WorkingDirectory,
20752078
string? ConfigDir,
20762079
bool? EnableConfigDiscovery,
2080+
bool? EnableOnDemandInstructionDiscovery,
20772081
bool? DisableResume,
20782082
bool? Streaming,
20792083
bool? IncludeSubAgentStreamingEvents,

dotnet/src/Types.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2058,6 +2058,7 @@ protected SessionConfig(SessionConfig? other)
20582058
Agent = other.Agent;
20592059
DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null;
20602060
EnableConfigDiscovery = other.EnableConfigDiscovery;
2061+
EnableOnDemandInstructionDiscovery = other.EnableOnDemandInstructionDiscovery;
20612062
ExcludedTools = other.ExcludedTools is not null ? [.. other.ExcludedTools] : null;
20622063
Hooks = other.Hooks;
20632064
InfiniteSessions = other.InfiniteSessions;
@@ -2138,6 +2139,25 @@ protected SessionConfig(SessionConfig? other)
21382139
/// </summary>
21392140
public bool? EnableConfigDiscovery { get; set; }
21402141

2142+
/// <summary>
2143+
/// When <see langword="true"/>, requests on-demand discovery of custom instruction
2144+
/// files after the agent successfully reads or views files. Discovered instruction
2145+
/// files are treated as model instructions and may influence agent behavior.
2146+
/// <para>
2147+
/// Runtime-gated: only takes effect when custom instructions are enabled and the
2148+
/// connected runtime supports and enables on-demand custom instruction discovery.
2149+
/// Otherwise the runtime accepts the option but performs no on-demand instruction
2150+
/// discovery.
2151+
/// </para>
2152+
/// <para>
2153+
/// Security: enable only for trusted repositories or workspaces. Discovered
2154+
/// instruction files may be stored or replayed with session history. Do not enable
2155+
/// for untrusted content, CI jobs processing untrusted forks, or directories
2156+
/// writable by untrusted users or processes.
2157+
/// </para>
2158+
/// </summary>
2159+
public bool? EnableOnDemandInstructionDiscovery { get; set; }
2160+
21412161
/// <summary>
21422162
/// Custom tool declarations available to the language model during the session.
21432163
/// Declarations backed by an <see cref="AIFunction"/> are invoked automatically; declarations without one
@@ -2368,6 +2388,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
23682388
DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null;
23692389
DisableResume = other.DisableResume;
23702390
EnableConfigDiscovery = other.EnableConfigDiscovery;
2391+
EnableOnDemandInstructionDiscovery = other.EnableOnDemandInstructionDiscovery;
23712392
ContinuePendingWork = other.ContinuePendingWork;
23722393
ExcludedTools = other.ExcludedTools is not null ? [.. other.ExcludedTools] : null;
23732394
Hooks = other.Hooks;
@@ -2528,6 +2549,17 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
25282549
/// </summary>
25292550
public bool? EnableConfigDiscovery { get; set; }
25302551

2552+
/// <summary>
2553+
/// When <see langword="true"/>, requests on-demand discovery of custom instruction
2554+
/// files after the agent successfully reads or views files. See
2555+
/// <see cref="SessionConfig.EnableOnDemandInstructionDiscovery"/> for details.
2556+
/// <para>
2557+
/// For resumed sessions, omitting this option leaves the existing session setting
2558+
/// unchanged; set <see langword="false"/> to disable future on-demand discovery.
2559+
/// </para>
2560+
/// </summary>
2561+
public bool? EnableOnDemandInstructionDiscovery { get; set; }
2562+
25312563
/// <summary>
25322564
/// When true, the session.resume event is not emitted.
25332565
/// Default: false (resume event is emitted).

dotnet/test/Unit/CloneTests.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
9292
WorkingDirectory = "/workspace",
9393
Streaming = true,
9494
EnableSessionTelemetry = false,
95+
EnableOnDemandInstructionDiscovery = true,
9596
IncludeSubAgentStreamingEvents = false,
9697
McpServers = new Dictionary<string, McpServerConfig> { ["server1"] = new McpStdioServerConfig { Command = "echo" } },
9798
CustomAgents = [new CustomAgentConfig { Name = "agent1", Model = "claude-haiku-4.5" }],
@@ -125,6 +126,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
125126
Assert.Equal(original.WorkingDirectory, clone.WorkingDirectory);
126127
Assert.Equal(original.Streaming, clone.Streaming);
127128
Assert.Equal(original.EnableSessionTelemetry, clone.EnableSessionTelemetry);
129+
Assert.Equal(original.EnableOnDemandInstructionDiscovery, clone.EnableOnDemandInstructionDiscovery);
128130
Assert.Equal(original.IncludeSubAgentStreamingEvents, clone.IncludeSubAgentStreamingEvents);
129131
Assert.Equal(original.McpServers.Count, clone.McpServers!.Count);
130132
Assert.Equal(original.CustomAgents.Count, clone.CustomAgents!.Count);
@@ -403,4 +405,50 @@ public void ResumeSessionConfig_Clone_PreservesEnableSessionTelemetryDefault()
403405

404406
Assert.Null(clone.EnableSessionTelemetry);
405407
}
408+
409+
[Fact]
410+
public void SessionConfig_Clone_CopiesEnableOnDemandInstructionDiscovery()
411+
{
412+
var original = new SessionConfig
413+
{
414+
EnableOnDemandInstructionDiscovery = false,
415+
};
416+
417+
var clone = original.Clone();
418+
419+
Assert.False(clone.EnableOnDemandInstructionDiscovery);
420+
}
421+
422+
[Fact]
423+
public void ResumeSessionConfig_Clone_CopiesEnableOnDemandInstructionDiscovery()
424+
{
425+
var original = new ResumeSessionConfig
426+
{
427+
EnableOnDemandInstructionDiscovery = true,
428+
};
429+
430+
var clone = original.Clone();
431+
432+
Assert.True(clone.EnableOnDemandInstructionDiscovery);
433+
}
434+
435+
[Fact]
436+
public void SessionConfig_Clone_PreservesEnableOnDemandInstructionDiscoveryDefault()
437+
{
438+
var original = new SessionConfig();
439+
440+
var clone = original.Clone();
441+
442+
Assert.Null(clone.EnableOnDemandInstructionDiscovery);
443+
}
444+
445+
[Fact]
446+
public void ResumeSessionConfig_Clone_PreservesEnableOnDemandInstructionDiscoveryDefault()
447+
{
448+
var original = new ResumeSessionConfig();
449+
450+
var clone = original.Clone();
451+
452+
Assert.Null(clone.EnableOnDemandInstructionDiscovery);
453+
}
406454
}

dotnet/test/Unit/SerializationTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,60 @@ public void ResumeSessionRequest_CanSerializeEnableSessionTelemetry_WithSdkOptio
203203
Assert.False(root.GetProperty("enableSessionTelemetry").GetBoolean());
204204
}
205205

206+
[Fact]
207+
public void CreateSessionRequest_CanSerializeEnableOnDemandInstructionDiscovery_WithSdkOptions()
208+
{
209+
var options = GetSerializerOptions();
210+
var requestType = GetNestedType(typeof(CopilotClient), "CreateSessionRequest");
211+
212+
var requestTrue = CreateInternalRequest(
213+
requestType,
214+
("SessionId", "session-id"),
215+
("EnableOnDemandInstructionDiscovery", true));
216+
var rootTrue = JsonDocument.Parse(JsonSerializer.Serialize(requestTrue, requestType, options)).RootElement;
217+
Assert.True(rootTrue.GetProperty("enableOnDemandInstructionDiscovery").GetBoolean());
218+
219+
var requestFalse = CreateInternalRequest(
220+
requestType,
221+
("SessionId", "session-id"),
222+
("EnableOnDemandInstructionDiscovery", false));
223+
var rootFalse = JsonDocument.Parse(JsonSerializer.Serialize(requestFalse, requestType, options)).RootElement;
224+
Assert.False(rootFalse.GetProperty("enableOnDemandInstructionDiscovery").GetBoolean());
225+
226+
var requestOmitted = CreateInternalRequest(
227+
requestType,
228+
("SessionId", "session-id"));
229+
var rootOmitted = JsonDocument.Parse(JsonSerializer.Serialize(requestOmitted, requestType, options)).RootElement;
230+
Assert.False(rootOmitted.TryGetProperty("enableOnDemandInstructionDiscovery", out _));
231+
}
232+
233+
[Fact]
234+
public void ResumeSessionRequest_CanSerializeEnableOnDemandInstructionDiscovery_WithSdkOptions()
235+
{
236+
var options = GetSerializerOptions();
237+
var requestType = GetNestedType(typeof(CopilotClient), "ResumeSessionRequest");
238+
239+
var requestTrue = CreateInternalRequest(
240+
requestType,
241+
("SessionId", "session-id"),
242+
("EnableOnDemandInstructionDiscovery", true));
243+
var rootTrue = JsonDocument.Parse(JsonSerializer.Serialize(requestTrue, requestType, options)).RootElement;
244+
Assert.True(rootTrue.GetProperty("enableOnDemandInstructionDiscovery").GetBoolean());
245+
246+
var requestFalse = CreateInternalRequest(
247+
requestType,
248+
("SessionId", "session-id"),
249+
("EnableOnDemandInstructionDiscovery", false));
250+
var rootFalse = JsonDocument.Parse(JsonSerializer.Serialize(requestFalse, requestType, options)).RootElement;
251+
Assert.False(rootFalse.GetProperty("enableOnDemandInstructionDiscovery").GetBoolean());
252+
253+
var requestOmitted = CreateInternalRequest(
254+
requestType,
255+
("SessionId", "session-id"));
256+
var rootOmitted = JsonDocument.Parse(JsonSerializer.Serialize(requestOmitted, requestType, options)).RootElement;
257+
Assert.False(rootOmitted.TryGetProperty("enableOnDemandInstructionDiscovery", out _));
258+
}
259+
206260
[Fact]
207261
public void ResumeSessionRequest_CanSerializeModeRequestFlags_WithSdkOptions()
208262
{

go/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
625625
if config.EnableConfigDiscovery {
626626
req.EnableConfigDiscovery = Bool(true)
627627
}
628+
req.EnableOnDemandInstructionDiscovery = config.EnableOnDemandInstructionDiscovery
628629
req.Tools = config.Tools
629630
wireSystemMessage, transformCallbacks := extractTransformCallbacks(config.SystemMessage)
630631
req.SystemMessage = wireSystemMessage
@@ -831,6 +832,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
831832
if config.EnableConfigDiscovery {
832833
req.EnableConfigDiscovery = Bool(true)
833834
}
835+
req.EnableOnDemandInstructionDiscovery = config.EnableOnDemandInstructionDiscovery
834836
if config.DisableResume {
835837
req.DisableResume = Bool(true)
836838
}

go/client_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,6 +1318,100 @@ func TestResumeSessionRequest_IncludeSubAgentStreamingEvents(t *testing.T) {
13181318
})
13191319
}
13201320

1321+
func TestCreateSessionRequest_EnableOnDemandInstructionDiscovery(t *testing.T) {
1322+
t.Run("forwards explicit true", func(t *testing.T) {
1323+
req := createSessionRequest{
1324+
EnableOnDemandInstructionDiscovery: Bool(true),
1325+
}
1326+
data, err := json.Marshal(req)
1327+
if err != nil {
1328+
t.Fatalf("Failed to marshal: %v", err)
1329+
}
1330+
var m map[string]any
1331+
if err := json.Unmarshal(data, &m); err != nil {
1332+
t.Fatalf("Failed to unmarshal: %v", err)
1333+
}
1334+
if m["enableOnDemandInstructionDiscovery"] != true {
1335+
t.Errorf("Expected enableOnDemandInstructionDiscovery to be true, got %v", m["enableOnDemandInstructionDiscovery"])
1336+
}
1337+
})
1338+
1339+
t.Run("preserves explicit false", func(t *testing.T) {
1340+
req := createSessionRequest{
1341+
EnableOnDemandInstructionDiscovery: Bool(false),
1342+
}
1343+
data, err := json.Marshal(req)
1344+
if err != nil {
1345+
t.Fatalf("Failed to marshal: %v", err)
1346+
}
1347+
var m map[string]any
1348+
if err := json.Unmarshal(data, &m); err != nil {
1349+
t.Fatalf("Failed to unmarshal: %v", err)
1350+
}
1351+
if m["enableOnDemandInstructionDiscovery"] != false {
1352+
t.Errorf("Expected enableOnDemandInstructionDiscovery to be false, got %v", m["enableOnDemandInstructionDiscovery"])
1353+
}
1354+
})
1355+
1356+
t.Run("omits enableOnDemandInstructionDiscovery when not set", func(t *testing.T) {
1357+
req := createSessionRequest{}
1358+
data, _ := json.Marshal(req)
1359+
var m map[string]any
1360+
json.Unmarshal(data, &m)
1361+
if _, ok := m["enableOnDemandInstructionDiscovery"]; ok {
1362+
t.Error("Expected enableOnDemandInstructionDiscovery to be omitted when not set")
1363+
}
1364+
})
1365+
}
1366+
1367+
func TestResumeSessionRequest_EnableOnDemandInstructionDiscovery(t *testing.T) {
1368+
t.Run("forwards explicit true", func(t *testing.T) {
1369+
req := resumeSessionRequest{
1370+
SessionID: "s1",
1371+
EnableOnDemandInstructionDiscovery: Bool(true),
1372+
}
1373+
data, err := json.Marshal(req)
1374+
if err != nil {
1375+
t.Fatalf("Failed to marshal: %v", err)
1376+
}
1377+
var m map[string]any
1378+
if err := json.Unmarshal(data, &m); err != nil {
1379+
t.Fatalf("Failed to unmarshal: %v", err)
1380+
}
1381+
if m["enableOnDemandInstructionDiscovery"] != true {
1382+
t.Errorf("Expected enableOnDemandInstructionDiscovery to be true, got %v", m["enableOnDemandInstructionDiscovery"])
1383+
}
1384+
})
1385+
1386+
t.Run("preserves explicit false", func(t *testing.T) {
1387+
req := resumeSessionRequest{
1388+
SessionID: "s1",
1389+
EnableOnDemandInstructionDiscovery: Bool(false),
1390+
}
1391+
data, err := json.Marshal(req)
1392+
if err != nil {
1393+
t.Fatalf("Failed to marshal: %v", err)
1394+
}
1395+
var m map[string]any
1396+
if err := json.Unmarshal(data, &m); err != nil {
1397+
t.Fatalf("Failed to unmarshal: %v", err)
1398+
}
1399+
if m["enableOnDemandInstructionDiscovery"] != false {
1400+
t.Errorf("Expected enableOnDemandInstructionDiscovery to be false, got %v", m["enableOnDemandInstructionDiscovery"])
1401+
}
1402+
})
1403+
1404+
t.Run("omits enableOnDemandInstructionDiscovery when not set", func(t *testing.T) {
1405+
req := resumeSessionRequest{SessionID: "s1"}
1406+
data, _ := json.Marshal(req)
1407+
var m map[string]any
1408+
json.Unmarshal(data, &m)
1409+
if _, ok := m["enableOnDemandInstructionDiscovery"]; ok {
1410+
t.Error("Expected enableOnDemandInstructionDiscovery to be omitted when not set")
1411+
}
1412+
})
1413+
}
1414+
13211415
func TestCreateSessionResponse_Capabilities(t *testing.T) {
13221416
t.Run("reads capabilities from session.create response", func(t *testing.T) {
13231417
responseJSON := `{"sessionId":"s1","workspacePath":"/tmp","capabilities":{"ui":{"elicitation":true}}}`

go/internal/e2e/client_options_e2e_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -198,9 +198,10 @@ func TestClientOptionsE2E(t *testing.T) {
198198
}
199199

200200
session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
201-
EnableConfigDiscovery: true,
202-
IncludeSubAgentStreamingEvents: copilot.Bool(false),
203-
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
201+
EnableConfigDiscovery: true,
202+
EnableOnDemandInstructionDiscovery: copilot.Bool(true),
203+
IncludeSubAgentStreamingEvents: copilot.Bool(false),
204+
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
204205
})
205206
if err != nil {
206207
t.Fatalf("CreateSession failed: %v", err)
@@ -225,6 +226,9 @@ func TestClientOptionsE2E(t *testing.T) {
225226
if v, ok := params["enableConfigDiscovery"].(bool); !ok || v != true {
226227
t.Errorf("Expected session.create.params.enableConfigDiscovery=true, got %v", params["enableConfigDiscovery"])
227228
}
229+
if v, ok := params["enableOnDemandInstructionDiscovery"].(bool); !ok || v != true {
230+
t.Errorf("Expected session.create.params.enableOnDemandInstructionDiscovery=true, got %v", params["enableOnDemandInstructionDiscovery"])
231+
}
228232
if v, ok := params["includeSubAgentStreamingEvents"].(bool); !ok || v != false {
229233
t.Errorf("Expected session.create.params.includeSubAgentStreamingEvents=false, got %v", params["includeSubAgentStreamingEvents"])
230234
}

0 commit comments

Comments
 (0)