Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ See [GitHub Releases](https://github.com/github/copilot-sdk/releases) for the fu

## [Unreleased]

### Feature: declare host identity with client info
### Feature: declare application identity with client info

Client options now accept optional client info (editor name and version, extension name and version) across all six SDKs, exposed idiomatically per language (`clientInfo` in Node.js, `client_info` in Python and Rust, `ClientInfo` in Go and .NET, `setClientInfo` in Java). When set, the SDK forwards it on the `server.connect` handshake so the telemetry the runtime emits on the connection is attributed to the host editor and its Copilot extension instead of the runtime's own build. All fields are optional, and leaving client info unset keeps the runtime's default attribution. See [Client info](./docs/features/client-info.md).
Client options now accept optional client info (application name and version, integration name and version) across all six SDKs, exposed idiomatically per language (`clientInfo` in Node.js, `client_info` in Python and Rust, `ClientInfo` in Go and .NET, `setClientInfo` in Java). When set, the SDK forwards it on the `server.connect` handshake so the telemetry the runtime emits on the connection is attributed to the application and its Copilot integration instead of the runtime's own build. All fields are optional, and leaving client info unset keeps the runtime's default attribution. See [Client info](./docs/features/client-info.md).

### Feature: Node Agent Factories pagination and run notifications

Expand Down
2 changes: 1 addition & 1 deletion docs/features/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ These guides cover the capabilities you can add to your Copilot SDK application.
| [Image Input](./image-input.md) | Send images to sessions as attachments |
| [Streaming Events](./streaming-events.md) | Subscribe to real-time session events (40+ event types) |
| [Usage and Billing](./usage-and-billing.md) | Read token counts, context-window utilization, AI credit cost, and account quota |
| [Client info](./client-info.md) | Declare the host editor and extension so runtime telemetry is attributed to your surface |
| [Client info](./client-info.md) | Declare application and integration identity for runtime telemetry attribution |
| [Steering & Queueing](./steering-and-queueing.md) | Control message delivery—immediate steering vs. sequential queueing |
| [Context Clearing](./context-management.md) | Replace conversation context safely with terminal tools |
| [Session Persistence](./session-persistence.md) | Resume sessions across restarts, manage session storage |
Expand Down
96 changes: 49 additions & 47 deletions docs/features/client-info.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
# Client info

Client info identifies the editor or extension that hosts your Copilot SDK application. Set the optional `clientInfo` client option to attribute runtime telemetry for that connection to your host instead of the runtime's own build.
Client info identifies the application using the Copilot SDK and, when applicable, a specific integration within it. An integration is an identifiable sub-part of the application through which the SDK is used, such as an extension or plugin. Set the optional `clientInfo` client option to attribute runtime telemetry for that connection to your application instead of the runtime's own build.

## When to set client info

Set client info when you embed the SDK in an editor, an extension, or another application with its own identity.
Set client info when your SDK application represents a distinct product, service, or integration whose runtime activity should be attributed consistently.

Leave client info unset for scripts, one-off tools, and back-end jobs that do not represent a distinct host. The runtime then keeps its default attribution.
Leave client info unset for scripts, one-off tools, and jobs that do not represent a distinct application. The runtime then keeps its default attribution.

Client info has four optional string fields. Set the fields you know and omit the rest. The SDK includes client info in the `server.connect` handshake only when at least one field has a non-empty value.

| Field | Example | Meaning |
|---|---|---|
| `editorName` | `"vscode"` | Name of the host editor |
| `editorVersion` | `"1.124.2"` | Version of the host editor |
| `extensionName` | `"copilot-chat"` | Name of the Copilot extension within the host |
| `extensionVersion` | `"0.54.0"` | Version of the Copilot extension within the host |
| `applicationName` | `"vscode"` | Name of the application using the SDK |
| `applicationVersion` | `"1.124.2"` | Version of the application using the SDK |
| `integrationName` | `"copilot-chat"` | Name of the extension, plugin, or other application sub-part using the SDK |
| `integrationVersion` | `"0.54.0"` | Version of that extension, plugin, or application sub-part |

For a standalone application without a distinct integration, set only the application fields. For example, a developer portal could set `applicationName` to `"acme-developer-portal"` and `applicationVersion` to `"2.4.0"`, leaving both integration fields unset.

The SDK sends client info once when it establishes the connection. The identity applies for the lifetime of that connection.

Expand All @@ -33,10 +35,10 @@ import { CopilotClient } from "@github/copilot-sdk";
async function main() {
const client = new CopilotClient({
clientInfo: {
editorName: "JetBrains-IU",
editorVersion: "2026.1",
extensionName: "copilot-intellij",
extensionVersion: "1.5.0",
applicationName: "vscode",
applicationVersion: "1.124.2",
integrationName: "copilot-chat",
integrationVersion: "0.54.0",
},
});

Expand All @@ -52,10 +54,10 @@ import { CopilotClient } from "@github/copilot-sdk";

const client = new CopilotClient({
clientInfo: {
editorName: "JetBrains-IU",
editorVersion: "2026.1",
extensionName: "copilot-intellij",
extensionVersion: "1.5.0",
applicationName: "vscode",
applicationVersion: "1.124.2",
integrationName: "copilot-chat",
integrationVersion: "0.54.0",
},
});

Expand All @@ -73,10 +75,10 @@ from copilot import CopilotClient

client = CopilotClient(
client_info={
"editor_name": "JetBrains-IU",
"editor_version": "2026.1",
"extension_name": "copilot-intellij",
"extension_version": "1.5.0",
"application_name": "vscode",
"application_version": "1.124.2",
"integration_name": "copilot-chat",
"integration_version": "0.54.0",
},
)
await client.start()
Expand All @@ -101,10 +103,10 @@ func main() {
ctx := context.Background()
client := copilot.NewClient(&copilot.ClientOptions{
ClientInfo: &copilot.ClientInfo{
EditorName: "JetBrains-IU",
EditorVersion: "2026.1",
ExtensionName: "copilot-intellij",
ExtensionVersion: "1.5.0",
ApplicationName: "vscode",
ApplicationVersion: "1.124.2",
IntegrationName: "copilot-chat",
IntegrationVersion: "0.54.0",
},
})
if err := client.Start(ctx); err != nil {
Expand All @@ -117,10 +119,10 @@ func main() {
```go
client := copilot.NewClient(&copilot.ClientOptions{
ClientInfo: &copilot.ClientInfo{
EditorName: "JetBrains-IU",
EditorVersion: "2026.1",
ExtensionName: "copilot-intellij",
ExtensionVersion: "1.5.0",
ApplicationName: "vscode",
ApplicationVersion: "1.124.2",
IntegrationName: "copilot-chat",
IntegrationVersion: "0.54.0",
},
})
if err := client.Start(ctx); err != nil {
Expand All @@ -140,10 +142,10 @@ await using var client = new CopilotClient(new CopilotClientOptions
{
ClientInfo = new CopilotClientInfo
{
EditorName = "JetBrains-IU",
EditorVersion = "2026.1",
ExtensionName = "copilot-intellij",
ExtensionVersion = "1.5.0",
ApplicationName = "vscode",
ApplicationVersion = "1.124.2",
IntegrationName = "copilot-chat",
IntegrationVersion = "0.54.0",
},
});

Expand All @@ -165,10 +167,10 @@ public class ClientInfoExample {
public static void main(String[] args) throws Exception {
var options = new CopilotClientOptions()
.setClientInfo(new ClientInfo()
.setEditorName("JetBrains-IU")
.setEditorVersion("2026.1")
.setExtensionName("copilot-intellij")
.setExtensionVersion("1.5.0"));
.setApplicationName("vscode")
.setApplicationVersion("1.124.2")
.setIntegrationName("copilot-chat")
.setIntegrationVersion("0.54.0"));

var client = new CopilotClient(options);
client.start().get();
Expand All @@ -180,10 +182,10 @@ public class ClientInfoExample {
```java
var options = new CopilotClientOptions()
.setClientInfo(new ClientInfo()
.setEditorName("JetBrains-IU")
.setEditorVersion("2026.1")
.setExtensionName("copilot-intellij")
.setExtensionVersion("1.5.0"));
.setApplicationName("vscode")
.setApplicationVersion("1.124.2")
.setIntegrationName("copilot-chat")
.setIntegrationVersion("0.54.0"));

var client = new CopilotClient(options);
client.start().get();
Expand All @@ -203,10 +205,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let _client = Client::start(
ClientOptions::new().with_client_info(
ClientInfo::new()
.with_editor_name("JetBrains-IU")
.with_editor_version("2026.1")
.with_extension_name("copilot-intellij")
.with_extension_version("1.5.0"),
.with_application_name("vscode")
.with_application_version("1.124.2")
.with_integration_name("copilot-chat")
.with_integration_version("0.54.0"),
),
)
.await?;
Expand All @@ -221,10 +223,10 @@ use github_copilot_sdk::{Client, ClientInfo, ClientOptions};
let client = Client::start(
ClientOptions::new().with_client_info(
ClientInfo::new()
.with_editor_name("JetBrains-IU")
.with_editor_version("2026.1")
.with_extension_name("copilot-intellij")
.with_extension_version("1.5.0"),
.with_application_name("vscode")
.with_application_version("1.124.2")
.with_integration_name("copilot-chat")
.with_integration_version("0.54.0"),
),
)
.await?;
Expand Down
10 changes: 5 additions & 5 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2183,7 +2183,7 @@ [new ConnectHandshakeRequest(
// `connect` handshake so the first session's un-replayable `session.start`
// event is forwarded). Also sent on session.create/resume for older CLIs.
_options.OnGitHubTelemetry != null ? true : null,
// Declare the integrating host's identity so the runtime attributes the
// Declare the integrating application's identity so the runtime attributes the
// telemetry it emits on this connection to a consistent surface instead
// of its own build. Null when the app didn't supply it.
ConnectHandshakeClientInfo.From(_options.ClientInfo))],
Expand Down Expand Up @@ -3213,10 +3213,10 @@ internal record ConnectHandshakeClientInfo(
return null;
}

var editorName = NullIfEmpty(info.EditorName);
var editorVersion = NullIfEmpty(info.EditorVersion);
var extensionName = NullIfEmpty(info.ExtensionName);
var extensionVersion = NullIfEmpty(info.ExtensionVersion);
var editorName = NullIfEmpty(info.ApplicationName);
var editorVersion = NullIfEmpty(info.ApplicationVersion);
var extensionName = NullIfEmpty(info.IntegrationName);
var extensionVersion = NullIfEmpty(info.IntegrationVersion);
if (editorName is null && editorVersion is null && extensionName is null && extensionVersion is null)
{
return null;
Expand Down
22 changes: 11 additions & 11 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -467,10 +467,10 @@ private CopilotClientOptions(CopilotClientOptions? other)
public bool EnableRemoteSessions { get; set; }

/// <summary>
/// Declares the integrating host's identity, forwarded to the runtime on the
/// Declares the integrating application's identity, forwarded to the runtime on the
/// <c>server.connect</c> handshake. Declaring it lets the telemetry the
/// runtime emits on this connection be attributed to a consistent surface
/// (the host editor and its Copilot extension) instead of the runtime's own
/// (the application and its Copilot integration) instead of the runtime's own
/// build. All fields are optional; leave it <see langword="null"/> to keep
/// the runtime's default attribution.
/// </summary>
Expand Down Expand Up @@ -543,7 +543,7 @@ public sealed class TelemetryConfig
}

/// <summary>
/// Identifies the integrating host on the <c>server.connect</c> handshake.
/// Identifies the integrating application on the <c>server.connect</c> handshake.
/// </summary>
/// <remarks>
/// Declaring it lets the telemetry the runtime emits on the connection be
Expand All @@ -554,24 +554,24 @@ public sealed class TelemetryConfig
public sealed class CopilotClientInfo
{
/// <summary>
/// Name of the host editor, e.g. <c>"vscode"</c>.
/// Name of the application using the SDK.
/// </summary>
public string? EditorName { get; set; }
public string? ApplicationName { get; set; }

/// <summary>
/// Version of the host editor, e.g. <c>"1.124.2"</c>.
/// Version of the application using the SDK.
/// </summary>
public string? EditorVersion { get; set; }
public string? ApplicationVersion { get; set; }

/// <summary>
/// Name of the Copilot extension within the host, e.g. <c>"copilot-chat"</c>.
/// Optionally specifies a named integration within the application, such as an extension or plugin.
/// </summary>
public string? ExtensionName { get; set; }
public string? IntegrationName { get; set; }

/// <summary>
/// Version of the Copilot extension within the host, e.g. <c>"0.54.0"</c>.
/// Optionally specifies the version of that integration.
/// </summary>
public string? ExtensionVersion { get; set; }
public string? IntegrationVersion { get; set; }
}

/// <summary>
Expand Down
8 changes: 4 additions & 4 deletions dotnet/test/Unit/CloneTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ public void CopilotClientOptions_Clone_CopiesAllProperties()
SessionIdleTimeoutSeconds = 600,
ClientInfo = new CopilotClientInfo
{
EditorName = "example-editor",
EditorVersion = "1.0.0",
ExtensionName = "example-extension",
ExtensionVersion = "2.0.0",
ApplicationName = "example-app",
ApplicationVersion = "1.0.0",
IntegrationName = "example-integration",
IntegrationVersion = "2.0.0",
},
};

Expand Down
26 changes: 13 additions & 13 deletions dotnet/test/Unit/GitHubTelemetryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,19 +149,19 @@ public async Task Connect_Forwards_Declared_ClientInfo()
Connection = RuntimeConnection.ForUri(server.Url),
ClientInfo = new CopilotClientInfo
{
EditorName = "JetBrains-IU",
EditorVersion = "2026.1",
ExtensionName = "copilot-intellij",
ExtensionVersion = "1.5.0",
ApplicationName = "acme-developer-portal",
ApplicationVersion = "2.4.0",
IntegrationName = "copilot-assistant",
IntegrationVersion = "1.5.0",
},
});
await client.StartAsync();

var connectParams = server.LastConnectParams ?? throw new InvalidOperationException("connect was not captured.");
Assert.True(connectParams.TryGetProperty("clientInfo", out var clientInfo));
Assert.Equal("JetBrains-IU", clientInfo.GetProperty("editorName").GetString());
Assert.Equal("2026.1", clientInfo.GetProperty("editorVersion").GetString());
Assert.Equal("copilot-intellij", clientInfo.GetProperty("extensionName").GetString());
Assert.Equal("acme-developer-portal", clientInfo.GetProperty("editorName").GetString());
Assert.Equal("2.4.0", clientInfo.GetProperty("editorVersion").GetString());
Assert.Equal("copilot-assistant", clientInfo.GetProperty("extensionName").GetString());
Assert.Equal("1.5.0", clientInfo.GetProperty("extensionVersion").GetString());
}

