Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d2f5697
codegen: add notification flag to RpcMethod metadata
MackinnonBuck Jun 29, 2026
8561b06
nodejs: add GitHub telemetry redirection support
MackinnonBuck Jun 29, 2026
fc5638a
dotnet: add GitHub telemetry redirection support
MackinnonBuck Jun 29, 2026
ca4c483
python: add GitHub telemetry redirection support
MackinnonBuck Jun 29, 2026
276aa33
go: add GitHub telemetry redirection support
MackinnonBuck Jun 29, 2026
119899a
rust: add GitHub telemetry redirection support
MackinnonBuck Jun 29, 2026
5001268
java: add GitHub telemetry redirection support
MackinnonBuck Jun 29, 2026
907eea1
Merge origin/main into mackinnonbuck-sdk-github-telemetry-contract
MackinnonBuck Jun 29, 2026
d1fb55a
Fix telemetry codegen after schema bump
MackinnonBuck Jun 29, 2026
c810cbd
Rename telemetry redirection to forwarding across SDKs
MackinnonBuck Jun 30, 2026
6a24d89
Merge remote-tracking branch 'origin/main' into mackinnonbuck-sdk-git…
MackinnonBuck Jun 30, 2026
da54ca3
nodejs: align GitHub telemetry forwarding with cross-SDK contract
MackinnonBuck Jun 30, 2026
85726bc
python: address telemetry review nits
MackinnonBuck Jun 30, 2026
1c398a4
dotnet: observe expected exception in telemetry test teardown
MackinnonBuck Jun 30, 2026
3f91f81
Merge origin/main into mackinnonbuck-sdk-github-telemetry-contract
MackinnonBuck Jun 30, 2026
dcef40e
go: format telemetry forwarding fields
MackinnonBuck Jun 30, 2026
bb65bd8
codegen: remove temporary GitHub telemetry schema overlay
MackinnonBuck Jun 30, 2026
a377339
Merge remote-tracking branch 'origin/main' into mackinnonbuck-sdk-git…
Copilot Jul 1, 2026
7c49ebb
Reconcile Node/Python/Go telemetry glue with main's generated dispatch
Copilot Jul 1, 2026
279a0be
Repoint Java telemetry glue at generated types; finish Rust/.NET reco…
Copilot Jul 1, 2026
d7fac92
codegen: restore notification dispatch for gitHubTelemetry.event
MackinnonBuck Jul 1, 2026
fa3fccb
test: guard gitHubTelemetry.event notification-style dispatch
MackinnonBuck Jul 1, 2026
e7d173e
test: clarify telemetry forwarding comment in e2e
MackinnonBuck Jul 1, 2026
52099d1
test: use shared waitForCondition helper in telemetry e2e
MackinnonBuck Jul 1, 2026
b2a2567
test: add live gitHubTelemetry forwarding E2E for all languages
MackinnonBuck Jul 1, 2026
8c40c1d
fix: guard gitHubTelemetry callbacks in Python and .NET adapters
MackinnonBuck Jul 1, 2026
086b20d
test: normalize telemetry E2E line endings
MackinnonBuck Jul 1, 2026
d4781da
fix(java): log gitHubTelemetry callback errors at WARNING not SEVERE
MackinnonBuck Jul 1, 2026
3f67d2c
fix(python): log gitHubTelemetry callback errors at WARNING not ERROR
MackinnonBuck Jul 1, 2026
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
50 changes: 42 additions & 8 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.Extensions.Logging.Abstractions;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Net.Sockets;
using System.Runtime.ExceptionServices;
Expand Down Expand Up @@ -1037,7 +1038,8 @@
Providers: config.Providers,
Models: config.Models,
ToolFilterPrecedence: toolFilter.ToolFilterPrecedence,
ExpAssignments: config.ExpAssignments);
ExpAssignments: config.ExpAssignments,
EnableGitHubTelemetryForwarding: _options.OnGitHubTelemetry != null ? true : null);

var rpcTimestamp = Stopwatch.GetTimestamp();

Expand Down Expand Up @@ -1246,7 +1248,8 @@
Providers: config.Providers,
Models: config.Models,
ToolFilterPrecedence: toolFilter.ToolFilterPrecedence,
ExpAssignments: config.ExpAssignments);
ExpAssignments: config.ExpAssignments,
EnableGitHubTelemetryForwarding: _options.OnGitHubTelemetry != null ? true : null);

var rpcTimestamp = Stopwatch.GetTimestamp();
var response = await InvokeRpcAsync<ResumeSessionResponse>(
Expand Down Expand Up @@ -1724,21 +1727,24 @@
}

/// <summary>
/// Builds the client-global RPC handler bag at construction time. Currently
/// only the LLM inference provider adapter is registered; returns null when no
/// Builds the client-global RPC handler bag at construction time. Registers
/// the LLM inference provider adapter and/or the GitHub telemetry adapter
/// depending on which options are configured; returns null when no
/// client-global API is configured so the registration is skipped entirely.
/// </summary>
private ClientGlobalApiHandlers? BuildClientGlobalApis()
{
var handler = _options.RequestHandler;
if (handler is null)
var onGitHubTelemetry = _options.OnGitHubTelemetry;
if (handler is null && onGitHubTelemetry is null)
{
return null;
}

return new ClientGlobalApiHandlers
{
LlmInference = new LlmInferenceAdapter(handler, () => _serverRpc),
LlmInference = handler is null ? null : new LlmInferenceAdapter(handler, () => _serverRpc),
GitHubTelemetry = onGitHubTelemetry is null ? null : new GitHubTelemetryAdapter(onGitHubTelemetry, _logger),
};
}

