Skip to content

Commit 2b3e342

Browse files
DavsterlCopilot
andcommitted
Add enableSessionTelemetry option to all SDKs
Adds an optional enableSessionTelemetry field to SessionConfig and ResumeSessionConfig across Node.js, Python, Go, and .NET SDKs. When omitted (default), session telemetry remains enabled — no extra parameter is needed. When set to false, internal session telemetry (Hydro/AppInsights) is disabled for that session. Includes unit tests for serialization, cloning, and wire forwarding in all four languages. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 45666cc commit 2b3e342

13 files changed

Lines changed: 269 additions & 0 deletions

File tree

dotnet/src/Client.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,7 @@ public async Task<CopilotSession> CreateSessionAsync(SessionConfig config, Cance
568568
config.AvailableTools,
569569
config.ExcludedTools,
570570
config.Provider,
571+
config.EnableSessionTelemetry,
571572
(bool?)true,
572573
config.OnUserInputRequest != null ? true : null,
573574
hasHooks ? true : null,
@@ -694,6 +695,7 @@ public async Task<CopilotSession> ResumeSessionAsync(string sessionId, ResumeSes
694695
config.AvailableTools,
695696
config.ExcludedTools,
696697
config.Provider,
698+
config.EnableSessionTelemetry,
697699
(bool?)true,
698700
config.OnUserInputRequest != null ? true : null,
699701
hasHooks ? true : null,
@@ -1781,6 +1783,7 @@ internal record CreateSessionRequest(
17811783
IList<string>? AvailableTools,
17821784
IList<string>? ExcludedTools,
17831785
ProviderConfig? Provider,
1786+
bool? EnableSessionTelemetry,
17841787
bool? RequestPermission,
17851788
bool? RequestUserInput,
17861789
bool? Hooks,
@@ -1837,6 +1840,7 @@ internal record ResumeSessionRequest(
18371840
IList<string>? AvailableTools,
18381841
IList<string>? ExcludedTools,
18391842
ProviderConfig? Provider,
1843+
bool? EnableSessionTelemetry,
18401844
bool? RequestPermission,
18411845
bool? RequestUserInput,
18421846
bool? Hooks,

dotnet/src/Types.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1849,6 +1849,7 @@ protected SessionConfig(SessionConfig? other)
18491849
OnPermissionRequest = other.OnPermissionRequest;
18501850
OnUserInputRequest = other.OnUserInputRequest;
18511851
Provider = other.Provider;
1852+
EnableSessionTelemetry = other.EnableSessionTelemetry;
18521853
ReasoningEffort = other.ReasoningEffort;
18531854
CreateSessionFsHandler = other.CreateSessionFsHandler;
18541855
GitHubToken = other.GitHubToken;
@@ -1930,6 +1931,11 @@ protected SessionConfig(SessionConfig? other)
19301931
/// </summary>
19311932
public ProviderConfig? Provider { get; set; }
19321933

1934+
/// <summary>
1935+
/// When false, disables internal session telemetry for this session.
1936+
/// </summary>
1937+
public bool? EnableSessionTelemetry { get; set; }
1938+
19331939
/// <summary>
19341940
/// Handler for permission requests from the server.
19351941
/// When provided, the server will call this handler to request permission for operations.
@@ -2114,6 +2120,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
21142120
OnPermissionRequest = other.OnPermissionRequest;
21152121
OnUserInputRequest = other.OnUserInputRequest;
21162122
Provider = other.Provider;
2123+
EnableSessionTelemetry = other.EnableSessionTelemetry;
21172124
ReasoningEffort = other.ReasoningEffort;
21182125
CreateSessionFsHandler = other.CreateSessionFsHandler;
21192126
GitHubToken = other.GitHubToken;
@@ -2164,6 +2171,11 @@ protected ResumeSessionConfig(ResumeSessionConfig? other)
21642171
/// </summary>
21652172
public ProviderConfig? Provider { get; set; }
21662173

2174+
/// <summary>
2175+
/// When false, disables internal session telemetry for this session.
2176+
/// </summary>
2177+
public bool? EnableSessionTelemetry { get; set; }
2178+
21672179
/// <summary>
21682180
/// Reasoning effort level for models that support it.
21692181
/// Valid values: "low", "medium", "high", "xhigh".

dotnet/test/Unit/CloneTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
9090
ExcludedTools = ["tool3"],
9191
WorkingDirectory = "/workspace",
9292
Streaming = true,
93+
EnableSessionTelemetry = false,
9394
IncludeSubAgentStreamingEvents = false,
9495
McpServers = new Dictionary<string, McpServerConfig> { ["server1"] = new McpStdioServerConfig { Command = "echo" } },
9596
CustomAgents = [new CustomAgentConfig { Name = "agent1" }],
@@ -111,6 +112,7 @@ public void SessionConfig_Clone_CopiesAllProperties()
111112
Assert.Equal(original.ExcludedTools, clone.ExcludedTools);
112113
Assert.Equal(original.WorkingDirectory, clone.WorkingDirectory);
113114
Assert.Equal(original.Streaming, clone.Streaming);
115+
Assert.Equal(original.EnableSessionTelemetry, clone.EnableSessionTelemetry);
114116
Assert.Equal(original.IncludeSubAgentStreamingEvents, clone.IncludeSubAgentStreamingEvents);
115117
Assert.Equal(original.McpServers.Count, clone.McpServers!.Count);
116118
Assert.Equal(original.CustomAgents.Count, clone.CustomAgents!.Count);
@@ -315,6 +317,19 @@ public void ResumeSessionConfig_Clone_PreservesIncludeSubAgentStreamingEventsDef
315317
Assert.True(clone.IncludeSubAgentStreamingEvents);
316318
}
317319

320+
[Fact]
321+
public void ResumeSessionConfig_Clone_CopiesEnableSessionTelemetry()
322+
{
323+
var original = new ResumeSessionConfig
324+
{
325+
EnableSessionTelemetry = false,
326+
};
327+
328+
var clone = original.Clone();
329+
330+
Assert.False(clone.EnableSessionTelemetry);
331+
}
332+
318333
[Fact]
319334
public void ResumeSessionConfig_Clone_CopiesContinuePendingWork()
320335
{
@@ -337,4 +352,24 @@ public void ResumeSessionConfig_Clone_PreservesContinuePendingWorkDefault()
337352

338353
Assert.Null(clone.ContinuePendingWork);
339354
}
355+
356+
[Fact]
357+
public void SessionConfig_Clone_PreservesEnableSessionTelemetryDefault()
358+
{
359+
var original = new SessionConfig();
360+
361+
var clone = original.Clone();
362+
363+
Assert.Null(clone.EnableSessionTelemetry);
364+
}
365+
366+
[Fact]
367+
public void ResumeSessionConfig_Clone_PreservesEnableSessionTelemetryDefault()
368+
{
369+
var original = new ResumeSessionConfig();
370+
371+
var clone = original.Clone();
372+
373+
Assert.Null(clone.EnableSessionTelemetry);
374+
}
340375
}

dotnet/test/Unit/SerializationTests.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,38 @@ public void ResumeSessionRequest_CanSerializeInstructionDirectories_WithSdkOptio
126126
Assert.Equal("C:\\resume-instructions", root.GetProperty("instructionDirectories")[0].GetString());
127127
}
128128

129+
[Fact]
130+
public void CreateSessionRequest_CanSerializeEnableSessionTelemetry_WithSdkOptions()
131+
{
132+
var options = GetSerializerOptions();
133+
var requestType = GetNestedType(typeof(CopilotClient), "CreateSessionRequest");
134+
var request = CreateInternalRequest(
135+
requestType,
136+
("SessionId", "session-id"),
137+
("EnableSessionTelemetry", false));
138+
139+
var json = JsonSerializer.Serialize(request, requestType, options);
140+
using var document = JsonDocument.Parse(json);
141+
var root = document.RootElement;
142+
Assert.False(root.GetProperty("enableSessionTelemetry").GetBoolean());
143+
}
144+
145+
[Fact]
146+
public void ResumeSessionRequest_CanSerializeEnableSessionTelemetry_WithSdkOptions()
147+
{
148+
var options = GetSerializerOptions();
149+
var requestType = GetNestedType(typeof(CopilotClient), "ResumeSessionRequest");
150+
var request = CreateInternalRequest(
151+
requestType,
152+
("SessionId", "session-id"),
153+
("EnableSessionTelemetry", false));
154+
155+
var json = JsonSerializer.Serialize(request, requestType, options);
156+
using var document = JsonDocument.Parse(json);
157+
var root = document.RootElement;
158+
Assert.False(root.GetProperty("enableSessionTelemetry").GetBoolean());
159+
}
160+
129161
[Fact]
130162
public void McpHttpServerConfig_CanSerializeOauthOptions_WithSdkOptions()
131163
{

go/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses
630630
req.AvailableTools = config.AvailableTools
631631
req.ExcludedTools = config.ExcludedTools
632632
req.Provider = config.Provider
633+
req.EnableSessionTelemetry = config.EnableSessionTelemetry
633634
req.ModelCapabilities = config.ModelCapabilities
634635
req.WorkingDirectory = config.WorkingDirectory
635636
req.MCPServers = config.MCPServers
@@ -789,6 +790,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string,
789790
req.SystemMessage = wireSystemMessage
790791
req.Tools = config.Tools
791792
req.Provider = config.Provider
793+
req.EnableSessionTelemetry = config.EnableSessionTelemetry
792794
req.ModelCapabilities = config.ModelCapabilities
793795
req.AvailableTools = config.AvailableTools
794796
req.ExcludedTools = config.ExcludedTools

go/client_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,35 @@ func TestResumeSessionRequest_ContinuePendingWork(t *testing.T) {
990990
})
991991
}
992992