Expand All @@ -188,13 +188,13 @@ public async Task Connect_Omits_Empty_ClientInfo_Fields()
await using var client = new CopilotClient(new CopilotClientOptions
{
Connection = RuntimeConnection.ForUri(server.Url),
ClientInfo = new CopilotClientInfo { EditorName = "example-editor", EditorVersion = "" },
ClientInfo = new CopilotClientInfo { ApplicationName = "example-app", ApplicationVersion = "" },
});
await client.StartAsync();

var connectParams = server.LastConnectParams ?? throw new InvalidOperationException("connect was not captured.");
Assert.True(connectParams.TryGetProperty("clientInfo", out var clientInfo));
Assert.Equal("example-editor", clientInfo.GetProperty("editorName").GetString());
Assert.Equal("example-app", clientInfo.GetProperty("editorName").GetString());
Assert.False(clientInfo.TryGetProperty("editorVersion", out _));
Assert.False(clientInfo.TryGetProperty("extensionName", out _));
Assert.False(clientInfo.TryGetProperty("extensionVersion", out _));
Expand All @@ -209,10 +209,10 @@ public async Task Connect_Omits_All_Empty_ClientInfo()
Connection = RuntimeConnection.ForUri(server.Url),
ClientInfo = new CopilotClientInfo
{
EditorName = "",
EditorVersion = "",
ExtensionName = "",
ExtensionVersion = "",
ApplicationName = "",
ApplicationVersion = "",
IntegrationName = "",
IntegrationVersion = "",
},
});
await client.StartAsync();
Expand Down
18 changes: 9 additions & 9 deletions go/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,10 @@ func TestClient_ClientInfo(t *testing.T) {
client := NewClient(&ClientOptions{
Connection: URIConnection{URL: url},
ClientInfo: &ClientInfo{
EditorName: "JetBrains-IU",
EditorVersion: "2026.1",
ExtensionName: "copilot-intellij",
ExtensionVersion: "1.5.0",
ApplicationName: "acme-developer-portal",
ApplicationVersion: "2.4.0",
IntegrationName: "copilot-assistant",
IntegrationVersion: "1.5.0",
},
})
if err := client.Start(t.Context()); err != nil {
Expand All @@ -313,9 +313,9 @@ func TestClient_ClientInfo(t *testing.T) {

params := findConnect(requests())
want := map[string]any{
"editorName": "JetBrains-IU",
"editorVersion": "2026.1",
"extensionName": "copilot-intellij",
"editorName": "acme-developer-portal",
"editorVersion": "2.4.0",
"extensionName": "copilot-assistant",
"extensionVersion": "1.5.0",
}
if !reflect.DeepEqual(params["clientInfo"], want) {
Expand Down Expand Up @@ -344,14 +344,14 @@ func TestClient_ClientInfo(t *testing.T) {

client := NewClient(&ClientOptions{
Connection: URIConnection{URL: url},
ClientInfo: &ClientInfo{EditorName: "example-editor"},
ClientInfo: &ClientInfo{ApplicationName: "example-app"},
})
if err := client.Start(t.Context()); err != nil {
t.Fatalf("Start failed: %v", err)
}
defer client.ForceStop()

want := map[string]any{"editorName": "example-editor"}
want := map[string]any{"editorName": "example-app"}
if got := findConnect(requests())["clientInfo"]; !reflect.DeepEqual(got, want) {
t.Fatalf("clientInfo = %v, want %v", got, want)
}
Expand Down
Loading
Loading