Expand Down Expand Up @@ -2495,7 +2501,8 @@
IList<NamedProviderConfig>? Providers = null,
IList<ProviderModelConfig>? Models = null,
OptionsUpdateToolFilterPrecedence? ToolFilterPrecedence = null,
[property: JsonPropertyName("expAssignments")] JsonElement? ExpAssignments = null);
[property: JsonPropertyName("expAssignments")] JsonElement? ExpAssignments = null,
bool? EnableGitHubTelemetryForwarding = null);
#pragma warning restore GHCP001

internal record ToolDefinition(
Expand Down Expand Up @@ -2594,7 +2601,8 @@
IList<NamedProviderConfig>? Providers = null,
IList<ProviderModelConfig>? Models = null,
OptionsUpdateToolFilterPrecedence? ToolFilterPrecedence = null,
[property: JsonPropertyName("expAssignments")] JsonElement? ExpAssignments = null);
[property: JsonPropertyName("expAssignments")] JsonElement? ExpAssignments = null,
bool? EnableGitHubTelemetryForwarding = null);
#pragma warning restore GHCP001

internal record ResumeSessionResponse(
Expand Down Expand Up @@ -2713,3 +2721,29 @@
/// </summary>
public ToolResultObject Result => toolResult;
}

/// <summary>
/// Bridges the generated <see cref="Rpc.IGitHubTelemetryHandler"/> client-global handler to
/// the public <c>OnGitHubTelemetry</c> callback, forwarding the generated
/// <see cref="Rpc.GitHubTelemetryNotification"/> payload unchanged.
/// </summary>
[Experimental(Diagnostics.Experimental)]
internal sealed class GitHubTelemetryAdapter(Action<Rpc.GitHubTelemetryNotification> callback, ILogger logger) : Rpc.IGitHubTelemetryHandler
{
private readonly Action<Rpc.GitHubTelemetryNotification> _callback = callback ?? throw new ArgumentNullException(nameof(callback));
private readonly ILogger _logger = logger ?? NullLogger.Instance;

public Task EventAsync(Rpc.GitHubTelemetryNotification request, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
try
{
_callback(request);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Error handling gitHubTelemetry.event notification");
}

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.
Comment thread
MackinnonBuck marked this conversation as resolved.
return Task.CompletedTask;
}
}
9 changes: 9 additions & 0 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ private CopilotClientOptions(CopilotClientOptions? other)
OnListModels = other.OnListModels;
SessionFs = other.SessionFs;
RequestHandler = other.RequestHandler;
OnGitHubTelemetry = other.OnGitHubTelemetry;
SessionIdleTimeoutSeconds = other.SessionIdleTimeoutSeconds;
EnableRemoteSessions = other.EnableRemoteSessions;
Mode = other.Mode;
Expand Down Expand Up @@ -378,6 +379,14 @@ private CopilotClientOptions(CopilotClientOptions? other)
[Experimental(Diagnostics.Experimental)]
public CopilotRequestHandler? RequestHandler { get; set; }

/// <summary>
/// Experimental. Receives GitHub telemetry events the runtime forwards to this
/// connection; setting a handler opts created/resumed sessions into forwarding.
/// </summary>
[Experimental(Diagnostics.Experimental)]
[EditorBrowsable(EditorBrowsableState.Never)]
public Action<Rpc.GitHubTelemetryNotification>? OnGitHubTelemetry { get; set; }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Are other handlers sync or async? I'm wondering if this should be Func<..., Task>. I imagine someone might want to handle this by doing I/O to write it out somewhere.


/// <summary>
/// OpenTelemetry configuration for the runtime.
/// When set to a non-<see langword="null"/> instance, the runtime is started with OpenTelemetry instrumentation enabled.
Expand Down
59 changes: 59 additions & 0 deletions dotnet/test/E2E/GitHubTelemetryForwardingE2ETests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/

using System.Collections.Concurrent;
using GitHub.Copilot.Rpc;
using GitHub.Copilot.Test.Harness;
using Xunit;
using Xunit.Abstractions;

namespace GitHub.Copilot.Test.E2E;

#pragma warning disable GHCP001 // GitHub telemetry forwarding is experimental.

public class GitHubTelemetryForwardingE2ETests(E2ETestFixture fixture, ITestOutputHelper output)
: E2ETestBase(fixture, "github_telemetry", output)
{
[Fact]
public async Task Should_Forward_GitHub_Telemetry_For_A_Live_Session()
{
var notifications = new ConcurrentQueue<GitHubTelemetryNotification>();

await using var client = Ctx.CreateClient(options: new CopilotClientOptions
{
OnGitHubTelemetry = notifications.Enqueue,
});

CopilotSession? session = null;
try
{
session = await client.CreateSessionAsync(new SessionConfig
{
OnPermissionRequest = PermissionHandler.ApproveAll,
});

await TestHelper.WaitForConditionAsync(
() => !notifications.IsEmpty,
timeout: TimeSpan.FromSeconds(30),
timeoutMessage: "Timed out waiting for GitHub telemetry notification.");

Assert.True(notifications.TryPeek(out var notification));
Assert.NotEmpty(notification.SessionId);
Assert.NotNull(notification.Event);
Assert.NotEmpty(notification.Event.Kind);
Assert.IsType<bool>(notification.Restricted);
}
finally
{
if (session is not null)
{
await session.DisposeAsync();
}

await client.StopAsync();
}
}
}

#pragma warning restore GHCP001
Loading
Loading