993+
func TestCreateSessionRequest_EnableSessionTelemetry(t *testing.T) {
994+
t.Run("forwards enableSessionTelemetry when false", func(t *testing.T) {
995+
req := createSessionRequest{
996+
EnableSessionTelemetry: Bool(false),
997+
}
998+
data, err := json.Marshal(req)
999+
if err != nil {
1000+
t.Fatalf("Failed to marshal: %v", err)
1001+
}
1002+
var m map[string]any
1003+
if err := json.Unmarshal(data, &m); err != nil {
1004+
t.Fatalf("Failed to unmarshal: %v", err)
1005+
}
1006+
if m["enableSessionTelemetry"] != false {
1007+
t.Errorf("Expected enableSessionTelemetry to be false, got %v", m["enableSessionTelemetry"])
1008+
}
1009+
})
1010+
1011+
t.Run("omits enableSessionTelemetry when not set", func(t *testing.T) {
1012+
req := createSessionRequest{}
1013+
data, _ := json.Marshal(req)
1014+
var m map[string]any
1015+
json.Unmarshal(data, &m)
1016+
if _, ok := m["enableSessionTelemetry"]; ok {
1017+
t.Error("Expected enableSessionTelemetry to be omitted when not set")
1018+
}
1019+
})
1020+
}
1021+
9931022
func TestCreateSessionRequest_IncludeSubAgentStreamingEvents(t *testing.T) {
9941023
t.Run("defaults to true when nil", func(t *testing.T) {
9951024
req := createSessionRequest{
@@ -1026,6 +1055,36 @@ func TestCreateSessionRequest_IncludeSubAgentStreamingEvents(t *testing.T) {
10261055
})
10271056
}
10281057

1058+
func TestResumeSessionRequest_EnableSessionTelemetry(t *testing.T) {
1059+
t.Run("forwards enableSessionTelemetry when false", func(t *testing.T) {
1060+
req := resumeSessionRequest{
1061+
SessionID: "s1",
1062+
EnableSessionTelemetry: Bool(false),
1063+
}
1064+
data, err := json.Marshal(req)
1065+
if err != nil {
1066+
t.Fatalf("Failed to marshal: %v", err)
1067+
}
1068+
var m map[string]any
1069+
if err := json.Unmarshal(data, &m); err != nil {
1070+
t.Fatalf("Failed to unmarshal: %v", err)
1071+
}
1072+
if m["enableSessionTelemetry"] != false {
1073+
t.Errorf("Expected enableSessionTelemetry to be false, got %v", m["enableSessionTelemetry"])
1074+
}
1075+
})
1076+
1077+
t.Run("omits enableSessionTelemetry when not set", func(t *testing.T) {
1078+
req := resumeSessionRequest{SessionID: "s1"}
1079+
data, _ := json.Marshal(req)
1080+
var m map[string]any
1081+
json.Unmarshal(data, &m)
1082+
if _, ok := m["enableSessionTelemetry"]; ok {
1083+
t.Error("Expected enableSessionTelemetry to be omitted when not set")
1084+
}
1085+
})
1086+
}
1087+
10291088
func TestResumeSessionRequest_IncludeSubAgentStreamingEvents(t *testing.T) {
10301089
t.Run("defaults to true when nil", func(t *testing.T) {
10311090
req := resumeSessionRequest{

go/types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,8 @@ type SessionConfig struct {
570570
IncludeSubAgentStreamingEvents *bool
571571
// Provider configures a custom model provider (BYOK)
572572
Provider *ProviderConfig
573+
// When false, disables internal session telemetry for this session.
574+
EnableSessionTelemetry *bool
573575
// ModelCapabilities overrides individual model capabilities resolved by the runtime.
574576
// Only non-nil fields are applied over the runtime-resolved capabilities.
575577
ModelCapabilities *rpc.ModelCapabilitiesOverride
@@ -760,6 +762,8 @@ type ResumeSessionConfig struct {
760762
ExcludedTools []string
761763
// Provider configures a custom model provider
762764
Provider *ProviderConfig
765+
// When false, disables internal session telemetry for this session.
766+
EnableSessionTelemetry *bool
763767
// ModelCapabilities overrides individual model capabilities resolved by the runtime.
764768
// Only non-nil fields are applied over the runtime-resolved capabilities.
765769
ModelCapabilities *rpc.ModelCapabilitiesOverride
@@ -1039,6 +1043,7 @@ type createSessionRequest struct {
10391043
AvailableTools []string `json:"availableTools"`
10401044
ExcludedTools []string `json:"excludedTools,omitempty"`
10411045
Provider *ProviderConfig `json:"provider,omitempty"`
1046+
EnableSessionTelemetry *bool `json:"enableSessionTelemetry,omitempty"`
10421047
ModelCapabilities *rpc.ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"`
10431048
RequestPermission *bool `json:"requestPermission,omitempty"`
10441049
RequestUserInput *bool `json:"requestUserInput,omitempty"`
@@ -1088,6 +1093,7 @@ type resumeSessionRequest struct {
10881093
AvailableTools []string `json:"availableTools"`
10891094
ExcludedTools []string `json:"excludedTools,omitempty"`
10901095
Provider *ProviderConfig `json:"provider,omitempty"`
1096+
EnableSessionTelemetry *bool `json:"enableSessionTelemetry,omitempty"`
10911097
ModelCapabilities *rpc.ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"`
10921098
RequestPermission *bool `json:"requestPermission,omitempty"`
10931099
RequestUserInput *bool `json:"requestUserInput,omitempty"`

nodejs/src/client.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,7 @@ export class CopilotClient {
801801
availableTools: config.availableTools,
802802
excludedTools: config.excludedTools,
803803
provider: config.provider ? toWireProviderConfig(config.provider) : undefined,
804+
enableSessionTelemetry: config.enableSessionTelemetry,
804805
modelCapabilities: config.modelCapabilities,
805806
requestPermission: true,
806807
requestUserInput: !!config.onUserInputRequest,
@@ -932,6 +933,7 @@ export class CopilotClient {
932933
systemMessage: wireSystemMessage,
933934
availableTools: config.availableTools,
934935
excludedTools: config.excludedTools,
936+
enableSessionTelemetry: config.enableSessionTelemetry,
935937
tools: config.tools?.map((tool) => ({
936938
name: tool.name,
937939
description: tool.description,

nodejs/src/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,6 +1273,15 @@ export interface SessionConfig {
12731273
*/
12741274
provider?: ProviderConfig;
12751275

1276+
/**
1277+
* When false, disables internal session telemetry (Hydro/AppInsights) for this session.
1278+
* This is independent of the OpenTelemetry configuration in {@link CopilotClientOptions.telemetry}.
1279+
* When a custom provider (BYOK) is configured, telemetry is always disabled
1280+
* regardless of this flag.
1281+
* By default, telemetry remains enabled when this field is omitted.
1282+
*/
1283+
enableSessionTelemetry?: boolean;
1284+
12761285
/**
12771286
* Handler for permission requests from the server.
12781287
* When provided, the server will call this handler to request permission for operations.
@@ -1415,6 +1424,7 @@ export type ResumeSessionConfig = Pick<
14151424
| "availableTools"
14161425
| "excludedTools"
14171426
| "provider"
1427+
| "enableSessionTelemetry"
14181428
| "modelCapabilities"
14191429
| "streaming"
14201430
| "includeSubAgentStreamingEvents"

nodejs/test/client.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,47 @@ describe("CopilotClient", () => {
9898
spy.mockRestore();
9999
});
100100

101+
it("forwards enableSessionTelemetry in session.create request", async () => {
102+
const client = new CopilotClient();
103+
await client.start();
104+
onTestFinished(() => client.forceStop());
105+
106+
const spy = vi.spyOn((client as any).connection!, "sendRequest");
107+
await client.createSession({
108+
enableSessionTelemetry: false,
109+
onPermissionRequest: approveAll,
110+
});
111+
112+
expect(spy).toHaveBeenCalledWith(
113+
"session.create",
114+
expect.objectContaining({ enableSessionTelemetry: false })
115+
);
116+
});
117+
118+
it("forwards enableSessionTelemetry in session.resume request", async () => {
119+
const client = new CopilotClient();
120+
await client.start();
121+
onTestFinished(() => client.forceStop());
122+
123+
const session = await client.createSession({ onPermissionRequest: approveAll });
124+
const spy = vi
125+
.spyOn((client as any).connection!, "sendRequest")
126+
.mockImplementation(async (method: string, params: any) => {
127+
if (method === "session.resume") return { sessionId: params.sessionId };
128+
throw new Error(`Unexpected method: ${method}`);
129+
});
130+
await client.resumeSession(session.sessionId, {
131+
enableSessionTelemetry: false,
132+
onPermissionRequest: approveAll,
133+
});
134+
135+
expect(spy).toHaveBeenCalledWith(
136+
"session.resume",
137+
expect.objectContaining({ enableSessionTelemetry: false, sessionId: session.sessionId })
138+
);
139+
spy.mockRestore();
140+
});
141+
101142
it("defaults includeSubAgentStreamingEvents to true in session.create when not specified", async () => {
102143
const client = new CopilotClient();
103144
await client.start();

0 commit comments

Comments
 (0)