diff --git a/CHANGELOG.md b/CHANGELOG.md index 54e0c94d66..f7e753beda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docs/features/README.md b/docs/features/README.md index 6350958720..5ea070a5aa 100644 --- a/docs/features/README.md +++ b/docs/features/README.md @@ -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 | diff --git a/docs/features/client-info.md b/docs/features/client-info.md index 48ada2d13a..f72780e41b 100644 --- a/docs/features/client-info.md +++ b/docs/features/client-info.md @@ -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. @@ -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", }, }); @@ -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", }, }); @@ -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() @@ -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 { @@ -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 { @@ -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", }, }); @@ -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(); @@ -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(); @@ -203,10 +205,10 @@ async fn main() -> Result<(), Box> { 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?; @@ -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?; diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index a23032a905..92650ee60c 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -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))], @@ -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; diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index ec58000e97..3af7ea7f12 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -467,10 +467,10 @@ private CopilotClientOptions(CopilotClientOptions? other) public bool EnableRemoteSessions { get; set; } /// - /// Declares the integrating host's identity, forwarded to the runtime on the + /// Declares the integrating application's identity, forwarded to the runtime on the /// server.connect 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 to keep /// the runtime's default attribution. /// @@ -543,7 +543,7 @@ public sealed class TelemetryConfig } /// -/// Identifies the integrating host on the server.connect handshake. +/// Identifies the integrating application on the server.connect handshake. /// /// /// Declaring it lets the telemetry the runtime emits on the connection be @@ -554,24 +554,24 @@ public sealed class TelemetryConfig public sealed class CopilotClientInfo { /// - /// Name of the host editor, e.g. "vscode". + /// Name of the application using the SDK. /// - public string? EditorName { get; set; } + public string? ApplicationName { get; set; } /// - /// Version of the host editor, e.g. "1.124.2". + /// Version of the application using the SDK. /// - public string? EditorVersion { get; set; } + public string? ApplicationVersion { get; set; } /// - /// Name of the Copilot extension within the host, e.g. "copilot-chat". + /// Optionally specifies a named integration within the application, such as an extension or plugin. /// - public string? ExtensionName { get; set; } + public string? IntegrationName { get; set; } /// - /// Version of the Copilot extension within the host, e.g. "0.54.0". + /// Optionally specifies the version of that integration. /// - public string? ExtensionVersion { get; set; } + public string? IntegrationVersion { get; set; } } /// diff --git a/dotnet/test/Unit/CloneTests.cs b/dotnet/test/Unit/CloneTests.cs index 2d25d4761f..2f213525f6 100644 --- a/dotnet/test/Unit/CloneTests.cs +++ b/dotnet/test/Unit/CloneTests.cs @@ -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", }, }; diff --git a/dotnet/test/Unit/GitHubTelemetryTests.cs b/dotnet/test/Unit/GitHubTelemetryTests.cs index 708e599d9f..6d76af5742 100644 --- a/dotnet/test/Unit/GitHubTelemetryTests.cs +++ b/dotnet/test/Unit/GitHubTelemetryTests.cs @@ -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()); } @@ -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 _)); @@ -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(); diff --git a/go/client_test.go b/go/client_test.go index 99a21255dc..e74c234637 100644 --- a/go/client_test.go +++ b/go/client_test.go @@ -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 { @@ -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) { @@ -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) } diff --git a/go/types.go b/go/types.go index a1deebd1d7..772202a0bc 100644 --- a/go/types.go +++ b/go/types.go @@ -193,10 +193,10 @@ type ClientOptions struct { // directory are accessible from GitHub web and mobile. // Ignored when connecting to an existing runtime via [URIConnection]. EnableRemoteSessions bool - // ClientInfo declares the integrating host's identity, forwarded to the + // ClientInfo declares the integrating application's identity, forwarded to the // runtime on the `server.connect` 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 + // consistent surface (the application and its Copilot integration) instead of // the runtime's own build. All fields are optional; leave it nil to keep the // runtime's default attribution. ClientInfo *ClientInfo @@ -211,22 +211,21 @@ type ClientOptions struct { Mode ClientMode } -// ClientInfo identifies the integrating host on the `server.connect` handshake. +// ClientInfo identifies the integrating application on the `server.connect` handshake. // // Declaring it lets the telemetry the runtime emits on the connection be // attributed to a single, consistent surface instead of the runtime's own // build. All fields are optional; an empty field is omitted from the handshake. type ClientInfo struct { - // EditorName is the name of the host editor, e.g. "vscode". - EditorName string - // EditorVersion is the version of the host editor, e.g. "1.124.2". - EditorVersion string - // ExtensionName is the name of the Copilot extension within the host, e.g. - // "copilot-chat". - ExtensionName string - // ExtensionVersion is the version of the Copilot extension within the host, - // e.g. "0.54.0". - ExtensionVersion string + // ApplicationName is the name of the application using the SDK. + ApplicationName string + // ApplicationVersion is the version of the application using the SDK. + ApplicationVersion string + // IntegrationName optionally identifies a specific integration within the + // application, such as an extension or plugin. + IntegrationName string + // IntegrationVersion is the optional version of the named integration. + IntegrationVersion string } // toWire maps the public [ClientInfo] onto the generated connect wire shape, @@ -238,20 +237,20 @@ func (ci *ClientInfo) toWire() *rpc.ConnectClientInfo { } wire := &rpc.ConnectClientInfo{} populated := false - if ci.EditorName != "" { - wire.EditorName = &ci.EditorName + if ci.ApplicationName != "" { + wire.EditorName = &ci.ApplicationName populated = true } - if ci.EditorVersion != "" { - wire.EditorVersion = &ci.EditorVersion + if ci.ApplicationVersion != "" { + wire.EditorVersion = &ci.ApplicationVersion populated = true } - if ci.ExtensionName != "" { - wire.ExtensionName = &ci.ExtensionName + if ci.IntegrationName != "" { + wire.ExtensionName = &ci.IntegrationName populated = true } - if ci.ExtensionVersion != "" { - wire.ExtensionVersion = &ci.ExtensionVersion + if ci.IntegrationVersion != "" { + wire.ExtensionVersion = &ci.IntegrationVersion populated = true } if !populated { diff --git a/java/sdk/src/main/java/com/github/copilot/CopilotClient.java b/java/sdk/src/main/java/com/github/copilot/CopilotClient.java index 3818c82646..c986e829a4 100644 --- a/java/sdk/src/main/java/com/github/copilot/CopilotClient.java +++ b/java/sdk/src/main/java/com/github/copilot/CopilotClient.java @@ -642,7 +642,7 @@ private void verifyProtocolVersion(Connection connection) throws Exception { if (this.options.getOnGitHubTelemetry() != null) { connectParams.put("enableGitHubTelemetryForwarding", true); } - // 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. Omitted when the app didn't supply it (or supplied no fields). var clientInfo = this.options.getClientInfo(); diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/ClientInfo.java b/java/sdk/src/main/java/com/github/copilot/rpc/ClientInfo.java index ebb54db6db..ee9d97e1e9 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/ClientInfo.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/ClientInfo.java @@ -9,19 +9,19 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * Identity of the integrating host, declared on the {@code server.connect} - * handshake. + * Identity of the integrating application, declared on the + * {@code server.connect} handshake. *

* Declaring it lets the telemetry the runtime emits on the connection be - * attributed to a single, consistent surface (the host editor and its Copilot - * extension) instead of the runtime's own build. All fields are optional; an + * attributed to a single, consistent surface (the application and its Copilot + * integration) instead of the runtime's own build. All fields are optional; an * empty field is omitted from the handshake. * *

Example Usage

* *
{@code
- * var options = new CopilotClientOptions().setClientInfo(new ClientInfo().setEditorName("vscode")
- * 		.setEditorVersion("1.124.2").setExtensionName("copilot-chat").setExtensionVersion("0.54.0"));
+ * var options = new CopilotClientOptions().setClientInfo(new ClientInfo().setApplicationName("acme-developer-portal")
+ * 		.setApplicationVersion("2.4.0").setIntegrationName("copilot-assistant").setIntegrationVersion("1.5.0"));
  * }
* * @see CopilotClientOptions#setClientInfo(ClientInfo) @@ -30,99 +30,105 @@ @JsonInclude(JsonInclude.Include.NON_EMPTY) public class ClientInfo { - @JsonProperty("editorName") - private String editorName; + private String applicationName; - @JsonProperty("editorVersion") - private String editorVersion; + private String applicationVersion; - @JsonProperty("extensionName") - private String extensionName; + private String integrationName; - @JsonProperty("extensionVersion") - private String extensionVersion; + private String integrationVersion; /** - * Gets the name of the host editor. + * Gets the name of the application using the SDK. * - * @return the editor name (e.g., {@code "vscode"}), or {@code null} + * @return the application name, or {@code null} */ - public String getEditorName() { - return editorName; + @JsonProperty("editorName") + public String getApplicationName() { + return applicationName; } /** - * Sets the name of the host editor. + * Sets the name of the application using the SDK. * - * @param editorName - * the editor name (e.g., {@code "vscode"}) + * @param applicationName + * the application name * @return this client info for method chaining */ - public ClientInfo setEditorName(String editorName) { - this.editorName = editorName; + @JsonProperty("editorName") + public ClientInfo setApplicationName(String applicationName) { + this.applicationName = applicationName; return this; } /** - * Gets the version of the host editor. + * Gets the version of the application using the SDK. * - * @return the editor version (e.g., {@code "1.124.2"}), or {@code null} + * @return the application version, or {@code null} */ - public String getEditorVersion() { - return editorVersion; + @JsonProperty("editorVersion") + public String getApplicationVersion() { + return applicationVersion; } /** - * Sets the version of the host editor. + * Sets the version of the application using the SDK. * - * @param editorVersion - * the editor version (e.g., {@code "1.124.2"}) + * @param applicationVersion + * the application version * @return this client info for method chaining */ - public ClientInfo setEditorVersion(String editorVersion) { - this.editorVersion = editorVersion; + @JsonProperty("editorVersion") + public ClientInfo setApplicationVersion(String applicationVersion) { + this.applicationVersion = applicationVersion; return this; } /** - * Gets the name of the Copilot extension within the host. + * Gets the optional name of a specific integration within the application, such + * as an extension or plugin. * - * @return the extension name (e.g., {@code "copilot-chat"}), or {@code null} + * @return the integration name, or {@code null} */ - public String getExtensionName() { - return extensionName; + @JsonProperty("extensionName") + public String getIntegrationName() { + return integrationName; } /** - * Sets the name of the Copilot extension within the host. + * Sets the optional name of a specific integration within the application, such + * as an extension or plugin. * - * @param extensionName - * the extension name (e.g., {@code "copilot-chat"}) + * @param integrationName + * the integration name * @return this client info for method chaining */ - public ClientInfo setExtensionName(String extensionName) { - this.extensionName = extensionName; + @JsonProperty("extensionName") + public ClientInfo setIntegrationName(String integrationName) { + this.integrationName = integrationName; return this; } /** - * Gets the version of the Copilot extension within the host. + * Gets the optional version of the named integration. * - * @return the extension version (e.g., {@code "0.54.0"}), or {@code null} + * @return the integration version, or {@code null} */ - public String getExtensionVersion() { - return extensionVersion; + @JsonProperty("extensionVersion") + public String getIntegrationVersion() { + return integrationVersion; } /** - * Sets the version of the Copilot extension within the host. + * Sets the optional version of the named integration. * - * @param extensionVersion - * the extension version (e.g., {@code "0.54.0"}) + * @param integrationVersion + * the integration version * @return this client info for method chaining */ - public ClientInfo setExtensionVersion(String extensionVersion) { - this.extensionVersion = extensionVersion; + @JsonProperty("extensionVersion") + public ClientInfo setIntegrationVersion(String integrationVersion) { + this.integrationVersion = integrationVersion; return this; } @@ -135,7 +141,8 @@ public ClientInfo setExtensionVersion(String extensionVersion) { */ @JsonIgnore public boolean isEmpty() { - return isBlank(editorName) && isBlank(editorVersion) && isBlank(extensionName) && isBlank(extensionVersion); + return isBlank(applicationName) && isBlank(applicationVersion) && isBlank(integrationName) + && isBlank(integrationVersion); } private static boolean isBlank(String value) { diff --git a/java/sdk/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java b/java/sdk/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java index 3af76da52f..c67947bf48 100644 --- a/java/sdk/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java +++ b/java/sdk/src/main/java/com/github/copilot/rpc/CopilotClientOptions.java @@ -680,7 +680,7 @@ public CopilotClientOptions setTelemetry(TelemetryConfig telemetry) { } /** - * Gets the integrating host's declared identity. + * Gets the integrating application's declared identity. * * @return the client info, or {@code null} * @since 1.6.0 @@ -690,17 +690,17 @@ public ClientInfo getClientInfo() { } /** - * Declares the integrating host's identity, forwarded to the runtime on the - * {@code server.connect} handshake. + * Declares the integrating application's identity, forwarded to the runtime on + * the {@code server.connect} 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 build. All fields on + * attributed to a consistent surface (the application and its Copilot + * integration) instead of the runtime's own build. All fields on * {@link ClientInfo} are optional; leave this unset to keep the runtime's * default attribution. * * @param clientInfo - * the host identity to declare + * the application identity to declare * @return this options instance for method chaining * @since 1.6.0 */ diff --git a/java/sdk/src/test/java/com/github/copilot/GitHubTelemetryTest.java b/java/sdk/src/test/java/com/github/copilot/GitHubTelemetryTest.java index af83fc7803..5a6e378b2a 100644 --- a/java/sdk/src/test/java/com/github/copilot/GitHubTelemetryTest.java +++ b/java/sdk/src/test/java/com/github/copilot/GitHubTelemetryTest.java @@ -209,17 +209,18 @@ void clientOmitsForwardingWhenNoHandler() throws Exception { void connectForwardsDeclaredClientInfo() throws Exception { try (var server = new FakeRuntimeServer(); var client = new CopilotClient(new CopilotClientOptions().setCliUrl(server.url()) - .setClientInfo(new ClientInfo().setEditorName("JetBrains-IU").setEditorVersion("2026.1") - .setExtensionName("copilot-intellij").setExtensionVersion("1.5.0")))) { + .setClientInfo(new ClientInfo().setApplicationName("acme-developer-portal") + .setApplicationVersion("2.4.0").setIntegrationName("copilot-assistant") + .setIntegrationVersion("1.5.0")))) { client.start().get(15, TimeUnit.SECONDS); JsonNode connectParams = server.awaitConnect(); JsonNode clientInfo = connectParams.path("clientInfo"); assertEquals(4, clientInfo.size(), "clientInfo should carry only the four declared fields"); - assertEquals("JetBrains-IU", clientInfo.path("editorName").asText()); - assertEquals("2026.1", clientInfo.path("editorVersion").asText()); - assertEquals("copilot-intellij", clientInfo.path("extensionName").asText()); + assertEquals("acme-developer-portal", clientInfo.path("editorName").asText()); + assertEquals("2.4.0", clientInfo.path("editorVersion").asText()); + assertEquals("copilot-assistant", clientInfo.path("extensionName").asText()); assertEquals("1.5.0", clientInfo.path("extensionVersion").asText()); } } @@ -241,13 +242,13 @@ void connectOmitsClientInfoWhenUnset() throws Exception { void connectOmitsEmptyClientInfoFields() throws Exception { try (var server = new FakeRuntimeServer(); var client = new CopilotClient(new CopilotClientOptions().setCliUrl(server.url()) - .setClientInfo(new ClientInfo().setEditorName("example-editor")))) { + .setClientInfo(new ClientInfo().setApplicationName("example-app")))) { client.start().get(15, TimeUnit.SECONDS); JsonNode connectParams = server.awaitConnect(); JsonNode clientInfo = connectParams.path("clientInfo"); - assertEquals("example-editor", clientInfo.path("editorName").asText()); + assertEquals("example-app", clientInfo.path("editorName").asText()); assertFalse(clientInfo.has("editorVersion"), "unset editorVersion should be omitted"); assertFalse(clientInfo.has("extensionName"), "unset extensionName should be omitted"); assertFalse(clientInfo.has("extensionVersion"), "unset extensionVersion should be omitted"); @@ -258,14 +259,14 @@ void connectOmitsEmptyClientInfoFields() throws Exception { void connectDropsEmptyClientInfoFields() throws Exception { try (var server = new FakeRuntimeServer(); var client = new CopilotClient(new CopilotClientOptions().setCliUrl(server.url()) - .setClientInfo(new ClientInfo().setEditorName("vscode").setEditorVersion("")))) { + .setClientInfo(new ClientInfo().setApplicationName("example-app").setApplicationVersion("")))) { client.start().get(15, TimeUnit.SECONDS); JsonNode connectParams = server.awaitConnect(); JsonNode clientInfo = connectParams.path("clientInfo"); assertEquals(1, clientInfo.size(), "clientInfo should carry only the non-empty field"); - assertEquals("vscode", clientInfo.path("editorName").asText()); + assertEquals("example-app", clientInfo.path("editorName").asText()); assertFalse(clientInfo.has("editorVersion"), "empty editorVersion should be dropped"); } } @@ -274,8 +275,8 @@ void connectDropsEmptyClientInfoFields() throws Exception { void connectOmitsAllEmptyClientInfo() throws Exception { try (var server = new FakeRuntimeServer(); var client = new CopilotClient(new CopilotClientOptions().setCliUrl(server.url()) - .setClientInfo(new ClientInfo().setEditorName("").setEditorVersion("").setExtensionName("") - .setExtensionVersion("")))) { + .setClientInfo(new ClientInfo().setApplicationName("").setApplicationVersion("") + .setIntegrationName("").setIntegrationVersion("")))) { client.start().get(15, TimeUnit.SECONDS); @@ -286,7 +287,7 @@ void connectOmitsAllEmptyClientInfo() throws Exception { @Test void optionsRetainAndCloneClientInfo() { - var info = new ClientInfo().setEditorName("example-editor"); + var info = new ClientInfo().setApplicationName("example-app"); var options = new CopilotClientOptions().setClientInfo(info); assertSame(info, options.getClientInfo()); diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index eb8391e1dd..83b0a5f483 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -270,10 +270,10 @@ function toWireCustomAgents(agents: CustomAgentConfig[] | undefined): unknown[] function clientInfoToWire(info: CopilotClientInfo | undefined): ConnectClientInfo | undefined { if (info == null) return undefined; const wire: ConnectClientInfo = {}; - if (info.editorName) wire.editorName = info.editorName; - if (info.editorVersion) wire.editorVersion = info.editorVersion; - if (info.extensionName) wire.extensionName = info.extensionName; - if (info.extensionVersion) wire.extensionVersion = info.extensionVersion; + if (info.applicationName) wire.editorName = info.applicationName; + if (info.applicationVersion) wire.editorVersion = info.applicationVersion; + if (info.integrationName) wire.extensionName = info.integrationName; + if (info.integrationVersion) wire.extensionVersion = info.integrationVersion; return Object.keys(wire).length > 0 ? wire : undefined; } @@ -2247,7 +2247,7 @@ export class CopilotClient { if (this.onGitHubTelemetry != null) { connectParams.enableGitHubTelemetryForwarding = true; } - // Declare the integrating host's identity so the runtime attributes + // 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. Empty fields are dropped, and an // all-empty identity is omitted entirely. diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index efed11194a..128ced3d68 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -308,7 +308,7 @@ export type InternalRuntimeConnection = RuntimeConnection | ParentProcessRuntime export type CopilotClientMode = "empty" | "copilot-cli"; /** - * Identity of the integrating host, declared once on the `server.connect` + * Identity of the integrating application, declared once on the `server.connect` * handshake so the telemetry the runtime emits on this connection is attributed * to a single, consistent surface rather than to the runtime's own build. * @@ -318,24 +318,24 @@ export type CopilotClientMode = "empty" | "copilot-cli"; */ export interface CopilotClientInfo { /** - * Name of the host editor, e.g. `"vscode"`. + * Name of the application using the SDK, e.g. `"acme-developer-portal"`. */ - editorName?: string; + applicationName?: string; /** - * Version of the host editor, e.g. `"1.124.2"`. + * Version of the application using the SDK, e.g. `"2.4.0"`. */ - editorVersion?: string; + applicationVersion?: string; /** - * Name of the Copilot extension within the host, e.g. `"copilot-chat"`. + * Optional name of a specific integration within the application, such as an extension or plugin. */ - extensionName?: string; + integrationName?: string; /** - * Version of the Copilot extension within the host, e.g. `"0.54.0"`. + * Optional version of the integration identified by `integrationName`. */ - extensionVersion?: string; + integrationVersion?: string; } export interface CopilotClientOptions { @@ -510,11 +510,11 @@ export interface CopilotClientOptions { enableRemoteSessions?: boolean; /** - * Identity of the integrating host, forwarded to the runtime on the + * Identity of the integrating application, forwarded to the runtime on the * `server.connect` handshake. Declaring it lets the telemetry the runtime * emits on this connection be attributed to a single, consistent surface - * (e.g. the host editor and its Copilot extension) instead of the runtime's - * own build. All fields are optional; omit it to keep the default + * (e.g. the application and its Copilot integration) instead of the + * runtime's own build. All fields are optional; omit it to keep the default * attribution. */ clientInfo?: CopilotClientInfo; diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index cef9cbf490..bd1cf9812f 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -4272,15 +4272,20 @@ describe("connect handshake clientInfo", () => { it("forwards a declared client identity on the connect handshake", async () => { const 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", }; const params = await captureConnectParams({ clientInfo }); - expect(params.clientInfo).toEqual(clientInfo); + expect(params.clientInfo).toEqual({ + editorName: "acme-developer-portal", + editorVersion: "2.4.0", + extensionName: "copilot-assistant", + extensionVersion: "1.5.0", + }); }); it("omits clientInfo from the handshake when the host declares none", async () => { @@ -4292,28 +4297,28 @@ describe("connect handshake clientInfo", () => { it("drops empty fields and omits an all-empty identity", async () => { const allEmpty = await captureConnectParams({ clientInfo: { - editorName: "", - editorVersion: "", - extensionName: "", - extensionVersion: "", + applicationName: "", + applicationVersion: "", + integrationName: "", + integrationVersion: "", }, }); expect(allEmpty).not.toHaveProperty("clientInfo"); const partial = await captureConnectParams({ - clientInfo: { editorName: "vscode", editorVersion: "" }, + clientInfo: { applicationName: "example-app", applicationVersion: "" }, }); - expect(partial.clientInfo).toEqual({ editorName: "vscode" }); + expect(partial.clientInfo).toEqual({ editorName: "example-app" }); }); it("keeps telemetry forwarding alongside a declared identity", async () => { const params = await captureConnectParams({ - clientInfo: { editorName: "example-editor" }, + clientInfo: { applicationName: "example-app" }, onGitHubTelemetry: () => {}, }); expect(params).toMatchObject({ - clientInfo: { editorName: "example-editor" }, + clientInfo: { editorName: "example-app" }, enableGitHubTelemetryForwarding: true, }); }); diff --git a/python/copilot/client.py b/python/copilot/client.py index 4db29d1e78..929194c9b7 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -508,22 +508,22 @@ class TelemetryConfig(TypedDict, total=False): class ClientInfo(TypedDict, total=False): - """Identity of the integrating host, declared on the ``server.connect`` handshake. + """Identity of the integrating application, declared on ``server.connect``. Declaring it lets the telemetry the runtime emits on this connection be - attributed to a single, consistent surface (the host editor and its Copilot - extension) instead of the runtime's own build. All fields are optional; omit - any of them (or the whole object) to keep the runtime's default attribution. + attributed to a single, consistent surface (the application and its Copilot + integration) instead of the runtime's own build. All fields are optional; + omit any of them (or the whole object) to keep the default attribution. """ - editor_name: str - """Name of the host editor, e.g. ``"vscode"``.""" - editor_version: str - """Version of the host editor, e.g. ``"1.124.2"``.""" - extension_name: str - """Name of the Copilot extension within the host, e.g. ``"copilot-chat"``.""" - extension_version: str - """Version of the Copilot extension within the host, e.g. ``"0.54.0"``.""" + application_name: str + """Name of the application using the SDK, e.g. ``"acme-developer-portal"``.""" + application_version: str + """Version of the application using the SDK, e.g. ``"2.4.0"``.""" + integration_name: str + """Optional name of an application integration, such as an extension or plugin.""" + integration_version: str + """Optional version of the integration named by ``integration_name``.""" def _client_info_to_wire(client_info: ClientInfo | None) -> dict[str, str] | None: @@ -536,14 +536,14 @@ def _client_info_to_wire(client_info: ClientInfo | None) -> dict[str, str] | Non if not client_info: return None wire: dict[str, str] = {} - if client_info.get("editor_name"): - wire["editorName"] = client_info["editor_name"] - if client_info.get("editor_version"): - wire["editorVersion"] = client_info["editor_version"] - if client_info.get("extension_name"): - wire["extensionName"] = client_info["extension_name"] - if client_info.get("extension_version"): - wire["extensionVersion"] = client_info["extension_version"] + if client_info.get("application_name"): + wire["editorName"] = client_info["application_name"] + if client_info.get("application_version"): + wire["editorVersion"] = client_info["application_version"] + if client_info.get("integration_name"): + wire["extensionName"] = client_info["integration_name"] + if client_info.get("integration_version"): + wire["extensionVersion"] = client_info["integration_version"] return wire or None @@ -1621,7 +1621,7 @@ def __init__( Control integration). When ``True``, sessions in a GitHub repository working directory are accessible from GitHub web and mobile. - client_info: Identity of the integrating host, forwarded to the + client_info: Identity of the integrating application, forwarded to the runtime on the ``server.connect`` handshake. Declaring it lets the telemetry the runtime emits on this connection be attributed to a consistent surface instead of the runtime's own build. All @@ -4102,7 +4102,7 @@ async def _verify_protocol_version(self) -> None: # event is forwarded). Also sent on session.create/resume for older CLIs. if self._on_github_telemetry is not None: connect_params["enableGitHubTelemetryForwarding"] = True - # Declare the integrating host's identity so the runtime attributes + # 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. Omitted when the app didn't supply it. client_info = _client_info_to_wire(self._options.client_info) diff --git a/python/test_client.py b/python/test_client.py index 197103dd68..bf8de113ae 100644 --- a/python/test_client.py +++ b/python/test_client.py @@ -3156,10 +3156,10 @@ async def test_connect_forwards_client_info(self): client = CopilotClient( connection=RuntimeConnection.for_stdio(path=CLI_PATH), client_info={ - "editor_name": "JetBrains-IU", - "editor_version": "2026.1", - "extension_name": "copilot-intellij", - "extension_version": "1.5.0", + "application_name": "acme-developer-portal", + "application_version": "2.4.0", + "integration_name": "copilot-assistant", + "integration_version": "1.5.0", }, ) captured = {} @@ -3172,9 +3172,9 @@ async def request(self, method, params, **kwargs): client._client = _FakeClient() await client._verify_protocol_version() assert captured["connect"]["clientInfo"] == { - "editorName": "JetBrains-IU", - "editorVersion": "2026.1", - "extensionName": "copilot-intellij", + "editorName": "acme-developer-portal", + "editorVersion": "2.4.0", + "extensionName": "copilot-assistant", "extensionVersion": "1.5.0", } @@ -3196,7 +3196,7 @@ async def request(self, method, params, **kwargs): async def test_connect_forwards_partial_client_info_with_forwarding(self): client = CopilotClient( connection=RuntimeConnection.for_stdio(path=CLI_PATH), - client_info={"editor_name": "example-editor"}, + client_info={"application_name": "example-app"}, on_github_telemetry=lambda _notification: None, ) captured = {} @@ -3208,14 +3208,14 @@ async def request(self, method, params, **kwargs): client._client = _FakeClient() await client._verify_protocol_version() - assert captured["connect"]["clientInfo"] == {"editorName": "example-editor"} + assert captured["connect"]["clientInfo"] == {"editorName": "example-app"} assert captured["connect"]["enableGitHubTelemetryForwarding"] is True @pytest.mark.asyncio async def test_connect_drops_empty_client_info_fields(self): client = CopilotClient( connection=RuntimeConnection.for_stdio(path=CLI_PATH), - client_info={"editor_name": "vscode", "editor_version": ""}, + client_info={"application_name": "example-app", "application_version": ""}, ) captured = {} @@ -3226,17 +3226,17 @@ async def request(self, method, params, **kwargs): client._client = _FakeClient() await client._verify_protocol_version() - assert captured["connect"]["clientInfo"] == {"editorName": "vscode"} + assert captured["connect"]["clientInfo"] == {"editorName": "example-app"} @pytest.mark.asyncio async def test_connect_omits_all_empty_client_info(self): client = CopilotClient( connection=RuntimeConnection.for_stdio(path=CLI_PATH), client_info={ - "editor_name": "", - "editor_version": "", - "extension_name": "", - "extension_version": "", + "application_name": "", + "application_version": "", + "integration_name": "", + "integration_version": "", }, ) captured = {} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 9734417e20..fd7f12cf14 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -401,16 +401,16 @@ pub struct ClientOptions { /// (the default) or are stripped to a minimal/safe baseline. See /// [`ClientMode`] for the contract and trade-offs. pub mode: ClientMode, - /// Declares the integrating host's identity, forwarded to the runtime on + /// Declares the integrating application's identity, forwarded to the runtime on /// the `server.connect` 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 `None` to keep the runtime's /// default attribution. pub client_info: Option, } -/// Identity of the integrating host, declared on the `server.connect` +/// Identity of the integrating application, declared on the `server.connect` /// handshake. /// /// Declaring it lets the telemetry the runtime emits on the connection be @@ -424,14 +424,15 @@ pub struct ClientOptions { #[derive(Debug, Clone, Default, PartialEq, Eq)] #[non_exhaustive] pub struct ClientInfo { - /// Name of the host editor, e.g. `"vscode"`. - pub editor_name: Option, - /// Version of the host editor, e.g. `"1.124.2"`. - pub editor_version: Option, - /// Name of the Copilot extension within the host, e.g. `"copilot-chat"`. - pub extension_name: Option, - /// Version of the Copilot extension within the host, e.g. `"0.54.0"`. - pub extension_version: Option, + /// Name of the application using the SDK. + pub application_name: Option, + /// Version of the application using the SDK. + pub application_version: Option, + /// Optional name of a specific integration within the application, such as an + /// extension or plugin. + pub integration_name: Option, + /// Optional version of the integration identified by [`Self::integration_name`]. + pub integration_version: Option, } impl ClientInfo { @@ -441,27 +442,29 @@ impl ClientInfo { Self::default() } - /// Set the host editor name, e.g. `"vscode"`. - pub fn with_editor_name(mut self, editor_name: impl Into) -> Self { - self.editor_name = Some(editor_name.into()); + /// Set the name of the application using the SDK. + pub fn with_application_name(mut self, application_name: impl Into) -> Self { + self.application_name = Some(application_name.into()); self } - /// Set the host editor version, e.g. `"1.124.2"`. - pub fn with_editor_version(mut self, editor_version: impl Into) -> Self { - self.editor_version = Some(editor_version.into()); + /// Set the version of the application using the SDK. + pub fn with_application_version(mut self, application_version: impl Into) -> Self { + self.application_version = Some(application_version.into()); self } - /// Set the Copilot extension name within the host, e.g. `"copilot-chat"`. - pub fn with_extension_name(mut self, extension_name: impl Into) -> Self { - self.extension_name = Some(extension_name.into()); + /// Set the name of a specific integration within the application, such as an + /// extension or plugin. + pub fn with_integration_name(mut self, integration_name: impl Into) -> Self { + self.integration_name = Some(integration_name.into()); self } - /// Set the Copilot extension version within the host, e.g. `"0.54.0"`. - pub fn with_extension_version(mut self, extension_version: impl Into) -> Self { - self.extension_version = Some(extension_version.into()); + /// Set the version of the integration identified by + /// [`Self::with_integration_name`]. + pub fn with_integration_version(mut self, integration_version: impl Into) -> Self { + self.integration_version = Some(integration_version.into()); self } @@ -469,10 +472,10 @@ impl ClientInfo { /// SDK omits `clientInfo` from the handshake and the runtime keeps its /// default attribution. fn is_empty(&self) -> bool { - Self::non_empty(&self.editor_name).is_none() - && Self::non_empty(&self.editor_version).is_none() - && Self::non_empty(&self.extension_name).is_none() - && Self::non_empty(&self.extension_version).is_none() + Self::non_empty(&self.application_name).is_none() + && Self::non_empty(&self.application_version).is_none() + && Self::non_empty(&self.integration_name).is_none() + && Self::non_empty(&self.integration_version).is_none() } /// Clone the field only when it holds a non-empty string, so empty fields are @@ -488,10 +491,10 @@ impl ClientInfo { return None; } Some(crate::generated::api_types::ConnectClientInfo { - editor_name: Self::non_empty(&self.editor_name), - editor_version: Self::non_empty(&self.editor_version), - extension_name: Self::non_empty(&self.extension_name), - extension_version: Self::non_empty(&self.extension_version), + editor_name: Self::non_empty(&self.application_name), + editor_version: Self::non_empty(&self.application_version), + extension_name: Self::non_empty(&self.integration_name), + extension_version: Self::non_empty(&self.integration_version), }) } } @@ -1027,7 +1030,7 @@ impl ClientOptions { self } - /// Declare the integrating host's identity, forwarded to the runtime on + /// Declare the integrating application's identity, forwarded to the runtime on /// the `server.connect` handshake so its telemetry is attributed to a /// consistent surface. See [`Self::client_info`]. pub fn with_client_info(mut self, client_info: ClientInfo) -> Self { @@ -1203,7 +1206,7 @@ struct ClientInner { /// `None` for stdio and for external-server transport without an /// explicit token. effective_connection_token: Option, - /// Host identity forwarded on the `connect` handshake, set from + /// Application identity forwarded on the `connect` handshake, set from /// [`ClientOptions::client_info`]. `None` keeps the runtime's default /// telemetry attribution. client_info: Option, @@ -1822,7 +1825,7 @@ impl Client { /// Construct a [`Client`] from raw streams with a preset /// [`ClientInfo`], for integration testing the `connect` handshake's - /// host-identity forwarding path. + /// application-identity forwarding path. #[doc(hidden)] #[cfg(any(test, feature = "test-support"))] pub fn from_streams_with_client_info( @@ -2474,7 +2477,7 @@ impl Client { .on_github_telemetry .is_some() .then_some(true), - // Declare the integrating host's identity so the runtime attributes + // 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. `None` when the app didn't supply it, and // empty fields are dropped. diff --git a/rust/tests/protocol_version_test.rs b/rust/tests/protocol_version_test.rs index 33b6c8d155..cd8563f87b 100644 --- a/rust/tests/protocol_version_test.rs +++ b/rust/tests/protocol_version_test.rs @@ -240,10 +240,10 @@ async fn connect_handshake_forwards_auto_generated_token() { .unwrap(); } -/// Positive coverage for host-identity forwarding on the `connect` +/// Positive coverage for application-identity forwarding on the `connect` /// handshake. A client constructed with a [`ClientInfo`] MUST serialize it /// (camelCase) into the outbound `connect` request's `clientInfo` param so -/// the runtime attributes this connection's telemetry to the host surface. +/// the runtime attributes this connection's telemetry to the application. #[tokio::test] async fn connect_handshake_forwards_client_info() { let (client_write, server_read) = duplex(8192); @@ -254,10 +254,10 @@ async fn connect_handshake_forwards_client_info() { std::env::temp_dir(), Some( github_copilot_sdk::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("acme-developer-portal") + .with_application_version("2.4.0") + .with_integration_name("copilot-assistant") + .with_integration_version("1.5.0"), ), ) .unwrap(); @@ -273,9 +273,9 @@ async fn connect_handshake_forwards_client_info() { let req = read_framed(&mut server_read).await; assert_eq!(req["method"], "connect"); let client_info = &req["params"]["clientInfo"]; - assert_eq!(client_info["editorName"], "JetBrains-IU"); - assert_eq!(client_info["editorVersion"], "2026.1"); - assert_eq!(client_info["extensionName"], "copilot-intellij"); + assert_eq!(client_info["editorName"], "acme-developer-portal"); + assert_eq!(client_info["editorVersion"], "2.4.0"); + assert_eq!(client_info["extensionName"], "copilot-assistant"); assert_eq!(client_info["extensionVersion"], "1.5.0"); let response = serde_json::json!({ @@ -305,8 +305,8 @@ async fn connect_handshake_omits_empty_client_info_fields() { std::env::temp_dir(), Some( github_copilot_sdk::ClientInfo::new() - .with_editor_name("example-editor") - .with_editor_version(""), + .with_application_name("example-app") + .with_application_version(""), ), ) .unwrap(); @@ -322,7 +322,7 @@ async fn connect_handshake_omits_empty_client_info_fields() { let req = read_framed(&mut server_read).await; assert_eq!(req["method"], "connect"); let client_info = &req["params"]["clientInfo"]; - assert_eq!(client_info["editorName"], "example-editor"); + assert_eq!(client_info["editorName"], "example-app"); assert!(client_info.get("editorVersion").is_none()); assert!(client_info.get("extensionName").is_none()); assert!(client_info.get("extensionVersion").is_none()); @@ -353,10 +353,10 @@ async fn connect_handshake_omits_all_empty_client_info() { std::env::temp_dir(), Some( github_copilot_sdk::ClientInfo::new() - .with_editor_name("") - .with_editor_version("") - .with_extension_name("") - .with_extension_version(""), + .with_application_name("") + .with_application_version("") + .with_integration_name("") + .with_integration_version(""), ), ) .unwrap();