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: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ See [GitHub Releases](https://github.com/github/copilot-sdk/releases) for the fu

## [Unreleased]

### Feature: declare host 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).

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

The experimental Node.js Agent Factories convenience API now supports paginated run history. Existing `session.factory.listRuns()` calls still return the runs array, while calls with `afterSeq`, `beforeSeq`, or `limit` return the full page with cursor and truncation metadata.
Expand Down
1 change: 1 addition & 0 deletions docs/features/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +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 |
| [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
239 changes: 239 additions & 0 deletions docs/features/client-info.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# 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.

## 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.

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.

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 |

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

## Configure client info

Pass client info when you create the client:

<details open>
<summary><strong>TypeScript</strong></summary>

<!-- docs-validate: hidden -->
```typescript
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",
},
});

await client.start();
}

main();
```
<!-- /docs-validate: hidden -->

```typescript
import { CopilotClient } from "@github/copilot-sdk";

const client = new CopilotClient({
clientInfo: {
editorName: "JetBrains-IU",
editorVersion: "2026.1",
extensionName: "copilot-intellij",
extensionVersion: "1.5.0",
},
});

await client.start();
```

</details>

<details>
<summary><strong>Python</strong></summary>

<!-- docs-validate: wrap-async -->
```python
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",
},
)
await client.start()
```

</details>

<details>
<summary><strong>Go</strong></summary>

<!-- docs-validate: hidden -->
```go
package main

import (
"context"

copilot "github.com/github/copilot-sdk/go"
)

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",
},
})
if err := client.Start(ctx); err != nil {
return
}
}
```
<!-- /docs-validate: hidden -->

```go
client := copilot.NewClient(&copilot.ClientOptions{
ClientInfo: &copilot.ClientInfo{
EditorName: "JetBrains-IU",
EditorVersion: "2026.1",
ExtensionName: "copilot-intellij",
ExtensionVersion: "1.5.0",
},
})
if err := client.Start(ctx); err != nil {
return err
}
```

</details>

<details>
<summary><strong>.NET</strong></summary>

```csharp
using GitHub.Copilot;

await using var client = new CopilotClient(new CopilotClientOptions
{
ClientInfo = new CopilotClientInfo
{
EditorName = "JetBrains-IU",
EditorVersion = "2026.1",
ExtensionName = "copilot-intellij",
ExtensionVersion = "1.5.0",
},
});

await client.StartAsync();
```

</details>

<details>
<summary><strong>Java</strong></summary>

<!-- docs-validate: hidden -->
```java
import com.github.copilot.CopilotClient;
import com.github.copilot.rpc.ClientInfo;
import com.github.copilot.rpc.CopilotClientOptions;

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"));

var client = new CopilotClient(options);
client.start().get();
}
}
```
<!-- /docs-validate: hidden -->

```java
var options = new CopilotClientOptions()
.setClientInfo(new ClientInfo()
.setEditorName("JetBrains-IU")
.setEditorVersion("2026.1")
.setExtensionName("copilot-intellij")
.setExtensionVersion("1.5.0"));

var client = new CopilotClient(options);
client.start().get();
```

</details>

<details>
<summary><strong>Rust</strong></summary>

<!-- docs-validate: hidden -->
```rust
use github_copilot_sdk::{Client, ClientInfo, ClientOptions};

#[tokio::main]
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"),
),
)
.await?;
Ok(())
}
```
<!-- /docs-validate: hidden -->

```rust
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"),
),
)
.await?;
```

</details>

## Notes

* Client info is advisory. The runtime can ignore values that do not match the expected format, such as an invalid version string.
* Setting client info changes how the runtime attributes its telemetry. It does not change what the runtime records.
* If every field is unset or empty, the SDK omits client info from the handshake and the runtime keeps its default attribution.
44 changes: 42 additions & 2 deletions dotnet/src/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2182,7 +2182,11 @@ [new ConnectHandshakeRequest(
// handler is registered (mirrors the runtime, which reads this flag on the
// `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)],
_options.OnGitHubTelemetry != null ? true : null,
// Declare the integrating host'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))],
connection.StderrBuffer,
cancellationToken);
serverVersion = (int)connectResponse.ProtocolVersion;
Expand Down Expand Up @@ -3187,7 +3191,42 @@ internal record GetSessionMetadataResponse(

internal record ConnectHandshakeRequest(
string? Token,
[property: JsonPropertyName("enableGitHubTelemetryForwarding")] bool? EnableGitHubTelemetryForwarding = null);
[property: JsonPropertyName("enableGitHubTelemetryForwarding")] bool? EnableGitHubTelemetryForwarding = null,
[property: JsonPropertyName("clientInfo")] ConnectHandshakeClientInfo? ClientInfo = null);

internal record ConnectHandshakeClientInfo(
[property: JsonPropertyName("editorName")] string? EditorName = null,
[property: JsonPropertyName("editorVersion")] string? EditorVersion = null,
[property: JsonPropertyName("extensionName")] string? ExtensionName = null,
[property: JsonPropertyName("extensionVersion")] string? ExtensionVersion = null)
{
/// <summary>
/// Maps the public <see cref="CopilotClientInfo"/> onto the connect wire
/// shape, dropping empty fields. Returns <see langword="null"/> when no
/// identity was supplied so the handshake omits <c>clientInfo</c> and the
/// runtime keeps its default attribution.
/// </summary>
public static ConnectHandshakeClientInfo? From(CopilotClientInfo? info)
{
if (info is null)
{
return null;
}

var editorName = NullIfEmpty(info.EditorName);
var editorVersion = NullIfEmpty(info.EditorVersion);
var extensionName = NullIfEmpty(info.ExtensionName);
var extensionVersion = NullIfEmpty(info.ExtensionVersion);
if (editorName is null && editorVersion is null && extensionName is null && extensionVersion is null)
{
return null;
}

return new ConnectHandshakeClientInfo(editorName, editorVersion, extensionName, extensionVersion);
}

private static string? NullIfEmpty(string? value) => string.IsNullOrEmpty(value) ? null : value;
}

internal record BuiltinPluginDirectoriesRequest(
string[] Paths);
Expand Down Expand Up @@ -3227,6 +3266,7 @@ internal record HooksInvokeResponse(
[JsonSerializable(typeof(GetSessionMetadataRequest))]
[JsonSerializable(typeof(GetSessionMetadataResponse))]
[JsonSerializable(typeof(ConnectHandshakeRequest))]
[JsonSerializable(typeof(ConnectHandshakeClientInfo))]
[JsonSerializable(typeof(BuiltinPluginDirectoriesRequest))]
[JsonSerializable(typeof(McpOAuthTokenStorageMode))]
[JsonSerializable(typeof(EmbeddingCacheStorageMode))]
Expand Down
43 changes: 43 additions & 0 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ private CopilotClientOptions(CopilotClientOptions? other)
OnGitHubTelemetry = other.OnGitHubTelemetry;
SessionIdleTimeoutSeconds = other.SessionIdleTimeoutSeconds;
EnableRemoteSessions = other.EnableRemoteSessions;
ClientInfo = other.ClientInfo;
Mode = other.Mode;
}

Expand Down Expand Up @@ -465,6 +466,16 @@ private CopilotClientOptions(CopilotClientOptions? other)
/// </summary>
public bool EnableRemoteSessions { get; set; }

/// <summary>
/// Declares the integrating host'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
/// build. All fields are optional; leave it <see langword="null"/> to keep
/// the runtime's default attribution.
/// </summary>
public CopilotClientInfo? ClientInfo { get; set; }

/// <summary>
/// Creates a shallow clone of this <see cref="CopilotClientOptions"/> instance.
/// </summary>
Expand Down Expand Up @@ -531,6 +542,38 @@ public sealed class TelemetryConfig
public bool? CaptureContent { get; set; }
}

/// <summary>
/// Identifies the integrating host on the <c>server.connect</c> handshake.
/// </summary>
/// <remarks>
/// 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 properties are optional; an unset property is omitted from the
/// handshake.
/// </remarks>
public sealed class CopilotClientInfo
{
/// <summary>
/// Name of the host editor, e.g. <c>"vscode"</c>.
/// </summary>
public string? EditorName { get; set; }

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

/// <summary>
/// Name of the Copilot extension within the host, e.g. <c>"copilot-chat"</c>.
/// </summary>
public string? ExtensionName { get; set; }

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

/// <summary>
/// Configuration for a custom session filesystem provider.
/// </summary>
Expand Down
Loading
Loading